================================================== Worklog choice1 path probe generated_at=2026-04-30 19:16:54 root=/volume1/web/gpt ================================================== [1] 핵심 파일 존재 여부 EXISTS engine_modules/tools/WorklogTool.php EXISTS engine_modules/tools/worklog/WorklogPendingChoiceService.php EXISTS engine_modules/tools/worklog/WorklogChoicePendingStore.php MISSING engine_modules/tools/worklog/WorklogChoice1LiveWriter.php [2] WorklogChoice1LiveWriter 실제 참조 여부 cmd=grep -Rni --exclude-dir=.git --exclude-dir=repair_jobs "WorklogChoice1LiveWriter" '/volume1/web/gpt' | head -n 80 exit=0 /volume1/web/gpt/data/project_board_state.json:23: "WorklogChoice1LiveWriter.php missing 은 확인 필요. 실제 호출 여부는 grep 결과로 판단해야 함" /volume1/web/gpt/reports/chatgpt_handoff_latest.txt:27:- missing: engine_modules/tools/worklog/WorklogChoice1LiveWriter.php /volume1/web/gpt/tools/export_chatgpt_handoff.php:235: $root . '/engine_modules/tools/worklog/WorklogChoice1LiveWriter.php', /volume1/web/gpt/tools/probe_worklog_choice1_path.php:100: $root . '/engine_modules/tools/worklog/WorklogChoice1LiveWriter.php', /volume1/web/gpt/tools/probe_worklog_choice1_path.php:132:$report[] = "[2] WorklogChoice1LiveWriter 실제 참조 여부"; /volume1/web/gpt/tools/probe_worklog_choice1_path.php:133:$report[] = shell_capture('grep -Rni --exclude-dir=.git --exclude-dir=repair_jobs "WorklogChoice1LiveWriter" ' . escapeshellarg($root) . ' | head -n 80'); /volume1/web/gpt/tools/probe_worklog_choice1_path.php:176:$writerRef = trim(shell_capture('grep -Rni --exclude-dir=.git --exclude-dir=repair_jobs "WorklogChoice1LiveWriter" ' . escapeshellarg($root) . ' | head -n 5')); /volume1/web/gpt/tools/probe_worklog_choice1_path.php:177:if (str_contains($writerRef, 'exit=0') && !str_contains($writerRef, 'WorklogChoice1LiveWriter.php')) { /volume1/web/gpt/tools/probe_worklog_choice1_path.php:179:} elseif (str_contains($writerRef, 'WorklogChoice1LiveWriter')) { [3] choice1 / existing / pending 코드 주변 ## FILE /volume1/web/gpt/engine_modules/tools/WorklogTool.php ### HIT needle=existing lines=1-11 1 | gptWorklogDirectPendingServiceBridgeV2($params ?? []); 56 | if ($___gpt_pending_bridge_result instanceof ToolResult) { 57 | return $___gpt_pending_bridge_result; 58 | } 59 | } catch (\Throwable $___gpt_pending_bridge_error) { 60 | if (function_exists('error_log')) { ### HIT needle=pending lines=48-62 48 | } 49 | 50 | public function execute(array $params): ToolResult 51 | { 52 | 53 | // WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2_CALL 54 | try { 55 | $___gpt_pending_bridge_result = $this->gptWorklogDirectPendingServiceBridgeV2($params ?? []); 56 | if ($___gpt_pending_bridge_result instanceof ToolResult) { 57 | return $___gpt_pending_bridge_result; 58 | } 59 | } catch (\Throwable $___gpt_pending_bridge_error) { 60 | if (function_exists('error_log')) { 61 | error_log('[WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2] ' . $___gpt_pending_bridge_error->getMessage()); 62 | } ### HIT needle=pending lines=49-63 49 | 50 | public function execute(array $params): ToolResult 51 | { 52 | 53 | // WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2_CALL 54 | try { 55 | $___gpt_pending_bridge_result = $this->gptWorklogDirectPendingServiceBridgeV2($params ?? []); 56 | if ($___gpt_pending_bridge_result instanceof ToolResult) { 57 | return $___gpt_pending_bridge_result; 58 | } 59 | } catch (\Throwable $___gpt_pending_bridge_error) { 60 | if (function_exists('error_log')) { 61 | error_log('[WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2] ' . $___gpt_pending_bridge_error->getMessage()); 62 | } 63 | } ### HIT needle=pending lines=50-64 50 | public function execute(array $params): ToolResult 51 | { 52 | 53 | // WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2_CALL 54 | try { 55 | $___gpt_pending_bridge_result = $this->gptWorklogDirectPendingServiceBridgeV2($params ?? []); 56 | if ($___gpt_pending_bridge_result instanceof ToolResult) { 57 | return $___gpt_pending_bridge_result; 58 | } 59 | } catch (\Throwable $___gpt_pending_bridge_error) { 60 | if (function_exists('error_log')) { 61 | error_log('[WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2] ' . $___gpt_pending_bridge_error->getMessage()); 62 | } 63 | } 64 | ### HIT needle=pending lines=52-66 52 | 53 | // WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2_CALL 54 | try { 55 | $___gpt_pending_bridge_result = $this->gptWorklogDirectPendingServiceBridgeV2($params ?? []); 56 | if ($___gpt_pending_bridge_result instanceof ToolResult) { 57 | return $___gpt_pending_bridge_result; 58 | } 59 | } catch (\Throwable $___gpt_pending_bridge_error) { 60 | if (function_exists('error_log')) { 61 | error_log('[WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2] ' . $___gpt_pending_bridge_error->getMessage()); 62 | } 63 | } 64 | 65 | // WORKLOG_EXISTING_CHECKER_FUNCTION_GUARD_V3 66 | // 함수 내부 시작 지점에서 쓰기 의도 요청을 먼저 검사한다. ### HIT needle=pending lines=54-68 54 | try { 55 | $___gpt_pending_bridge_result = $this->gptWorklogDirectPendingServiceBridgeV2($params ?? []); 56 | if ($___gpt_pending_bridge_result instanceof ToolResult) { 57 | return $___gpt_pending_bridge_result; 58 | } 59 | } catch (\Throwable $___gpt_pending_bridge_error) { 60 | if (function_exists('error_log')) { 61 | error_log('[WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2] ' . $___gpt_pending_bridge_error->getMessage()); 62 | } 63 | } 64 | 65 | // WORKLOG_EXISTING_CHECKER_FUNCTION_GUARD_V3 66 | // 함수 내부 시작 지점에서 쓰기 의도 요청을 먼저 검사한다. 67 | // 기존 오늘 기록이 있으면 pending 선택지를 만들고 이후 직접 project_logs 저장을 막는다. 68 | try { ### HIT needle=existing lines=58-72 58 | } 59 | } catch (\Throwable $___gpt_pending_bridge_error) { 60 | if (function_exists('error_log')) { 61 | error_log('[WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2] ' . $___gpt_pending_bridge_error->getMessage()); 62 | } 63 | } 64 | 65 | // WORKLOG_EXISTING_CHECKER_FUNCTION_GUARD_V3 66 | // 함수 내부 시작 지점에서 쓰기 의도 요청을 먼저 검사한다. 67 | // 기존 오늘 기록이 있으면 pending 선택지를 만들고 이후 직접 project_logs 저장을 막는다. 68 | try { 69 | $___wl_guard_query = ''; 70 | 71 | if (isset($params) && is_array($params)) { 72 | foreach (['query', 'message', 'input', 'text', 'raw_query', 'user_query'] as $___wl_k) { ### HIT needle=pending lines=60-74 60 | if (function_exists('error_log')) { 61 | error_log('[WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2] ' . $___gpt_pending_bridge_error->getMessage()); 62 | } 63 | } 64 | 65 | // WORKLOG_EXISTING_CHECKER_FUNCTION_GUARD_V3 66 | // 함수 내부 시작 지점에서 쓰기 의도 요청을 먼저 검사한다. 67 | // 기존 오늘 기록이 있으면 pending 선택지를 만들고 이후 직접 project_logs 저장을 막는다. 68 | try { 69 | $___wl_guard_query = ''; 70 | 71 | if (isset($params) && is_array($params)) { 72 | foreach (['query', 'message', 'input', 'text', 'raw_query', 'user_query'] as $___wl_k) { 73 | if ($___wl_guard_query === '' && isset($params[$___wl_k]) && is_scalar($params[$___wl_k])) { 74 | $___wl_guard_query = trim((string)$params[$___wl_k]); ### HIT needle=existing lines=164-178 164 | FILE_APPEND 165 | ); 166 | } catch (Throwable $___wl_trace_error) { 167 | } 168 | 169 | 170 | if ($___wl_guard_is_write && !$___wl_guard_is_read) { 171 | $___wl_checker_file = __DIR__ . '/worklog/WorklogExistingTodayChecker.php'; 172 | $___wl_service_file = __DIR__ . '/worklog/WorklogPendingChoiceService.php'; 173 | 174 | if (is_file($___wl_checker_file)) { 175 | require_once $___wl_checker_file; 176 | } 177 | if (is_file($___wl_service_file)) { 178 | require_once $___wl_service_file; ### HIT needle=pending lines=165-179 165 | ); 166 | } catch (Throwable $___wl_trace_error) { 167 | } 168 | 169 | 170 | if ($___wl_guard_is_write && !$___wl_guard_is_read) { 171 | $___wl_checker_file = __DIR__ . '/worklog/WorklogExistingTodayChecker.php'; 172 | $___wl_service_file = __DIR__ . '/worklog/WorklogPendingChoiceService.php'; 173 | 174 | if (is_file($___wl_checker_file)) { 175 | require_once $___wl_checker_file; 176 | } 177 | if (is_file($___wl_service_file)) { 178 | require_once $___wl_service_file; 179 | } ### HIT needle=checker lines=167-181 167 | } 168 | 169 | 170 | if ($___wl_guard_is_write && !$___wl_guard_is_read) { 171 | $___wl_checker_file = __DIR__ . '/worklog/WorklogExistingTodayChecker.php'; 172 | $___wl_service_file = __DIR__ . '/worklog/WorklogPendingChoiceService.php'; 173 | 174 | if (is_file($___wl_checker_file)) { 175 | require_once $___wl_checker_file; 176 | } 177 | if (is_file($___wl_service_file)) { 178 | require_once $___wl_service_file; 179 | } 180 | 181 | if (class_exists('WorklogExistingTodayChecker') && class_exists('WorklogPendingChoiceService')) { ### HIT needle=checker lines=168-182 168 | 169 | 170 | if ($___wl_guard_is_write && !$___wl_guard_is_read) { 171 | $___wl_checker_file = __DIR__ . '/worklog/WorklogExistingTodayChecker.php'; 172 | $___wl_service_file = __DIR__ . '/worklog/WorklogPendingChoiceService.php'; 173 | 174 | if (is_file($___wl_checker_file)) { 175 | require_once $___wl_checker_file; 176 | } 177 | if (is_file($___wl_service_file)) { 178 | require_once $___wl_service_file; 179 | } 180 | 181 | if (class_exists('WorklogExistingTodayChecker') && class_exists('WorklogPendingChoiceService')) { 182 | $___wl_checker = new WorklogExistingTodayChecker(); ### HIT needle=existing lines=174-188 174 | if (is_file($___wl_checker_file)) { 175 | require_once $___wl_checker_file; 176 | } 177 | if (is_file($___wl_service_file)) { 178 | require_once $___wl_service_file; 179 | } 180 | 181 | if (class_exists('WorklogExistingTodayChecker') && class_exists('WorklogPendingChoiceService')) { 182 | $___wl_checker = new WorklogExistingTodayChecker(); 183 | $___wl_existing = null; 184 | 185 | foreach (['check', 'find', 'findExisting', 'findToday', 'findExistingToday', 'run'] as $___wl_method) { 186 | if (!method_exists($___wl_checker, $___wl_method)) { 187 | continue; 188 | } ### HIT needle=existing lines=175-189 175 | require_once $___wl_checker_file; 176 | } 177 | if (is_file($___wl_service_file)) { 178 | require_once $___wl_service_file; 179 | } 180 | 181 | if (class_exists('WorklogExistingTodayChecker') && class_exists('WorklogPendingChoiceService')) { 182 | $___wl_checker = new WorklogExistingTodayChecker(); 183 | $___wl_existing = null; 184 | 185 | foreach (['check', 'find', 'findExisting', 'findToday', 'findExistingToday', 'run'] as $___wl_method) { 186 | if (!method_exists($___wl_checker, $___wl_method)) { 187 | continue; 188 | } 189 | ### HIT needle=existing lines=176-190 176 | } 177 | if (is_file($___wl_service_file)) { 178 | require_once $___wl_service_file; 179 | } 180 | 181 | if (class_exists('WorklogExistingTodayChecker') && class_exists('WorklogPendingChoiceService')) { 182 | $___wl_checker = new WorklogExistingTodayChecker(); 183 | $___wl_existing = null; 184 | 185 | foreach (['check', 'find', 'findExisting', 'findToday', 'findExistingToday', 'run'] as $___wl_method) { 186 | if (!method_exists($___wl_checker, $___wl_method)) { 187 | continue; 188 | } 189 | 190 | try { ### HIT needle=existing lines=178-192 178 | require_once $___wl_service_file; 179 | } 180 | 181 | if (class_exists('WorklogExistingTodayChecker') && class_exists('WorklogPendingChoiceService')) { 182 | $___wl_checker = new WorklogExistingTodayChecker(); 183 | $___wl_existing = null; 184 | 185 | foreach (['check', 'find', 'findExisting', 'findToday', 'findExistingToday', 'run'] as $___wl_method) { 186 | if (!method_exists($___wl_checker, $___wl_method)) { 187 | continue; 188 | } 189 | 190 | try { 191 | $___wl_existing = $___wl_checker->{$___wl_method}($___wl_guard_query); 192 | break; ### HIT needle=checker lines=179-193 179 | } 180 | 181 | if (class_exists('WorklogExistingTodayChecker') && class_exists('WorklogPendingChoiceService')) { 182 | $___wl_checker = new WorklogExistingTodayChecker(); 183 | $___wl_existing = null; 184 | 185 | foreach (['check', 'find', 'findExisting', 'findToday', 'findExistingToday', 'run'] as $___wl_method) { 186 | if (!method_exists($___wl_checker, $___wl_method)) { 187 | continue; 188 | } 189 | 190 | try { 191 | $___wl_existing = $___wl_checker->{$___wl_method}($___wl_guard_query); 192 | break; 193 | } catch (Throwable $___wl_checker_error_1) { ### HIT needle=existing lines=184-198 184 | 185 | foreach (['check', 'find', 'findExisting', 'findToday', 'findExistingToday', 'run'] as $___wl_method) { 186 | if (!method_exists($___wl_checker, $___wl_method)) { 187 | continue; 188 | } 189 | 190 | try { 191 | $___wl_existing = $___wl_checker->{$___wl_method}($___wl_guard_query); 192 | break; 193 | } catch (Throwable $___wl_checker_error_1) { 194 | try { 195 | $___wl_existing = $___wl_checker->{$___wl_method}([ 196 | 'query' => $___wl_guard_query, 197 | 'params' => $params ?? [], 198 | ]); ### HIT needle=checker lines=186-200 186 | if (!method_exists($___wl_checker, $___wl_method)) { 187 | continue; 188 | } 189 | 190 | try { 191 | $___wl_existing = $___wl_checker->{$___wl_method}($___wl_guard_query); 192 | break; 193 | } catch (Throwable $___wl_checker_error_1) { 194 | try { 195 | $___wl_existing = $___wl_checker->{$___wl_method}([ 196 | 'query' => $___wl_guard_query, 197 | 'params' => $params ?? [], 198 | ]); 199 | break; 200 | } catch (Throwable $___wl_checker_error_2) { ### HIT needle=existing lines=188-202 188 | } 189 | 190 | try { 191 | $___wl_existing = $___wl_checker->{$___wl_method}($___wl_guard_query); 192 | break; 193 | } catch (Throwable $___wl_checker_error_1) { 194 | try { 195 | $___wl_existing = $___wl_checker->{$___wl_method}([ 196 | 'query' => $___wl_guard_query, 197 | 'params' => $params ?? [], 198 | ]); 199 | break; 200 | } catch (Throwable $___wl_checker_error_2) { 201 | continue; 202 | } ### HIT needle=checker lines=193-207 193 | } catch (Throwable $___wl_checker_error_1) { 194 | try { 195 | $___wl_existing = $___wl_checker->{$___wl_method}([ 196 | 'query' => $___wl_guard_query, 197 | 'params' => $params ?? [], 198 | ]); 199 | break; 200 | } catch (Throwable $___wl_checker_error_2) { 201 | continue; 202 | } 203 | } 204 | } 205 | 206 | $___wl_has_existing = false; 207 | ### HIT needle=existing lines=199-213 199 | break; 200 | } catch (Throwable $___wl_checker_error_2) { 201 | continue; 202 | } 203 | } 204 | } 205 | 206 | $___wl_has_existing = false; 207 | 208 | if (is_array($___wl_existing)) { 209 | $___wl_has_existing = 210 | $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 211 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 212 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 213 | || !empty($___wl_existing['rows']) ### HIT needle=existing lines=201-215 201 | continue; 202 | } 203 | } 204 | } 205 | 206 | $___wl_has_existing = false; 207 | 208 | if (is_array($___wl_existing)) { 209 | $___wl_has_existing = 210 | $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 211 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 212 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 213 | || !empty($___wl_existing['rows']) 214 | || !empty($___wl_existing['items']) 215 | || !empty($___wl_existing['existing']); ### HIT needle=existing lines=202-216 202 | } 203 | } 204 | } 205 | 206 | $___wl_has_existing = false; 207 | 208 | if (is_array($___wl_existing)) { 209 | $___wl_has_existing = 210 | $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 211 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 212 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 213 | || !empty($___wl_existing['rows']) 214 | || !empty($___wl_existing['items']) 215 | || !empty($___wl_existing['existing']); 216 | } elseif (is_object($___wl_existing)) { ### HIT needle=existing lines=203-217 203 | } 204 | } 205 | 206 | $___wl_has_existing = false; 207 | 208 | if (is_array($___wl_existing)) { 209 | $___wl_has_existing = 210 | $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 211 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 212 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 213 | || !empty($___wl_existing['rows']) 214 | || !empty($___wl_existing['items']) 215 | || !empty($___wl_existing['existing']); 216 | } elseif (is_object($___wl_existing)) { 217 | $___wl_has_existing = true; ### HIT needle=existing lines=204-218 204 | } 205 | 206 | $___wl_has_existing = false; 207 | 208 | if (is_array($___wl_existing)) { 209 | $___wl_has_existing = 210 | $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 211 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 212 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 213 | || !empty($___wl_existing['rows']) 214 | || !empty($___wl_existing['items']) 215 | || !empty($___wl_existing['existing']); 216 | } elseif (is_object($___wl_existing)) { 217 | $___wl_has_existing = true; 218 | } ### HIT needle=existing lines=205-219 205 | 206 | $___wl_has_existing = false; 207 | 208 | if (is_array($___wl_existing)) { 209 | $___wl_has_existing = 210 | $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 211 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 212 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 213 | || !empty($___wl_existing['rows']) 214 | || !empty($___wl_existing['items']) 215 | || !empty($___wl_existing['existing']); 216 | } elseif (is_object($___wl_existing)) { 217 | $___wl_has_existing = true; 218 | } 219 | ### HIT needle=existing lines=206-220 206 | $___wl_has_existing = false; 207 | 208 | if (is_array($___wl_existing)) { 209 | $___wl_has_existing = 210 | $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 211 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 212 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 213 | || !empty($___wl_existing['rows']) 214 | || !empty($___wl_existing['items']) 215 | || !empty($___wl_existing['existing']); 216 | } elseif (is_object($___wl_existing)) { 217 | $___wl_has_existing = true; 218 | } 219 | 220 | // WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1 ### HIT needle=existing lines=207-221 207 | 208 | if (is_array($___wl_existing)) { 209 | $___wl_has_existing = 210 | $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 211 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 212 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 213 | || !empty($___wl_existing['rows']) 214 | || !empty($___wl_existing['items']) 215 | || !empty($___wl_existing['existing']); 216 | } elseif (is_object($___wl_existing)) { 217 | $___wl_has_existing = true; 218 | } 219 | 220 | // WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1 221 | try { ### HIT needle=existing lines=208-222 208 | if (is_array($___wl_existing)) { 209 | $___wl_has_existing = 210 | $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 211 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 212 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 213 | || !empty($___wl_existing['rows']) 214 | || !empty($___wl_existing['items']) 215 | || !empty($___wl_existing['existing']); 216 | } elseif (is_object($___wl_existing)) { 217 | $___wl_has_existing = true; 218 | } 219 | 220 | // WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1 221 | try { 222 | $___wl_checker_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; ### HIT needle=existing lines=209-223 209 | $___wl_has_existing = 210 | $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 211 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 212 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 213 | || !empty($___wl_existing['rows']) 214 | || !empty($___wl_existing['items']) 215 | || !empty($___wl_existing['existing']); 216 | } elseif (is_object($___wl_existing)) { 217 | $___wl_has_existing = true; 218 | } 219 | 220 | // WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1 221 | try { 222 | $___wl_checker_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 223 | ### HIT needle=existing lines=210-224 210 | $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 211 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 212 | || $this->gptWorklogExistingCheckerMatchedV1($___wl_existing) 213 | || !empty($___wl_existing['rows']) 214 | || !empty($___wl_existing['items']) 215 | || !empty($___wl_existing['existing']); 216 | } elseif (is_object($___wl_existing)) { 217 | $___wl_has_existing = true; 218 | } 219 | 220 | // WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1 221 | try { 222 | $___wl_checker_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 223 | 224 | $___wl_existing_type = gettype($___wl_existing); ### HIT needle=return lines=213-227 213 | || !empty($___wl_existing['rows']) 214 | || !empty($___wl_existing['items']) 215 | || !empty($___wl_existing['existing']); 216 | } elseif (is_object($___wl_existing)) { 217 | $___wl_has_existing = true; 218 | } 219 | 220 | // WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1 221 | try { 222 | $___wl_checker_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 223 | 224 | $___wl_existing_type = gettype($___wl_existing); 225 | $___wl_existing_keys = []; 226 | $___wl_existing_brief = null; 227 | ### HIT needle=return lines=215-229 215 | || !empty($___wl_existing['existing']); 216 | } elseif (is_object($___wl_existing)) { 217 | $___wl_has_existing = true; 218 | } 219 | 220 | // WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1 221 | try { 222 | $___wl_checker_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 223 | 224 | $___wl_existing_type = gettype($___wl_existing); 225 | $___wl_existing_keys = []; 226 | $___wl_existing_brief = null; 227 | 228 | if (is_array($___wl_existing)) { 229 | $___wl_existing_keys = array_keys($___wl_existing); ### HIT needle=existing lines=217-231 217 | $___wl_has_existing = true; 218 | } 219 | 220 | // WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1 221 | try { 222 | $___wl_checker_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 223 | 224 | $___wl_existing_type = gettype($___wl_existing); 225 | $___wl_existing_keys = []; 226 | $___wl_existing_brief = null; 227 | 228 | if (is_array($___wl_existing)) { 229 | $___wl_existing_keys = array_keys($___wl_existing); 230 | $___wl_existing_brief = array_intersect_key( 231 | $___wl_existing, ### HIT needle=existing lines=218-232 218 | } 219 | 220 | // WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1 221 | try { 222 | $___wl_checker_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 223 | 224 | $___wl_existing_type = gettype($___wl_existing); 225 | $___wl_existing_keys = []; 226 | $___wl_existing_brief = null; 227 | 228 | if (is_array($___wl_existing)) { 229 | $___wl_existing_keys = array_keys($___wl_existing); 230 | $___wl_existing_brief = array_intersect_key( 231 | $___wl_existing, 232 | array_flip(['found','matched','exists','rows','items','existing','count','message','reason']) ### HIT needle=existing lines=219-233 219 | 220 | // WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1 221 | try { 222 | $___wl_checker_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 223 | 224 | $___wl_existing_type = gettype($___wl_existing); 225 | $___wl_existing_keys = []; 226 | $___wl_existing_brief = null; 227 | 228 | if (is_array($___wl_existing)) { 229 | $___wl_existing_keys = array_keys($___wl_existing); 230 | $___wl_existing_brief = array_intersect_key( 231 | $___wl_existing, 232 | array_flip(['found','matched','exists','rows','items','existing','count','message','reason']) 233 | ); ### HIT needle=existing lines=221-235 221 | try { 222 | $___wl_checker_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 223 | 224 | $___wl_existing_type = gettype($___wl_existing); 225 | $___wl_existing_keys = []; 226 | $___wl_existing_brief = null; 227 | 228 | if (is_array($___wl_existing)) { 229 | $___wl_existing_keys = array_keys($___wl_existing); 230 | $___wl_existing_brief = array_intersect_key( 231 | $___wl_existing, 232 | array_flip(['found','matched','exists','rows','items','existing','count','message','reason']) 233 | ); 234 | } elseif (is_object($___wl_existing)) { 235 | $___wl_existing_type = get_class($___wl_existing); ### HIT needle=existing lines=222-236 222 | $___wl_checker_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 223 | 224 | $___wl_existing_type = gettype($___wl_existing); 225 | $___wl_existing_keys = []; 226 | $___wl_existing_brief = null; 227 | 228 | if (is_array($___wl_existing)) { 229 | $___wl_existing_keys = array_keys($___wl_existing); 230 | $___wl_existing_brief = array_intersect_key( 231 | $___wl_existing, 232 | array_flip(['found','matched','exists','rows','items','existing','count','message','reason']) 233 | ); 234 | } elseif (is_object($___wl_existing)) { 235 | $___wl_existing_type = get_class($___wl_existing); 236 | } else { ### HIT needle=existing lines=223-237 223 | 224 | $___wl_existing_type = gettype($___wl_existing); 225 | $___wl_existing_keys = []; 226 | $___wl_existing_brief = null; 227 | 228 | if (is_array($___wl_existing)) { 229 | $___wl_existing_keys = array_keys($___wl_existing); 230 | $___wl_existing_brief = array_intersect_key( 231 | $___wl_existing, 232 | array_flip(['found','matched','exists','rows','items','existing','count','message','reason']) 233 | ); 234 | } elseif (is_object($___wl_existing)) { 235 | $___wl_existing_type = get_class($___wl_existing); 236 | } else { 237 | $___wl_existing_brief = $___wl_existing; ### HIT needle=existing lines=224-238 224 | $___wl_existing_type = gettype($___wl_existing); 225 | $___wl_existing_keys = []; 226 | $___wl_existing_brief = null; 227 | 228 | if (is_array($___wl_existing)) { 229 | $___wl_existing_keys = array_keys($___wl_existing); 230 | $___wl_existing_brief = array_intersect_key( 231 | $___wl_existing, 232 | array_flip(['found','matched','exists','rows','items','existing','count','message','reason']) 233 | ); 234 | } elseif (is_object($___wl_existing)) { 235 | $___wl_existing_type = get_class($___wl_existing); 236 | } else { 237 | $___wl_existing_brief = $___wl_existing; 238 | } ### HIT needle=existing lines=225-239 225 | $___wl_existing_keys = []; 226 | $___wl_existing_brief = null; 227 | 228 | if (is_array($___wl_existing)) { 229 | $___wl_existing_keys = array_keys($___wl_existing); 230 | $___wl_existing_brief = array_intersect_key( 231 | $___wl_existing, 232 | array_flip(['found','matched','exists','rows','items','existing','count','message','reason']) 233 | ); 234 | } elseif (is_object($___wl_existing)) { 235 | $___wl_existing_type = get_class($___wl_existing); 236 | } else { 237 | $___wl_existing_brief = $___wl_existing; 238 | } 239 | ### HIT needle=existing lines=227-241 227 | 228 | if (is_array($___wl_existing)) { 229 | $___wl_existing_keys = array_keys($___wl_existing); 230 | $___wl_existing_brief = array_intersect_key( 231 | $___wl_existing, 232 | array_flip(['found','matched','exists','rows','items','existing','count','message','reason']) 233 | ); 234 | } elseif (is_object($___wl_existing)) { 235 | $___wl_existing_type = get_class($___wl_existing); 236 | } else { 237 | $___wl_existing_brief = $___wl_existing; 238 | } 239 | 240 | @file_put_contents( 241 | $___wl_checker_trace_file, ### HIT needle=existing lines=228-242 228 | if (is_array($___wl_existing)) { 229 | $___wl_existing_keys = array_keys($___wl_existing); 230 | $___wl_existing_brief = array_intersect_key( 231 | $___wl_existing, 232 | array_flip(['found','matched','exists','rows','items','existing','count','message','reason']) 233 | ); 234 | } elseif (is_object($___wl_existing)) { 235 | $___wl_existing_type = get_class($___wl_existing); 236 | } else { 237 | $___wl_existing_brief = $___wl_existing; 238 | } 239 | 240 | @file_put_contents( 241 | $___wl_checker_trace_file, 242 | json_encode([ ### HIT needle=existing lines=230-244 230 | $___wl_existing_brief = array_intersect_key( 231 | $___wl_existing, 232 | array_flip(['found','matched','exists','rows','items','existing','count','message','reason']) 233 | ); 234 | } elseif (is_object($___wl_existing)) { 235 | $___wl_existing_type = get_class($___wl_existing); 236 | } else { 237 | $___wl_existing_brief = $___wl_existing; 238 | } 239 | 240 | @file_put_contents( 241 | $___wl_checker_trace_file, 242 | json_encode([ 243 | 'ts' => date('Y-m-d H:i:s'), 244 | 'phase' => 'checker_return', ### HIT needle=checker lines=234-248 234 | } elseif (is_object($___wl_existing)) { 235 | $___wl_existing_type = get_class($___wl_existing); 236 | } else { 237 | $___wl_existing_brief = $___wl_existing; 238 | } 239 | 240 | @file_put_contents( 241 | $___wl_checker_trace_file, 242 | json_encode([ 243 | 'ts' => date('Y-m-d H:i:s'), 244 | 'phase' => 'checker_return', 245 | 'marker' => 'WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1', 246 | 'query' => $___wl_guard_query, 247 | 'has_existing' => $___wl_has_existing, 248 | 'existing_type' => $___wl_existing_type, ### HIT needle=return lines=237-251 237 | $___wl_existing_brief = $___wl_existing; 238 | } 239 | 240 | @file_put_contents( 241 | $___wl_checker_trace_file, 242 | json_encode([ 243 | 'ts' => date('Y-m-d H:i:s'), 244 | 'phase' => 'checker_return', 245 | 'marker' => 'WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1', 246 | 'query' => $___wl_guard_query, 247 | 'has_existing' => $___wl_has_existing, 248 | 'existing_type' => $___wl_existing_type, 249 | 'existing_keys' => $___wl_existing_keys, 250 | 'existing_brief' => $___wl_existing_brief, 251 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, ### HIT needle=return lines=238-252 238 | } 239 | 240 | @file_put_contents( 241 | $___wl_checker_trace_file, 242 | json_encode([ 243 | 'ts' => date('Y-m-d H:i:s'), 244 | 'phase' => 'checker_return', 245 | 'marker' => 'WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1', 246 | 'query' => $___wl_guard_query, 247 | 'has_existing' => $___wl_has_existing, 248 | 'existing_type' => $___wl_existing_type, 249 | 'existing_keys' => $___wl_existing_keys, 250 | 'existing_brief' => $___wl_existing_brief, 251 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 252 | FILE_APPEND ### HIT needle=existing lines=240-254 240 | @file_put_contents( 241 | $___wl_checker_trace_file, 242 | json_encode([ 243 | 'ts' => date('Y-m-d H:i:s'), 244 | 'phase' => 'checker_return', 245 | 'marker' => 'WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1', 246 | 'query' => $___wl_guard_query, 247 | 'has_existing' => $___wl_has_existing, 248 | 'existing_type' => $___wl_existing_type, 249 | 'existing_keys' => $___wl_existing_keys, 250 | 'existing_brief' => $___wl_existing_brief, 251 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 252 | FILE_APPEND 253 | ); 254 | } catch (Throwable $___wl_checker_trace_error) { ### HIT needle=existing lines=241-255 241 | $___wl_checker_trace_file, 242 | json_encode([ 243 | 'ts' => date('Y-m-d H:i:s'), 244 | 'phase' => 'checker_return', 245 | 'marker' => 'WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1', 246 | 'query' => $___wl_guard_query, 247 | 'has_existing' => $___wl_has_existing, 248 | 'existing_type' => $___wl_existing_type, 249 | 'existing_keys' => $___wl_existing_keys, 250 | 'existing_brief' => $___wl_existing_brief, 251 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 252 | FILE_APPEND 253 | ); 254 | } catch (Throwable $___wl_checker_trace_error) { 255 | } ### HIT needle=existing lines=242-256 242 | json_encode([ 243 | 'ts' => date('Y-m-d H:i:s'), 244 | 'phase' => 'checker_return', 245 | 'marker' => 'WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1', 246 | 'query' => $___wl_guard_query, 247 | 'has_existing' => $___wl_has_existing, 248 | 'existing_type' => $___wl_existing_type, 249 | 'existing_keys' => $___wl_existing_keys, 250 | 'existing_brief' => $___wl_existing_brief, 251 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 252 | FILE_APPEND 253 | ); 254 | } catch (Throwable $___wl_checker_trace_error) { 255 | } 256 | ### HIT needle=existing lines=243-257 243 | 'ts' => date('Y-m-d H:i:s'), 244 | 'phase' => 'checker_return', 245 | 'marker' => 'WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1', 246 | 'query' => $___wl_guard_query, 247 | 'has_existing' => $___wl_has_existing, 248 | 'existing_type' => $___wl_existing_type, 249 | 'existing_keys' => $___wl_existing_keys, 250 | 'existing_brief' => $___wl_existing_brief, 251 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 252 | FILE_APPEND 253 | ); 254 | } catch (Throwable $___wl_checker_trace_error) { 255 | } 256 | 257 | if ($___wl_has_existing) { ### HIT needle=checker lines=247-261 247 | 'has_existing' => $___wl_has_existing, 248 | 'existing_type' => $___wl_existing_type, 249 | 'existing_keys' => $___wl_existing_keys, 250 | 'existing_brief' => $___wl_existing_brief, 251 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 252 | FILE_APPEND 253 | ); 254 | } catch (Throwable $___wl_checker_trace_error) { 255 | } 256 | 257 | if ($___wl_has_existing) { 258 | $___wl_pending_service = new WorklogPendingChoiceService(); 259 | $___wl_pending_result = null; 260 | 261 | $___wl_payload = [ ### HIT needle=existing lines=250-264 250 | 'existing_brief' => $___wl_existing_brief, 251 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 252 | FILE_APPEND 253 | ); 254 | } catch (Throwable $___wl_checker_trace_error) { 255 | } 256 | 257 | if ($___wl_has_existing) { 258 | $___wl_pending_service = new WorklogPendingChoiceService(); 259 | $___wl_pending_result = null; 260 | 261 | $___wl_payload = [ 262 | 'query' => $___wl_guard_query, 263 | 'original_query' => $___wl_guard_query, 264 | 'existing' => $___wl_existing, ### HIT needle=pending lines=251-265 251 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 252 | FILE_APPEND 253 | ); 254 | } catch (Throwable $___wl_checker_trace_error) { 255 | } 256 | 257 | if ($___wl_has_existing) { 258 | $___wl_pending_service = new WorklogPendingChoiceService(); 259 | $___wl_pending_result = null; 260 | 261 | $___wl_payload = [ 262 | 'query' => $___wl_guard_query, 263 | 'original_query' => $___wl_guard_query, 264 | 'existing' => $___wl_existing, 265 | 'params' => $params ?? [], ### HIT needle=pending lines=252-266 252 | FILE_APPEND 253 | ); 254 | } catch (Throwable $___wl_checker_trace_error) { 255 | } 256 | 257 | if ($___wl_has_existing) { 258 | $___wl_pending_service = new WorklogPendingChoiceService(); 259 | $___wl_pending_result = null; 260 | 261 | $___wl_payload = [ 262 | 'query' => $___wl_guard_query, 263 | 'original_query' => $___wl_guard_query, 264 | 'existing' => $___wl_existing, 265 | 'params' => $params ?? [], 266 | 'source' => 'function_guard_v3', ### HIT needle=existing lines=257-271 257 | if ($___wl_has_existing) { 258 | $___wl_pending_service = new WorklogPendingChoiceService(); 259 | $___wl_pending_result = null; 260 | 261 | $___wl_payload = [ 262 | 'query' => $___wl_guard_query, 263 | 'original_query' => $___wl_guard_query, 264 | 'existing' => $___wl_existing, 265 | 'params' => $params ?? [], 266 | 'source' => 'function_guard_v3', 267 | ]; 268 | 269 | foreach (['create', 'save', 'store', 'prepare', 'run'] as $___wl_pm) { 270 | if (!method_exists($___wl_pending_service, $___wl_pm)) { 271 | continue; ### HIT needle=save lines=262-276 262 | 'query' => $___wl_guard_query, 263 | 'original_query' => $___wl_guard_query, 264 | 'existing' => $___wl_existing, 265 | 'params' => $params ?? [], 266 | 'source' => 'function_guard_v3', 267 | ]; 268 | 269 | foreach (['create', 'save', 'store', 'prepare', 'run'] as $___wl_pm) { 270 | if (!method_exists($___wl_pending_service, $___wl_pm)) { 271 | continue; 272 | } 273 | 274 | try { 275 | $___wl_pending_result = $___wl_pending_service->{$___wl_pm}($___wl_payload); 276 | break; ### HIT needle=pending lines=263-277 263 | 'original_query' => $___wl_guard_query, 264 | 'existing' => $___wl_existing, 265 | 'params' => $params ?? [], 266 | 'source' => 'function_guard_v3', 267 | ]; 268 | 269 | foreach (['create', 'save', 'store', 'prepare', 'run'] as $___wl_pm) { 270 | if (!method_exists($___wl_pending_service, $___wl_pm)) { 271 | continue; 272 | } 273 | 274 | try { 275 | $___wl_pending_result = $___wl_pending_service->{$___wl_pm}($___wl_payload); 276 | break; 277 | } catch (Throwable $___wl_pending_error) { ### HIT needle=pending lines=268-282 268 | 269 | foreach (['create', 'save', 'store', 'prepare', 'run'] as $___wl_pm) { 270 | if (!method_exists($___wl_pending_service, $___wl_pm)) { 271 | continue; 272 | } 273 | 274 | try { 275 | $___wl_pending_result = $___wl_pending_service->{$___wl_pm}($___wl_payload); 276 | break; 277 | } catch (Throwable $___wl_pending_error) { 278 | continue; 279 | } 280 | } 281 | 282 | try { ### HIT needle=pending lines=270-284 270 | if (!method_exists($___wl_pending_service, $___wl_pm)) { 271 | continue; 272 | } 273 | 274 | try { 275 | $___wl_pending_result = $___wl_pending_service->{$___wl_pm}($___wl_payload); 276 | break; 277 | } catch (Throwable $___wl_pending_error) { 278 | continue; 279 | } 280 | } 281 | 282 | try { 283 | $___wl_pending_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 284 | ### HIT needle=pending lines=276-290 276 | break; 277 | } catch (Throwable $___wl_pending_error) { 278 | continue; 279 | } 280 | } 281 | 282 | try { 283 | $___wl_pending_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 284 | 285 | $___wl_pending_type = gettype($___wl_pending_result); 286 | $___wl_pending_keys = []; 287 | $___wl_pending_brief = null; 288 | 289 | if (is_array($___wl_pending_result)) { 290 | $___wl_pending_keys = array_keys($___wl_pending_result); ### HIT needle=pending lines=278-292 278 | continue; 279 | } 280 | } 281 | 282 | try { 283 | $___wl_pending_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 284 | 285 | $___wl_pending_type = gettype($___wl_pending_result); 286 | $___wl_pending_keys = []; 287 | $___wl_pending_brief = null; 288 | 289 | if (is_array($___wl_pending_result)) { 290 | $___wl_pending_keys = array_keys($___wl_pending_result); 291 | $___wl_pending_brief = array_intersect_key( 292 | $___wl_pending_result, ### HIT needle=pending lines=279-293 279 | } 280 | } 281 | 282 | try { 283 | $___wl_pending_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 284 | 285 | $___wl_pending_type = gettype($___wl_pending_result); 286 | $___wl_pending_keys = []; 287 | $___wl_pending_brief = null; 288 | 289 | if (is_array($___wl_pending_result)) { 290 | $___wl_pending_keys = array_keys($___wl_pending_result); 291 | $___wl_pending_brief = array_intersect_key( 292 | $___wl_pending_result, 293 | array_flip(['ok','success','pending_id','id','file','path','status','message','reason']) ### HIT needle=pending lines=280-294 280 | } 281 | 282 | try { 283 | $___wl_pending_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 284 | 285 | $___wl_pending_type = gettype($___wl_pending_result); 286 | $___wl_pending_keys = []; 287 | $___wl_pending_brief = null; 288 | 289 | if (is_array($___wl_pending_result)) { 290 | $___wl_pending_keys = array_keys($___wl_pending_result); 291 | $___wl_pending_brief = array_intersect_key( 292 | $___wl_pending_result, 293 | array_flip(['ok','success','pending_id','id','file','path','status','message','reason']) 294 | ); ### HIT needle=pending lines=282-296 282 | try { 283 | $___wl_pending_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 284 | 285 | $___wl_pending_type = gettype($___wl_pending_result); 286 | $___wl_pending_keys = []; 287 | $___wl_pending_brief = null; 288 | 289 | if (is_array($___wl_pending_result)) { 290 | $___wl_pending_keys = array_keys($___wl_pending_result); 291 | $___wl_pending_brief = array_intersect_key( 292 | $___wl_pending_result, 293 | array_flip(['ok','success','pending_id','id','file','path','status','message','reason']) 294 | ); 295 | } elseif (is_object($___wl_pending_result)) { 296 | $___wl_pending_type = get_class($___wl_pending_result); ### HIT needle=pending lines=283-297 283 | $___wl_pending_trace_file = dirname(__DIR__, 2) . '/repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl'; 284 | 285 | $___wl_pending_type = gettype($___wl_pending_result); 286 | $___wl_pending_keys = []; 287 | $___wl_pending_brief = null; 288 | 289 | if (is_array($___wl_pending_result)) { 290 | $___wl_pending_keys = array_keys($___wl_pending_result); 291 | $___wl_pending_brief = array_intersect_key( 292 | $___wl_pending_result, 293 | array_flip(['ok','success','pending_id','id','file','path','status','message','reason']) 294 | ); 295 | } elseif (is_object($___wl_pending_result)) { 296 | $___wl_pending_type = get_class($___wl_pending_result); 297 | } else { ### HIT needle=pending lines=284-298 284 | 285 | $___wl_pending_type = gettype($___wl_pending_result); 286 | $___wl_pending_keys = []; 287 | $___wl_pending_brief = null; 288 | 289 | if (is_array($___wl_pending_result)) { 290 | $___wl_pending_keys = array_keys($___wl_pending_result); 291 | $___wl_pending_brief = array_intersect_key( 292 | $___wl_pending_result, 293 | array_flip(['ok','success','pending_id','id','file','path','status','message','reason']) 294 | ); 295 | } elseif (is_object($___wl_pending_result)) { 296 | $___wl_pending_type = get_class($___wl_pending_result); 297 | } else { 298 | $___wl_pending_brief = $___wl_pending_result; ### HIT needle=pending lines=285-299 285 | $___wl_pending_type = gettype($___wl_pending_result); 286 | $___wl_pending_keys = []; 287 | $___wl_pending_brief = null; 288 | 289 | if (is_array($___wl_pending_result)) { 290 | $___wl_pending_keys = array_keys($___wl_pending_result); 291 | $___wl_pending_brief = array_intersect_key( 292 | $___wl_pending_result, 293 | array_flip(['ok','success','pending_id','id','file','path','status','message','reason']) 294 | ); 295 | } elseif (is_object($___wl_pending_result)) { 296 | $___wl_pending_type = get_class($___wl_pending_result); 297 | } else { 298 | $___wl_pending_brief = $___wl_pending_result; 299 | } ### HIT needle=pending lines=286-300 286 | $___wl_pending_keys = []; 287 | $___wl_pending_brief = null; 288 | 289 | if (is_array($___wl_pending_result)) { 290 | $___wl_pending_keys = array_keys($___wl_pending_result); 291 | $___wl_pending_brief = array_intersect_key( 292 | $___wl_pending_result, 293 | array_flip(['ok','success','pending_id','id','file','path','status','message','reason']) 294 | ); 295 | } elseif (is_object($___wl_pending_result)) { 296 | $___wl_pending_type = get_class($___wl_pending_result); 297 | } else { 298 | $___wl_pending_brief = $___wl_pending_result; 299 | } 300 | ### HIT needle=pending lines=288-302 288 | 289 | if (is_array($___wl_pending_result)) { 290 | $___wl_pending_keys = array_keys($___wl_pending_result); 291 | $___wl_pending_brief = array_intersect_key( 292 | $___wl_pending_result, 293 | array_flip(['ok','success','pending_id','id','file','path','status','message','reason']) 294 | ); 295 | } elseif (is_object($___wl_pending_result)) { 296 | $___wl_pending_type = get_class($___wl_pending_result); 297 | } else { 298 | $___wl_pending_brief = $___wl_pending_result; 299 | } 300 | 301 | @file_put_contents( 302 | $___wl_pending_trace_file, ### HIT needle=pending lines=289-303 289 | if (is_array($___wl_pending_result)) { 290 | $___wl_pending_keys = array_keys($___wl_pending_result); 291 | $___wl_pending_brief = array_intersect_key( 292 | $___wl_pending_result, 293 | array_flip(['ok','success','pending_id','id','file','path','status','message','reason']) 294 | ); 295 | } elseif (is_object($___wl_pending_result)) { 296 | $___wl_pending_type = get_class($___wl_pending_result); 297 | } else { 298 | $___wl_pending_brief = $___wl_pending_result; 299 | } 300 | 301 | @file_put_contents( 302 | $___wl_pending_trace_file, 303 | json_encode([ ### HIT needle=pending lines=291-305 291 | $___wl_pending_brief = array_intersect_key( 292 | $___wl_pending_result, 293 | array_flip(['ok','success','pending_id','id','file','path','status','message','reason']) 294 | ); 295 | } elseif (is_object($___wl_pending_result)) { 296 | $___wl_pending_type = get_class($___wl_pending_result); 297 | } else { 298 | $___wl_pending_brief = $___wl_pending_result; 299 | } 300 | 301 | @file_put_contents( 302 | $___wl_pending_trace_file, 303 | json_encode([ 304 | 'ts' => date('Y-m-d H:i:s'), 305 | 'phase' => 'pending_result', ### HIT needle=pending lines=295-309 295 | } elseif (is_object($___wl_pending_result)) { 296 | $___wl_pending_type = get_class($___wl_pending_result); 297 | } else { 298 | $___wl_pending_brief = $___wl_pending_result; 299 | } 300 | 301 | @file_put_contents( 302 | $___wl_pending_trace_file, 303 | json_encode([ 304 | 'ts' => date('Y-m-d H:i:s'), 305 | 'phase' => 'pending_result', 306 | 'marker' => 'WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1', 307 | 'query' => $___wl_guard_query, 308 | 'pending_type' => $___wl_pending_type, 309 | 'pending_keys' => $___wl_pending_keys, ### HIT needle=pending lines=298-312 298 | $___wl_pending_brief = $___wl_pending_result; 299 | } 300 | 301 | @file_put_contents( 302 | $___wl_pending_trace_file, 303 | json_encode([ 304 | 'ts' => date('Y-m-d H:i:s'), 305 | 'phase' => 'pending_result', 306 | 'marker' => 'WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1', 307 | 'query' => $___wl_guard_query, 308 | 'pending_type' => $___wl_pending_type, 309 | 'pending_keys' => $___wl_pending_keys, 310 | 'pending_brief' => $___wl_pending_brief, 311 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 312 | FILE_APPEND ### HIT needle=return lines=299-313 299 | } 300 | 301 | @file_put_contents( 302 | $___wl_pending_trace_file, 303 | json_encode([ 304 | 'ts' => date('Y-m-d H:i:s'), 305 | 'phase' => 'pending_result', 306 | 'marker' => 'WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1', 307 | 'query' => $___wl_guard_query, 308 | 'pending_type' => $___wl_pending_type, 309 | 'pending_keys' => $___wl_pending_keys, 310 | 'pending_brief' => $___wl_pending_brief, 311 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 312 | FILE_APPEND 313 | ); ### HIT needle=pending lines=301-315 301 | @file_put_contents( 302 | $___wl_pending_trace_file, 303 | json_encode([ 304 | 'ts' => date('Y-m-d H:i:s'), 305 | 'phase' => 'pending_result', 306 | 'marker' => 'WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1', 307 | 'query' => $___wl_guard_query, 308 | 'pending_type' => $___wl_pending_type, 309 | 'pending_keys' => $___wl_pending_keys, 310 | 'pending_brief' => $___wl_pending_brief, 311 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 312 | FILE_APPEND 313 | ); 314 | } catch (Throwable $___wl_pending_trace_error) { 315 | } ### HIT needle=pending lines=302-316 302 | $___wl_pending_trace_file, 303 | json_encode([ 304 | 'ts' => date('Y-m-d H:i:s'), 305 | 'phase' => 'pending_result', 306 | 'marker' => 'WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1', 307 | 'query' => $___wl_guard_query, 308 | 'pending_type' => $___wl_pending_type, 309 | 'pending_keys' => $___wl_pending_keys, 310 | 'pending_brief' => $___wl_pending_brief, 311 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 312 | FILE_APPEND 313 | ); 314 | } catch (Throwable $___wl_pending_trace_error) { 315 | } 316 | ### HIT needle=pending lines=303-317 303 | json_encode([ 304 | 'ts' => date('Y-m-d H:i:s'), 305 | 'phase' => 'pending_result', 306 | 'marker' => 'WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1', 307 | 'query' => $___wl_guard_query, 308 | 'pending_type' => $___wl_pending_type, 309 | 'pending_keys' => $___wl_pending_keys, 310 | 'pending_brief' => $___wl_pending_brief, 311 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 312 | FILE_APPEND 313 | ); 314 | } catch (Throwable $___wl_pending_trace_error) { 315 | } 316 | 317 | $___wl_msg = "⚠️ 오늘 같은 현장/고객의 작업기록이 이미 있습니다.\n\n"; ### HIT needle=pending lines=307-321 307 | 'query' => $___wl_guard_query, 308 | 'pending_type' => $___wl_pending_type, 309 | 'pending_keys' => $___wl_pending_keys, 310 | 'pending_brief' => $___wl_pending_brief, 311 | ], JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES) . PHP_EOL, 312 | FILE_APPEND 313 | ); 314 | } catch (Throwable $___wl_pending_trace_error) { 315 | } 316 | 317 | $___wl_msg = "⚠️ 오늘 같은 현장/고객의 작업기록이 이미 있습니다.\n\n"; 318 | $___wl_msg .= "1번: 기존 기록에 추가\n"; 319 | $___wl_msg .= "2번: 새 작업기록으로 저장\n"; 320 | $___wl_msg .= "3번: 저장 안 함\n"; 321 | ### HIT needle=return lines=316-330 316 | 317 | $___wl_msg = "⚠️ 오늘 같은 현장/고객의 작업기록이 이미 있습니다.\n\n"; 318 | $___wl_msg .= "1번: 기존 기록에 추가\n"; 319 | $___wl_msg .= "2번: 새 작업기록으로 저장\n"; 320 | $___wl_msg .= "3번: 저장 안 함\n"; 321 | 322 | if (class_exists('ToolResult') && method_exists('ToolResult', 'success')) { 323 | return ToolResult::success($___wl_msg, [ 324 | 'pending_saved' => true, 325 | 'pending_mode' => 'function_guard_v3', 326 | 'existing_today_found' => true, 327 | 'pending_result' => $___wl_pending_result, 328 | 'actions' => [], 329 | ]); 330 | } ### HIT needle=pending lines=317-331 317 | $___wl_msg = "⚠️ 오늘 같은 현장/고객의 작업기록이 이미 있습니다.\n\n"; 318 | $___wl_msg .= "1번: 기존 기록에 추가\n"; 319 | $___wl_msg .= "2번: 새 작업기록으로 저장\n"; 320 | $___wl_msg .= "3번: 저장 안 함\n"; 321 | 322 | if (class_exists('ToolResult') && method_exists('ToolResult', 'success')) { 323 | return ToolResult::success($___wl_msg, [ 324 | 'pending_saved' => true, 325 | 'pending_mode' => 'function_guard_v3', 326 | 'existing_today_found' => true, 327 | 'pending_result' => $___wl_pending_result, 328 | 'actions' => [], 329 | ]); 330 | } 331 | ### HIT needle=pending lines=318-332 318 | $___wl_msg .= "1번: 기존 기록에 추가\n"; 319 | $___wl_msg .= "2번: 새 작업기록으로 저장\n"; 320 | $___wl_msg .= "3번: 저장 안 함\n"; 321 | 322 | if (class_exists('ToolResult') && method_exists('ToolResult', 'success')) { 323 | return ToolResult::success($___wl_msg, [ 324 | 'pending_saved' => true, 325 | 'pending_mode' => 'function_guard_v3', 326 | 'existing_today_found' => true, 327 | 'pending_result' => $___wl_pending_result, 328 | 'actions' => [], 329 | ]); 330 | } 331 | 332 | return [ ### HIT needle=existing lines=319-333 319 | $___wl_msg .= "2번: 새 작업기록으로 저장\n"; 320 | $___wl_msg .= "3번: 저장 안 함\n"; 321 | 322 | if (class_exists('ToolResult') && method_exists('ToolResult', 'success')) { 323 | return ToolResult::success($___wl_msg, [ 324 | 'pending_saved' => true, 325 | 'pending_mode' => 'function_guard_v3', 326 | 'existing_today_found' => true, 327 | 'pending_result' => $___wl_pending_result, 328 | 'actions' => [], 329 | ]); 330 | } 331 | 332 | return [ 333 | 'ok' => true, ### HIT needle=pending lines=320-334 320 | $___wl_msg .= "3번: 저장 안 함\n"; 321 | 322 | if (class_exists('ToolResult') && method_exists('ToolResult', 'success')) { 323 | return ToolResult::success($___wl_msg, [ 324 | 'pending_saved' => true, 325 | 'pending_mode' => 'function_guard_v3', 326 | 'existing_today_found' => true, 327 | 'pending_result' => $___wl_pending_result, 328 | 'actions' => [], 329 | ]); 330 | } 331 | 332 | return [ 333 | 'ok' => true, 334 | 'message' => $___wl_msg, ### HIT needle=return lines=325-339 325 | 'pending_mode' => 'function_guard_v3', 326 | 'existing_today_found' => true, 327 | 'pending_result' => $___wl_pending_result, 328 | 'actions' => [], 329 | ]); 330 | } 331 | 332 | return [ 333 | 'ok' => true, 334 | 'message' => $___wl_msg, 335 | 'pending_saved' => true, 336 | 'pending_mode' => 'function_guard_v3', 337 | 'existing_today_found' => true, 338 | 'pending_result' => $___wl_pending_result, 339 | 'actions' => [], ### HIT needle=pending lines=328-342 328 | 'actions' => [], 329 | ]); 330 | } 331 | 332 | return [ 333 | 'ok' => true, 334 | 'message' => $___wl_msg, 335 | 'pending_saved' => true, 336 | 'pending_mode' => 'function_guard_v3', 337 | 'existing_today_found' => true, 338 | 'pending_result' => $___wl_pending_result, 339 | 'actions' => [], 340 | ]; 341 | } 342 | } ### HIT needle=pending lines=329-343 329 | ]); 330 | } 331 | 332 | return [ 333 | 'ok' => true, 334 | 'message' => $___wl_msg, 335 | 'pending_saved' => true, 336 | 'pending_mode' => 'function_guard_v3', 337 | 'existing_today_found' => true, 338 | 'pending_result' => $___wl_pending_result, 339 | 'actions' => [], 340 | ]; 341 | } 342 | } 343 | } ### HIT needle=existing lines=330-344 330 | } 331 | 332 | return [ 333 | 'ok' => true, 334 | 'message' => $___wl_msg, 335 | 'pending_saved' => true, 336 | 'pending_mode' => 'function_guard_v3', 337 | 'existing_today_found' => true, 338 | 'pending_result' => $___wl_pending_result, 339 | 'actions' => [], 340 | ]; 341 | } 342 | } 343 | } 344 | } catch (Throwable $___wl_guard_error) { ### HIT needle=pending lines=331-345 331 | 332 | return [ 333 | 'ok' => true, 334 | 'message' => $___wl_msg, 335 | 'pending_saved' => true, 336 | 'pending_mode' => 'function_guard_v3', 337 | 'existing_today_found' => true, 338 | 'pending_result' => $___wl_pending_result, 339 | 'actions' => [], 340 | ]; 341 | } 342 | } 343 | } 344 | } catch (Throwable $___wl_guard_error) { 345 | // guard 실패가 기존 흐름을 죽이면 안 된다. ### HIT needle=return lines=353-367 353 | // "현장 리스트/프로젝트 목록/공사 목록"은 작업일지 저장보다 먼저 조회로 처리한다. 354 | $__worksiteListFile = __DIR__ . '/WorksiteListSupport.php'; 355 | if (is_file($__worksiteListFile)) { 356 | require_once $__worksiteListFile; 357 | if (class_exists('WorksiteListSupport')) { 358 | $__worksiteListResult = WorksiteListSupport::tryHandle($params); 359 | if ($__worksiteListResult instanceof ToolResult) { 360 | return $__worksiteListResult; 361 | } 362 | } 363 | } 364 | $query = trim((string)($params['query'] ?? $params['param'] ?? '')); 365 | 366 | $this->addTrace('[worklog] query=' . $query); 367 | ### HIT needle=return lines=362-376 362 | } 363 | } 364 | $query = trim((string)($params['query'] ?? $params['param'] ?? '')); 365 | 366 | $this->addTrace('[worklog] query=' . $query); 367 | 368 | if ($query === '') { 369 | return ToolResult::success( 370 | '❌ 작업 기록 실패: 작업 내용이 비어 있습니다.', 371 | ['status' => 'empty_query'], 372 | $this->getTrace() 373 | ); 374 | } 375 | 376 | // WORKLOG_READ_LIST_INTERCEPT_V1 ### HIT needle=return lines=374-388 374 | } 375 | 376 | // WORKLOG_READ_LIST_INTERCEPT_V1 377 | // "어제까지 1주일간 한일 리스트로 보여줘" 같은 요청은 저장이 아니라 조회다. 378 | // hasFieldSignal 가드보다 먼저 처리해야 "기록했어요" 오답을 막을 수 있다. 379 | if (WorklogReadListService::isReadListQuery($query, $params)) { 380 | $this->addTrace('[worklog.read_list] intercept=true'); 381 | return WorklogReadListService::handle($query, $params, $this->getTrace()); 382 | } 383 | 384 | // 강한 신호 가드: WorklogTool이 잘못 호출됐을 때 DB 저장을 차단하고 385 | // 메모성 응답만 돌려준다. (ToolSelector 변경과 이중 안전망) 386 | if (!$this->hasFieldSignal($query)) { 387 | $this->addTrace('[worklog] skip_save: no field signal in query'); 388 | ### HIT needle=save lines=380-394 380 | $this->addTrace('[worklog.read_list] intercept=true'); 381 | return WorklogReadListService::handle($query, $params, $this->getTrace()); 382 | } 383 | 384 | // 강한 신호 가드: WorklogTool이 잘못 호출됐을 때 DB 저장을 차단하고 385 | // 메모성 응답만 돌려준다. (ToolSelector 변경과 이중 안전망) 386 | if (!$this->hasFieldSignal($query)) { 387 | $this->addTrace('[worklog] skip_save: no field signal in query'); 388 | 389 | return ToolResult::success( 390 | '📝 기록했어요: ' . $this->cleanForMemoEcho($query), 391 | [ 392 | 'status' => 'skipped_no_field_signal', 393 | 'reason' => 'worklog 강한 신호(현장/작업/공사 등)가 없어 project_logs 저장을 생략했습니다.', 394 | 'query' => $query, ### HIT needle=return lines=382-396 382 | } 383 | 384 | // 강한 신호 가드: WorklogTool이 잘못 호출됐을 때 DB 저장을 차단하고 385 | // 메모성 응답만 돌려준다. (ToolSelector 변경과 이중 안전망) 386 | if (!$this->hasFieldSignal($query)) { 387 | $this->addTrace('[worklog] skip_save: no field signal in query'); 388 | 389 | return ToolResult::success( 390 | '📝 기록했어요: ' . $this->cleanForMemoEcho($query), 391 | [ 392 | 'status' => 'skipped_no_field_signal', 393 | 'reason' => 'worklog 강한 신호(현장/작업/공사 등)가 없어 project_logs 저장을 생략했습니다.', 394 | 'query' => $query, 395 | ], 396 | $this->getTrace() ### HIT needle=save lines=417-431 417 | $context = $params['context'] ?? []; 418 | if (!is_array($context)) { 419 | $context = []; 420 | } 421 | 422 | $parsed = $this->parseWorklogQuery($query); 423 | 424 | // WORKLOG_SAVE_QUALITY_GUARD_V1 425 | // 저장 직전 최종 품질 가드. 426 | // "해줘", "보여줘", "저장", "조회" 같은 명령어가 고객/현장명으로 들어가거나 427 | // 현장/고객이 불명확하면 DB 저장을 막고 되묻는다. 428 | // WORKLOG_ADDRESS_SITE_PARSE_V1 429 | // 주소형 현장 문장을 파서가 못 뽑았을 때 저장 품질검사 전에 보정한다. 430 | // 예: "오늘 파크 2차 204동 902호 화장실등 교체하고 출장비 3만원 받앗어 등록해줘" 431 | // → customer_name/site_name = "파크 2차 204동 902호" ### HIT needle=save lines=556-570 556 | } 557 | } 558 | } catch (Throwable $e) { 559 | $this->addTrace('[worklog.project_hint_from_site_v2] error=' . $e->getMessage()); 560 | } 561 | 562 | // WORKLOG_CANONICAL_FIELDS_BEFORE_QUALITY_V1 563 | // save_quality_guard는 customer_name/project_hint/project_name/content를 검사한다. 564 | // 기존 address_site_parse_v1은 work_content만 채우는 경우가 있어, 검사 직전에 표준 필드를 전부 맞춘다. 565 | try { 566 | if (isset($parsed) && is_array($parsed)) { 567 | $worklogCanonicalRawV1 = trim((string)($query ?? '')); 568 | $worklogCanonicalBodyV1 = trim((string)preg_replace( 569 | '/^\s*(오늘|금일|어제|내일|그제|이번\s*주|이번주)\s+/u', 570 | '', ### HIT needle=save lines=674-688 674 | $this->addTrace('[worklog.canonical_fields_v1] content=' . (string)($parsed['content'] ?? '')); 675 | } 676 | } catch (Throwable $e) { 677 | $this->addTrace('[worklog.canonical_fields_v1] error=' . $e->getMessage()); 678 | } 679 | 680 | // WORKLOG_CANONICAL_FIELDS_BEFORE_QUALITY_STRUCTURAL_V1 681 | // save_quality_guard는 customer_name/project_hint/project_name/content를 본다. 682 | // 주소형/연락처형 업무 문장을 저장 직전에 표준 필드로 쪼갠다. 683 | try { 684 | if (isset($parsed) && is_array($parsed)) { 685 | $__wlRawV1 = trim((string)($query ?? '')); 686 | $__wlBodyV1 = trim((string)preg_replace( 687 | '/^\s*(오늘|금일|어제|내일|그제|이번\s*주|이번주)\s+/u', 688 | '', ### HIT needle=matched lines=801-815 801 | $worklogPhoneRawV2 = trim((string)($query ?? '')); 802 | $worklogPhoneBodyV2 = trim((string)preg_replace( 803 | '/^\s*(오늘|금일|어제|내일|그제|이번\s*주|이번주)\s+/u', 804 | '', 805 | $worklogPhoneRawV2 806 | )); 807 | 808 | $worklogPhoneMatchedV2 = false; 809 | $worklogPhoneCustomerV2 = ''; 810 | $worklogPhoneProjectV2 = ''; 811 | $worklogPhoneContentV2 = ''; 812 | 813 | if (preg_match('/^(.+?\s*(?:상무님|소장님|사장님|고객님|팀장님|과장님|부장님))\s*(?:전화|연락)\s*와서\s*(.+)$/u', $worklogPhoneBodyV2, $m) === 1) { 814 | $worklogPhoneMatchedV2 = true; 815 | $worklogPhoneCustomerV2 = trim((string)$m[1]); ### HIT needle=matched lines=807-821 807 | 808 | $worklogPhoneMatchedV2 = false; 809 | $worklogPhoneCustomerV2 = ''; 810 | $worklogPhoneProjectV2 = ''; 811 | $worklogPhoneContentV2 = ''; 812 | 813 | if (preg_match('/^(.+?\s*(?:상무님|소장님|사장님|고객님|팀장님|과장님|부장님))\s*(?:전화|연락)\s*와서\s*(.+)$/u', $worklogPhoneBodyV2, $m) === 1) { 814 | $worklogPhoneMatchedV2 = true; 815 | $worklogPhoneCustomerV2 = trim((string)$m[1]); 816 | $worklogPhoneContentV2 = trim((string)$m[2]); 817 | } elseif (preg_match('/^(.+?(?:농협|전기|철물|상가|공장|식당|아파트|빌라))\s*(?:전화|연락)\s*와서\s*(.+)$/u', $worklogPhoneBodyV2, $m) === 1) { 818 | $worklogPhoneMatchedV2 = true; 819 | $worklogPhoneCustomerV2 = trim((string)$m[1]); 820 | $worklogPhoneContentV2 = trim((string)$m[2]); 821 | } ### HIT needle=matched lines=811-825 811 | $worklogPhoneContentV2 = ''; 812 | 813 | if (preg_match('/^(.+?\s*(?:상무님|소장님|사장님|고객님|팀장님|과장님|부장님))\s*(?:전화|연락)\s*와서\s*(.+)$/u', $worklogPhoneBodyV2, $m) === 1) { 814 | $worklogPhoneMatchedV2 = true; 815 | $worklogPhoneCustomerV2 = trim((string)$m[1]); 816 | $worklogPhoneContentV2 = trim((string)$m[2]); 817 | } elseif (preg_match('/^(.+?(?:농협|전기|철물|상가|공장|식당|아파트|빌라))\s*(?:전화|연락)\s*와서\s*(.+)$/u', $worklogPhoneBodyV2, $m) === 1) { 818 | $worklogPhoneMatchedV2 = true; 819 | $worklogPhoneCustomerV2 = trim((string)$m[1]); 820 | $worklogPhoneContentV2 = trim((string)$m[2]); 821 | } 822 | 823 | if ($worklogPhoneMatchedV2 && $worklogPhoneCustomerV2 !== '') { 824 | $worklogPhoneProjectV2 = $worklogPhoneCustomerV2; 825 | ### HIT needle=matched lines=816-830 816 | $worklogPhoneContentV2 = trim((string)$m[2]); 817 | } elseif (preg_match('/^(.+?(?:농협|전기|철물|상가|공장|식당|아파트|빌라))\s*(?:전화|연락)\s*와서\s*(.+)$/u', $worklogPhoneBodyV2, $m) === 1) { 818 | $worklogPhoneMatchedV2 = true; 819 | $worklogPhoneCustomerV2 = trim((string)$m[1]); 820 | $worklogPhoneContentV2 = trim((string)$m[2]); 821 | } 822 | 823 | if ($worklogPhoneMatchedV2 && $worklogPhoneCustomerV2 !== '') { 824 | $worklogPhoneProjectV2 = $worklogPhoneCustomerV2; 825 | 826 | if (preg_match('/지인|지인분/u', $worklogPhoneContentV2) === 1) { 827 | $worklogPhoneProjectV2 .= ' 지인 현장'; 828 | } else { 829 | $worklogPhoneProjectV2 .= ' 요청 현장'; 830 | } ### HIT needle=matched lines=996-1010 996 | $vrpRaw = trim((string)($query ?? '')); 997 | $vrpBody = trim((string)preg_replace( 998 | '/^\s*(방금|아까|좀\s*전에|좀전에|오늘|금일|어제|내일|그제|이번\s*주|이번주)\s+/u', 999 | '', 1000 | $vrpRaw 1001 | )); 1002 | 1003 | $vrpMatched = false; 1004 | $vrpCustomer = ''; 1005 | $vrpReason = ''; 1006 | $vrpFieldWork = ''; 1007 | 1008 | if ( 1009 | preg_match( 1010 | '/^(.+?\s*(?:사장님|고객님|상무님|소장님|팀장님|과장님|부장님))\s+(.+?)\s+해서\s+갔다가\s+현장\s+(.+)$/u', ### HIT needle=matched lines=1008-1022 1008 | if ( 1009 | preg_match( 1010 | '/^(.+?\s*(?:사장님|고객님|상무님|소장님|팀장님|과장님|부장님))\s+(.+?)\s+해서\s+갔다가\s+현장\s+(.+)$/u', 1011 | $vrpBody, 1012 | $m 1013 | ) === 1 1014 | ) { 1015 | $vrpMatched = true; 1016 | $vrpCustomer = trim((string)$m[1]); 1017 | $vrpReason = trim((string)$m[2]); 1018 | $vrpFieldWork = trim((string)$m[3]); 1019 | } elseif ( 1020 | preg_match( 1021 | '/^(.+?(?:농협|전기|철물|상가|공장|식당|아파트|빌라))\s+(.+?)\s+해서\s+갔다가\s+현장\s+(.+)$/u', 1022 | $vrpBody, ### HIT needle=matched lines=1019-1033 1019 | } elseif ( 1020 | preg_match( 1021 | '/^(.+?(?:농협|전기|철물|상가|공장|식당|아파트|빌라))\s+(.+?)\s+해서\s+갔다가\s+현장\s+(.+)$/u', 1022 | $vrpBody, 1023 | $m 1024 | ) === 1 1025 | ) { 1026 | $vrpMatched = true; 1027 | $vrpCustomer = trim((string)$m[1]); 1028 | $vrpReason = trim((string)$m[2]); 1029 | $vrpFieldWork = trim((string)$m[3]); 1030 | } 1031 | 1032 | if ($vrpMatched && $vrpCustomer !== '') { 1033 | // 시간어가 customer 앞에 끼어 있으면 제거. ### HIT needle=matched lines=1025-1039 1025 | ) { 1026 | $vrpMatched = true; 1027 | $vrpCustomer = trim((string)$m[1]); 1028 | $vrpReason = trim((string)$m[2]); 1029 | $vrpFieldWork = trim((string)$m[3]); 1030 | } 1031 | 1032 | if ($vrpMatched && $vrpCustomer !== '') { 1033 | // 시간어가 customer 앞에 끼어 있으면 제거. 1034 | $vrpCustomer = trim((string)preg_replace( 1035 | '/^\s*(방금|아까|좀\s*전에|좀전에|오늘|금일|어제|내일|그제)\s+/u', 1036 | '', 1037 | $vrpCustomer 1038 | )); 1039 | ### HIT needle=save lines=1143-1157 1143 | } 1144 | } 1145 | } 1146 | } catch (Throwable $e) { 1147 | $this->addTrace('[worklog.amount_hint_v1] error=' . $e->getMessage()); 1148 | } 1149 | 1150 | $qualityIssue = $this->inspectWorklogSaveQuality($query, $parsed); 1151 | if ($qualityIssue !== '') { 1152 | $this->addTrace('[worklog.save_quality_guard] blocked=true'); 1153 | $this->addTrace('[worklog.save_quality_guard] issue=' . $qualityIssue); 1154 | 1155 | return ToolResult::success( 1156 | $this->buildWorklogSaveQualityMessage($query, $parsed, $qualityIssue), 1157 | [ ### HIT needle=save lines=1145-1159 1145 | } 1146 | } catch (Throwable $e) { 1147 | $this->addTrace('[worklog.amount_hint_v1] error=' . $e->getMessage()); 1148 | } 1149 | 1150 | $qualityIssue = $this->inspectWorklogSaveQuality($query, $parsed); 1151 | if ($qualityIssue !== '') { 1152 | $this->addTrace('[worklog.save_quality_guard] blocked=true'); 1153 | $this->addTrace('[worklog.save_quality_guard] issue=' . $qualityIssue); 1154 | 1155 | return ToolResult::success( 1156 | $this->buildWorklogSaveQualityMessage($query, $parsed, $qualityIssue), 1157 | [ 1158 | 'status' => 'needs_confirmation', 1159 | 'reason' => $qualityIssue, ### HIT needle=save lines=1146-1160 1146 | } catch (Throwable $e) { 1147 | $this->addTrace('[worklog.amount_hint_v1] error=' . $e->getMessage()); 1148 | } 1149 | 1150 | $qualityIssue = $this->inspectWorklogSaveQuality($query, $parsed); 1151 | if ($qualityIssue !== '') { 1152 | $this->addTrace('[worklog.save_quality_guard] blocked=true'); 1153 | $this->addTrace('[worklog.save_quality_guard] issue=' . $qualityIssue); 1154 | 1155 | return ToolResult::success( 1156 | $this->buildWorklogSaveQualityMessage($query, $parsed, $qualityIssue), 1157 | [ 1158 | 'status' => 'needs_confirmation', 1159 | 'reason' => $qualityIssue, 1160 | 'parsed' => $parsed, ### HIT needle=return lines=1148-1162 1148 | } 1149 | 1150 | $qualityIssue = $this->inspectWorklogSaveQuality($query, $parsed); 1151 | if ($qualityIssue !== '') { 1152 | $this->addTrace('[worklog.save_quality_guard] blocked=true'); 1153 | $this->addTrace('[worklog.save_quality_guard] issue=' . $qualityIssue); 1154 | 1155 | return ToolResult::success( 1156 | $this->buildWorklogSaveQualityMessage($query, $parsed, $qualityIssue), 1157 | [ 1158 | 'status' => 'needs_confirmation', 1159 | 'reason' => $qualityIssue, 1160 | 'parsed' => $parsed, 1161 | 'query' => $query, 1162 | ], ### HIT needle=save lines=1149-1163 1149 | 1150 | $qualityIssue = $this->inspectWorklogSaveQuality($query, $parsed); 1151 | if ($qualityIssue !== '') { 1152 | $this->addTrace('[worklog.save_quality_guard] blocked=true'); 1153 | $this->addTrace('[worklog.save_quality_guard] issue=' . $qualityIssue); 1154 | 1155 | return ToolResult::success( 1156 | $this->buildWorklogSaveQualityMessage($query, $parsed, $qualityIssue), 1157 | [ 1158 | 'status' => 'needs_confirmation', 1159 | 'reason' => $qualityIssue, 1160 | 'parsed' => $parsed, 1161 | 'query' => $query, 1162 | ], 1163 | $this->getTrace() ### HIT needle=save lines=1162-1176 1162 | ], 1163 | $this->getTrace() 1164 | ); 1165 | } 1166 | 1167 | // customer_name이 시간어/일상어로 잘못 잡혔으면 DB 저장 중단하고 확인 요청. 1168 | if ($this->isAmbiguousCustomer($parsed['customer_name'])) { 1169 | $this->addTrace('[worklog] skip_save: ambiguous customer_name=' . $parsed['customer_name']); 1170 | 1171 | return ToolResult::success( 1172 | "❓ 어느 현장 작업인지 확인이 필요합니다.\n고객명/현장명을 함께 말씀해 주세요. 예: \"김형국 현장 차단기 교체했어\"", 1173 | [ 1174 | 'status' => 'needs_confirmation', 1175 | 'reason' => 'customer_name이 시간어/일상어로 의심되어 저장을 보류했습니다.', 1176 | 'parsed' => $parsed, ### HIT needle=return lines=1164-1178 1164 | ); 1165 | } 1166 | 1167 | // customer_name이 시간어/일상어로 잘못 잡혔으면 DB 저장 중단하고 확인 요청. 1168 | if ($this->isAmbiguousCustomer($parsed['customer_name'])) { 1169 | $this->addTrace('[worklog] skip_save: ambiguous customer_name=' . $parsed['customer_name']); 1170 | 1171 | return ToolResult::success( 1172 | "❓ 어느 현장 작업인지 확인이 필요합니다.\n고객명/현장명을 함께 말씀해 주세요. 예: \"김형국 현장 차단기 교체했어\"", 1173 | [ 1174 | 'status' => 'needs_confirmation', 1175 | 'reason' => 'customer_name이 시간어/일상어로 의심되어 저장을 보류했습니다.', 1176 | 'parsed' => $parsed, 1177 | ], 1178 | $this->getTrace() ### HIT needle=return lines=1204-1218 1204 | 1205 | // WORKLOG_DRY_RUN_GUARD_V1 1206 | // dry_run이면 WorklogAgent/DB 저장 라인으로 내려가지 않고 여기서 멈춘다. 1207 | if (!empty($context['dry_run'])) { 1208 | $this->addTrace('[worklog] dry_run_guard=true'); 1209 | $this->addTrace('[worklog] status=dry_run'); 1210 | 1211 | return ToolResult::success( 1212 | $this->buildWorklogDryRunMessage($parsed), 1213 | [ 1214 | 'status' => 'dry_run', 1215 | 'query' => $query, 1216 | 'parsed' => $parsed, 1217 | 'context' => $context, 1218 | ], ### HIT needle=existing lines=1217-1231 1217 | 'context' => $context, 1218 | ], 1219 | $this->getTrace() 1220 | ); 1221 | } 1222 | 1223 | 1224 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1225 | // 일반 작업기록은 승인 없이 저장하되, 오늘 같은 고객/현장 기록이 있으면 먼저 물어본다. 1226 | $existingTodayCandidates = $this->findTodayExistingWorklogCandidates($parsed); 1227 | 1228 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_V1 1229 | // 1차 실제 이관 준비: checker/service 모듈을 WorklogTool에서 호출 가능한 상태로 연결한다. 1230 | // 주의: 중복 pending 생성을 막기 위해 service는 이 단계에서 dry_run만 수행하고, 1231 | // 실제 pending 생성/응답은 기존 legacy 흐름을 유지한다. ### HIT needle=existing lines=1219-1233 1219 | $this->getTrace() 1220 | ); 1221 | } 1222 | 1223 | 1224 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1225 | // 일반 작업기록은 승인 없이 저장하되, 오늘 같은 고객/현장 기록이 있으면 먼저 물어본다. 1226 | $existingTodayCandidates = $this->findTodayExistingWorklogCandidates($parsed); 1227 | 1228 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_V1 1229 | // 1차 실제 이관 준비: checker/service 모듈을 WorklogTool에서 호출 가능한 상태로 연결한다. 1230 | // 주의: 중복 pending 생성을 막기 위해 service는 이 단계에서 dry_run만 수행하고, 1231 | // 실제 pending 생성/응답은 기존 legacy 흐름을 유지한다. 1232 | $existingPendingBridgeResult = null; 1233 | try { ### HIT needle=existing lines=1221-1235 1221 | } 1222 | 1223 | 1224 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1225 | // 일반 작업기록은 승인 없이 저장하되, 오늘 같은 고객/현장 기록이 있으면 먼저 물어본다. 1226 | $existingTodayCandidates = $this->findTodayExistingWorklogCandidates($parsed); 1227 | 1228 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_V1 1229 | // 1차 실제 이관 준비: checker/service 모듈을 WorklogTool에서 호출 가능한 상태로 연결한다. 1230 | // 주의: 중복 pending 생성을 막기 위해 service는 이 단계에서 dry_run만 수행하고, 1231 | // 실제 pending 생성/응답은 기존 legacy 흐름을 유지한다. 1232 | $existingPendingBridgeResult = null; 1233 | try { 1234 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_PDO_FIX_V1 1235 | // bridge 삽입 위치에 $pdo가 없을 수 있으므로, 없으면 gpt_db_pdo()로 SELECT용 PDO만 확보한다. ### HIT needle=checker lines=1222-1236 1222 | 1223 | 1224 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1225 | // 일반 작업기록은 승인 없이 저장하되, 오늘 같은 고객/현장 기록이 있으면 먼저 물어본다. 1226 | $existingTodayCandidates = $this->findTodayExistingWorklogCandidates($parsed); 1227 | 1228 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_V1 1229 | // 1차 실제 이관 준비: checker/service 모듈을 WorklogTool에서 호출 가능한 상태로 연결한다. 1230 | // 주의: 중복 pending 생성을 막기 위해 service는 이 단계에서 dry_run만 수행하고, 1231 | // 실제 pending 생성/응답은 기존 legacy 흐름을 유지한다. 1232 | $existingPendingBridgeResult = null; 1233 | try { 1234 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_PDO_FIX_V1 1235 | // bridge 삽입 위치에 $pdo가 없을 수 있으므로, 없으면 gpt_db_pdo()로 SELECT용 PDO만 확보한다. 1236 | $existingPendingBridgePdo = null; ### HIT needle=pending lines=1223-1237 1223 | 1224 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1225 | // 일반 작업기록은 승인 없이 저장하되, 오늘 같은 고객/현장 기록이 있으면 먼저 물어본다. 1226 | $existingTodayCandidates = $this->findTodayExistingWorklogCandidates($parsed); 1227 | 1228 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_V1 1229 | // 1차 실제 이관 준비: checker/service 모듈을 WorklogTool에서 호출 가능한 상태로 연결한다. 1230 | // 주의: 중복 pending 생성을 막기 위해 service는 이 단계에서 dry_run만 수행하고, 1231 | // 실제 pending 생성/응답은 기존 legacy 흐름을 유지한다. 1232 | $existingPendingBridgeResult = null; 1233 | try { 1234 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_PDO_FIX_V1 1235 | // bridge 삽입 위치에 $pdo가 없을 수 있으므로, 없으면 gpt_db_pdo()로 SELECT용 PDO만 확보한다. 1236 | $existingPendingBridgePdo = null; 1237 | ### HIT needle=pending lines=1224-1238 1224 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1225 | // 일반 작업기록은 승인 없이 저장하되, 오늘 같은 고객/현장 기록이 있으면 먼저 물어본다. 1226 | $existingTodayCandidates = $this->findTodayExistingWorklogCandidates($parsed); 1227 | 1228 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_V1 1229 | // 1차 실제 이관 준비: checker/service 모듈을 WorklogTool에서 호출 가능한 상태로 연결한다. 1230 | // 주의: 중복 pending 생성을 막기 위해 service는 이 단계에서 dry_run만 수행하고, 1231 | // 실제 pending 생성/응답은 기존 legacy 흐름을 유지한다. 1232 | $existingPendingBridgeResult = null; 1233 | try { 1234 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_PDO_FIX_V1 1235 | // bridge 삽입 위치에 $pdo가 없을 수 있으므로, 없으면 gpt_db_pdo()로 SELECT용 PDO만 확보한다. 1236 | $existingPendingBridgePdo = null; 1237 | 1238 | if (isset($pdo) && $pdo instanceof PDO) { ### HIT needle=existing lines=1225-1239 1225 | // 일반 작업기록은 승인 없이 저장하되, 오늘 같은 고객/현장 기록이 있으면 먼저 물어본다. 1226 | $existingTodayCandidates = $this->findTodayExistingWorklogCandidates($parsed); 1227 | 1228 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_V1 1229 | // 1차 실제 이관 준비: checker/service 모듈을 WorklogTool에서 호출 가능한 상태로 연결한다. 1230 | // 주의: 중복 pending 생성을 막기 위해 service는 이 단계에서 dry_run만 수행하고, 1231 | // 실제 pending 생성/응답은 기존 legacy 흐름을 유지한다. 1232 | $existingPendingBridgeResult = null; 1233 | try { 1234 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_PDO_FIX_V1 1235 | // bridge 삽입 위치에 $pdo가 없을 수 있으므로, 없으면 gpt_db_pdo()로 SELECT용 PDO만 확보한다. 1236 | $existingPendingBridgePdo = null; 1237 | 1238 | if (isset($pdo) && $pdo instanceof PDO) { 1239 | $existingPendingBridgePdo = $pdo; ### HIT needle=existing lines=1227-1241 1227 | 1228 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_V1 1229 | // 1차 실제 이관 준비: checker/service 모듈을 WorklogTool에서 호출 가능한 상태로 연결한다. 1230 | // 주의: 중복 pending 생성을 막기 위해 service는 이 단계에서 dry_run만 수행하고, 1231 | // 실제 pending 생성/응답은 기존 legacy 흐름을 유지한다. 1232 | $existingPendingBridgeResult = null; 1233 | try { 1234 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_PDO_FIX_V1 1235 | // bridge 삽입 위치에 $pdo가 없을 수 있으므로, 없으면 gpt_db_pdo()로 SELECT용 PDO만 확보한다. 1236 | $existingPendingBridgePdo = null; 1237 | 1238 | if (isset($pdo) && $pdo instanceof PDO) { 1239 | $existingPendingBridgePdo = $pdo; 1240 | } elseif (function_exists('gpt_db_pdo')) { 1241 | $existingPendingBridgePdo = gpt_db_pdo(); ### HIT needle=existing lines=1229-1243 1229 | // 1차 실제 이관 준비: checker/service 모듈을 WorklogTool에서 호출 가능한 상태로 연결한다. 1230 | // 주의: 중복 pending 생성을 막기 위해 service는 이 단계에서 dry_run만 수행하고, 1231 | // 실제 pending 생성/응답은 기존 legacy 흐름을 유지한다. 1232 | $existingPendingBridgeResult = null; 1233 | try { 1234 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_PDO_FIX_V1 1235 | // bridge 삽입 위치에 $pdo가 없을 수 있으므로, 없으면 gpt_db_pdo()로 SELECT용 PDO만 확보한다. 1236 | $existingPendingBridgePdo = null; 1237 | 1238 | if (isset($pdo) && $pdo instanceof PDO) { 1239 | $existingPendingBridgePdo = $pdo; 1240 | } elseif (function_exists('gpt_db_pdo')) { 1241 | $existingPendingBridgePdo = gpt_db_pdo(); 1242 | } 1243 | ### HIT needle=existing lines=1232-1246 1232 | $existingPendingBridgeResult = null; 1233 | try { 1234 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_PDO_FIX_V1 1235 | // bridge 삽입 위치에 $pdo가 없을 수 있으므로, 없으면 gpt_db_pdo()로 SELECT용 PDO만 확보한다. 1236 | $existingPendingBridgePdo = null; 1237 | 1238 | if (isset($pdo) && $pdo instanceof PDO) { 1239 | $existingPendingBridgePdo = $pdo; 1240 | } elseif (function_exists('gpt_db_pdo')) { 1241 | $existingPendingBridgePdo = gpt_db_pdo(); 1242 | } 1243 | 1244 | if (class_exists('WorklogExistingTodayChecker') && class_exists('WorklogPendingChoiceService') && $existingPendingBridgePdo instanceof PDO) { 1245 | $existingPendingParsed = []; 1246 | ### HIT needle=existing lines=1234-1248 1234 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_PDO_FIX_V1 1235 | // bridge 삽입 위치에 $pdo가 없을 수 있으므로, 없으면 gpt_db_pdo()로 SELECT용 PDO만 확보한다. 1236 | $existingPendingBridgePdo = null; 1237 | 1238 | if (isset($pdo) && $pdo instanceof PDO) { 1239 | $existingPendingBridgePdo = $pdo; 1240 | } elseif (function_exists('gpt_db_pdo')) { 1241 | $existingPendingBridgePdo = gpt_db_pdo(); 1242 | } 1243 | 1244 | if (class_exists('WorklogExistingTodayChecker') && class_exists('WorklogPendingChoiceService') && $existingPendingBridgePdo instanceof PDO) { 1245 | $existingPendingParsed = []; 1246 | 1247 | if (isset($parsed) && is_array($parsed)) { 1248 | $existingPendingParsed = $parsed; ### HIT needle=existing lines=1237-1251 1237 | 1238 | if (isset($pdo) && $pdo instanceof PDO) { 1239 | $existingPendingBridgePdo = $pdo; 1240 | } elseif (function_exists('gpt_db_pdo')) { 1241 | $existingPendingBridgePdo = gpt_db_pdo(); 1242 | } 1243 | 1244 | if (class_exists('WorklogExistingTodayChecker') && class_exists('WorklogPendingChoiceService') && $existingPendingBridgePdo instanceof PDO) { 1245 | $existingPendingParsed = []; 1246 | 1247 | if (isset($parsed) && is_array($parsed)) { 1248 | $existingPendingParsed = $parsed; 1249 | } 1250 | 1251 | if (isset($query)) { ### HIT needle=existing lines=1238-1252 1238 | if (isset($pdo) && $pdo instanceof PDO) { 1239 | $existingPendingBridgePdo = $pdo; 1240 | } elseif (function_exists('gpt_db_pdo')) { 1241 | $existingPendingBridgePdo = gpt_db_pdo(); 1242 | } 1243 | 1244 | if (class_exists('WorklogExistingTodayChecker') && class_exists('WorklogPendingChoiceService') && $existingPendingBridgePdo instanceof PDO) { 1245 | $existingPendingParsed = []; 1246 | 1247 | if (isset($parsed) && is_array($parsed)) { 1248 | $existingPendingParsed = $parsed; 1249 | } 1250 | 1251 | if (isset($query)) { 1252 | $existingPendingParsed['query'] = (string)$query; ### HIT needle=existing lines=1241-1255 1241 | $existingPendingBridgePdo = gpt_db_pdo(); 1242 | } 1243 | 1244 | if (class_exists('WorklogExistingTodayChecker') && class_exists('WorklogPendingChoiceService') && $existingPendingBridgePdo instanceof PDO) { 1245 | $existingPendingParsed = []; 1246 | 1247 | if (isset($parsed) && is_array($parsed)) { 1248 | $existingPendingParsed = $parsed; 1249 | } 1250 | 1251 | if (isset($query)) { 1252 | $existingPendingParsed['query'] = (string)$query; 1253 | } 1254 | if (isset($rawQuery)) { 1255 | $existingPendingParsed['raw_query'] = (string)$rawQuery; ### HIT needle=existing lines=1245-1259 1245 | $existingPendingParsed = []; 1246 | 1247 | if (isset($parsed) && is_array($parsed)) { 1248 | $existingPendingParsed = $parsed; 1249 | } 1250 | 1251 | if (isset($query)) { 1252 | $existingPendingParsed['query'] = (string)$query; 1253 | } 1254 | if (isset($rawQuery)) { 1255 | $existingPendingParsed['raw_query'] = (string)$rawQuery; 1256 | } 1257 | 1258 | if (isset($customerName)) { 1259 | $existingPendingParsed['customer_name'] = (string)$customerName; ### HIT needle=existing lines=1248-1262 1248 | $existingPendingParsed = $parsed; 1249 | } 1250 | 1251 | if (isset($query)) { 1252 | $existingPendingParsed['query'] = (string)$query; 1253 | } 1254 | if (isset($rawQuery)) { 1255 | $existingPendingParsed['raw_query'] = (string)$rawQuery; 1256 | } 1257 | 1258 | if (isset($customerName)) { 1259 | $existingPendingParsed['customer_name'] = (string)$customerName; 1260 | } 1261 | if (isset($customer_name)) { 1262 | $existingPendingParsed['customer_name'] = (string)$customer_name; ### HIT needle=existing lines=1252-1266 1252 | $existingPendingParsed['query'] = (string)$query; 1253 | } 1254 | if (isset($rawQuery)) { 1255 | $existingPendingParsed['raw_query'] = (string)$rawQuery; 1256 | } 1257 | 1258 | if (isset($customerName)) { 1259 | $existingPendingParsed['customer_name'] = (string)$customerName; 1260 | } 1261 | if (isset($customer_name)) { 1262 | $existingPendingParsed['customer_name'] = (string)$customer_name; 1263 | } 1264 | if (isset($customer)) { 1265 | $existingPendingParsed['customer_name'] = (string)$customer; 1266 | } ### HIT needle=existing lines=1255-1269 1255 | $existingPendingParsed['raw_query'] = (string)$rawQuery; 1256 | } 1257 | 1258 | if (isset($customerName)) { 1259 | $existingPendingParsed['customer_name'] = (string)$customerName; 1260 | } 1261 | if (isset($customer_name)) { 1262 | $existingPendingParsed['customer_name'] = (string)$customer_name; 1263 | } 1264 | if (isset($customer)) { 1265 | $existingPendingParsed['customer_name'] = (string)$customer; 1266 | } 1267 | 1268 | if (isset($projectId)) { 1269 | $existingPendingParsed['project_id'] = $projectId; ### HIT needle=existing lines=1258-1272 1258 | if (isset($customerName)) { 1259 | $existingPendingParsed['customer_name'] = (string)$customerName; 1260 | } 1261 | if (isset($customer_name)) { 1262 | $existingPendingParsed['customer_name'] = (string)$customer_name; 1263 | } 1264 | if (isset($customer)) { 1265 | $existingPendingParsed['customer_name'] = (string)$customer; 1266 | } 1267 | 1268 | if (isset($projectId)) { 1269 | $existingPendingParsed['project_id'] = $projectId; 1270 | } 1271 | if (isset($project_id)) { 1272 | $existingPendingParsed['project_id'] = $project_id; ### HIT needle=existing lines=1262-1276 1262 | $existingPendingParsed['customer_name'] = (string)$customer_name; 1263 | } 1264 | if (isset($customer)) { 1265 | $existingPendingParsed['customer_name'] = (string)$customer; 1266 | } 1267 | 1268 | if (isset($projectId)) { 1269 | $existingPendingParsed['project_id'] = $projectId; 1270 | } 1271 | if (isset($project_id)) { 1272 | $existingPendingParsed['project_id'] = $project_id; 1273 | } 1274 | 1275 | if (isset($projectName)) { 1276 | $existingPendingParsed['project_name'] = (string)$projectName; ### HIT needle=existing lines=1265-1279 1265 | $existingPendingParsed['customer_name'] = (string)$customer; 1266 | } 1267 | 1268 | if (isset($projectId)) { 1269 | $existingPendingParsed['project_id'] = $projectId; 1270 | } 1271 | if (isset($project_id)) { 1272 | $existingPendingParsed['project_id'] = $project_id; 1273 | } 1274 | 1275 | if (isset($projectName)) { 1276 | $existingPendingParsed['project_name'] = (string)$projectName; 1277 | } 1278 | if (isset($project_name)) { 1279 | $existingPendingParsed['project_name'] = (string)$project_name; ### HIT needle=existing lines=1269-1283 1269 | $existingPendingParsed['project_id'] = $projectId; 1270 | } 1271 | if (isset($project_id)) { 1272 | $existingPendingParsed['project_id'] = $project_id; 1273 | } 1274 | 1275 | if (isset($projectName)) { 1276 | $existingPendingParsed['project_name'] = (string)$projectName; 1277 | } 1278 | if (isset($project_name)) { 1279 | $existingPendingParsed['project_name'] = (string)$project_name; 1280 | } 1281 | if (isset($projectHint)) { 1282 | $existingPendingParsed['project_hint'] = (string)$projectHint; 1283 | } ### HIT needle=existing lines=1272-1286 1272 | $existingPendingParsed['project_id'] = $project_id; 1273 | } 1274 | 1275 | if (isset($projectName)) { 1276 | $existingPendingParsed['project_name'] = (string)$projectName; 1277 | } 1278 | if (isset($project_name)) { 1279 | $existingPendingParsed['project_name'] = (string)$project_name; 1280 | } 1281 | if (isset($projectHint)) { 1282 | $existingPendingParsed['project_hint'] = (string)$projectHint; 1283 | } 1284 | if (isset($project_hint)) { 1285 | $existingPendingParsed['project_hint'] = (string)$project_hint; 1286 | } ### HIT needle=existing lines=1275-1289 1275 | if (isset($projectName)) { 1276 | $existingPendingParsed['project_name'] = (string)$projectName; 1277 | } 1278 | if (isset($project_name)) { 1279 | $existingPendingParsed['project_name'] = (string)$project_name; 1280 | } 1281 | if (isset($projectHint)) { 1282 | $existingPendingParsed['project_hint'] = (string)$projectHint; 1283 | } 1284 | if (isset($project_hint)) { 1285 | $existingPendingParsed['project_hint'] = (string)$project_hint; 1286 | } 1287 | 1288 | if (isset($content)) { 1289 | $existingPendingParsed['content'] = (string)$content; ### HIT needle=existing lines=1278-1292 1278 | if (isset($project_name)) { 1279 | $existingPendingParsed['project_name'] = (string)$project_name; 1280 | } 1281 | if (isset($projectHint)) { 1282 | $existingPendingParsed['project_hint'] = (string)$projectHint; 1283 | } 1284 | if (isset($project_hint)) { 1285 | $existingPendingParsed['project_hint'] = (string)$project_hint; 1286 | } 1287 | 1288 | if (isset($content)) { 1289 | $existingPendingParsed['content'] = (string)$content; 1290 | } 1291 | if (isset($logContent)) { 1292 | $existingPendingParsed['content'] = (string)$logContent; ### HIT needle=existing lines=1282-1296 1282 | $existingPendingParsed['project_hint'] = (string)$projectHint; 1283 | } 1284 | if (isset($project_hint)) { 1285 | $existingPendingParsed['project_hint'] = (string)$project_hint; 1286 | } 1287 | 1288 | if (isset($content)) { 1289 | $existingPendingParsed['content'] = (string)$content; 1290 | } 1291 | if (isset($logContent)) { 1292 | $existingPendingParsed['content'] = (string)$logContent; 1293 | } 1294 | if (isset($log_content)) { 1295 | $existingPendingParsed['content'] = (string)$log_content; 1296 | } ### HIT needle=existing lines=1285-1299 1285 | $existingPendingParsed['project_hint'] = (string)$project_hint; 1286 | } 1287 | 1288 | if (isset($content)) { 1289 | $existingPendingParsed['content'] = (string)$content; 1290 | } 1291 | if (isset($logContent)) { 1292 | $existingPendingParsed['content'] = (string)$logContent; 1293 | } 1294 | if (isset($log_content)) { 1295 | $existingPendingParsed['content'] = (string)$log_content; 1296 | } 1297 | 1298 | if (isset($amountHint)) { 1299 | $existingPendingParsed['amount_hint'] = $amountHint; ### HIT needle=existing lines=1288-1302 1288 | if (isset($content)) { 1289 | $existingPendingParsed['content'] = (string)$content; 1290 | } 1291 | if (isset($logContent)) { 1292 | $existingPendingParsed['content'] = (string)$logContent; 1293 | } 1294 | if (isset($log_content)) { 1295 | $existingPendingParsed['content'] = (string)$log_content; 1296 | } 1297 | 1298 | if (isset($amountHint)) { 1299 | $existingPendingParsed['amount_hint'] = $amountHint; 1300 | } 1301 | if (isset($amount_hint)) { 1302 | $existingPendingParsed['amount_hint'] = $amount_hint; ### HIT needle=existing lines=1292-1306 1292 | $existingPendingParsed['content'] = (string)$logContent; 1293 | } 1294 | if (isset($log_content)) { 1295 | $existingPendingParsed['content'] = (string)$log_content; 1296 | } 1297 | 1298 | if (isset($amountHint)) { 1299 | $existingPendingParsed['amount_hint'] = $amountHint; 1300 | } 1301 | if (isset($amount_hint)) { 1302 | $existingPendingParsed['amount_hint'] = $amount_hint; 1303 | } 1304 | 1305 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_FALLBACK_V1 1306 | // 1차는 today 기준으로 확인하고, 못 찾으면 legacy와 맞추기 위해 any fallback SELECT를 한 번 더 수행한다. ### HIT needle=existing lines=1295-1309 1295 | $existingPendingParsed['content'] = (string)$log_content; 1296 | } 1297 | 1298 | if (isset($amountHint)) { 1299 | $existingPendingParsed['amount_hint'] = $amountHint; 1300 | } 1301 | if (isset($amount_hint)) { 1302 | $existingPendingParsed['amount_hint'] = $amount_hint; 1303 | } 1304 | 1305 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_FALLBACK_V1 1306 | // 1차는 today 기준으로 확인하고, 못 찾으면 legacy와 맞추기 위해 any fallback SELECT를 한 번 더 수행한다. 1307 | // 여전히 SELECT 읽기 전용이며, pending 저장은 dry_run만 수행한다. 1308 | $existingPendingChecker = WorklogExistingTodayChecker::check($existingPendingBridgePdo, $existingPendingParsed, [ 1309 | 'date' => date('Y-m-d'), ### HIT needle=existing lines=1298-1312 1298 | if (isset($amountHint)) { 1299 | $existingPendingParsed['amount_hint'] = $amountHint; 1300 | } 1301 | if (isset($amount_hint)) { 1302 | $existingPendingParsed['amount_hint'] = $amount_hint; 1303 | } 1304 | 1305 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_FALLBACK_V1 1306 | // 1차는 today 기준으로 확인하고, 못 찾으면 legacy와 맞추기 위해 any fallback SELECT를 한 번 더 수행한다. 1307 | // 여전히 SELECT 읽기 전용이며, pending 저장은 dry_run만 수행한다. 1308 | $existingPendingChecker = WorklogExistingTodayChecker::check($existingPendingBridgePdo, $existingPendingParsed, [ 1309 | 'date' => date('Y-m-d'), 1310 | 'date_mode' => 'today', 1311 | 'limit' => 5, 1312 | ]); ### HIT needle=pending lines=1300-1314 1300 | } 1301 | if (isset($amount_hint)) { 1302 | $existingPendingParsed['amount_hint'] = $amount_hint; 1303 | } 1304 | 1305 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_FALLBACK_V1 1306 | // 1차는 today 기준으로 확인하고, 못 찾으면 legacy와 맞추기 위해 any fallback SELECT를 한 번 더 수행한다. 1307 | // 여전히 SELECT 읽기 전용이며, pending 저장은 dry_run만 수행한다. 1308 | $existingPendingChecker = WorklogExistingTodayChecker::check($existingPendingBridgePdo, $existingPendingParsed, [ 1309 | 'date' => date('Y-m-d'), 1310 | 'date_mode' => 'today', 1311 | 'limit' => 5, 1312 | ]); 1313 | 1314 | $existingPendingCheckerMode = 'today'; ### HIT needle=existing lines=1301-1315 1301 | if (isset($amount_hint)) { 1302 | $existingPendingParsed['amount_hint'] = $amount_hint; 1303 | } 1304 | 1305 | // WORKLOG_EXISTING_PENDING_SERVICE_BRIDGE_FALLBACK_V1 1306 | // 1차는 today 기준으로 확인하고, 못 찾으면 legacy와 맞추기 위해 any fallback SELECT를 한 번 더 수행한다. 1307 | // 여전히 SELECT 읽기 전용이며, pending 저장은 dry_run만 수행한다. 1308 | $existingPendingChecker = WorklogExistingTodayChecker::check($existingPendingBridgePdo, $existingPendingParsed, [ 1309 | 'date' => date('Y-m-d'), 1310 | 'date_mode' => 'today', 1311 | 'limit' => 5, 1312 | ]); 1313 | 1314 | $existingPendingCheckerMode = 'today'; 1315 | ### HIT needle=existing lines=1307-1321 1307 | // 여전히 SELECT 읽기 전용이며, pending 저장은 dry_run만 수행한다. 1308 | $existingPendingChecker = WorklogExistingTodayChecker::check($existingPendingBridgePdo, $existingPendingParsed, [ 1309 | 'date' => date('Y-m-d'), 1310 | 'date_mode' => 'today', 1311 | 'limit' => 5, 1312 | ]); 1313 | 1314 | $existingPendingCheckerMode = 'today'; 1315 | 1316 | if (empty($existingPendingChecker['found'])) { 1317 | $existingPendingCheckerFallback = WorklogExistingTodayChecker::check($existingPendingBridgePdo, $existingPendingParsed, [ 1318 | 'date_mode' => 'any', 1319 | 'limit' => 5, 1320 | ]); 1321 | ### HIT needle=existing lines=1309-1323 1309 | 'date' => date('Y-m-d'), 1310 | 'date_mode' => 'today', 1311 | 'limit' => 5, 1312 | ]); 1313 | 1314 | $existingPendingCheckerMode = 'today'; 1315 | 1316 | if (empty($existingPendingChecker['found'])) { 1317 | $existingPendingCheckerFallback = WorklogExistingTodayChecker::check($existingPendingBridgePdo, $existingPendingParsed, [ 1318 | 'date_mode' => 'any', 1319 | 'limit' => 5, 1320 | ]); 1321 | 1322 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingCheckerFallback)) { 1323 | $existingPendingChecker = $existingPendingCheckerFallback; ### HIT needle=existing lines=1310-1324 1310 | 'date_mode' => 'today', 1311 | 'limit' => 5, 1312 | ]); 1313 | 1314 | $existingPendingCheckerMode = 'today'; 1315 | 1316 | if (empty($existingPendingChecker['found'])) { 1317 | $existingPendingCheckerFallback = WorklogExistingTodayChecker::check($existingPendingBridgePdo, $existingPendingParsed, [ 1318 | 'date_mode' => 'any', 1319 | 'limit' => 5, 1320 | ]); 1321 | 1322 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingCheckerFallback)) { 1323 | $existingPendingChecker = $existingPendingCheckerFallback; 1324 | $existingPendingCheckerMode = 'any_fallback'; ### HIT needle=existing lines=1315-1329 1315 | 1316 | if (empty($existingPendingChecker['found'])) { 1317 | $existingPendingCheckerFallback = WorklogExistingTodayChecker::check($existingPendingBridgePdo, $existingPendingParsed, [ 1318 | 'date_mode' => 'any', 1319 | 'limit' => 5, 1320 | ]); 1321 | 1322 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingCheckerFallback)) { 1323 | $existingPendingChecker = $existingPendingCheckerFallback; 1324 | $existingPendingCheckerMode = 'any_fallback'; 1325 | } 1326 | } 1327 | 1328 | $existingPendingBridgeResult = $existingPendingChecker; 1329 | ### HIT needle=existing lines=1316-1330 1316 | if (empty($existingPendingChecker['found'])) { 1317 | $existingPendingCheckerFallback = WorklogExistingTodayChecker::check($existingPendingBridgePdo, $existingPendingParsed, [ 1318 | 'date_mode' => 'any', 1319 | 'limit' => 5, 1320 | ]); 1321 | 1322 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingCheckerFallback)) { 1323 | $existingPendingChecker = $existingPendingCheckerFallback; 1324 | $existingPendingCheckerMode = 'any_fallback'; 1325 | } 1326 | } 1327 | 1328 | $existingPendingBridgeResult = $existingPendingChecker; 1329 | 1330 | if (method_exists($this, 'addTrace')) { ### HIT needle=existing lines=1317-1331 1317 | $existingPendingCheckerFallback = WorklogExistingTodayChecker::check($existingPendingBridgePdo, $existingPendingParsed, [ 1318 | 'date_mode' => 'any', 1319 | 'limit' => 5, 1320 | ]); 1321 | 1322 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingCheckerFallback)) { 1323 | $existingPendingChecker = $existingPendingCheckerFallback; 1324 | $existingPendingCheckerMode = 'any_fallback'; 1325 | } 1326 | } 1327 | 1328 | $existingPendingBridgeResult = $existingPendingChecker; 1329 | 1330 | if (method_exists($this, 'addTrace')) { 1331 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_found=' . ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker) ? 'true' : 'false')); ### HIT needle=existing lines=1321-1335 1321 | 1322 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingCheckerFallback)) { 1323 | $existingPendingChecker = $existingPendingCheckerFallback; 1324 | $existingPendingCheckerMode = 'any_fallback'; 1325 | } 1326 | } 1327 | 1328 | $existingPendingBridgeResult = $existingPendingChecker; 1329 | 1330 | if (method_exists($this, 'addTrace')) { 1331 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_found=' . ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker) ? 'true' : 'false')); 1332 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_mode=' . $existingPendingCheckerMode); 1333 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_reason=' . (string)($existingPendingChecker['reason'] ?? '')); 1334 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_candidates=' . count((array)($existingPendingChecker['candidates'] ?? []))); 1335 | } ### HIT needle=existing lines=1324-1338 1324 | $existingPendingCheckerMode = 'any_fallback'; 1325 | } 1326 | } 1327 | 1328 | $existingPendingBridgeResult = $existingPendingChecker; 1329 | 1330 | if (method_exists($this, 'addTrace')) { 1331 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_found=' . ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker) ? 'true' : 'false')); 1332 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_mode=' . $existingPendingCheckerMode); 1333 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_reason=' . (string)($existingPendingChecker['reason'] ?? '')); 1334 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_candidates=' . count((array)($existingPendingChecker['candidates'] ?? []))); 1335 | } 1336 | 1337 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker)) { 1338 | // WORKLOG_PENDING_SERVICE_REAL_WRITE_V1 ### HIT needle=existing lines=1325-1339 1325 | } 1326 | } 1327 | 1328 | $existingPendingBridgeResult = $existingPendingChecker; 1329 | 1330 | if (method_exists($this, 'addTrace')) { 1331 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_found=' . ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker) ? 'true' : 'false')); 1332 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_mode=' . $existingPendingCheckerMode); 1333 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_reason=' . (string)($existingPendingChecker['reason'] ?? '')); 1334 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_candidates=' . count((array)($existingPendingChecker['candidates'] ?? []))); 1335 | } 1336 | 1337 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker)) { 1338 | // WORKLOG_PENDING_SERVICE_REAL_WRITE_V1 1339 | // service가 실제 pending 파일을 만들고, legacy 중복 저장은 WorklogChoicePendingStore guard에서 재사용한다. ### HIT needle=existing lines=1326-1340 1326 | } 1327 | 1328 | $existingPendingBridgeResult = $existingPendingChecker; 1329 | 1330 | if (method_exists($this, 'addTrace')) { 1331 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_found=' . ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker) ? 'true' : 'false')); 1332 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_mode=' . $existingPendingCheckerMode); 1333 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_reason=' . (string)($existingPendingChecker['reason'] ?? '')); 1334 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_candidates=' . count((array)($existingPendingChecker['candidates'] ?? []))); 1335 | } 1336 | 1337 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker)) { 1338 | // WORKLOG_PENDING_SERVICE_REAL_WRITE_V1 1339 | // service가 실제 pending 파일을 만들고, legacy 중복 저장은 WorklogChoicePendingStore guard에서 재사용한다. 1340 | $pendingServiceWrite = WorklogPendingChoiceService::save( ### HIT needle=existing lines=1327-1341 1327 | 1328 | $existingPendingBridgeResult = $existingPendingChecker; 1329 | 1330 | if (method_exists($this, 'addTrace')) { 1331 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_found=' . ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker) ? 'true' : 'false')); 1332 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_mode=' . $existingPendingCheckerMode); 1333 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_reason=' . (string)($existingPendingChecker['reason'] ?? '')); 1334 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_candidates=' . count((array)($existingPendingChecker['candidates'] ?? []))); 1335 | } 1336 | 1337 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker)) { 1338 | // WORKLOG_PENDING_SERVICE_REAL_WRITE_V1 1339 | // service가 실제 pending 파일을 만들고, legacy 중복 저장은 WorklogChoicePendingStore guard에서 재사용한다. 1340 | $pendingServiceWrite = WorklogPendingChoiceService::save( 1341 | $existingPendingParsed, ### HIT needle=existing lines=1330-1344 1330 | if (method_exists($this, 'addTrace')) { 1331 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_found=' . ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker) ? 'true' : 'false')); 1332 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_mode=' . $existingPendingCheckerMode); 1333 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_reason=' . (string)($existingPendingChecker['reason'] ?? '')); 1334 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_candidates=' . count((array)($existingPendingChecker['candidates'] ?? []))); 1335 | } 1336 | 1337 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker)) { 1338 | // WORKLOG_PENDING_SERVICE_REAL_WRITE_V1 1339 | // service가 실제 pending 파일을 만들고, legacy 중복 저장은 WorklogChoicePendingStore guard에서 재사용한다. 1340 | $pendingServiceWrite = WorklogPendingChoiceService::save( 1341 | $existingPendingParsed, 1342 | (array)($existingPendingChecker['candidates'] ?? []), 1343 | [ 1344 | 'dry_run' => false, ### HIT needle=pending lines=1331-1345 1331 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_found=' . ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker) ? 'true' : 'false')); 1332 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_mode=' . $existingPendingCheckerMode); 1333 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_reason=' . (string)($existingPendingChecker['reason'] ?? '')); 1334 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_candidates=' . count((array)($existingPendingChecker['candidates'] ?? []))); 1335 | } 1336 | 1337 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker)) { 1338 | // WORKLOG_PENDING_SERVICE_REAL_WRITE_V1 1339 | // service가 실제 pending 파일을 만들고, legacy 중복 저장은 WorklogChoicePendingStore guard에서 재사용한다. 1340 | $pendingServiceWrite = WorklogPendingChoiceService::save( 1341 | $existingPendingParsed, 1342 | (array)($existingPendingChecker['candidates'] ?? []), 1343 | [ 1344 | 'dry_run' => false, 1345 | 'query' => (string)($existingPendingParsed['query'] ?? $existingPendingParsed['raw_query'] ?? ''), ### HIT needle=pending lines=1332-1346 1332 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_mode=' . $existingPendingCheckerMode); 1333 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_reason=' . (string)($existingPendingChecker['reason'] ?? '')); 1334 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_candidates=' . count((array)($existingPendingChecker['candidates'] ?? []))); 1335 | } 1336 | 1337 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker)) { 1338 | // WORKLOG_PENDING_SERVICE_REAL_WRITE_V1 1339 | // service가 실제 pending 파일을 만들고, legacy 중복 저장은 WorklogChoicePendingStore guard에서 재사용한다. 1340 | $pendingServiceWrite = WorklogPendingChoiceService::save( 1341 | $existingPendingParsed, 1342 | (array)($existingPendingChecker['candidates'] ?? []), 1343 | [ 1344 | 'dry_run' => false, 1345 | 'query' => (string)($existingPendingParsed['query'] ?? $existingPendingParsed['raw_query'] ?? ''), 1346 | ] ### HIT needle=pending lines=1333-1347 1333 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_reason=' . (string)($existingPendingChecker['reason'] ?? '')); 1334 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_candidates=' . count((array)($existingPendingChecker['candidates'] ?? []))); 1335 | } 1336 | 1337 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker)) { 1338 | // WORKLOG_PENDING_SERVICE_REAL_WRITE_V1 1339 | // service가 실제 pending 파일을 만들고, legacy 중복 저장은 WorklogChoicePendingStore guard에서 재사용한다. 1340 | $pendingServiceWrite = WorklogPendingChoiceService::save( 1341 | $existingPendingParsed, 1342 | (array)($existingPendingChecker['candidates'] ?? []), 1343 | [ 1344 | 'dry_run' => false, 1345 | 'query' => (string)($existingPendingParsed['query'] ?? $existingPendingParsed['raw_query'] ?? ''), 1346 | ] 1347 | ); ### HIT needle=existing lines=1334-1348 1334 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] checker_candidates=' . count((array)($existingPendingChecker['candidates'] ?? []))); 1335 | } 1336 | 1337 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker)) { 1338 | // WORKLOG_PENDING_SERVICE_REAL_WRITE_V1 1339 | // service가 실제 pending 파일을 만들고, legacy 중복 저장은 WorklogChoicePendingStore guard에서 재사용한다. 1340 | $pendingServiceWrite = WorklogPendingChoiceService::save( 1341 | $existingPendingParsed, 1342 | (array)($existingPendingChecker['candidates'] ?? []), 1343 | [ 1344 | 'dry_run' => false, 1345 | 'query' => (string)($existingPendingParsed['query'] ?? $existingPendingParsed['raw_query'] ?? ''), 1346 | ] 1347 | ); 1348 | ### HIT needle=existing lines=1335-1349 1335 | } 1336 | 1337 | if ($this->gptWorklogExistingCheckerMatchedV1($existingPendingChecker)) { 1338 | // WORKLOG_PENDING_SERVICE_REAL_WRITE_V1 1339 | // service가 실제 pending 파일을 만들고, legacy 중복 저장은 WorklogChoicePendingStore guard에서 재사용한다. 1340 | $pendingServiceWrite = WorklogPendingChoiceService::save( 1341 | $existingPendingParsed, 1342 | (array)($existingPendingChecker['candidates'] ?? []), 1343 | [ 1344 | 'dry_run' => false, 1345 | 'query' => (string)($existingPendingParsed['query'] ?? $existingPendingParsed['raw_query'] ?? ''), 1346 | ] 1347 | ); 1348 | 1349 | $pendingServiceWriteOk = !empty($pendingServiceWrite['ok']) && empty($pendingServiceWrite['dry_run']); ### HIT needle=existing lines=1338-1352 1338 | // WORKLOG_PENDING_SERVICE_REAL_WRITE_V1 1339 | // service가 실제 pending 파일을 만들고, legacy 중복 저장은 WorklogChoicePendingStore guard에서 재사용한다. 1340 | $pendingServiceWrite = WorklogPendingChoiceService::save( 1341 | $existingPendingParsed, 1342 | (array)($existingPendingChecker['candidates'] ?? []), 1343 | [ 1344 | 'dry_run' => false, 1345 | 'query' => (string)($existingPendingParsed['query'] ?? $existingPendingParsed['raw_query'] ?? ''), 1346 | ] 1347 | ); 1348 | 1349 | $pendingServiceWriteOk = !empty($pendingServiceWrite['ok']) && empty($pendingServiceWrite['dry_run']); 1350 | 1351 | if ($pendingServiceWriteOk) { 1352 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1'] = true; ### HIT needle=pending lines=1342-1356 1342 | (array)($existingPendingChecker['candidates'] ?? []), 1343 | [ 1344 | 'dry_run' => false, 1345 | 'query' => (string)($existingPendingParsed['query'] ?? $existingPendingParsed['raw_query'] ?? ''), 1346 | ] 1347 | ); 1348 | 1349 | $pendingServiceWriteOk = !empty($pendingServiceWrite['ok']) && empty($pendingServiceWrite['dry_run']); 1350 | 1351 | if ($pendingServiceWriteOk) { 1352 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1'] = true; 1353 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] = $pendingServiceWrite; 1354 | } 1355 | 1356 | if (method_exists($this, 'addTrace')) { ### HIT needle=pending lines=1344-1358 1344 | 'dry_run' => false, 1345 | 'query' => (string)($existingPendingParsed['query'] ?? $existingPendingParsed['raw_query'] ?? ''), 1346 | ] 1347 | ); 1348 | 1349 | $pendingServiceWriteOk = !empty($pendingServiceWrite['ok']) && empty($pendingServiceWrite['dry_run']); 1350 | 1351 | if ($pendingServiceWriteOk) { 1352 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1'] = true; 1353 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] = $pendingServiceWrite; 1354 | } 1355 | 1356 | if (method_exists($this, 'addTrace')) { 1357 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_service_real_write_ok=' . ($pendingServiceWriteOk ? 'true' : 'false')); 1358 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_mode=service_real_write_with_legacy_guard'); ### HIT needle=pending lines=1345-1359 1345 | 'query' => (string)($existingPendingParsed['query'] ?? $existingPendingParsed['raw_query'] ?? ''), 1346 | ] 1347 | ); 1348 | 1349 | $pendingServiceWriteOk = !empty($pendingServiceWrite['ok']) && empty($pendingServiceWrite['dry_run']); 1350 | 1351 | if ($pendingServiceWriteOk) { 1352 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1'] = true; 1353 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] = $pendingServiceWrite; 1354 | } 1355 | 1356 | if (method_exists($this, 'addTrace')) { 1357 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_service_real_write_ok=' . ($pendingServiceWriteOk ? 'true' : 'false')); 1358 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_mode=service_real_write_with_legacy_guard'); 1359 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_file=' . (string)($pendingServiceWrite['file'] ?? '')); ### HIT needle=pending lines=1346-1360 1346 | ] 1347 | ); 1348 | 1349 | $pendingServiceWriteOk = !empty($pendingServiceWrite['ok']) && empty($pendingServiceWrite['dry_run']); 1350 | 1351 | if ($pendingServiceWriteOk) { 1352 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1'] = true; 1353 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] = $pendingServiceWrite; 1354 | } 1355 | 1356 | if (method_exists($this, 'addTrace')) { 1357 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_service_real_write_ok=' . ($pendingServiceWriteOk ? 'true' : 'false')); 1358 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_mode=service_real_write_with_legacy_guard'); 1359 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_file=' . (string)($pendingServiceWrite['file'] ?? '')); 1360 | } ### HIT needle=existing lines=1350-1364 1350 | 1351 | if ($pendingServiceWriteOk) { 1352 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1'] = true; 1353 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] = $pendingServiceWrite; 1354 | } 1355 | 1356 | if (method_exists($this, 'addTrace')) { 1357 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_service_real_write_ok=' . ($pendingServiceWriteOk ? 'true' : 'false')); 1358 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_mode=service_real_write_with_legacy_guard'); 1359 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_file=' . (string)($pendingServiceWrite['file'] ?? '')); 1360 | } 1361 | } 1362 | } else { 1363 | if (method_exists($this, 'addTrace')) { 1364 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] skipped=missing_class_or_pdo'); ### HIT needle=existing lines=1351-1365 1351 | if ($pendingServiceWriteOk) { 1352 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1'] = true; 1353 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] = $pendingServiceWrite; 1354 | } 1355 | 1356 | if (method_exists($this, 'addTrace')) { 1357 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_service_real_write_ok=' . ($pendingServiceWriteOk ? 'true' : 'false')); 1358 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_mode=service_real_write_with_legacy_guard'); 1359 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_file=' . (string)($pendingServiceWrite['file'] ?? '')); 1360 | } 1361 | } 1362 | } else { 1363 | if (method_exists($this, 'addTrace')) { 1364 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] skipped=missing_class_or_pdo'); 1365 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] class_checker=' . (class_exists('WorklogExistingTodayChecker') ? 'true' : 'false')); ### HIT needle=existing lines=1352-1366 1352 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1'] = true; 1353 | $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] = $pendingServiceWrite; 1354 | } 1355 | 1356 | if (method_exists($this, 'addTrace')) { 1357 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_service_real_write_ok=' . ($pendingServiceWriteOk ? 'true' : 'false')); 1358 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_mode=service_real_write_with_legacy_guard'); 1359 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_file=' . (string)($pendingServiceWrite['file'] ?? '')); 1360 | } 1361 | } 1362 | } else { 1363 | if (method_exists($this, 'addTrace')) { 1364 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] skipped=missing_class_or_pdo'); 1365 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] class_checker=' . (class_exists('WorklogExistingTodayChecker') ? 'true' : 'false')); 1366 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] class_service=' . (class_exists('WorklogPendingChoiceService') ? 'true' : 'false')); ### HIT needle=existing lines=1357-1371 1357 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_service_real_write_ok=' . ($pendingServiceWriteOk ? 'true' : 'false')); 1358 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_mode=service_real_write_with_legacy_guard'); 1359 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_file=' . (string)($pendingServiceWrite['file'] ?? '')); 1360 | } 1361 | } 1362 | } else { 1363 | if (method_exists($this, 'addTrace')) { 1364 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] skipped=missing_class_or_pdo'); 1365 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] class_checker=' . (class_exists('WorklogExistingTodayChecker') ? 'true' : 'false')); 1366 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] class_service=' . (class_exists('WorklogPendingChoiceService') ? 'true' : 'false')); 1367 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pdo_ready=' . ($existingPendingBridgePdo instanceof PDO ? 'true' : 'false')); 1368 | } 1369 | } 1370 | } catch (Throwable $e) { 1371 | if (method_exists($this, 'addTrace')) { ### HIT needle=existing lines=1358-1372 1358 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_mode=service_real_write_with_legacy_guard'); 1359 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_file=' . (string)($pendingServiceWrite['file'] ?? '')); 1360 | } 1361 | } 1362 | } else { 1363 | if (method_exists($this, 'addTrace')) { 1364 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] skipped=missing_class_or_pdo'); 1365 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] class_checker=' . (class_exists('WorklogExistingTodayChecker') ? 'true' : 'false')); 1366 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] class_service=' . (class_exists('WorklogPendingChoiceService') ? 'true' : 'false')); 1367 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pdo_ready=' . ($existingPendingBridgePdo instanceof PDO ? 'true' : 'false')); 1368 | } 1369 | } 1370 | } catch (Throwable $e) { 1371 | if (method_exists($this, 'addTrace')) { 1372 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] error=' . $e->getMessage()); ### HIT needle=existing lines=1359-1373 1359 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pending_file=' . (string)($pendingServiceWrite['file'] ?? '')); 1360 | } 1361 | } 1362 | } else { 1363 | if (method_exists($this, 'addTrace')) { 1364 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] skipped=missing_class_or_pdo'); 1365 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] class_checker=' . (class_exists('WorklogExistingTodayChecker') ? 'true' : 'false')); 1366 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] class_service=' . (class_exists('WorklogPendingChoiceService') ? 'true' : 'false')); 1367 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pdo_ready=' . ($existingPendingBridgePdo instanceof PDO ? 'true' : 'false')); 1368 | } 1369 | } 1370 | } catch (Throwable $e) { 1371 | if (method_exists($this, 'addTrace')) { 1372 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] error=' . $e->getMessage()); 1373 | } ### HIT needle=existing lines=1360-1374 1360 | } 1361 | } 1362 | } else { 1363 | if (method_exists($this, 'addTrace')) { 1364 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] skipped=missing_class_or_pdo'); 1365 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] class_checker=' . (class_exists('WorklogExistingTodayChecker') ? 'true' : 'false')); 1366 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] class_service=' . (class_exists('WorklogPendingChoiceService') ? 'true' : 'false')); 1367 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pdo_ready=' . ($existingPendingBridgePdo instanceof PDO ? 'true' : 'false')); 1368 | } 1369 | } 1370 | } catch (Throwable $e) { 1371 | if (method_exists($this, 'addTrace')) { 1372 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] error=' . $e->getMessage()); 1373 | } 1374 | } ### HIT needle=existing lines=1365-1379 1365 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] class_checker=' . (class_exists('WorklogExistingTodayChecker') ? 'true' : 'false')); 1366 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] class_service=' . (class_exists('WorklogPendingChoiceService') ? 'true' : 'false')); 1367 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] pdo_ready=' . ($existingPendingBridgePdo instanceof PDO ? 'true' : 'false')); 1368 | } 1369 | } 1370 | } catch (Throwable $e) { 1371 | if (method_exists($this, 'addTrace')) { 1372 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] error=' . $e->getMessage()); 1373 | } 1374 | } 1375 | 1376 | $this->addTrace('[worklog_existing_today_preflight_v1] enter'); 1377 | $this->addTrace('[worklog_existing_today_preflight_v1] candidates=' . count($existingTodayCandidates)); 1378 | 1379 | if (count($existingTodayCandidates) > 0) { ### HIT needle=existing lines=1369-1383 1369 | } 1370 | } catch (Throwable $e) { 1371 | if (method_exists($this, 'addTrace')) { 1372 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] error=' . $e->getMessage()); 1373 | } 1374 | } 1375 | 1376 | $this->addTrace('[worklog_existing_today_preflight_v1] enter'); 1377 | $this->addTrace('[worklog_existing_today_preflight_v1] candidates=' . count($existingTodayCandidates)); 1378 | 1379 | if (count($existingTodayCandidates) > 0) { 1380 | $this->addTrace('[worklog_existing_today_preflight_v1] found=true'); 1381 | $this->addTrace('[worklog_existing_today_preflight_v1] blocked=true reason=existing_today_record'); 1382 | 1383 | $pendingChoiceSave = $this->saveExistingTodayPendingChoice($parsed, $existingTodayCandidates, $params ?? []); ### HIT needle=existing lines=1370-1384 1370 | } catch (Throwable $e) { 1371 | if (method_exists($this, 'addTrace')) { 1372 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] error=' . $e->getMessage()); 1373 | } 1374 | } 1375 | 1376 | $this->addTrace('[worklog_existing_today_preflight_v1] enter'); 1377 | $this->addTrace('[worklog_existing_today_preflight_v1] candidates=' . count($existingTodayCandidates)); 1378 | 1379 | if (count($existingTodayCandidates) > 0) { 1380 | $this->addTrace('[worklog_existing_today_preflight_v1] found=true'); 1381 | $this->addTrace('[worklog_existing_today_preflight_v1] blocked=true reason=existing_today_record'); 1382 | 1383 | $pendingChoiceSave = $this->saveExistingTodayPendingChoice($parsed, $existingTodayCandidates, $params ?? []); 1384 | $this->addTrace('[worklog_choice_pending_v1] saved=' . (!empty($pendingChoiceSave['ok']) ? 'true' : 'false')); ### HIT needle=existing lines=1372-1386 1372 | $this->addTrace('[worklog_existing_pending_service_bridge_v1] error=' . $e->getMessage()); 1373 | } 1374 | } 1375 | 1376 | $this->addTrace('[worklog_existing_today_preflight_v1] enter'); 1377 | $this->addTrace('[worklog_existing_today_preflight_v1] candidates=' . count($existingTodayCandidates)); 1378 | 1379 | if (count($existingTodayCandidates) > 0) { 1380 | $this->addTrace('[worklog_existing_today_preflight_v1] found=true'); 1381 | $this->addTrace('[worklog_existing_today_preflight_v1] blocked=true reason=existing_today_record'); 1382 | 1383 | $pendingChoiceSave = $this->saveExistingTodayPendingChoice($parsed, $existingTodayCandidates, $params ?? []); 1384 | $this->addTrace('[worklog_choice_pending_v1] saved=' . (!empty($pendingChoiceSave['ok']) ? 'true' : 'false')); 1385 | $this->addTrace('[worklog_choice_pending_v1] id=' . (string)($pendingChoiceSave['id'] ?? '')); 1386 | $this->addTrace('[worklog_choice_pending_v1] file=' . (string)($pendingChoiceSave['file'] ?? '')); ### HIT needle=existing lines=1373-1387 1373 | } 1374 | } 1375 | 1376 | $this->addTrace('[worklog_existing_today_preflight_v1] enter'); 1377 | $this->addTrace('[worklog_existing_today_preflight_v1] candidates=' . count($existingTodayCandidates)); 1378 | 1379 | if (count($existingTodayCandidates) > 0) { 1380 | $this->addTrace('[worklog_existing_today_preflight_v1] found=true'); 1381 | $this->addTrace('[worklog_existing_today_preflight_v1] blocked=true reason=existing_today_record'); 1382 | 1383 | $pendingChoiceSave = $this->saveExistingTodayPendingChoice($parsed, $existingTodayCandidates, $params ?? []); 1384 | $this->addTrace('[worklog_choice_pending_v1] saved=' . (!empty($pendingChoiceSave['ok']) ? 'true' : 'false')); 1385 | $this->addTrace('[worklog_choice_pending_v1] id=' . (string)($pendingChoiceSave['id'] ?? '')); 1386 | $this->addTrace('[worklog_choice_pending_v1] file=' . (string)($pendingChoiceSave['file'] ?? '')); 1387 | ### HIT needle=existing lines=1374-1388 1374 | } 1375 | 1376 | $this->addTrace('[worklog_existing_today_preflight_v1] enter'); 1377 | $this->addTrace('[worklog_existing_today_preflight_v1] candidates=' . count($existingTodayCandidates)); 1378 | 1379 | if (count($existingTodayCandidates) > 0) { 1380 | $this->addTrace('[worklog_existing_today_preflight_v1] found=true'); 1381 | $this->addTrace('[worklog_existing_today_preflight_v1] blocked=true reason=existing_today_record'); 1382 | 1383 | $pendingChoiceSave = $this->saveExistingTodayPendingChoice($parsed, $existingTodayCandidates, $params ?? []); 1384 | $this->addTrace('[worklog_choice_pending_v1] saved=' . (!empty($pendingChoiceSave['ok']) ? 'true' : 'false')); 1385 | $this->addTrace('[worklog_choice_pending_v1] id=' . (string)($pendingChoiceSave['id'] ?? '')); 1386 | $this->addTrace('[worklog_choice_pending_v1] file=' . (string)($pendingChoiceSave['file'] ?? '')); 1387 | 1388 | return ToolResult::success( ### HIT needle=existing lines=1376-1390 1376 | $this->addTrace('[worklog_existing_today_preflight_v1] enter'); 1377 | $this->addTrace('[worklog_existing_today_preflight_v1] candidates=' . count($existingTodayCandidates)); 1378 | 1379 | if (count($existingTodayCandidates) > 0) { 1380 | $this->addTrace('[worklog_existing_today_preflight_v1] found=true'); 1381 | $this->addTrace('[worklog_existing_today_preflight_v1] blocked=true reason=existing_today_record'); 1382 | 1383 | $pendingChoiceSave = $this->saveExistingTodayPendingChoice($parsed, $existingTodayCandidates, $params ?? []); 1384 | $this->addTrace('[worklog_choice_pending_v1] saved=' . (!empty($pendingChoiceSave['ok']) ? 'true' : 'false')); 1385 | $this->addTrace('[worklog_choice_pending_v1] id=' . (string)($pendingChoiceSave['id'] ?? '')); 1386 | $this->addTrace('[worklog_choice_pending_v1] file=' . (string)($pendingChoiceSave['file'] ?? '')); 1387 | 1388 | return ToolResult::success( 1389 | $this->buildExistingTodayWorklogMessage($parsed, $existingTodayCandidates), 1390 | [ ### HIT needle=pending lines=1377-1391 1377 | $this->addTrace('[worklog_existing_today_preflight_v1] candidates=' . count($existingTodayCandidates)); 1378 | 1379 | if (count($existingTodayCandidates) > 0) { 1380 | $this->addTrace('[worklog_existing_today_preflight_v1] found=true'); 1381 | $this->addTrace('[worklog_existing_today_preflight_v1] blocked=true reason=existing_today_record'); 1382 | 1383 | $pendingChoiceSave = $this->saveExistingTodayPendingChoice($parsed, $existingTodayCandidates, $params ?? []); 1384 | $this->addTrace('[worklog_choice_pending_v1] saved=' . (!empty($pendingChoiceSave['ok']) ? 'true' : 'false')); 1385 | $this->addTrace('[worklog_choice_pending_v1] id=' . (string)($pendingChoiceSave['id'] ?? '')); 1386 | $this->addTrace('[worklog_choice_pending_v1] file=' . (string)($pendingChoiceSave['file'] ?? '')); 1387 | 1388 | return ToolResult::success( 1389 | $this->buildExistingTodayWorklogMessage($parsed, $existingTodayCandidates), 1390 | [ 1391 | 'pending_choice' => $pendingChoiceSave, ### HIT needle=pending lines=1378-1392 1378 | 1379 | if (count($existingTodayCandidates) > 0) { 1380 | $this->addTrace('[worklog_existing_today_preflight_v1] found=true'); 1381 | $this->addTrace('[worklog_existing_today_preflight_v1] blocked=true reason=existing_today_record'); 1382 | 1383 | $pendingChoiceSave = $this->saveExistingTodayPendingChoice($parsed, $existingTodayCandidates, $params ?? []); 1384 | $this->addTrace('[worklog_choice_pending_v1] saved=' . (!empty($pendingChoiceSave['ok']) ? 'true' : 'false')); 1385 | $this->addTrace('[worklog_choice_pending_v1] id=' . (string)($pendingChoiceSave['id'] ?? '')); 1386 | $this->addTrace('[worklog_choice_pending_v1] file=' . (string)($pendingChoiceSave['file'] ?? '')); 1387 | 1388 | return ToolResult::success( 1389 | $this->buildExistingTodayWorklogMessage($parsed, $existingTodayCandidates), 1390 | [ 1391 | 'pending_choice' => $pendingChoiceSave, 1392 | 'status' => 'existing_today_found', ### HIT needle=pending lines=1379-1393 1379 | if (count($existingTodayCandidates) > 0) { 1380 | $this->addTrace('[worklog_existing_today_preflight_v1] found=true'); 1381 | $this->addTrace('[worklog_existing_today_preflight_v1] blocked=true reason=existing_today_record'); 1382 | 1383 | $pendingChoiceSave = $this->saveExistingTodayPendingChoice($parsed, $existingTodayCandidates, $params ?? []); 1384 | $this->addTrace('[worklog_choice_pending_v1] saved=' . (!empty($pendingChoiceSave['ok']) ? 'true' : 'false')); 1385 | $this->addTrace('[worklog_choice_pending_v1] id=' . (string)($pendingChoiceSave['id'] ?? '')); 1386 | $this->addTrace('[worklog_choice_pending_v1] file=' . (string)($pendingChoiceSave['file'] ?? '')); 1387 | 1388 | return ToolResult::success( 1389 | $this->buildExistingTodayWorklogMessage($parsed, $existingTodayCandidates), 1390 | [ 1391 | 'pending_choice' => $pendingChoiceSave, 1392 | 'status' => 'existing_today_found', 1393 | 'reason' => 'existing_today_record', ### HIT needle=return lines=1381-1395 1381 | $this->addTrace('[worklog_existing_today_preflight_v1] blocked=true reason=existing_today_record'); 1382 | 1383 | $pendingChoiceSave = $this->saveExistingTodayPendingChoice($parsed, $existingTodayCandidates, $params ?? []); 1384 | $this->addTrace('[worklog_choice_pending_v1] saved=' . (!empty($pendingChoiceSave['ok']) ? 'true' : 'false')); 1385 | $this->addTrace('[worklog_choice_pending_v1] id=' . (string)($pendingChoiceSave['id'] ?? '')); 1386 | $this->addTrace('[worklog_choice_pending_v1] file=' . (string)($pendingChoiceSave['file'] ?? '')); 1387 | 1388 | return ToolResult::success( 1389 | $this->buildExistingTodayWorklogMessage($parsed, $existingTodayCandidates), 1390 | [ 1391 | 'pending_choice' => $pendingChoiceSave, 1392 | 'status' => 'existing_today_found', 1393 | 'reason' => 'existing_today_record', 1394 | 'parsed' => $parsed, 1395 | 'existing_today_candidates' => $existingTodayCandidates, ### HIT needle=existing lines=1382-1396 1382 | 1383 | $pendingChoiceSave = $this->saveExistingTodayPendingChoice($parsed, $existingTodayCandidates, $params ?? []); 1384 | $this->addTrace('[worklog_choice_pending_v1] saved=' . (!empty($pendingChoiceSave['ok']) ? 'true' : 'false')); 1385 | $this->addTrace('[worklog_choice_pending_v1] id=' . (string)($pendingChoiceSave['id'] ?? '')); 1386 | $this->addTrace('[worklog_choice_pending_v1] file=' . (string)($pendingChoiceSave['file'] ?? '')); 1387 | 1388 | return ToolResult::success( 1389 | $this->buildExistingTodayWorklogMessage($parsed, $existingTodayCandidates), 1390 | [ 1391 | 'pending_choice' => $pendingChoiceSave, 1392 | 'status' => 'existing_today_found', 1393 | 'reason' => 'existing_today_record', 1394 | 'parsed' => $parsed, 1395 | 'existing_today_candidates' => $existingTodayCandidates, 1396 | 'actions' => [], ### HIT needle=pending lines=1384-1398 1384 | $this->addTrace('[worklog_choice_pending_v1] saved=' . (!empty($pendingChoiceSave['ok']) ? 'true' : 'false')); 1385 | $this->addTrace('[worklog_choice_pending_v1] id=' . (string)($pendingChoiceSave['id'] ?? '')); 1386 | $this->addTrace('[worklog_choice_pending_v1] file=' . (string)($pendingChoiceSave['file'] ?? '')); 1387 | 1388 | return ToolResult::success( 1389 | $this->buildExistingTodayWorklogMessage($parsed, $existingTodayCandidates), 1390 | [ 1391 | 'pending_choice' => $pendingChoiceSave, 1392 | 'status' => 'existing_today_found', 1393 | 'reason' => 'existing_today_record', 1394 | 'parsed' => $parsed, 1395 | 'existing_today_candidates' => $existingTodayCandidates, 1396 | 'actions' => [], 1397 | ], 1398 | $this->getTrace() ### HIT needle=existing lines=1385-1399 1385 | $this->addTrace('[worklog_choice_pending_v1] id=' . (string)($pendingChoiceSave['id'] ?? '')); 1386 | $this->addTrace('[worklog_choice_pending_v1] file=' . (string)($pendingChoiceSave['file'] ?? '')); 1387 | 1388 | return ToolResult::success( 1389 | $this->buildExistingTodayWorklogMessage($parsed, $existingTodayCandidates), 1390 | [ 1391 | 'pending_choice' => $pendingChoiceSave, 1392 | 'status' => 'existing_today_found', 1393 | 'reason' => 'existing_today_record', 1394 | 'parsed' => $parsed, 1395 | 'existing_today_candidates' => $existingTodayCandidates, 1396 | 'actions' => [], 1397 | ], 1398 | $this->getTrace() 1399 | ); ### HIT needle=existing lines=1386-1400 1386 | $this->addTrace('[worklog_choice_pending_v1] file=' . (string)($pendingChoiceSave['file'] ?? '')); 1387 | 1388 | return ToolResult::success( 1389 | $this->buildExistingTodayWorklogMessage($parsed, $existingTodayCandidates), 1390 | [ 1391 | 'pending_choice' => $pendingChoiceSave, 1392 | 'status' => 'existing_today_found', 1393 | 'reason' => 'existing_today_record', 1394 | 'parsed' => $parsed, 1395 | 'existing_today_candidates' => $existingTodayCandidates, 1396 | 'actions' => [], 1397 | ], 1398 | $this->getTrace() 1399 | ); 1400 | } ### HIT needle=existing lines=1388-1402 1388 | return ToolResult::success( 1389 | $this->buildExistingTodayWorklogMessage($parsed, $existingTodayCandidates), 1390 | [ 1391 | 'pending_choice' => $pendingChoiceSave, 1392 | 'status' => 'existing_today_found', 1393 | 'reason' => 'existing_today_record', 1394 | 'parsed' => $parsed, 1395 | 'existing_today_candidates' => $existingTodayCandidates, 1396 | 'actions' => [], 1397 | ], 1398 | $this->getTrace() 1399 | ); 1400 | } 1401 | 1402 | $this->addTrace('[worklog_existing_today_preflight_v1] found=false'); ### HIT needle=existing lines=1395-1409 1395 | 'existing_today_candidates' => $existingTodayCandidates, 1396 | 'actions' => [], 1397 | ], 1398 | $this->getTrace() 1399 | ); 1400 | } 1401 | 1402 | $this->addTrace('[worklog_existing_today_preflight_v1] found=false'); 1403 | 1404 | 1405 | $trace[] = '[worklog] parsed=' . json_encode($parsed, JSON_UNESCAPED_UNICODE); 1406 | 1407 | $intents = [ 1408 | [ 1409 | 'type' => 'worklog', ### HIT needle=save lines=1403-1417 1403 | 1404 | 1405 | $trace[] = '[worklog] parsed=' . json_encode($parsed, JSON_UNESCAPED_UNICODE); 1406 | 1407 | $intents = [ 1408 | [ 1409 | 'type' => 'worklog', 1410 | 'param' => 'save', 1411 | ], 1412 | ]; 1413 | 1414 | try { 1415 | $paymentAgent = new PaymentAgent(); 1416 | $worklogAgent = new WorklogAgent($paymentAgent); 1417 | ### HIT needle=return lines=1428-1442 1428 | fn($v) => $v !== '' 1429 | ))); 1430 | 1431 | if ($message === '') { 1432 | $message = '✅ 작업 기록 처리 완료'; 1433 | } 1434 | 1435 | return ToolResult::success( 1436 | $message, 1437 | [ 1438 | 'status' => 'worklog_processed', 1439 | 'query' => $query, 1440 | 'parsed' => $parsed, 1441 | ], 1442 | $trace, ### HIT needle=return lines=1441-1455 1441 | ], 1442 | $trace, 1443 | $actions 1444 | ); 1445 | } catch (Throwable $e) { 1446 | $trace[] = '[worklog] exception=' . $e->getMessage(); 1447 | 1448 | return ToolResult::success( 1449 | '❌ 작업 기록 처리 중 오류: ' . $e->getMessage(), 1450 | [ 1451 | 'status' => 'error', 1452 | 'error' => $e->getMessage(), 1453 | ], 1454 | $trace 1455 | ); ### HIT needle=return lines=1455-1469 1455 | ); 1456 | } 1457 | } 1458 | 1459 | 1460 | private function isWorklogReadListQuery(string $query, array $params = []): bool 1461 | { 1462 | return WorklogReadListService::isReadListQuery($query, $params); 1463 | } 1464 | 1465 | private function handleWorklogReadList(string $query, array $params = []): ToolResult 1466 | { 1467 | return WorklogReadListService::handle($query, $params, $this->getTrace()); 1468 | } 1469 | ### HIT needle=return lines=1460-1474 1460 | private function isWorklogReadListQuery(string $query, array $params = []): bool 1461 | { 1462 | return WorklogReadListService::isReadListQuery($query, $params); 1463 | } 1464 | 1465 | private function handleWorklogReadList(string $query, array $params = []): ToolResult 1466 | { 1467 | return WorklogReadListService::handle($query, $params, $this->getTrace()); 1468 | } 1469 | 1470 | private function worklogReadListDisplayLimit(string $query, int $count): int 1471 | { 1472 | return WorklogFormatHelper::readListDisplayLimit($query, $count); 1473 | } 1474 | ### HIT needle=return lines=1465-1479 1465 | private function handleWorklogReadList(string $query, array $params = []): ToolResult 1466 | { 1467 | return WorklogReadListService::handle($query, $params, $this->getTrace()); 1468 | } 1469 | 1470 | private function worklogReadListDisplayLimit(string $query, int $count): int 1471 | { 1472 | return WorklogFormatHelper::readListDisplayLimit($query, $count); 1473 | } 1474 | 1475 | private function normalizeWorklogListRecords(array $records): array 1476 | { 1477 | return WorklogReadListService::normalizeRecords($records); 1478 | } 1479 | ### HIT needle=return lines=1470-1484 1470 | private function worklogReadListDisplayLimit(string $query, int $count): int 1471 | { 1472 | return WorklogFormatHelper::readListDisplayLimit($query, $count); 1473 | } 1474 | 1475 | private function normalizeWorklogListRecords(array $records): array 1476 | { 1477 | return WorklogReadListService::normalizeRecords($records); 1478 | } 1479 | 1480 | private function makeWorklogDedupeKey(array $record): string 1481 | { 1482 | return WorklogReadListService::makeDedupeKey($record); 1483 | } 1484 | ### HIT needle=return lines=1475-1489 1475 | private function normalizeWorklogListRecords(array $records): array 1476 | { 1477 | return WorklogReadListService::normalizeRecords($records); 1478 | } 1479 | 1480 | private function makeWorklogDedupeKey(array $record): string 1481 | { 1482 | return WorklogReadListService::makeDedupeKey($record); 1483 | } 1484 | 1485 | private function cleanWorklogDisplayPlace(string $place): string 1486 | { 1487 | return WorklogFormatHelper::cleanDisplayPlace($place); 1488 | } 1489 | ### HIT needle=return lines=1480-1494 1480 | private function makeWorklogDedupeKey(array $record): string 1481 | { 1482 | return WorklogReadListService::makeDedupeKey($record); 1483 | } 1484 | 1485 | private function cleanWorklogDisplayPlace(string $place): string 1486 | { 1487 | return WorklogFormatHelper::cleanDisplayPlace($place); 1488 | } 1489 | 1490 | private function detectWorklogDateRange(string $query, array $params = []): array 1491 | { 1492 | return WorklogDateRangeParser::detect($query, $params); 1493 | } 1494 | ### HIT needle=return lines=1485-1499 1485 | private function cleanWorklogDisplayPlace(string $place): string 1486 | { 1487 | return WorklogFormatHelper::cleanDisplayPlace($place); 1488 | } 1489 | 1490 | private function detectWorklogDateRange(string $query, array $params = []): array 1491 | { 1492 | return WorklogDateRangeParser::detect($query, $params); 1493 | } 1494 | 1495 | private function loadWorklogRecords(string $startDate, string $endDate): array 1496 | { 1497 | $trace = []; 1498 | $records = WorklogRecordLoader::load($startDate, $endDate, $trace); 1499 | foreach ($trace as $line) { ### HIT needle=return lines=1496-1510 1496 | { 1497 | $trace = []; 1498 | $records = WorklogRecordLoader::load($startDate, $endDate, $trace); 1499 | foreach ($trace as $line) { 1500 | $this->addTrace((string)$line); 1501 | } 1502 | 1503 | return $records; 1504 | } 1505 | 1506 | private function loadWorklogRecordsFromDb(string $startDate, string $endDate): array 1507 | { 1508 | $trace = []; 1509 | $records = WorklogRecordLoader::loadFromDb($startDate, $endDate, $trace); 1510 | foreach ($trace as $line) { ### HIT needle=return lines=1507-1521 1507 | { 1508 | $trace = []; 1509 | $records = WorklogRecordLoader::loadFromDb($startDate, $endDate, $trace); 1510 | foreach ($trace as $line) { 1511 | $this->addTrace((string)$line); 1512 | } 1513 | 1514 | return $records; 1515 | } 1516 | 1517 | private function loadWorklogRecordsFromFiles(string $startDate, string $endDate): array 1518 | { 1519 | return WorklogRecordLoader::loadFromFiles($startDate, $endDate); 1520 | } 1521 | ### HIT needle=return lines=1512-1526 1512 | } 1513 | 1514 | return $records; 1515 | } 1516 | 1517 | private function loadWorklogRecordsFromFiles(string $startDate, string $endDate): array 1518 | { 1519 | return WorklogRecordLoader::loadFromFiles($startDate, $endDate); 1520 | } 1521 | 1522 | private function normalizeDbWorklogRecord(array $row, string $table): array 1523 | { 1524 | return WorklogRecordLoader::normalizeDbRecord($row, $table); 1525 | } 1526 | ### HIT needle=return lines=1517-1531 1517 | private function loadWorklogRecordsFromFiles(string $startDate, string $endDate): array 1518 | { 1519 | return WorklogRecordLoader::loadFromFiles($startDate, $endDate); 1520 | } 1521 | 1522 | private function normalizeDbWorklogRecord(array $row, string $table): array 1523 | { 1524 | return WorklogRecordLoader::normalizeDbRecord($row, $table); 1525 | } 1526 | 1527 | private function firstNonEmptyWorklogValue(array $row, array $keys): string 1528 | { 1529 | return WorklogRecordLoader::firstNonEmptyValue($row, $keys); 1530 | } 1531 | ### HIT needle=return lines=1522-1536 1522 | private function normalizeDbWorklogRecord(array $row, string $table): array 1523 | { 1524 | return WorklogRecordLoader::normalizeDbRecord($row, $table); 1525 | } 1526 | 1527 | private function firstNonEmptyWorklogValue(array $row, array $keys): string 1528 | { 1529 | return WorklogRecordLoader::firstNonEmptyValue($row, $keys); 1530 | } 1531 | 1532 | private function firstWorklogDateValue(array $row): string 1533 | { 1534 | return WorklogRecordLoader::firstDateValue($row); 1535 | } 1536 | ### HIT needle=return lines=1527-1541 1527 | private function firstNonEmptyWorklogValue(array $row, array $keys): string 1528 | { 1529 | return WorklogRecordLoader::firstNonEmptyValue($row, $keys); 1530 | } 1531 | 1532 | private function firstWorklogDateValue(array $row): string 1533 | { 1534 | return WorklogRecordLoader::firstDateValue($row); 1535 | } 1536 | 1537 | private function worklogDateColumns(array $cols): array 1538 | { 1539 | return WorklogRecordLoader::dateColumns($cols); 1540 | } 1541 | ### HIT needle=return lines=1532-1546 1532 | private function firstWorklogDateValue(array $row): string 1533 | { 1534 | return WorklogRecordLoader::firstDateValue($row); 1535 | } 1536 | 1537 | private function worklogDateColumns(array $cols): array 1538 | { 1539 | return WorklogRecordLoader::dateColumns($cols); 1540 | } 1541 | 1542 | private function worklogSortKey(array $record): string 1543 | { 1544 | return WorklogRecordLoader::sortKey($record); 1545 | } 1546 | ### HIT needle=return lines=1537-1551 1537 | private function worklogDateColumns(array $cols): array 1538 | { 1539 | return WorklogRecordLoader::dateColumns($cols); 1540 | } 1541 | 1542 | private function worklogSortKey(array $record): string 1543 | { 1544 | return WorklogRecordLoader::sortKey($record); 1545 | } 1546 | 1547 | private function dedupeWorklogRecordsById(array $records): array 1548 | { 1549 | return WorklogRecordLoader::dedupeById($records); 1550 | } 1551 | ### HIT needle=return lines=1542-1556 1542 | private function worklogSortKey(array $record): string 1543 | { 1544 | return WorklogRecordLoader::sortKey($record); 1545 | } 1546 | 1547 | private function dedupeWorklogRecordsById(array $records): array 1548 | { 1549 | return WorklogRecordLoader::dedupeById($records); 1550 | } 1551 | 1552 | private function worklogTableColumnsIfExists(PDO $pdo, string $table): array 1553 | { 1554 | return WorklogRecordLoader::tableColumnsIfExists($pdo, $table); 1555 | } 1556 | ### HIT needle=return lines=1547-1561 1547 | private function dedupeWorklogRecordsById(array $records): array 1548 | { 1549 | return WorklogRecordLoader::dedupeById($records); 1550 | } 1551 | 1552 | private function worklogTableColumnsIfExists(PDO $pdo, string $table): array 1553 | { 1554 | return WorklogRecordLoader::tableColumnsIfExists($pdo, $table); 1555 | } 1556 | 1557 | private function worklogReadListColumns(PDO $pdo, string $table): array 1558 | { 1559 | return WorklogRecordLoader::readListColumns($pdo, $table); 1560 | } 1561 | ### HIT needle=return lines=1552-1566 1552 | private function worklogTableColumnsIfExists(PDO $pdo, string $table): array 1553 | { 1554 | return WorklogRecordLoader::tableColumnsIfExists($pdo, $table); 1555 | } 1556 | 1557 | private function worklogReadListColumns(PDO $pdo, string $table): array 1558 | { 1559 | return WorklogRecordLoader::readListColumns($pdo, $table); 1560 | } 1561 | 1562 | private function decodeWorklogFile(string $raw): array 1563 | { 1564 | return WorklogRecordLoader::decodeFile($raw); 1565 | } 1566 | ### HIT needle=return lines=1557-1571 1557 | private function worklogReadListColumns(PDO $pdo, string $table): array 1558 | { 1559 | return WorklogRecordLoader::readListColumns($pdo, $table); 1560 | } 1561 | 1562 | private function decodeWorklogFile(string $raw): array 1563 | { 1564 | return WorklogRecordLoader::decodeFile($raw); 1565 | } 1566 | 1567 | private function extractWorklogDate(array $item, string $file = ''): string 1568 | { 1569 | return WorklogRecordLoader::extractDate($item, $file); 1570 | } 1571 | ### HIT needle=return lines=1562-1576 1562 | private function decodeWorklogFile(string $raw): array 1563 | { 1564 | return WorklogRecordLoader::decodeFile($raw); 1565 | } 1566 | 1567 | private function extractWorklogDate(array $item, string $file = ''): string 1568 | { 1569 | return WorklogRecordLoader::extractDate($item, $file); 1570 | } 1571 | 1572 | private function compactJsonForWorklog(array $record, int $max = 120): string 1573 | { 1574 | return WorklogFormatHelper::compactJson($record, $max); 1575 | } 1576 | ### HIT needle=return lines=1567-1581 1567 | private function extractWorklogDate(array $item, string $file = ''): string 1568 | { 1569 | return WorklogRecordLoader::extractDate($item, $file); 1570 | } 1571 | 1572 | private function compactJsonForWorklog(array $record, int $max = 120): string 1573 | { 1574 | return WorklogFormatHelper::compactJson($record, $max); 1575 | } 1576 | 1577 | private function inspectWorklogSaveQuality(string $query, array $parsed): string 1578 | { 1579 | $customer = trim((string)($parsed['customer_name'] ?? '')); 1580 | $projectHint = trim((string)($parsed['project_hint'] ?? '')); 1581 | $projectName = trim((string)($parsed['project_name'] ?? '')); ### HIT needle=save lines=1570-1584 1570 | } 1571 | 1572 | private function compactJsonForWorklog(array $record, int $max = 120): string 1573 | { 1574 | return WorklogFormatHelper::compactJson($record, $max); 1575 | } 1576 | 1577 | private function inspectWorklogSaveQuality(string $query, array $parsed): string 1578 | { 1579 | $customer = trim((string)($parsed['customer_name'] ?? '')); 1580 | $projectHint = trim((string)($parsed['project_hint'] ?? '')); 1581 | $projectName = trim((string)($parsed['project_name'] ?? '')); 1582 | $content = trim((string)($parsed['content'] ?? '')); 1583 | 1584 | // 조회/목록 요청은 저장되면 안 된다. ### HIT needle=return lines=1581-1595 1581 | $projectName = trim((string)($parsed['project_name'] ?? '')); 1582 | $content = trim((string)($parsed['content'] ?? '')); 1583 | 1584 | // 조회/목록 요청은 저장되면 안 된다. 1585 | if (preg_match('/리스트|목록|내역|조회|보여줘|알려줘|한\s*일|한일|했던\s*일/u', $query) === 1 1586 | && preg_match('/저장|기록|등록|완료|했어|햇어/u', $query) !== 1 1587 | ) { 1588 | return '조회/목록 성격의 문장이라 작업기록 저장 대상이 아님'; 1589 | } 1590 | 1591 | foreach ([ 1592 | 'customer_name' => $customer, 1593 | 'project_hint' => $projectHint, 1594 | 'project_name' => $projectName, 1595 | ] as $field => $value) { ### HIT needle=return lines=1590-1604 1590 | 1591 | foreach ([ 1592 | 'customer_name' => $customer, 1593 | 'project_hint' => $projectHint, 1594 | 'project_name' => $projectName, 1595 | ] as $field => $value) { 1596 | if ($this->isBadWorklogTargetName($value)) { 1597 | return $field . ' 값이 명령어/조사/일상어로 오염됨: ' . ($value !== '' ? $value : '(empty)'); 1598 | } 1599 | } 1600 | 1601 | if ($customer === '' && $projectHint === '' && $projectName === '') { 1602 | return '고객명/현장명이 비어 있음'; 1603 | } 1604 | ### HIT needle=return lines=1595-1609 1595 | ] as $field => $value) { 1596 | if ($this->isBadWorklogTargetName($value)) { 1597 | return $field . ' 값이 명령어/조사/일상어로 오염됨: ' . ($value !== '' ? $value : '(empty)'); 1598 | } 1599 | } 1600 | 1601 | if ($customer === '' && $projectHint === '' && $projectName === '') { 1602 | return '고객명/현장명이 비어 있음'; 1603 | } 1604 | 1605 | if ($content === '' || mb_strlen($content) < 2) { 1606 | return '작업 내용이 너무 짧거나 비어 있음'; 1607 | } 1608 | 1609 | // WORKLOG_CONTENT_QUALITY_CHECK_V1 ### HIT needle=return lines=1599-1613 1599 | } 1600 | 1601 | if ($customer === '' && $projectHint === '' && $projectName === '') { 1602 | return '고객명/현장명이 비어 있음'; 1603 | } 1604 | 1605 | if ($content === '' || mb_strlen($content) < 2) { 1606 | return '작업 내용이 너무 짧거나 비어 있음'; 1607 | } 1608 | 1609 | // WORKLOG_CONTENT_QUALITY_CHECK_V1 1610 | // 고객/현장 이름은 짧은 단어이므로 명령어/조사를 거르는 isBadWorklogTargetName이 맞지만, 1611 | // 작업내용에는 교체/설치/작업/받았어 같은 단어가 자연스럽게 들어가므로 별도 검사를 쓴다. 1612 | if ($this->isBadWorklogContent($content)) { 1613 | return '작업 내용이 너무 짧거나 작업내용으로 보기 어려움: ' . $content; ### HIT needle=return lines=1606-1620 1606 | return '작업 내용이 너무 짧거나 비어 있음'; 1607 | } 1608 | 1609 | // WORKLOG_CONTENT_QUALITY_CHECK_V1 1610 | // 고객/현장 이름은 짧은 단어이므로 명령어/조사를 거르는 isBadWorklogTargetName이 맞지만, 1611 | // 작업내용에는 교체/설치/작업/받았어 같은 단어가 자연스럽게 들어가므로 별도 검사를 쓴다. 1612 | if ($this->isBadWorklogContent($content)) { 1613 | return '작업 내용이 너무 짧거나 작업내용으로 보기 어려움: ' . $content; 1614 | } 1615 | 1616 | return ''; 1617 | } 1618 | 1619 | private function isBadWorklogContent(string $value): bool 1620 | { ### HIT needle=return lines=1609-1623 1609 | // WORKLOG_CONTENT_QUALITY_CHECK_V1 1610 | // 고객/현장 이름은 짧은 단어이므로 명령어/조사를 거르는 isBadWorklogTargetName이 맞지만, 1611 | // 작업내용에는 교체/설치/작업/받았어 같은 단어가 자연스럽게 들어가므로 별도 검사를 쓴다. 1612 | if ($this->isBadWorklogContent($content)) { 1613 | return '작업 내용이 너무 짧거나 작업내용으로 보기 어려움: ' . $content; 1614 | } 1615 | 1616 | return ''; 1617 | } 1618 | 1619 | private function isBadWorklogContent(string $value): bool 1620 | { 1621 | $v = trim($value); 1622 | $v = preg_replace('/\s+/u', ' ', $v) ?? $v; 1623 | $v = trim($v, " \t\n\r\0\x0B/·,,ㆍ"); ### HIT needle=return lines=1619-1633 1619 | private function isBadWorklogContent(string $value): bool 1620 | { 1621 | $v = trim($value); 1622 | $v = preg_replace('/\s+/u', ' ', $v) ?? $v; 1623 | $v = trim($v, " \t\n\r\0\x0B/·,,ㆍ"); 1624 | 1625 | if ($v === '' || mb_strlen($v) < 2) { 1626 | return true; 1627 | } 1628 | 1629 | // 명령어만 단독으로 있는 경우. 1630 | if (preg_match('/^(저장|등록|기록|보여줘|알려줘|조회|저장해줘|등록해줘|기록해줘|해줘|해|줘|좀)$/u', $v) === 1) { 1631 | return true; 1632 | } 1633 | ### HIT needle=return lines=1624-1638 1624 | 1625 | if ($v === '' || mb_strlen($v) < 2) { 1626 | return true; 1627 | } 1628 | 1629 | // 명령어만 단독으로 있는 경우. 1630 | if (preg_match('/^(저장|등록|기록|보여줘|알려줘|조회|저장해줘|등록해줘|기록해줘|해줘|해|줘|좀)$/u', $v) === 1) { 1631 | return true; 1632 | } 1633 | 1634 | if (preg_match('/^(은|는|이|가|을|를|에|에서|으로|로|의|도|만)$/u', $v) === 1) { 1635 | return true; 1636 | } 1637 | 1638 | // 작업/현장/금전 신호가 하나라도 있으면 정상 작업내용으로 본다. ### HIT needle=return lines=1628-1642 1628 | 1629 | // 명령어만 단독으로 있는 경우. 1630 | if (preg_match('/^(저장|등록|기록|보여줘|알려줘|조회|저장해줘|등록해줘|기록해줘|해줘|해|줘|좀)$/u', $v) === 1) { 1631 | return true; 1632 | } 1633 | 1634 | if (preg_match('/^(은|는|이|가|을|를|에|에서|으로|로|의|도|만)$/u', $v) === 1) { 1635 | return true; 1636 | } 1637 | 1638 | // 작업/현장/금전 신호가 하나라도 있으면 정상 작업내용으로 본다. 1639 | $workSignals = '/(교체|설치|이전\s*설치|이전설치|수리|점검|작업|방문|갔다|갓다|봤다|봣다|해주고|해줫|해줬|전등|콘센트|차단기|스위치|배선|전원선|화장실등|조명|출장비|받았|받앗|수금|입금|만원|원)/u'; 1640 | 1641 | if (preg_match($workSignals, $v) === 1) { 1642 | return false; ### HIT needle=return lines=1635-1649 1635 | return true; 1636 | } 1637 | 1638 | // 작업/현장/금전 신호가 하나라도 있으면 정상 작업내용으로 본다. 1639 | $workSignals = '/(교체|설치|이전\s*설치|이전설치|수리|점검|작업|방문|갔다|갓다|봤다|봣다|해주고|해줫|해줬|전등|콘센트|차단기|스위치|배선|전원선|화장실등|조명|출장비|받았|받앗|수금|입금|만원|원)/u'; 1640 | 1641 | if (preg_match($workSignals, $v) === 1) { 1642 | return false; 1643 | } 1644 | 1645 | // 신호가 전혀 없고 길이도 짧으면 작업내용으로 보기 어렵다. 1646 | if (mb_strlen($v) < 4) { 1647 | return true; 1648 | } 1649 | ### HIT needle=return lines=1640-1654 1640 | 1641 | if (preg_match($workSignals, $v) === 1) { 1642 | return false; 1643 | } 1644 | 1645 | // 신호가 전혀 없고 길이도 짧으면 작업내용으로 보기 어렵다. 1646 | if (mb_strlen($v) < 4) { 1647 | return true; 1648 | } 1649 | 1650 | return false; 1651 | } 1652 | 1653 | private function isBadWorklogTargetName(string $value): bool 1654 | { ### HIT needle=return lines=1643-1657 1643 | } 1644 | 1645 | // 신호가 전혀 없고 길이도 짧으면 작업내용으로 보기 어렵다. 1646 | if (mb_strlen($v) < 4) { 1647 | return true; 1648 | } 1649 | 1650 | return false; 1651 | } 1652 | 1653 | private function isBadWorklogTargetName(string $value): bool 1654 | { 1655 | $v = trim($value); 1656 | $v = preg_replace('/\s+/u', ' ', $v) ?? $v; 1657 | $v = trim($v, " \t\n\r\0\x0B/·,,ㆍ"); ### HIT needle=return lines=1653-1667 1653 | private function isBadWorklogTargetName(string $value): bool 1654 | { 1655 | $v = trim($value); 1656 | $v = preg_replace('/\s+/u', ' ', $v) ?? $v; 1657 | $v = trim($v, " \t\n\r\0\x0B/·,,ㆍ"); 1658 | 1659 | if ($v === '') { 1660 | return true; 1661 | } 1662 | 1663 | if (preg_match('/^(해줘|보여줘|알려줘|저장해줘|저장|등록|기록|작업|현장|공사|시공|리스트|목록|내역|조회|해|줘|좀)$/u', $v) === 1) { 1664 | return true; 1665 | } 1666 | 1667 | if (preg_match('/^(은|는|이|가|을|를|에|에서|으로|로|의|도|만)$/u', $v) === 1) { ### HIT needle=return lines=1657-1671 1657 | $v = trim($v, " \t\n\r\0\x0B/·,,ㆍ"); 1658 | 1659 | if ($v === '') { 1660 | return true; 1661 | } 1662 | 1663 | if (preg_match('/^(해줘|보여줘|알려줘|저장해줘|저장|등록|기록|작업|현장|공사|시공|리스트|목록|내역|조회|해|줘|좀)$/u', $v) === 1) { 1664 | return true; 1665 | } 1666 | 1667 | if (preg_match('/^(은|는|이|가|을|를|에|에서|으로|로|의|도|만)$/u', $v) === 1) { 1668 | return true; 1669 | } 1670 | 1671 | if (preg_match('/^(오늘|어제|내일|금일|명일|작일|최근|지난|이번주|저번주)$/u', $v) === 1) { ### HIT needle=return lines=1661-1675 1661 | } 1662 | 1663 | if (preg_match('/^(해줘|보여줘|알려줘|저장해줘|저장|등록|기록|작업|현장|공사|시공|리스트|목록|내역|조회|해|줘|좀)$/u', $v) === 1) { 1664 | return true; 1665 | } 1666 | 1667 | if (preg_match('/^(은|는|이|가|을|를|에|에서|으로|로|의|도|만)$/u', $v) === 1) { 1668 | return true; 1669 | } 1670 | 1671 | if (preg_match('/^(오늘|어제|내일|금일|명일|작일|최근|지난|이번주|저번주)$/u', $v) === 1) { 1672 | return true; 1673 | } 1674 | 1675 | // 문장 전체가 대상명으로 들어간 흔적 ### HIT needle=return lines=1665-1679 1665 | } 1666 | 1667 | if (preg_match('/^(은|는|이|가|을|를|에|에서|으로|로|의|도|만)$/u', $v) === 1) { 1668 | return true; 1669 | } 1670 | 1671 | if (preg_match('/^(오늘|어제|내일|금일|명일|작일|최근|지난|이번주|저번주)$/u', $v) === 1) { 1672 | return true; 1673 | } 1674 | 1675 | // 문장 전체가 대상명으로 들어간 흔적 1676 | if (mb_strlen($v) > 25 && preg_match('/했|햇|교체|설치|수리|점검|보여|알려|저장|등록|리스트/u', $v) === 1) { 1677 | return true; 1678 | } 1679 | ### HIT needle=return lines=1670-1684 1670 | 1671 | if (preg_match('/^(오늘|어제|내일|금일|명일|작일|최근|지난|이번주|저번주)$/u', $v) === 1) { 1672 | return true; 1673 | } 1674 | 1675 | // 문장 전체가 대상명으로 들어간 흔적 1676 | if (mb_strlen($v) > 25 && preg_match('/했|햇|교체|설치|수리|점검|보여|알려|저장|등록|리스트/u', $v) === 1) { 1677 | return true; 1678 | } 1679 | 1680 | return false; 1681 | } 1682 | 1683 | private function buildWorklogSaveQualityMessage(string $query, array $parsed, string $issue): string 1684 | { ### HIT needle=return lines=1673-1687 1673 | } 1674 | 1675 | // 문장 전체가 대상명으로 들어간 흔적 1676 | if (mb_strlen($v) > 25 && preg_match('/했|햇|교체|설치|수리|점검|보여|알려|저장|등록|리스트/u', $v) === 1) { 1677 | return true; 1678 | } 1679 | 1680 | return false; 1681 | } 1682 | 1683 | private function buildWorklogSaveQualityMessage(string $query, array $parsed, string $issue): string 1684 | { 1685 | $customer = trim((string)($parsed['customer_name'] ?? '')); 1686 | $project = trim((string)($parsed['project_name'] ?? $parsed['project_hint'] ?? '')); 1687 | $content = trim((string)($parsed['content'] ?? '')); ### HIT needle=save lines=1676-1690 1676 | if (mb_strlen($v) > 25 && preg_match('/했|햇|교체|설치|수리|점검|보여|알려|저장|등록|리스트/u', $v) === 1) { 1677 | return true; 1678 | } 1679 | 1680 | return false; 1681 | } 1682 | 1683 | private function buildWorklogSaveQualityMessage(string $query, array $parsed, string $issue): string 1684 | { 1685 | $customer = trim((string)($parsed['customer_name'] ?? '')); 1686 | $project = trim((string)($parsed['project_name'] ?? $parsed['project_hint'] ?? '')); 1687 | $content = trim((string)($parsed['content'] ?? '')); 1688 | 1689 | return "⚠️ 작업기록 저장을 보류했어.\n" 1690 | . "- 요청: {$query}\n" ### HIT needle=return lines=1682-1696 1682 | 1683 | private function buildWorklogSaveQualityMessage(string $query, array $parsed, string $issue): string 1684 | { 1685 | $customer = trim((string)($parsed['customer_name'] ?? '')); 1686 | $project = trim((string)($parsed['project_name'] ?? $parsed['project_hint'] ?? '')); 1687 | $content = trim((string)($parsed['content'] ?? '')); 1688 | 1689 | return "⚠️ 작업기록 저장을 보류했어.\n" 1690 | . "- 요청: {$query}\n" 1691 | . "- 사유: {$issue}\n\n" 1692 | . "현재 파싱값:\n" 1693 | . "- 고객명: " . ($customer !== '' ? $customer : '-') . "\n" 1694 | . "- 현장명: " . ($project !== '' ? $project : '-') . "\n" 1695 | . "- 작업내용: " . ($content !== '' ? $content : '-') . "\n\n" 1696 | . "저장하려면 이렇게 다시 말해줘.\n" ### HIT needle=return lines=1700-1714 1700 | 1701 | private function isAmbiguousCustomer(string $name): bool 1702 | { 1703 | // WORKLOG_AMBIGUOUS_CUSTOMER_GUARD_V1 1704 | $name = trim($name); 1705 | 1706 | if ($name === '') { 1707 | return true; 1708 | } 1709 | 1710 | $bad = [ 1711 | '오늘', '어제', '내일', '금일', '명일', '작일', 1712 | '작업', '현장', '공사', '시공', 1713 | '보여줘', '알려줘', '해줘', '저장해줘', '저장', 1714 | '차단기', '교체', '설치', '수리', '점검', ### HIT needle=return lines=1711-1725 1711 | '오늘', '어제', '내일', '금일', '명일', '작일', 1712 | '작업', '현장', '공사', '시공', 1713 | '보여줘', '알려줘', '해줘', '저장해줘', '저장', 1714 | '차단기', '교체', '설치', '수리', '점검', 1715 | ]; 1716 | 1717 | if (in_array($name, $bad, true)) { 1718 | return true; 1719 | } 1720 | 1721 | if (preg_match('/^[0-9\s]+$/u', $name) === 1) { 1722 | return true; 1723 | } 1724 | 1725 | if (preg_match('/(보여줘|알려줘|해줘|저장해줘)$/u', $name) === 1) { ### HIT needle=return lines=1715-1729 1715 | ]; 1716 | 1717 | if (in_array($name, $bad, true)) { 1718 | return true; 1719 | } 1720 | 1721 | if (preg_match('/^[0-9\s]+$/u', $name) === 1) { 1722 | return true; 1723 | } 1724 | 1725 | if (preg_match('/(보여줘|알려줘|해줘|저장해줘)$/u', $name) === 1) { 1726 | return true; 1727 | } 1728 | 1729 | return false; ### HIT needle=return lines=1719-1733 1719 | } 1720 | 1721 | if (preg_match('/^[0-9\s]+$/u', $name) === 1) { 1722 | return true; 1723 | } 1724 | 1725 | if (preg_match('/(보여줘|알려줘|해줘|저장해줘)$/u', $name) === 1) { 1726 | return true; 1727 | } 1728 | 1729 | return false; 1730 | } 1731 | 1732 | private function parseWorklogQuery(string $query): array 1733 | { ### HIT needle=return lines=1722-1736 1722 | return true; 1723 | } 1724 | 1725 | if (preg_match('/(보여줘|알려줘|해줘|저장해줘)$/u', $name) === 1) { 1726 | return true; 1727 | } 1728 | 1729 | return false; 1730 | } 1731 | 1732 | private function parseWorklogQuery(string $query): array 1733 | { 1734 | $clean = trim($query); 1735 | $clean = preg_replace('/\s+/u', ' ', $clean) ?? $clean; 1736 | ### HIT needle=create lines=1772-1786 1772 | $content = $this->normalizeWorkContent($content); 1773 | 1774 | // WORKLOG_QUERY_UNDERSTANDING_PARSE_V1 1775 | // 예: 김형국 오늘 차단기 교체 작업 저장해줘 1776 | // → customer_name=김형국, content=차단기 교체 1777 | if (class_exists('QueryUnderstanding')) { 1778 | $u = QueryUnderstanding::understand($query); 1779 | if (($u['category'] ?? '') === 'worksite' && ($u['intent'] ?? '') === 'create') { 1780 | $missing = is_array($u['missing'] ?? null) ? $u['missing'] : []; 1781 | $slots = is_array($u['slots'] ?? null) ? $u['slots'] : []; 1782 | 1783 | if (empty($missing)) { 1784 | $slotCustomer = trim((string)($slots['customer'] ?? '')); 1785 | $slotWork = trim((string)($slots['work'] ?? '')); 1786 | ### HIT needle=return lines=1803-1817 1803 | } 1804 | } 1805 | 1806 | if ($content === '') { 1807 | $content = $clean; 1808 | } 1809 | 1810 | return [ 1811 | 'customer_name' => $customerName, 1812 | 'project_hint' => $projectHint, 1813 | 'project_name' => $projectName, 1814 | 'content' => $content, 1815 | ]; 1816 | } 1817 | ### HIT needle=return lines=1818-1832 1818 | private function normalizeNameHint(string $name): string 1819 | { 1820 | $name = trim($name); 1821 | $name = preg_replace('/^(오늘|어제|내일|금일|명일|작일)\s*/u', '', $name) ?? $name; 1822 | $name = preg_replace('/\s*(현장|작업|시공)$/u', '', $name) ?? $name; 1823 | $name = trim(preg_replace('/\s+/u', ' ', $name) ?? $name); 1824 | 1825 | return $name; 1826 | } 1827 | 1828 | private function normalizeWorkContent(string $content): string 1829 | { 1830 | $content = trim($content); 1831 | $content = preg_replace('/(작업\s*)?(기록|저장|등록)(해줘|해|)$/u', '', $content) ?? $content; 1832 | $content = trim(preg_replace('/\s+/u', ' ', $content) ?? $content); ### HIT needle=return lines=1827-1841 1827 | 1828 | private function normalizeWorkContent(string $content): string 1829 | { 1830 | $content = trim($content); 1831 | $content = preg_replace('/(작업\s*)?(기록|저장|등록)(해줘|해|)$/u', '', $content) ?? $content; 1832 | $content = trim(preg_replace('/\s+/u', ' ', $content) ?? $content); 1833 | 1834 | return $content; 1835 | } 1836 | 1837 | 1838 | // GPT_WORKLOG_HAS_FIELD_SIGNAL_COMPAT_V1 1839 | // 기존 코드에서 호출하지만 누락된 메서드 보강. 1840 | private static function hasFieldSignal(...$args): bool 1841 | { ### HIT needle=return lines=1836-1850 1836 | 1837 | 1838 | // GPT_WORKLOG_HAS_FIELD_SIGNAL_COMPAT_V1 1839 | // 기존 코드에서 호출하지만 누락된 메서드 보강. 1840 | private static function hasFieldSignal(...$args): bool 1841 | { 1842 | $query = trim(implode(' ', array_map(static function ($v): string { 1843 | if (is_scalar($v)) return (string)$v; 1844 | return ''; 1845 | }, $args))); 1846 | 1847 | if ($query === '') { 1848 | return false; 1849 | } 1850 | ### HIT needle=return lines=1837-1851 1837 | 1838 | // GPT_WORKLOG_HAS_FIELD_SIGNAL_COMPAT_V1 1839 | // 기존 코드에서 호출하지만 누락된 메서드 보강. 1840 | private static function hasFieldSignal(...$args): bool 1841 | { 1842 | $query = trim(implode(' ', array_map(static function ($v): string { 1843 | if (is_scalar($v)) return (string)$v; 1844 | return ''; 1845 | }, $args))); 1846 | 1847 | if ($query === '') { 1848 | return false; 1849 | } 1850 | 1851 | return (bool)preg_match('/(현장|프로젝트|공사|작업|시공|설치|교체|수리|점검|방문|고객|주소|전화|메모|내용|완료|처리)/u', $query); ### HIT needle=return lines=1841-1855 1841 | { 1842 | $query = trim(implode(' ', array_map(static function ($v): string { 1843 | if (is_scalar($v)) return (string)$v; 1844 | return ''; 1845 | }, $args))); 1846 | 1847 | if ($query === '') { 1848 | return false; 1849 | } 1850 | 1851 | return (bool)preg_match('/(현장|프로젝트|공사|작업|시공|설치|교체|수리|점검|방문|고객|주소|전화|메모|내용|완료|처리)/u', $query); 1852 | } 1853 | 1854 | 1855 | // WORKLOG_CLEAN_FOR_MEMO_ECHO_V1 ### HIT needle=return lines=1844-1858 1844 | return ''; 1845 | }, $args))); 1846 | 1847 | if ($query === '') { 1848 | return false; 1849 | } 1850 | 1851 | return (bool)preg_match('/(현장|프로젝트|공사|작업|시공|설치|교체|수리|점검|방문|고객|주소|전화|메모|내용|완료|처리)/u', $query); 1852 | } 1853 | 1854 | 1855 | // WORKLOG_CLEAN_FOR_MEMO_ECHO_V1 1856 | // 작업기록 조회/출력 중 누락된 정리 함수 보강. 1857 | private function cleanForMemoEcho(mixed $value): string 1858 | { ### HIT needle=return lines=1861-1875 1861 | } 1862 | 1863 | $text = trim((string)$value); 1864 | $text = preg_replace('/\r\n|\r/u', "\n", $text) ?? $text; 1865 | $text = preg_replace('/[ \t]+/u', ' ', $text) ?? $text; 1866 | $text = strip_tags($text); 1867 | 1868 | return trim($text); 1869 | } 1870 | 1871 | 1872 | 1873 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1874 | private function normalizeWorklogPersonProjectKeyV1(string $value): string 1875 | { ### HIT needle=existing lines=1866-1880 1866 | $text = strip_tags($text); 1867 | 1868 | return trim($text); 1869 | } 1870 | 1871 | 1872 | 1873 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1874 | private function normalizeWorklogPersonProjectKeyV1(string $value): string 1875 | { 1876 | $v = trim($value); 1877 | if ($v === '') return ''; 1878 | 1879 | $v = preg_replace('/\s+/u', ' ', $v) ?? $v; 1880 | ### HIT needle=return lines=1870-1884 1870 | 1871 | 1872 | 1873 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1874 | private function normalizeWorklogPersonProjectKeyV1(string $value): string 1875 | { 1876 | $v = trim($value); 1877 | if ($v === '') return ''; 1878 | 1879 | $v = preg_replace('/\s+/u', ' ', $v) ?? $v; 1880 | 1881 | // 시간어 제거 1882 | $v = preg_replace('/^(방금|아까|좀전에|조금전에|오늘|금일|어제|내일)\s*/u', '', $v) ?? $v; 1883 | 1884 | // 지역명은 비교 시 선택 토큰으로 보고 제거 ### HIT needle=return lines=1891-1905 1891 | $v = preg_replace('/(요청\s*현장|지인\s*현장|현장)$/u', '', $v) ?? $v; 1892 | 1893 | // 작업 설명으로 붙은 흔한 꼬리 제거 1894 | $v = preg_replace('/(전등|콘센트|차단기|출장비|교체|설치|이전설치|작업|점검|수리|방문|갔다|해서).*$/u', '', $v) ?? $v; 1895 | 1896 | $v = preg_replace('/[^가-힣a-zA-Z0-9]/u', '', $v) ?? $v; 1897 | 1898 | return trim($v); 1899 | } 1900 | 1901 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1902 | private function findTodayExistingWorklogCandidates(array $parsed): array 1903 | { 1904 | $keys = []; 1905 | ### HIT needle=existing lines=1894-1908 1894 | $v = preg_replace('/(전등|콘센트|차단기|출장비|교체|설치|이전설치|작업|점검|수리|방문|갔다|해서).*$/u', '', $v) ?? $v; 1895 | 1896 | $v = preg_replace('/[^가-힣a-zA-Z0-9]/u', '', $v) ?? $v; 1897 | 1898 | return trim($v); 1899 | } 1900 | 1901 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1902 | private function findTodayExistingWorklogCandidates(array $parsed): array 1903 | { 1904 | $keys = []; 1905 | 1906 | foreach (['customer_name', 'project_name', 'project_hint', 'site_name', 'worksite_name', 'site', 'project'] as $field) { 1907 | $key = $this->normalizeWorklogPersonProjectKeyV1((string)($parsed[$field] ?? '')); 1908 | if ($key !== '' && mb_strlen($key, 'UTF-8') >= 2) { ### HIT needle=existing lines=1895-1909 1895 | 1896 | $v = preg_replace('/[^가-힣a-zA-Z0-9]/u', '', $v) ?? $v; 1897 | 1898 | return trim($v); 1899 | } 1900 | 1901 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1902 | private function findTodayExistingWorklogCandidates(array $parsed): array 1903 | { 1904 | $keys = []; 1905 | 1906 | foreach (['customer_name', 'project_name', 'project_hint', 'site_name', 'worksite_name', 'site', 'project'] as $field) { 1907 | $key = $this->normalizeWorklogPersonProjectKeyV1((string)($parsed[$field] ?? '')); 1908 | if ($key !== '' && mb_strlen($key, 'UTF-8') >= 2) { 1909 | $keys[$key] = true; ### HIT needle=return lines=1906-1920 1906 | foreach (['customer_name', 'project_name', 'project_hint', 'site_name', 'worksite_name', 'site', 'project'] as $field) { 1907 | $key = $this->normalizeWorklogPersonProjectKeyV1((string)($parsed[$field] ?? '')); 1908 | if ($key !== '' && mb_strlen($key, 'UTF-8') >= 2) { 1909 | $keys[$key] = true; 1910 | } 1911 | } 1912 | 1913 | if (!$keys) return []; 1914 | 1915 | try { 1916 | if (!function_exists('gpt_db_pdo')) { 1917 | $storage = dirname(__DIR__) . '/storage.php'; 1918 | if (is_file($storage)) { 1919 | require_once $storage; 1920 | } ### HIT needle=existing lines=1917-1931 1917 | $storage = dirname(__DIR__) . '/storage.php'; 1918 | if (is_file($storage)) { 1919 | require_once $storage; 1920 | } 1921 | } 1922 | 1923 | if (!function_exists('gpt_db_pdo')) { 1924 | $this->addTrace('[worklog_existing_today_preflight_v1] db=missing_gpt_db_pdo'); 1925 | return []; 1926 | } 1927 | 1928 | $pdo = gpt_db_pdo(); 1929 | if (!$pdo instanceof PDO) { 1930 | $this->addTrace('[worklog_existing_today_preflight_v1] db=not_pdo'); 1931 | return []; ### HIT needle=return lines=1918-1932 1918 | if (is_file($storage)) { 1919 | require_once $storage; 1920 | } 1921 | } 1922 | 1923 | if (!function_exists('gpt_db_pdo')) { 1924 | $this->addTrace('[worklog_existing_today_preflight_v1] db=missing_gpt_db_pdo'); 1925 | return []; 1926 | } 1927 | 1928 | $pdo = gpt_db_pdo(); 1929 | if (!$pdo instanceof PDO) { 1930 | $this->addTrace('[worklog_existing_today_preflight_v1] db=not_pdo'); 1931 | return []; 1932 | } ### HIT needle=existing lines=1923-1937 1923 | if (!function_exists('gpt_db_pdo')) { 1924 | $this->addTrace('[worklog_existing_today_preflight_v1] db=missing_gpt_db_pdo'); 1925 | return []; 1926 | } 1927 | 1928 | $pdo = gpt_db_pdo(); 1929 | if (!$pdo instanceof PDO) { 1930 | $this->addTrace('[worklog_existing_today_preflight_v1] db=not_pdo'); 1931 | return []; 1932 | } 1933 | 1934 | $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 1935 | 1936 | $stmt = $pdo->query(" 1937 | SELECT ### HIT needle=return lines=1924-1938 1924 | $this->addTrace('[worklog_existing_today_preflight_v1] db=missing_gpt_db_pdo'); 1925 | return []; 1926 | } 1927 | 1928 | $pdo = gpt_db_pdo(); 1929 | if (!$pdo instanceof PDO) { 1930 | $this->addTrace('[worklog_existing_today_preflight_v1] db=not_pdo'); 1931 | return []; 1932 | } 1933 | 1934 | $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); 1935 | 1936 | $stmt = $pdo->query(" 1937 | SELECT 1938 | l.id, ### HIT needle=create lines=1935-1949 1935 | 1936 | $stmt = $pdo->query(" 1937 | SELECT 1938 | l.id, 1939 | l.project_id, 1940 | l.log_date, 1941 | l.log_content, 1942 | l.created_at, 1943 | p.project_name, 1944 | p.client_name 1945 | FROM project_logs l 1946 | LEFT JOIN projects p ON p.id = l.project_id 1947 | WHERE l.log_date = CURDATE() 1948 | ORDER BY l.id DESC 1949 | LIMIT 100 ### HIT needle=create lines=1973-1987 1973 | $found[] = [ 1974 | 'id' => (int)($row['id'] ?? 0), 1975 | 'project_id' => (int)($row['project_id'] ?? 0), 1976 | 'project_name' => (string)($row['project_name'] ?? ''), 1977 | 'client_name' => (string)($row['client_name'] ?? ''), 1978 | 'log_date' => (string)($row['log_date'] ?? ''), 1979 | 'log_content' => (string)($row['log_content'] ?? ''), 1980 | 'created_at' => (string)($row['created_at'] ?? ''), 1981 | 'matched_key' => $key, 1982 | ]; 1983 | break; 1984 | } 1985 | } 1986 | 1987 | if (count($found) >= 5) break; ### HIT needle=matched lines=1974-1988 1974 | 'id' => (int)($row['id'] ?? 0), 1975 | 'project_id' => (int)($row['project_id'] ?? 0), 1976 | 'project_name' => (string)($row['project_name'] ?? ''), 1977 | 'client_name' => (string)($row['client_name'] ?? ''), 1978 | 'log_date' => (string)($row['log_date'] ?? ''), 1979 | 'log_content' => (string)($row['log_content'] ?? ''), 1980 | 'created_at' => (string)($row['created_at'] ?? ''), 1981 | 'matched_key' => $key, 1982 | ]; 1983 | break; 1984 | } 1985 | } 1986 | 1987 | if (count($found) >= 5) break; 1988 | } ### HIT needle=return lines=1983-1997 1983 | break; 1984 | } 1985 | } 1986 | 1987 | if (count($found) >= 5) break; 1988 | } 1989 | 1990 | return $found; 1991 | } catch (Throwable $e) { 1992 | $this->addTrace('[worklog_existing_today_preflight_v1] error=' . $e->getMessage()); 1993 | return []; 1994 | } 1995 | } 1996 | 1997 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 ### HIT needle=existing lines=1985-1999 1985 | } 1986 | 1987 | if (count($found) >= 5) break; 1988 | } 1989 | 1990 | return $found; 1991 | } catch (Throwable $e) { 1992 | $this->addTrace('[worklog_existing_today_preflight_v1] error=' . $e->getMessage()); 1993 | return []; 1994 | } 1995 | } 1996 | 1997 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1998 | private function buildExistingTodayWorklogMessage(array $parsed, array $candidates): string 1999 | { ### HIT needle=return lines=1986-2000 1986 | 1987 | if (count($found) >= 5) break; 1988 | } 1989 | 1990 | return $found; 1991 | } catch (Throwable $e) { 1992 | $this->addTrace('[worklog_existing_today_preflight_v1] error=' . $e->getMessage()); 1993 | return []; 1994 | } 1995 | } 1996 | 1997 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1998 | private function buildExistingTodayWorklogMessage(array $parsed, array $candidates): string 1999 | { 2000 | $customer = trim((string)($parsed['customer_name'] ?? '')); ### HIT needle=existing lines=1990-2004 1990 | return $found; 1991 | } catch (Throwable $e) { 1992 | $this->addTrace('[worklog_existing_today_preflight_v1] error=' . $e->getMessage()); 1993 | return []; 1994 | } 1995 | } 1996 | 1997 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1998 | private function buildExistingTodayWorklogMessage(array $parsed, array $candidates): string 1999 | { 2000 | $customer = trim((string)($parsed['customer_name'] ?? '')); 2001 | $project = trim((string)($parsed['project_name'] ?? ($parsed['project_hint'] ?? ''))); 2002 | $content = trim((string)($parsed['content'] ?? ($parsed['work_content'] ?? ''))); 2003 | $amount = $parsed['amount_hint'] ?? null; 2004 | ### HIT needle=existing lines=1991-2005 1991 | } catch (Throwable $e) { 1992 | $this->addTrace('[worklog_existing_today_preflight_v1] error=' . $e->getMessage()); 1993 | return []; 1994 | } 1995 | } 1996 | 1997 | // WORKLOG_EXISTING_TODAY_PREFLIGHT_V1 1998 | private function buildExistingTodayWorklogMessage(array $parsed, array $candidates): string 1999 | { 2000 | $customer = trim((string)($parsed['customer_name'] ?? '')); 2001 | $project = trim((string)($parsed['project_name'] ?? ($parsed['project_hint'] ?? ''))); 2002 | $content = trim((string)($parsed['content'] ?? ($parsed['work_content'] ?? ''))); 2003 | $amount = $parsed['amount_hint'] ?? null; 2004 | 2005 | $lines = []; ### HIT needle=return lines=2024-2038 2024 | 2025 | $lines[] = ''; 2026 | $lines[] = '어떻게 할까?'; 2027 | $lines[] = '1) 기존 기록에 내용 추가'; 2028 | $lines[] = '2) 새 작업기록으로 따로 저장'; 2029 | $lines[] = '3) 저장 안 함'; 2030 | 2031 | return implode("\n", $lines); 2032 | } 2033 | 2034 | // WORKLOG_DRY_RUN_MESSAGE_FIX_V2 2035 | private function buildWorklogDryRunMessage(array $parsed): string 2036 | { 2037 | $customer = trim((string)($parsed['customer_name'] ?? '')); 2038 | $project = trim((string)($parsed['project_name'] ?? ($parsed['project_hint'] ?? ''))); ### HIT needle=return lines=2046-2060 2046 | if ($customer !== '') $lines[] = '- 고객: ' . $customer; 2047 | if ($project !== '') $lines[] = '- 현장: ' . $project; 2048 | if ($content !== '') $lines[] = '- 작업: ' . $content; 2049 | if ($amount !== null && $amount !== '' && is_numeric($amount)) { 2050 | $lines[] = '- 금액 힌트: ' . number_format((int)$amount) . '원'; 2051 | } 2052 | 2053 | return implode("\n", $lines); 2054 | } 2055 | 2056 | 2057 | // WORKLOG_EXISTING_CHOICE_PENDING_SAVE_V1 2058 | private function saveExistingTodayPendingChoice(array $parsed, array $existingTodayCandidates, array $params = []): array 2059 | { 2060 | try { ### HIT needle=existing lines=2050-2064 2050 | $lines[] = '- 금액 힌트: ' . number_format((int)$amount) . '원'; 2051 | } 2052 | 2053 | return implode("\n", $lines); 2054 | } 2055 | 2056 | 2057 | // WORKLOG_EXISTING_CHOICE_PENDING_SAVE_V1 2058 | private function saveExistingTodayPendingChoice(array $parsed, array $existingTodayCandidates, array $params = []): array 2059 | { 2060 | try { 2061 | if (!class_exists('WorklogChoicePendingStore')) { 2062 | $file = __DIR__ . '/worklog/WorklogChoicePendingStore.php'; 2063 | if (is_file($file)) { 2064 | require_once $file; ### HIT needle=existing lines=2051-2065 2051 | } 2052 | 2053 | return implode("\n", $lines); 2054 | } 2055 | 2056 | 2057 | // WORKLOG_EXISTING_CHOICE_PENDING_SAVE_V1 2058 | private function saveExistingTodayPendingChoice(array $parsed, array $existingTodayCandidates, array $params = []): array 2059 | { 2060 | try { 2061 | if (!class_exists('WorklogChoicePendingStore')) { 2062 | $file = __DIR__ . '/worklog/WorklogChoicePendingStore.php'; 2063 | if (is_file($file)) { 2064 | require_once $file; 2065 | } ### HIT needle=pending lines=2054-2068 2054 | } 2055 | 2056 | 2057 | // WORKLOG_EXISTING_CHOICE_PENDING_SAVE_V1 2058 | private function saveExistingTodayPendingChoice(array $parsed, array $existingTodayCandidates, array $params = []): array 2059 | { 2060 | try { 2061 | if (!class_exists('WorklogChoicePendingStore')) { 2062 | $file = __DIR__ . '/worklog/WorklogChoicePendingStore.php'; 2063 | if (is_file($file)) { 2064 | require_once $file; 2065 | } 2066 | } 2067 | 2068 | if (!class_exists('WorklogChoicePendingStore')) { ### HIT needle=pending lines=2055-2069 2055 | 2056 | 2057 | // WORKLOG_EXISTING_CHOICE_PENDING_SAVE_V1 2058 | private function saveExistingTodayPendingChoice(array $parsed, array $existingTodayCandidates, array $params = []): array 2059 | { 2060 | try { 2061 | if (!class_exists('WorklogChoicePendingStore')) { 2062 | $file = __DIR__ . '/worklog/WorklogChoicePendingStore.php'; 2063 | if (is_file($file)) { 2064 | require_once $file; 2065 | } 2066 | } 2067 | 2068 | if (!class_exists('WorklogChoicePendingStore')) { 2069 | return [ ### HIT needle=pending lines=2061-2075 2061 | if (!class_exists('WorklogChoicePendingStore')) { 2062 | $file = __DIR__ . '/worklog/WorklogChoicePendingStore.php'; 2063 | if (is_file($file)) { 2064 | require_once $file; 2065 | } 2066 | } 2067 | 2068 | if (!class_exists('WorklogChoicePendingStore')) { 2069 | return [ 2070 | 'ok' => false, 2071 | 'reason' => 'missing_WorklogChoicePendingStore', 2072 | ]; 2073 | } 2074 | 2075 | $store = new WorklogChoicePendingStore(); ### HIT needle=return lines=2062-2076 2062 | $file = __DIR__ . '/worklog/WorklogChoicePendingStore.php'; 2063 | if (is_file($file)) { 2064 | require_once $file; 2065 | } 2066 | } 2067 | 2068 | if (!class_exists('WorklogChoicePendingStore')) { 2069 | return [ 2070 | 'ok' => false, 2071 | 'reason' => 'missing_WorklogChoicePendingStore', 2072 | ]; 2073 | } 2074 | 2075 | $store = new WorklogChoicePendingStore(); 2076 | ### HIT needle=pending lines=2064-2078 2064 | require_once $file; 2065 | } 2066 | } 2067 | 2068 | if (!class_exists('WorklogChoicePendingStore')) { 2069 | return [ 2070 | 'ok' => false, 2071 | 'reason' => 'missing_WorklogChoicePendingStore', 2072 | ]; 2073 | } 2074 | 2075 | $store = new WorklogChoicePendingStore(); 2076 | 2077 | $first = is_array($existingTodayCandidates[0] ?? null) ? $existingTodayCandidates[0] : []; 2078 | ### HIT needle=pending lines=2068-2082 2068 | if (!class_exists('WorklogChoicePendingStore')) { 2069 | return [ 2070 | 'ok' => false, 2071 | 'reason' => 'missing_WorklogChoicePendingStore', 2072 | ]; 2073 | } 2074 | 2075 | $store = new WorklogChoicePendingStore(); 2076 | 2077 | $first = is_array($existingTodayCandidates[0] ?? null) ? $existingTodayCandidates[0] : []; 2078 | 2079 | $payload = [ 2080 | 'trace_id' => (string)($params['trace_id'] ?? ''), 2081 | 'session_id' => (string)($params['session_id'] ?? ''), 2082 | 'original_query' => (string)($params['raw_query'] ?? $params['query'] ?? $params['original_query'] ?? ''), ### HIT needle=existing lines=2070-2084 2070 | 'ok' => false, 2071 | 'reason' => 'missing_WorklogChoicePendingStore', 2072 | ]; 2073 | } 2074 | 2075 | $store = new WorklogChoicePendingStore(); 2076 | 2077 | $first = is_array($existingTodayCandidates[0] ?? null) ? $existingTodayCandidates[0] : []; 2078 | 2079 | $payload = [ 2080 | 'trace_id' => (string)($params['trace_id'] ?? ''), 2081 | 'session_id' => (string)($params['session_id'] ?? ''), 2082 | 'original_query' => (string)($params['raw_query'] ?? $params['query'] ?? $params['original_query'] ?? ''), 2083 | 'parsed' => $parsed, 2084 | 'existing_candidates' => $existingTodayCandidates, ### HIT needle=existing lines=2077-2091 2077 | $first = is_array($existingTodayCandidates[0] ?? null) ? $existingTodayCandidates[0] : []; 2078 | 2079 | $payload = [ 2080 | 'trace_id' => (string)($params['trace_id'] ?? ''), 2081 | 'session_id' => (string)($params['session_id'] ?? ''), 2082 | 'original_query' => (string)($params['raw_query'] ?? $params['query'] ?? $params['original_query'] ?? ''), 2083 | 'parsed' => $parsed, 2084 | 'existing_candidates' => $existingTodayCandidates, 2085 | 'target_worklog_id' => (int)($first['id'] ?? 0), 2086 | 'target_project_id' => (int)($first['project_id'] ?? 0), 2087 | 'source' => 'worklog_existing_today_preflight_v1', 2088 | ]; 2089 | 2090 | return $store->save($payload); 2091 | } catch (Throwable $e) { ### HIT needle=existing lines=2080-2094 2080 | 'trace_id' => (string)($params['trace_id'] ?? ''), 2081 | 'session_id' => (string)($params['session_id'] ?? ''), 2082 | 'original_query' => (string)($params['raw_query'] ?? $params['query'] ?? $params['original_query'] ?? ''), 2083 | 'parsed' => $parsed, 2084 | 'existing_candidates' => $existingTodayCandidates, 2085 | 'target_worklog_id' => (int)($first['id'] ?? 0), 2086 | 'target_project_id' => (int)($first['project_id'] ?? 0), 2087 | 'source' => 'worklog_existing_today_preflight_v1', 2088 | ]; 2089 | 2090 | return $store->save($payload); 2091 | } catch (Throwable $e) { 2092 | return [ 2093 | 'ok' => false, 2094 | 'reason' => 'exception:' . $e->getMessage(), ### HIT needle=save lines=2083-2097 2083 | 'parsed' => $parsed, 2084 | 'existing_candidates' => $existingTodayCandidates, 2085 | 'target_worklog_id' => (int)($first['id'] ?? 0), 2086 | 'target_project_id' => (int)($first['project_id'] ?? 0), 2087 | 'source' => 'worklog_existing_today_preflight_v1', 2088 | ]; 2089 | 2090 | return $store->save($payload); 2091 | } catch (Throwable $e) { 2092 | return [ 2093 | 'ok' => false, 2094 | 'reason' => 'exception:' . $e->getMessage(), 2095 | ]; 2096 | } 2097 | } ### HIT needle=return lines=2085-2099 2085 | 'target_worklog_id' => (int)($first['id'] ?? 0), 2086 | 'target_project_id' => (int)($first['project_id'] ?? 0), 2087 | 'source' => 'worklog_existing_today_preflight_v1', 2088 | ]; 2089 | 2090 | return $store->save($payload); 2091 | } catch (Throwable $e) { 2092 | return [ 2093 | 'ok' => false, 2094 | 'reason' => 'exception:' . $e->getMessage(), 2095 | ]; 2096 | } 2097 | } 2098 | 2099 | ### HIT needle=existing lines=2094-2108 2094 | 'reason' => 'exception:' . $e->getMessage(), 2095 | ]; 2096 | } 2097 | } 2098 | 2099 | 2100 | 2101 | // WORKLOGTOOL_EXISTING_CHECKER_MATCH_HELPER_V1 2102 | private function gptWorklogExistingCheckerMatchedV1(mixed $___result): bool 2103 | { 2104 | if (is_object($___result)) { 2105 | return true; 2106 | } 2107 | 2108 | if (!is_array($___result)) { ### HIT needle=existing lines=2095-2109 2095 | ]; 2096 | } 2097 | } 2098 | 2099 | 2100 | 2101 | // WORKLOGTOOL_EXISTING_CHECKER_MATCH_HELPER_V1 2102 | private function gptWorklogExistingCheckerMatchedV1(mixed $___result): bool 2103 | { 2104 | if (is_object($___result)) { 2105 | return true; 2106 | } 2107 | 2108 | if (!is_array($___result)) { 2109 | return false; ### HIT needle=return lines=2098-2112 2098 | 2099 | 2100 | 2101 | // WORKLOGTOOL_EXISTING_CHECKER_MATCH_HELPER_V1 2102 | private function gptWorklogExistingCheckerMatchedV1(mixed $___result): bool 2103 | { 2104 | if (is_object($___result)) { 2105 | return true; 2106 | } 2107 | 2108 | if (!is_array($___result)) { 2109 | return false; 2110 | } 2111 | 2112 | foreach (['found', 'matched', 'exists', 'existing'] as $___key) { ### HIT needle=return lines=2102-2116 2102 | private function gptWorklogExistingCheckerMatchedV1(mixed $___result): bool 2103 | { 2104 | if (is_object($___result)) { 2105 | return true; 2106 | } 2107 | 2108 | if (!is_array($___result)) { 2109 | return false; 2110 | } 2111 | 2112 | foreach (['found', 'matched', 'exists', 'existing'] as $___key) { 2113 | if (!empty($___result[$___key])) { 2114 | return true; 2115 | } 2116 | } ### HIT needle=existing lines=2105-2119 2105 | return true; 2106 | } 2107 | 2108 | if (!is_array($___result)) { 2109 | return false; 2110 | } 2111 | 2112 | foreach (['found', 'matched', 'exists', 'existing'] as $___key) { 2113 | if (!empty($___result[$___key])) { 2114 | return true; 2115 | } 2116 | } 2117 | 2118 | foreach (['rows', 'items', 'candidates', 'matches'] as $___key) { 2119 | if (isset($___result[$___key]) && is_array($___result[$___key]) && count($___result[$___key]) > 0) { ### HIT needle=return lines=2107-2121 2107 | 2108 | if (!is_array($___result)) { 2109 | return false; 2110 | } 2111 | 2112 | foreach (['found', 'matched', 'exists', 'existing'] as $___key) { 2113 | if (!empty($___result[$___key])) { 2114 | return true; 2115 | } 2116 | } 2117 | 2118 | foreach (['rows', 'items', 'candidates', 'matches'] as $___key) { 2119 | if (isset($___result[$___key]) && is_array($___result[$___key]) && count($___result[$___key]) > 0) { 2120 | return true; 2121 | } ### HIT needle=return lines=2113-2127 2113 | if (!empty($___result[$___key])) { 2114 | return true; 2115 | } 2116 | } 2117 | 2118 | foreach (['rows', 'items', 'candidates', 'matches'] as $___key) { 2119 | if (isset($___result[$___key]) && is_array($___result[$___key]) && count($___result[$___key]) > 0) { 2120 | return true; 2121 | } 2122 | } 2123 | 2124 | if (isset($___result['count']) && is_numeric($___result['count']) && (int)$___result['count'] > 0) { 2125 | return true; 2126 | } 2127 | ### HIT needle=return lines=2118-2132 2118 | foreach (['rows', 'items', 'candidates', 'matches'] as $___key) { 2119 | if (isset($___result[$___key]) && is_array($___result[$___key]) && count($___result[$___key]) > 0) { 2120 | return true; 2121 | } 2122 | } 2123 | 2124 | if (isset($___result['count']) && is_numeric($___result['count']) && (int)$___result['count'] > 0) { 2125 | return true; 2126 | } 2127 | 2128 | return false; 2129 | } 2130 | 2131 | 2132 | ### HIT needle=return lines=2121-2135 2121 | } 2122 | } 2123 | 2124 | if (isset($___result['count']) && is_numeric($___result['count']) && (int)$___result['count'] > 0) { 2125 | return true; 2126 | } 2127 | 2128 | return false; 2129 | } 2130 | 2131 | 2132 | 2133 | // WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2 2134 | private function gptWorklogDirectPendingServiceBridgeV2(array $___params): ?ToolResult 2135 | { ### HIT needle=pending lines=2126-2140 2126 | } 2127 | 2128 | return false; 2129 | } 2130 | 2131 | 2132 | 2133 | // WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2 2134 | private function gptWorklogDirectPendingServiceBridgeV2(array $___params): ?ToolResult 2135 | { 2136 | $___query = $this->gptWorklogBridgeExtractQueryV2($___params); 2137 | if ($___query === '') { 2138 | return null; 2139 | } 2140 | ### HIT needle=pending lines=2127-2141 2127 | 2128 | return false; 2129 | } 2130 | 2131 | 2132 | 2133 | // WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2 2134 | private function gptWorklogDirectPendingServiceBridgeV2(array $___params): ?ToolResult 2135 | { 2136 | $___query = $this->gptWorklogBridgeExtractQueryV2($___params); 2137 | if ($___query === '') { 2138 | return null; 2139 | } 2140 | 2141 | if (!$this->gptWorklogBridgeIsWriteIntentV2($___query)) { ### HIT needle=return lines=2131-2145 2131 | 2132 | 2133 | // WORKLOGTOOL_DIRECT_PENDING_SERVICE_BRIDGE_V2 2134 | private function gptWorklogDirectPendingServiceBridgeV2(array $___params): ?ToolResult 2135 | { 2136 | $___query = $this->gptWorklogBridgeExtractQueryV2($___params); 2137 | if ($___query === '') { 2138 | return null; 2139 | } 2140 | 2141 | if (!$this->gptWorklogBridgeIsWriteIntentV2($___query)) { 2142 | return null; 2143 | } 2144 | 2145 | $___checkerResult = $this->gptWorklogBridgeCheckExistingTodayV2($___query, $___params); ### HIT needle=return lines=2135-2149 2135 | { 2136 | $___query = $this->gptWorklogBridgeExtractQueryV2($___params); 2137 | if ($___query === '') { 2138 | return null; 2139 | } 2140 | 2141 | if (!$this->gptWorklogBridgeIsWriteIntentV2($___query)) { 2142 | return null; 2143 | } 2144 | 2145 | $___checkerResult = $this->gptWorklogBridgeCheckExistingTodayV2($___query, $___params); 2146 | if (!$this->gptWorklogBridgeMatchedV2($___checkerResult)) { 2147 | return null; 2148 | } 2149 | ### HIT needle=existing lines=2138-2152 2138 | return null; 2139 | } 2140 | 2141 | if (!$this->gptWorklogBridgeIsWriteIntentV2($___query)) { 2142 | return null; 2143 | } 2144 | 2145 | $___checkerResult = $this->gptWorklogBridgeCheckExistingTodayV2($___query, $___params); 2146 | if (!$this->gptWorklogBridgeMatchedV2($___checkerResult)) { 2147 | return null; 2148 | } 2149 | 2150 | $___pendingResult = $this->gptWorklogBridgeSavePendingChoiceV2($___query, $___params, $___checkerResult); 2151 | if (!($___pendingResult['ok'] ?? false)) { 2152 | return null; ### HIT needle=checker lines=2139-2153 2139 | } 2140 | 2141 | if (!$this->gptWorklogBridgeIsWriteIntentV2($___query)) { 2142 | return null; 2143 | } 2144 | 2145 | $___checkerResult = $this->gptWorklogBridgeCheckExistingTodayV2($___query, $___params); 2146 | if (!$this->gptWorklogBridgeMatchedV2($___checkerResult)) { 2147 | return null; 2148 | } 2149 | 2150 | $___pendingResult = $this->gptWorklogBridgeSavePendingChoiceV2($___query, $___params, $___checkerResult); 2151 | if (!($___pendingResult['ok'] ?? false)) { 2152 | return null; 2153 | } ### HIT needle=return lines=2140-2154 2140 | 2141 | if (!$this->gptWorklogBridgeIsWriteIntentV2($___query)) { 2142 | return null; 2143 | } 2144 | 2145 | $___checkerResult = $this->gptWorklogBridgeCheckExistingTodayV2($___query, $___params); 2146 | if (!$this->gptWorklogBridgeMatchedV2($___checkerResult)) { 2147 | return null; 2148 | } 2149 | 2150 | $___pendingResult = $this->gptWorklogBridgeSavePendingChoiceV2($___query, $___params, $___checkerResult); 2151 | if (!($___pendingResult['ok'] ?? false)) { 2152 | return null; 2153 | } 2154 | ### HIT needle=pending lines=2143-2157 2143 | } 2144 | 2145 | $___checkerResult = $this->gptWorklogBridgeCheckExistingTodayV2($___query, $___params); 2146 | if (!$this->gptWorklogBridgeMatchedV2($___checkerResult)) { 2147 | return null; 2148 | } 2149 | 2150 | $___pendingResult = $this->gptWorklogBridgeSavePendingChoiceV2($___query, $___params, $___checkerResult); 2151 | if (!($___pendingResult['ok'] ?? false)) { 2152 | return null; 2153 | } 2154 | 2155 | $___message = "이미 오늘 작업기록으로 보이는 내용이 있습니다.\n\n"; 2156 | $___message .= "1. 기존 기록에 추가\n"; 2157 | $___message .= "2. 새 작업기록으로 저장\n"; ### HIT needle=pending lines=2144-2158 2144 | 2145 | $___checkerResult = $this->gptWorklogBridgeCheckExistingTodayV2($___query, $___params); 2146 | if (!$this->gptWorklogBridgeMatchedV2($___checkerResult)) { 2147 | return null; 2148 | } 2149 | 2150 | $___pendingResult = $this->gptWorklogBridgeSavePendingChoiceV2($___query, $___params, $___checkerResult); 2151 | if (!($___pendingResult['ok'] ?? false)) { 2152 | return null; 2153 | } 2154 | 2155 | $___message = "이미 오늘 작업기록으로 보이는 내용이 있습니다.\n\n"; 2156 | $___message .= "1. 기존 기록에 추가\n"; 2157 | $___message .= "2. 새 작업기록으로 저장\n"; 2158 | $___message .= "3. 저장 안 함\n\n"; ### HIT needle=return lines=2145-2159 2145 | $___checkerResult = $this->gptWorklogBridgeCheckExistingTodayV2($___query, $___params); 2146 | if (!$this->gptWorklogBridgeMatchedV2($___checkerResult)) { 2147 | return null; 2148 | } 2149 | 2150 | $___pendingResult = $this->gptWorklogBridgeSavePendingChoiceV2($___query, $___params, $___checkerResult); 2151 | if (!($___pendingResult['ok'] ?? false)) { 2152 | return null; 2153 | } 2154 | 2155 | $___message = "이미 오늘 작업기록으로 보이는 내용이 있습니다.\n\n"; 2156 | $___message .= "1. 기존 기록에 추가\n"; 2157 | $___message .= "2. 새 작업기록으로 저장\n"; 2158 | $___message .= "3. 저장 안 함\n\n"; 2159 | $___message .= "번호로 선택해 주세요."; ### HIT needle=pending lines=2154-2168 2154 | 2155 | $___message = "이미 오늘 작업기록으로 보이는 내용이 있습니다.\n\n"; 2156 | $___message .= "1. 기존 기록에 추가\n"; 2157 | $___message .= "2. 새 작업기록으로 저장\n"; 2158 | $___message .= "3. 저장 안 함\n\n"; 2159 | $___message .= "번호로 선택해 주세요."; 2160 | 2161 | // WORKLOGTOOL_BRIDGE_PENDING_SAVED_FLAG_FIX_V2_SAFE_MESSAGE_TRACE 2162 | $___message .= "\n\n[pending_saved=true]"; 2163 | $___message .= "\n[pending_mode_service=true]"; 2164 | $___message .= "\n[service_real_write_trace=true]"; 2165 | $___message .= "\n[new_pending_created=true]"; 2166 | 2167 | return $this->gptWorklogBridgeMakeToolResultV2($___message, [ 2168 | 'pending_saved' => true, ### HIT needle=pending lines=2155-2169 2155 | $___message = "이미 오늘 작업기록으로 보이는 내용이 있습니다.\n\n"; 2156 | $___message .= "1. 기존 기록에 추가\n"; 2157 | $___message .= "2. 새 작업기록으로 저장\n"; 2158 | $___message .= "3. 저장 안 함\n\n"; 2159 | $___message .= "번호로 선택해 주세요."; 2160 | 2161 | // WORKLOGTOOL_BRIDGE_PENDING_SAVED_FLAG_FIX_V2_SAFE_MESSAGE_TRACE 2162 | $___message .= "\n\n[pending_saved=true]"; 2163 | $___message .= "\n[pending_mode_service=true]"; 2164 | $___message .= "\n[service_real_write_trace=true]"; 2165 | $___message .= "\n[new_pending_created=true]"; 2166 | 2167 | return $this->gptWorklogBridgeMakeToolResultV2($___message, [ 2168 | 'pending_saved' => true, 2169 | 'pending_mode' => 'service_direct_bridge_v2', ### HIT needle=pending lines=2156-2170 2156 | $___message .= "1. 기존 기록에 추가\n"; 2157 | $___message .= "2. 새 작업기록으로 저장\n"; 2158 | $___message .= "3. 저장 안 함\n\n"; 2159 | $___message .= "번호로 선택해 주세요."; 2160 | 2161 | // WORKLOGTOOL_BRIDGE_PENDING_SAVED_FLAG_FIX_V2_SAFE_MESSAGE_TRACE 2162 | $___message .= "\n\n[pending_saved=true]"; 2163 | $___message .= "\n[pending_mode_service=true]"; 2164 | $___message .= "\n[service_real_write_trace=true]"; 2165 | $___message .= "\n[new_pending_created=true]"; 2166 | 2167 | return $this->gptWorklogBridgeMakeToolResultV2($___message, [ 2168 | 'pending_saved' => true, 2169 | 'pending_mode' => 'service_direct_bridge_v2', 2170 | 'pending_result' => $___pendingResult, ### HIT needle=pending lines=2158-2172 2158 | $___message .= "3. 저장 안 함\n\n"; 2159 | $___message .= "번호로 선택해 주세요."; 2160 | 2161 | // WORKLOGTOOL_BRIDGE_PENDING_SAVED_FLAG_FIX_V2_SAFE_MESSAGE_TRACE 2162 | $___message .= "\n\n[pending_saved=true]"; 2163 | $___message .= "\n[pending_mode_service=true]"; 2164 | $___message .= "\n[service_real_write_trace=true]"; 2165 | $___message .= "\n[new_pending_created=true]"; 2166 | 2167 | return $this->gptWorklogBridgeMakeToolResultV2($___message, [ 2168 | 'pending_saved' => true, 2169 | 'pending_mode' => 'service_direct_bridge_v2', 2170 | 'pending_result' => $___pendingResult, 2171 | 'existing_checker' => $___checkerResult, 2172 | ]); ### HIT needle=return lines=2160-2174 2160 | 2161 | // WORKLOGTOOL_BRIDGE_PENDING_SAVED_FLAG_FIX_V2_SAFE_MESSAGE_TRACE 2162 | $___message .= "\n\n[pending_saved=true]"; 2163 | $___message .= "\n[pending_mode_service=true]"; 2164 | $___message .= "\n[service_real_write_trace=true]"; 2165 | $___message .= "\n[new_pending_created=true]"; 2166 | 2167 | return $this->gptWorklogBridgeMakeToolResultV2($___message, [ 2168 | 'pending_saved' => true, 2169 | 'pending_mode' => 'service_direct_bridge_v2', 2170 | 'pending_result' => $___pendingResult, 2171 | 'existing_checker' => $___checkerResult, 2172 | ]); 2173 | } 2174 | ### HIT needle=pending lines=2161-2175 2161 | // WORKLOGTOOL_BRIDGE_PENDING_SAVED_FLAG_FIX_V2_SAFE_MESSAGE_TRACE 2162 | $___message .= "\n\n[pending_saved=true]"; 2163 | $___message .= "\n[pending_mode_service=true]"; 2164 | $___message .= "\n[service_real_write_trace=true]"; 2165 | $___message .= "\n[new_pending_created=true]"; 2166 | 2167 | return $this->gptWorklogBridgeMakeToolResultV2($___message, [ 2168 | 'pending_saved' => true, 2169 | 'pending_mode' => 'service_direct_bridge_v2', 2170 | 'pending_result' => $___pendingResult, 2171 | 'existing_checker' => $___checkerResult, 2172 | ]); 2173 | } 2174 | 2175 | private function gptWorklogBridgeExtractQueryV2(array $___params): string ### HIT needle=pending lines=2162-2176 2162 | $___message .= "\n\n[pending_saved=true]"; 2163 | $___message .= "\n[pending_mode_service=true]"; 2164 | $___message .= "\n[service_real_write_trace=true]"; 2165 | $___message .= "\n[new_pending_created=true]"; 2166 | 2167 | return $this->gptWorklogBridgeMakeToolResultV2($___message, [ 2168 | 'pending_saved' => true, 2169 | 'pending_mode' => 'service_direct_bridge_v2', 2170 | 'pending_result' => $___pendingResult, 2171 | 'existing_checker' => $___checkerResult, 2172 | ]); 2173 | } 2174 | 2175 | private function gptWorklogBridgeExtractQueryV2(array $___params): string 2176 | { ### HIT needle=pending lines=2163-2177 2163 | $___message .= "\n[pending_mode_service=true]"; 2164 | $___message .= "\n[service_real_write_trace=true]"; 2165 | $___message .= "\n[new_pending_created=true]"; 2166 | 2167 | return $this->gptWorklogBridgeMakeToolResultV2($___message, [ 2168 | 'pending_saved' => true, 2169 | 'pending_mode' => 'service_direct_bridge_v2', 2170 | 'pending_result' => $___pendingResult, 2171 | 'existing_checker' => $___checkerResult, 2172 | ]); 2173 | } 2174 | 2175 | private function gptWorklogBridgeExtractQueryV2(array $___params): string 2176 | { 2177 | foreach (['query', 'message', 'q', 'text', 'input', 'raw', 'raw_query', 'user_query', 'prompt', 'content'] as $___key) { ### HIT needle=existing lines=2164-2178 2164 | $___message .= "\n[service_real_write_trace=true]"; 2165 | $___message .= "\n[new_pending_created=true]"; 2166 | 2167 | return $this->gptWorklogBridgeMakeToolResultV2($___message, [ 2168 | 'pending_saved' => true, 2169 | 'pending_mode' => 'service_direct_bridge_v2', 2170 | 'pending_result' => $___pendingResult, 2171 | 'existing_checker' => $___checkerResult, 2172 | ]); 2173 | } 2174 | 2175 | private function gptWorklogBridgeExtractQueryV2(array $___params): string 2176 | { 2177 | foreach (['query', 'message', 'q', 'text', 'input', 'raw', 'raw_query', 'user_query', 'prompt', 'content'] as $___key) { 2178 | if (isset($___params[$___key]) && is_scalar($___params[$___key])) { ### HIT needle=return lines=2174-2188 2174 | 2175 | private function gptWorklogBridgeExtractQueryV2(array $___params): string 2176 | { 2177 | foreach (['query', 'message', 'q', 'text', 'input', 'raw', 'raw_query', 'user_query', 'prompt', 'content'] as $___key) { 2178 | if (isset($___params[$___key]) && is_scalar($___params[$___key])) { 2179 | $___v = trim((string)$___params[$___key]); 2180 | if ($___v !== '') { 2181 | return $___v; 2182 | } 2183 | } 2184 | } 2185 | 2186 | foreach ($___params as $___v) { 2187 | if (is_array($___v)) { 2188 | $___nested = $this->gptWorklogBridgeExtractQueryV2($___v); ### HIT needle=return lines=2183-2197 2183 | } 2184 | } 2185 | 2186 | foreach ($___params as $___v) { 2187 | if (is_array($___v)) { 2188 | $___nested = $this->gptWorklogBridgeExtractQueryV2($___v); 2189 | if ($___nested !== '') { 2190 | return $___nested; 2191 | } 2192 | } 2193 | } 2194 | 2195 | return ''; 2196 | } 2197 | ### HIT needle=return lines=2188-2202 2188 | $___nested = $this->gptWorklogBridgeExtractQueryV2($___v); 2189 | if ($___nested !== '') { 2190 | return $___nested; 2191 | } 2192 | } 2193 | } 2194 | 2195 | return ''; 2196 | } 2197 | 2198 | private function gptWorklogBridgeIsWriteIntentV2(string $___query): bool 2199 | { 2200 | if ( 2201 | str_contains($___query, '리스트') || 2202 | str_contains($___query, '목록') || ### HIT needle=return lines=2200-2214 2200 | if ( 2201 | str_contains($___query, '리스트') || 2202 | str_contains($___query, '목록') || 2203 | str_contains($___query, '조회') || 2204 | str_contains($___query, '보여') || 2205 | str_contains($___query, '확인') 2206 | ) { 2207 | return false; 2208 | } 2209 | 2210 | return 2211 | str_contains($___query, '했어') || 2212 | str_contains($___query, '다녀') || 2213 | str_contains($___query, '작업기록') || 2214 | str_contains($___query, '기록') || ### HIT needle=return lines=2203-2217 2203 | str_contains($___query, '조회') || 2204 | str_contains($___query, '보여') || 2205 | str_contains($___query, '확인') 2206 | ) { 2207 | return false; 2208 | } 2209 | 2210 | return 2211 | str_contains($___query, '했어') || 2212 | str_contains($___query, '다녀') || 2213 | str_contains($___query, '작업기록') || 2214 | str_contains($___query, '기록') || 2215 | str_contains($___query, '남겨') || 2216 | str_contains($___query, '저장') || 2217 | str_contains($___query, '현장가서'); ### HIT needle=matched lines=2213-2227 2213 | str_contains($___query, '작업기록') || 2214 | str_contains($___query, '기록') || 2215 | str_contains($___query, '남겨') || 2216 | str_contains($___query, '저장') || 2217 | str_contains($___query, '현장가서'); 2218 | } 2219 | 2220 | private function gptWorklogBridgeMatchedV2(mixed $___result): bool 2221 | { 2222 | if (is_object($___result)) { 2223 | return true; 2224 | } 2225 | 2226 | if (!is_array($___result)) { 2227 | return false; ### HIT needle=return lines=2216-2230 2216 | str_contains($___query, '저장') || 2217 | str_contains($___query, '현장가서'); 2218 | } 2219 | 2220 | private function gptWorklogBridgeMatchedV2(mixed $___result): bool 2221 | { 2222 | if (is_object($___result)) { 2223 | return true; 2224 | } 2225 | 2226 | if (!is_array($___result)) { 2227 | return false; 2228 | } 2229 | 2230 | foreach (['found', 'matched', 'exists', 'existing'] as $___key) { ### HIT needle=return lines=2220-2234 2220 | private function gptWorklogBridgeMatchedV2(mixed $___result): bool 2221 | { 2222 | if (is_object($___result)) { 2223 | return true; 2224 | } 2225 | 2226 | if (!is_array($___result)) { 2227 | return false; 2228 | } 2229 | 2230 | foreach (['found', 'matched', 'exists', 'existing'] as $___key) { 2231 | if (!empty($___result[$___key])) { 2232 | return true; 2233 | } 2234 | } ### HIT needle=existing lines=2223-2237 2223 | return true; 2224 | } 2225 | 2226 | if (!is_array($___result)) { 2227 | return false; 2228 | } 2229 | 2230 | foreach (['found', 'matched', 'exists', 'existing'] as $___key) { 2231 | if (!empty($___result[$___key])) { 2232 | return true; 2233 | } 2234 | } 2235 | 2236 | foreach (['rows', 'items', 'matches', 'candidates'] as $___key) { 2237 | if (isset($___result[$___key]) && is_array($___result[$___key]) && count($___result[$___key]) > 0) { ### HIT needle=return lines=2225-2239 2225 | 2226 | if (!is_array($___result)) { 2227 | return false; 2228 | } 2229 | 2230 | foreach (['found', 'matched', 'exists', 'existing'] as $___key) { 2231 | if (!empty($___result[$___key])) { 2232 | return true; 2233 | } 2234 | } 2235 | 2236 | foreach (['rows', 'items', 'matches', 'candidates'] as $___key) { 2237 | if (isset($___result[$___key]) && is_array($___result[$___key]) && count($___result[$___key]) > 0) { 2238 | return true; 2239 | } ### HIT needle=return lines=2231-2245 2231 | if (!empty($___result[$___key])) { 2232 | return true; 2233 | } 2234 | } 2235 | 2236 | foreach (['rows', 'items', 'matches', 'candidates'] as $___key) { 2237 | if (isset($___result[$___key]) && is_array($___result[$___key]) && count($___result[$___key]) > 0) { 2238 | return true; 2239 | } 2240 | } 2241 | 2242 | return isset($___result['count']) && is_numeric($___result['count']) && (int)$___result['count'] > 0; 2243 | } 2244 | 2245 | private function gptWorklogBridgeCheckExistingTodayV2(string $___query, array $___params): array ### HIT needle=return lines=2235-2249 2235 | 2236 | foreach (['rows', 'items', 'matches', 'candidates'] as $___key) { 2237 | if (isset($___result[$___key]) && is_array($___result[$___key]) && count($___result[$___key]) > 0) { 2238 | return true; 2239 | } 2240 | } 2241 | 2242 | return isset($___result['count']) && is_numeric($___result['count']) && (int)$___result['count'] > 0; 2243 | } 2244 | 2245 | private function gptWorklogBridgeCheckExistingTodayV2(string $___query, array $___params): array 2246 | { 2247 | $___root = dirname(__DIR__, 2); 2248 | 2249 | foreach ([ ### HIT needle=existing lines=2238-2252 2238 | return true; 2239 | } 2240 | } 2241 | 2242 | return isset($___result['count']) && is_numeric($___result['count']) && (int)$___result['count'] > 0; 2243 | } 2244 | 2245 | private function gptWorklogBridgeCheckExistingTodayV2(string $___query, array $___params): array 2246 | { 2247 | $___root = dirname(__DIR__, 2); 2248 | 2249 | foreach ([ 2250 | $___root . '/engine_modules/tools/worklog/WorklogExistingTodayChecker.php', 2251 | $___root . '/engine_modules/tools/worklog/WorklogPendingChoiceService.php', 2252 | $___root . '/engine_modules/tools/worklog/WorklogChoicePendingStore.php', ### HIT needle=existing lines=2243-2257 2243 | } 2244 | 2245 | private function gptWorklogBridgeCheckExistingTodayV2(string $___query, array $___params): array 2246 | { 2247 | $___root = dirname(__DIR__, 2); 2248 | 2249 | foreach ([ 2250 | $___root . '/engine_modules/tools/worklog/WorklogExistingTodayChecker.php', 2251 | $___root . '/engine_modules/tools/worklog/WorklogPendingChoiceService.php', 2252 | $___root . '/engine_modules/tools/worklog/WorklogChoicePendingStore.php', 2253 | ] as $___file) { 2254 | if (is_file($___file)) { 2255 | try { 2256 | require_once $___file; 2257 | } catch (\Throwable $___e) { ### HIT needle=pending lines=2244-2258 2244 | 2245 | private function gptWorklogBridgeCheckExistingTodayV2(string $___query, array $___params): array 2246 | { 2247 | $___root = dirname(__DIR__, 2); 2248 | 2249 | foreach ([ 2250 | $___root . '/engine_modules/tools/worklog/WorklogExistingTodayChecker.php', 2251 | $___root . '/engine_modules/tools/worklog/WorklogPendingChoiceService.php', 2252 | $___root . '/engine_modules/tools/worklog/WorklogChoicePendingStore.php', 2253 | ] as $___file) { 2254 | if (is_file($___file)) { 2255 | try { 2256 | require_once $___file; 2257 | } catch (\Throwable $___e) { 2258 | } ### HIT needle=pending lines=2245-2259 2245 | private function gptWorklogBridgeCheckExistingTodayV2(string $___query, array $___params): array 2246 | { 2247 | $___root = dirname(__DIR__, 2); 2248 | 2249 | foreach ([ 2250 | $___root . '/engine_modules/tools/worklog/WorklogExistingTodayChecker.php', 2251 | $___root . '/engine_modules/tools/worklog/WorklogPendingChoiceService.php', 2252 | $___root . '/engine_modules/tools/worklog/WorklogChoicePendingStore.php', 2253 | ] as $___file) { 2254 | if (is_file($___file)) { 2255 | try { 2256 | require_once $___file; 2257 | } catch (\Throwable $___e) { 2258 | } 2259 | } ### HIT needle=existing lines=2255-2269 2255 | try { 2256 | require_once $___file; 2257 | } catch (\Throwable $___e) { 2258 | } 2259 | } 2260 | } 2261 | 2262 | if (!class_exists('WorklogExistingTodayChecker')) { 2263 | return ['found' => false, 'reason' => 'checker_class_missing']; 2264 | } 2265 | 2266 | try { 2267 | $___checker = $this->gptWorklogBridgeInstantiateV2('WorklogExistingTodayChecker'); 2268 | 2269 | foreach (['check', 'find', 'findExisting', 'findToday', 'findExistingToday', 'run'] as $___method) { ### HIT needle=return lines=2256-2270 2256 | require_once $___file; 2257 | } catch (\Throwable $___e) { 2258 | } 2259 | } 2260 | } 2261 | 2262 | if (!class_exists('WorklogExistingTodayChecker')) { 2263 | return ['found' => false, 'reason' => 'checker_class_missing']; 2264 | } 2265 | 2266 | try { 2267 | $___checker = $this->gptWorklogBridgeInstantiateV2('WorklogExistingTodayChecker'); 2268 | 2269 | foreach (['check', 'find', 'findExisting', 'findToday', 'findExistingToday', 'run'] as $___method) { 2270 | if (!is_object($___checker) || !method_exists($___checker, $___method)) { ### HIT needle=existing lines=2260-2274 2260 | } 2261 | 2262 | if (!class_exists('WorklogExistingTodayChecker')) { 2263 | return ['found' => false, 'reason' => 'checker_class_missing']; 2264 | } 2265 | 2266 | try { 2267 | $___checker = $this->gptWorklogBridgeInstantiateV2('WorklogExistingTodayChecker'); 2268 | 2269 | foreach (['check', 'find', 'findExisting', 'findToday', 'findExistingToday', 'run'] as $___method) { 2270 | if (!is_object($___checker) || !method_exists($___checker, $___method)) { 2271 | continue; 2272 | } 2273 | 2274 | foreach ([[$___query], [['query' => $___query]], [['query' => $___query, 'params' => $___params]]] as $___args) { ### HIT needle=existing lines=2262-2276 2262 | if (!class_exists('WorklogExistingTodayChecker')) { 2263 | return ['found' => false, 'reason' => 'checker_class_missing']; 2264 | } 2265 | 2266 | try { 2267 | $___checker = $this->gptWorklogBridgeInstantiateV2('WorklogExistingTodayChecker'); 2268 | 2269 | foreach (['check', 'find', 'findExisting', 'findToday', 'findExistingToday', 'run'] as $___method) { 2270 | if (!is_object($___checker) || !method_exists($___checker, $___method)) { 2271 | continue; 2272 | } 2273 | 2274 | foreach ([[$___query], [['query' => $___query]], [['query' => $___query, 'params' => $___params]]] as $___args) { 2275 | try { 2276 | $___ret = $___checker->{$___method}(...$___args); ### HIT needle=checker lines=2263-2277 2263 | return ['found' => false, 'reason' => 'checker_class_missing']; 2264 | } 2265 | 2266 | try { 2267 | $___checker = $this->gptWorklogBridgeInstantiateV2('WorklogExistingTodayChecker'); 2268 | 2269 | foreach (['check', 'find', 'findExisting', 'findToday', 'findExistingToday', 'run'] as $___method) { 2270 | if (!is_object($___checker) || !method_exists($___checker, $___method)) { 2271 | continue; 2272 | } 2273 | 2274 | foreach ([[$___query], [['query' => $___query]], [['query' => $___query, 'params' => $___params]]] as $___args) { 2275 | try { 2276 | $___ret = $___checker->{$___method}(...$___args); 2277 | if ($this->gptWorklogBridgeMatchedV2($___ret)) { ### HIT needle=checker lines=2269-2283 2269 | foreach (['check', 'find', 'findExisting', 'findToday', 'findExistingToday', 'run'] as $___method) { 2270 | if (!is_object($___checker) || !method_exists($___checker, $___method)) { 2271 | continue; 2272 | } 2273 | 2274 | foreach ([[$___query], [['query' => $___query]], [['query' => $___query, 'params' => $___params]]] as $___args) { 2275 | try { 2276 | $___ret = $___checker->{$___method}(...$___args); 2277 | if ($this->gptWorklogBridgeMatchedV2($___ret)) { 2278 | return is_array($___ret) ? $___ret : ['found' => true, 'object' => get_class($___ret)]; 2279 | } 2280 | } catch (\Throwable $___e) { 2281 | } 2282 | } 2283 | } ### HIT needle=matched lines=2270-2284 2270 | if (!is_object($___checker) || !method_exists($___checker, $___method)) { 2271 | continue; 2272 | } 2273 | 2274 | foreach ([[$___query], [['query' => $___query]], [['query' => $___query, 'params' => $___params]]] as $___args) { 2275 | try { 2276 | $___ret = $___checker->{$___method}(...$___args); 2277 | if ($this->gptWorklogBridgeMatchedV2($___ret)) { 2278 | return is_array($___ret) ? $___ret : ['found' => true, 'object' => get_class($___ret)]; 2279 | } 2280 | } catch (\Throwable $___e) { 2281 | } 2282 | } 2283 | } 2284 | } catch (\Throwable $___e) { ### HIT needle=return lines=2271-2285 2271 | continue; 2272 | } 2273 | 2274 | foreach ([[$___query], [['query' => $___query]], [['query' => $___query, 'params' => $___params]]] as $___args) { 2275 | try { 2276 | $___ret = $___checker->{$___method}(...$___args); 2277 | if ($this->gptWorklogBridgeMatchedV2($___ret)) { 2278 | return is_array($___ret) ? $___ret : ['found' => true, 'object' => get_class($___ret)]; 2279 | } 2280 | } catch (\Throwable $___e) { 2281 | } 2282 | } 2283 | } 2284 | } catch (\Throwable $___e) { 2285 | return ['found' => false, 'reason' => 'checker_exception', 'error' => $___e->getMessage()]; ### HIT needle=return lines=2278-2292 2278 | return is_array($___ret) ? $___ret : ['found' => true, 'object' => get_class($___ret)]; 2279 | } 2280 | } catch (\Throwable $___e) { 2281 | } 2282 | } 2283 | } 2284 | } catch (\Throwable $___e) { 2285 | return ['found' => false, 'reason' => 'checker_exception', 'error' => $___e->getMessage()]; 2286 | } 2287 | 2288 | return ['found' => false, 'reason' => 'checker_no_match']; 2289 | } 2290 | 2291 | private function gptWorklogBridgeSavePendingChoiceV2(string $___query, array $___params, array $___checkerResult): array 2292 | { ### HIT needle=return lines=2281-2295 2281 | } 2282 | } 2283 | } 2284 | } catch (\Throwable $___e) { 2285 | return ['found' => false, 'reason' => 'checker_exception', 'error' => $___e->getMessage()]; 2286 | } 2287 | 2288 | return ['found' => false, 'reason' => 'checker_no_match']; 2289 | } 2290 | 2291 | private function gptWorklogBridgeSavePendingChoiceV2(string $___query, array $___params, array $___checkerResult): array 2292 | { 2293 | $___root = dirname(__DIR__, 2); 2294 | $___pendingDir = $___root . '/data/pending_worklog_choices'; 2295 | $___before = is_dir($___pendingDir) ? count(glob($___pendingDir . '/*.json') ?: []) : 0; ### HIT needle=pending lines=2284-2298 2284 | } catch (\Throwable $___e) { 2285 | return ['found' => false, 'reason' => 'checker_exception', 'error' => $___e->getMessage()]; 2286 | } 2287 | 2288 | return ['found' => false, 'reason' => 'checker_no_match']; 2289 | } 2290 | 2291 | private function gptWorklogBridgeSavePendingChoiceV2(string $___query, array $___params, array $___checkerResult): array 2292 | { 2293 | $___root = dirname(__DIR__, 2); 2294 | $___pendingDir = $___root . '/data/pending_worklog_choices'; 2295 | $___before = is_dir($___pendingDir) ? count(glob($___pendingDir . '/*.json') ?: []) : 0; 2296 | 2297 | foreach ([ 2298 | $___root . '/engine_modules/tools/worklog/WorklogPendingChoiceService.php', ### HIT needle=pending lines=2287-2301 2287 | 2288 | return ['found' => false, 'reason' => 'checker_no_match']; 2289 | } 2290 | 2291 | private function gptWorklogBridgeSavePendingChoiceV2(string $___query, array $___params, array $___checkerResult): array 2292 | { 2293 | $___root = dirname(__DIR__, 2); 2294 | $___pendingDir = $___root . '/data/pending_worklog_choices'; 2295 | $___before = is_dir($___pendingDir) ? count(glob($___pendingDir . '/*.json') ?: []) : 0; 2296 | 2297 | foreach ([ 2298 | $___root . '/engine_modules/tools/worklog/WorklogPendingChoiceService.php', 2299 | $___root . '/engine_modules/tools/worklog/WorklogChoicePendingStore.php', 2300 | ] as $___file) { 2301 | if (is_file($___file)) { ### HIT needle=pending lines=2288-2302 2288 | return ['found' => false, 'reason' => 'checker_no_match']; 2289 | } 2290 | 2291 | private function gptWorklogBridgeSavePendingChoiceV2(string $___query, array $___params, array $___checkerResult): array 2292 | { 2293 | $___root = dirname(__DIR__, 2); 2294 | $___pendingDir = $___root . '/data/pending_worklog_choices'; 2295 | $___before = is_dir($___pendingDir) ? count(glob($___pendingDir . '/*.json') ?: []) : 0; 2296 | 2297 | foreach ([ 2298 | $___root . '/engine_modules/tools/worklog/WorklogPendingChoiceService.php', 2299 | $___root . '/engine_modules/tools/worklog/WorklogChoicePendingStore.php', 2300 | ] as $___file) { 2301 | if (is_file($___file)) { 2302 | try { ### HIT needle=pending lines=2291-2305 2291 | private function gptWorklogBridgeSavePendingChoiceV2(string $___query, array $___params, array $___checkerResult): array 2292 | { 2293 | $___root = dirname(__DIR__, 2); 2294 | $___pendingDir = $___root . '/data/pending_worklog_choices'; 2295 | $___before = is_dir($___pendingDir) ? count(glob($___pendingDir . '/*.json') ?: []) : 0; 2296 | 2297 | foreach ([ 2298 | $___root . '/engine_modules/tools/worklog/WorklogPendingChoiceService.php', 2299 | $___root . '/engine_modules/tools/worklog/WorklogChoicePendingStore.php', 2300 | ] as $___file) { 2301 | if (is_file($___file)) { 2302 | try { 2303 | require_once $___file; 2304 | } catch (\Throwable $___e) { 2305 | } ### HIT needle=pending lines=2292-2306 2292 | { 2293 | $___root = dirname(__DIR__, 2); 2294 | $___pendingDir = $___root . '/data/pending_worklog_choices'; 2295 | $___before = is_dir($___pendingDir) ? count(glob($___pendingDir . '/*.json') ?: []) : 0; 2296 | 2297 | foreach ([ 2298 | $___root . '/engine_modules/tools/worklog/WorklogPendingChoiceService.php', 2299 | $___root . '/engine_modules/tools/worklog/WorklogChoicePendingStore.php', 2300 | ] as $___file) { 2301 | if (is_file($___file)) { 2302 | try { 2303 | require_once $___file; 2304 | } catch (\Throwable $___e) { 2305 | } 2306 | } ### HIT needle=checker lines=2306-2320 2306 | } 2307 | } 2308 | 2309 | $___payload = [ 2310 | 'query' => $___query, 2311 | 'raw_query' => $___query, 2312 | 'params' => $___params, 2313 | 'checker_result' => $___checkerResult, 2314 | 'existing' => $___checkerResult, 2315 | 'source' => 'WorklogTool::direct_pending_service_bridge_v2', 2316 | 'mode' => 'service_direct_bridge_v2', 2317 | 'created_at' => date('Y-m-d H:i:s'), 2318 | ]; 2319 | 2320 | $___classes = []; ### HIT needle=existing lines=2307-2321 2307 | } 2308 | 2309 | $___payload = [ 2310 | 'query' => $___query, 2311 | 'raw_query' => $___query, 2312 | 'params' => $___params, 2313 | 'checker_result' => $___checkerResult, 2314 | 'existing' => $___checkerResult, 2315 | 'source' => 'WorklogTool::direct_pending_service_bridge_v2', 2316 | 'mode' => 'service_direct_bridge_v2', 2317 | 'created_at' => date('Y-m-d H:i:s'), 2318 | ]; 2319 | 2320 | $___classes = []; 2321 | if (class_exists('WorklogPendingChoiceService')) { ### HIT needle=pending lines=2308-2322 2308 | 2309 | $___payload = [ 2310 | 'query' => $___query, 2311 | 'raw_query' => $___query, 2312 | 'params' => $___params, 2313 | 'checker_result' => $___checkerResult, 2314 | 'existing' => $___checkerResult, 2315 | 'source' => 'WorklogTool::direct_pending_service_bridge_v2', 2316 | 'mode' => 'service_direct_bridge_v2', 2317 | 'created_at' => date('Y-m-d H:i:s'), 2318 | ]; 2319 | 2320 | $___classes = []; 2321 | if (class_exists('WorklogPendingChoiceService')) { 2322 | $___classes[] = 'WorklogPendingChoiceService'; ### HIT needle=create lines=2310-2324 2310 | 'query' => $___query, 2311 | 'raw_query' => $___query, 2312 | 'params' => $___params, 2313 | 'checker_result' => $___checkerResult, 2314 | 'existing' => $___checkerResult, 2315 | 'source' => 'WorklogTool::direct_pending_service_bridge_v2', 2316 | 'mode' => 'service_direct_bridge_v2', 2317 | 'created_at' => date('Y-m-d H:i:s'), 2318 | ]; 2319 | 2320 | $___classes = []; 2321 | if (class_exists('WorklogPendingChoiceService')) { 2322 | $___classes[] = 'WorklogPendingChoiceService'; 2323 | } 2324 | if (class_exists('WorklogChoicePendingStore')) { ### HIT needle=pending lines=2314-2328 2314 | 'existing' => $___checkerResult, 2315 | 'source' => 'WorklogTool::direct_pending_service_bridge_v2', 2316 | 'mode' => 'service_direct_bridge_v2', 2317 | 'created_at' => date('Y-m-d H:i:s'), 2318 | ]; 2319 | 2320 | $___classes = []; 2321 | if (class_exists('WorklogPendingChoiceService')) { 2322 | $___classes[] = 'WorklogPendingChoiceService'; 2323 | } 2324 | if (class_exists('WorklogChoicePendingStore')) { 2325 | $___classes[] = 'WorklogChoicePendingStore'; 2326 | } 2327 | 2328 | $___errors = []; ### HIT needle=pending lines=2315-2329 2315 | 'source' => 'WorklogTool::direct_pending_service_bridge_v2', 2316 | 'mode' => 'service_direct_bridge_v2', 2317 | 'created_at' => date('Y-m-d H:i:s'), 2318 | ]; 2319 | 2320 | $___classes = []; 2321 | if (class_exists('WorklogPendingChoiceService')) { 2322 | $___classes[] = 'WorklogPendingChoiceService'; 2323 | } 2324 | if (class_exists('WorklogChoicePendingStore')) { 2325 | $___classes[] = 'WorklogChoicePendingStore'; 2326 | } 2327 | 2328 | $___errors = []; 2329 | ### HIT needle=pending lines=2317-2331 2317 | 'created_at' => date('Y-m-d H:i:s'), 2318 | ]; 2319 | 2320 | $___classes = []; 2321 | if (class_exists('WorklogPendingChoiceService')) { 2322 | $___classes[] = 'WorklogPendingChoiceService'; 2323 | } 2324 | if (class_exists('WorklogChoicePendingStore')) { 2325 | $___classes[] = 'WorklogChoicePendingStore'; 2326 | } 2327 | 2328 | $___errors = []; 2329 | 2330 | foreach ($___classes as $___class) { 2331 | try { ### HIT needle=pending lines=2318-2332 2318 | ]; 2319 | 2320 | $___classes = []; 2321 | if (class_exists('WorklogPendingChoiceService')) { 2322 | $___classes[] = 'WorklogPendingChoiceService'; 2323 | } 2324 | if (class_exists('WorklogChoicePendingStore')) { 2325 | $___classes[] = 'WorklogChoicePendingStore'; 2326 | } 2327 | 2328 | $___errors = []; 2329 | 2330 | foreach ($___classes as $___class) { 2331 | try { 2332 | $___obj = $this->gptWorklogBridgeInstantiateV2($___class); ### HIT needle=pending lines=2335-2349 2335 | } 2336 | 2337 | $___methods = get_class_methods($___obj) ?: []; 2338 | usort($___methods, static function ($___a, $___b) { 2339 | $___score = static function ($___m) { 2340 | $___m = strtolower((string)$___m); 2341 | $___s = 0; 2342 | foreach (['create', 'save', 'write', 'pending', 'choice', 'prepare', 'open'] as $___kw) { 2343 | if (str_contains($___m, $___kw)) { 2344 | $___s += 10; 2345 | } 2346 | } 2347 | if ($___m === 'create') { 2348 | $___s += 30; 2349 | } ### HIT needle=create lines=2340-2354 2340 | $___m = strtolower((string)$___m); 2341 | $___s = 0; 2342 | foreach (['create', 'save', 'write', 'pending', 'choice', 'prepare', 'open'] as $___kw) { 2343 | if (str_contains($___m, $___kw)) { 2344 | $___s += 10; 2345 | } 2346 | } 2347 | if ($___m === 'create') { 2348 | $___s += 30; 2349 | } 2350 | if ($___m === 'save') { 2351 | $___s += 25; 2352 | } 2353 | return -$___s; 2354 | }; ### HIT needle=save lines=2343-2357 2343 | if (str_contains($___m, $___kw)) { 2344 | $___s += 10; 2345 | } 2346 | } 2347 | if ($___m === 'create') { 2348 | $___s += 30; 2349 | } 2350 | if ($___m === 'save') { 2351 | $___s += 25; 2352 | } 2353 | return -$___s; 2354 | }; 2355 | return $___score($___a) <=> $___score($___b); 2356 | }); 2357 | ### HIT needle=return lines=2346-2360 2346 | } 2347 | if ($___m === 'create') { 2348 | $___s += 30; 2349 | } 2350 | if ($___m === 'save') { 2351 | $___s += 25; 2352 | } 2353 | return -$___s; 2354 | }; 2355 | return $___score($___a) <=> $___score($___b); 2356 | }); 2357 | 2358 | foreach ($___methods as $___method) { 2359 | if (!preg_match('/create|save|write|pending|choice|prepare|open/i', $___method)) { 2360 | continue; ### HIT needle=return lines=2348-2362 2348 | $___s += 30; 2349 | } 2350 | if ($___m === 'save') { 2351 | $___s += 25; 2352 | } 2353 | return -$___s; 2354 | }; 2355 | return $___score($___a) <=> $___score($___b); 2356 | }); 2357 | 2358 | foreach ($___methods as $___method) { 2359 | if (!preg_match('/create|save|write|pending|choice|prepare|open/i', $___method)) { 2360 | continue; 2361 | } 2362 | if (str_starts_with($___method, '__')) { ### HIT needle=pending lines=2352-2366 2352 | } 2353 | return -$___s; 2354 | }; 2355 | return $___score($___a) <=> $___score($___b); 2356 | }); 2357 | 2358 | foreach ($___methods as $___method) { 2359 | if (!preg_match('/create|save|write|pending|choice|prepare|open/i', $___method)) { 2360 | continue; 2361 | } 2362 | if (str_starts_with($___method, '__')) { 2363 | continue; 2364 | } 2365 | 2366 | try { ### HIT needle=checker lines=2365-2379 2365 | 2366 | try { 2367 | $___ref = new \ReflectionMethod($___obj, $___method); 2368 | if (!$___ref->isPublic() || $___ref->isStatic()) { 2369 | continue; 2370 | } 2371 | 2372 | $___args = $this->gptWorklogBridgeBuildArgsV2($___ref, $___payload, $___query, $___checkerResult); 2373 | $___ret = $___ref->invokeArgs($___obj, $___args); 2374 | 2375 | $___after = is_dir($___pendingDir) ? count(glob($___pendingDir . '/*.json') ?: []) : 0; 2376 | 2377 | if ($___after > $___before || $this->gptWorklogBridgeResultOkV2($___ret)) { 2378 | return [ 2379 | 'ok' => true, ### HIT needle=pending lines=2368-2382 2368 | if (!$___ref->isPublic() || $___ref->isStatic()) { 2369 | continue; 2370 | } 2371 | 2372 | $___args = $this->gptWorklogBridgeBuildArgsV2($___ref, $___payload, $___query, $___checkerResult); 2373 | $___ret = $___ref->invokeArgs($___obj, $___args); 2374 | 2375 | $___after = is_dir($___pendingDir) ? count(glob($___pendingDir . '/*.json') ?: []) : 0; 2376 | 2377 | if ($___after > $___before || $this->gptWorklogBridgeResultOkV2($___ret)) { 2378 | return [ 2379 | 'ok' => true, 2380 | 'class' => $___class, 2381 | 'method' => $___method, 2382 | 'before_count' => $___before, ### HIT needle=return lines=2371-2385 2371 | 2372 | $___args = $this->gptWorklogBridgeBuildArgsV2($___ref, $___payload, $___query, $___checkerResult); 2373 | $___ret = $___ref->invokeArgs($___obj, $___args); 2374 | 2375 | $___after = is_dir($___pendingDir) ? count(glob($___pendingDir . '/*.json') ?: []) : 0; 2376 | 2377 | if ($___after > $___before || $this->gptWorklogBridgeResultOkV2($___ret)) { 2378 | return [ 2379 | 'ok' => true, 2380 | 'class' => $___class, 2381 | 'method' => $___method, 2382 | 'before_count' => $___before, 2383 | 'after_count' => $___after, 2384 | 'return_type' => is_object($___ret) ? get_class($___ret) : gettype($___ret), 2385 | 'return' => is_array($___ret) ? $___ret : null, ### HIT needle=return lines=2377-2391 2377 | if ($___after > $___before || $this->gptWorklogBridgeResultOkV2($___ret)) { 2378 | return [ 2379 | 'ok' => true, 2380 | 'class' => $___class, 2381 | 'method' => $___method, 2382 | 'before_count' => $___before, 2383 | 'after_count' => $___after, 2384 | 'return_type' => is_object($___ret) ? get_class($___ret) : gettype($___ret), 2385 | 'return' => is_array($___ret) ? $___ret : null, 2386 | ]; 2387 | } 2388 | } catch (\Throwable $___e) { 2389 | $___errors[] = $___class . '::' . $___method . ' ' . $___e->getMessage(); 2390 | } 2391 | } ### HIT needle=return lines=2378-2392 2378 | return [ 2379 | 'ok' => true, 2380 | 'class' => $___class, 2381 | 'method' => $___method, 2382 | 'before_count' => $___before, 2383 | 'after_count' => $___after, 2384 | 'return_type' => is_object($___ret) ? get_class($___ret) : gettype($___ret), 2385 | 'return' => is_array($___ret) ? $___ret : null, 2386 | ]; 2387 | } 2388 | } catch (\Throwable $___e) { 2389 | $___errors[] = $___class . '::' . $___method . ' ' . $___e->getMessage(); 2390 | } 2391 | } 2392 | } catch (\Throwable $___e) { ### HIT needle=return lines=2390-2404 2390 | } 2391 | } 2392 | } catch (\Throwable $___e) { 2393 | $___errors[] = $___class . ' instantiate ' . $___e->getMessage(); 2394 | } 2395 | } 2396 | 2397 | return [ 2398 | 'ok' => false, 2399 | 'reason' => 'pending_service_call_failed', 2400 | 'before_count' => $___before, 2401 | 'errors' => array_slice($___errors, -10), 2402 | ]; 2403 | } 2404 | ### HIT needle=pending lines=2392-2406 2392 | } catch (\Throwable $___e) { 2393 | $___errors[] = $___class . ' instantiate ' . $___e->getMessage(); 2394 | } 2395 | } 2396 | 2397 | return [ 2398 | 'ok' => false, 2399 | 'reason' => 'pending_service_call_failed', 2400 | 'before_count' => $___before, 2401 | 'errors' => array_slice($___errors, -10), 2402 | ]; 2403 | } 2404 | 2405 | private function gptWorklogBridgeResultOkV2(mixed $___ret): bool 2406 | { ### HIT needle=return lines=2401-2415 2401 | 'errors' => array_slice($___errors, -10), 2402 | ]; 2403 | } 2404 | 2405 | private function gptWorklogBridgeResultOkV2(mixed $___ret): bool 2406 | { 2407 | if (is_object($___ret)) { 2408 | return true; 2409 | } 2410 | 2411 | if (!is_array($___ret)) { 2412 | return false; 2413 | } 2414 | 2415 | foreach (['ok', 'success', 'saved', 'pending_saved', 'created', 'new_pending_created', 'pending_mode_service', 'service_real_write_trace'] as $___key) { ### HIT needle=return lines=2405-2419 2405 | private function gptWorklogBridgeResultOkV2(mixed $___ret): bool 2406 | { 2407 | if (is_object($___ret)) { 2408 | return true; 2409 | } 2410 | 2411 | if (!is_array($___ret)) { 2412 | return false; 2413 | } 2414 | 2415 | foreach (['ok', 'success', 'saved', 'pending_saved', 'created', 'new_pending_created', 'pending_mode_service', 'service_real_write_trace'] as $___key) { 2416 | if (!empty($___ret[$___key])) { 2417 | return true; 2418 | } 2419 | } ### HIT needle=pending lines=2408-2422 2408 | return true; 2409 | } 2410 | 2411 | if (!is_array($___ret)) { 2412 | return false; 2413 | } 2414 | 2415 | foreach (['ok', 'success', 'saved', 'pending_saved', 'created', 'new_pending_created', 'pending_mode_service', 'service_real_write_trace'] as $___key) { 2416 | if (!empty($___ret[$___key])) { 2417 | return true; 2418 | } 2419 | } 2420 | 2421 | foreach (['file', 'path', 'pending_file', 'approval_id', 'id'] as $___key) { 2422 | if (!empty($___ret[$___key])) { ### HIT needle=return lines=2410-2424 2410 | 2411 | if (!is_array($___ret)) { 2412 | return false; 2413 | } 2414 | 2415 | foreach (['ok', 'success', 'saved', 'pending_saved', 'created', 'new_pending_created', 'pending_mode_service', 'service_real_write_trace'] as $___key) { 2416 | if (!empty($___ret[$___key])) { 2417 | return true; 2418 | } 2419 | } 2420 | 2421 | foreach (['file', 'path', 'pending_file', 'approval_id', 'id'] as $___key) { 2422 | if (!empty($___ret[$___key])) { 2423 | return true; 2424 | } ### HIT needle=pending lines=2414-2428 2414 | 2415 | foreach (['ok', 'success', 'saved', 'pending_saved', 'created', 'new_pending_created', 'pending_mode_service', 'service_real_write_trace'] as $___key) { 2416 | if (!empty($___ret[$___key])) { 2417 | return true; 2418 | } 2419 | } 2420 | 2421 | foreach (['file', 'path', 'pending_file', 'approval_id', 'id'] as $___key) { 2422 | if (!empty($___ret[$___key])) { 2423 | return true; 2424 | } 2425 | } 2426 | 2427 | return false; 2428 | } ### HIT needle=return lines=2416-2430 2416 | if (!empty($___ret[$___key])) { 2417 | return true; 2418 | } 2419 | } 2420 | 2421 | foreach (['file', 'path', 'pending_file', 'approval_id', 'id'] as $___key) { 2422 | if (!empty($___ret[$___key])) { 2423 | return true; 2424 | } 2425 | } 2426 | 2427 | return false; 2428 | } 2429 | 2430 | private function gptWorklogBridgeInstantiateV2(string $___class): mixed ### HIT needle=return lines=2420-2434 2420 | 2421 | foreach (['file', 'path', 'pending_file', 'approval_id', 'id'] as $___key) { 2422 | if (!empty($___ret[$___key])) { 2423 | return true; 2424 | } 2425 | } 2426 | 2427 | return false; 2428 | } 2429 | 2430 | private function gptWorklogBridgeInstantiateV2(string $___class): mixed 2431 | { 2432 | $___ref = new \ReflectionClass($___class); 2433 | $___ctor = $___ref->getConstructor(); 2434 | ### HIT needle=return lines=2429-2443 2429 | 2430 | private function gptWorklogBridgeInstantiateV2(string $___class): mixed 2431 | { 2432 | $___ref = new \ReflectionClass($___class); 2433 | $___ctor = $___ref->getConstructor(); 2434 | 2435 | if (!$___ctor || $___ctor->getNumberOfRequiredParameters() === 0) { 2436 | return new $___class(); 2437 | } 2438 | 2439 | $___args = []; 2440 | foreach ($___ctor->getParameters() as $___param) { 2441 | $___type = $___param->hasType() ? (string)$___param->getType() : ''; 2442 | 2443 | if ($___param->isDefaultValueAvailable()) { ### HIT needle=pending lines=2438-2452 2438 | 2439 | $___args = []; 2440 | foreach ($___ctor->getParameters() as $___param) { 2441 | $___type = $___param->hasType() ? (string)$___param->getType() : ''; 2442 | 2443 | if ($___param->isDefaultValueAvailable()) { 2444 | $___args[] = $___param->getDefaultValue(); 2445 | } elseif ($___type === 'WorklogChoicePendingStore' && class_exists('WorklogChoicePendingStore')) { 2446 | $___args[] = $this->gptWorklogBridgeInstantiateV2('WorklogChoicePendingStore'); 2447 | } elseif (class_exists($___type)) { 2448 | $___args[] = $this->gptWorklogBridgeInstantiateV2($___type); 2449 | } elseif ($___type === 'array') { 2450 | $___args[] = []; 2451 | } elseif ($___type === 'string') { 2452 | $___args[] = ''; ### HIT needle=pending lines=2439-2453 2439 | $___args = []; 2440 | foreach ($___ctor->getParameters() as $___param) { 2441 | $___type = $___param->hasType() ? (string)$___param->getType() : ''; 2442 | 2443 | if ($___param->isDefaultValueAvailable()) { 2444 | $___args[] = $___param->getDefaultValue(); 2445 | } elseif ($___type === 'WorklogChoicePendingStore' && class_exists('WorklogChoicePendingStore')) { 2446 | $___args[] = $this->gptWorklogBridgeInstantiateV2('WorklogChoicePendingStore'); 2447 | } elseif (class_exists($___type)) { 2448 | $___args[] = $this->gptWorklogBridgeInstantiateV2($___type); 2449 | } elseif ($___type === 'array') { 2450 | $___args[] = []; 2451 | } elseif ($___type === 'string') { 2452 | $___args[] = ''; 2453 | } elseif ($___type === 'int') { ### HIT needle=return lines=2455-2469 2455 | } elseif ($___type === 'bool') { 2456 | $___args[] = false; 2457 | } else { 2458 | $___args[] = null; 2459 | } 2460 | } 2461 | 2462 | return $___ref->newInstanceArgs($___args); 2463 | } 2464 | 2465 | private function gptWorklogBridgeBuildArgsV2(\ReflectionMethod $___method, array $___payload, string $___query, array $___checkerResult): array 2466 | { 2467 | $___args = []; 2468 | 2469 | foreach ($___method->getParameters() as $___param) { ### HIT needle=checker lines=2458-2472 2458 | $___args[] = null; 2459 | } 2460 | } 2461 | 2462 | return $___ref->newInstanceArgs($___args); 2463 | } 2464 | 2465 | private function gptWorklogBridgeBuildArgsV2(\ReflectionMethod $___method, array $___payload, string $___query, array $___checkerResult): array 2466 | { 2467 | $___args = []; 2468 | 2469 | foreach ($___method->getParameters() as $___param) { 2470 | $___name = strtolower($___param->getName()); 2471 | $___type = $___param->hasType() ? (string)$___param->getType() : ''; 2472 | ### HIT needle=checker lines=2468-2482 2468 | 2469 | foreach ($___method->getParameters() as $___param) { 2470 | $___name = strtolower($___param->getName()); 2471 | $___type = $___param->hasType() ? (string)$___param->getType() : ''; 2472 | 2473 | if ($___type === 'array') { 2474 | if (str_contains($___name, 'check') || str_contains($___name, 'exist')) { 2475 | $___args[] = $___checkerResult; 2476 | } else { 2477 | $___args[] = $___payload; 2478 | } 2479 | } elseif ($___type === 'string') { 2480 | $___args[] = $___query; 2481 | } elseif ($___type === 'int') { 2482 | $___args[] = 0; ### HIT needle=return lines=2485-2499 2485 | } elseif ($___param->isDefaultValueAvailable()) { 2486 | $___args[] = $___param->getDefaultValue(); 2487 | } else { 2488 | $___args[] = null; 2489 | } 2490 | } 2491 | 2492 | return $___args; 2493 | } 2494 | 2495 | private function gptWorklogBridgeMakeToolResultV2(string $___message, array $___data = []): ?ToolResult 2496 | { 2497 | if (!class_exists('ToolResult')) { 2498 | return null; 2499 | } ### HIT needle=return lines=2491-2505 2491 | 2492 | return $___args; 2493 | } 2494 | 2495 | private function gptWorklogBridgeMakeToolResultV2(string $___message, array $___data = []): ?ToolResult 2496 | { 2497 | if (!class_exists('ToolResult')) { 2498 | return null; 2499 | } 2500 | 2501 | foreach (['success', 'ok', 'message', 'text'] as $___method) { 2502 | if (!method_exists('ToolResult', $___method)) { 2503 | continue; 2504 | } 2505 | ### HIT needle=return lines=2510-2524 2510 | } 2511 | 2512 | foreach ([[$___message, $___data], [$___message], [true, $___message, $___data], [true, $___message]] as $___args) { 2513 | try { 2514 | $___slice = array_slice($___args, 0, $___ref->getNumberOfParameters()); 2515 | $___v = $___ref->invokeArgs(null, $___slice); 2516 | if ($___v instanceof ToolResult) { 2517 | return $___v; 2518 | } 2519 | } catch (\Throwable $___e) { 2520 | } 2521 | } 2522 | } catch (\Throwable $___e) { 2523 | } 2524 | } ### HIT needle=return lines=2525-2539 2525 | 2526 | try { 2527 | $___ref = new \ReflectionClass('ToolResult'); 2528 | foreach ([[$___message], [true, $___message], [true, $___message, $___data], [$___message, $___data]] as $___args) { 2529 | try { 2530 | $___v = $___ref->newInstanceArgs($___args); 2531 | if ($___v instanceof ToolResult) { 2532 | return $___v; 2533 | } 2534 | } catch (\Throwable $___e) { 2535 | } 2536 | } 2537 | } catch (\Throwable $___e) { 2538 | } 2539 | ### HIT needle=return lines=2533-2545 2533 | } 2534 | } catch (\Throwable $___e) { 2535 | } 2536 | } 2537 | } catch (\Throwable $___e) { 2538 | } 2539 | 2540 | return null; 2541 | } 2542 | 2543 | 2544 | // WORKLOGTOOL_BRIDGE_PENDING_SAVED_FLAG_FIX_V2_SAFE 2545 | } ### HIT needle=pending lines=2537-2545 2537 | } catch (\Throwable $___e) { 2538 | } 2539 | 2540 | return null; 2541 | } 2542 | 2543 | 2544 | // WORKLOGTOOL_BRIDGE_PENDING_SAVED_FLAG_FIX_V2_SAFE 2545 | } ## FILE /volume1/web/gpt/engine_modules/tools/worklog/WorklogPendingChoiceService.php ### HIT needle=pending lines=1-12 1 | $parsed 20 | * @param array> $existingCandidates ### HIT needle=pending lines=9-23 9 | * 정책: 10 | * - DB 접근 금지 11 | * - project_logs 저장 금지 12 | * - ToolResult 생성 금지 13 | * - pending_worklog_choices JSON 저장만 담당 14 | */ 15 | 16 | final class WorklogPendingChoiceService 17 | { 18 | /** 19 | * @param array $parsed 20 | * @param array> $existingCandidates 21 | * @param array $params 22 | * @return array 23 | */ ### HIT needle=existing lines=13-27 13 | * - pending_worklog_choices JSON 저장만 담당 14 | */ 15 | 16 | final class WorklogPendingChoiceService 17 | { 18 | /** 19 | * @param array $parsed 20 | * @param array> $existingCandidates 21 | * @param array $params 22 | * @return array 23 | */ 24 | public static function save(array $parsed, array $existingCandidates, array $params = []): array 25 | { 26 | $dryRun = !empty($params['dry_run']); 27 | $root = self::root($params); ### HIT needle=return lines=15-29 15 | 16 | final class WorklogPendingChoiceService 17 | { 18 | /** 19 | * @param array $parsed 20 | * @param array> $existingCandidates 21 | * @param array $params 22 | * @return array 23 | */ 24 | public static function save(array $parsed, array $existingCandidates, array $params = []): array 25 | { 26 | $dryRun = !empty($params['dry_run']); 27 | $root = self::root($params); 28 | $dir = (string)($params['dir'] ?? ($root . '/data/pending_worklog_choices')); 29 | $prefix = (string)($params['id_prefix'] ?? 'wpc_'); ### HIT needle=existing lines=17-31 17 | { 18 | /** 19 | * @param array $parsed 20 | * @param array> $existingCandidates 21 | * @param array $params 22 | * @return array 23 | */ 24 | public static function save(array $parsed, array $existingCandidates, array $params = []): array 25 | { 26 | $dryRun = !empty($params['dry_run']); 27 | $root = self::root($params); 28 | $dir = (string)($params['dir'] ?? ($root . '/data/pending_worklog_choices')); 29 | $prefix = (string)($params['id_prefix'] ?? 'wpc_'); 30 | 31 | $now = date('Y-m-d H:i:s'); ### HIT needle=pending lines=21-35 21 | * @param array $params 22 | * @return array 23 | */ 24 | public static function save(array $parsed, array $existingCandidates, array $params = []): array 25 | { 26 | $dryRun = !empty($params['dry_run']); 27 | $root = self::root($params); 28 | $dir = (string)($params['dir'] ?? ($root . '/data/pending_worklog_choices')); 29 | $prefix = (string)($params['id_prefix'] ?? 'wpc_'); 30 | 31 | $now = date('Y-m-d H:i:s'); 32 | $id = $prefix . date('Ymd_His') . '_' . bin2hex(random_bytes(5)); 33 | $file = rtrim($dir, '/') . '/' . $id . '.json'; 34 | 35 | $payload = [ ### HIT needle=existing lines=31-45 31 | $now = date('Y-m-d H:i:s'); 32 | $id = $prefix . date('Ymd_His') . '_' . bin2hex(random_bytes(5)); 33 | $file = rtrim($dir, '/') . '/' . $id . '.json'; 34 | 35 | $payload = [ 36 | 'id' => $id, 37 | 'kind' => 'worklog_choice', 38 | 'type' => 'existing_today_record', 39 | 'status' => 'open', 40 | 'created_at' => $now, 41 | 'expires_at' => date('Y-m-d H:i:s', time() + 21600), 42 | 'query' => (string)($params['query'] ?? $parsed['query'] ?? $parsed['raw_query'] ?? ''), 43 | 'customer_name' => (string)($parsed['customer_name'] ?? $parsed['customerName'] ?? $parsed['customer'] ?? ''), 44 | 'project_name' => (string)($parsed['project_name'] ?? $parsed['projectName'] ?? $parsed['project_hint'] ?? $parsed['projectHint'] ?? ''), 45 | 'project_id' => $parsed['project_id'] ?? $parsed['projectId'] ?? null, ### HIT needle=create lines=33-47 33 | $file = rtrim($dir, '/') . '/' . $id . '.json'; 34 | 35 | $payload = [ 36 | 'id' => $id, 37 | 'kind' => 'worklog_choice', 38 | 'type' => 'existing_today_record', 39 | 'status' => 'open', 40 | 'created_at' => $now, 41 | 'expires_at' => date('Y-m-d H:i:s', time() + 21600), 42 | 'query' => (string)($params['query'] ?? $parsed['query'] ?? $parsed['raw_query'] ?? ''), 43 | 'customer_name' => (string)($parsed['customer_name'] ?? $parsed['customerName'] ?? $parsed['customer'] ?? ''), 44 | 'project_name' => (string)($parsed['project_name'] ?? $parsed['projectName'] ?? $parsed['project_hint'] ?? $parsed['projectHint'] ?? ''), 45 | 'project_id' => $parsed['project_id'] ?? $parsed['projectId'] ?? null, 46 | 'content' => (string)($parsed['content'] ?? $parsed['log_content'] ?? $parsed['logContent'] ?? ''), 47 | 'amount_hint' => $parsed['amount_hint'] ?? null, ### HIT needle=existing lines=42-56 42 | 'query' => (string)($params['query'] ?? $parsed['query'] ?? $parsed['raw_query'] ?? ''), 43 | 'customer_name' => (string)($parsed['customer_name'] ?? $parsed['customerName'] ?? $parsed['customer'] ?? ''), 44 | 'project_name' => (string)($parsed['project_name'] ?? $parsed['projectName'] ?? $parsed['project_hint'] ?? $parsed['projectHint'] ?? ''), 45 | 'project_id' => $parsed['project_id'] ?? $parsed['projectId'] ?? null, 46 | 'content' => (string)($parsed['content'] ?? $parsed['log_content'] ?? $parsed['logContent'] ?? ''), 47 | 'amount_hint' => $parsed['amount_hint'] ?? null, 48 | 'parsed' => $parsed, 49 | 'existing_candidates' => array_values($existingCandidates), 50 | 'choices' => [ 51 | '1' => 'append_existing', 52 | '2' => 'save_new', 53 | '3' => 'cancel', 54 | ], 55 | 'trace' => [ 56 | 'worklog_pending_choice_service_v1', ### HIT needle=existing lines=44-58 44 | 'project_name' => (string)($parsed['project_name'] ?? $parsed['projectName'] ?? $parsed['project_hint'] ?? $parsed['projectHint'] ?? ''), 45 | 'project_id' => $parsed['project_id'] ?? $parsed['projectId'] ?? null, 46 | 'content' => (string)($parsed['content'] ?? $parsed['log_content'] ?? $parsed['logContent'] ?? ''), 47 | 'amount_hint' => $parsed['amount_hint'] ?? null, 48 | 'parsed' => $parsed, 49 | 'existing_candidates' => array_values($existingCandidates), 50 | 'choices' => [ 51 | '1' => 'append_existing', 52 | '2' => 'save_new', 53 | '3' => 'cancel', 54 | ], 55 | 'trace' => [ 56 | 'worklog_pending_choice_service_v1', 57 | 'candidate_count=' . count($existingCandidates), 58 | ], ### HIT needle=save lines=45-59 45 | 'project_id' => $parsed['project_id'] ?? $parsed['projectId'] ?? null, 46 | 'content' => (string)($parsed['content'] ?? $parsed['log_content'] ?? $parsed['logContent'] ?? ''), 47 | 'amount_hint' => $parsed['amount_hint'] ?? null, 48 | 'parsed' => $parsed, 49 | 'existing_candidates' => array_values($existingCandidates), 50 | 'choices' => [ 51 | '1' => 'append_existing', 52 | '2' => 'save_new', 53 | '3' => 'cancel', 54 | ], 55 | 'trace' => [ 56 | 'worklog_pending_choice_service_v1', 57 | 'candidate_count=' . count($existingCandidates), 58 | ], 59 | ]; ### HIT needle=pending lines=49-63 49 | 'existing_candidates' => array_values($existingCandidates), 50 | 'choices' => [ 51 | '1' => 'append_existing', 52 | '2' => 'save_new', 53 | '3' => 'cancel', 54 | ], 55 | 'trace' => [ 56 | 'worklog_pending_choice_service_v1', 57 | 'candidate_count=' . count($existingCandidates), 58 | ], 59 | ]; 60 | 61 | if ($dryRun) { 62 | return [ 63 | 'ok' => true, ### HIT needle=existing lines=50-64 50 | 'choices' => [ 51 | '1' => 'append_existing', 52 | '2' => 'save_new', 53 | '3' => 'cancel', 54 | ], 55 | 'trace' => [ 56 | 'worklog_pending_choice_service_v1', 57 | 'candidate_count=' . count($existingCandidates), 58 | ], 59 | ]; 60 | 61 | if ($dryRun) { 62 | return [ 63 | 'ok' => true, 64 | 'dry_run' => true, ### HIT needle=return lines=55-69 55 | 'trace' => [ 56 | 'worklog_pending_choice_service_v1', 57 | 'candidate_count=' . count($existingCandidates), 58 | ], 59 | ]; 60 | 61 | if ($dryRun) { 62 | return [ 63 | 'ok' => true, 64 | 'dry_run' => true, 65 | 'id' => $id, 66 | 'file' => $file, 67 | 'payload' => $payload, 68 | 'trace' => $payload['trace'], 69 | ]; ### HIT needle=return lines=71-85 71 | 72 | if (!is_dir($dir)) { 73 | mkdir($dir, 0775, true); 74 | } 75 | 76 | file_put_contents($file, json_encode($payload, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT)); 77 | 78 | return [ 79 | 'ok' => is_file($file), 80 | 'dry_run' => false, 81 | 'id' => $id, 82 | 'file' => $file, 83 | 'payload' => $payload, 84 | 'trace' => $payload['trace'], 85 | ]; ### HIT needle=return lines=87-101 87 | 88 | /** 89 | * @param array $params 90 | */ 91 | private static function root(array $params): string 92 | { 93 | if (!empty($params['root'])) { 94 | return rtrim((string)$params['root'], '/'); 95 | } 96 | 97 | return dirname(__DIR__, 3); 98 | } 99 | 100 | 101 | // WORKLOG_PENDING_CHOICE_SERVICE_SUCCESS_RETURN_BRIDGE_V1 ### HIT needle=return lines=90-104 90 | */ 91 | private static function root(array $params): string 92 | { 93 | if (!empty($params['root'])) { 94 | return rtrim((string)$params['root'], '/'); 95 | } 96 | 97 | return dirname(__DIR__, 3); 98 | } 99 | 100 | 101 | // WORKLOG_PENDING_CHOICE_SERVICE_SUCCESS_RETURN_BRIDGE_V1 102 | public function createPendingChoiceBridgeV1(array $payload): array 103 | { 104 | $root = dirname(__DIR__, 3); ### HIT needle=pending lines=94-108 94 | return rtrim((string)$params['root'], '/'); 95 | } 96 | 97 | return dirname(__DIR__, 3); 98 | } 99 | 100 | 101 | // WORKLOG_PENDING_CHOICE_SERVICE_SUCCESS_RETURN_BRIDGE_V1 102 | public function createPendingChoiceBridgeV1(array $payload): array 103 | { 104 | $root = dirname(__DIR__, 3); 105 | $pendingDir = $root . '/data/pending_worklog_choices'; 106 | 107 | if (!is_dir($pendingDir)) { 108 | @mkdir($pendingDir, 0775, true); ### HIT needle=pending lines=95-109 95 | } 96 | 97 | return dirname(__DIR__, 3); 98 | } 99 | 100 | 101 | // WORKLOG_PENDING_CHOICE_SERVICE_SUCCESS_RETURN_BRIDGE_V1 102 | public function createPendingChoiceBridgeV1(array $payload): array 103 | { 104 | $root = dirname(__DIR__, 3); 105 | $pendingDir = $root . '/data/pending_worklog_choices'; 106 | 107 | if (!is_dir($pendingDir)) { 108 | @mkdir($pendingDir, 0775, true); 109 | } ### HIT needle=pending lines=98-112 98 | } 99 | 100 | 101 | // WORKLOG_PENDING_CHOICE_SERVICE_SUCCESS_RETURN_BRIDGE_V1 102 | public function createPendingChoiceBridgeV1(array $payload): array 103 | { 104 | $root = dirname(__DIR__, 3); 105 | $pendingDir = $root . '/data/pending_worklog_choices'; 106 | 107 | if (!is_dir($pendingDir)) { 108 | @mkdir($pendingDir, 0775, true); 109 | } 110 | 111 | $beforeFiles = is_dir($pendingDir) ? (glob($pendingDir . '/*.json') ?: []) : []; 112 | $beforeCount = count($beforeFiles); ### HIT needle=pending lines=100-114 100 | 101 | // WORKLOG_PENDING_CHOICE_SERVICE_SUCCESS_RETURN_BRIDGE_V1 102 | public function createPendingChoiceBridgeV1(array $payload): array 103 | { 104 | $root = dirname(__DIR__, 3); 105 | $pendingDir = $root . '/data/pending_worklog_choices'; 106 | 107 | if (!is_dir($pendingDir)) { 108 | @mkdir($pendingDir, 0775, true); 109 | } 110 | 111 | $beforeFiles = is_dir($pendingDir) ? (glob($pendingDir . '/*.json') ?: []) : []; 112 | $beforeCount = count($beforeFiles); 113 | 114 | $query = trim((string)($payload['query'] ?? $payload['raw_query'] ?? '')); ### HIT needle=pending lines=101-115 101 | // WORKLOG_PENDING_CHOICE_SERVICE_SUCCESS_RETURN_BRIDGE_V1 102 | public function createPendingChoiceBridgeV1(array $payload): array 103 | { 104 | $root = dirname(__DIR__, 3); 105 | $pendingDir = $root . '/data/pending_worklog_choices'; 106 | 107 | if (!is_dir($pendingDir)) { 108 | @mkdir($pendingDir, 0775, true); 109 | } 110 | 111 | $beforeFiles = is_dir($pendingDir) ? (glob($pendingDir . '/*.json') ?: []) : []; 112 | $beforeCount = count($beforeFiles); 113 | 114 | $query = trim((string)($payload['query'] ?? $payload['raw_query'] ?? '')); 115 | if ($query === '' && isset($payload['params']) && is_array($payload['params'])) { ### HIT needle=pending lines=104-118 104 | $root = dirname(__DIR__, 3); 105 | $pendingDir = $root . '/data/pending_worklog_choices'; 106 | 107 | if (!is_dir($pendingDir)) { 108 | @mkdir($pendingDir, 0775, true); 109 | } 110 | 111 | $beforeFiles = is_dir($pendingDir) ? (glob($pendingDir . '/*.json') ?: []) : []; 112 | $beforeCount = count($beforeFiles); 113 | 114 | $query = trim((string)($payload['query'] ?? $payload['raw_query'] ?? '')); 115 | if ($query === '' && isset($payload['params']) && is_array($payload['params'])) { 116 | foreach (['query','message','q','text','input','raw','raw_query','user_query','prompt','content'] as $key) { 117 | if (isset($payload['params'][$key]) && is_scalar($payload['params'][$key])) { 118 | $query = trim((string)$payload['params'][$key]); ### HIT needle=pending lines=118-132 118 | $query = trim((string)$payload['params'][$key]); 119 | if ($query !== '') break; 120 | } 121 | } 122 | } 123 | 124 | $id = 'wpc_' . date('Ymd_His') . '_' . substr(bin2hex(random_bytes(8)), 0, 8); 125 | $file = $pendingDir . '/' . $id . '.json'; 126 | 127 | $doc = [ 128 | 'id' => $id, 129 | 'pending_id' => $id, 130 | 'kind' => 'worklog_choice', 131 | 'type' => 'existing_today_choice', 132 | 'status' => 'open', ### HIT needle=pending lines=122-136 122 | } 123 | 124 | $id = 'wpc_' . date('Ymd_His') . '_' . substr(bin2hex(random_bytes(8)), 0, 8); 125 | $file = $pendingDir . '/' . $id . '.json'; 126 | 127 | $doc = [ 128 | 'id' => $id, 129 | 'pending_id' => $id, 130 | 'kind' => 'worklog_choice', 131 | 'type' => 'existing_today_choice', 132 | 'status' => 'open', 133 | 'query' => $query, 134 | 'raw_query' => $query, 135 | 'params' => $payload['params'] ?? [], 136 | 'existing' => $payload['existing'] ?? $payload['checker_result'] ?? [], ### HIT needle=existing lines=124-138 124 | $id = 'wpc_' . date('Ymd_His') . '_' . substr(bin2hex(random_bytes(8)), 0, 8); 125 | $file = $pendingDir . '/' . $id . '.json'; 126 | 127 | $doc = [ 128 | 'id' => $id, 129 | 'pending_id' => $id, 130 | 'kind' => 'worklog_choice', 131 | 'type' => 'existing_today_choice', 132 | 'status' => 'open', 133 | 'query' => $query, 134 | 'raw_query' => $query, 135 | 'params' => $payload['params'] ?? [], 136 | 'existing' => $payload['existing'] ?? $payload['checker_result'] ?? [], 137 | 'checker_result' => $payload['checker_result'] ?? $payload['existing'] ?? [], 138 | 'choices' => [ ### HIT needle=existing lines=129-143 129 | 'pending_id' => $id, 130 | 'kind' => 'worklog_choice', 131 | 'type' => 'existing_today_choice', 132 | 'status' => 'open', 133 | 'query' => $query, 134 | 'raw_query' => $query, 135 | 'params' => $payload['params'] ?? [], 136 | 'existing' => $payload['existing'] ?? $payload['checker_result'] ?? [], 137 | 'checker_result' => $payload['checker_result'] ?? $payload['existing'] ?? [], 138 | 'choices' => [ 139 | '1' => '기존 기록에 추가', 140 | '2' => '새 작업기록으로 저장', 141 | '3' => '저장 안 함', 142 | ], 143 | 'source' => 'WorklogPendingChoiceService::createPendingChoiceBridgeV1', ### HIT needle=existing lines=130-144 130 | 'kind' => 'worklog_choice', 131 | 'type' => 'existing_today_choice', 132 | 'status' => 'open', 133 | 'query' => $query, 134 | 'raw_query' => $query, 135 | 'params' => $payload['params'] ?? [], 136 | 'existing' => $payload['existing'] ?? $payload['checker_result'] ?? [], 137 | 'checker_result' => $payload['checker_result'] ?? $payload['existing'] ?? [], 138 | 'choices' => [ 139 | '1' => '기존 기록에 추가', 140 | '2' => '새 작업기록으로 저장', 141 | '3' => '저장 안 함', 142 | ], 143 | 'source' => 'WorklogPendingChoiceService::createPendingChoiceBridgeV1', 144 | 'mode' => 'service_success_return_bridge_v1', ### HIT needle=pending lines=136-150 136 | 'existing' => $payload['existing'] ?? $payload['checker_result'] ?? [], 137 | 'checker_result' => $payload['checker_result'] ?? $payload['existing'] ?? [], 138 | 'choices' => [ 139 | '1' => '기존 기록에 추가', 140 | '2' => '새 작업기록으로 저장', 141 | '3' => '저장 안 함', 142 | ], 143 | 'source' => 'WorklogPendingChoiceService::createPendingChoiceBridgeV1', 144 | 'mode' => 'service_success_return_bridge_v1', 145 | 'created_at' => date('Y-m-d H:i:s'), 146 | 'updated_at' => date('Y-m-d H:i:s'), 147 | 'expires_at' => date('Y-m-d H:i:s', time() + 21600), 148 | ]; 149 | 150 | $writeOk = false; ### HIT needle=return lines=137-151 137 | 'checker_result' => $payload['checker_result'] ?? $payload['existing'] ?? [], 138 | 'choices' => [ 139 | '1' => '기존 기록에 추가', 140 | '2' => '새 작업기록으로 저장', 141 | '3' => '저장 안 함', 142 | ], 143 | 'source' => 'WorklogPendingChoiceService::createPendingChoiceBridgeV1', 144 | 'mode' => 'service_success_return_bridge_v1', 145 | 'created_at' => date('Y-m-d H:i:s'), 146 | 'updated_at' => date('Y-m-d H:i:s'), 147 | 'expires_at' => date('Y-m-d H:i:s', time() + 21600), 148 | ]; 149 | 150 | $writeOk = false; 151 | $error = null; ### HIT needle=create lines=138-152 138 | 'choices' => [ 139 | '1' => '기존 기록에 추가', 140 | '2' => '새 작업기록으로 저장', 141 | '3' => '저장 안 함', 142 | ], 143 | 'source' => 'WorklogPendingChoiceService::createPendingChoiceBridgeV1', 144 | 'mode' => 'service_success_return_bridge_v1', 145 | 'created_at' => date('Y-m-d H:i:s'), 146 | 'updated_at' => date('Y-m-d H:i:s'), 147 | 'expires_at' => date('Y-m-d H:i:s', time() + 21600), 148 | ]; 149 | 150 | $writeOk = false; 151 | $error = null; 152 | ### HIT needle=pending lines=157-171 157 | } 158 | 159 | $writeOk = (bool)file_put_contents($file, $json); 160 | } catch (\Throwable $e) { 161 | $error = $e->getMessage(); 162 | } 163 | 164 | $afterFiles = is_dir($pendingDir) ? (glob($pendingDir . '/*.json') ?: []) : []; 165 | $afterCount = count($afterFiles); 166 | 167 | if (!$writeOk && $afterCount <= $beforeCount) { 168 | return [ 169 | 'ok' => false, 170 | 'success' => false, 171 | 'saved' => false, ### HIT needle=return lines=161-175 161 | $error = $e->getMessage(); 162 | } 163 | 164 | $afterFiles = is_dir($pendingDir) ? (glob($pendingDir . '/*.json') ?: []) : []; 165 | $afterCount = count($afterFiles); 166 | 167 | if (!$writeOk && $afterCount <= $beforeCount) { 168 | return [ 169 | 'ok' => false, 170 | 'success' => false, 171 | 'saved' => false, 172 | 'pending_saved' => false, 173 | 'created' => false, 174 | 'reason' => 'pending_file_write_failed', 175 | 'error' => $error, ### HIT needle=save lines=164-178 164 | $afterFiles = is_dir($pendingDir) ? (glob($pendingDir . '/*.json') ?: []) : []; 165 | $afterCount = count($afterFiles); 166 | 167 | if (!$writeOk && $afterCount <= $beforeCount) { 168 | return [ 169 | 'ok' => false, 170 | 'success' => false, 171 | 'saved' => false, 172 | 'pending_saved' => false, 173 | 'created' => false, 174 | 'reason' => 'pending_file_write_failed', 175 | 'error' => $error, 176 | 'before_count' => $beforeCount, 177 | 'after_count' => $afterCount, 178 | ]; ### HIT needle=pending lines=165-179 165 | $afterCount = count($afterFiles); 166 | 167 | if (!$writeOk && $afterCount <= $beforeCount) { 168 | return [ 169 | 'ok' => false, 170 | 'success' => false, 171 | 'saved' => false, 172 | 'pending_saved' => false, 173 | 'created' => false, 174 | 'reason' => 'pending_file_write_failed', 175 | 'error' => $error, 176 | 'before_count' => $beforeCount, 177 | 'after_count' => $afterCount, 178 | ]; 179 | } ### HIT needle=create lines=166-180 166 | 167 | if (!$writeOk && $afterCount <= $beforeCount) { 168 | return [ 169 | 'ok' => false, 170 | 'success' => false, 171 | 'saved' => false, 172 | 'pending_saved' => false, 173 | 'created' => false, 174 | 'reason' => 'pending_file_write_failed', 175 | 'error' => $error, 176 | 'before_count' => $beforeCount, 177 | 'after_count' => $afterCount, 178 | ]; 179 | } 180 | ### HIT needle=pending lines=167-181 167 | if (!$writeOk && $afterCount <= $beforeCount) { 168 | return [ 169 | 'ok' => false, 170 | 'success' => false, 171 | 'saved' => false, 172 | 'pending_saved' => false, 173 | 'created' => false, 174 | 'reason' => 'pending_file_write_failed', 175 | 'error' => $error, 176 | 'before_count' => $beforeCount, 177 | 'after_count' => $afterCount, 178 | ]; 179 | } 180 | 181 | return [ ### HIT needle=return lines=174-188 174 | 'reason' => 'pending_file_write_failed', 175 | 'error' => $error, 176 | 'before_count' => $beforeCount, 177 | 'after_count' => $afterCount, 178 | ]; 179 | } 180 | 181 | return [ 182 | 'ok' => true, 183 | 'success' => true, 184 | 'saved' => true, 185 | 'pending_saved' => true, 186 | 'created' => true, 187 | 'status' => 'open', 188 | 'id' => $id, ### HIT needle=save lines=177-191 177 | 'after_count' => $afterCount, 178 | ]; 179 | } 180 | 181 | return [ 182 | 'ok' => true, 183 | 'success' => true, 184 | 'saved' => true, 185 | 'pending_saved' => true, 186 | 'created' => true, 187 | 'status' => 'open', 188 | 'id' => $id, 189 | 'pending_id' => $id, 190 | 'file' => $file, 191 | 'path' => $file, ### HIT needle=pending lines=178-192 178 | ]; 179 | } 180 | 181 | return [ 182 | 'ok' => true, 183 | 'success' => true, 184 | 'saved' => true, 185 | 'pending_saved' => true, 186 | 'created' => true, 187 | 'status' => 'open', 188 | 'id' => $id, 189 | 'pending_id' => $id, 190 | 'file' => $file, 191 | 'path' => $file, 192 | 'pending_file' => $file, ### HIT needle=create lines=179-193 179 | } 180 | 181 | return [ 182 | 'ok' => true, 183 | 'success' => true, 184 | 'saved' => true, 185 | 'pending_saved' => true, 186 | 'created' => true, 187 | 'status' => 'open', 188 | 'id' => $id, 189 | 'pending_id' => $id, 190 | 'file' => $file, 191 | 'path' => $file, 192 | 'pending_file' => $file, 193 | 'before_count' => $beforeCount, ### HIT needle=pending lines=182-196 182 | 'ok' => true, 183 | 'success' => true, 184 | 'saved' => true, 185 | 'pending_saved' => true, 186 | 'created' => true, 187 | 'status' => 'open', 188 | 'id' => $id, 189 | 'pending_id' => $id, 190 | 'file' => $file, 191 | 'path' => $file, 192 | 'pending_file' => $file, 193 | 'before_count' => $beforeCount, 194 | 'after_count' => $afterCount, 195 | 'new_pending_created' => $afterCount > $beforeCount, 196 | 'mode' => 'service_success_return_bridge_v1', ### HIT needle=pending lines=185-199 185 | 'pending_saved' => true, 186 | 'created' => true, 187 | 'status' => 'open', 188 | 'id' => $id, 189 | 'pending_id' => $id, 190 | 'file' => $file, 191 | 'path' => $file, 192 | 'pending_file' => $file, 193 | 'before_count' => $beforeCount, 194 | 'after_count' => $afterCount, 195 | 'new_pending_created' => $afterCount > $beforeCount, 196 | 'mode' => 'service_success_return_bridge_v1', 197 | 'source' => 'WorklogPendingChoiceService::createPendingChoiceBridgeV1', 198 | ]; 199 | } ### HIT needle=pending lines=188-201 188 | 'id' => $id, 189 | 'pending_id' => $id, 190 | 'file' => $file, 191 | 'path' => $file, 192 | 'pending_file' => $file, 193 | 'before_count' => $beforeCount, 194 | 'after_count' => $afterCount, 195 | 'new_pending_created' => $afterCount > $beforeCount, 196 | 'mode' => 'service_success_return_bridge_v1', 197 | 'source' => 'WorklogPendingChoiceService::createPendingChoiceBridgeV1', 198 | ]; 199 | } 200 | 201 | } ### HIT needle=return lines=189-201 189 | 'pending_id' => $id, 190 | 'file' => $file, 191 | 'path' => $file, 192 | 'pending_file' => $file, 193 | 'before_count' => $beforeCount, 194 | 'after_count' => $afterCount, 195 | 'new_pending_created' => $afterCount > $beforeCount, 196 | 'mode' => 'service_success_return_bridge_v1', 197 | 'source' => 'WorklogPendingChoiceService::createPendingChoiceBridgeV1', 198 | ]; 199 | } 200 | 201 | } ### HIT needle=pending lines=190-201 190 | 'file' => $file, 191 | 'path' => $file, 192 | 'pending_file' => $file, 193 | 'before_count' => $beforeCount, 194 | 'after_count' => $afterCount, 195 | 'new_pending_created' => $afterCount > $beforeCount, 196 | 'mode' => 'service_success_return_bridge_v1', 197 | 'source' => 'WorklogPendingChoiceService::createPendingChoiceBridgeV1', 198 | ]; 199 | } 200 | 201 | } ## FILE /volume1/web/gpt/engine_modules/tools/worklog/WorklogChoicePendingStore.php ### HIT needle=pending lines=1-12 1 | dir = $dir ?: $root . '/data/pending_worklog_choices'; 28 | $this->ttlSeconds = $ttlSeconds; 29 | 30 | if (!is_dir($this->dir)) { 31 | mkdir($this->dir, 0775, true); 32 | } 33 | } 34 | ### HIT needle=save lines=28-42 28 | $this->ttlSeconds = $ttlSeconds; 29 | 30 | if (!is_dir($this->dir)) { 31 | mkdir($this->dir, 0775, true); 32 | } 33 | } 34 | 35 | public function save(array $payload): array 36 | { 37 | 38 | // WORKLOG_PENDING_SERVICE_LEGACY_GUARD_V1 39 | // WorklogPendingChoiceService가 이미 실제 pending 파일을 만든 경우, 40 | // legacy save가 중복 파일을 만들지 않도록 기존 service payload를 재사용한다. 41 | if (!empty($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1']) 42 | && is_array($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] ?? null) ### HIT needle=pending lines=31-45 31 | mkdir($this->dir, 0775, true); 32 | } 33 | } 34 | 35 | public function save(array $payload): array 36 | { 37 | 38 | // WORKLOG_PENDING_SERVICE_LEGACY_GUARD_V1 39 | // WorklogPendingChoiceService가 이미 실제 pending 파일을 만든 경우, 40 | // legacy save가 중복 파일을 만들지 않도록 기존 service payload를 재사용한다. 41 | if (!empty($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1']) 42 | && is_array($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] ?? null) 43 | ) { 44 | $serviceResult = $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1']; 45 | $payload = []; ### HIT needle=pending lines=32-46 32 | } 33 | } 34 | 35 | public function save(array $payload): array 36 | { 37 | 38 | // WORKLOG_PENDING_SERVICE_LEGACY_GUARD_V1 39 | // WorklogPendingChoiceService가 이미 실제 pending 파일을 만든 경우, 40 | // legacy save가 중복 파일을 만들지 않도록 기존 service payload를 재사용한다. 41 | if (!empty($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1']) 42 | && is_array($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] ?? null) 43 | ) { 44 | $serviceResult = $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1']; 45 | $payload = []; 46 | ### HIT needle=save lines=33-47 33 | } 34 | 35 | public function save(array $payload): array 36 | { 37 | 38 | // WORKLOG_PENDING_SERVICE_LEGACY_GUARD_V1 39 | // WorklogPendingChoiceService가 이미 실제 pending 파일을 만든 경우, 40 | // legacy save가 중복 파일을 만들지 않도록 기존 service payload를 재사용한다. 41 | if (!empty($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1']) 42 | && is_array($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] ?? null) 43 | ) { 44 | $serviceResult = $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1']; 45 | $payload = []; 46 | 47 | if (isset($serviceResult['payload']) && is_array($serviceResult['payload'])) { ### HIT needle=pending lines=34-48 34 | 35 | public function save(array $payload): array 36 | { 37 | 38 | // WORKLOG_PENDING_SERVICE_LEGACY_GUARD_V1 39 | // WorklogPendingChoiceService가 이미 실제 pending 파일을 만든 경우, 40 | // legacy save가 중복 파일을 만들지 않도록 기존 service payload를 재사용한다. 41 | if (!empty($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1']) 42 | && is_array($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] ?? null) 43 | ) { 44 | $serviceResult = $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1']; 45 | $payload = []; 46 | 47 | if (isset($serviceResult['payload']) && is_array($serviceResult['payload'])) { 48 | $payload = $serviceResult['payload']; ### HIT needle=pending lines=35-49 35 | public function save(array $payload): array 36 | { 37 | 38 | // WORKLOG_PENDING_SERVICE_LEGACY_GUARD_V1 39 | // WorklogPendingChoiceService가 이미 실제 pending 파일을 만든 경우, 40 | // legacy save가 중복 파일을 만들지 않도록 기존 service payload를 재사용한다. 41 | if (!empty($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1']) 42 | && is_array($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] ?? null) 43 | ) { 44 | $serviceResult = $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1']; 45 | $payload = []; 46 | 47 | if (isset($serviceResult['payload']) && is_array($serviceResult['payload'])) { 48 | $payload = $serviceResult['payload']; 49 | } ### HIT needle=pending lines=37-51 37 | 38 | // WORKLOG_PENDING_SERVICE_LEGACY_GUARD_V1 39 | // WorklogPendingChoiceService가 이미 실제 pending 파일을 만든 경우, 40 | // legacy save가 중복 파일을 만들지 않도록 기존 service payload를 재사용한다. 41 | if (!empty($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_V1']) 42 | && is_array($GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1'] ?? null) 43 | ) { 44 | $serviceResult = $GLOBALS['WORKLOG_PENDING_SERVICE_REAL_WRITE_RESULT_V1']; 45 | $payload = []; 46 | 47 | if (isset($serviceResult['payload']) && is_array($serviceResult['payload'])) { 48 | $payload = $serviceResult['payload']; 49 | } 50 | 51 | if ($payload) { ### HIT needle=return lines=47-61 47 | if (isset($serviceResult['payload']) && is_array($serviceResult['payload'])) { 48 | $payload = $serviceResult['payload']; 49 | } 50 | 51 | if ($payload) { 52 | $payload['_legacy_guard_reused'] = true; 53 | $payload['_legacy_guard_file'] = (string)($serviceResult['file'] ?? ''); 54 | return $payload; 55 | } 56 | 57 | return $serviceResult; 58 | } 59 | 60 | 61 | $now = time(); ### HIT needle=return lines=50-64 50 | 51 | if ($payload) { 52 | $payload['_legacy_guard_reused'] = true; 53 | $payload['_legacy_guard_file'] = (string)($serviceResult['file'] ?? ''); 54 | return $payload; 55 | } 56 | 57 | return $serviceResult; 58 | } 59 | 60 | 61 | $now = time(); 62 | $id = $this->makeId($payload); 63 | 64 | $record = [ ### HIT needle=existing lines=60-74 60 | 61 | $now = time(); 62 | $id = $this->makeId($payload); 63 | 64 | $record = [ 65 | 'id' => $id, 66 | 'status' => 'open', 67 | 'kind' => 'worklog_existing_choice', 68 | 'created_at' => date('Y-m-d H:i:s', $now), 69 | 'updated_at' => date('Y-m-d H:i:s', $now), 70 | 'expires_at' => date('Y-m-d H:i:s', $now + $this->ttlSeconds), 71 | 'payload' => $payload, 72 | ]; 73 | 74 | $file = $this->pathForId($id); ### HIT needle=create lines=61-75 61 | $now = time(); 62 | $id = $this->makeId($payload); 63 | 64 | $record = [ 65 | 'id' => $id, 66 | 'status' => 'open', 67 | 'kind' => 'worklog_existing_choice', 68 | 'created_at' => date('Y-m-d H:i:s', $now), 69 | 'updated_at' => date('Y-m-d H:i:s', $now), 70 | 'expires_at' => date('Y-m-d H:i:s', $now + $this->ttlSeconds), 71 | 'payload' => $payload, 72 | ]; 73 | 74 | $file = $this->pathForId($id); 75 | file_put_contents( ### HIT needle=return lines=73-87 73 | 74 | $file = $this->pathForId($id); 75 | file_put_contents( 76 | $file, 77 | json_encode($record, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) 78 | ); 79 | 80 | return [ 81 | 'ok' => true, 82 | 'id' => $id, 83 | 'file' => $file, 84 | 'expires_at' => $record['expires_at'], 85 | ]; 86 | } 87 | ### HIT needle=return lines=85-99 85 | ]; 86 | } 87 | 88 | public function load(string $id): ?array 89 | { 90 | $file = $this->pathForId($id); 91 | if (!is_file($file)) { 92 | return null; 93 | } 94 | 95 | $raw = (string)file_get_contents($file); 96 | $record = json_decode($raw, true); 97 | 98 | if (!is_array($record)) { 99 | return null; ### HIT needle=return lines=92-106 92 | return null; 93 | } 94 | 95 | $raw = (string)file_get_contents($file); 96 | $record = json_decode($raw, true); 97 | 98 | if (!is_array($record)) { 99 | return null; 100 | } 101 | 102 | if ($this->isExpired($record)) { 103 | return null; 104 | } 105 | 106 | return $record; ### HIT needle=return lines=96-110 96 | $record = json_decode($raw, true); 97 | 98 | if (!is_array($record)) { 99 | return null; 100 | } 101 | 102 | if ($this->isExpired($record)) { 103 | return null; 104 | } 105 | 106 | return $record; 107 | } 108 | 109 | public function latestOpen(?string $sessionId = null): ?array 110 | { ### HIT needle=return lines=99-113 99 | return null; 100 | } 101 | 102 | if ($this->isExpired($record)) { 103 | return null; 104 | } 105 | 106 | return $record; 107 | } 108 | 109 | public function latestOpen(?string $sessionId = null): ?array 110 | { 111 | $files = glob($this->dir . '/*.json') ?: []; 112 | usort($files, static fn($a, $b) => filemtime($b) <=> filemtime($a)); 113 | ### HIT needle=return lines=130-144 130 | if ($sessionId !== null && $sessionId !== '') { 131 | $payloadSession = (string)($record['payload']['session_id'] ?? ''); 132 | if ($payloadSession !== '' && $payloadSession !== $sessionId) { 133 | continue; 134 | } 135 | } 136 | 137 | return $record; 138 | } 139 | 140 | return null; 141 | } 142 | 143 | public function markResolved(string $id, string $choice, array $extra = []): array 144 | { ### HIT needle=return lines=133-147 133 | continue; 134 | } 135 | } 136 | 137 | return $record; 138 | } 139 | 140 | return null; 141 | } 142 | 143 | public function markResolved(string $id, string $choice, array $extra = []): array 144 | { 145 | $file = $this->pathForId($id); 146 | if (!is_file($file)) { 147 | return [ ### HIT needle=return lines=140-154 140 | return null; 141 | } 142 | 143 | public function markResolved(string $id, string $choice, array $extra = []): array 144 | { 145 | $file = $this->pathForId($id); 146 | if (!is_file($file)) { 147 | return [ 148 | 'ok' => false, 149 | 'reason' => 'pending_not_found', 150 | 'id' => $id, 151 | ]; 152 | } 153 | 154 | $raw = (string)file_get_contents($file); ### HIT needle=pending lines=142-156 142 | 143 | public function markResolved(string $id, string $choice, array $extra = []): array 144 | { 145 | $file = $this->pathForId($id); 146 | if (!is_file($file)) { 147 | return [ 148 | 'ok' => false, 149 | 'reason' => 'pending_not_found', 150 | 'id' => $id, 151 | ]; 152 | } 153 | 154 | $raw = (string)file_get_contents($file); 155 | $record = json_decode($raw, true); 156 | ### HIT needle=return lines=151-165 151 | ]; 152 | } 153 | 154 | $raw = (string)file_get_contents($file); 155 | $record = json_decode($raw, true); 156 | 157 | if (!is_array($record)) { 158 | return [ 159 | 'ok' => false, 160 | 'reason' => 'invalid_pending_json', 161 | 'id' => $id, 162 | ]; 163 | } 164 | 165 | $record['status'] = 'resolved'; ### HIT needle=pending lines=153-167 153 | 154 | $raw = (string)file_get_contents($file); 155 | $record = json_decode($raw, true); 156 | 157 | if (!is_array($record)) { 158 | return [ 159 | 'ok' => false, 160 | 'reason' => 'invalid_pending_json', 161 | 'id' => $id, 162 | ]; 163 | } 164 | 165 | $record['status'] = 'resolved'; 166 | $record['choice'] = $choice; 167 | $record['resolved_at'] = date('Y-m-d H:i:s'); ### HIT needle=return lines=169-183 169 | $record['resolution_extra'] = $extra; 170 | 171 | file_put_contents( 172 | $file, 173 | json_encode($record, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) 174 | ); 175 | 176 | return [ 177 | 'ok' => true, 178 | 'id' => $id, 179 | 'status' => 'resolved', 180 | 'choice' => $choice, 181 | 'file' => $file, 182 | ]; 183 | } ### HIT needle=return lines=180-194 180 | 'choice' => $choice, 181 | 'file' => $file, 182 | ]; 183 | } 184 | 185 | public function markCancelled(string $id, array $extra = []): array 186 | { 187 | return $this->markResolved($id, 'cancel', $extra); 188 | } 189 | 190 | public static function detectChoice(string $query): string 191 | { 192 | $q = trim(mb_strtolower($query, 'UTF-8')); 193 | $q = preg_replace('/\s+/u', '', $q) ?? $q; 194 | ### HIT needle=return lines=189-203 189 | 190 | public static function detectChoice(string $query): string 191 | { 192 | $q = trim(mb_strtolower($query, 'UTF-8')); 193 | $q = preg_replace('/\s+/u', '', $q) ?? $q; 194 | 195 | if ($q === '') { 196 | return ''; 197 | } 198 | 199 | if (preg_match('/^(1|1번|일번|기존|기존기록|기존에추가|기존기록에추가|추가)$/u', $q)) { 200 | return 'append_existing'; 201 | } 202 | 203 | if (preg_match('/^(2|2번|이번|새로|새기록|새기록으로저장|새작업기록|새작업기록으로저장|새작업으로저장|따로|따로저장|따로저장해|새로저장|새로저장해)$/u', $q)) { ### HIT needle=existing lines=193-207 193 | $q = preg_replace('/\s+/u', '', $q) ?? $q; 194 | 195 | if ($q === '') { 196 | return ''; 197 | } 198 | 199 | if (preg_match('/^(1|1번|일번|기존|기존기록|기존에추가|기존기록에추가|추가)$/u', $q)) { 200 | return 'append_existing'; 201 | } 202 | 203 | if (preg_match('/^(2|2번|이번|새로|새기록|새기록으로저장|새작업기록|새작업기록으로저장|새작업으로저장|따로|따로저장|따로저장해|새로저장|새로저장해)$/u', $q)) { 204 | return 'save_new'; 205 | } 206 | 207 | if (preg_match('/^(3|3번|삼번|취소|저장안함|저장하지마|안해|안함)$/u', $q)) { ### HIT needle=save lines=197-211 197 | } 198 | 199 | if (preg_match('/^(1|1번|일번|기존|기존기록|기존에추가|기존기록에추가|추가)$/u', $q)) { 200 | return 'append_existing'; 201 | } 202 | 203 | if (preg_match('/^(2|2번|이번|새로|새기록|새기록으로저장|새작업기록|새작업기록으로저장|새작업으로저장|따로|따로저장|따로저장해|새로저장|새로저장해)$/u', $q)) { 204 | return 'save_new'; 205 | } 206 | 207 | if (preg_match('/^(3|3번|삼번|취소|저장안함|저장하지마|안해|안함)$/u', $q)) { 208 | return 'cancel'; 209 | } 210 | 211 | return ''; ### HIT needle=return lines=201-215 201 | } 202 | 203 | if (preg_match('/^(2|2번|이번|새로|새기록|새기록으로저장|새작업기록|새작업기록으로저장|새작업으로저장|따로|따로저장|따로저장해|새로저장|새로저장해)$/u', $q)) { 204 | return 'save_new'; 205 | } 206 | 207 | if (preg_match('/^(3|3번|삼번|취소|저장안함|저장하지마|안해|안함)$/u', $q)) { 208 | return 'cancel'; 209 | } 210 | 211 | return ''; 212 | } 213 | 214 | public function listRecent(int $limit = 20): array 215 | { ### HIT needle=return lines=204-218 204 | return 'save_new'; 205 | } 206 | 207 | if (preg_match('/^(3|3번|삼번|취소|저장안함|저장하지마|안해|안함)$/u', $q)) { 208 | return 'cancel'; 209 | } 210 | 211 | return ''; 212 | } 213 | 214 | public function listRecent(int $limit = 20): array 215 | { 216 | $files = glob($this->dir . '/*.json') ?: []; 217 | usort($files, static fn($a, $b) => filemtime($b) <=> filemtime($a)); 218 | ### HIT needle=return lines=222-236 222 | $record = json_decode($raw, true); 223 | if (is_array($record)) { 224 | $record['_file'] = $file; 225 | $out[] = $record; 226 | } 227 | } 228 | 229 | return $out; 230 | } 231 | 232 | private function makeId(array $payload): string 233 | { 234 | $seed = implode('|', [ 235 | (string)($payload['trace_id'] ?? ''), 236 | (string)($payload['session_id'] ?? ''), ### HIT needle=return lines=235-249 235 | (string)($payload['trace_id'] ?? ''), 236 | (string)($payload['session_id'] ?? ''), 237 | (string)($payload['original_query'] ?? ''), 238 | microtime(true), 239 | random_int(1000, 9999), 240 | ]); 241 | 242 | return 'wpc_' . date('Ymd_His') . '_' . substr(sha1($seed), 0, 10); 243 | } 244 | 245 | private function pathForId(string $id): string 246 | { 247 | $safe = preg_replace('/[^a-zA-Z0-9_\-]/', '', $id) ?: 'invalid'; 248 | return $this->dir . '/' . $safe . '.json'; 249 | } ### HIT needle=return lines=241-255 241 | 242 | return 'wpc_' . date('Ymd_His') . '_' . substr(sha1($seed), 0, 10); 243 | } 244 | 245 | private function pathForId(string $id): string 246 | { 247 | $safe = preg_replace('/[^a-zA-Z0-9_\-]/', '', $id) ?: 'invalid'; 248 | return $this->dir . '/' . $safe . '.json'; 249 | } 250 | 251 | private function isExpired(array $record): bool 252 | { 253 | $expires = strtotime((string)($record['expires_at'] ?? '')); 254 | if ($expires <= 0) { 255 | return true; ### HIT needle=return lines=248-260 248 | return $this->dir . '/' . $safe . '.json'; 249 | } 250 | 251 | private function isExpired(array $record): bool 252 | { 253 | $expires = strtotime((string)($record['expires_at'] ?? '')); 254 | if ($expires <= 0) { 255 | return true; 256 | } 257 | 258 | return time() > $expires; 259 | } 260 | } ### HIT needle=return lines=251-260 251 | private function isExpired(array $record): bool 252 | { 253 | $expires = strtotime((string)($record['expires_at'] ?? '')); 254 | if ($expires <= 0) { 255 | return true; 256 | } 257 | 258 | return time() > $expires; 259 | } 260 | } ## MISSING /volume1/web/gpt/engine_modules/tools/worklog/WorklogChoice1LiveWriter.php [4] 최근 Worklog guard trace ### repair_jobs/logs/worklog_guard_v3_checker_return_trace.jsonl {"ts":"2026-04-30 17:58:01","phase":"checker_return","marker":"WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1","query":"오늘 김정철 현장가서 GPT_CHOICE1_CHECKER_TRACE_20260430_175801 현장 메모 정리했어. 작업기록으로 남겨줘","has_existing":false,"existing_type":"NULL","existing_keys":[],"existing_brief":null} {"ts":"2026-04-30 18:32:38","phase":"checker_return","marker":"WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1","query":"오늘 회귀대상20260430_183237 현장가서 GPT_CHOICE1_FIXTURE_20260430_183237 현장 메모 정리했어. 작업기록으로 남겨줘","has_existing":true,"existing_type":"array","existing_keys":["found","matched","exists","source","query","tokens","rows","items","count"],"existing_brief":{"found":true,"matched":true,"exists":true,"rows":[{"matched_tokens":["회귀대상20260430_183237"],"row":{"id":33,"project_id":115,"quotation_id":null,"log_date":"2026-04-30","log_content":"전등 나갔다 해서 방문, 현장 콘센트 이전설치 해주고 전등 교체 작업 해주고 출장비 5 전등 4 해서 9만원 받았어\nGPT 테스트 fixture 기존 오늘 기록 GPT_FIXTURE_20260430_183237 회귀대상20260430_183237 현장 메모 정리 작업기록","worker_name":"AI Agent","photo_url":null,"progress_rate":0,"created_at":"2026-04-30 18:32:37","updated_at":"2026-04-30 18:32:37"}}],"items":[{"matched_tokens":["회귀대상20260430_183237"],"row":{"id":33,"project_id":115,"quotation_id":null,"log_date":"2026-04-30","log_content":"전등 나갔다 해서 방문, 현장 콘센트 이전설치 해주고 전등 교체 작업 해주고 출장비 5 전등 4 해서 9만원 받았어\nGPT 테스트 fixture 기존 오늘 기록 GPT_FIXTURE_20260430_183237 회귀대상20260430_183237 현장 메모 정리 작업기록","worker_name":"AI Agent","photo_url":null,"progress_rate":0,"created_at":"2026-04-30 18:32:37","updated_at":"2026-04-30 18:32:37"}}],"count":1}} {"ts":"2026-04-30 18:32:38","phase":"pending_result","marker":"WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1","query":"오늘 회귀대상20260430_183237 현장가서 GPT_CHOICE1_FIXTURE_20260430_183237 현장 메모 정리했어. 작업기록으로 남겨줘","pending_type":"NULL","pending_keys":[],"pending_brief":null} {"ts":"2026-04-30 18:36:39","phase":"checker_return","marker":"WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1","query":"오늘 회귀대상20260430_183638 현장가서 GPT_CHOICE1_FIXTURE_20260430_183639 현장 메모 정리했어. 작업기록으로 남겨줘","has_existing":true,"existing_type":"array","existing_keys":["found","matched","exists","source","query","tokens","rows","items","count"],"existing_brief":{"found":true,"matched":true,"exists":true,"rows":[{"matched_tokens":["회귀대상20260430_183638"],"row":{"id":34,"project_id":115,"quotation_id":null,"log_date":"2026-04-30","log_content":"전등 나갔다 해서 방문, 현장 콘센트 이전설치 해주고 전등 교체 작업 해주고 출장비 5 전등 4 해서 9만원 받았어\nGPT 테스트 fixture 기존 오늘 기록 GPT_FIXTURE_20260430_183638 회귀대상20260430_183638 현장 메모 정리 작업기록","worker_name":"AI Agent","photo_url":null,"progress_rate":0,"created_at":"2026-04-30 18:36:38","updated_at":"2026-04-30 18:36:38"}}],"items":[{"matched_tokens":["회귀대상20260430_183638"],"row":{"id":34,"project_id":115,"quotation_id":null,"log_date":"2026-04-30","log_content":"전등 나갔다 해서 방문, 현장 콘센트 이전설치 해주고 전등 교체 작업 해주고 출장비 5 전등 4 해서 9만원 받았어\nGPT 테스트 fixture 기존 오늘 기록 GPT_FIXTURE_20260430_183638 회귀대상20260430_183638 현장 메모 정리 작업기록","worker_name":"AI Agent","photo_url":null,"progress_rate":0,"created_at":"2026-04-30 18:36:38","updated_at":"2026-04-30 18:36:38"}}],"count":1}} {"ts":"2026-04-30 18:36:39","phase":"pending_result","marker":"WORKLOG_GUARD_V3_CHECKER_RETURN_TRACE_V1","query":"오늘 회귀대상20260430_183638 현장가서 GPT_CHOICE1_FIXTURE_20260430_183639 현장 메모 정리했어. 작업기록으로 남겨줘","pending_type":"NULL","pending_keys":[],"pending_brief":null} ### repair_jobs/logs/worklog_guard_v3_runtime_trace.jsonl {"ts":"2026-04-30 17:55:36","marker":"WORKLOG_GUARD_V3_TRACE_QUERY_EXTRACTION_V1","query":"오늘 김정철 현장가서 GPT_CHOICE1_GUARD_TRACE_20260430_175536 현장 메모 정리했어. 작업기록으로 남겨줘","query_len":74,"is_write":true,"is_read":false,"defined_keys":["params","___wl_guard_query","___wl_k","___wl_guard_is_write","___wl_guard_is_read","___wl_guard_trace_file"]} {"ts":"2026-04-30 17:58:01","marker":"WORKLOG_GUARD_V3_TRACE_QUERY_EXTRACTION_V1","query":"오늘 김정철 현장가서 GPT_CHOICE1_CHECKER_TRACE_20260430_175801 현장 메모 정리했어. 작업기록으로 남겨줘","query_len":76,"is_write":true,"is_read":false,"defined_keys":["params","___wl_guard_query","___wl_k","___wl_guard_is_write","___wl_guard_is_read","___wl_guard_trace_file"]} {"ts":"2026-04-30 18:32:38","marker":"WORKLOG_GUARD_V3_TRACE_QUERY_EXTRACTION_V1","query":"오늘 회귀대상20260430_183237 현장가서 GPT_CHOICE1_FIXTURE_20260430_183237 현장 메모 정리했어. 작업기록으로 남겨줘","query_len":86,"is_write":true,"is_read":false,"defined_keys":["params","___wl_guard_query","___wl_k","___wl_guard_is_write","___wl_guard_is_read","___wl_guard_trace_file"]} {"ts":"2026-04-30 18:36:39","marker":"WORKLOG_GUARD_V3_TRACE_QUERY_EXTRACTION_V1","query":"오늘 회귀대상20260430_183638 현장가서 GPT_CHOICE1_FIXTURE_20260430_183639 현장 메모 정리했어. 작업기록으로 남겨줘","query_len":86,"is_write":true,"is_read":false,"defined_keys":["params","___wl_guard_query","___wl_k","___wl_guard_is_write","___wl_guard_is_read","___wl_guard_trace_file"]} [5] 자동 판정 has_existing_true_count=2 has_existing_false_count=1 pending_type_NULL_count=2 JUDGMENT=기존 기록 감지는 성공하지만 pending 생성/반환이 NULL 로 끊김. NEXT=WorklogTool 에서 existing check 결과를 받은 뒤 WorklogPendingChoiceService 또는 WorklogChoicePendingStore 로 넘기는 branch 확인 필요. WRITER_REF=참조 발견. missing 파일이 실제 원인일 가능성 있음. [6] 다음 ChatGPT에 줄 핵심 질문 WorklogTool.php / WorklogPendingChoiceService.php / WorklogChoicePendingStore.php 코드 주변을 보고, 기존 기록 감지 has_existing=true 이후 pending_result=NULL 로 끝나는 branch 를 실제 pending 생성으로 연결하는 패치를 만들어줘.