From 80d108e50662bb8a4f16b2fe18dd40875197fd77 Mon Sep 17 00:00:00 2001 From: dctouch Date: Fri, 10 Apr 2026 19:22:40 +0300 Subject: [PATCH] =?UTF-8?q?=D0=93=D0=9B=D0=9E=D0=91=D0=90=D0=9B=D0=AC?= =?UTF-8?q?=D0=9D=D0=AB=D0=99=20=D0=A0=D0=95=D0=A4=D0=90=D0=9A=D0=A2=D0=9E?= =?UTF-8?q?=D0=A0=D0=98=D0=9D=D0=93=20=D0=90=D0=A0=D0=A5=D0=98=D0=A2=D0=95?= =?UTF-8?q?=D0=9A=D0=A2=D0=A3=D0=A0=D0=AB=20-=20=D0=A0=D0=B5=D1=84=D0=B0?= =?UTF-8?q?=D0=BA=D1=82=D0=BE=D1=80=D0=B8=D0=BD=D0=B3=20=D1=8D=D1=82=D0=B0?= =?UTF-8?q?=D0=BF=D0=BE=D0=B2=202.12.23:=20=D0=B4=D0=B5=D0=BA=D0=BE=D0=BC?= =?UTF-8?q?=D0=BF=D0=BE=D0=B7=D0=B8=D1=86=D0=B8=D1=8F=20deep-turn=20=D0=BF?= =?UTF-8?q?=D0=B0=D0=B9=D0=BF=D0=BB=D0=B0=D0=B9=D0=BD=D0=B0=20=D0=B0=D1=81?= =?UTF-8?q?=D1=81=D0=B8=D1=81=D1=82=D0=B5=D0=BD=D1=82=D0=B0=20=D0=B2=20run?= =?UTF-8?q?time-=D0=B0=D0=B4=D0=B0=D0=BF=D1=82=D0=B5=D1=80=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- designconfig.ts | 2 +- docs/TECH/1CLLMARCH-FACT.md | 867 +++++++++++ docs/TECH/1CLLMARCH.md | 1351 +++++++++++++++++ docs/TECH/1CLLMARCH.zip | Bin 0 -> 16141 bytes .../backend/dist/routes/autoRuns.js | 1 + llm_normalizer/backend/dist/routes/eval.js | 33 +- .../dist/services/addressFilterExtractor.js | 36 +- .../dist/services/addressIntentResolver.js | 90 +- .../dist/services/addressQueryService.js | 117 +- .../services/address_runtime/composeStage.js | 37 +- .../services/assistantAnswerPackageBuilder.js | 107 ++ .../assistantContractsBundleAssembler.js | 41 + .../services/assistantCoverageGrounding.js | 344 +++++ .../dist/services/assistantDataLayer.js | 78 +- .../assistantDebugPayloadAssembler.js | 92 ++ .../assistantDeepResponseAssembler.js | 40 + ...istantDeepTurnCompositionRuntimeAdapter.js | 40 + .../assistantDeepTurnContextRuntimeAdapter.js | 72 + ...ssistantDeepTurnGroundingRuntimeAdapter.js | 49 + .../assistantDeepTurnGuardRuntimeAdapter.js | 51 + .../services/assistantDeepTurnInputBuilder.js | 19 + .../services/assistantDeepTurnPackaging.js | 139 ++ ...ssistantDeepTurnPackagingRuntimeAdapter.js | 106 ++ .../assistantDeepTurnPlanRuntimeAdapter.js | 27 + .../assistantDeepTurnPrePackagingContext.js | 20 + .../assistantDeepTurnResponseBuilder.js | 14 + ...ssistantDeepTurnRetrievalRuntimeAdapter.js | 78 + .../assistantEvidenceBundleAssembler.js | 23 + ...sistantInvestigationStateRuntimeAdapter.js | 29 + .../services/assistantMessageLogAssembler.js | 102 ++ .../assistantOrchestrationContracts.js | 148 ++ .../assistantOrchestrationRuntimeAdapter.js | 13 + .../dist/services/assistantQueryPlanning.js | 106 ++ .../dist/services/assistantRuntimeGuards.js | 96 +- .../backend/dist/services/assistantService.js | 1115 +++----------- .../assistantTurnCommitRuntimeAdapter.js | 24 + .../backend/dist/services/evalService.js | 64 + .../dist/services/questionTypeResolver.js | 2 +- llm_normalizer/backend/src/routes/autoRuns.ts | 2 + llm_normalizer/backend/src/routes/eval.ts | 39 +- .../src/services/addressFilterExtractor.ts | 36 +- .../src/services/addressIntentResolver.ts | 115 +- .../src/services/addressQueryService.ts | 132 +- .../services/address_runtime/composeStage.ts | 41 +- .../services/assistantAnswerPackageBuilder.ts | 159 ++ .../assistantContractsBundleAssembler.ts | 97 ++ .../services/assistantCoverageGrounding.ts | 421 +++++ .../src/services/assistantDataLayer.ts | 101 +- .../assistantDebugPayloadAssembler.ts | 160 ++ .../assistantDeepResponseAssembler.ts | 68 + ...istantDeepTurnCompositionRuntimeAdapter.ts | 77 + .../assistantDeepTurnContextRuntimeAdapter.ts | 169 +++ ...ssistantDeepTurnGroundingRuntimeAdapter.ts | 122 ++ .../assistantDeepTurnGuardRuntimeAdapter.ts | 112 ++ .../services/assistantDeepTurnInputBuilder.ts | 112 ++ .../services/assistantDeepTurnPackaging.ts | 250 +++ ...ssistantDeepTurnPackagingRuntimeAdapter.ts | 212 +++ .../assistantDeepTurnPlanRuntimeAdapter.ts | 91 ++ .../assistantDeepTurnPrePackagingContext.ts | 57 + .../assistantDeepTurnResponseBuilder.ts | 29 + ...ssistantDeepTurnRetrievalRuntimeAdapter.ts | 131 ++ .../assistantEvidenceBundleAssembler.ts | 36 + ...sistantInvestigationStateRuntimeAdapter.ts | 55 + .../services/assistantMessageLogAssembler.ts | 159 ++ .../assistantOrchestrationContracts.ts | 275 ++++ .../assistantOrchestrationRuntimeAdapter.ts | 66 + .../src/services/assistantQueryPlanning.ts | 133 ++ .../src/services/assistantRuntimeGuards.ts | 111 +- .../backend/src/services/assistantService.ts | 1115 +++----------- .../assistantTurnCommitRuntimeAdapter.ts | 49 + .../backend/src/services/evalService.ts | 71 + .../src/services/questionTypeResolver.ts | 2 +- .../backend/src/types/normalizer.ts | 9 + .../tests/addressQueryRuntimeM23.test.ts | 173 +++ .../assistantAnswerPackageBuilder.test.ts | 186 +++ .../assistantContractsBundleAssembler.test.ts | 123 ++ .../tests/assistantCoverageGrounding.test.ts | 199 +++ .../assistantDebugPayloadAssembler.test.ts | 130 ++ .../assistantDeepResponseAssembler.test.ts | 131 ++ ...tDeepTurnCompositionRuntimeAdapter.test.ts | 126 ++ ...stantDeepTurnContextRuntimeAdapter.test.ts | 106 ++ ...antDeepTurnGroundingRuntimeAdapter.test.ts | 142 ++ ...sistantDeepTurnGuardRuntimeAdapter.test.ts | 121 ++ .../assistantDeepTurnInputBuilder.test.ts | 140 ++ .../tests/assistantDeepTurnPackaging.test.ts | 179 +++ ...antDeepTurnPackagingRuntimeAdapter.test.ts | 184 +++ ...ssistantDeepTurnPlanRuntimeAdapter.test.ts | 96 ++ ...sistantDeepTurnPrePackagingContext.test.ts | 60 + .../assistantDeepTurnResponseBuilder.test.ts | 69 + ...antDeepTurnRetrievalRuntimeAdapter.test.ts | 158 ++ .../assistantEvidenceBundleAssembler.test.ts | 105 ++ ...ntInvestigationStateRuntimeAdapter.test.ts | 158 ++ .../tests/assistantLivingRouter.test.ts | 27 + .../assistantMessageLogAssembler.test.ts | 123 ++ .../assistantOrchestrationContracts.test.ts | 304 ++++ ...sistantOrchestrationRuntimeAdapter.test.ts | 104 ++ .../tests/assistantQueryPlanning.test.ts | 107 ++ .../assistantTurnCommitRuntimeAdapter.test.ts | 93 ++ ...ve10SettlementCorrectiveRegression.test.ts | 4 +- .../tests/questionTypeResolver.test.ts | 5 + .../data/autorun_annotations/annotations.json | 453 +++++- .../data/autorun_generators/history.json | 80 + ...d_20260410082127_gen-mnsmzzcr-ffcz48q.json | 334 ++++ ...d_20260410090601_gen-mnsolawk-vugqyoc.json | 334 ++++ ...istant_autogen_runtime_job-7dfZNiKKmG.json | 250 +++ ...istant_autogen_runtime_job-NCdNOETmqd.json | 250 +++ .../frontend/dist/assets/index-BfGkpjEM.css | 1 + .../frontend/dist/assets/index-DMMD5-xN.js | 13 + .../frontend/dist/assets/index-DNcr9aV9.js | 13 - .../frontend/dist/assets/index-qw2gR5-7.css | 1 - llm_normalizer/frontend/dist/index.html | 4 +- llm_normalizer/frontend/src/api/client.ts | 8 +- .../src/components/AssistantPanel.tsx | 167 +- .../src/components/AutoRunsHistoryPanel.tsx | 180 ++- llm_normalizer/frontend/src/state/types.ts | 3 + llm_normalizer/frontend/src/styles.css | 159 +- 116 files changed, 13950 insertions(+), 2040 deletions(-) create mode 100644 docs/TECH/1CLLMARCH-FACT.md create mode 100644 docs/TECH/1CLLMARCH.md create mode 100644 docs/TECH/1CLLMARCH.zip create mode 100644 llm_normalizer/backend/dist/services/assistantAnswerPackageBuilder.js create mode 100644 llm_normalizer/backend/dist/services/assistantContractsBundleAssembler.js create mode 100644 llm_normalizer/backend/dist/services/assistantCoverageGrounding.js create mode 100644 llm_normalizer/backend/dist/services/assistantDebugPayloadAssembler.js create mode 100644 llm_normalizer/backend/dist/services/assistantDeepResponseAssembler.js create mode 100644 llm_normalizer/backend/dist/services/assistantDeepTurnCompositionRuntimeAdapter.js create mode 100644 llm_normalizer/backend/dist/services/assistantDeepTurnContextRuntimeAdapter.js create mode 100644 llm_normalizer/backend/dist/services/assistantDeepTurnGroundingRuntimeAdapter.js create mode 100644 llm_normalizer/backend/dist/services/assistantDeepTurnGuardRuntimeAdapter.js create mode 100644 llm_normalizer/backend/dist/services/assistantDeepTurnInputBuilder.js create mode 100644 llm_normalizer/backend/dist/services/assistantDeepTurnPackaging.js create mode 100644 llm_normalizer/backend/dist/services/assistantDeepTurnPackagingRuntimeAdapter.js create mode 100644 llm_normalizer/backend/dist/services/assistantDeepTurnPlanRuntimeAdapter.js create mode 100644 llm_normalizer/backend/dist/services/assistantDeepTurnPrePackagingContext.js create mode 100644 llm_normalizer/backend/dist/services/assistantDeepTurnResponseBuilder.js create mode 100644 llm_normalizer/backend/dist/services/assistantDeepTurnRetrievalRuntimeAdapter.js create mode 100644 llm_normalizer/backend/dist/services/assistantEvidenceBundleAssembler.js create mode 100644 llm_normalizer/backend/dist/services/assistantInvestigationStateRuntimeAdapter.js create mode 100644 llm_normalizer/backend/dist/services/assistantMessageLogAssembler.js create mode 100644 llm_normalizer/backend/dist/services/assistantOrchestrationContracts.js create mode 100644 llm_normalizer/backend/dist/services/assistantOrchestrationRuntimeAdapter.js create mode 100644 llm_normalizer/backend/dist/services/assistantQueryPlanning.js create mode 100644 llm_normalizer/backend/dist/services/assistantTurnCommitRuntimeAdapter.js create mode 100644 llm_normalizer/backend/src/services/assistantAnswerPackageBuilder.ts create mode 100644 llm_normalizer/backend/src/services/assistantContractsBundleAssembler.ts create mode 100644 llm_normalizer/backend/src/services/assistantCoverageGrounding.ts create mode 100644 llm_normalizer/backend/src/services/assistantDebugPayloadAssembler.ts create mode 100644 llm_normalizer/backend/src/services/assistantDeepResponseAssembler.ts create mode 100644 llm_normalizer/backend/src/services/assistantDeepTurnCompositionRuntimeAdapter.ts create mode 100644 llm_normalizer/backend/src/services/assistantDeepTurnContextRuntimeAdapter.ts create mode 100644 llm_normalizer/backend/src/services/assistantDeepTurnGroundingRuntimeAdapter.ts create mode 100644 llm_normalizer/backend/src/services/assistantDeepTurnGuardRuntimeAdapter.ts create mode 100644 llm_normalizer/backend/src/services/assistantDeepTurnInputBuilder.ts create mode 100644 llm_normalizer/backend/src/services/assistantDeepTurnPackaging.ts create mode 100644 llm_normalizer/backend/src/services/assistantDeepTurnPackagingRuntimeAdapter.ts create mode 100644 llm_normalizer/backend/src/services/assistantDeepTurnPlanRuntimeAdapter.ts create mode 100644 llm_normalizer/backend/src/services/assistantDeepTurnPrePackagingContext.ts create mode 100644 llm_normalizer/backend/src/services/assistantDeepTurnResponseBuilder.ts create mode 100644 llm_normalizer/backend/src/services/assistantDeepTurnRetrievalRuntimeAdapter.ts create mode 100644 llm_normalizer/backend/src/services/assistantEvidenceBundleAssembler.ts create mode 100644 llm_normalizer/backend/src/services/assistantInvestigationStateRuntimeAdapter.ts create mode 100644 llm_normalizer/backend/src/services/assistantMessageLogAssembler.ts create mode 100644 llm_normalizer/backend/src/services/assistantOrchestrationContracts.ts create mode 100644 llm_normalizer/backend/src/services/assistantOrchestrationRuntimeAdapter.ts create mode 100644 llm_normalizer/backend/src/services/assistantQueryPlanning.ts create mode 100644 llm_normalizer/backend/src/services/assistantTurnCommitRuntimeAdapter.ts create mode 100644 llm_normalizer/backend/tests/assistantAnswerPackageBuilder.test.ts create mode 100644 llm_normalizer/backend/tests/assistantContractsBundleAssembler.test.ts create mode 100644 llm_normalizer/backend/tests/assistantCoverageGrounding.test.ts create mode 100644 llm_normalizer/backend/tests/assistantDebugPayloadAssembler.test.ts create mode 100644 llm_normalizer/backend/tests/assistantDeepResponseAssembler.test.ts create mode 100644 llm_normalizer/backend/tests/assistantDeepTurnCompositionRuntimeAdapter.test.ts create mode 100644 llm_normalizer/backend/tests/assistantDeepTurnContextRuntimeAdapter.test.ts create mode 100644 llm_normalizer/backend/tests/assistantDeepTurnGroundingRuntimeAdapter.test.ts create mode 100644 llm_normalizer/backend/tests/assistantDeepTurnGuardRuntimeAdapter.test.ts create mode 100644 llm_normalizer/backend/tests/assistantDeepTurnInputBuilder.test.ts create mode 100644 llm_normalizer/backend/tests/assistantDeepTurnPackaging.test.ts create mode 100644 llm_normalizer/backend/tests/assistantDeepTurnPackagingRuntimeAdapter.test.ts create mode 100644 llm_normalizer/backend/tests/assistantDeepTurnPlanRuntimeAdapter.test.ts create mode 100644 llm_normalizer/backend/tests/assistantDeepTurnPrePackagingContext.test.ts create mode 100644 llm_normalizer/backend/tests/assistantDeepTurnResponseBuilder.test.ts create mode 100644 llm_normalizer/backend/tests/assistantDeepTurnRetrievalRuntimeAdapter.test.ts create mode 100644 llm_normalizer/backend/tests/assistantEvidenceBundleAssembler.test.ts create mode 100644 llm_normalizer/backend/tests/assistantInvestigationStateRuntimeAdapter.test.ts create mode 100644 llm_normalizer/backend/tests/assistantMessageLogAssembler.test.ts create mode 100644 llm_normalizer/backend/tests/assistantOrchestrationContracts.test.ts create mode 100644 llm_normalizer/backend/tests/assistantOrchestrationRuntimeAdapter.test.ts create mode 100644 llm_normalizer/backend/tests/assistantQueryPlanning.test.ts create mode 100644 llm_normalizer/backend/tests/assistantTurnCommitRuntimeAdapter.test.ts create mode 100644 llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260410082127_gen-mnsmzzcr-ffcz48q.json create mode 100644 llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260410090601_gen-mnsolawk-vugqyoc.json create mode 100644 llm_normalizer/data/eval_cases/assistant_autogen_runtime_job-7dfZNiKKmG.json create mode 100644 llm_normalizer/data/eval_cases/assistant_autogen_runtime_job-NCdNOETmqd.json create mode 100644 llm_normalizer/frontend/dist/assets/index-BfGkpjEM.css create mode 100644 llm_normalizer/frontend/dist/assets/index-DMMD5-xN.js delete mode 100644 llm_normalizer/frontend/dist/assets/index-DNcr9aV9.js delete mode 100644 llm_normalizer/frontend/dist/assets/index-qw2gR5-7.css diff --git a/designconfig.ts b/designconfig.ts index 8d897e2..ec72507 100644 --- a/designconfig.ts +++ b/designconfig.ts @@ -14,7 +14,7 @@ export const designConfig = { scrollbarThumbHoverRgb: "30, 50, 30" }, layout: { - modeColumnWidthPx: 440, + modeColumnWidthPx: 406, modeToggleWidthPx: 188 } } as const; diff --git a/docs/TECH/1CLLMARCH-FACT.md b/docs/TECH/1CLLMARCH-FACT.md new file mode 100644 index 0000000..2f4c917 --- /dev/null +++ b/docs/TECH/1CLLMARCH-FACT.md @@ -0,0 +1,867 @@ +# 1CLLMARCH Fact Check And Stabilization Plan + +Updated at: 2026-04-10 +Source baseline: `docs/TECH/1CLLMARCH.md` + +## 1. Purpose + +This document fixes the current factual state of the codebase against `1CLLMARCH` and records a production-focused stabilization plan that preserves: + +1. existing MCP routes; +2. manual routing and guard logic; +3. GUI manual markup + autorun operational loop. + +## 2. Executive Reality Check + +Current state is **not MVP** and also **not stable production**. +It is an advanced prototype with strong observability and eval tooling, but with architectural coupling and quality bottlenecks. + +Approximate readiness against target architecture: **55/100**. + +## 3. Verified Facts (Code + Runtime) + +1. Strong engineering layers already exist: + - requirement extraction, coverage, grounding checks; + - rich debug payload and session trace; + - investigation state/followup continuity; + - manual annotation schema and post-analysis queues; + - async eval run + live history APIs. +2. Main production blockers: + - orchestration monolith and coupling in `assistantService.ts`; + - deterministic/template-heavy final answer construction; + - large lexical heuristic surface for intent routing; + - temporal policy inconsistency (date handling differs by lane/path); + - hidden fallback behavior around historical snapshot assumptions. +3. Quality metrics from latest Stage1 runs confirm weak user value: + - low retrieval differentiation; + - high generic explanation rate; + - low accountant actionability; + - zero mechanism specificity. + +## 4. Gap Vs `1CLLMARCH` + +### 4.1 Already aligned (partially or strongly) + +1. Structured normalization contracts and validator loops. +2. Coverage and grounding artifacts (with different naming than target). +3. Conversation state persistence. +4. Operational diagnostics and report generation. + +### 4.2 Not aligned enough for stable prod + +1. Role separation (Interpreter/Planner/Critic/Answer) is incomplete and strongly coupled. +2. Final user answer quality is constrained by deterministic template synthesis. +3. Unified contract for analysis date/time scope is missing across all lanes. +4. Result classes from target (`FULLY_ANSWERED`, `BLOCKED_BY_*`, etc.) are not normalized as one contract. +5. Reason-code taxonomy exists but is fragmented across modules. + +## 5. Stabilization Plan (No Route Breakage) + +## Stage 1 (P0): Unified Analysis Context + Temporal Hardening + +Goal: +1. Introduce unified `analysis_context` contract. +2. Propagate it through eval -> assistant -> runtime lanes. +3. Remove hidden hardcoded period fallback in live MCP plan generation. +4. Keep backward compatibility (`period_hint` still supported). + +Acceptance: +1. If analysis date is set, runtime uses it explicitly in both deep/address paths. +2. If analysis date is absent, runtime no longer silently injects fixed historical date in live-plan fallback. +3. Existing APIs and manual workflows remain operational. + +Implemented in current pass: +1. Added unified `analysis_context` contract to request context (`as_of_date`, `period_from`, `period_to`, `snapshot_mode`, `source`). +2. Added compatibility bridge: legacy `period_hint` is still accepted and normalized into `analysis_context`. +3. Propagated analysis context through eval flows into assistant runtime. +4. Applied analysis context in temporal guard with explicit precedence over implicit snapshot lock. +5. Removed hidden hardcoded live-plan fallback period by switching to: + - explicit analysis period/date when provided; + - query-derived period when present; + - generic live probe when period is absent. + +Validation: +1. `npm run build` passed. +2. Targeted runtime tests passed: + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Status: **Completed** + +## Stage 2 (P1): Orchestrator Decomposition (Behavior-Preserving Refactor) + +Goal: +1. Split monolith into explicit modules: + - QueryFrame builder + - Execution planner + - Evidence assembler + - Coverage critic + - Answer package builder +2. Preserve current behavior under compatibility adapter. + +Acceptance: +1. No MCP route regressions. +2. Existing tests and autorun loop remain green. + +Implemented in current pass (Phase 2.1): +1. Added new orchestration contract module: + - `assistant_query_frame_v1` + - `assistant_execution_plan_v1` + - `assistant_evidence_bundle_v1` + - `assistant_coverage_contract_v1` + - outcome classifier (`FULLY_ANSWERED`, `PARTIALLY_ANSWERED`, `BLOCKED_*`, `MISROUTED`, `FAILED_TO_BIND_ENTITIES`) +2. Integrated contracts into deep-lane runtime without route/answer behavior changes: + - debug payload now includes `assistant_outcome_class_v1`; + - debug payload and event logs now include `assistant_orchestration_contracts_v1`. +3. Added unit regression tests: + - `assistantOrchestrationContracts.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted tests passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.2): +1. Added runtime orchestration adapter for the deep lane: + - `assistantOrchestrationRuntimeAdapter.ts` + - unified pipeline call for `requirements -> coverage -> grounding` with stable interfaces. +2. Integrated adapter into `assistantService` main deep-lane flow (behavior-preserving): + - existing extraction/coverage/grounding logic preserved; + - execution now routed through one orchestration boundary. +3. Added adapter unit tests: + - `assistantOrchestrationRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted tests passed: + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantOrchestrationContracts.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.3): +1. Extracted answer package builder (`answer_structure_v11`) from `assistantService` into dedicated module: + - `assistantAnswerPackageBuilder.ts` +2. Rewired deep-lane answer structure assembly to use the new module without contract changes. +3. Added focused unit tests for answer package behavior: + - `assistantAnswerPackageBuilder.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 test pack passed: + - `assistantAnswerPackageBuilder.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantOrchestrationContracts.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.4): +1. Extracted coverage/grounding pipeline into dedicated module: + - `assistantCoverageGrounding.ts` + - moved requirement extraction, coverage resolution, and grounding checks under one reusable boundary. +2. Rewired `assistantService` to use extracted coverage/grounding module via compatibility wrappers (behavior-preserving). +3. Added focused regression tests: + - `assistantCoverageGrounding.test.ts` +4. Stabilized deterministic Stage 2 regression environment for followup continuity checks: + - fixed accidental false-positive token in grounding test fixture; + - fixed env isolation in `assistantWave10SettlementCorrectiveRegression.test.ts` by explicitly controlling `FEATURE_ASSISTANT_ADDRESS_QUERY_V1`. + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 + safety regressions passed: + - `assistantCoverageGrounding.test.ts` + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantOrchestrationContracts.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.5): +1. Extracted query-planning helpers from `assistantService` into dedicated module: + - `assistantQueryPlanning.ts` + - moved `fragmentTextById`, execution-plan builder, and debug-routes builder under one reusable boundary. +2. Rewired `assistantService` to use extracted query-planning module (behavior-preserving wrapper integration). +3. Added focused unit tests for query-planning behavior: + - `assistantQueryPlanning.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 + safety regressions passed: + - `assistantQueryPlanning.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantOrchestrationContracts.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.6): +1. Extracted evidence-bundle assembly from `assistantService` into dedicated module: + - `assistantEvidenceBundleAssembler.ts` + - centralized: + - `assistant_evidence_bundle_v1` contract assembly; + - debug `retrieval_status` projection from normalized retrieval results. +2. Rewired `assistantService` to use assembler output for both: + - `assistant_orchestration_contracts_v1.evidence_bundle`; + - debug payload `retrieval_status`. +3. Added focused unit tests: + - `assistantEvidenceBundleAssembler.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 + safety regressions passed: + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantOrchestrationContracts.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.7): +1. Extracted deep-lane debug payload assembly from `assistantService` into dedicated module: + - `assistantDebugPayloadAssembler.ts` +2. Rewired `assistantService` to build `debug` via assembler (behavior-preserving): + - keeps all existing fields, guard audits, orchestration contracts and optional sections. +3. Added focused unit tests: + - `assistantDebugPayloadAssembler.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 + safety regressions passed: + - `assistantDebugPayloadAssembler.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantOrchestrationContracts.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.8): +1. Extracted deep-lane processed log payload assembly from `assistantService` into dedicated module: + - `assistantMessageLogAssembler.ts` +2. Rewired `assistantService` to build `assistant_message_processed.details` via assembler (behavior-preserving). +3. Added focused unit tests: + - `assistantMessageLogAssembler.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 + safety regressions passed: + - `assistantMessageLogAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantOrchestrationContracts.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.9): +1. Extracted orchestration-contract bundle assembly from `assistantService` into dedicated module: + - `assistantContractsBundleAssembler.ts` +2. Rewired `assistantService` to consume bundled contracts/outcome class from assembler (behavior-preserving): + - `query_frame`, `execution_plan`, `evidence_bundle`, `coverage`, `outcome_class`. +3. Added focused unit tests: + - `assistantContractsBundleAssembler.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 + safety regressions passed: + - `assistantContractsBundleAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantOrchestrationContracts.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.10): +1. Extracted deep response envelope assembly from `assistantService` into dedicated module: + - `assistantDeepResponseAssembler.ts` + - centralized: + - safe final assistant text cleanup (debug tail stripping); + - `answer_structure_v11` selection/building policy; + - assistant conversation item construction. +2. Rewired `assistantService` to consume deep response assembler (behavior-preserving). +3. Added focused unit tests: + - `assistantDeepResponseAssembler.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 + safety regressions passed: + - `assistantDeepResponseAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantOrchestrationContracts.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.11): +1. Added top-level deep turn packager to compose extracted Stage 2 modules behind one boundary: + - `assistantDeepTurnPackaging.ts` + - centralizes orchestration of: + - evidence bundle assembly; + - contracts/outcome class assembly; + - deep answer artifacts; + - debug payload; + - assistant conversation item; + - processed log details. +2. Rewired deep-lane `assistantService` flow to use `assembleAssistantDeepTurnPackaging(...)` (behavior-preserving): + - replaced duplicated per-artifact assembly block with single orchestrator call; + - preserved investigation-state update and existing response/event contract. +3. Added focused unit test: + - `assistantDeepTurnPackaging.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 assembler/adapter pack passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantDeepResponseAssembler.test.ts` + - `assistantDeepTurnPackaging.test.ts` +3. Additional safety regressions passed: + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.12): +1. Extracted deep-turn input normalization/defaulting from `assistantService` into dedicated module: + - `assistantDeepTurnInputBuilder.ts` + - centralized normalization of: + - `followupStateUsage` default (`null` when absent); + - composition defaults (`problem_*` fields, `answer_structure_v11`); + - `problem_unit_ids_used` array normalization. +2. Rewired `assistantService` to build deep-turn input via: + - `buildAssistantDeepTurnPackagingInput(...)` + - followed by existing `assembleAssistantDeepTurnPackaging(...)` call (behavior-preserving). +3. Added focused unit tests: + - `assistantDeepTurnInputBuilder.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 assembler/adapter pack passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantDeepResponseAssembler.test.ts` + - `assistantDeepTurnPackaging.test.ts` + - `assistantDeepTurnInputBuilder.test.ts` +3. Additional safety regressions passed: + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.13): +1. Extracted investigation-state update/persist runtime block from `assistantService` into dedicated adapter: + - `assistantInvestigationStateRuntimeAdapter.ts` + - introduced: + - `buildAssistantInvestigationStateSnapshot(...)` + - `persistAssistantInvestigationStateSnapshot(...)` +2. Rewired `assistantService` to consume new adapter (behavior-preserving): + - same `investigation_state` snapshot semantics; + - same `FEATURE_ASSISTANT_INVESTIGATION_STATE_V1` gating; + - same session persistence call path. +3. Added focused unit tests: + - `assistantInvestigationStateRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 assembler/adapter pack passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantDeepResponseAssembler.test.ts` + - `assistantDeepTurnPackaging.test.ts` + - `assistantDeepTurnInputBuilder.test.ts` + - `assistantInvestigationStateRuntimeAdapter.test.ts` +3. Additional safety regressions passed: + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.14): +1. Extracted deep-lane assistant post-turn commit/persist/log block from `assistantService` into dedicated adapter: + - `assistantTurnCommitRuntimeAdapter.ts` + - introduced: + - `commitAssistantTurnAndLog(...)` +2. Rewired `assistantService` deep-lane to use new adapter (behavior-preserving): + - same append-to-session semantics; + - same session persistence behavior when session exists; + - same `assistant_message_processed` log envelope and event type. +3. Added focused unit tests: + - `assistantTurnCommitRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 assembler/adapter pack passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantDeepResponseAssembler.test.ts` + - `assistantDeepTurnPackaging.test.ts` + - `assistantDeepTurnInputBuilder.test.ts` + - `assistantInvestigationStateRuntimeAdapter.test.ts` + - `assistantTurnCommitRuntimeAdapter.test.ts` +3. Additional safety regressions passed: + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.15): +1. Extracted deep-lane pre-packaging context assembly from `assistantService` into dedicated module: + - `assistantDeepTurnPrePackagingContext.ts` + - centralized: + - dropped intent segments extraction; + - analysis context projection for contracts; + - debug route projection; + - resolved execution state projection; + - safe assistant reply base sanitization. +2. Rewired `assistantService` deep-lane to consume `buildAssistantDeepTurnPrePackagingContext(...)` (behavior-preserving). +3. Added focused unit tests: + - `assistantDeepTurnPrePackagingContext.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 assembler/adapter pack passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantDeepResponseAssembler.test.ts` + - `assistantDeepTurnPackaging.test.ts` + - `assistantDeepTurnInputBuilder.test.ts` + - `assistantInvestigationStateRuntimeAdapter.test.ts` + - `assistantTurnCommitRuntimeAdapter.test.ts` + - `assistantDeepTurnPrePackagingContext.test.ts` +3. Additional safety regressions passed: + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.16): +1. Extracted deep-lane success response envelope assembly from `assistantService` into dedicated builder: + - `assistantDeepTurnResponseBuilder.ts` + - introduced: + - `buildAssistantDeepTurnSuccessResponse(...)` +2. Rewired `assistantService` deep-lane to return response via new builder (behavior-preserving). +3. Added focused unit tests: + - `assistantDeepTurnResponseBuilder.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 assembler/adapter pack passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantDeepResponseAssembler.test.ts` + - `assistantDeepTurnPackaging.test.ts` + - `assistantDeepTurnInputBuilder.test.ts` + - `assistantInvestigationStateRuntimeAdapter.test.ts` + - `assistantTurnCommitRuntimeAdapter.test.ts` + - `assistantDeepTurnPrePackagingContext.test.ts` + - `assistantDeepTurnResponseBuilder.test.ts` +3. Additional safety regressions passed: + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.17): +1. Extracted deep-lane composition assembly from `assistantService` into dedicated runtime adapter: + - `assistantDeepTurnCompositionRuntimeAdapter.ts` + - introduced: + - `buildAssistantDeepTurnComposition(...)` +2. Centralized composition-time derivations (behavior-preserving): + - `focusDomainHint` from followup usage + investigation state; + - `questionTypeClass` via `resolveQuestionType(...)`; + - period presence signals from company anchors/normalization payload; + - downstream `composeAssistantAnswer(...)` call wiring. +3. Rewired `assistantService` deep-lane to consume adapter output (behavior-preserving). +4. Added focused unit tests: + - `assistantDeepTurnCompositionRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 assembler/adapter pack passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantDeepResponseAssembler.test.ts` + - `assistantDeepTurnPackaging.test.ts` + - `assistantDeepTurnInputBuilder.test.ts` + - `assistantInvestigationStateRuntimeAdapter.test.ts` + - `assistantTurnCommitRuntimeAdapter.test.ts` + - `assistantDeepTurnPrePackagingContext.test.ts` + - `assistantDeepTurnResponseBuilder.test.ts` + - `assistantDeepTurnCompositionRuntimeAdapter.test.ts` +3. Additional safety regressions passed: + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.18): +1. Extracted deep-lane guard runtime pipeline from `assistantService` into dedicated adapter: + - `assistantDeepTurnGuardRuntimeAdapter.ts` + - introduced: + - `applyAssistantDeepTurnRetrievalGuards(...)` + - `applyAssistantDeepTurnGroundingEligibility(...)` +2. Centralized and isolated runtime sequence (behavior-preserving): + - polarity guard on retrieval results; + - targeted evidence acquisition; + - evidence admissibility gate; + - grounded-answer eligibility evaluation + grounding status overlay. +3. Rewired `assistantService` deep-lane to consume adapter output (behavior-preserving). +4. Added focused unit tests: + - `assistantDeepTurnGuardRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 assembler/adapter pack passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantDeepResponseAssembler.test.ts` + - `assistantDeepTurnPackaging.test.ts` + - `assistantDeepTurnInputBuilder.test.ts` + - `assistantInvestigationStateRuntimeAdapter.test.ts` + - `assistantTurnCommitRuntimeAdapter.test.ts` + - `assistantDeepTurnPrePackagingContext.test.ts` + - `assistantDeepTurnResponseBuilder.test.ts` + - `assistantDeepTurnCompositionRuntimeAdapter.test.ts` + - `assistantDeepTurnGuardRuntimeAdapter.test.ts` +3. Additional safety regressions passed: + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.19): +1. Extracted deep-lane retrieval execution loop from `assistantService` into dedicated runtime adapter: + - `assistantDeepTurnRetrievalRuntimeAdapter.ts` + - introduced: + - `executeAssistantDeepTurnRetrievalPlan(...)` +2. Centralized retrieval runtime behavior (behavior-preserving): + - skipped/no-route call record generation; + - sequential route execution with temporal hint propagation; + - raw result capture; + - failed-route fallback normalization with stable error envelope. +3. Rewired `assistantService` deep-lane to consume retrieval adapter output (behavior-preserving). +4. Added focused unit tests: + - `assistantDeepTurnRetrievalRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 assembler/adapter pack passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantDeepResponseAssembler.test.ts` + - `assistantDeepTurnPackaging.test.ts` + - `assistantDeepTurnInputBuilder.test.ts` + - `assistantInvestigationStateRuntimeAdapter.test.ts` + - `assistantTurnCommitRuntimeAdapter.test.ts` + - `assistantDeepTurnPrePackagingContext.test.ts` + - `assistantDeepTurnResponseBuilder.test.ts` + - `assistantDeepTurnCompositionRuntimeAdapter.test.ts` + - `assistantDeepTurnGuardRuntimeAdapter.test.ts` + - `assistantDeepTurnRetrievalRuntimeAdapter.test.ts` +3. Additional safety regressions passed: + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.20): +1. Extracted deep-lane execution-plan assembly/enforcement from `assistantService` into dedicated runtime adapter: + - `assistantDeepTurnPlanRuntimeAdapter.ts` + - introduced: + - `buildAssistantDeepTurnExecutionPlan(...)` +2. Centralized planning runtime sequence (behavior-preserving): + - requirement extraction by fragment; + - execution plan build from route summary; + - RBP route-plan enforcement; + - FA route-plan enforcement; + - temporal hint overlay; + - polarity hint overlay. +3. Rewired `assistantService` deep-lane to consume adapter output (behavior-preserving). +4. Added focused unit tests: + - `assistantDeepTurnPlanRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 assembler/adapter pack passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantDeepResponseAssembler.test.ts` + - `assistantDeepTurnPackaging.test.ts` + - `assistantDeepTurnInputBuilder.test.ts` + - `assistantInvestigationStateRuntimeAdapter.test.ts` + - `assistantTurnCommitRuntimeAdapter.test.ts` + - `assistantDeepTurnPrePackagingContext.test.ts` + - `assistantDeepTurnResponseBuilder.test.ts` + - `assistantDeepTurnCompositionRuntimeAdapter.test.ts` + - `assistantDeepTurnGuardRuntimeAdapter.test.ts` + - `assistantDeepTurnRetrievalRuntimeAdapter.test.ts` + - `assistantDeepTurnPlanRuntimeAdapter.test.ts` +3. Additional safety regressions passed: + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.21): +1. Extracted deep-lane pre-guard runtime context assembly from `assistantService` into dedicated adapter: + - `assistantDeepTurnContextRuntimeAdapter.ts` + - introduced: + - `buildAssistantDeepTurnRuntimeContext(...)` +2. Centralized context runtime sequence (behavior-preserving): + - company anchors resolution; + - initial business-scope alignment; + - P0 domain inference + domain whitelist gating for guard focus; + - temporal guard resolution with runtime analysis context; + - domain polarity guard resolution; + - claim-bound anchors resolution; + - live business-scope resolution with followup flag; + - normalized live temporal hint projection. +3. Rewired `assistantService` deep-lane to consume context adapter output (behavior-preserving). +4. Added focused unit tests: + - `assistantDeepTurnContextRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 assembler/adapter pack passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantDeepResponseAssembler.test.ts` + - `assistantDeepTurnPackaging.test.ts` + - `assistantDeepTurnInputBuilder.test.ts` + - `assistantInvestigationStateRuntimeAdapter.test.ts` + - `assistantTurnCommitRuntimeAdapter.test.ts` + - `assistantDeepTurnPrePackagingContext.test.ts` + - `assistantDeepTurnResponseBuilder.test.ts` + - `assistantDeepTurnCompositionRuntimeAdapter.test.ts` + - `assistantDeepTurnGuardRuntimeAdapter.test.ts` + - `assistantDeepTurnRetrievalRuntimeAdapter.test.ts` + - `assistantDeepTurnPlanRuntimeAdapter.test.ts` + - `assistantDeepTurnContextRuntimeAdapter.test.ts` +3. Additional safety regressions passed: + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.22): +1. Extracted deep-lane post-retrieval grounding runtime orchestration from `assistantService` into dedicated adapter: + - `assistantDeepTurnGroundingRuntimeAdapter.ts` + - introduced: + - `runAssistantDeepTurnGroundingRuntime(...)` +2. Centralized grounding/runtime sequence (behavior-preserving): + - RBP live-route audit projection; + - FA live-route audit projection; + - coverage+grounding pipeline execution; + - grounded-answer eligibility overlay on base grounding check. +3. Rewired `assistantService` deep-lane to consume adapter output (behavior-preserving). +4. Added focused unit tests: + - `assistantDeepTurnGroundingRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 assembler/adapter pack passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantDeepResponseAssembler.test.ts` + - `assistantDeepTurnPackaging.test.ts` + - `assistantDeepTurnInputBuilder.test.ts` + - `assistantInvestigationStateRuntimeAdapter.test.ts` + - `assistantTurnCommitRuntimeAdapter.test.ts` + - `assistantDeepTurnPrePackagingContext.test.ts` + - `assistantDeepTurnResponseBuilder.test.ts` + - `assistantDeepTurnCompositionRuntimeAdapter.test.ts` + - `assistantDeepTurnGuardRuntimeAdapter.test.ts` + - `assistantDeepTurnRetrievalRuntimeAdapter.test.ts` + - `assistantDeepTurnPlanRuntimeAdapter.test.ts` + - `assistantDeepTurnContextRuntimeAdapter.test.ts` + - `assistantDeepTurnGroundingRuntimeAdapter.test.ts` +3. Additional safety regressions passed: + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Implemented in current pass (Phase 2.23): +1. Extracted deep-lane packaging orchestration block from `assistantService` into dedicated runtime adapter: + - `assistantDeepTurnPackagingRuntimeAdapter.ts` + - introduced: + - `runAssistantDeepTurnPackagingRuntime(...)` +2. Centralized packaging/runtime sequence (behavior-preserving): + - pre-packaging context assembly; + - investigation-state snapshot build/persist; + - deep-turn packaging input assembly; + - deep-turn packaging output projection (`safeAssistantReply`, debug payload, assistant item, processed log details). +3. Rewired `assistantService` deep-lane to consume packaging runtime adapter output (behavior-preserving). +4. Added focused unit tests: + - `assistantDeepTurnPackagingRuntimeAdapter.test.ts` + +Validation: +1. `npm run build` passed. +2. Targeted Stage 2 assembler/adapter pack passed: + - `assistantOrchestrationContracts.test.ts` + - `assistantOrchestrationRuntimeAdapter.test.ts` + - `assistantAnswerPackageBuilder.test.ts` + - `assistantCoverageGrounding.test.ts` + - `assistantQueryPlanning.test.ts` + - `assistantEvidenceBundleAssembler.test.ts` + - `assistantDebugPayloadAssembler.test.ts` + - `assistantMessageLogAssembler.test.ts` + - `assistantContractsBundleAssembler.test.ts` + - `assistantDeepResponseAssembler.test.ts` + - `assistantDeepTurnPackaging.test.ts` + - `assistantDeepTurnInputBuilder.test.ts` + - `assistantInvestigationStateRuntimeAdapter.test.ts` + - `assistantTurnCommitRuntimeAdapter.test.ts` + - `assistantDeepTurnPrePackagingContext.test.ts` + - `assistantDeepTurnResponseBuilder.test.ts` + - `assistantDeepTurnCompositionRuntimeAdapter.test.ts` + - `assistantDeepTurnGuardRuntimeAdapter.test.ts` + - `assistantDeepTurnRetrievalRuntimeAdapter.test.ts` + - `assistantDeepTurnPlanRuntimeAdapter.test.ts` + - `assistantDeepTurnContextRuntimeAdapter.test.ts` + - `assistantDeepTurnGroundingRuntimeAdapter.test.ts` + - `assistantDeepTurnPackagingRuntimeAdapter.test.ts` +3. Additional safety regressions passed: + - `assistantWave10SettlementCorrectiveRegression.test.ts` + - `assistantMcpRuntimeBridge.test.ts` + - `assistantAddressFollowupContext.test.ts` + +Status: **In progress (Phase 2.1 + 2.2 + 2.3 + 2.4 + 2.5 + 2.6 + 2.7 + 2.8 + 2.9 + 2.10 + 2.11 + 2.12 + 2.13 + 2.14 + 2.15 + 2.16 + 2.17 + 2.18 + 2.19 + 2.20 + 2.21 + 2.22 + 2.23 completed)** + +## Stage 3 (P2): Hybrid Semantic Layer (LLM + Deterministic Guards) + +Goal: +1. Use LLM for semantic extraction/decomposition in strict schema. +2. Keep deterministic guardrails as verifier, not primary “brain”. +3. Reduce dictionary overfitting and false route drifts. + +Status: Planned + +## Stage 4 (P2): Human-Centric Answer Layer + +Goal: +1. Move final user response to contract-driven answer package with: + - direct answer; + - what was checked; + - what was found; + - what remains unproven; + - best next step. +2. Keep claim-to-evidence binding strict. + +Status: Planned + +## Stage 5 (P3): Quality Loop Driven By GUI Markup + +Goal: +1. Drive backlog from `manual_case_decision` queues. +2. Build targeted regression packs from real failed comments. +3. Track trend by reason-code clusters. + +Status: Planned + +## 6. Non-Negotiable Constraints + +1. Do not break MCP route interfaces. +2. Do not remove manual logic without compatible replacement. +3. Preserve UTF-8 (without BOM) for all source/doc files. +4. Keep manual markup and autorun API contract stable. diff --git a/docs/TECH/1CLLMARCH.md b/docs/TECH/1CLLMARCH.md new file mode 100644 index 0000000..5b03739 --- /dev/null +++ b/docs/TECH/1CLLMARCH.md @@ -0,0 +1,1351 @@ +Да — помочь могу. И нет, у тебя не “всё в корне неправильно”. По форме ты уже описал вполне нормальный паттерн: **augmented LLM** с инструментами/MCP, **prompt chaining** по шагам и отдельный проход на проверку качества перед финальным ответом. Это очень похоже на то, что в официальных гайдах описывают как рабочую основу: сначала простые композиционные паттерны, потом только при необходимости — более автономные agent-циклы. И OpenAI, и Anthropic отдельно советуют сначала выжать максимум из **одного агента с инструментами**, а не городить много агентов слишком рано. ([Anthropic][1]) + +Но у тебя, по описанию, смешаны в одну массу **четыре разные роли**: + +1. разговор с пользователем, +2. семантическое понимание вопроса, +3. походы в 1С/MCP, +4. объяснение результата человеку. + Когда это всё делает одна и та же “мыслящая каша”, система и становится сухой, тупой и неясной в дебаге. Для таких систем проблема обычно не в том, что “мало LLM”, а в том, что нет **жёстких контрактов состояния** между шагами. Anthropic прямо разделяет **workflow** как предопределённые кодовые этапы и **agent** как динамический loop; для запросов в 1С у тебя, по сути, большая часть работы должна быть именно workflow, а не полностью свободный агент. ([Anthropic][1]) + +Самая важная мысль: **не надо “сильно упрощать” вопрос пользователя и выбрасывать оригинал**. Это одна из типовых ловушек. Нужно не simplification, а **semantic unpacking**: хранить рядом + +* исходный вопрос, +* нормализованную формулировку, +* список неоднозначностей, +* гипотезы домена, +* контракт ответа: что именно надо доказать, чтобы считать вопрос отвеченным. + Иначе ты рано убиваешь нюанс, а потом уже собираешь “правильные” данные не на тот вопрос. Это особенно критично в длинных agent-пайплайнах: актуальная практика сейчас вообще больше про **context engineering**, то есть про управление состоянием контекста, а не про красивый системный промпт. Контекст конечен, и чем больше ты тащишь в модель сырых логов/кусков, тем выше риск потери фокуса. ([Anthropic][2]) + +Я бы собрал тебе архитектуру так: + +```text +Пользователь + ↓ +A. Dialogue Interpreter (LLM) + ↓ +B. Query Frame Builder (LLM, strict JSON) + ↓ +C. Planner / Resolver (LLM + код) + ↓ +D. MCP / 1C Executor (только код и инструменты) + ↓ +E. Coverage Critic (LLM + правила) + ↓ +F. Answer Composer (LLM) + ↓ +Пользовательский ответ + инженерный trace +``` + +Ключевая идея тут в том, что **LLM не должна быть источником истины**. Истина — это 1С/MCP и детерминированные результаты вызовов. LLM делает интерпретацию, план, проверку покрытия и человеческую упаковку ответа. А всё, что связано с фактом “нашлось / не нашлось / есть противоречие / не удалось привязать сущность”, должно жить в машинном объекте состояния, а не в свободном тексте. Такой подход прямо согласуется с current best practices: модели + tools + state/memory + orchestration, с loop-выходами и внятными stop conditions. ([OpenAI Developers][3]) + +### Что должно появиться между шагами + +Не “много промптов”, а **несколько обязательных артефактов**. + +**1. QueryFrame** — как система поняла вопрос. + +```json +{ + "original_user_question": "...", + "normalized_question": "...", + "business_intent": "settlement_tail_check", + "requested_answer_shape": "list_with_explanation", + "time_scope": { + "explicit": null, + "inferred": "current_as_of_date", + "ambiguity": true + }, + "entity_candidates": [ + {"surface": "поставщики", "type": "counterparty_group"}, + {"surface": "хвосты", "type": "informal_term", "needs_domain_resolution": true} + ], + "ambiguities": [ + "что считать хвостами", + "на какую дату смотреть", + "по какому контуру: расчеты, акты, оплаты, закрытие документов" + ], + "success_criteria": [ + "определено доменное значение 'хвостов'", + "выбран период/as_of", + "собран список контрагентов с доказательством" + ] +} +``` + +**2. ExecutionPlan** — не просто “сходить в 1С”, а конкретный план доказательства. + +```json +{ + "plan_steps": [ + "resolve_domain_term('хвосты')", + "resolve_counterparty_scope('поставщики')", + "resolve_as_of_date()", + "query_open_settlements()", + "query_unclosed_docs_if_needed()" + ], + "tool_calls_expected": [...], + "fallbacks": [...], + "stop_conditions": [ + "all success criteria covered", + "hard blocker", + "ambiguity requires user clarification" + ] +} +``` + +**3. EvidenceBundle** — весь ground truth. + +```json +{ + "facts": [...], + "source_refs": [...], + "tool_runs": [...], + "entity_bindings": [...], + "not_found": [...], + "conflicts": [...], + "execution_errors": [...] +} +``` + +**4. CoverageReport** — самый недостающий у вас слой. + +```json +{ + "answerability": "full | partial | insufficient", + "covered_points": [...], + "uncovered_points": [...], + "why_partial": [...], + "next_best_actions": [...] +} +``` + +**5. FinalAnswerPackage** — уже для человека. + +```json +{ + "short_answer": "...", + "detailed_explanation": "...", + "evidence_summary": "...", + "limitations": "...", + "clarifying_question_if_needed": "..." +} +``` + +Вот это и даёт тебе одновременно и хороший UX, и инженерную диагностируемость. + +### Где у вас сейчас, скорее всего, ломается качество + +Первая ошибка: **слишком ранняя компрессия смысла**. +Не надо превращать вопрос пользователя в один “упрощённый” вариант и жить с ним дальше. Нужен пакет из: оригинал + нормализация + неоднозначности + критерии ответа. + +Вторая ошибка: **LLM сама решает, достаточно ли она ответила**. +Нужен отдельный слой **coverage critic**, который сверяет: + +* что хотел пользователь, +* что реально доказано evidence’ом, +* какие дыры остались. + Это очень похоже на паттерн **evaluator-optimizer**, который Anthropic отдельно рекомендует для задач, где качество растёт от дополнительной критики и итеративной доработки. ([Anthropic][1]) + +Третья ошибка: **инженерный trace не оформлен как продукт системы**. +У тебя он, судя по описанию, где-то размазан по логам. А он должен быть первоклассным объектом: чтобы и пользователь, и инженер могли понять, почему ответ полный/частичный/невозможный. + +### Как сделать ассистента “человечным”, а не сухим + +Сухость почти никогда не лечится “более креативным промптом”. Она лечится **формой финального ответа**. + +Финальный ответ у такого ассистента должен всегда иметь 5 смысловых слоёв: + +1. **Короткий вывод** — прямой ответ на вопрос. +2. **Что именно проверено** — какие регистры/документы/сущности смотрели. +3. **Что найдено** — с привязкой к фактам. +4. **Что не удалось установить** — честно, без стыда. +5. **Что нужно уточнить или что система сделает следующим лучшим шагом**. + +То есть не “вот ссылка на документ, иди сам разбирайся”, а: +“Под хвостами в текущем прогоне система проверила незакрытые расчёты с поставщиками на дату X. Найдены такие-то контрагенты. Для двух контрагентов есть движения без подтверждающего закрывающего документа, поэтому ответ частичный. Чтобы дать окончательный список хвостов, нужно уточнить: считать ли хвостами только просроченные расчёты или любые незакрытые остатки.” + +Вот это уже и человеку полезно, и инженеру показывает, **какого доменного определения не хватает**. + +### Практическое правило №1 + +**Не спрашивай LLM: “ответь на вопрос”**. +Спрашивай по шагам: + +* “Собери QueryFrame” +* “Собери ExecutionPlan” +* “Оцени CoverageReport” +* “Собери человеческий ответ по пакету evidence + coverage” + +То есть LLM не пишет свободный роман, а закрывает **конкретный контракт шага**. + +### Практическое правило №2 + +Для внутренних шагов используй **строгие структурированные выходы**, а не просто JSON “на честном слове”. В официальных гайдах OpenAI это отдельно вынесено как способ добиваться строгого совпадения со схемой при tool calling / structured outputs. Даже если у тебя не OpenAI-стек, сам принцип тот же: каждый шаг должен возвращать объект по схеме, иначе ретрай/repair loop. ([OpenAI][4]) + +### Практическое правило №3 + +**Не пихай в финальную LLM сырые результаты всех MCP-вызовов.** +Перед финальным проходом делай **evidence compaction**: + +* только нужные факты, +* только привязанные сущности, +* только важные ошибки, +* только конфликты, +* только краткие source refs. + +Иначе у тебя начинается классическая деградация от перегруженного контекста. Это сейчас уже прямо рассматривается как отдельная инженерная дисциплина — context engineering. ([Anthropic][2]) + +### Практическое правило №4 + +Держи **два параллельных выхода** на каждый turn: + +**Пользовательский** + +* короткий вывод, +* объяснение, +* ограничения, +* следующий вопрос/шаг. + +**Инженерный** + +* QueryFrame, +* Plan, +* Tool trace, +* EvidenceBundle, +* CoverageReport, +* reason codes. + +Например reason codes: + +* `missing_period` +* `ambiguous_business_term` +* `entity_not_resolved` +* `insufficient_1c_coverage` +* `conflicting_documents` +* `tool_execution_error` + +Это резко упрощает и улучшение доменов, и анализ того, почему “ответ тупой”. + +### Практическое правило №5 + +Введи **claim-to-evidence binding**. +Любое содержательное утверждение в финальном ответе должно быть либо: + +* доказано конкретным evidence id, +* либо помечено как inference, +* либо помечено как unknown. + +Это очень сильно отрезает галлюцинации в бухгалтерских и 1С-задачах. + +### Что я бы сделал именно в твоём кейсе с 1С + +Не строил бы “свободного умного агента”, который сам решает, куда идти по 1С. Я бы сделал **manager-style orchestrator** с небольшим числом жёстко описанных инструментов и доменных процедур. OpenAI отдельно отмечает, что чаще проблема не в количестве инструментов, а в том, что они похожи, пересекаются и плохо описаны; если инструменты чёткие, один агент часто справляется лучше и проще в сопровождении. ([OpenAI][5]) + +То есть не 40 мутных MCP-тулов вроде: + +* `search_docs` +* `find_data` +* `get_entries` + +А доменно: + +* `resolve_counterparty` +* `resolve_period_or_asof` +* `get_open_settlements` +* `get_unclosed_documents` +* `get_payments_for_counterparty` +* `get_document_movements` +* `explain_domain_term` + +И каждому — чёткое описание входа, выхода, ограничений и типичных ошибок. И Anthropic, и OpenAI отдельно подчёркивают, что надёжность агента очень сильно зависит от качества инструментов и их документации. ([Anthropic][1]) + +### Про Qwen 2.5 отдельно + +Если ты сидишь на локальном Qwen 2.5 и у тебя tool calling/MCP местами ведёт себя странно, это может быть не только “модель тупая”, а ещё и проблема **шаблона/парсера tool calls**. В `llama.cpp` прямо указана поддержка native tool call formats для **Qwen 2.5** и generic fallback, причём generic менее эффективен. У текущего `Qwen-Agent` функция tool calling и MCP есть, но сам репозиторий уже ориентирован на Qwen>=3.0; при этом в changelog есть отдельные упоминания адаптаций под Qwen2.5 general models в 2025 году. То есть сам подход рабочий, но стек надо очень внимательно проверять на уровне parser/template/runtime, а не только промпта. ([GitHub][6]) + +Итоговый тезис такой: +**вам не нужен “ещё более агентный агент”. Вам нужен доказательный диалоговый workflow с отдельным critic-слоем и явными артефактами состояния.** +Тогда система станет: + +* человечнее для пользователя, +* полезнее для инженера, +* понятнее в дебаге, +* устойчивее к доменным дырам, +* и при этом не обязательно сложнее по коду. + + + +[1]: https://www.anthropic.com/research/building-effective-agents "Building Effective AI Agents \ Anthropic" +[2]: https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents "Effective context engineering for AI agents \ Anthropic" +[3]: https://developers.openai.com/tracks/building-agents "Building agents" +[4]: https://openai.com/index/introducing-structured-outputs-in-the-api/ "Introducing Structured Outputs in the API | OpenAI" +[5]: https://openai.com/business/guides-and-resources/a-practical-guide-to-building-ai-agents/ "A practical guide to building agents | OpenAI" +[6]: https://github.com/ggml-org/llama.cpp/blob/master/docs/function-calling.md "llama.cpp/docs/function-calling.md at master · ggml-org/llama.cpp · GitHub" + +______ + +Ниже — ТЗ в прикладном виде под **диалогового 1С-ассистента с MCP**, где приоритет — **максимальное качество, объяснимость, удержание контекста и инженерная диагностируемость**, а не скорость. + +--- + +# ТЗ + +## Диалоговый LLM-ассистент для 1С через MCP + +### Версия: Draft v1 + +### Назначение: архитектура качественного, объяснимого и инженерно прозрачного ассистента + +## 1. Цель + +Разработать диалоговый ассистент для работы с данными 1С через MCP, который: + +* корректно понимает пользовательский вопрос в бизнес-терминах; +* удерживает смысл и контекст вопроса без грубой потери нюансов; +* умеет декомпозировать вопрос на доменные сущности и проверяемые подзадачи; +* выполняет доказательный проход по 1С через MCP-инструменты; +* формирует **человеческий**, а не сухой ответ; +* явно сообщает, **что найдено, что не найдено, чего не хватает, где неоднозначность**; +* одновременно помогает **инженерам системы** понимать, почему ответ получился полным, частичным или не получился. + +--- + +## 2. Проблема текущего состояния + +Текущее поведение ассистента описывается следующими симптомами: + +* ответы сухие и мало полезные для пользователя; +* система рано “сжимает” вопрос и теряет часть смысла; +* механика маршрутизации, извлечения данных и ответа человеку смешаны в одну логическую массу; +* отсутствует явный слой, который проверяет: **действительно ли собранные данные покрывают исходный вопрос**; +* отсутствует прозрачный reason trace для инженеров; +* при невозможности ответа система не объясняет полезно и предметно, **почему именно** ответ неполный. + +Следствие: ассистент выглядит тупым не только из-за модели, а в первую очередь из-за **неразделённых ролей и отсутствия промежуточных артефактов состояния**. + +--- + +## 3. Основной архитектурный принцип + +Ассистент должен быть построен не как “одна умная LLM, которая делает всё”, а как **жёсткий workflow из нескольких этапов**, где каждый этап производит **структурированный артефакт**, а не свободный текст. + +### Ключевой принцип + +Каждый шаг обязан оставлять после себя: + +* машинно проверяемый результат; +* объяснимую причину выбранного пути; +* список неопределённостей и ограничений. + +### Базовое разделение ролей + +Нужно развести четыре логики: + +1. **Диалоговая интерпретация** — что пользователь на самом деле хочет узнать. +2. **Доменная нормализация и планирование** — какие сущности, периоды, типы объектов и операции нужны. +3. **Детерминированное извлечение данных** — походы в 1С через MCP. +4. **Человеческая упаковка ответа** — как сформулировать результат полезно, мягко и честно. + +--- + +## 4. Границы этапа + +### Входит в этап + +* архитектура пайплайна запроса; +* контракты промежуточных артефактов; +* правила диалогового ответа; +* правила неполного ответа; +* reason codes; +* логирование и трассировка; +* требования к памяти контекста; +* требования к качеству и acceptance. + +### Не входит в этап + +* обучение собственной модели; +* полная реализация семантического поиска по документам вне 1С; +* универсальный open-ended autonomous agent; +* полный онтологический слой предприятия; +* автоматическое исправление данных в 1С. + +--- + +## 5. Целевой результат + +После внедрения этапа система должна уметь: + +* отвечать на вопрос не “как LLM”, а **как доказательный доменный ассистент**; +* возвращать не только ответ, но и полезное пояснение, почему ответ именно такой; +* разделять: + + * уверенно доказанное, + * вероятностный вывод, + * пробел данных, + * пробел доменной формализации, + * ошибку инструмента; +* удерживать контекст диалога между репликами; +* накапливать инженерную телеметрию для улучшения маршрутов и доменов. + +--- + +# 6. Общая архитектура + +## 6.1. Логическая схема + +```text +Пользователь + ↓ +1. Dialogue Interpreter + ↓ +2. Query Frame Builder + ↓ +3. Domain Resolver / Entity Resolver + ↓ +4. Execution Planner + ↓ +5. MCP / 1C Executor + ↓ +6. Evidence Builder + ↓ +7. Coverage Critic + ↓ +8. Answer Composer + ↓ +Пользовательский ответ + инженерный trace +``` + +--- + +## 6.2. Назначение блоков + +### 1. Dialogue Interpreter + +Задача: + +* понять намерение пользователя; +* сохранить исходную формулировку; +* выявить скрытые неоднозначности; +* определить ожидаемую форму ответа. + +Важно: +**Нельзя заменять исходный вопрос одним “упрощённым” вопросом и жить только с ним.** +Нужно сохранять: + +* оригинал; +* нормализованный вариант; +* доменные гипотезы; +* список неоднозначностей. + +--- + +### 2. Query Frame Builder + +Формирует структурированный каркас запроса — QueryFrame. + +В QueryFrame должны быть: + +* исходный вопрос; +* нормализованный вопрос; +* intent; +* предполагаемый домен; +* сущности-кандидаты; +* временной контекст; +* ожидаемый тип ответа; +* критерии успешности ответа. + +--- + +### 3. Domain Resolver / Entity Resolver + +Задача: + +* привязать пользовательские слова к доменным сущностям; +* разрешить разговорные термины; +* проверить, что именно имеется в виду под неоднозначными словами. + +Примеры: + +* “хвосты” → незакрытые остатки / просроченные расчёты / не закрытые документами движения; +* “поставщики” → контрагенты конкретного типа; +* “на конец месяца” → требуется точная дата отсечения; +* “договоры висят” → проверить логику “остаток по расчётам”, “незакрытые акты”, “непроведённые взаиморасчёты” и т.д. + +На этом этапе нельзя молча принимать одну трактовку, если есть несколько бизнес-валидных. + +--- + +### 4. Execution Planner + +Строит **план доказательства**, а не просто список тулов. + +План должен отвечать на вопросы: + +* что надо доказать; +* какие сущности обязательны; +* какие поля/регистры/документы нужны; +* какие шаги обязательны; +* какие шаги fallback; +* при каких условиях можно завершить ответ; +* когда нужно остановиться и запросить уточнение. + +--- + +### 5. MCP / 1C Executor + +Это детерминированный контур. + +Требования: + +* никакой свободной импровизации LLM при исполнении; +* строго типизированные вызовы; +* прозрачные ошибки; +* возвращаемый результат должен быть нормализован; +* каждый вызов должен иметь идентификатор, статус, параметры, источник, время выполнения. + +--- + +### 6. Evidence Builder + +Собирает результат исполнений в единый **EvidenceBundle**: + +* факты; +* привязки сущностей; +* источники; +* найденные документы; +* найденные движения; +* конфликты; +* пробелы; +* ошибки исполнения. + +EvidenceBundle — это основной ground truth для ответа. + +--- + +### 7. Coverage Critic + +Самый важный слой. + +Проверяет: + +* что хотел пользователь; +* что действительно найдено; +* что доказано; +* что покрыто частично; +* что не покрыто вообще; +* какая причина неполноты. + +Этот блок не должен “додумывать”, он должен **оценивать покрытие**. + +--- + +### 8. Answer Composer + +Формирует финальный ответ человеку. + +Ответ должен быть: + +* прямым; +* полезным; +* объясняющим; +* честным по ограничениям; +* пригодным и для пользователя, и для инженера в разборе кейса. + +--- + +# 7. Контракты данных + +## 7.1. QueryFrame + +```json +{ + "request_id": "uuid", + "conversation_id": "uuid", + "turn_id": 12, + "original_user_question": "По каким поставщикам висят хвосты?", + "normalized_question": "Определить список поставщиков с незавершенными расчетами или иными незакрытыми остатками на целевую дату.", + "business_intent": "detect_open_supplier_settlements", + "expected_answer_shape": "list_with_explanation", + "domain_candidates": [ + "supplier_settlements", + "document_closure", + "mutual_reconciliations" + ], + "time_scope": { + "explicit_period": null, + "as_of_date": null, + "needs_resolution": true + }, + "entity_candidates": [ + { + "surface": "поставщики", + "type": "counterparty_group", + "confidence": 0.96 + }, + { + "surface": "хвосты", + "type": "informal_business_term", + "confidence": 0.41, + "requires_domain_resolution": true + } + ], + "ambiguities": [ + "Не определено, что считать 'хвостами'", + "Не определена дата отсечения", + "Не определен точный контур проверки" + ], + "success_criteria": [ + "Определить трактовку термина 'хвосты'", + "Определить дату/период", + "Найти подтверждаемые остатки или незакрытые документы", + "Сформировать список контрагентов с обоснованием" + ] +} +``` + +--- + +## 7.2. ExecutionPlan + +```json +{ + "request_id": "uuid", + "plan_version": 1, + "goal": "Подтвердить наличие незакрытых обязательств/остатков по поставщикам", + "steps": [ + { + "step_id": "S1", + "type": "resolve_business_term", + "input": "хвосты", + "required": true + }, + { + "step_id": "S2", + "type": "resolve_as_of_date", + "required": true + }, + { + "step_id": "S3", + "type": "resolve_counterparty_scope", + "input": "поставщики", + "required": true + }, + { + "step_id": "S4", + "type": "query_open_settlements", + "required": true + }, + { + "step_id": "S5", + "type": "query_supporting_documents", + "required": false + } + ], + "fallbacks": [ + "Если термин 'хвосты' не резолвится однозначно — проверить 2-3 допустимые трактовки и явно показать пользователю", + "Если дата отсутствует — использовать рабочую дату с явной пометкой", + "Если список поставщиков не выделяется напрямую — применить фильтр по типу контрагента" + ], + "stop_conditions": [ + "success_criteria_fully_covered", + "critical_ambiguity_requires_user_input", + "tooling_failure_no_evidence", + "max_execution_budget_reached" + ] +} +``` + +--- + +## 7.3. ToolRun + +```json +{ + "tool_run_id": "uuid", + "tool_name": "get_open_settlements", + "status": "success", + "input": { + "counterparty_type": "supplier", + "as_of_date": "2026-04-10" + }, + "started_at": "2026-04-10T12:01:15Z", + "finished_at": "2026-04-10T12:01:16Z", + "duration_ms": 842, + "rows_returned": 17, + "error_code": null, + "error_message": null +} +``` + +--- + +## 7.4. EvidenceBundle + +```json +{ + "request_id": "uuid", + "facts": [ + { + "fact_id": "F1", + "type": "open_balance", + "entity": "ООО Альфа", + "value": 152340.55, + "currency": "RUB", + "as_of_date": "2026-04-10", + "source_ref": "SRC12" + } + ], + "entity_bindings": [ + { + "surface": "поставщики", + "resolved_to": "counterparty.role=supplier", + "confidence": 0.97 + } + ], + "source_refs": [ + { + "source_ref": "SRC12", + "kind": "1C_register_extract", + "object_type": "settlement_register", + "object_id": "..." + } + ], + "documents": [], + "conflicts": [], + "not_found": [], + "execution_errors": [] +} +``` + +--- + +## 7.5. CoverageReport + +```json +{ + "request_id": "uuid", + "answerability": "partial", + "covered_points": [ + "Сформирован список поставщиков с открытыми остатками", + "Определена дата отсечения" + ], + "uncovered_points": [ + "Не доказано, считать ли все остатки именно 'хвостами' в бизнес-смысле", + "Не подтверждено закрытие документами по каждому случаю" + ], + "why_partial": [ + { + "reason_code": "AMBIGUOUS_BUSINESS_TERM", + "message": "Термин 'хвосты' не имеет единственной фиксированной трактовки" + }, + { + "reason_code": "EVIDENCE_DEPTH_LIMIT", + "message": "Для части контрагентов есть остатки, но нет дополнительной проверки закрывающих документов" + } + ], + "next_best_actions": [ + "Уточнить, считать ли хвостами любые незакрытые остатки или только просроченные/неподтвержденные", + "Запустить углубленный проход по документам" + ] +} +``` + +--- + +## 7.6. FinalAnswerPackage + +```json +{ + "short_answer": "На текущую дату выявлены поставщики с незакрытыми остатками.", + "user_facing_answer": "Проверка выполнена по контуру расчетов с поставщиками...", + "evidence_summary": [ + "Найдены открытые остатки по 7 контрагентам", + "Для 2 случаев есть недостающая глубина подтверждения" + ], + "limitations": [ + "Термин 'хвосты' интерпретирован как незакрытые остатки по расчетам" + ], + "clarifying_question": "Нужно ли считать хвостами только просроченные случаи, или любые незакрытые остатки?" +} +``` + +--- + +# 8. Пайплайн обработки запроса + +## 8.1. Этап 1. Приём и первичная интерпретация + +Вход: пользовательская реплика. +Выход: QueryFrame draft. + +Обязательные действия: + +* сохранить оригинальную формулировку; +* выделить intent; +* выделить сущности; +* выявить разговорные/неформальные термины; +* определить недостающие обязательные параметры; +* определить ожидаемый формат ответа. + +--- + +## 8.2. Этап 2. Разрешение доменной неоднозначности + +Вход: QueryFrame draft. +Выход: QueryFrame resolved. + +Обязательные действия: + +* резолвить бизнес-термины; +* резолвить типы сущностей; +* резолвить временные параметры; +* не делать скрытых предположений без пометки. + +Если есть несколько допустимых трактовок: + +* либо выбрать одну с явным дисклеймером; +* либо проверить несколько трактовок; +* либо задать уточнение, если без этого ответ потенциально вводит в заблуждение. + +--- + +## 8.3. Этап 3. Построение плана + +Вход: QueryFrame resolved. +Выход: ExecutionPlan. + +Требования: + +* шаги должны быть атомарными; +* обязательные и необязательные шаги должны быть разделены; +* должен быть определён критерий остановки; +* должен быть определён fallback. + +--- + +## 8.4. Этап 4. Исполнение плана + +Вход: ExecutionPlan. +Выход: ToolRun[]. + +Требования: + +* каждый вызов независим и логируется; +* ошибка не должна теряться; +* если инструмент вернул пустой результат, это отдельное состояние, а не “успех без данных”. + +--- + +## 8.5. Этап 5. Сбор доказательной базы + +Вход: ToolRun[]. +Выход: EvidenceBundle. + +Правила: + +* факты должны быть нормализованы; +* дубликаты должны быть схлопнуты; +* противоречия должны быть вынесены отдельно; +* важна не полнота сырых данных, а качественная сборка доказательной базы. + +--- + +## 8.6. Этап 6. Проверка покрытия + +Вход: QueryFrame + EvidenceBundle. +Выход: CoverageReport. + +CoverageReport обязателен всегда, даже если ответ “полный”. + +--- + +## 8.7. Этап 7. Формирование ответа + +Вход: QueryFrame + EvidenceBundle + CoverageReport. +Выход: FinalAnswerPackage. + +--- + +# 9. Правила ответа пользователю + +## 9.1. Общая форма ответа + +Каждый ответ должен содержать до пяти смысловых блоков: + +1. **Прямой ответ** +2. **Что именно проверено** +3. **Что найдено** +4. **Что не удалось установить** +5. **Что нужно уточнить / какой следующий шаг наиболее полезен** + +--- + +## 9.2. Требования к стилю ответа + +Ответ должен: + +* быть человеческим; +* не быть “канцелярской выгрузкой”; +* не быть пустым перечислением ссылок и документов; +* объяснять связь между вопросом и найденными данными. + +Плохой ответ: + +> Найден документ списания с расчетного счета. Ссылка: ... + +Хороший ответ: + +> Вопрос интерпретирован как проверка незакрытых расчетов с поставщиками на дату X. +> По данным 1С найдены такие-то контрагенты с открытыми остатками. +> Один из найденных документов относится к списанию средств, но сам по себе не доказывает наличие “хвоста”, поэтому использован только как вспомогательный источник, а не как основное основание ответа. + +--- + +## 9.3. Правила неполного ответа + +Если ответ частичный, система обязана не просто написать “недостаточно данных”, а объяснить: + +* чего конкретно не хватает; +* это проблема данных, маршрута или доменного определения; +* какой следующий шаг даст наибольшую пользу. + +Пример: + +* “Найдены открытые остатки, но для части случаев не выполнена проверка закрывающих документов.” +* “Термин ‘хвосты’ не имеет фиксированной трактовки в текущем домене.” +* “Вопрос можно доуточнить по дате отсечения, иначе используется рабочая дата.” + +--- + +# 10. Reason codes + +Нужно ввести единый справочник причин неполноты, ошибок и спорных состояний. + +## 10.1. Минимальный набор reason codes + +### Домен и интерпретация + +* `AMBIGUOUS_BUSINESS_TERM` +* `MISSING_PERIOD` +* `MISSING_AS_OF_DATE` +* `MULTIPLE_VALID_INTERPRETATIONS` +* `ENTITY_NOT_RESOLVED` +* `LOW_CONFIDENCE_ENTITY_BINDING` + +### Данные и покрытие + +* `NO_MATCH_IN_1C` +* `PARTIAL_MATCH_IN_1C` +* `INSUFFICIENT_DOCUMENT_DEPTH` +* `CONFLICTING_EVIDENCE` +* `MISSING_SUPPORTING_DOCUMENT` +* `EVIDENCE_DEPTH_LIMIT` + +### Инструменты и исполнение + +* `TOOL_EXECUTION_ERROR` +* `MCP_TIMEOUT` +* `MCP_SCHEMA_ERROR` +* `INVALID_TOOL_OUTPUT` +* `ROUTE_EXECUTION_ABORTED` + +### Диалог и UX + +* `QUESTION_UNDER_SPECIFIED` +* `NEEDS_USER_CONFIRMATION` +* `ASSUMPTION_APPLIED_EXPLICITLY` + +--- + +# 11. Память и удержание контекста + +## 11.1. Требование + +Ассистент должен работать не как набор независимых реплик, а как **диалоговая система с состоянием**. + +## 11.2. Что хранить между ходами + +Нужно хранить ConversationState: + +```json +{ + "conversation_id": "uuid", + "active_domain": "supplier_settlements", + "active_entities": [ + {"type": "counterparty_group", "value": "suppliers"} + ], + "active_time_scope": { + "as_of_date": "2026-04-10" + }, + "resolved_business_terms": [ + { + "surface": "хвосты", + "resolved_to": "open_settlement_balance", + "confidence": 0.72 + } + ], + "last_clarifications": [], + "open_ambiguities": [], + "last_evidence_refs": ["SRC12", "SRC19"] +} +``` + +## 11.3. Правила использования контекста + +* контекст не должен бесконтрольно разрастаться; +* в следующий ход передаются только релевантные выжимки; +* старые гипотезы не должны silently переезжать в “истину”; +* каждая предпосылка должна иметь пометку: + + * подтверждена, + * унаследована, + * предположена. + +--- + +# 12. Требования к инструментальному контуру + +## 12.1. MCP-инструменты должны быть доменно гранулированы + +Плохо: + +* `search_data` +* `find_docs` +* `query_1c_anything` + +Хорошо: + +* `resolve_counterparty` +* `resolve_period_or_asof` +* `get_open_supplier_settlements` +* `get_unclosed_documents_for_counterparty` +* `get_document_movements` +* `get_payment_chain` +* `get_balance_as_of` +* `get_related_closing_documents` + +--- + +## 12.2. Каждый инструмент обязан иметь + +* чёткое описание назначения; +* входную схему; +* выходную схему; +* перечень типовых ошибок; +* ограничения; +* политику пустого ответа. + +--- + +## 12.3. Политика пустого результата + +Пустой результат не равен успешному ответу пользователю. + +Нужно различать: + +* данных нет; +* данных не найдено по текущему маршруту; +* сущность не привязана; +* фильтр слишком узкий; +* инструмент вернул ноль из-за ошибки/схемы; +* вопрос некорректно поставлен. + +--- + +# 13. Требования к LLM-слоям + +## 13.1. Общий принцип + +LLM не должна быть единственным источником решения. +LLM должна выполнять следующие типы задач: + +* интерпретация вопроса; +* построение QueryFrame; +* планирование; +* оценка покрытия; +* человекоориентированная сборка ответа. + +LLM не должна: + +* выдумывать факты из 1С; +* подменять инструментальный ответ домыслом; +* молча трактовать неопределённый бизнес-термин как однозначный. + +--- + +## 13.2. Формат взаимодействия с LLM + +Для внутренних шагов использовать только: + +* строгие схемы JSON; +* validator; +* repair/retry loop при нарушении схемы. + +--- + +## 13.3. Отдельные проходы LLM + +Нужно разделить как минимум на 4 разных промптовых режима: + +1. **Interpreter Prompt** + Понимание вопроса и неоднозначностей. + +2. **Planner Prompt** + Построение плана извлечения. + +3. **Critic Prompt** + Проверка покрытия и полноты. + +4. **Answer Prompt** + Формирование финального ответа человеку. + +Запрещается использовать один и тот же общий промпт для всех режимов. + +--- + +# 14. Требования к инженерной диагностике + +## 14.1. По каждому запросу сохранять + +* QueryFrame; +* ExecutionPlan; +* ToolRun[]; +* EvidenceBundle; +* CoverageReport; +* FinalAnswerPackage; +* latency по этапам; +* reason codes; +* summary failure class. + +--- + +## 14.2. Нужен отдельный debug-вывод + +Инженерный trace должен быть читаемым. + +Минимальная структура debug view: + +* как понят вопрос; +* какие сущности распознаны; +* какие сущности не распознаны; +* какой маршрут выбран; +* какие тулзы вызваны; +* что реально найдено; +* почему ответ полный/частичный/ошибочный. + +--- + +## 14.3. Классы итогов запроса + +На верхнем уровне запрос должен классифицироваться как: + +* `FULLY_ANSWERED` +* `PARTIALLY_ANSWERED` +* `BLOCKED_BY_AMBIGUITY` +* `BLOCKED_BY_MISSING_DATA` +* `BLOCKED_BY_TOOLING` +* `MISROUTED` +* `FAILED_TO_BIND_ENTITIES` + +--- + +# 15. Acceptance criteria + +Система считается принятой на этапе, если: + +## 15.1. По качеству ответа + +* ответ всегда содержит прямой вывод; +* ответ не состоит из голого набора ссылок; +* ответ объясняет, почему найденные данные относятся к вопросу; +* неполный ответ объясняется предметно. + +## 15.2. По объяснимости + +* по каждому кейсу доступен CoverageReport; +* по каждому кейсу доступны reason codes; +* можно восстановить, почему выбран маршрут. + +## 15.3. По устойчивости + +* сломанный или пустой tool output не приводит к “уверенному бреду”; +* неоднозначные термины не замалчиваются; +* система не теряет исходный смысл вопроса после нормализации. + +## 15.4. По контексту + +* следующий ход корректно использует ранее разрешённые сущности и периоды; +* контекст не дрейфует в соседний домен без причины; +* предыдущие предположения не становятся “фактами” без подтверждения. + +--- + +# 16. План внедрения + +## Этап 1. QueryFrame и разделение ролей + +Сделать: + +* отдельный Interpreter; +* отдельный Planner; +* отдельный Critic; +* отдельный Answer Composer. + +Результат: + +* система перестаёт быть “одной кашей”. + +--- + +## Этап 2. Ввести структурированные артефакты + +Сделать: + +* QueryFrame; +* ExecutionPlan; +* ToolRun; +* EvidenceBundle; +* CoverageReport. + +Результат: + +* появляется прозрачная трассировка. + +--- + +## Этап 3. Ввести reason codes + +Сделать: + +* единый справочник; +* обязательную простановку причин неполноты. + +Результат: + +* становится видно, где проблема: домен, данные, маршрут или инструмент. + +--- + +## Этап 4. Переделать финальный ответ + +Сделать: + +* новый шаблон ответа; +* вывод “что найдено / что не доказано / что уточнить”. + +Результат: + +* ответы становятся полезными и человеческими. + +--- + +## Этап 5. Контекст и диалог + +Сделать: + +* ConversationState; +* наследование разрешённых сущностей и периода; +* учёт открытых неоднозначностей. + +Результат: + +* появляется реальный диалог, а не серия разрозненных вызовов. + +--- + +## Этап 6. Оценка качества + +Сделать: + +* набор сценариев; +* ручную разметку; +* сравнение old vs new; +* сбор статистики по reason codes. + +Результат: + +* становится видно, что реально улучшилось. + +--- + +# 17. Основные риски + +## Риск 1. Ранняя потеря смысла + +Причина: слишком сильная “нормализация”. +Снижение риска: всегда хранить original question + normalized question + ambiguities. + +## Риск 2. Скрытые предположения + +Причина: LLM молча выбирает трактовку. +Снижение риска: reason code + явная пометка assumption. + +## Риск 3. Сырой контекст перегружает модель + +Причина: в финальный шаг передаётся весь мусор. +Снижение риска: отдельный EvidenceBuilder и evidence compaction. + +## Риск 4. Пустой output трактуется как ответ + +Причина: тул вернул ноль, а система делает вывод. +Снижение риска: разделить no-data / no-match / tool-failure. + +## Риск 5. Ответ красивый, но недоказательный + +Причина: хороший prose без claim binding. +Снижение риска: каждое содержательное утверждение должно опираться на evidence. + +--- + +# 18. Итоговое архитектурное решение + +Для данной задачи рекомендуется не fully autonomous agent, а **жёсткий доказательный workflow с LLM-слоями**. + +## Итоговая формула + +Ассистент = + +* LLM для понимания вопроса, +* LLM для планирования, +* код и MCP для извлечения фактов, +* LLM для проверки покрытия, +* LLM для человеческого ответа, +* обязательные промежуточные артефакты, +* обязательные reason codes, +* обязательный инженерный trace. + +--- + +# 19. Коротко: что именно надо сделать в первую очередь + +Если совсем приземлённо, то первыми задачами должны стать вот эти: + +1. Вынести отдельный `QueryFrame`. +2. Вынести отдельный `CoverageReport`. +3. Запретить финальному ответу формироваться без CoverageReport. +4. Ввести reason codes. +5. Сделать новый шаблон финального ответа. +6. Ввести `ConversationState`. +7. Перестать считать “упрощённый вопрос” заменой исходного вопроса. + +--- + +# 20. Ожидаемый эффект после внедрения + +После внедрения ассистент должен перестать выглядеть как: + +* сухая LLM-выгрузка; +* случайный маршрутизатор по 1С; +* генератор ссылок без объяснения. + +И должен начать выглядеть как: + +* осмысленный доменный ассистент; +* понятный и честный собеседник; +* диагностируемая инженерная система; +* инструмент, который одновременно помогает пользователю и вскрывает пробелы домена/маршрутов. + +--- + + diff --git a/docs/TECH/1CLLMARCH.zip b/docs/TECH/1CLLMARCH.zip new file mode 100644 index 0000000000000000000000000000000000000000..cecf5e0048aed8e85d5d3f843362d7cfcb3c7ce4 GIT binary patch literal 16141 zcmaKzLy#~`%%I1%=N;R&ZQHhO+qP}nwr$(C_xty-yQfVm-CdPb<=CB09(gHX5EKB2 z{}kLNQXAm^5f}h`00tpxX&C`UA#qw8V;6U506>uc3HaZOiZUz!c-+#uMc02;_@A}- zTsdm3rJVZlj!>M6A%TI&NH!W(F6uN2lU*;+bQUK)wBthr6E{ov$3&sZaH=GlYrKj# z7?LqLbaA<6PfPzvvIrw0i3iH7@C^)5y-S$ZZS7j=!HIjAdRk&5p0(0h)m^o%PWt7& zHQC+%@=ocU9(gCnk-?M2F};iZS~sf9KF&3Lpk5} z^h}g#^yZv+oo;l!bA6NfS^S=#ZGF)1<#Dt2MfY`li9L1JJvy*{0NCMVXpy~<#Z5;& zo!zgmM;owrnBi`**$o5UOhI1+@b*|glJA88zRet&3g2&>SliZac6BJ&xn5aqY&4yn zo;FVOOnd+KN8kS**M5Y5AQTrVHHm1AKi)VY0)R&sC>tB>o;3d@RBAf5W)#w*+_$ zRu3TLHrin?i0xl|)gznM{6hT&=HVUo;~kCPN6lF%eAW{fH8H_o%-XbXbh|$mcy#!@ z91DapZLlz%58-vso)($PC&`h%ITU~BhHAZ{Sm;|lLUv!{Quk~e*POuo!9Fq2wKk2k z_<$5`2t5Yycntt!WPQP(7q+P+iIHSN&LWrj3+)6^^lOC)3m6Gmw0EEC5Pc?h1UG-8 zNEiXsu`-9+vG?CCy;ZWoVoo37zfM=-5n$@ASF_30r0R3Yv2?X<*35{~tgElTd3A@p z89+`i1~2Wh>bb`*4Hy}w&TJciK|F&VzYfl$!2JVzhA9Pc9u8*9AwD~uKL2!X-B%zc zWAge?J&ZA}rXTciS`uppZH_oyb$sn{yB6N_F?-m#o0~rN3#RCFlEx~K+YR{Glasuc zWn$(AF_nmD)R*O7Fwa^3&5dw)ix z61HS4`q$qZ4{z_;MWSD<)lS}pnTMDA@3&EA$Wt=ei1g3vof&Sz+ub}p@VD5$v8)-L9!R}f z%#>h19KpwSX#hv#HmGS(i4p8K3V8q+c~|@qBzEFyqU>GfACT=0AT|uQN^1MXaU7Xk zJbNBMSg6s{_Vs1z;nGKaHGFGEpobyIvuv_h**B!$muuC}Zp_Q7jXM?tUv{&>jI;=_ z@4nL5$g4fq*fz$EXKf1>oMRW^%a3&%1u(7TZNF9U=X;+N z2yTe1k&#o6Ly*G<}z6*GgoP6OnQK6bAghfHSsGP&kG3LH?04Fpy zfQ4fi+dkq|k1S0PiGtDEC$`TZ?G)}jNmM)V>XEB`6ui10Q3%*FdC^FZ@Izx1lW%?Y z!eiD#)1a8yP02}NKJbe~>Mc@TzQcf}9}KYTwQ+y;-n?Cn3Ce|@Ym?`bX^}=e5Y$xDov=lJyF@+xLO#AoLa7FPFN|7I+_XNHTmTtN%Ps6BsXac*$Ysel zY5~&n`+XjfU#&0&zZqW8XK=G5RWi zihg9=4CKe@(B4corJ32km6z2ST^=o#t*u?_wHd#rJ4}ZkpWd??$JZs@9v?EVE}fln zGrO06S61Fq6j#n$F`8^&T~p|@Q$J->8zV24O%}Hr!GP8U>4 zoNY7XvRGDiE0Cl*&90)4IL}xj!HjwpnmT2;@*N zH1P+JDdx6tqH<&T{MJtzp5bN@%s#(}s+WjRO>-x9trr?hCN1b`F5sBY03+iFpbI)A zqPQe3`pUU8ESfk^e^Z!nYfH{y&LA^O-H*wC_u#)N&tPJm`G?|aBzsv3J} zGZ7%$Xpa3nh}OB|3`>7BSNpCS0xk^OXCV+lUiNSzZP)~QeEcn5cpfW8LRqO{Uwxn0 z@aUK|7}x~$T)vAi4q?vv-igy1m>?70^GCd{|IjWbY?sFLiRvfXWJXuTh#}4A7OV+a zc#qm}A8-^+z zj5};(8yCRN&@vE-@K{YjKY7+=kkiA=p|jqS`~)ko$uWNmmMmw#C+#~oRJ#mCexU~YcGHYH%U3wn$uB@>-ERA{$U0YspcXn!zq^74T$9<@Bv{q&~xtn*-ovGTecC=DH>DXev`3dvGI%8ry zOO&h*V(4w3u(p@WS@ykVc0WBMd~KNRmSI6_2cR?1s8(c4T#6b^$!${aDs8pxwy#+_ z-KK4JTeAh;`$3CrYFsF-YGN#lG%SLI zZys3t&;4w6ziHw0PFLiC^ID?kmI`*-A_`3`#7FTJ5{B%+@zx37<}gyc*ze+QfaxT} zk>@ZH;*zETlqFJ`T07Til#y;wepyI4`u%$PsixmWZf_{k(m9}-TpM$AYru@vm6x^%Z>H-cBGR+8D4SA^S@D#q4*+G=N(Yi|sB;w&u|-d{;9)9;yr@%a z-n{cp)wxT3$NBe*Ri{06l~LA6bL1e<%HO^AR)&9f?xqSSYIY+Al74&cN@X>^i@A38 zn$~_1+l@!Mhhb(K3uWDV)~ai;dhBMRn@ro+VxX{_J~KOJ7BR*thG19MxcZYXTWB3q z-C*vDuGpqau0~Vu-1`E_E7=u}(MGp(7&c4=TSZMVYgW3`#$2m5b*F0U+AQx@wh;R! z{e4)cmznn0G;De{TXjSIRbw$VJ^C6{?N%@CUNWt&RQ0WkP9NEUdzZC=;TC&PZz~Vv zcl2dzbh}j9rq4qExN7CQ)}H-Lcs+XzR`WfU`c|!7sMz+GcI!+#Y%zRsy<2FPbs4mK zR$9?_nB%xw{uF(hR&F1e7o2zqJ9+W(a2FC4hWJE5aoJZ`H+#=%*gj?3p@<7A*RZaL zDNXG#1d@J~z?3bcNuTOOo0hr^Sn~k`R$z?r?+%RGaL_gt@PT5~cG77_Hv7)*O7%d6Rt#fL2(+ow5?@AwDbgN=D8 z&l}4C$iYfl9}bzqXAaP!$0Z4Amg1md&HBHC0UH{`i+rKl0`iz~b^u-8BVaoT%=*GC z50I00j?HJj@}&wNFSYH6soNA}{j#4MP(4*SAdFKHO=4XZH1R%YyDYxQnHg+@py|M1 zyK#b8kJ*gGqF>74#E%0V2S`t%PR6aE!Q9WmNu-`2btqkJ;1);*W?7N0h4w;0CWVYH zZ5`?QFG+NPaa+2u@rU-?4>=4= zkd(5TL?5TtJG@J*(uSa(1HaEveT-fBr_C60bgRH~vm<`HH}eM1gRthZBaV zn+Cn{WKzb?i+KdOx8hK;GS>F$YTqh%RK0pzEU1BvGMbT_^3djcqORqZ2XGh-dfuV$ zQ^QJNhCG7Ky(1;P@IK%_To}ZM6A}xWF2-Y9Z?)A?47s@rc)8%-M{x+5BiH+9Cd?=E zL?Go5j2oyTUxph-G_R2}D%967(2e2@w^#Fp)fT0H(L-J^g08?~{&1XW$cRRpJ)Z=H z`5A}`uzW+c%YBAfztQxrq#d!dUtD{PLm`^QV#B=9hzq{)OqJ4H&H+|C!-tI^`^nKc zi>-x}n1L>kct&%RCPc^dGth}|MQy#lAv@++9di*CS$?U3j-eTLH|#`}ZkUcrPsox1 z?A7)lLzCK|csw)7-?@U<6@FPj18b6?2(aHM5Tthnp;V9+u9MaxPY~Z(P;w39*!|@x zX*%z&F%Xl_q&ORXx1#%zB7y(C8PFbbDNv*L;C%eS^wZ~#t$22xJS-=E1QS2Hg$a5R zaq@3K(qNNxi}_(-w#x8>I#u@SzZ~?o`?WDdo5Mw)udMQ%!l$SXI}K1DVExe`!Y=j7 zGi&kC#SIa07gYdNX+hK&KPBDC3s?Iy?FO_@0fg8avFnq^{}M;~oKzae?dLwF4OW1z z2m8t$-v+;r4v$fAr_w=)@$i>G6zb72io)OcAM+gaY>BEfoX^C;IbpW`(~ z;%mzrMs(TpzC+N>^+)vys#%?9AKU{7%HZ>#&F(QNlYlk+$ssPrcl-B;Cx?k(f1PIn za)cI?QX4owA-MY?v!1~p(AE@B|y zD;nHA`{bTFVQ-VQ^Nu558)Gh+(goX=@HV~eAy7SGdQUG+U2~IuG#V9va3_WJN~NJN zoWVTiNS9ay_h5tezD*=PL=3^FMPQxRlB>{$7PV#0!33A+;{q+fKnh#b=SO2`!qPA_ z(1#(COBx>mwA?xfZf}~yXG3k0Ddx;H>@8jIyzTH9*$~}7z=8%x{`V55amUIvN^aHb z0C4xmRstM!c%1?NGeW$!Y);g_v%laqobIOu=}b+4RwpD_-EZ<}QZ8rl!QsL4mr!5` zTl0xAz=VS1;^U+C@ZNC(?$qjKR)wb(*+-G+BSuw$Hp(h4)qtM9PA~r&bwt7n9v?JH zp>_X2l2JMBA+Qclfrtco4Pv57uP<(59L^jM^v$}_Wh^plZH|%k|DyJThkzhsG{8El zlFego?u&Y5=cN~oa*p4?U`0NUXM;jTwUH@rlOe-0*rNc3uSD0cU8B~GUU zA07@v+7u~Yk}r?+Ic05sR>Y@tM#JPc8Zx#7J$Gj`eI?0MbYwpmgZWtmUPyx2f|ebI z*{+T`ov{X_2kp^pTdS=zsnK<7TD!?#KSwUYjQ=7U&0V@a)quH85k7DH=j3novzWpRA`Z?2yO?qAVh&Iih-n09}_-P=}MY1FlQUNccq)qd@rx*A$S%`r5$5Zco= zdL&}%q~J7necHG+9zxU+27$zO3joJiJ_ugG90SJqVF#A+dw$HJhtd4M0y!jvMwI6Z zRJkDs50yzVa&^P9^hj5%gAjYsDhd9tt0?r|% zddJ_N4dMDc_z)>Z<|Q^o@BK3%2FhU&{0`7t+q zM#We2gF{jtC7{0Ohj@(Sm>R$yudMJA%J3R4G%yJ|e9 zbUBtoDbM=+W#jAw8M=qWgfd8+9j2OcuDyjA1f_R4%)tOHM?2476Dnd8APDIk1f#Gx zbQ&8%$pZKAGt7W4TQVUAx%LByc6O|K*Q|GJQ*TbG_?p0J+G-Qf1tI6cZPJh42%?_g zDVXbFpFrpS?Fz_hAApx7{6> z4sKz38ne_AaKqaXcX!9#c#0b5P9qm~C$gLN`$FN2uM?HQaWajEy(iKKi*|o|8S*+g zVXc&MRNQ>XzRh~-9CT{h$PRM#r|00?Ej&FUq+IzBvZa9|xu<4?lrffy3%pf}bY{eU zgiR~nrHUZNvgp4DkVl-+%9ezc|3-w?KIvnqiJjAOOgO7Ug=r*%1wl%4f-zESM8qL^ z$2C5Qlfy$c2E2JjF?%>KUQAf5TZ`rbC>lu8MxH?od+?5~LV>Zfg(R7Sq^d3Ir)NRU z+FS3)8;ux*YJTttJVcLAxPG&Y!)_3Y4n$Hs&iibh*g&abpyHCqmg%w(hw{TE1G2%e zfk=y^{&i~HsYbh-vt8mKfb_wBLq12MAf06P@-J2|TB=2NcNyqm7fN4)!qQ!Qq_I}H z-G@!WKDC|)*@tl}ZO{wO@kAfIi7F9U>5JxRy@dRz9qXRJ;(kRm51xnsQ+j;LQ&gnN zLu~{n>aQb)S`<}_$1y;jI-xrYF=)6?jyr%Kbwz=UbChx7Ow-E`{pChU#u*@{Fl&rY z=*8~~$7l*VDa_t5p9XkyKO!J~$$=hK6XTij_2}4{N}V7tM9-l8xHV`_%t_iB$nq7$ zLqGcQ;jSHmCoP@c2uH3 zESh|-y1<&f8I3z(k6t_2!kvsOW#?yH09l-v+hqcFY&av-{%W||`1Eut(5$ht)p>wT zrY@3V>~|WeVA=NrQ=a^EdKrY`Puhwdb~-sV7Un;}H)Xw)axAn+(`&`S^_YcYg4-4}^(~=3 zi#AFaWcB&vEy8uxvE}~d*;iJ-G9(NP1pJ(fqNvWze!x|-2zw19s)z#t*xR}ECLo}#;?|E4~ zhO@FY+Sg~e+f*rVQ5{DO!CIf?cvG|L(8UiH?8uI38R-LV!CKv_XhZLOfgRH6dI@PY z7>6}t?dZ3>HaHt6Grj$8U-*%@`|P^wjvc&qVt^plfLuE9C+SE(xn)eZ-C}l-q^qxo zXSemv?ap>J@HOZq;R5*3f#XR8aF}cYvEO_PrHIJOI9|5U#9){-&P8noT;b=h?*_Oh z+UFD_D|7~NrugA#>sU1CjvWetYdZ9(xFCm8EdodnJ`ITFJvZ}?6WQCH`T%jR%fpQk z{Xhe^aZR9XyXfuV-x2;pC+&N~gkp+@$3LJy1Oedu{2EX8z^1c-LNU(B`eRZw>h&@n&YEVjTzW76w$DAEO9Wsjo>%CUZ>=}oQ~FPRR% zP;!3hV<+I{dYWBaj-z0k4g7-P@;F^t@feCatAgbMdOHXfgTP>eDbM^j&%lz;lC`b? zMpM1nFA(5#)DxTK$87aE(0$eJndTX=fOFNy{CCr;W!3Yqdyu2}Y=eMj&wSR+E`wyp zn%cOj>UQ_K(MeY?-z#P%GsCM%30JU$(eS@jh!K`;vI}02Y#XCAD z$7ItO#t0z}SrM-P}Mdu_$umbJ6ZbIpOB&Ca%Zu5FVI zy;@zjy?3{>w_f1}Xxp%Y;)$EWy7y~{1ASiZdlXI z%#`xo&-@#%!*K9UApRRLj`E{uKFhKxbKb9!2=bci82$MjcWc=EzhaCy6#3sl^aJK- zz=GQ-8l5{{n89v!r7t-Nt!QkDw_SdXVIbt3)~pJVrswgzOo3|697|rL=1Kr*l#EOG z>c@vi(ad3Hpei(Cn!02*j~8u&>Y!$W^nj47e4qb_(a$@RYZAt7Uv`d#nw@jg8~Vp4 zo`R@H1;*6Gv+C=jCGgfquM)HrZrre^2Ei}T&n|q57cnWYcC-kQ;U8Org;0>+pnJ>E z#S3T|0-gGUVaFM!$HC*M*s(a&Dx>o%HP;@~ZnM~eKp#TnP`S*M$xDCl{-GcYO(@VA z87EsapNBn?Ndbup7P&k>rS`MGbC4l5+OU8F%)GPf&$oG6DtystU0}9Jbtyl)h(Iq@ zQq=quTE+WPB+5k#m_zEb^qf4;1u zdNIJr+{yU-%jCR2e_GLaJGk~s?2tlz_Wq(^+#pYt{$c_3hHD|7}-+GVt!@MlrSW#I!$A1OgBV`@P$x!9N6(Z06 zQhX)GB~1{ctd6YsdK>(MAs7uaZtHx(0!;GN4bu@^4sdw}SK9g^kSUtbbWc(l7JpD+ zF;De`0Eg4<-s-Uwf4Z*`L*x%UjljdL>~m{|3#I;Zu0^m=*MSg)W-9DyE&Guf(Mnqn zB0$O}J2=T3!w5#yZfCAw<+GkO5`UgM8f(XQOgwP_#pwU=w;(#xF} zbA?$gKyiLDTO^2ugV*Wk#PFb?<+q`f=DVc}Y<8(TcS7H3P-Gg3ee>if;)P6oi+S7^ zoge#2BaRJTCi=nZF;O8`aHYOHK<&PX2YuC%i8X#>uK0w>NJFjSrXjsX3CDo*DQ96# znEcEleuU(aL!w?r-rYhV=;^1|9-PtBKNM7$`X~u2+yH?vRG(Lh6IZCM9m>Z;=Z>C> zmz5+R(YXs06v-gF1^*R@?G*3I-fH3;)Lt_+5&~6lyU5`)$E8{Y?_RCn@*XS96$D<& zsMf4pX6;xusnTho$3S6}wK_!SV~BlLYklfw>d5JFTxdTJ!;)s3UOEIyDD^AjySCO-`P?M;jS-jB9A34@KT; z294$Hh=;ARjGG5Ah>SEL;YEItNQc^IC=F*SGpI|vuoLT-X1+18CN`=9mYljfi<9=$`~bv8(hS8eWZ_h4kSou7vv{=Hu}vTX|o!4Zsiqqj$f;^TS!v&XbHeEe05R zdiTW49{oeA592LhAlJzEJLZguy5NlQcA?NHNTBtis?}dBm-Is*N%maR>!9jGQ~H1pOm=c#os_F(M3`pl}XKTqUH2xUjvG ziXxC7z?iq3sU^#eu7b?L(NoO`ueZX)L1Ax(qYDv5f}W8Wf|{0NEVk!x=`ED#je*J@ z`O_Cxv^S)TpjRjQG(ZBk&sx=&f(Ee3>ZOtjU!&~PO!8t|W*DftCW-^qC!>*M_}=AG z2Kp*9;E{|0PHsgm_lAgvOV^mc2r0t*fVnUh;6o5Eh?W6t0E$_C`Gu4-V1Zt;yFu1{P64L6woVDJ7g&KDY~b?n7R{ z1Jz~i^sdVo>;^Rp4KB6Qy)TEqRY%GyWb!DkXzc>axlgg@{l1~Fm|Ku|g}78%qG|m+ z7;GRY8Jf1~o7m)xoN{;Vp5*bO;0c-~E?h5Mk|>!eMRN@g%K155?&0&{pB~v+UsYVv zbk{V8NZMWvO!}CDW5%m=fhT+nG}qD;elGY?XZ;#YlRMnf)Uy&wB1{x`nzAnJ3hmCe zaYxv@esXlU1TuE-iXsA!adP4v)|7`GKb0v(?N|eVeZb*Oo`MnD5y{tC9{;2hZcazB z-a9%Z7kJpf$s4EK7Z(kM>NBI62!*p8da#WMRft=1Yh;kxNVbeEj5n9>TlX@8XG#6& z@lwAZ-~j@k`+Oxm^6+gOva?Tdnl6V%8kVM_kUMa{m5lE@Y`573>QPOk?TsZ2+)Awm zJU0L=U`3VAMc)=z{m81Y@S~0X?$ARKTvcvJ&6*-i1vGu4!vnQy!D2%~1C(XqqORbO zG60Xsp}0KxCZCXfbWfA9?C^8B6CJen3~17-hzO*sp4Dz=HgxNg)?tf`&73Rz05K8u zDx3vp@mVoK5jLD0E}}4A$#2&vOcH>`*1IoLyN<{R2^j6iKP9P9qewc9HeP$|XtohN z`q?co@R6ueEN{rg932iULqkF25L%@mZWQx1p%TFs+ldZKZUlM8%Z9$nNrGmGJRuhf zwS?;;zesJw50_=1Ir)cgpB7XseO5@n!n=Ti)szN}o|4}}4bDXXE@m*j=7Y{C zS=JHP-^DwYv4SIAydbTuNr#U0B7DbF2^svE2e2Kc2v@P+N}54!V%7y5JT|5|Z=rzV zBvK@A8Ox+D+fP0sZR%LUkWXF_Hu?fFTK_~ zfp@f$2*FPCTB{5ktLIMc#;5DfymU>&?mp0TZROQ#) z>U|%3pO7cjc#IM!^}1I+GYF)x)0x!?o8B1oqCdfdL?67+JH=MaK$W(_d&g1KYb1&l z-Ssv3_kKNG_EpCa6P@S^eGzR?N^v1I1umhlaYk=YWe}x3XLQA zVaigbSU!%i3O5)chz$`j#G@#OHJ*cTMi31y|0Ti!q0ii|wL!tBMy0w%+C*#C{wMi1 z6ooFPB_}q8j^+}Wd{cOywt-@ZOuW>$|agWvvA z&uPv{{6sk*H7Y#*rTi0(u3z>0i-S|lFmc(_jvCI)gFEv@DfZqkGFc=q;9%p*D-|#a zpHoriE~H2O+CtD=#ng_QG?_jJ3}gO3#{A{Z)b0xWxc^%D@BC?}aSkL7v#dCbLy#Ky zCspg%aB3n>p2YjeQ(nfhfM;?3TkP_6=LM$90#5d8RO4_mi$jp|F|^s{Fk%a^6RMk| zi~X$@bTxN?9JTX}{s!wUDFDDyi1ilpb^eVLonFpkgfm88lDeoY&XAxQpq#zV%0GDeyGa}|)=C<^#Ecq7Cn06piwd)j$ZmB>+!#Sf-~dS&js{e@6yfxkONU zTM=Vs!S_siU#^0U2dX-CwQ4vE>LS@P%uj!f-)-*ry!6esc7dl*_pu|$Qe&}ws=N+^ z6rJmGHKBCv&M!1?;s?DOT-tB=#ye@UbhI;I(QFHh=UST(io&&vHX)^g9XpzJf8_aH z!4vnQ^zHdy3iS2E$JbeVR{BLn8zW1np;@rwuB>9{^prYoN>`tJ{xTMGAy#>0bHZ<| z+CkWU<+8^a0i>Q(B$@Q6Z<>+B}7P$%hD2^IH=7sl&c9vua+abAk-7?;duG| zn(W*3jZ(Rm*TpfV{RA44sV}Afsv)NaK_`r+Xk}MjIP8J<9HVA30-#A`ZaT+yk+#m} zQZfSX@Ne7fn4!C{1*oY0>Ji{H)6cIfoa}0T-!DU|(x~iodb&M*PaaPuQ&ObPusgVV zIiVxsl~(A$3GEjG5CWwQ;6rL|yQC6gS+bM`gD9%MtzV`mKbELcn5fX}zKb1QU$!s* ztDq`|o1ZxGN?v=!-~v^7>|<94Q<(xDawx;{s9i>Q&{(7-Uh$1KKck>i=sSoPBsPq?8DPv5Tu0W(mXN1jm^j^3q^+mA1g*5Q^dr7*caE@x&>N(o^x6Ts$! z@yk>EQoJyQj4u8pjtxTEw1X5WFm$C3B*oRQ04NAZ755JTjxDE#@W2>fz007IF?FF1*eK#1 zk(k53HOX+>ADtL7=_52{Ti&GC))Kp40XFTG`JajCL;|OzMiZQbRHPy+_xBX(P2+2+ zW(d6~Ewz%=qzOQuiAv|IlH|`PJhin^LI-9ZkdY_4q=9J^_wM66mRhg)MQL@Gm_c+c ze>s=$)#uUcXDfZ;+e;9bEPV`@^4${L;PN$lPtBEqBi*)uR#+$40RVQDNS@m*eS5_+ z`m&|zPxV`?TyTKNjub|LAN2@JtQX1YVg!$CwS(QCk+Ez#aA&_&X$ae#(vsi~pFtT9 zeNf0-Z6n+aV=IuIU_Lt1%IWlZCLwRpvBAV{Ke^o*;w;3Oz>lN`wwXc;4O`=bvuKP7 zJQ_nCOPAK*KX!@5Va+DxiiQh|pAnLKu?qh{)}5E%BFwh8T8}KZ_V@C{^lJ)jpj}1m zFt{l9JlrU$>mj~Q5kQ10t%APvmuzsVIsDaLfVpP@ul5z$mw6d$>RXF10Z$$InxYAF zVl*f(hlenmkyYq{ls+07)I1lyZP9u0p+2tW7!D-Yk2h%3LvjQfq|bwo#o0?rn?)%06*iis@lt ztjeWmxReVqDV`S*KfRD&K7;uu%QwMVA4)5wJdYOV#gPnC?MkFRVzO(g+$52H014Kn zdv{Gv;zsaMm5)PfP60;h5E_WtCj(Bx-?Uo*CBch+OV3U4NPR*er%A1j!ka?tI+ky_ zQ-&}POE~BxR|b={6CISt=cy0(++)Ox7&_$xu2IQ%Hqi~0N~)beh>wR|U3F$BO)v)j zCM$F=#Xjg6LgyTHif+Yov(gBHWCM(KM`F-k%tHcvL`0)>WUD!f(0|5zwBoQ82JU<_ zoP-c*-A-&A1CBCu{q&XOZzmQWTkn%#ek5C~GxeDBvUSu2BX=Yu6FUvV2?Qsm;F5RQ z1(eo7U;+W{XSt7ic22cAv`fm~?@Addgrn+XR+(-6^8Lgo+8a$3#|#~qdw645eacvR zX+%T>lojfX9We};%vLcR>74sTGGxw;BpT|(ATfs zEtw&52CcF*&vP^{%V(^NkE~5@kf@MOR{ez1qE>vRUTu@9P>wUA&=HU%#40{&z8MD& z7?x3o=gfZ>Kf2J1&a3^gv{BS#*5KR||CIV`3s0*0B_(c)*gkh$YYM1ic;V-0^;r9U zI-DF%maIUVb?3?I)M~qTIi4=3pniT_ZctUB&($jXx?WyiW=vjsJjF((sn%w9HhLa> zn{=C;e5b5X^_6G$*_Sw4oIHj!@o5#X<@Vgar7WS(_RSRN^xo90Jef_q)ox%`T=yF! zdjv6Y_!T(bE~hNG^!!tSnWXTBR_d%j)rj{rQ(O z>V`dJ>+tx1no2|vxAnB%!StLe4v`im7t%n8d3@CZHF&9yx(`MIHe|w)i ztEi7oPL@ca(=O1<>Q!rN@U%G|T%vxxoh?Ey;y=HMdH(Mmv@x2=nS`k zPf%VmU=5?UXX_tAV85MKi%1|ako@;WS2~mG>b-hesE1sUvi|a$+h{#ndlrkrez8St zes;4XdufjbJvfr$w3fQi1b$Dca$NK;lrk-QGW)KB+gX`MX(UK+uKA;6Nu3R=%XcgW z@ZI0#)4C{^$+&EIz*q_!5NN+qqY9hI>9zI_a8Bj59V%U>wLMY?@VP*asdo!6k3GVK<;mj_-dK18Y<3QEo3iC zu~R|OPVz!of&x0KMY6@C%&Tgl+hfLw<(tn!He)|rsZ+VvQAu)Tp;58RWWD|;WdrV{ zZC!86<-D;q^dnN>%hYNl(?)ZUTl3^eI#jE>aWPeu4hub zAuKUp@L}Fq9` zzK2;Z6v$^7PX{=&5s1l02~qbrRl8va4CBO2zNAqDzl>;(HnGL`LXj!*&t!2oX#x7I3EFh@X&QR<>S(V#yejG2|AA0enm0~j&^ zb2+iK0#f$k8-_1iLZYB{dZe85t6Lgfc327{8mRGL+om$6chjahUGvq>YmRl#%4Rc3 z6kNQGjmGr4e9u%K`^y}}SDWQ_;|J#q>tPS>e$1LD@h?t32ox1|+Lw#mi*_cM4OdfA z*PA%G!ixM1ZL1bm)=mcpJ*&KA1iXZ&;+0XwKGRa9u_1q=RF(){IuL=R>o>#QP&`RW z7o@J6IJXXJf~$xg{pbX$T6Y_&NbS!g6{JI6b+4ejtG?s*aFigK2yxby?H$NgNYSG< zeNyDp)9+>=ImX!A+eTGPx4U;|=PlcTQ;xaHl3;ncpt4W5^llpYmGF@}1tbai6;#;# z*$(bHgm1k(TK6R&;66GYBtYNrgWo$U%7*C;+!ao%JLkht{V|`>CX?*-bhO9L#krGr z_9RLdTCV}t?v8bhZ;zYPF6vLH?~sQ8>@kpeT7G*(+%Zvp-gw@WqWR%k;?2@q(;L>y z7F=*k870A(iC&rVIZ+gsABEXgp--vG3o?uIWYD%H|An`1R@yzXaudRgbzlO;enRs4 z{w_#JOW9)u&55}h+F|%+AQPk_(hWP~D;>g7WWzg5n=LgV1wgaWCWHg|&@()FXdRZ$ z6*$o4dQ#l6Iwl+{iAqwSP(!`BaYC=p%k_qyvTRP@_ZwB(`oW~{&HY*1tSKWX1hI_sE3{*<4#^l%=da-WE?+Q0RYgzrLuS-+9-6rS{tc<6RCY0duZMB&k`x36$hYyCj>048-T+IQ-SIp!S3-ybo7+# z6S~_G2Pw2|M%sZ&P0wao{k99VXLL|px)i!tn`;Crlv1k7#AJR*J5h~sQ}Of4Lz@FEFFIpRCP!2w$MBbqZ8`wsHMHU|}uu`G3K96OzVJ`WK8)%w&tNoogS=^-ka&P+VxSSO)qlRy{k8_E|pMAMW3 zhh-akdYB1f$D>HUqeRJ2Zs${GKr=#JPXZWTnb7!@;)5s9rjy5GHLnYrxp$|FEltN2 zrlHqC=h{stz?OvFe#CIvHg2g}io}9a9SUJ9Dm!6F7SVeQzf!<3vW_Ei!>%*LC<93d zxx79REClZz=>is~qPmfLB=QPNd5mBH^C?;iL^MbO$e1|FQ1NE!S{Tx|?hq4X!gG&I znbH)J3y~&UI_A*4ljsL+!rnSUpcFbO3&#oIoupHQw#aw%D*;ktj3_ndlWCtQkb#8i zR+St-iXggI7-56nhJp%0AqpA9>XDZg^Pqs50`>pndWLs+?Vfa4W8FGVaAMu(bjOjS zNV$#3!t~KKP_gk$IJfCZe6wdsrl+lX0>Y9v}nx7Uw|6{xixG*GxkfjF1D$tdea&Tbgh>74cm8D=e(TH zJWfFKkasROJ^PF7lt<=mDE#;2=?AT?ILSaK#*pQOGG~CupT2BNrpUc)dZurYcP6Q| zUmG+P0C+~ERs3yBYVooGwbtqB|k$xpFXbA^HSb; zO0c9dY#2HtvimvMN1WA~r^5B$FgocpZTcJo$Qs|E5L(-ggE`owq5JGnGo1H%INA)7 z@}{~e#H^|jGp|c9v>5WeB*qxF5Om84PV} z%eE-_d^vCs*8#fXt4}0lsbKW@ksvICx%SNAef$I&A?VC1H~zn9Zsxzua@YTm8NLC} zY_m60`a5Lo0{t)FI30H(q^-(&8O|G6TgmBPfm$a<)-6#jG^xT?T^nm-ZQ1Mk85gu4 z{KF9RRS2nT?EU2opVS)diPb-*q=D#-O1PT2> z62pcDTP?dHF+~ct6`lPEjk!D&k(@lh;b@#0CTu3&$VrngQU?~myYuD40ZZvBzLGw* zv5zQFF~Mjn)5K(AlhD?e*(@gbPDXbqe9%y7`Xn~bf*EZHFfwnSNq)Siw_8@_One)* zp*Hx8wlKWFGkPo0I87_KklvP|jqO@7xmv$*yWSdjZ)8JndQb9o;XcJ`JHC#uPgF1C zp(2~iz@@2SYdN}J|FLrO4HEToc)@{czZ{(Xyq0V zJJ@+aNmmOUrI?(+q*oyvFe6z_2(gNR4Y)mEqHoiQH^@ue=d2O@cx!uM$Bx=R#Xw=BuJoz+e8!Jmqf?kJdsNSRY% zcE<2_BQ}G!$)Ag2r0?+>r*S2p0iS^H7=>#fB$Os1w(Bd%Aw;bzaH=B0RSV-9Xp6X7 z(hGpykc*_@DwRoLi9|OzXDr7wd#HPV3A-RJMy={Wu item.length > 0); return normalized.length > 0 ? normalized : undefined; } +function normalizeAnalysisDate(value) { + if (typeof value !== "string") { + return undefined; + } + const trimmed = value.trim(); + const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!match) { + return undefined; + } + const year = Number(match[1]); + const month = Number(match[2]); + const day = Number(match[3]); + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + return undefined; + } + const candidate = new Date(Date.UTC(year, month - 1, day)); + if (candidate.getUTCFullYear() !== year || + candidate.getUTCMonth() + 1 !== month || + candidate.getUTCDate() !== day) { + return undefined; + } + return `${match[1]}-${match[2]}-${match[3]}`; +} function buildEvalPayloadFromBody(body) { + const analysisDate = normalizeAnalysisDate(body.analysis_date) ?? + normalizeAnalysisDate(body.analysisDate); return { normalizeConfig: (body.normalizeConfig ?? {}), caseIds: normalizeCaseIds(body.caseIds), @@ -103,7 +128,8 @@ function buildEvalPayloadFromBody(body) { ? body.compare_with_report_file : typeof body.comparisonBaselineReportFile === "string" ? body.comparisonBaselineReportFile - : undefined + : undefined, + analysisDate }; } function resolveReadablePath(inputPath) { @@ -245,6 +271,7 @@ function snapshotJob(job) { eval_target: job.eval_target, run_id: job.run_id, case_set_file: job.case_set_file, + analysis_date: job.analysis_date, total_cases: job.total_cases, completed_cases: job.completed_cases, error: job.error, @@ -258,7 +285,8 @@ function snapshotJob(job) { : toRecord(job.report.metrics) && typeof toRecord(job.report.metrics)?.score_index === "number" ? Number(toRecord(job.report.metrics)?.score_index) : null, - cases_total: typeof job.report.cases_total === "number" ? Number(job.report.cases_total) : null + cases_total: typeof job.report.cases_total === "number" ? Number(job.report.cases_total) : null, + analysis_date: toStringSafe(job.report.analysis_date) ?? job.analysis_date } : null }; @@ -310,6 +338,7 @@ function buildEvalRouter(services) { eval_target: payload.evalTarget, run_id: runId, case_set_file: runtimeCaseSetFile, + analysis_date: payload.analysisDate ?? null, total_cases: caseSeeds.length, completed_cases: 0, cases: caseSeeds.map((item) => ({ diff --git a/llm_normalizer/backend/dist/services/addressFilterExtractor.js b/llm_normalizer/backend/dist/services/addressFilterExtractor.js index 627a1b4..8fe0329 100644 --- a/llm_normalizer/backend/dist/services/addressFilterExtractor.js +++ b/llm_normalizer/backend/dist/services/addressFilterExtractor.js @@ -427,8 +427,20 @@ function extractLooseByAnchorValue(text) { } const lowered = token.toLowerCase(); const stopWords = new Set([ + "какой", + "какая", + "какие", + "каких", + "каким", + "какими", + "каком", + "кто", + "что", + "мы", + "видим", "контрагенту", "контрагента", + "контрагентам", "контре", "компании", "компанию", @@ -436,10 +448,14 @@ function extractLooseByAnchorValue(text) { "организацию", "поставщику", "поставщика", + "поставщикам", "клиенту", "клиента", + "клиентам", "покупателю", "покупателя", + "покупателям", + "заказчикам", "партнеру", "партнера", "договору", @@ -552,6 +568,9 @@ function isLikelyCounterpartyToken(rawToken) { "какая", "какое", "каких", + "каким", + "какими", + "каком", "какому", "какую", "кто", @@ -566,6 +585,8 @@ function isLikelyCounterpartyToken(rawToken) { "чья", "чей", "чью", + "мы", + "видим", "самый", "самая", "самое", @@ -620,10 +641,23 @@ function isLikelyCounterpartyToken(rawToken) { "контрагент", "контрагенту", "контрагента", + "контрагентам", "компания", "компании", "организация", "организации", + "поставщикам", + "клиентам", + "покупателям", + "заказчикам", + "аванс", + "авансы", + "проблемный", + "проблемные", + "проблемным", + "закрытия", + "закрыть", + "закрыты", "год", "года", "г", @@ -727,7 +761,7 @@ function isLowQualityCounterpartyAnchorValue(rawValue) { if (tokens.length === 0) { return true; } - const questionCue = /(?:кто|что|какой|какая|какие|какого|сколько|где|когда|почему|зачем|which|who|what|how\s+many)/iu.test(value) || + const questionCue = /(?:кто|что|какой|какая|какие|какого|каких|каким|какими|каком|сколько|где|когда|почему|зачем|which|who|what|how\s+many)/iu.test(value) || /[?]/u.test(String(rawValue ?? "")); const rankingCue = /(?:больше|меньше|сам(?:ый|ая|ое|ые)|крупн|жирн|максим|миним)/iu.test(value); const paymentCue = /(?:плат(?:ит|ят|еж|ёж|ежн|ежей|ежа)|денег|деньг|money|payment)/iu.test(value); diff --git a/llm_normalizer/backend/dist/services/addressIntentResolver.js b/llm_normalizer/backend/dist/services/addressIntentResolver.js index 0efe132..fd55955 100644 --- a/llm_normalizer/backend/dist/services/addressIntentResolver.js +++ b/llm_normalizer/backend/dist/services/addressIntentResolver.js @@ -604,6 +604,10 @@ function hasLifecycleSegmentationSignal(text) { return /(?:вперв|нов(?:ые|ых|ые\s+контрагент|ые\s+клиент|ые\s+заказчик)|исчез|ушед|ушл|пропал|отвал|только\s+один\s+раз|ровно\s+один\s+раз|однораз|дольше\s+всех|долгожив|самые\s+старые|старые\s+по\s+сотрудничеству|регуляр|эпизодич|разов(?:ые|ой|ые\s+поставщик)|давно\s+не\s+использ|неиспольз|потом\s+перестал)/iu.test(text); } function hasCounterpartyActivityLifecycleSignal(text) { + const hasPaymentRiskLexeme = /(?:не\s+плат(?:ит|ят|ил|или)|без\s+оплат|оплат(?:ы|а)?\s+нет|нет\s+оплат|задерж(?:ива|к)|просроч|долг|задолж)/iu.test(text); + if (hasPaymentRiskLexeme) { + return false; + } if ((hasDocumentSignal(text) || hasBankOperationSignal(text)) && !hasLifecycleSegmentationSignal(text)) { return false; } @@ -678,6 +682,7 @@ function hasCustomerRevenueAndPaymentsSignal(text) { /(?:топ|top|сам(?:ый|ая|ое|ые)|крупн|мален|жирн|мелк|больше\s+всего|чаще\s+всего|наибольш|максимальн|минимальн)/iu.test(text); const asksRevenueTotal = /(?:сколько|скока|скок).*(?:денег|выручк|доход|заработ|оборот)/iu.test(text); const asksOverallTurnover = /(?:общ(?:ий|ие|ая)\s+оборот|общ(?:ая|ий)\s+выручк|total\s+turnover|turnover\s+total)/iu.test(text); + const asksMajorShare = /(?:основн(?:ую|ая|ые|ой)\s+част|больш(?:ую|ая|ие)\s+част|львин(?:ая|ую)\s+дол[яю]|ключев(?:ую|ая)\s+част)/iu.test(text); const asksValue = /(?:доходн|выручк|приход|поступлен|входящ|зачислен|оплат|плат(?:еж|ёж|ежн|ежей|ежа|ит|ят)|деньг|денег|заработ|оборот|чек|сделк|бюджет|занес|занёс|принес|принёс|revenue|inflow|deal|turnover)/iu.test(text); const asksRankOrTop = /(?:топ|top|сам(?:ый|ая|ое|ые)|крупн|мален|жирн|мелк|больше\s+всего|чаще\s+всего|наибольш|максимальн)/iu.test(text); const asksCountOnly = /(?:сколько|скока|скок)\s+/iu.test(text) && !asksValue; @@ -702,6 +707,9 @@ function hasCustomerRevenueAndPaymentsSignal(text) { if (asksCounterpartySource && asksValue) { return true; } + if (!hasFuzzySupplierLexeme && (asksCustomerGroup || hasCounterpartyLexeme) && asksMajorShare && asksValue) { + return true; + } if (!hasFuzzySupplierLexeme && asksIncomingFlow && asksRankOrTop) { return true; } @@ -801,6 +809,58 @@ function hasOpenContractsListSignal(text) { } return true; } +function hasSupplierTailRiskSignal(text) { + const hasSupplier = /(?:поставщик|supplier|vendor)/iu.test(text); + const hasTail = /(?:хвост|висят|незакрыт|задолж|долг|просроч)/iu.test(text); + const hasRisk = /(?:систематич|регулярн|проблем|тревог|не\s+разов|больше\s+похож)/iu.test(text); + const hasPeriodCue = /(?:на\s+конец\s+(?:месяц|период)|конец\s+месяц|пару\s+месяц|несколько\s+месяц)/iu.test(text); + return hasSupplier && hasTail && (hasRisk || hasPeriodCue); +} +function hasReceivablesLatencyRiskSignal(text) { + const hasBuyer = /(?:покупател|клиент|заказчик|customer|buyer)/iu.test(text); + const hasCounterparty = /(?:контрагент|counterparty|partner)/iu.test(text); + const hasPayment = /(?:оплат|платеж|платёж|payment)/iu.test(text); + const hasShipment = /(?:отправк|отгруз|реализ|shipment|delivery)/iu.test(text); + const hasDelay = /(?:длинн|долг|просроч|задерж|висят|тревог|too\s+long|late)/iu.test(text); + const hasNonPayment = /(?:не\s+плат(?:ит|ят|ил|или)|без\s+оплат|оплат(?:ы|а)?\s+нет|нет\s+оплат|неоплач)/iu.test(text); + const hasPeriodOrRiskCue = /(?:за\s+текущ|на\s+конец|тревог|просроч|задерж|долг|длинн)/iu.test(text); + const hasBetweenShipmentAndPayment = /между[\s\S]{0,80}(?:отправк|отгруз|реализ)[\s\S]{0,80}(?:оплат|платеж|платёж|payment)/iu.test(text); + if (hasBuyer && hasPayment && ((hasShipment && hasDelay) || hasBetweenShipmentAndPayment)) { + return true; + } + return (hasBuyer || hasCounterparty) && hasNonPayment && hasPeriodOrRiskCue; +} +function hasSettlementGapSignal(text) { + const hasPayment = /(?:платеж|платёж|оплат|списани|поступлен|payment)/iu.test(text); + const hasDocument = /(?:док(?:и|умент|ументы|ументов)|docs?|documents?)/iu.test(text); + const hasAdvance = /(?:аванс|предоплат)/iu.test(text); + const hasNoDocumentForClosing = /(?:нет|без)\s+(?:док(?:и|умент|ументы|ументов)|закрывающ)/iu.test(text) && + /(?:закрыти|взаиморасч|акт)/iu.test(text); + const hasNoDocumentForClosingReversed = /(?:док(?:и|умент|ументы|ументов)|закрывающ)[\s\S]{0,48}(?:нет|без)/iu.test(text) && + /(?:закрыти|взаиморасч|акт)/iu.test(text); + const hasNoPayments = /(?:нет|без)\s+(?:оплат|платеж|платёж|payment)/iu.test(text) || + /(?:оплат|платеж|платёж|payment)\s+нет/iu.test(text); + const hasDocsWithoutPayments = hasDocument && hasNoPayments; + const hasPaymentsWithoutClosingDocs = hasPayment && (hasNoDocumentForClosing || hasNoDocumentForClosingReversed); + const hasUnclosedAdvanceGap = hasAdvance && + (/(?:не\s+закрыт|незакрыт|долго\s+не\s+закрыт|давно\s+не\s+закрыт)/iu.test(text) || + hasNoDocumentForClosing || + hasNoDocumentForClosingReversed); + return hasPaymentsWithoutClosingDocs || hasDocsWithoutPayments || hasUnclosedAdvanceGap; +} +function hasReconciliationMismatchSignal(text) { + const hasCounterparty = /(?:контрагент|поставщик|клиент|покупател|customer|supplier|counterparty)/iu.test(text); + const hasReconciliationLexeme = /(?:акт(?:а|ом|ах)?\s+свер(?:к|ок)|свер(?:к|ок))/iu.test(text); + const hasMismatchLexeme = /(?:не\s+совпад|несовпад|расхожд|расход|не\s+сход|несход|разъех|разниц|не\s+бь[её]т)/iu.test(text); + const hasBalanceLexeme = /(?:сальд|остат|баланс|saldo|balance)/iu.test(text); + const hasLookupVerb = /(?:покажи|выведи|найд[иь]|show|list)/iu.test(text); + const hasInterrogativeLookup = /(?:по\s+каким|у\s+кого|какие|какой|кто|где)/iu.test(text); + return (hasCounterparty && + hasReconciliationLexeme && + hasMismatchLexeme && + hasBalanceLexeme && + (hasLookupVerb || hasInterrogativeLookup)); +} function isLikelyCounterpartyToken(rawToken) { const token = String(rawToken ?? "").trim().toLowerCase(); if (!token || token.length < 2) { @@ -1115,6 +1175,34 @@ function resolveAddressIntent(userMessage) { reasons: ["payables_signal_detected"] }; } + if (hasSettlementGapSignal(text)) { + return { + intent: "list_open_contracts", + confidence: "medium", + reasons: ["settlement_gap_signal_detected"] + }; + } + if (hasReconciliationMismatchSignal(text)) { + return { + intent: "list_open_contracts", + confidence: "medium", + reasons: ["reconciliation_mismatch_signal_detected"] + }; + } + if (hasReceivablesLatencyRiskSignal(text)) { + return { + intent: "list_receivables_counterparties", + confidence: "medium", + reasons: ["receivables_payment_lag_signal_detected"] + }; + } + if (hasSupplierTailRiskSignal(text)) { + return { + intent: "list_payables_counterparties", + confidence: "medium", + reasons: ["supplier_tail_risk_signal_detected"] + }; + } if (hasDocumentsFormingBalanceSignal(text) && hasDocumentsFormingBalanceAccountAnchor(text)) { return { intent: "documents_forming_balance", @@ -1137,7 +1225,7 @@ function resolveAddressIntent(userMessage) { }; } if (hasAny(text, OPEN_ITEMS_HINTS) && - (text.includes("контраг") || text.includes("договор") || text.includes("контракт") || text.includes("counterparty") || text.includes("contract"))) { + /(?:контраг|договор|контракт|counterparty|contract|покупател|клиент|заказчик|customer|client|buyer|supplier|поставщик)/iu.test(text)) { return { intent: "open_items_by_counterparty_or_contract", confidence: "medium", diff --git a/llm_normalizer/backend/dist/services/addressQueryService.js b/llm_normalizer/backend/dist/services/addressQueryService.js index 8a303cb..18a7426 100644 --- a/llm_normalizer/backend/dist/services/addressQueryService.js +++ b/llm_normalizer/backend/dist/services/addressQueryService.js @@ -86,6 +86,33 @@ function parseFiniteNumber(value) { } return null; } +function normalizeAnalysisDateHint(value) { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + if (!trimmed) { + return null; + } + const strictDate = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); + const isoPrefix = strictDate ?? trimmed.match(/^(\d{4})-(\d{2})-(\d{2})T/i); + if (!isoPrefix) { + return null; + } + const year = Number(isoPrefix[1]); + const month = Number(isoPrefix[2]); + const day = Number(isoPrefix[3]); + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + return null; + } + const candidate = new Date(Date.UTC(year, month - 1, day)); + if (candidate.getUTCFullYear() !== year || + candidate.getUTCMonth() + 1 !== month || + candidate.getUTCDate() !== day) { + return null; + } + return `${isoPrefix[1]}-${isoPrefix[2]}-${isoPrefix[3]}`; +} function valueAsString(value) { if (value === null || value === undefined) { return ""; @@ -665,6 +692,50 @@ function runtimeReadinessForLimitedCategory(category) { } return "UNKNOWN"; } +function normalizeLimitedReason(reason) { + let normalized = String(reason ?? "").trim(); + if (!normalized) { + return "не хватает подтвержденных данных для уверенного вывода"; + } + const replacements = [ + [/address_query\s*v?1/giu, "текущий адресный режим"], + [/address\s*v1/giu, "текущий адресный режим"], + [/intent-specific\s+recipe/giu, "встроенный фильтр сценария"], + [/live\s+recipe/giu, "текущий сценарий выборки"], + [/materialized\s+live-строках/giu, "доступном срезе данных"], + [/live-выборке/giu, "выборке данных"], + [/live-данных/giu, "данных"], + [/deep-analysis/giu, "режим расширенной проверки"], + [/\blookup\b/giu, "поиск"], + [/\bintent\b/giu, "сценария"], + [/\brecipe\b/giu, "шаблон выборки"], + [/\byakor\b/giu, "ориентир"], + [/\banchor\b/giu, "ориентир"], + [/\s+/gu, " "] + ]; + for (const [pattern, value] of replacements) { + normalized = normalized.replace(pattern, value); + } + return normalized.trim(); +} +function normalizeLimitedNextStep(nextStep) { + let normalized = String(nextStep ?? "").trim(); + if (!normalized) { + return ""; + } + const replacements = [ + [/address_query\s*v?1/giu, "текущий адресный режим"], + [/deep-analysis/giu, "режим расширенной проверки"], + [/\bP0 intent\b/giu, "поддерживаемый сценарий"], + [/\bintent\b/giu, "сценарий"], + [/\blookup\b/giu, "поиск"], + [/\s+/gu, " "] + ]; + for (const [pattern, value] of replacements) { + normalized = normalized.replace(pattern, value); + } + return normalized.trim(); +} function rowHasNonEmptyField(row, keys) { return keys.some((key) => String(row[key] ?? "").trim().length > 0); } @@ -766,20 +837,27 @@ function toLegacyMcpStatus(status) { } function composeLimitedReply(category, reason, nextStep) { const heading = category === "empty_match" - ? "В live-данных по текущему фильтру записи не найдены." + ? "По текущим условиям в доступном срезе данных совпадений не нашлось." : category === "missing_anchor" - ? "Для точного адресного поиска не хватает обязательного якоря." + ? "Чтобы ответить надежно, нужен более точный ориентир в запросе." : category === "recipe_visibility_gap" - ? "Текущий live recipe не дает нужную видимость данных для этого сценария." + ? "Запрос понятен, но текущий режим не дает нужной детализации." : category === "unsupported" - ? "Этот запрос не подходит под address_query V1." - : "Не удалось выполнить адресный live-запрос в V1."; + ? "Сейчас этот тип вопроса вне поддерживаемого контура адресного режима." + : "Не удалось завершить проверку в адресном режиме."; + const reasonLine = category === "unsupported" + ? "Коротко: этот сценарий пока не поддержан в текущем адресном контуре." + : category === "missing_anchor" + ? "Коротко: в запросе не хватает конкретного ориентира (контрагент, договор или период)." + : category === "recipe_visibility_gap" + ? "Коротко: для уверенного ответа нужен более специализированный сценарий выборки." + : `Коротко: ${normalizeLimitedReason(reason)}.`; const lines = [ heading, - `Причина: ${reason}.` + reasonLine ]; if (nextStep) { - lines.push(`Что нужно уточнить: ${nextStep}.`); + lines.push(`Что можно сделать дальше: ${normalizeLimitedNextStep(nextStep)}.`); } return lines.join("\n"); } @@ -842,7 +920,22 @@ class AddressQueryService { if (!decompose) { return null; } - const { mode, shape, intent, filters, baseReasons } = decompose; + const { mode, shape, intent, filters } = decompose; + const baseReasons = [...decompose.baseReasons]; + const analysisDate = normalizeAnalysisDateHint(options.analysisDateHint); + if (analysisDate) { + const hasTemporalFilter = Boolean((typeof filters.extracted_filters.period_from === "string" && filters.extracted_filters.period_from.trim().length > 0) || + (typeof filters.extracted_filters.period_to === "string" && filters.extracted_filters.period_to.trim().length > 0) || + (typeof filters.extracted_filters.as_of_date === "string" && filters.extracted_filters.as_of_date.trim().length > 0)); + if (!hasTemporalFilter) { + filters.extracted_filters = { + ...filters.extracted_filters, + as_of_date: analysisDate + }; + filters.warnings = [...new Set([...(filters.warnings ?? []), "as_of_date_from_analysis_context"])]; + baseReasons.push("as_of_date_from_analysis_context"); + } + } const composeOptionsFromFilters = (filterSet) => ({ userMessage, periodFrom: typeof filterSet.period_from === "string" ? filterSet.period_from : undefined, @@ -863,8 +956,8 @@ class AddressQueryService { rowsFetched: 0, rowsMatched: 0, category: "unsupported", - reasonText: "intent пока не поддержан в address V1", - nextStep: "переформулируйте вопрос как адресный lookup по счету/контрагенту/договору", + reasonText: "сценарий пока вне поддерживаемого контура текущего адресного режима", + nextStep: "могу проверить близкие сценарии: документы/платежи по контрагенту, договоры или остаток по счету", limitations: ["intent_not_supported_in_v1"], reasons: baseReasons }); @@ -903,8 +996,8 @@ class AddressQueryService { rowsFetched: 0, rowsMatched: 0, category: "recipe_visibility_gap", - reasonText: "для intent пока нет recipe в address V1", - nextStep: "выберите поддерживаемый P0 intent или переключите запрос в deep-analysis", + reasonText: "для этого сценария пока нет готового шаблона выборки в текущем режиме", + nextStep: "можно выбрать близкий поддерживаемый сценарий или переключить запрос в режим расширенной проверки", limitations: ["recipe_not_available"], reasons: [...baseReasons, ...recipeSelection.selection_reason] }); diff --git a/llm_normalizer/backend/dist/services/address_runtime/composeStage.js b/llm_normalizer/backend/dist/services/address_runtime/composeStage.js index b3b8f9e..03c4f8f 100644 --- a/llm_normalizer/backend/dist/services/address_runtime/composeStage.js +++ b/llm_normalizer/backend/dist/services/address_runtime/composeStage.js @@ -1172,7 +1172,7 @@ function composeFactualReply(intent, rows, options = {}) { if (intent === "list_open_contracts") { const contracts = contractCandidatesFromRows(rows); const lines = [ - "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).", + "Проверил потенциальные разрывы во взаиморасчетах (платежи без закрытия и документы без оплат).", `Строк движения: ${rows.length}.`, `Договорных кандидатов: ${contracts.length}.` ]; @@ -1188,6 +1188,34 @@ function composeFactualReply(intent, rows, options = {}) { text: lines.join("\n") }; } + if (intent === "list_payables_counterparties") { + const lines = [ + "Проверил поставщиков с признаками незакрытых хвостов по взаиморасчетам (контур 60/76).", + `Строк в выборке: ${rows.length}.`, + ...(rows.length > 0 + ? ["Ниже примеры строк для ручной проверки."] + : ["Явных признаков системной задолженности по доступному срезу не найдено."]), + ...formatTopRows(rows, 6) + ]; + return { + responseType: "FACTUAL_LIST", + text: lines.join("\n") + }; + } + if (intent === "list_receivables_counterparties") { + const lines = [ + "Проверил покупателей с признаками затянутой оплаты (контур 62/76).", + `Строк в выборке: ${rows.length}.`, + ...(rows.length > 0 + ? ["Ниже примеры строк, которые стоит проверить в первую очередь."] + : ["Явных признаков затяжной дебиторки по доступному срезу не найдено."]), + ...formatTopRows(rows, 6) + ]; + return { + responseType: "FACTUAL_LIST", + text: lines.join("\n") + }; + } if (intent === "open_items_by_counterparty_or_contract") { const lines = [ "Собраны открытые позиции по указанному фильтру (контрагент/договор).", @@ -1279,12 +1307,7 @@ function composeFactualReply(intent, rows, options = {}) { text: lines.join("\n") }; } - const title = intent === "list_payables_counterparties" - ? "Срез обязательств (payables) собран по движениям с account scope 60/76." - : intent === "list_receivables_counterparties" - ? "Срез требований (receivables) собран по движениям с account scope 62/76." - : "Срез адресного запроса собран."; - const lines = [title, `Строк отобрано: ${rows.length}.`, ...formatTopRows(rows, 6)]; + const lines = ["Срез адресного запроса собран.", `Строк отобрано: ${rows.length}.`, ...formatTopRows(rows, 6)]; return { responseType: "FACTUAL_LIST", text: lines.join("\n") diff --git a/llm_normalizer/backend/dist/services/assistantAnswerPackageBuilder.js b/llm_normalizer/backend/dist/services/assistantAnswerPackageBuilder.js new file mode 100644 index 0000000..7b31962 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantAnswerPackageBuilder.js @@ -0,0 +1,107 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantAnswerStructureV11 = buildAssistantAnswerStructureV11; +const config_1 = require("../config"); +const stage1Contracts_1 = require("../types/stage1Contracts"); +const EVIDENCE_LIMITATION_REASON_CODE_SET = new Set([ + "snapshot_only", + "heuristic_inference", + "missing_mechanism", + "weak_source_mapping", + "insufficient_detail", + "unknown" +]); +function summarizeUnique(values, limit = 6) { + return Array.from(new Set(values.map((item) => String(item ?? "").trim()).filter(Boolean))).slice(0, limit); +} +function isEvidenceLimitationReasonCode(value) { + return EVIDENCE_LIMITATION_REASON_CODE_SET.has(value); +} +function firstNonEmptyLine(text) { + const line = String(text ?? "") + .split("\n") + .map((item) => item.trim()) + .find((item) => item.length > 0); + return (line ?? String(text ?? "")).slice(0, 220); +} +function buildClaimEvidenceLinks(retrievalResults) { + const byClaim = new Map(); + for (const result of retrievalResults) { + for (const evidence of result.evidence) { + const claimRef = String(evidence.claim_ref ?? "").trim(); + if (!claimRef) { + continue; + } + const evidenceId = String(evidence.evidence_id ?? "").trim(); + if (!evidenceId) { + continue; + } + const current = byClaim.get(claimRef) ?? []; + current.push(evidenceId); + byClaim.set(claimRef, current); + } + } + return Array.from(byClaim.entries()) + .slice(0, 10) + .map(([claimRef, evidenceIds]) => ({ + claim_ref: claimRef, + evidence_ids: summarizeUnique(evidenceIds, 10) + })); +} +function buildAssistantAnswerStructureV11(input) { + const evidenceIds = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence.map((evidence) => evidence.evidence_id)), 10); + const mechanismNotes = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence + .map((evidence) => evidence.mechanism_note) + .filter((note) => typeof note === "string" && note.trim().length > 0)), 6); + const sourceRefs = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence + .map((evidence) => evidence.source_ref?.canonical_ref) + .filter((value) => typeof value === "string" && value.trim().length > 0)), 8); + const limitationReasonCodes = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence.flatMap((evidence) => { + const code = evidence.limitation?.reason_code; + return typeof code === "string" && code.trim().length > 0 ? [code] : []; + })), 8).filter(isEvidenceLimitationReasonCode); + const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults); + const limitations = summarizeUnique([...input.retrievalResults.flatMap((item) => item.limitations), ...input.groundingCheck.reasons], 8); + const clarificationQuestions = input.coverageReport.clarification_needed_for.map((item) => `Уточните требование ${item}.`); + const recommendedActions = summarizeUnique([ + ...input.coverageReport.requirements_uncovered.map((item) => `Проверить непокрытое требование ${item}.`), + ...input.coverageReport.requirements_partially_covered.map((item) => `Доуточнить частично покрытое требование ${item}.`) + ], 6); + const mechanismStatus = mechanismNotes.length === 0 + ? "unresolved" + : limitationReasonCodes.includes("missing_mechanism") || limitationReasonCodes.includes("heuristic_inference") + ? "limited" + : "grounded"; + const enableEvidenceEnrichment = input.options?.enableEvidenceEnrichment ?? config_1.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1; + return { + schema_version: stage1Contracts_1.ANSWER_STRUCTURE_SCHEMA_VERSION, + answer_summary: firstNonEmptyLine(input.assistantReply), + direct_answer: input.assistantReply, + mechanism_block: { + status: mechanismStatus, + mechanism_notes: mechanismNotes, + limitation_reason_codes: limitationReasonCodes + }, + evidence_block: { + evidence_ids: evidenceIds, + source_refs: sourceRefs, + mechanism_notes: mechanismNotes, + coverage_note: input.coverageReport.requirements_total === input.coverageReport.requirements_covered + ? "coverage_full_or_near_full" + : "coverage_partial_or_limited", + ...(enableEvidenceEnrichment && claimEvidenceLinks.length > 0 + ? { + claim_evidence_links: claimEvidenceLinks + } + : {}) + }, + uncertainty_block: { + open_uncertainties: input.groundingCheck.missing_requirements, + limitations + }, + next_step_block: { + recommended_actions: recommendedActions, + clarification_questions: clarificationQuestions + } + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantContractsBundleAssembler.js b/llm_normalizer/backend/dist/services/assistantContractsBundleAssembler.js new file mode 100644 index 0000000..2f33cb0 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantContractsBundleAssembler.js @@ -0,0 +1,41 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.assembleAssistantContractsBundleV1 = assembleAssistantContractsBundleV1; +const assistantOrchestrationContracts_1 = require("./assistantOrchestrationContracts"); +function assembleAssistantContractsBundleV1(input) { + const queryFrameContractV1 = (0, assistantOrchestrationContracts_1.buildAssistantQueryFrameContractV1)({ + userMessage: input.userMessage, + normalizedQuestion: input.normalizedQuestion, + normalized: input.normalized, + routeSummary: input.routeSummary, + droppedIntentSegments: input.droppedIntentSegments, + analysisContext: input.analysisContext + }); + const executionPlanContractV1 = (0, assistantOrchestrationContracts_1.buildAssistantExecutionPlanContractV1)({ + executionPlan: input.executionPlan, + requirements: input.requirements + }); + const outcomeClassV1 = (0, assistantOrchestrationContracts_1.classifyAssistantOutcomeClassV1)({ + replyType: input.replyType, + coverageReport: input.coverageReport, + grounding: input.grounding, + retrievalResults: input.retrievalResults + }); + const coverageContractV1 = (0, assistantOrchestrationContracts_1.buildAssistantCoverageContractV1)({ + coverageReport: input.coverageReport, + grounding: input.grounding, + outcomeClass: outcomeClassV1 + }); + return { + queryFrameContractV1, + executionPlanContractV1, + outcomeClassV1, + coverageContractV1, + assistantOrchestrationContractsV1: { + query_frame: queryFrameContractV1, + execution_plan: executionPlanContractV1, + evidence_bundle: input.evidenceBundleContractV1, + coverage: coverageContractV1 + } + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantCoverageGrounding.js b/llm_normalizer/backend/dist/services/assistantCoverageGrounding.js new file mode 100644 index 0000000..fbc4bbc --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantCoverageGrounding.js @@ -0,0 +1,344 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.extractRequirementsForRoute = extractRequirementsForRoute; +exports.evaluateCoverageForRequirements = evaluateCoverageForRequirements; +exports.checkGroundingForRequirements = checkGroundingForRequirements; +function summarizeUnique(values, limit = 6) { + return Array.from(new Set(values.map((item) => String(item ?? "").trim()).filter(Boolean))).slice(0, limit); +} +const SUBJECT_TOKEN_RULES = { + nds: { + critical: true, + patterns: [ + "vat", + "accumulationregister", + "ндс", + "книгипокупок", + "книгипродаж", + "налогнадобавленнуюстоимость" + ] + }, + os: { + critical: true, + patterns: ["fixed_asset", "fixedasset", "основн", "амортиз"] + }, + saldo: { + critical: true, + patterns: ["balance", "saldo", "сальдо", "остат"] + }, + counterparty: { + critical: false, + patterns: [ + "counterparty", + "supplier", + "buyer", + "counterparty_id", + "journal_counterparty", + "document_has_counterparty", + "контрагент", + "поставщик", + "покупател" + ], + routes: ["hybrid_store_plus_live", "store_feature_risk", "store_canonical"] + }, + document: { + critical: false, + patterns: [ + "document", + "recorder", + "journal", + "document_refs_count", + "recorded_by_document", + "journal_refers_to_document", + "документ" + ], + routes: ["hybrid_store_plus_live", "store_feature_risk", "store_canonical", "live_mcp_drilldown"] + }, + anomaly: { + critical: false, + patterns: [ + "risk", + "risk_score", + "unknown_link_count", + "zero_guid", + "navigation_links", + "missing_counterparty_link", + "аномал", + "риск" + ], + routes: ["store_feature_risk", "batch_refresh_then_store"] + }, + chain: { + critical: false, + patterns: ["chain", "cross_entity_chain", "relation_types", "operations_count", "matched_counterparties", "цепоч"], + routes: ["hybrid_store_plus_live"] + } +}; +function hasRegexMatch(corpus, pattern) { + try { + return pattern.test(corpus); + } + catch { + return false; + } +} +function evaluateSubjectTokenMatch(token, corpus, executedRoutes) { + if (token.startsWith("account_")) { + const account = token.slice("account_".length).trim(); + if (!account) { + return { matched: false, critical: true }; + } + const escaped = account.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const accountPattern = new RegExp(`(^|[^0-9])${escaped}([^0-9]|$)`, "i"); + return { matched: hasRegexMatch(corpus, accountPattern), critical: true }; + } + const rule = SUBJECT_TOKEN_RULES[token]; + if (rule) { + const byPattern = rule.patterns.some((pattern) => corpus.includes(pattern)); + const byRoute = Array.isArray(rule.routes) ? rule.routes.some((route) => executedRoutes.has(route)) : false; + return { matched: byPattern || byRoute, critical: rule.critical }; + } + return { matched: corpus.includes(token), critical: false }; +} +function evidenceCountForRequirement(requirementId, result) { + const evidence = Array.isArray(result.evidence) ? result.evidence : []; + if (evidence.length === 0) { + return 0; + } + const tagged = evidence.filter((item) => { + const claimRef = typeof item?.claim_ref === "string" ? item.claim_ref : ""; + return claimRef.toLowerCase() === `requirement:${String(requirementId).toLowerCase()}`; + }).length; + if (tagged > 0) { + return tagged; + } + if (Array.isArray(result.requirement_ids) && + result.requirement_ids.length === 1 && + result.requirement_ids[0] === requirementId) { + return evidence.length; + } + return 0; +} +function hasSubstantiveCoverageForRequirement(requirementId, result) { + const evidenceCount = evidenceCountForRequirement(requirementId, result); + if (evidenceCount > 0) { + return true; + } + const problemUnitsCount = Array.isArray(result.problem_units) ? result.problem_units.length : 0; + const candidateEvidenceCount = Array.isArray(result.candidate_evidence) ? result.candidate_evidence.length : 0; + if (problemUnitsCount > 0 || candidateEvidenceCount > 0) { + if (Array.isArray(result.requirement_ids) && + result.requirement_ids.length === 1 && + result.requirement_ids[0] === requirementId) { + return true; + } + } + return false; +} +function extractRequirementsForRoute(input) { + const byFragment = new Map(); + const requirements = []; + const pushRequirement = (item) => { + const subjectTokens = input.extractSubjectTokens(item.requirement_text); + requirements.push({ + requirement_id: item.requirement_id, + source_fragment_id: item.source_fragment_id, + requirement_text: item.requirement_text, + subject_tokens: subjectTokens, + status: item.status, + route: item.route + }); + if (item.source_fragment_id) { + const current = byFragment.get(item.source_fragment_id) ?? []; + current.push(item.requirement_id); + byFragment.set(item.source_fragment_id, current); + } + }; + if (!input.routeSummary) { + pushRequirement({ + requirement_id: "R1", + source_fragment_id: null, + requirement_text: input.userMessage, + status: "clarification_needed", + route: null + }); + return { requirements, byFragment }; + } + if (input.routeSummary.mode === "legacy_v1") { + pushRequirement({ + requirement_id: "R1", + source_fragment_id: "F1", + requirement_text: input.userMessage, + status: "covered", + route: input.routeSummary.route_hint + }); + return { requirements, byFragment }; + } + input.routeSummary.decisions.forEach((decision, index) => { + const requirementId = `R${index + 1}`; + const text = input.fragmentTextById.get(decision.fragment_id) ?? input.userMessage; + let status = "covered"; + if (decision.route === "no_route") { + if (decision.no_route_reason === "out_of_scope") { + status = "out_of_scope"; + } + else if (decision.no_route_reason === "insufficient_specificity") { + status = "clarification_needed"; + } + else { + status = "uncovered"; + } + } + pushRequirement({ + requirement_id: requirementId, + source_fragment_id: decision.fragment_id, + requirement_text: text, + status, + route: decision.route === "no_route" ? null : decision.route + }); + }); + return { requirements, byFragment }; +} +function evaluateCoverageForRequirements(requirements, retrievalResults) { + const statusByRequirement = new Map(); + for (const result of retrievalResults) { + for (const requirementId of result.requirement_ids) { + const list = statusByRequirement.get(requirementId) ?? []; + list.push({ + status: result.status, + substantive: hasSubstantiveCoverageForRequirement(requirementId, result) + }); + statusByRequirement.set(requirementId, list); + } + } + const resolvedRequirements = requirements.map((requirement) => { + if (requirement.status === "out_of_scope" || requirement.status === "clarification_needed") { + return requirement; + } + const states = statusByRequirement.get(requirement.requirement_id) ?? []; + if (states.length === 0) { + return { ...requirement, status: "uncovered" }; + } + const hasAnySubstantive = states.some((item) => item.substantive); + if (!hasAnySubstantive) { + return { ...requirement, status: "uncovered" }; + } + const hasOk = states.some((item) => item.status === "ok"); + const hasPartial = states.some((item) => item.status === "partial"); + const hasEmpty = states.some((item) => item.status === "empty"); + const hasError = states.some((item) => item.status === "error"); + const hasWeakOk = states.some((item) => item.status === "ok" && !item.substantive); + const hasSubstantiveOk = states.some((item) => item.status === "ok" && item.substantive); + const hasSubstantivePartial = states.some((item) => item.status === "partial" && item.substantive); + if (hasSubstantiveOk && !hasSubstantivePartial && !hasWeakOk && !hasEmpty && !hasError) { + return { ...requirement, status: "covered" }; + } + if (hasSubstantiveOk || hasSubstantivePartial || hasOk || hasPartial) { + return { ...requirement, status: "partially_covered" }; + } + return { ...requirement, status: "uncovered" }; + }); + const requirementsCovered = resolvedRequirements.filter((item) => item.status === "covered").length; + const requirementsUncovered = resolvedRequirements + .filter((item) => item.status === "uncovered") + .map((item) => item.requirement_id); + const requirementsPartiallyCovered = resolvedRequirements + .filter((item) => item.status === "partially_covered") + .map((item) => item.requirement_id); + const clarificationNeededFor = resolvedRequirements + .filter((item) => item.status === "clarification_needed") + .map((item) => item.requirement_id); + const outOfScopeRequirements = resolvedRequirements + .filter((item) => item.status === "out_of_scope") + .map((item) => item.requirement_id); + return { + requirements: resolvedRequirements, + coverage: { + requirements_total: resolvedRequirements.length, + requirements_covered: requirementsCovered, + requirements_uncovered: requirementsUncovered, + requirements_partially_covered: requirementsPartiallyCovered, + clarification_needed_for: clarificationNeededFor, + out_of_scope_requirements: outOfScopeRequirements + } + }; +} +function checkGroundingForRequirements(input) { + const whyIncludedSummary = summarizeUnique(input.retrievalResults.flatMap((item) => item.why_included)); + const selectionReasonSummary = summarizeUnique(input.retrievalResults.flatMap((item) => item.selection_reason)); + const hasMaterialResults = input.retrievalResults.some((item) => item.status === "ok" || item.status === "partial"); + const subjectTokens = input.extractSubjectTokens(input.userMessage); + const executedRoutes = new Set(input.retrievalResults + .filter((item) => item.status !== "error") + .map((item) => item.route) + .filter(Boolean)); + const retrievalCorpus = JSON.stringify(input.retrievalResults.map((item) => ({ + route: item.route, + result_type: item.result_type, + summary: item.summary, + items: item.items, + evidence: item.evidence, + why_included: item.why_included, + selection_reason: item.selection_reason, + risk_factors: item.risk_factors, + business_interpretation: item.business_interpretation + }))).toLowerCase(); + const missingSubjectTokens = []; + const missingCriticalTokens = []; + for (const token of subjectTokens) { + const match = evaluateSubjectTokenMatch(token, retrievalCorpus, executedRoutes); + if (!match.matched) { + missingSubjectTokens.push(token); + if (match.critical) { + missingCriticalTokens.push(token); + } + } + } + const onlyAccountCriticalMissing = missingCriticalTokens.length > 0 && missingCriticalTokens.every((token) => token.startsWith("account_")); + const accountOnlyMismatchRecoverable = hasMaterialResults && + input.coverage.requirements_covered > 0 && + onlyAccountCriticalMissing && + (whyIncludedSummary.length > 0 || selectionReasonSummary.length > 0); + const routeSubjectMatch = !hasMaterialResults || missingCriticalTokens.length === 0 || accountOnlyMismatchRecoverable; + let status = "grounded"; + const reasons = []; + if (!routeSubjectMatch) { + status = "route_mismatch_blocked"; + reasons.push(`Ключевые ориентиры вопроса не подтверждены в найденных данных: ${missingCriticalTokens.join(", ")}`); + } + else if (accountOnlyMismatchRecoverable) { + status = "partial"; + reasons.push(`Часть счетных ориентиров не подтвердилась напрямую (${missingCriticalTokens.join(", ")}), но есть опора для ограниченного вывода.`); + } + else if (input.coverage.requirements_covered === 0) { + status = "no_grounded_answer"; + reasons.push("Ни одно требование не получило подтвержденного покрытия."); + } + else if (input.coverage.requirements_uncovered.length > 0 || + input.coverage.requirements_partially_covered.length > 0 || + input.coverage.clarification_needed_for.length > 0 || + input.coverage.out_of_scope_requirements.length > 0) { + status = "partial"; + reasons.push("Вопрос покрыт частично: есть непокрытые или требующие уточнения требования."); + } + if (whyIncludedSummary.length === 0) { + reasons.push("В текущей выборке не хватает явных подтверждений, почему записи попали в ответ."); + } + if (missingSubjectTokens.length > 0 && missingCriticalTokens.length === 0) { + reasons.push(`Часть контекста вопроса не подтверждена напрямую в найденных данных: ${missingSubjectTokens.join(", ")}`); + } + const missingRequirements = [ + ...input.coverage.requirements_uncovered, + ...input.coverage.requirements_partially_covered, + ...input.coverage.clarification_needed_for, + ...input.coverage.out_of_scope_requirements + ]; + return { + status, + route_subject_match: routeSubjectMatch, + missing_requirements: missingRequirements, + reasons, + why_included_summary: whyIncludedSummary, + selection_reason_summary: selectionReasonSummary + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDataLayer.js b/llm_normalizer/backend/dist/services/assistantDataLayer.js index 0e1701f..a298e9f 100644 --- a/llm_normalizer/backend/dist/services/assistantDataLayer.js +++ b/llm_normalizer/backend/dist/services/assistantDataLayer.js @@ -141,6 +141,29 @@ function formatIsoDateUtc(date) { const day = String(date.getUTCDate()).padStart(2, "0"); return `${year}-${month}-${day}`; } +function normalizeIsoDate(value) { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!match) { + return null; + } + const year = Number(match[1]); + const month = Number(match[2]); + const day = Number(match[3]); + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + return null; + } + const candidate = new Date(Date.UTC(year, month - 1, day)); + if (candidate.getUTCFullYear() !== year || + candidate.getUTCMonth() + 1 !== month || + candidate.getUTCDate() !== day) { + return null; + } + return `${match[1]}-${match[2]}-${match[3]}`; +} function monthEndFromIso(isoDate) { const match = String(isoDate ?? "").match(/^(\d{4})-(\d{2})-(\d{2})$/); if (!match) { @@ -198,14 +221,25 @@ function hasRbpSignal(text) { function hasFixedAssetAmortizationSignal(text) { return /(?:амортиз|основн(?:ые|ых)?\s+сред|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|depreciat|fixed\s*asset|account\s*0[12]|счет\s*0[12])/i.test(String(text ?? "").toLowerCase()); } -function buildLiveMcpCallPlan(route, fragmentText) { +function buildLiveMcpCallPlan(route, fragmentText, temporalHint) { const semanticProfile = buildSemanticRetrievalProfile(fragmentText); const preferredDomainHint = (0, investigationState_1.inferP0DomainFromMessage)(fragmentText); const periodScope = inferPeriodScope(fragmentText); - const primaryFrom = periodScope.from ?? "2020-07-01"; - const primaryTo = periodScope.to ?? monthEndFromIso(primaryFrom) ?? "2020-07-31"; - const carryFrom = shiftIsoDate(primaryFrom, -31) ?? primaryFrom; - const carryTo = shiftIsoDate(primaryTo, 31) ?? primaryTo; + const hintedAsOfDate = normalizeIsoDate(temporalHint?.as_of_date); + const hintedPeriodFrom = normalizeIsoDate(temporalHint?.period_from); + const hintedPeriodTo = normalizeIsoDate(temporalHint?.period_to); + const primaryFrom = periodScope.from ?? hintedPeriodFrom ?? hintedAsOfDate; + const primaryTo = periodScope.to ?? + hintedPeriodTo ?? + (!periodScope.from && !hintedPeriodFrom && hintedAsOfDate ? hintedAsOfDate : primaryFrom ? monthEndFromIso(primaryFrom) ?? primaryFrom : null); + const carryFrom = primaryFrom ? shiftIsoDate(primaryFrom, -31) ?? primaryFrom : null; + const carryTo = primaryTo ? shiftIsoDate(primaryTo, 31) ?? primaryTo : null; + const buildPrimaryQuery = (limit) => primaryFrom && primaryTo + ? buildLiveRangeQuery(primaryFrom, primaryTo, limit) + : MCP_LIVE_MOVEMENTS_QUERY_TEMPLATE.replace("__LIMIT__", String(limit)); + const buildCarryQuery = (limit) => carryFrom && carryTo + ? buildLiveRangeQuery(carryFrom, carryTo, limit) + : buildPrimaryQuery(limit); const faClaim = preferredDomainHint === "fixed_asset_amortization" || hasFixedAssetAmortizationSignal(fragmentText) || semanticProfile.query_subject === "fixed_asset_card_mismatch" || @@ -219,7 +253,7 @@ function buildLiveMcpCallPlan(route, fragmentText) { { call_id: "find_amortization_documents_in_period", purpose: "seed_amortization_documents", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["01", "02", "08"] @@ -227,7 +261,7 @@ function buildLiveMcpCallPlan(route, fragmentText) { { call_id: "find_fixed_asset_movements_accounts_01_02", purpose: "collect_fa_object_movements", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["01", "02", "08"] @@ -235,7 +269,7 @@ function buildLiveMcpCallPlan(route, fragmentText) { { call_id: "find_fixed_asset_cards_expected_for_period", purpose: "build_expected_fa_set", - query: buildLiveRangeQuery(carryFrom, primaryTo, CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), + query: buildCarryQuery(CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), limit: CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["01", "02", "08"] @@ -243,7 +277,7 @@ function buildLiveMcpCallPlan(route, fragmentText) { { call_id: "match_expected_vs_actual_fa_coverage", purpose: "compare_expected_vs_actual_fa_coverage", - query: buildLiveRangeQuery(carryFrom, carryTo, CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), + query: buildCarryQuery(CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), limit: CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["01", "02", "08"] @@ -265,7 +299,7 @@ function buildLiveMcpCallPlan(route, fragmentText) { { call_id: "find_vat_source_documents_in_period", purpose: "seed_vat_source_documents", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["19", "68"] @@ -273,7 +307,7 @@ function buildLiveMcpCallPlan(route, fragmentText) { { call_id: "find_vat_invoice_links_in_period", purpose: "collect_invoice_links", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["19", "68"] @@ -281,7 +315,7 @@ function buildLiveMcpCallPlan(route, fragmentText) { { call_id: "find_vat_register_entries_in_period", purpose: "collect_vat_register_entries", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["19", "68"] @@ -289,7 +323,7 @@ function buildLiveMcpCallPlan(route, fragmentText) { { call_id: "find_vat_book_entries_in_period", purpose: "collect_vat_book_entries", - query: buildLiveRangeQuery(carryFrom, carryTo, CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), + query: buildCarryQuery(CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), limit: CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["19", "68"] @@ -326,7 +360,7 @@ function buildLiveMcpCallPlan(route, fragmentText) { { call_id: "find_rbp_writeoff_documents_in_period", purpose: "seed_writeoff_documents", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["97", "20", "25", "26", "44"] @@ -334,7 +368,7 @@ function buildLiveMcpCallPlan(route, fragmentText) { { call_id: "find_rbp_object_movements_account_97", purpose: "collect_rbp_object_movements", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["97"] @@ -342,7 +376,7 @@ function buildLiveMcpCallPlan(route, fragmentText) { { call_id: "find_month_close_entries_linked_to_rbp", purpose: "link_month_close_to_rbp", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["97", "20", "25", "26", "44"] @@ -350,7 +384,7 @@ function buildLiveMcpCallPlan(route, fragmentText) { { call_id: "compute_end_period_residual_by_rbp_object", purpose: "collect_residual_tail_signals", - query: buildLiveRangeQuery(carryFrom, carryTo, CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), + query: buildCarryQuery(CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), limit: CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["97", "20", "25", "26", "44"] @@ -1410,7 +1444,7 @@ function buildSemanticRetrievalProfile(fragmentText) { pushMany(entityTypes, ["document", "tax_entry", "posting"]); pushMany(relationPatterns, ["invoice_to_vat", "document_to_posting"]); } - if (/ос|основн(ые|ых)\s+сред|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|основн(ые|ых|ым)?\s+средств|fixed asset|amort|амортиз|амортиз/i.test(lower) || + if (/основн(ые|ых)\s+сред|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|основн(ые|ых|ым)?\s+средств|fixed asset|amort|амортиз|амортиз/i.test(lower) || hasFixedAssetAccountScope) { pushMany(domainScope, ["fixed_assets"]); pushMany(documentTypes, ["fixed_asset_card", "fixed_asset_acceptance", "depreciation_document"]); @@ -2243,7 +2277,7 @@ class AssistantDataLayer { } return enforceBroadQueryGuards(route, fragmentText, result); } - async executeRouteRuntime(route, fragmentText) { + async executeRouteRuntime(route, fragmentText, options) { const base = this.executeRoute(route, fragmentText); if (!config_1.FEATURE_ASSISTANT_MCP_RUNTIME_V1) { return base; @@ -2251,7 +2285,7 @@ class AssistantDataLayer { if (route !== "hybrid_store_plus_live" && route !== "live_mcp_drilldown") { return base; } - const liveOverlay = await this.fetchLiveMcpOverlay(route, fragmentText); + const liveOverlay = await this.fetchLiveMcpOverlay(route, fragmentText, options?.temporalHint); return this.mergeWithLiveOverlay(base, liveOverlay); } cloneRawResult(base) { @@ -2301,9 +2335,9 @@ class AssistantDataLayer { } return merged; } - async fetchLiveMcpOverlay(route, fragmentText) { + async fetchLiveMcpOverlay(route, fragmentText, temporalHint) { const endpoint = this.buildMcpUrl("/api/execute_query"); - const livePlan = buildLiveMcpCallPlan(route, fragmentText); + const livePlan = buildLiveMcpCallPlan(route, fragmentText, temporalHint); const explicitAccountScope = extractAccountScopeFromText(fragmentText); const accountScope = livePlan.claim_type === "prove_fixed_asset_amortization_coverage" ? ["01", "02", "08"] diff --git a/llm_normalizer/backend/dist/services/assistantDebugPayloadAssembler.js b/llm_normalizer/backend/dist/services/assistantDebugPayloadAssembler.js new file mode 100644 index 0000000..4a22137 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDebugPayloadAssembler.js @@ -0,0 +1,92 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildDeepAnalysisDebugPayload = buildDeepAnalysisDebugPayload; +function toAnalysisContext(input) { + if (!input.active) { + return null; + } + return { + as_of_date: input.as_of_date, + period_from: input.period_from, + period_to: input.period_to, + source: input.source, + snapshot_mode: input.snapshot_mode + }; +} +function buildDeepAnalysisDebugPayload(input) { + const analysisContext = toAnalysisContext(input.runtimeAnalysisContext); + return { + trace_id: input.traceId, + prompt_version: input.promptVersion, + schema_version: input.schemaVersion, + fallback_type: input.fallbackType, + route_summary: input.routeSummary, + fragments: input.fragments, + requirements_extracted: input.requirementsExtracted, + coverage_report: input.coverageReport, + routes: input.routes, + retrieval_status: input.retrievalStatus, + retrieval_results: input.retrievalResults, + answer_grounding_check: input.groundingCheck, + dropped_intent_segments: input.droppedIntentSegments, + question_type_class: input.questionTypeClass, + company_anchors: input.companyAnchors, + analysis_context_applied: input.runtimeAnalysisContext.active, + analysis_context: analysisContext, + business_scope_raw: input.businessScopeResolution.business_scope_raw, + business_scope_resolved: input.businessScopeResolution.business_scope_resolved, + company_grounding_applied: input.businessScopeResolution.company_grounding_applied, + scope_resolution_reason: input.businessScopeResolution.scope_resolution_reason, + company_scope_resolution_reason: input.businessScopeResolution.scope_resolution_reason, + raw_time_anchor: input.temporalGuard.raw_time_anchor, + raw_time_scope: input.temporalGuard.raw_time_scope, + resolved_time_anchor: input.temporalGuard.resolved_time_anchor, + resolved_primary_period: input.temporalGuard.resolved_primary_period, + effective_primary_period: input.temporalGuard.effective_primary_period, + temporal_guard_input: input.temporalGuard.temporal_guard_input, + temporal_alignment_status: input.temporalGuard.temporal_alignment_status, + temporal_resolution_source: input.temporalGuard.temporal_resolution_source, + temporal_guard_basis: input.temporalGuard.temporal_guard_basis, + temporal_guard_applied: input.temporalGuard.temporal_guard_applied, + temporal_guard_outcome: input.temporalGuard.temporal_guard_outcome, + temporal_guard: input.temporalGuard, + raw_numeric_tokens: input.polarityAudit.raw_numeric_tokens, + classified_numeric_tokens: input.polarityAudit.classified_numeric_tokens, + rejected_as_non_accounts: input.polarityAudit.rejected_as_non_accounts, + resolved_account_anchors: input.polarityAudit.resolved_account_anchors, + domain_polarity_guard: input.polarityAudit, + claim_anchor_audit: input.claimAnchorAudit, + settlement_role: input.claimAnchorAudit.settlement_role ?? null, + settlement_role_resolution_reason: input.claimAnchorAudit.settlement_role_resolution_reason ?? [], + polarity_resolution_status: input.claimAnchorAudit.polarity_resolution_status ?? "not_applicable", + targeted_evidence_acquisition: input.targetedEvidenceAudit, + evidence_admissibility_gate: input.evidenceAdmissibilityGateAudit, + ...(input.rbpLiveRouteAudit ? { rbp_live_route_audit: input.rbpLiveRouteAudit } : {}), + ...(input.faLiveRouteAudit ? { fa_live_route_audit: input.faLiveRouteAudit } : {}), + eligibility_time_basis: input.groundedAnswerEligibilityGuard.eligibility_time_basis, + grounded_answer_eligibility_guard: input.groundedAnswerEligibilityGuard, + ...(input.followupStateUsage ? { followup_state_usage: input.followupStateUsage } : {}), + problem_centric_answer_applied: input.compositionDebug.problem_centric_answer_applied ?? false, + problem_units_used_count: input.compositionDebug.problem_units_used_count ?? 0, + problem_answer_mode: input.compositionDebug.problem_answer_mode ?? "stage1_policy_v11", + ...(Array.isArray(input.compositionDebug.problem_unit_ids_used) && input.compositionDebug.problem_unit_ids_used.length > 0 + ? { + problem_unit_ids_used: input.compositionDebug.problem_unit_ids_used + } + : {}), + address_llm_predecompose_attempted: Boolean(input.addressRuntimeMetaForDeep?.attempted), + address_llm_predecompose_applied: Boolean(input.addressRuntimeMetaForDeep?.applied), + address_llm_predecompose_reason: input.addressRuntimeMetaForDeep?.reason ?? null, + address_llm_predecompose_provider: input.addressRuntimeMetaForDeep?.provider ?? null, + address_fallback_rule_hit: input.addressRuntimeMetaForDeep?.fallbackRuleHit ?? null, + address_tool_gate_decision: input.addressRuntimeMetaForDeep?.toolGateDecision ?? null, + address_tool_gate_reason: input.addressRuntimeMetaForDeep?.toolGateReason ?? null, + address_llm_predecompose_contract: input.addressRuntimeMetaForDeep?.predecomposeContract ?? null, + orchestration_contract_v1: input.addressRuntimeMetaForDeep?.orchestrationContract ?? null, + assistant_outcome_class_v1: input.outcomeClassV1, + assistant_orchestration_contracts_v1: input.assistantOrchestrationContractsV1, + answer_structure_v11: input.answerStructureV11, + investigation_state_snapshot: input.investigationStateSnapshot, + normalized: input.normalizedPayload + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepResponseAssembler.js b/llm_normalizer/backend/dist/services/assistantDeepResponseAssembler.js new file mode 100644 index 0000000..4d2cb11 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepResponseAssembler.js @@ -0,0 +1,40 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildDeepAnswerArtifacts = buildDeepAnswerArtifacts; +exports.buildAssistantConversationItem = buildAssistantConversationItem; +const assistantAnswerPackageBuilder_1 = require("./assistantAnswerPackageBuilder"); +function stripTechnicalTail(text) { + return String(text ?? "") + .replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "") + .replace(/\b(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "") + .trim(); +} +function buildDeepAnswerArtifacts(input) { + const safeAssistantReply = stripTechnicalTail(input.safeAssistantReplyBase); + const answerStructureV11 = input.featureContractsV11 + ? input.featureAnswerPolicyV11 && input.compositionAnswerStructureV11 + ? input.compositionAnswerStructureV11 + : (0, assistantAnswerPackageBuilder_1.buildAssistantAnswerStructureV11)({ + assistantReply: safeAssistantReply, + coverageReport: input.coverageReport, + groundingCheck: input.groundingCheck, + retrievalResults: input.retrievalResults + }) + : null; + return { + safeAssistantReply, + answerStructureV11 + }; +} +function buildAssistantConversationItem(input) { + return { + message_id: input.messageId, + session_id: input.sessionId, + role: "assistant", + text: input.text, + reply_type: input.replyType, + created_at: new Date().toISOString(), + trace_id: input.traceId, + debug: input.debug + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnCompositionRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantDeepTurnCompositionRuntimeAdapter.js new file mode 100644 index 0000000..912e400 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnCompositionRuntimeAdapter.js @@ -0,0 +1,40 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantDeepTurnComposition = buildAssistantDeepTurnComposition; +const questionTypeResolver_1 = require("./questionTypeResolver"); +const answerComposer_1 = require("./answerComposer"); +function buildAssistantDeepTurnComposition(input) { + const resolveQuestionTypeSafe = input.resolveQuestionTypeFn ?? questionTypeResolver_1.resolveQuestionType; + const composeAssistantAnswerSafe = input.composeAssistantAnswerFn ?? answerComposer_1.composeAssistantAnswer; + const followupApplied = Boolean(input.followupUsage?.applied); + const focusDomainHint = followupApplied + ? input.investigationState?.followup_context?.active_domain ?? input.investigationState?.focus.domain ?? null + : null; + const questionTypeClass = resolveQuestionTypeSafe(input.userMessage); + const companyAnchorSet = input.companyAnchors; + const hasPeriodInCompanyAnchors = (Array.isArray(companyAnchorSet?.dates) && companyAnchorSet.dates.some((item) => String(item ?? "").trim().length > 0)) || + (Array.isArray(companyAnchorSet?.periods) && companyAnchorSet.periods.some((item) => String(item ?? "").trim().length > 0)); + const normalizationPeriodExplicit = input.hasExplicitPeriodAnchor(input.normalizedPayload) || hasPeriodInCompanyAnchors; + const composition = composeAssistantAnswerSafe({ + userMessage: input.userMessage, + routeSummary: input.routeSummary, + retrievalResults: input.retrievalResults, + requirements: input.requirements, + coverageReport: input.coverageReport, + groundingCheck: input.groundingCheck, + focusDomainHint, + questionTypeHint: questionTypeClass, + companyAnchors: input.companyAnchors, + normalizationPeriodExplicit, + enableAnswerPolicyV11: input.featureAnswerPolicyV11, + enableProblemCentricAnswerV1: input.featureProblemCentricAnswerV1, + enableLifecycleAnswerV1: input.featureLifecycleAnswerV1 + }); + return { + focusDomainHint, + questionTypeClass, + hasPeriodInCompanyAnchors, + normalizationPeriodExplicit, + composition + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnContextRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantDeepTurnContextRuntimeAdapter.js new file mode 100644 index 0000000..5a37c58 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnContextRuntimeAdapter.js @@ -0,0 +1,72 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantDeepTurnRuntimeContext = buildAssistantDeepTurnRuntimeContext; +const KNOWN_P0_DOMAINS = new Set([ + "settlements_60_62", + "vat_document_register_book", + "month_close_costs_20_44", + "fixed_asset_amortization" +]); +function toAnalysisContext(runtimeAnalysisContext) { + if (!runtimeAnalysisContext.active) { + return null; + } + return { + as_of_date: runtimeAnalysisContext.as_of_date, + period_from: runtimeAnalysisContext.period_from, + period_to: runtimeAnalysisContext.period_to, + source: runtimeAnalysisContext.source + }; +} +function buildAssistantDeepTurnRuntimeContext(input) { + const companyAnchors = input.resolveCompanyAnchors(input.userMessage); + const initialBusinessScopeResolution = input.resolveBusinessScopeAlignment({ + userMessage: input.userMessage, + companyAnchors, + normalized: input.normalizedPayload, + routeSummary: input.routeSummary + }); + const inferredDomainByMessage = input.inferP0DomainFromMessage(input.userMessage); + const focusDomainForGuards = inferredDomainByMessage && KNOWN_P0_DOMAINS.has(inferredDomainByMessage) ? inferredDomainByMessage : null; + const analysisContext = toAnalysisContext(input.runtimeAnalysisContext); + const temporalGuard = input.resolveTemporalGuard({ + userMessage: input.userMessage, + normalized: input.normalizedPayload, + companyAnchors, + analysisContext + }); + const domainPolarityGuardInitial = input.resolveDomainPolarityGuard({ + userMessage: input.userMessage, + companyAnchors, + focusDomainHint: focusDomainForGuards + }); + const claimAnchorAudit = input.resolveClaimBoundAnchors({ + userMessage: input.userMessage, + companyAnchors, + focusDomainHint: focusDomainForGuards, + primaryPeriod: temporalGuard.effective_primary_period ?? temporalGuard.primary_period_window + }); + const businessScopeResolution = input.resolveBusinessScopeFromLiveContext({ + current: initialBusinessScopeResolution, + temporalGuard, + claimType: claimAnchorAudit.claim_type, + focusDomainHint: focusDomainForGuards, + userMessage: input.userMessage, + companyAnchors, + followupApplied: Boolean(input.followupUsage?.applied) + }); + const resolvedRouteSummary = businessScopeResolution.route_summary_resolved; + const liveTemporalHint = toAnalysisContext(input.runtimeAnalysisContext); + return { + companyAnchors, + initialBusinessScopeResolution, + inferredDomainByMessage, + focusDomainForGuards, + temporalGuard, + domainPolarityGuardInitial, + claimAnchorAudit, + businessScopeResolution, + resolvedRouteSummary, + liveTemporalHint + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnGroundingRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantDeepTurnGroundingRuntimeAdapter.js new file mode 100644 index 0000000..78bc1b7 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnGroundingRuntimeAdapter.js @@ -0,0 +1,49 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.runAssistantDeepTurnGroundingRuntime = runAssistantDeepTurnGroundingRuntime; +const assistantOrchestrationRuntimeAdapter_1 = require("./assistantOrchestrationRuntimeAdapter"); +const assistantDeepTurnGuardRuntimeAdapter_1 = require("./assistantDeepTurnGuardRuntimeAdapter"); +function runAssistantDeepTurnGroundingRuntime(input) { + const runCoverageGroundingPipelineSafe = input.runCoverageGroundingPipelineFn ?? assistantOrchestrationRuntimeAdapter_1.runAssistantCoverageGroundingPipeline; + const applyGroundingEligibilitySafe = input.applyGroundingEligibilityFn ?? + ((payload) => (0, assistantDeepTurnGuardRuntimeAdapter_1.applyAssistantDeepTurnGroundingEligibility)(payload)); + const rbpLiveRouteAudit = input.collectRbpLiveRouteAudit({ + claimType: input.claimType, + retrievalResults: input.retrievalResults, + planAudit: input.rbpPlanAudit + }); + const faLiveRouteAudit = input.collectFaLiveRouteAudit({ + claimType: input.claimType, + retrievalResults: input.retrievalResults, + planAudit: input.faPlanAudit + }); + const orchestrationRuntime = runCoverageGroundingPipelineSafe({ + routeSummary: input.routeSummary, + normalized: input.normalizedPayload, + userMessage: input.userMessage, + retrievalResults: input.retrievalResults, + requirementExtraction: input.requirementExtraction, + extractRequirements: input.extractRequirements, + evaluateCoverage: input.evaluateCoverage, + checkGrounding: input.checkGrounding + }); + const coverageEvaluation = orchestrationRuntime.coverageEvaluation; + const groundingCheckBase = orchestrationRuntime.groundingCheckBase; + const groundingEligibilityRuntime = applyGroundingEligibilitySafe({ + groundingCheckBase, + temporalGuard: input.temporalGuard, + polarityAudit: input.polarityAudit, + evidenceAudit: input.evidenceAudit, + claimAnchorAudit: input.claimAnchorAudit, + targetedEvidenceHitRate: input.targetedEvidenceHitRate, + businessScopeResolved: input.businessScopeResolved + }); + return { + rbpLiveRouteAudit, + faLiveRouteAudit, + coverageEvaluation, + groundingCheckBase, + groundedAnswerEligibilityGuard: groundingEligibilityRuntime.groundedAnswerEligibilityGuard, + groundingCheck: groundingEligibilityRuntime.groundingCheck + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnGuardRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantDeepTurnGuardRuntimeAdapter.js new file mode 100644 index 0000000..e3d6592 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnGuardRuntimeAdapter.js @@ -0,0 +1,51 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.applyAssistantDeepTurnRetrievalGuards = applyAssistantDeepTurnRetrievalGuards; +exports.applyAssistantDeepTurnGroundingEligibility = applyAssistantDeepTurnGroundingEligibility; +const assistantClaimBoundEvidence_1 = require("./assistantClaimBoundEvidence"); +const assistantRuntimeGuards_1 = require("./assistantRuntimeGuards"); +function applyAssistantDeepTurnRetrievalGuards(input) { + const applyDomainPolarityGuardSafe = input.applyDomainPolarityGuardFn ?? assistantRuntimeGuards_1.applyDomainPolarityGuardToRetrievalResults; + const applyTargetedEvidenceSafe = input.applyTargetedEvidenceFn ?? assistantClaimBoundEvidence_1.applyTargetedEvidenceAcquisition; + const applyEvidenceAdmissibilityGateSafe = input.applyEvidenceAdmissibilityGateFn ?? assistantRuntimeGuards_1.applyEvidenceAdmissibilityGate; + const polarityGuardResult = applyDomainPolarityGuardSafe({ + retrievalResults: input.retrievalResults, + guard: input.domainPolarityGuardInitial + }); + const targetedEvidenceResult = applyTargetedEvidenceSafe({ + retrievalResults: polarityGuardResult.retrievalResults, + claimAudit: input.claimAnchorAudit + }); + const evidenceGateResult = applyEvidenceAdmissibilityGateSafe({ + retrievalResults: targetedEvidenceResult.retrievalResults, + temporal: input.temporalGuard, + focusDomainHint: input.focusDomainForGuards, + polarity: polarityGuardResult.audit.polarity, + companyAnchors: input.companyAnchors, + userMessage: input.userMessage + }); + return { + retrievalResults: evidenceGateResult.retrievalResults, + polarityGuardResult, + targetedEvidenceResult, + evidenceGateResult + }; +} +function applyAssistantDeepTurnGroundingEligibility(input) { + const evaluateGroundedAnswerEligibilitySafe = input.evaluateGroundedAnswerEligibilityFn ?? assistantRuntimeGuards_1.evaluateGroundedAnswerEligibility; + const applyEligibilityToGroundingCheckSafe = input.applyEligibilityToGroundingCheckFn ?? + ((groundingCheck, eligibility) => (0, assistantRuntimeGuards_1.applyEligibilityToGroundingCheck)(groundingCheck, eligibility)); + const groundedAnswerEligibilityGuard = evaluateGroundedAnswerEligibilitySafe({ + temporal: input.temporalGuard, + polarity: input.polarityAudit, + evidence: input.evidenceAudit, + claimAnchors: input.claimAnchorAudit, + targetedEvidenceHitRate: input.targetedEvidenceHitRate, + businessScopeResolved: input.businessScopeResolved + }); + const groundingCheck = applyEligibilityToGroundingCheckSafe(input.groundingCheckBase, groundedAnswerEligibilityGuard); + return { + groundedAnswerEligibilityGuard, + groundingCheck + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnInputBuilder.js b/llm_normalizer/backend/dist/services/assistantDeepTurnInputBuilder.js new file mode 100644 index 0000000..5d50731 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnInputBuilder.js @@ -0,0 +1,19 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantDeepTurnPackagingInput = buildAssistantDeepTurnPackagingInput; +function buildAssistantDeepTurnPackagingInput(args) { + return { + ...args, + routesForDebug: Array.isArray(args.routesForDebug) ? args.routesForDebug : [], + followupStateUsage: args.followupStateUsage ?? null, + composition: { + reply_type: args.composition.reply_type, + fallback_type: args.composition.fallback_type, + answer_structure_v11: args.composition.answer_structure_v11 ?? null, + problem_centric_answer_applied: args.composition.problem_centric_answer_applied ?? false, + problem_units_used_count: args.composition.problem_units_used_count ?? 0, + problem_answer_mode: args.composition.problem_answer_mode ?? "stage1_policy_v11", + problem_unit_ids_used: Array.isArray(args.composition.problem_unit_ids_used) ? args.composition.problem_unit_ids_used : [] + } + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnPackaging.js b/llm_normalizer/backend/dist/services/assistantDeepTurnPackaging.js new file mode 100644 index 0000000..abcd20d --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnPackaging.js @@ -0,0 +1,139 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.assembleAssistantDeepTurnPackaging = assembleAssistantDeepTurnPackaging; +const assistantEvidenceBundleAssembler_1 = require("./assistantEvidenceBundleAssembler"); +const assistantContractsBundleAssembler_1 = require("./assistantContractsBundleAssembler"); +const assistantDeepResponseAssembler_1 = require("./assistantDeepResponseAssembler"); +const assistantDebugPayloadAssembler_1 = require("./assistantDebugPayloadAssembler"); +const assistantMessageLogAssembler_1 = require("./assistantMessageLogAssembler"); +function assembleAssistantDeepTurnPackaging(input) { + const normalizedPayload = (input.normalized.normalized ?? null); + const normalizedFragments = Array.isArray(normalizedPayload?.["fragments"]) ? normalizedPayload?.["fragments"] : []; + const evidenceBundleAssembly = (0, assistantEvidenceBundleAssembler_1.assembleAssistantEvidenceBundle)({ + retrievalCalls: input.retrievalCalls, + retrievalResults: input.retrievalResults + }); + const contractsBundleV1 = (0, assistantContractsBundleAssembler_1.assembleAssistantContractsBundleV1)({ + userMessage: input.userMessage, + normalizedQuestion: input.normalizedQuestion, + normalized: input.normalized.normalized, + routeSummary: input.routeSummary, + droppedIntentSegments: input.droppedIntentSegments, + analysisContext: input.analysisContextForContract, + executionPlan: input.executionPlan, + requirements: input.requirementExtractionRequirements, + evidenceBundleContractV1: evidenceBundleAssembly.evidenceBundleContractV1, + replyType: input.composition.reply_type, + coverageReport: input.coverageReport, + grounding: input.groundingCheck, + retrievalResults: input.retrievalResults + }); + const deepAnswerArtifacts = (0, assistantDeepResponseAssembler_1.buildDeepAnswerArtifacts)({ + safeAssistantReplyBase: input.safeAssistantReplyBase, + featureContractsV11: input.featureContractsV11, + featureAnswerPolicyV11: input.featureAnswerPolicyV11, + compositionAnswerStructureV11: input.composition.answer_structure_v11 ?? null, + coverageReport: input.coverageReport, + groundingCheck: input.groundingCheck, + retrievalResults: input.retrievalResults + }); + const debug = (0, assistantDebugPayloadAssembler_1.buildDeepAnalysisDebugPayload)({ + traceId: input.normalized.trace_id, + promptVersion: input.normalized.prompt_version, + schemaVersion: input.normalized.schema_version, + fallbackType: input.composition.fallback_type, + routeSummary: input.routeSummary, + fragments: normalizedFragments, + requirementsExtracted: input.coverageEvaluationRequirements, + coverageReport: input.coverageReport, + routes: input.routesForDebug, + retrievalStatus: evidenceBundleAssembly.retrievalStatus, + retrievalResults: input.retrievalResults, + groundingCheck: input.groundingCheck, + droppedIntentSegments: input.droppedIntentSegments, + questionTypeClass: input.questionTypeClass, + companyAnchors: input.companyAnchors, + runtimeAnalysisContext: input.runtimeAnalysisContext, + businessScopeResolution: input.businessScopeResolution, + temporalGuard: input.temporalGuard, + polarityAudit: input.polarityAudit, + claimAnchorAudit: input.claimAnchorAudit, + targetedEvidenceAudit: input.targetedEvidenceAudit, + evidenceAdmissibilityGateAudit: input.evidenceAdmissibilityGateAudit, + rbpLiveRouteAudit: input.rbpLiveRouteAudit, + faLiveRouteAudit: input.faLiveRouteAudit, + groundedAnswerEligibilityGuard: input.groundedAnswerEligibilityGuard, + followupStateUsage: input.followupStateUsage, + compositionDebug: { + problem_centric_answer_applied: input.composition.problem_centric_answer_applied ?? false, + problem_units_used_count: input.composition.problem_units_used_count ?? 0, + problem_answer_mode: input.composition.problem_answer_mode ?? "stage1_policy_v11", + problem_unit_ids_used: Array.isArray(input.composition.problem_unit_ids_used) ? input.composition.problem_unit_ids_used : [] + }, + addressRuntimeMetaForDeep: input.addressRuntimeMetaForDeep, + outcomeClassV1: contractsBundleV1.outcomeClassV1, + assistantOrchestrationContractsV1: contractsBundleV1.assistantOrchestrationContractsV1, + answerStructureV11: deepAnswerArtifacts.answerStructureV11, + investigationStateSnapshot: input.investigationStateSnapshot, + normalizedPayload: normalizedPayload + }); + const assistantItem = (0, assistantDeepResponseAssembler_1.buildAssistantConversationItem)({ + messageId: input.messageId, + sessionId: input.sessionId, + text: deepAnswerArtifacts.safeAssistantReply, + replyType: input.composition.reply_type, + traceId: input.normalized.trace_id, + debug: debug + }); + const deepAnalysisLogDetails = (0, assistantMessageLogAssembler_1.buildDeepAnalysisProcessedLogDetails)({ + sessionId: input.sessionId, + messageId: input.messageId, + userMessage: input.userMessage, + normalizerOutput: input.normalized.normalized, + executionPlan: input.executionPlan, + resolvedExecutionState: input.resolvedExecutionState, + routes: input.routesForDebug, + retrievalCalls: input.retrievalCalls, + retrievalResultsRaw: input.retrievalResultsRaw, + retrievalResultsNormalized: input.retrievalResults, + requirementsExtracted: input.coverageEvaluationRequirements, + coverageReport: input.coverageReport, + groundingCheck: input.groundingCheck, + replyType: input.composition.reply_type, + droppedIntentSegments: input.droppedIntentSegments, + questionTypeClass: input.questionTypeClass, + companyAnchors: input.companyAnchors, + runtimeAnalysisContext: input.runtimeAnalysisContext, + businessScopeResolution: input.businessScopeResolution, + temporalGuard: input.temporalGuard, + polarityAudit: input.polarityAudit, + claimAnchorAudit: input.claimAnchorAudit, + targetedEvidenceAudit: input.targetedEvidenceAudit, + evidenceAdmissibilityGateAudit: input.evidenceAdmissibilityGateAudit, + rbpLiveRouteAudit: input.rbpLiveRouteAudit, + faLiveRouteAudit: input.faLiveRouteAudit, + groundedAnswerEligibilityGuard: input.groundedAnswerEligibilityGuard, + followupStateUsage: input.followupStateUsage, + compositionDebug: { + problem_centric_answer_applied: input.composition.problem_centric_answer_applied ?? false, + problem_units_used_count: input.composition.problem_units_used_count ?? 0, + problem_answer_mode: input.composition.problem_answer_mode ?? "stage1_policy_v11", + problem_unit_ids_used: Array.isArray(input.composition.problem_unit_ids_used) ? input.composition.problem_unit_ids_used : [], + fallback_type: input.composition.fallback_type + }, + outcomeClassV1: contractsBundleV1.outcomeClassV1, + assistantOrchestrationContractsV1: contractsBundleV1.assistantOrchestrationContractsV1, + answerStructureV11: deepAnswerArtifacts.answerStructureV11, + investigationStateSnapshot: input.investigationStateSnapshot, + assistantReply: deepAnswerArtifacts.safeAssistantReply, + traceId: input.normalized.trace_id + }); + return { + evidenceBundleAssembly, + contractsBundleV1, + deepAnswerArtifacts, + debug, + assistantItem, + deepAnalysisLogDetails + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnPackagingRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantDeepTurnPackagingRuntimeAdapter.js new file mode 100644 index 0000000..a75d31a --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnPackagingRuntimeAdapter.js @@ -0,0 +1,106 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.runAssistantDeepTurnPackagingRuntime = runAssistantDeepTurnPackagingRuntime; +const nanoid_1 = require("nanoid"); +const assistantDeepTurnInputBuilder_1 = require("./assistantDeepTurnInputBuilder"); +const assistantDeepTurnPackaging_1 = require("./assistantDeepTurnPackaging"); +const assistantDeepTurnPrePackagingContext_1 = require("./assistantDeepTurnPrePackagingContext"); +const assistantInvestigationStateRuntimeAdapter_1 = require("./assistantInvestigationStateRuntimeAdapter"); +function runAssistantDeepTurnPackagingRuntime(input) { + const buildPrePackagingContextSafe = input.buildPrePackagingContextFn ?? assistantDeepTurnPrePackagingContext_1.buildAssistantDeepTurnPrePackagingContext; + const buildInvestigationStateSnapshotSafe = input.buildInvestigationStateSnapshotFn ?? assistantInvestigationStateRuntimeAdapter_1.buildAssistantInvestigationStateSnapshot; + const persistInvestigationStateSnapshotSafe = input.persistInvestigationStateSnapshotFn ?? assistantInvestigationStateRuntimeAdapter_1.persistAssistantInvestigationStateSnapshot; + const buildDeepTurnPackagingInputSafe = input.buildDeepTurnPackagingInputFn ?? assistantDeepTurnInputBuilder_1.buildAssistantDeepTurnPackagingInput; + const assembleDeepTurnPackagingSafe = input.assembleDeepTurnPackagingFn ?? assistantDeepTurnPackaging_1.assembleAssistantDeepTurnPackaging; + const deepTurnPrePackagingContext = buildPrePackagingContextSafe({ + normalizedPayload: input.normalized.normalized, + routeSummary: input.routeSummary, + runtimeAnalysisContext: input.runtimeAnalysisContext, + assistantReply: input.composition.assistant_reply, + extractDroppedIntentSegments: input.extractDroppedIntentSegments, + buildDebugRoutes: input.buildDebugRoutes, + extractExecutionState: input.extractExecutionState, + sanitizeReply: input.sanitizeReply + }); + const investigationStateSnapshot = buildInvestigationStateSnapshotSafe({ + featureEnabled: input.featureInvestigationStateV1, + previousState: input.previousInvestigationState, + timestamp: input.nowIso ? input.nowIso() : new Date().toISOString(), + questionId: input.questionId, + userMessage: input.userMessage, + routeSummary: input.routeSummary, + requirements: input.coverageEvaluationRequirements, + coverageReport: input.coverageReport, + retrievalResults: input.retrievalResults, + replyType: input.composition.reply_type, + followupApplied: input.followupApplied + }); + persistInvestigationStateSnapshotSafe({ + featureEnabled: input.featureInvestigationStateV1, + sessionId: input.sessionId, + snapshot: investigationStateSnapshot, + persist: input.persistInvestigationState + }); + const messageId = input.messageIdFactory ? input.messageIdFactory() : `msg-${(0, nanoid_1.nanoid)(10)}`; + const deepTurnPackagingInput = buildDeepTurnPackagingInputSafe({ + sessionId: input.sessionId, + messageId, + userMessage: input.userMessage, + normalized: input.normalized, + normalizedQuestion: input.normalizedQuestion, + routeSummary: input.routeSummary, + droppedIntentSegments: deepTurnPrePackagingContext.droppedIntentSegments, + analysisContextForContract: deepTurnPrePackagingContext.analysisContextForContract, + executionPlan: input.executionPlan, + requirementExtractionRequirements: input.requirementExtractionRequirements, + coverageEvaluationRequirements: input.coverageEvaluationRequirements, + coverageReport: input.coverageReport, + groundingCheck: input.groundingCheck, + retrievalCalls: input.retrievalCalls, + retrievalResultsRaw: input.retrievalResultsRaw, + retrievalResults: input.retrievalResults, + routesForDebug: deepTurnPrePackagingContext.routesForDebug, + resolvedExecutionState: deepTurnPrePackagingContext.resolvedExecutionState, + questionTypeClass: input.questionTypeClass, + companyAnchors: input.companyAnchors, + runtimeAnalysisContext: input.runtimeAnalysisContext, + businessScopeResolution: input.businessScopeResolution, + temporalGuard: input.temporalGuard, + polarityAudit: input.polarityAudit, + claimAnchorAudit: input.claimAnchorAudit, + targetedEvidenceAudit: input.targetedEvidenceAudit, + evidenceAdmissibilityGateAudit: input.evidenceAdmissibilityGateAudit, + rbpLiveRouteAudit: input.rbpLiveRouteAudit, + faLiveRouteAudit: input.faLiveRouteAudit, + groundedAnswerEligibilityGuard: input.groundedAnswerEligibilityGuard, + followupStateUsage: input.followupStateUsage, + composition: { + reply_type: input.composition.reply_type, + fallback_type: input.composition.fallback_type, + answer_structure_v11: input.composition.answer_structure_v11, + problem_centric_answer_applied: input.composition.problem_centric_answer_applied, + problem_units_used_count: input.composition.problem_units_used_count, + problem_answer_mode: input.composition.problem_answer_mode, + problem_unit_ids_used: input.composition.problem_unit_ids_used + }, + safeAssistantReplyBase: deepTurnPrePackagingContext.safeAssistantReplyBase, + featureContractsV11: input.featureContractsV11, + featureAnswerPolicyV11: input.featureAnswerPolicyV11, + investigationStateSnapshot, + addressRuntimeMetaForDeep: input.addressRuntimeMetaForDeep + }); + const deepTurnPackaging = assembleDeepTurnPackagingSafe(deepTurnPackagingInput); + return { + messageId, + investigationStateSnapshot, + droppedIntentSegments: deepTurnPrePackagingContext.droppedIntentSegments, + analysisContextForContract: deepTurnPrePackagingContext.analysisContextForContract, + routesForDebug: deepTurnPrePackagingContext.routesForDebug, + resolvedExecutionState: deepTurnPrePackagingContext.resolvedExecutionState, + safeAssistantReplyBase: deepTurnPrePackagingContext.safeAssistantReplyBase, + safeAssistantReply: deepTurnPackaging.deepAnswerArtifacts.safeAssistantReply, + debug: deepTurnPackaging.debug, + assistantItem: deepTurnPackaging.assistantItem, + deepAnalysisLogDetails: deepTurnPackaging.deepAnalysisLogDetails + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnPlanRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantDeepTurnPlanRuntimeAdapter.js new file mode 100644 index 0000000..ab4ca89 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnPlanRuntimeAdapter.js @@ -0,0 +1,27 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantDeepTurnExecutionPlan = buildAssistantDeepTurnExecutionPlan; +function buildAssistantDeepTurnExecutionPlan(input) { + const requirementExtraction = input.extractRequirements(input.routeSummary, input.normalizedPayload, input.userMessage); + let executionPlan = input.toExecutionPlan(input.routeSummary, input.normalizedPayload, input.userMessage, requirementExtraction.byFragment); + const rbpRoutePlanEnforcement = input.enforceRbpLiveRoutePlan({ + executionPlan, + claimType: input.claimType, + temporalGuard: input.temporalGuard + }); + executionPlan = rbpRoutePlanEnforcement.executionPlan; + const faRoutePlanEnforcement = input.enforceFaLiveRoutePlan({ + executionPlan, + claimType: input.claimType, + temporalGuard: input.temporalGuard + }); + executionPlan = faRoutePlanEnforcement.executionPlan; + executionPlan = input.applyTemporalHintToExecutionPlan(executionPlan, input.temporalGuard); + executionPlan = input.applyPolarityHintToExecutionPlan(executionPlan, input.domainPolarityGuardInitial); + return { + requirementExtraction, + executionPlan, + rbpRoutePlanEnforcement, + faRoutePlanEnforcement + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnPrePackagingContext.js b/llm_normalizer/backend/dist/services/assistantDeepTurnPrePackagingContext.js new file mode 100644 index 0000000..aec0702 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnPrePackagingContext.js @@ -0,0 +1,20 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantDeepTurnPrePackagingContext = buildAssistantDeepTurnPrePackagingContext; +function buildAssistantDeepTurnPrePackagingContext(input) { + return { + droppedIntentSegments: input.extractDroppedIntentSegments(input.normalizedPayload), + analysisContextForContract: input.runtimeAnalysisContext.active + ? { + as_of_date: input.runtimeAnalysisContext.as_of_date, + period_from: input.runtimeAnalysisContext.period_from, + period_to: input.runtimeAnalysisContext.period_to, + source: input.runtimeAnalysisContext.source, + snapshot_mode: input.runtimeAnalysisContext.snapshot_mode + } + : null, + routesForDebug: input.buildDebugRoutes(input.routeSummary), + resolvedExecutionState: input.extractExecutionState(input.normalizedPayload), + safeAssistantReplyBase: input.sanitizeReply(input.assistantReply, "Нужны уточнения для надежного ответа.") + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnResponseBuilder.js b/llm_normalizer/backend/dist/services/assistantDeepTurnResponseBuilder.js new file mode 100644 index 0000000..36cb793 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnResponseBuilder.js @@ -0,0 +1,14 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantDeepTurnSuccessResponse = buildAssistantDeepTurnSuccessResponse; +function buildAssistantDeepTurnSuccessResponse(input) { + return { + ok: true, + session_id: input.sessionId, + assistant_reply: input.assistantReply, + reply_type: input.replyType, + conversation_item: input.conversationItem, + debug: input.debug, + conversation: input.conversation + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantDeepTurnRetrievalRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantDeepTurnRetrievalRuntimeAdapter.js new file mode 100644 index 0000000..5dfe69f --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantDeepTurnRetrievalRuntimeAdapter.js @@ -0,0 +1,78 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.executeAssistantDeepTurnRetrievalPlan = executeAssistantDeepTurnRetrievalPlan; +const retrievalResultNormalizer_1 = require("./retrievalResultNormalizer"); +function buildRouteExecutorErrorRawResult(route, message) { + return { + status: "error", + result_type: "summary", + items: [], + summary: { + route + }, + evidence: [], + why_included: [], + selection_reason: [], + risk_factors: [], + business_interpretation: [], + confidence: "low", + limitations: ["Route executor failed."], + errors: [message] + }; +} +async function executeAssistantDeepTurnRetrievalPlan(input) { + const normalizeRetrievalResultSafe = input.normalizeRetrievalResultFn ?? retrievalResultNormalizer_1.normalizeRetrievalResult; + const retrievalCalls = []; + const retrievalResultsRaw = []; + const retrievalResults = []; + for (const planItem of input.executionPlan) { + if (!planItem.should_execute) { + retrievalCalls.push({ + fragment_id: planItem.fragment_id, + requirement_ids: planItem.requirement_ids, + route: planItem.route, + status: "skipped", + query_text: planItem.fragment_text, + reason: input.mapNoRouteReason(planItem.no_route_reason) + }); + retrievalResults.push(input.buildSkippedResult(planItem)); + continue; + } + retrievalCalls.push({ + fragment_id: planItem.fragment_id, + requirement_ids: planItem.requirement_ids, + route: planItem.route, + status: "executed", + query_text: planItem.fragment_text, + reason: null + }); + try { + const raw = await input.executeRouteRuntime(planItem.route, planItem.fragment_text, { + temporalHint: input.liveTemporalHint + }); + retrievalResultsRaw.push({ + fragment_id: planItem.fragment_id, + route: planItem.route, + raw_result: raw + }); + retrievalResults.push(normalizeRetrievalResultSafe(planItem.fragment_id, planItem.requirement_ids, planItem.route, raw)); + } + catch (error) { + const message = error instanceof Error ? error.message : String(error); + retrievalCalls[retrievalCalls.length - 1].status = "failed"; + retrievalCalls[retrievalCalls.length - 1].reason = message; + const rawError = buildRouteExecutorErrorRawResult(planItem.route, message); + retrievalResultsRaw.push({ + fragment_id: planItem.fragment_id, + route: planItem.route, + raw_result: rawError + }); + retrievalResults.push(normalizeRetrievalResultSafe(planItem.fragment_id, planItem.requirement_ids, planItem.route, rawError)); + } + } + return { + retrievalCalls, + retrievalResultsRaw, + retrievalResults + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantEvidenceBundleAssembler.js b/llm_normalizer/backend/dist/services/assistantEvidenceBundleAssembler.js new file mode 100644 index 0000000..4db42c3 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantEvidenceBundleAssembler.js @@ -0,0 +1,23 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.assembleAssistantEvidenceBundle = assembleAssistantEvidenceBundle; +const assistantOrchestrationContracts_1 = require("./assistantOrchestrationContracts"); +function buildRetrievalStatus(retrievalResults) { + return retrievalResults.map((item) => ({ + fragment_id: item.fragment_id, + requirement_ids: item.requirement_ids, + route: item.route, + status: item.status, + result_type: item.result_type + })); +} +function assembleAssistantEvidenceBundle(input) { + const retrievalResults = Array.isArray(input.retrievalResults) ? input.retrievalResults : []; + return { + evidenceBundleContractV1: (0, assistantOrchestrationContracts_1.buildAssistantEvidenceBundleContractV1)({ + retrievalCalls: Array.isArray(input.retrievalCalls) ? input.retrievalCalls : [], + retrievalResults + }), + retrievalStatus: buildRetrievalStatus(retrievalResults) + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantInvestigationStateRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantInvestigationStateRuntimeAdapter.js new file mode 100644 index 0000000..4b5b495 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantInvestigationStateRuntimeAdapter.js @@ -0,0 +1,29 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildAssistantInvestigationStateSnapshot = buildAssistantInvestigationStateSnapshot; +exports.persistAssistantInvestigationStateSnapshot = persistAssistantInvestigationStateSnapshot; +const investigationState_1 = require("./investigationState"); +function buildAssistantInvestigationStateSnapshot(input) { + if (!input.featureEnabled || !input.previousState) { + return null; + } + return (0, investigationState_1.updateInvestigationState)({ + previous: input.previousState, + timestamp: input.timestamp, + questionId: input.questionId, + userMessage: input.userMessage, + routeSummary: input.routeSummary, + requirements: input.requirements, + coverageReport: input.coverageReport, + retrievalResults: input.retrievalResults, + replyType: input.replyType, + followupApplied: input.followupApplied + }); +} +function persistAssistantInvestigationStateSnapshot(input) { + if (!input.featureEnabled || !input.snapshot) { + return false; + } + input.persist(input.sessionId, input.snapshot); + return true; +} diff --git a/llm_normalizer/backend/dist/services/assistantMessageLogAssembler.js b/llm_normalizer/backend/dist/services/assistantMessageLogAssembler.js new file mode 100644 index 0000000..28e113e --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantMessageLogAssembler.js @@ -0,0 +1,102 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildDeepAnalysisProcessedLogDetails = buildDeepAnalysisProcessedLogDetails; +function toAnalysisContext(input) { + if (!input.active) { + return null; + } + return { + as_of_date: input.as_of_date, + period_from: input.period_from, + period_to: input.period_to, + source: input.source, + snapshot_mode: input.snapshot_mode + }; +} +function resolveCoverageStatus(coverageReport) { + return coverageReport.requirements_total === coverageReport.requirements_covered && + coverageReport.requirements_uncovered.length === 0 && + coverageReport.requirements_partially_covered.length === 0 + ? "full" + : "partial_or_limited"; +} +function buildDeepAnalysisProcessedLogDetails(input) { + const analysisContext = toAnalysisContext(input.runtimeAnalysisContext); + return { + session_id: input.sessionId, + message_id: input.messageId, + user_message: input.userMessage, + normalizer_output: input.normalizerOutput, + execution_plan: input.executionPlan, + resolved_execution_state: input.resolvedExecutionState, + routes: input.routes, + retrieval_calls: input.retrievalCalls, + retrieval_results_raw: input.retrievalResultsRaw, + retrieval_results_normalized: input.retrievalResultsNormalized, + requirements_extracted: input.requirementsExtracted, + requirements_total: input.coverageReport.requirements_total, + requirements_covered: input.coverageReport.requirements_covered, + requirements_uncovered: input.coverageReport.requirements_uncovered, + coverage_status: resolveCoverageStatus(input.coverageReport), + answer_grounding_status: input.groundingCheck.status, + reply_semantic_type: input.replyType, + why_included_summary: input.groundingCheck.why_included_summary, + selection_reason_summary: input.groundingCheck.selection_reason_summary, + route_subject_match: input.groundingCheck.route_subject_match, + clarification_target: input.coverageReport.clarification_needed_for, + dropped_intent_segments: input.droppedIntentSegments, + question_type_class: input.questionTypeClass, + company_anchors: input.companyAnchors, + analysis_context_applied: input.runtimeAnalysisContext.active, + analysis_context: analysisContext, + business_scope_raw: input.businessScopeResolution.business_scope_raw, + business_scope_resolved: input.businessScopeResolution.business_scope_resolved, + company_grounding_applied: input.businessScopeResolution.company_grounding_applied, + scope_resolution_reason: input.businessScopeResolution.scope_resolution_reason, + company_scope_resolution_reason: input.businessScopeResolution.scope_resolution_reason, + raw_time_anchor: input.temporalGuard.raw_time_anchor, + raw_time_scope: input.temporalGuard.raw_time_scope, + resolved_time_anchor: input.temporalGuard.resolved_time_anchor, + resolved_primary_period: input.temporalGuard.resolved_primary_period, + effective_primary_period: input.temporalGuard.effective_primary_period, + temporal_guard_input: input.temporalGuard.temporal_guard_input, + temporal_alignment_status: input.temporalGuard.temporal_alignment_status, + temporal_resolution_source: input.temporalGuard.temporal_resolution_source, + temporal_guard_basis: input.temporalGuard.temporal_guard_basis, + temporal_guard_applied: input.temporalGuard.temporal_guard_applied, + temporal_guard_outcome: input.temporalGuard.temporal_guard_outcome, + temporal_guard: input.temporalGuard, + raw_numeric_tokens: input.polarityAudit.raw_numeric_tokens, + classified_numeric_tokens: input.polarityAudit.classified_numeric_tokens, + rejected_as_non_accounts: input.polarityAudit.rejected_as_non_accounts, + resolved_account_anchors: input.polarityAudit.resolved_account_anchors, + domain_polarity_guard: input.polarityAudit, + claim_anchor_audit: input.claimAnchorAudit, + settlement_role: input.claimAnchorAudit.settlement_role ?? null, + settlement_role_resolution_reason: input.claimAnchorAudit.settlement_role_resolution_reason ?? [], + polarity_resolution_status: input.claimAnchorAudit.polarity_resolution_status ?? "not_applicable", + targeted_evidence_acquisition: input.targetedEvidenceAudit, + evidence_admissibility_gate: input.evidenceAdmissibilityGateAudit, + ...(input.rbpLiveRouteAudit ? { rbp_live_route_audit: input.rbpLiveRouteAudit } : {}), + ...(input.faLiveRouteAudit ? { fa_live_route_audit: input.faLiveRouteAudit } : {}), + eligibility_time_basis: input.groundedAnswerEligibilityGuard.eligibility_time_basis, + grounded_answer_eligibility_guard: input.groundedAnswerEligibilityGuard, + ...(input.followupStateUsage ? { followup_state_usage: input.followupStateUsage } : {}), + problem_centric_answer_applied: input.compositionDebug.problem_centric_answer_applied ?? false, + problem_units_used_count: input.compositionDebug.problem_units_used_count ?? 0, + problem_answer_mode: input.compositionDebug.problem_answer_mode ?? "stage1_policy_v11", + ...(Array.isArray(input.compositionDebug.problem_unit_ids_used) && input.compositionDebug.problem_unit_ids_used.length > 0 + ? { + problem_unit_ids_used: input.compositionDebug.problem_unit_ids_used + } + : {}), + assistant_outcome_class_v1: input.outcomeClassV1, + assistant_orchestration_contracts_v1: input.assistantOrchestrationContractsV1, + answer_structure_v11: input.answerStructureV11, + investigation_state_snapshot: input.investigationStateSnapshot, + fallback_type: input.compositionDebug.fallback_type, + assistant_reply: input.assistantReply, + reply_type: input.replyType, + trace_id: input.traceId + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantOrchestrationContracts.js b/llm_normalizer/backend/dist/services/assistantOrchestrationContracts.js new file mode 100644 index 0000000..7c0b9e1 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantOrchestrationContracts.js @@ -0,0 +1,148 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.classifyAssistantOutcomeClassV1 = classifyAssistantOutcomeClassV1; +exports.buildAssistantQueryFrameContractV1 = buildAssistantQueryFrameContractV1; +exports.buildAssistantExecutionPlanContractV1 = buildAssistantExecutionPlanContractV1; +exports.buildAssistantEvidenceBundleContractV1 = buildAssistantEvidenceBundleContractV1; +exports.buildAssistantCoverageContractV1 = buildAssistantCoverageContractV1; +function normalizeSnapshotMode(value) { + const token = String(value ?? "").trim(); + if (token === "force_snapshot" || token === "force_live") { + return token; + } + return "auto"; +} +function extractFragmentsTotal(normalized) { + if (!normalized || typeof normalized !== "object") { + return 0; + } + const source = normalized; + const fragments = source.fragments; + return Array.isArray(fragments) ? fragments.length : 0; +} +function collectEvidenceTotals(retrievalResults) { + let evidenceTotal = 0; + const sourceRefs = new Set(); + let limitationTotal = 0; + let errorTotal = 0; + for (const result of retrievalResults) { + evidenceTotal += Array.isArray(result.evidence) ? result.evidence.length : 0; + limitationTotal += Array.isArray(result.limitations) ? result.limitations.length : 0; + errorTotal += Array.isArray(result.errors) ? result.errors.length : 0; + for (const evidence of result.evidence ?? []) { + const ref = String(evidence?.source_ref?.canonical_ref ?? "").trim(); + if (ref) { + sourceRefs.add(ref); + } + } + } + return { + evidence_total: evidenceTotal, + source_refs_total: sourceRefs.size, + limitation_total: limitationTotal, + error_total: errorTotal + }; +} +function classifyAssistantOutcomeClassV1(input) { + const replyType = input.replyType; + const grounding = input.grounding; + const coverage = input.coverageReport; + const hasOnlyErrors = input.retrievalResults.length > 0 && + input.retrievalResults.every((item) => item.status === "error"); + if (replyType === "backend_error" || hasOnlyErrors) { + return "BLOCKED_BY_TOOLING"; + } + if (grounding.status === "route_mismatch_blocked" || replyType === "route_mismatch_blocked") { + return "MISROUTED"; + } + if (replyType === "clarification_required" || coverage.clarification_needed_for.length > 0) { + return "BLOCKED_BY_AMBIGUITY"; + } + if (replyType === "out_of_scope") { + return "BLOCKED_BY_AMBIGUITY"; + } + const fullCoverage = coverage.requirements_total > 0 && + coverage.requirements_total === coverage.requirements_covered && + coverage.requirements_uncovered.length === 0 && + coverage.requirements_partially_covered.length === 0 && + coverage.clarification_needed_for.length === 0 && + coverage.out_of_scope_requirements.length === 0; + if (fullCoverage && grounding.status === "grounded") { + return "FULLY_ANSWERED"; + } + const hasAnyCoverage = coverage.requirements_covered > 0 || + coverage.requirements_partially_covered.length > 0 || + grounding.status === "partial"; + if (hasAnyCoverage) { + return "PARTIALLY_ANSWERED"; + } + const missingRequirementSignal = grounding.missing_requirements.length > 0 || + coverage.requirements_uncovered.length > 0 || + coverage.requirements_total > 0; + const possibleBindingFailure = replyType === "no_grounded_answer" && + missingRequirementSignal && + grounding.route_subject_match; + if (possibleBindingFailure) { + return "FAILED_TO_BIND_ENTITIES"; + } + return "BLOCKED_BY_MISSING_DATA"; +} +function buildAssistantQueryFrameContractV1(input) { + const analysis = input.analysisContext + ? { + as_of_date: input.analysisContext.as_of_date ?? null, + period_from: input.analysisContext.period_from ?? null, + period_to: input.analysisContext.period_to ?? null, + source: input.analysisContext.source ?? null, + snapshot_mode: normalizeSnapshotMode(input.analysisContext.snapshot_mode) + } + : null; + return { + schema_version: "assistant_query_frame_v1", + original_user_question: String(input.userMessage ?? ""), + normalized_question: String(input.normalizedQuestion ?? ""), + route_summary_mode: input.routeSummary?.mode ?? "none", + fragments_total: extractFragmentsTotal(input.normalized), + dropped_intent_segments: Array.isArray(input.droppedIntentSegments) ? [...input.droppedIntentSegments] : [], + analysis_context: analysis + }; +} +function buildAssistantExecutionPlanContractV1(input) { + return { + schema_version: "assistant_execution_plan_v1", + steps: (Array.isArray(input.executionPlan) ? input.executionPlan : []).map((item) => ({ + fragment_id: String(item.fragment_id ?? ""), + route: String(item.route ?? ""), + should_execute: Boolean(item.should_execute), + requirement_ids: Array.isArray(item.requirement_ids) ? [...item.requirement_ids] : [], + no_route_reason: item.no_route_reason ?? null, + clarification_reason: item.clarification_reason ?? null + })), + requirements_total: Array.isArray(input.requirements) ? input.requirements.length : 0 + }; +} +function buildAssistantEvidenceBundleContractV1(input) { + const retrievalResults = Array.isArray(input.retrievalResults) ? input.retrievalResults : []; + const breakdown = { + ok: retrievalResults.filter((item) => item.status === "ok").length, + partial: retrievalResults.filter((item) => item.status === "partial").length, + empty: retrievalResults.filter((item) => item.status === "empty").length, + error: retrievalResults.filter((item) => item.status === "error").length + }; + const totals = collectEvidenceTotals(retrievalResults); + return { + schema_version: "assistant_evidence_bundle_v1", + retrieval_calls_total: Array.isArray(input.retrievalCalls) ? input.retrievalCalls.length : 0, + retrieval_results_total: retrievalResults.length, + retrieval_status_breakdown: breakdown, + ...totals + }; +} +function buildAssistantCoverageContractV1(input) { + return { + schema_version: "assistant_coverage_contract_v1", + coverage_report: input.coverageReport, + grounding: input.grounding, + outcome_class: input.outcomeClass + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantOrchestrationRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantOrchestrationRuntimeAdapter.js new file mode 100644 index 0000000..efead7b --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantOrchestrationRuntimeAdapter.js @@ -0,0 +1,13 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.runAssistantCoverageGroundingPipeline = runAssistantCoverageGroundingPipeline; +function runAssistantCoverageGroundingPipeline(input) { + const requirementExtraction = input.requirementExtraction ?? input.extractRequirements(input.routeSummary, input.normalized, input.userMessage); + const coverageEvaluation = input.evaluateCoverage(requirementExtraction.requirements, input.retrievalResults); + const groundingCheckBase = input.checkGrounding(input.userMessage, coverageEvaluation.requirements, coverageEvaluation.coverage, input.retrievalResults); + return { + requirementExtraction, + coverageEvaluation, + groundingCheckBase + }; +} diff --git a/llm_normalizer/backend/dist/services/assistantQueryPlanning.js b/llm_normalizer/backend/dist/services/assistantQueryPlanning.js new file mode 100644 index 0000000..41361d3 --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantQueryPlanning.js @@ -0,0 +1,106 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.buildFragmentTextById = buildFragmentTextById; +exports.buildExecutionPlanFromRoute = buildExecutionPlanFromRoute; +exports.buildDebugRoutesFromRoute = buildDebugRoutesFromRoute; +function escapeRegex(value) { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} +function enrichFragmentTextWithHints(fragment, text) { + const baseText = String(text ?? "").trim(); + const accountHints = Array.isArray(fragment.account_hints) + ? Array.from(new Set(fragment.account_hints.map((item) => String(item ?? "").trim()).filter((item) => item.length > 0))) + : []; + if (accountHints.length === 0) { + return baseText; + } + const hasAccountInText = accountHints.some((account) => new RegExp(`\\b${escapeRegex(account)}\\b`, "i").test(baseText)); + if (hasAccountInText) { + return baseText; + } + return `${baseText}, по счету ${accountHints.join(", ")}`; +} +function buildFragmentTextById(fragments) { + const result = new Map(); + for (const item of fragments) { + if (!item || typeof item !== "object") { + continue; + } + const fragment = item; + const fragmentId = typeof fragment.fragment_id === "string" ? fragment.fragment_id : ""; + if (!fragmentId) { + continue; + } + const text = (typeof fragment.raw_fragment_text === "string" && fragment.raw_fragment_text.trim()) || + (typeof fragment.normalized_fragment_text === "string" && fragment.normalized_fragment_text.trim()) || + ""; + result.set(fragmentId, enrichFragmentTextWithHints(fragment, text)); + } + return result; +} +function buildExecutionPlanFromRoute(input) { + if (!input.routeSummary) { + return []; + } + if (input.routeSummary.mode === "legacy_v1") { + return [ + { + fragment_id: "F1", + requirement_ids: input.requirementByFragment.get("F1") ?? ["R1"], + route: input.routeSummary.route_hint, + should_execute: true, + fragment_text: input.userMessage, + no_route_reason: null, + clarification_reason: null + } + ]; + } + return input.routeSummary.decisions.map((decision) => { + const text = input.fragmentTextById.get(decision.fragment_id) ?? input.userMessage; + if (decision.route === "no_route") { + return { + fragment_id: decision.fragment_id, + requirement_ids: input.requirementByFragment.get(decision.fragment_id) ?? [], + route: "no_route", + should_execute: false, + fragment_text: text, + no_route_reason: decision.no_route_reason ?? null, + clarification_reason: decision.clarification_reason ?? null + }; + } + return { + fragment_id: decision.fragment_id, + requirement_ids: input.requirementByFragment.get(decision.fragment_id) ?? [], + route: decision.route, + should_execute: true, + fragment_text: text, + no_route_reason: null, + clarification_reason: decision.clarification_reason ?? null + }; + }); +} +function buildDebugRoutesFromRoute(input) { + if (!input.routeSummary) { + return []; + } + if (input.routeSummary.mode === "legacy_v1") { + return [ + { + fragment_id: "F1", + route: input.routeSummary.route_hint, + reason: input.resolveLegacyRouteReason(input.routeSummary.route_hint), + confidence: input.routeSummary.confidence, + intent_class: input.routeSummary.intent_class + } + ]; + } + return input.routeSummary.decisions.map((decision) => ({ + fragment_id: decision.fragment_id, + route: decision.route, + reason: decision.reason, + route_status: decision.route_status ?? null, + no_route_reason: decision.no_route_reason ?? null, + clarification_reason: decision.clarification_reason ?? null, + execution_readiness: decision.execution_readiness ?? null + })); +} diff --git a/llm_normalizer/backend/dist/services/assistantRuntimeGuards.js b/llm_normalizer/backend/dist/services/assistantRuntimeGuards.js index 0c398aa..cb88959 100644 --- a/llm_normalizer/backend/dist/services/assistantRuntimeGuards.js +++ b/llm_normalizer/backend/dist/services/assistantRuntimeGuards.js @@ -584,7 +584,78 @@ function toTemporalGuardInput(window, fallback) { const value = String(fallback ?? "").trim(); return value || null; } +function normalizeIsoDate(value) { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!match) { + return null; + } + const year = Number(match[1]); + const month = Number(match[2]); + const day = Number(match[3]); + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + return null; + } + const candidate = new Date(Date.UTC(year, month - 1, day)); + if (candidate.getUTCFullYear() !== year || + candidate.getUTCMonth() + 1 !== month || + candidate.getUTCDate() !== day) { + return null; + } + return `${match[1]}-${match[2]}-${match[3]}`; +} +function normalizeTemporalWindow(input) { + const asOfDate = normalizeIsoDate(input.asOfDate); + if (asOfDate) { + return { + from: asOfDate, + to: asOfDate, + granularity: "day" + }; + } + const from = normalizeIsoDate(input.periodFrom); + const to = normalizeIsoDate(input.periodTo); + if (!from || !to) { + return null; + } + return { + from, + to, + granularity: from === to ? "day" : "month" + }; +} function resolveTemporalGuard(input) { + const analysisWindow = normalizeTemporalWindow({ + asOfDate: input.analysisContext?.as_of_date, + periodFrom: input.analysisContext?.period_from, + periodTo: input.analysisContext?.period_to + }); + if (analysisWindow) { + const source = String(input.analysisContext?.source ?? "").trim() || "analysis_context"; + const guardInput = toTemporalGuardInput(analysisWindow, analysisWindow.from); + return { + raw_time_anchor: analysisWindow.from, + raw_time_scope: guardInput, + resolved_time_anchor: analysisWindow.granularity === "day" ? analysisWindow.from : null, + resolved_primary_period: analysisWindow, + effective_primary_period: analysisWindow, + temporal_guard_input: guardInput, + temporal_alignment_status: "aligned", + temporal_resolution_source: source, + temporal_guard_basis: "raw_time_scope_unlocked", + temporal_guard_applied: false, + temporal_guard_outcome: "passed", + primary_period_window: null, + allowed_context_window: null, + controlled_temporal_expansion_enabled: false, + context_expansion_reasons_allowed: ["prehistory", "carryover", "post_period_closure", "long_running_contract_context"], + normalized_anchor_drift_detected: false, + reason_codes: ["analysis_context_applied"] + }; + } const rawAnchorText = collectRawTemporalAnchorText(input.userMessage, input.companyAnchors); const julyAnchor = resolveJulyAnchor(rawAnchorText); const normalizedAnchor = normalizedAnchorFromFragments(input.normalized); @@ -654,9 +725,14 @@ function applyTemporalHintToExecutionPlan(executionPlan, temporal) { return executionPlan; } const primaryWindow = temporal.effective_primary_period ?? temporal.primary_period_window; + const periodLabel = primaryWindow + ? `${primaryWindow.from}..${primaryWindow.to}` + : temporal.resolved_time_anchor + ? temporal.resolved_time_anchor + : "active_period"; const hint = primaryWindow?.granularity === "day" && temporal.resolved_time_anchor ? `primary period ${temporal.resolved_time_anchor}; controlled temporal expansion only for linked entities` - : `primary period July 2020 (${primaryWindow?.from ?? JULY_WINDOW.from}..${primaryWindow?.to ?? JULY_WINDOW.to}); controlled temporal expansion only for linked entities`; + : `primary period ${periodLabel}; controlled temporal expansion only for linked entities`; return executionPlan.map((item) => { if (!item.should_execute) { return item; @@ -1319,15 +1395,15 @@ function applyEligibilityToGroundingCheck(groundingCheck, eligibility) { ? "no_grounded_answer" : "partial"; const reasonMap = { - admissible_evidence_count_zero: "Недостаточно допустимого evidence для обоснованного ответа.", - critical_domain_or_account_contradiction: "Есть критическое противоречие по domain/account scope.", - temporal_guard_failed_out_of_snapshot_window: "Temporal anchor вышел за окно company snapshot (июль 2020).", - temporal_guard_ambiguous_limited: "Temporal anchor не разрешен надежно в пределах company snapshot.", - business_scope_generic_unresolved: "Business scope остался generic и не подтвержден как company-specific для доказательного ответа.", - polarity_guard_limited_unresolved_polarity: "Не удалось надежно определить supplier/customer polarity.", - polarity_guard_blocked_conflict: "Обнаружен конфликт supplier/customer polarity в retrieval-контуре.", - claim_anchor_coverage_insufficient: "Недостаточно покрытия required anchors для claim-bound grounding.", - targeted_evidence_hit_rate_zero: "Targeted evidence acquisition не дал допустимых попаданий по claim target path." + admissible_evidence_count_zero: "Недостаточно подтвержденных данных для уверенного ответа.", + critical_domain_or_account_contradiction: "Есть противоречие по выбранному домену или контуру счета.", + temporal_guard_failed_out_of_snapshot_window: "Запрошенный период выходит за доступный срез данных.", + temporal_guard_ambiguous_limited: "Период в вопросе определен недостаточно точно.", + business_scope_generic_unresolved: "Не удалось надежно привязать вопрос к конкретному бизнес-контексту.", + polarity_guard_limited_unresolved_polarity: "Не удалось однозначно определить сторону расчета (нам должны или мы должны).", + polarity_guard_blocked_conflict: "В данных есть конфликт по стороне расчета.", + claim_anchor_coverage_insufficient: "Не хватает ключевых ориентиров в вопросе (период, объект или контрагент).", + targeted_evidence_hit_rate_zero: "Не хватило целевых подтверждений по выбранному сценарию." }; const reasons = [ ...(Array.isArray(groundingCheck.reasons) ? groundingCheck.reasons : []), diff --git a/llm_normalizer/backend/dist/services/assistantService.js b/llm_normalizer/backend/dist/services/assistantService.js index 3b855ff..0037054 100644 --- a/llm_normalizer/backend/dist/services/assistantService.js +++ b/llm_normalizer/backend/dist/services/assistantService.js @@ -46,7 +46,6 @@ exports.resolveOrganizationNamesByRefsForTests = resolveOrganizationNamesByRefsF exports.resolveLivingAssistantModeDecision = resolveLivingAssistantModeDecision; // @ts-nocheck const nanoid_1 = __importStar(require("nanoid")); -const stage1Contracts_1 = __importStar(require("../types/stage1Contracts")); const config_1 = __importStar(require("../config")); const log_1 = __importStar(require("../utils/log")); const answerComposer_1 = __importStar(require("./answerComposer")); @@ -54,7 +53,6 @@ const assistantDataLayer_1 = __importStar(require("./assistantDataLayer")); const assistantSessionLogger_1 = __importStar(require("./assistantSessionLogger")); const investigationState_1 = __importStar(require("./investigationState")); const retrievalResultNormalizer_1 = __importStar(require("./retrievalResultNormalizer")); -const questionTypeResolver_1 = __importStar(require("./questionTypeResolver")); const companyAnchorResolver_1 = __importStar(require("./companyAnchorResolver")); const assistantRuntimeGuards_1 = __importStar(require("./assistantRuntimeGuards")); const assistantClaimBoundEvidence_1 = __importStar(require("./assistantClaimBoundEvidence")); @@ -67,6 +65,17 @@ const openaiResponsesClient_1 = __importStar(require("./openaiResponsesClient")) const addressMcpClient_1 = __importStar(require("./addressMcpClient")); const capabilitiesRegistry_1 = __importStar(require("./capabilitiesRegistry")); const assistantCanon_1 = __importStar(require("./assistantCanon")); +const assistantCoverageGrounding_1 = __importStar(require("./assistantCoverageGrounding")); +const assistantDeepTurnResponseBuilder_1 = __importStar(require("./assistantDeepTurnResponseBuilder")); +const assistantDeepTurnCompositionRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnCompositionRuntimeAdapter")); +const assistantDeepTurnContextRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnContextRuntimeAdapter")); +const assistantDeepTurnGuardRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnGuardRuntimeAdapter")); +const assistantDeepTurnGroundingRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnGroundingRuntimeAdapter")); +const assistantDeepTurnPackagingRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnPackagingRuntimeAdapter")); +const assistantDeepTurnPlanRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnPlanRuntimeAdapter")); +const assistantDeepTurnRetrievalRuntimeAdapter_1 = __importStar(require("./assistantDeepTurnRetrievalRuntimeAdapter")); +const assistantQueryPlanning_1 = __importStar(require("./assistantQueryPlanning")); +const assistantTurnCommitRuntimeAdapter_1 = __importStar(require("./assistantTurnCommitRuntimeAdapter")); const iconv_lite_1 = __importDefault(require("iconv-lite")); const DATA_SCOPE_CACHE_TTL_MS = 60_000; const dataScopeProbeCache = new Map(); @@ -125,6 +134,46 @@ function hasExplicitPeriodAnchorFromNormalized(normalized) { } return false; } +function normalizeIsoDate(value) { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!match) { + return null; + } + const year = Number(match[1]); + const month = Number(match[2]); + const day = Number(match[3]); + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + return null; + } + const candidate = new Date(Date.UTC(year, month - 1, day)); + if (candidate.getUTCFullYear() !== year || candidate.getUTCMonth() + 1 !== month || candidate.getUTCDate() !== day) { + return null; + } + return `${match[1]}-${match[2]}-${match[3]}`; +} +function resolveRuntimeAnalysisContext(context) { + const input = context && typeof context === "object" ? context : {}; + const analysis = input.analysis_context && typeof input.analysis_context === "object" ? input.analysis_context : {}; + const asOfDate = normalizeIsoDate(analysis.as_of_date) ?? normalizeIsoDate(input.period_hint); + const periodFrom = normalizeIsoDate(analysis.period_from); + const periodTo = normalizeIsoDate(analysis.period_to); + const sourceRaw = typeof analysis.source === "string" ? analysis.source.trim() : ""; + const source = sourceRaw || (asOfDate ? "period_hint_compat" : null); + const snapshotModeRaw = typeof analysis.snapshot_mode === "string" ? analysis.snapshot_mode.trim() : ""; + const snapshotMode = snapshotModeRaw === "force_snapshot" || snapshotModeRaw === "force_live" ? snapshotModeRaw : "auto"; + return { + as_of_date: asOfDate, + period_from: periodFrom, + period_to: periodTo, + source, + snapshot_mode: snapshotMode, + active: Boolean(asOfDate || (periodFrom && periodTo)) + }; +} function extractExecutionState(normalized) { const fragments = extractFragments(normalized); return fragments.map((item) => { @@ -363,22 +412,7 @@ function enrichFragmentTextWithHints(fragment, text) { return `${baseText}, по счету ${accountHints.join(", ")}`; } function fragmentTextById(normalized) { - const result = new Map(); - for (const item of extractFragments(normalized)) { - if (!item || typeof item !== "object") { - continue; - } - const fragment = item; - const fragmentId = typeof fragment.fragment_id === "string" ? fragment.fragment_id : ""; - if (!fragmentId) { - continue; - } - const text = (typeof fragment.raw_fragment_text === "string" && fragment.raw_fragment_text.trim()) || - (typeof fragment.normalized_fragment_text === "string" && fragment.normalized_fragment_text.trim()) || - ""; - result.set(fragmentId, enrichFragmentTextWithHints(fragment, text)); - } - return result; + return (0, assistantQueryPlanning_1.buildFragmentTextById)(extractFragments(normalized)); } function extractDiscardedIntentSegments(normalized) { if (!normalized || typeof normalized !== "object") { @@ -590,109 +624,20 @@ function extractSubjectTokens(text) { } function extractRequirements(routeSummary, normalized, userMessage) { const fragmentText = fragmentTextById(normalized); - const byFragment = new Map(); - const requirements = []; - const pushRequirement = (input) => { - const subjectTokens = extractSubjectTokens(input.requirement_text); - requirements.push({ - requirement_id: input.requirement_id, - source_fragment_id: input.source_fragment_id, - requirement_text: input.requirement_text, - subject_tokens: subjectTokens, - status: input.status, - route: input.route - }); - if (input.source_fragment_id) { - const current = byFragment.get(input.source_fragment_id) ?? []; - current.push(input.requirement_id); - byFragment.set(input.source_fragment_id, current); - } - }; - if (!routeSummary) { - pushRequirement({ - requirement_id: "R1", - source_fragment_id: null, - requirement_text: userMessage, - status: "clarification_needed", - route: null - }); - return { requirements, byFragment }; - } - if (routeSummary.mode === "legacy_v1") { - pushRequirement({ - requirement_id: "R1", - source_fragment_id: "F1", - requirement_text: userMessage, - status: "covered", - route: routeSummary.route_hint - }); - return { requirements, byFragment }; - } - routeSummary.decisions.forEach((decision, index) => { - const requirementId = `R${index + 1}`; - const text = fragmentText.get(decision.fragment_id) ?? userMessage; - let status = "covered"; - if (decision.route === "no_route") { - if (decision.no_route_reason === "out_of_scope") { - status = "out_of_scope"; - } - else if (decision.no_route_reason === "insufficient_specificity") { - status = "clarification_needed"; - } - else { - status = "uncovered"; - } - } - pushRequirement({ - requirement_id: requirementId, - source_fragment_id: decision.fragment_id, - requirement_text: text, - status, - route: decision.route === "no_route" ? null : decision.route - }); + return (0, assistantCoverageGrounding_1.extractRequirementsForRoute)({ + routeSummary, + userMessage, + fragmentTextById: fragmentText, + extractSubjectTokens }); - return { requirements, byFragment }; } function toExecutionPlan(routeSummary, normalized, userMessage, requirementByFragment) { - if (!routeSummary) { - return []; - } const fragmentText = fragmentTextById(normalized); - if (routeSummary.mode === "legacy_v1") { - return [ - { - fragment_id: "F1", - requirement_ids: requirementByFragment.get("F1") ?? ["R1"], - route: routeSummary.route_hint, - should_execute: true, - fragment_text: userMessage, - no_route_reason: null, - clarification_reason: null - } - ]; - } - return routeSummary.decisions.map((decision) => { - const text = fragmentText.get(decision.fragment_id) ?? userMessage; - if (decision.route === "no_route") { - return { - fragment_id: decision.fragment_id, - requirement_ids: requirementByFragment.get(decision.fragment_id) ?? [], - route: "no_route", - should_execute: false, - fragment_text: text, - no_route_reason: decision.no_route_reason ?? null, - clarification_reason: decision.clarification_reason ?? null - }; - } - return { - fragment_id: decision.fragment_id, - requirement_ids: requirementByFragment.get(decision.fragment_id) ?? [], - route: decision.route, - should_execute: true, - fragment_text: text, - no_route_reason: null, - clarification_reason: decision.clarification_reason ?? null - }; + return (0, assistantQueryPlanning_1.buildExecutionPlanFromRoute)({ + routeSummary, + userMessage, + fragmentTextById: fragmentText, + requirementByFragment }); } function enrichRbpFragmentForLive(fragmentText, temporalGuard) { @@ -1007,29 +952,10 @@ function collectFaLiveRouteAudit(input) { }; } function toDebugRoutes(routeSummary) { - if (!routeSummary) { - return []; - } - if (routeSummary.mode === "legacy_v1") { - return [ - { - fragment_id: "F1", - route: routeSummary.route_hint, - reason: retrievalSummaryForRoute(routeSummary.route_hint), - confidence: routeSummary.confidence, - intent_class: routeSummary.intent_class - } - ]; - } - return routeSummary.decisions.map((decision) => ({ - fragment_id: decision.fragment_id, - route: decision.route, - reason: decision.reason, - route_status: decision.route_status ?? null, - no_route_reason: decision.no_route_reason ?? null, - clarification_reason: decision.clarification_reason ?? null, - execution_readiness: decision.execution_readiness ?? null - })); + return (0, assistantQueryPlanning_1.buildDebugRoutesFromRoute)({ + routeSummary, + resolveLegacyRouteReason: retrievalSummaryForRoute + }); } function buildSkippedResult(item) { return (0, retrievalResultNormalizer_1.normalizeRetrievalResult)(item.fragment_id, item.requirement_ids, "no_route", { @@ -1052,374 +978,23 @@ function buildSkippedResult(item) { errors: [] }); } -function summarizeUnique(values, limit = 6) { - return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))).slice(0, limit); -} -const SUBJECT_TOKEN_RULES = { - nds: { - critical: true, - patterns: [ - "vat", - "accumulationregister", - "\u043d\u0434\u0441", - "\u043a\u043d\u0438\u0433\u0438\u043f\u043e\u043a\u0443\u043f\u043e\u043a", - "\u043a\u043d\u0438\u0433\u0438\u043f\u0440\u043e\u0434\u0430\u0436", - "\u043d\u0430\u043b\u043e\u0433\u043d\u0430\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u0443\u044e\u0441\u0442\u043e\u0438\u043c\u043e\u0441\u0442\u044c" - ] - }, - os: { - critical: true, - patterns: ["fixed_asset", "fixedasset", "\u043e\u0441\u043d\u043e\u0432\u043d", "\u0430\u043c\u043e\u0440\u0442\u0438\u0437"] - }, - saldo: { - critical: true, - patterns: ["balance", "saldo", "\u0441\u0430\u043b\u044c\u0434\u043e", "\u043e\u0441\u0442\u0430\u0442"] - }, - counterparty: { - critical: false, - patterns: [ - "counterparty", - "supplier", - "buyer", - "counterparty_id", - "journal_counterparty", - "document_has_counterparty", - "\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442", - "\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a", - "\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b" - ], - routes: ["hybrid_store_plus_live", "store_feature_risk", "store_canonical"] - }, - document: { - critical: false, - patterns: [ - "document", - "recorder", - "journal", - "document_refs_count", - "recorded_by_document", - "journal_refers_to_document", - "\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442" - ], - routes: ["hybrid_store_plus_live", "store_feature_risk", "store_canonical", "live_mcp_drilldown"] - }, - anomaly: { - critical: false, - patterns: [ - "risk", - "risk_score", - "unknown_link_count", - "zero_guid", - "navigation_links", - "missing_counterparty_link", - "\u0430\u043d\u043e\u043c\u0430\u043b", - "\u0440\u0438\u0441\u043a" - ], - routes: ["store_feature_risk", "batch_refresh_then_store"] - }, - chain: { - critical: false, - patterns: ["chain", "cross_entity_chain", "relation_types", "operations_count", "matched_counterparties", "\u0446\u0435\u043f\u043e\u0447"], - routes: ["hybrid_store_plus_live"] - } -}; -function hasRegexMatch(corpus, pattern) { - try { - return pattern.test(corpus); - } - catch { - return false; - } -} -function evaluateSubjectTokenMatch(token, corpus, executedRoutes) { - if (token.startsWith("account_")) { - const account = token.slice("account_".length).trim(); - if (!account) { - return { matched: false, critical: true }; - } - const escaped = account.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const accountPattern = new RegExp(`(^|[^0-9])${escaped}([^0-9]|$)`, "i"); - return { matched: hasRegexMatch(corpus, accountPattern), critical: true }; - } - const rule = SUBJECT_TOKEN_RULES[token]; - if (rule) { - const byPattern = rule.patterns.some((pattern) => corpus.includes(pattern)); - const byRoute = Array.isArray(rule.routes) ? rule.routes.some((route) => executedRoutes.has(route)) : false; - return { matched: byPattern || byRoute, critical: rule.critical }; - } - return { matched: corpus.includes(token), critical: false }; -} -function evidenceCountForRequirement(requirementId, result) { - const evidence = Array.isArray(result.evidence) ? result.evidence : []; - if (evidence.length === 0) { - return 0; - } - const tagged = evidence.filter((item) => { - const claimRef = typeof item?.claim_ref === "string" ? item.claim_ref : ""; - return claimRef.toLowerCase() === `requirement:${String(requirementId).toLowerCase()}`; - }).length; - if (tagged > 0) { - return tagged; - } - if (Array.isArray(result.requirement_ids) && - result.requirement_ids.length === 1 && - result.requirement_ids[0] === requirementId) { - return evidence.length; - } - return 0; -} -function hasSubstantiveCoverageForRequirement(requirementId, result) { - const evidenceCount = evidenceCountForRequirement(requirementId, result); - if (evidenceCount > 0) { - return true; - } - const problemUnitsCount = Array.isArray(result.problem_units) ? result.problem_units.length : 0; - const candidateEvidenceCount = Array.isArray(result.candidate_evidence) ? result.candidate_evidence.length : 0; - if (problemUnitsCount > 0 || candidateEvidenceCount > 0) { - if (Array.isArray(result.requirement_ids) && - result.requirement_ids.length === 1 && - result.requirement_ids[0] === requirementId) { - return true; - } - } - return false; -} function evaluateCoverage(requirements, retrievalResults) { - const statusByRequirement = new Map(); - for (const result of retrievalResults) { - for (const requirementId of result.requirement_ids) { - const list = statusByRequirement.get(requirementId) ?? []; - list.push({ - status: result.status, - substantive: hasSubstantiveCoverageForRequirement(requirementId, result) - }); - statusByRequirement.set(requirementId, list); - } - } - const resolvedRequirements = requirements.map((requirement) => { - if (requirement.status === "out_of_scope" || requirement.status === "clarification_needed") { - return requirement; - } - const states = statusByRequirement.get(requirement.requirement_id) ?? []; - if (states.length === 0) { - return { ...requirement, status: "uncovered" }; - } - const hasAnySubstantive = states.some((item) => item.substantive); - if (!hasAnySubstantive) { - return { ...requirement, status: "uncovered" }; - } - const hasOk = states.some((item) => item.status === "ok"); - const hasPartial = states.some((item) => item.status === "partial"); - const hasEmpty = states.some((item) => item.status === "empty"); - const hasError = states.some((item) => item.status === "error"); - const hasWeakOk = states.some((item) => item.status === "ok" && !item.substantive); - const hasSubstantiveOk = states.some((item) => item.status === "ok" && item.substantive); - const hasSubstantivePartial = states.some((item) => item.status === "partial" && item.substantive); - if (hasSubstantiveOk && !hasSubstantivePartial && !hasWeakOk && !hasEmpty && !hasError) { - return { ...requirement, status: "covered" }; - } - if (hasSubstantiveOk || hasSubstantivePartial || hasOk || hasPartial) { - return { ...requirement, status: "partially_covered" }; - } - return { ...requirement, status: "uncovered" }; - }); - const requirementsCovered = resolvedRequirements.filter((item) => item.status === "covered").length; - const requirementsUncovered = resolvedRequirements - .filter((item) => item.status === "uncovered") - .map((item) => item.requirement_id); - const requirementsPartiallyCovered = resolvedRequirements - .filter((item) => item.status === "partially_covered") - .map((item) => item.requirement_id); - const clarificationNeededFor = resolvedRequirements - .filter((item) => item.status === "clarification_needed") - .map((item) => item.requirement_id); - const outOfScopeRequirements = resolvedRequirements - .filter((item) => item.status === "out_of_scope") - .map((item) => item.requirement_id); - return { - requirements: resolvedRequirements, - coverage: { - requirements_total: resolvedRequirements.length, - requirements_covered: requirementsCovered, - requirements_uncovered: requirementsUncovered, - requirements_partially_covered: requirementsPartiallyCovered, - clarification_needed_for: clarificationNeededFor, - out_of_scope_requirements: outOfScopeRequirements - } - }; + return (0, assistantCoverageGrounding_1.evaluateCoverageForRequirements)(requirements, retrievalResults); } function evaluateCoverageForTests(requirements, retrievalResults) { - return evaluateCoverage(requirements, retrievalResults); + return (0, assistantCoverageGrounding_1.evaluateCoverageForRequirements)(requirements, retrievalResults); } function extractSubjectTokensForTests(text) { return extractSubjectTokens(text); } function checkGrounding(userMessage, requirements, coverage, retrievalResults) { - const whyIncludedSummary = summarizeUnique(retrievalResults.flatMap((item) => item.why_included)); - const selectionReasonSummary = summarizeUnique(retrievalResults.flatMap((item) => item.selection_reason)); - const hasMaterialResults = retrievalResults.some((item) => item.status === "ok" || item.status === "partial"); - const subjectTokens = extractSubjectTokens(userMessage); - const executedRoutes = new Set(retrievalResults - .filter((item) => item.status !== "error") - .map((item) => item.route) - .filter(Boolean)); - const retrievalCorpus = JSON.stringify(retrievalResults.map((item) => ({ - route: item.route, - result_type: item.result_type, - summary: item.summary, - items: item.items, - evidence: item.evidence, - why_included: item.why_included, - selection_reason: item.selection_reason, - risk_factors: item.risk_factors, - business_interpretation: item.business_interpretation - }))).toLowerCase(); - const missingSubjectTokens = []; - const missingCriticalTokens = []; - for (const token of subjectTokens) { - const match = evaluateSubjectTokenMatch(token, retrievalCorpus, executedRoutes); - if (!match.matched) { - missingSubjectTokens.push(token); - if (match.critical) { - missingCriticalTokens.push(token); - } - } - } - const onlyAccountCriticalMissing = missingCriticalTokens.length > 0 && missingCriticalTokens.every((token) => token.startsWith("account_")); - const accountOnlyMismatchRecoverable = hasMaterialResults && - coverage.requirements_covered > 0 && - onlyAccountCriticalMissing && - (whyIncludedSummary.length > 0 || selectionReasonSummary.length > 0); - const routeSubjectMatch = !hasMaterialResults || missingCriticalTokens.length === 0 || accountOnlyMismatchRecoverable; - let status = "grounded"; - const reasons = []; - if (!routeSubjectMatch) { - status = "route_mismatch_blocked"; - reasons.push(`Не подтверждены критичные предметные токены запроса: ${missingCriticalTokens.join(", ")}`); - } - else if (accountOnlyMismatchRecoverable) { - status = "partial"; - reasons.push(`Счет-токены не подтверждены напрямую (${missingCriticalTokens.join(", ")}), но есть релевантная опора для ограниченного вывода.`); - } - else if (coverage.requirements_covered === 0) { - status = "no_grounded_answer"; - reasons.push("Ни одно требование не получило подтвержденного покрытия."); - } - else if (coverage.requirements_uncovered.length > 0 || - coverage.requirements_partially_covered.length > 0 || - coverage.clarification_needed_for.length > 0 || - coverage.out_of_scope_requirements.length > 0) { - status = "partial"; - reasons.push("Вопрос покрыт частично: есть непокрытые или требующие уточнения требования."); - } - if (whyIncludedSummary.length === 0) { - reasons.push("Нет explainable-сигналов why_included в результатах выборки."); - } - if (missingSubjectTokens.length > 0 && missingCriticalTokens.length === 0) { - reasons.push(`Часть контекстных токенов не подтверждена напрямую: ${missingSubjectTokens.join(", ")}`); - } - const missingRequirements = [ - ...coverage.requirements_uncovered, - ...coverage.requirements_partially_covered, - ...coverage.clarification_needed_for, - ...coverage.out_of_scope_requirements - ]; - return { - status, - route_subject_match: routeSubjectMatch, - missing_requirements: missingRequirements, - reasons, - why_included_summary: whyIncludedSummary, - selection_reason_summary: selectionReasonSummary - }; -} -function firstNonEmptyLine(text) { - const line = text - .split("\n") - .map((item) => item.trim()) - .find((item) => item.length > 0); - return (line ?? text).slice(0, 220); -} -function buildClaimEvidenceLinks(retrievalResults) { - const byClaim = new Map(); - for (const result of retrievalResults) { - for (const evidence of result.evidence) { - const claimRef = String(evidence.claim_ref ?? "").trim(); - if (!claimRef) { - continue; - } - const evidenceId = String(evidence.evidence_id ?? "").trim(); - if (!evidenceId) { - continue; - } - const current = byClaim.get(claimRef) ?? []; - current.push(evidenceId); - byClaim.set(claimRef, current); - } - } - return Array.from(byClaim.entries()) - .slice(0, 10) - .map(([claimRef, evidenceIds]) => ({ - claim_ref: claimRef, - evidence_ids: summarizeUnique(evidenceIds, 10) - })); -} -function buildAnswerStructureV11(input) { - const evidenceIds = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence.map((evidence) => evidence.evidence_id)), 10); - const mechanismNotes = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence - .map((evidence) => evidence.mechanism_note) - .filter((note) => typeof note === "string" && note.trim().length > 0)), 6); - const sourceRefs = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence - .map((evidence) => evidence.source_ref?.canonical_ref) - .filter((value) => typeof value === "string" && value.trim().length > 0)), 8); - const limitationReasonCodes = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence - .flatMap((evidence) => { - const code = evidence.limitation?.reason_code; - return typeof code === "string" && code.trim().length > 0 ? [code] : []; - })), 8); - const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults); - const limitations = summarizeUnique([...input.retrievalResults.flatMap((item) => item.limitations), ...input.groundingCheck.reasons], 8); - const clarificationQuestions = input.coverageReport.clarification_needed_for.map((item) => `Уточните требование ${item}.`); - const recommendedActions = summarizeUnique([ - ...input.coverageReport.requirements_uncovered.map((item) => `Проверить непокрытое требование ${item}.`), - ...input.coverageReport.requirements_partially_covered.map((item) => `Доуточнить частично покрытое требование ${item}.`) - ], 6); - const mechanismStatus = mechanismNotes.length === 0 - ? "unresolved" - : limitationReasonCodes.includes("missing_mechanism") || limitationReasonCodes.includes("heuristic_inference") - ? "limited" - : "grounded"; - return { - schema_version: stage1Contracts_1.ANSWER_STRUCTURE_SCHEMA_VERSION, - answer_summary: firstNonEmptyLine(input.assistantReply), - direct_answer: input.assistantReply, - mechanism_block: { - status: mechanismStatus, - mechanism_notes: mechanismNotes, - limitation_reason_codes: limitationReasonCodes - }, - evidence_block: { - evidence_ids: evidenceIds, - source_refs: sourceRefs, - mechanism_notes: mechanismNotes, - coverage_note: input.coverageReport.requirements_total === input.coverageReport.requirements_covered - ? "coverage_full_or_near_full" - : "coverage_partial_or_limited", - ...(config_1.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1 && claimEvidenceLinks.length > 0 - ? { - claim_evidence_links: claimEvidenceLinks - } - : {}) - }, - uncertainty_block: { - open_uncertainties: input.groundingCheck.missing_requirements, - limitations - }, - next_step_block: { - recommended_actions: recommendedActions, - clarification_questions: clarificationQuestions - } - }; + return (0, assistantCoverageGrounding_1.checkGroundingForRequirements)({ + userMessage, + requirements, + coverage, + retrievalResults, + extractSubjectTokens + }); } const FOLLOWUP_ROUTE_HINTS = new Set(["store_canonical", "store_feature_risk", "hybrid_store_plus_live", "live_mcp_drilldown", "batch_refresh_then_store"]); const FOLLOWUP_ACTIVE_DOMAIN_ROUTE_MAP = { @@ -1657,6 +1232,14 @@ function buildFollowupStateBinding(input) { } if (periodHintFromState) { context.period_hint = periodHintFromState; + const existingAnalysisContext = context.analysis_context && typeof context.analysis_context === "object" ? context.analysis_context : {}; + if (!existingAnalysisContext.as_of_date) { + context.analysis_context = { + ...existingAnalysisContext, + as_of_date: periodHintFromState, + source: existingAnalysisContext.source ?? "followup_state_period_hint" + }; + } } const subject = withCappedLength(compactWhitespace(input.investigationState.focus.active_query_subject ?? ""), FOLLOWUP_SUBJECT_MAX); const businessContextPatch = ["followup_state_binding_v1"]; @@ -3599,7 +3182,7 @@ function resolveAssistantOrchestrationDecision(input) { const baseToolGate = resolveAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage); const unsupportedAddressIntentFallbackToDeep = Boolean(!followupContext && baseToolGate?.runAddressLane && - modeDetection.mode === "address_query" && + modeDetection.mode !== "address_query" && intentResolution.intent === "unknown" && strongDataSignal); let runAddressLane = Boolean(baseToolGate?.runAddressLane); @@ -4823,6 +4406,7 @@ class AssistantService { const userMessageRaw = String(payload.user_message ?? payload.message ?? "").trim(); const repairedUserMessage = compactWhitespace(repairAddressMojibake(userMessageRaw)); const userMessage = repairedUserMessage || userMessageRaw; + const runtimeAnalysisContext = resolveRuntimeAnalysisContext(payload?.context); const userItem = { message_id: `msg-${(0, nanoid_1.nanoid)(10)}`, session_id: sessionId, @@ -5230,6 +4814,7 @@ class AssistantService { } if (orchestrationDecision.runAddressLane) { const shouldPreferContextualLane = Boolean(carryover?.followupContext); + const analysisDateHint = runtimeAnalysisContext.as_of_date ?? toNonEmptyString(payload?.context?.period_hint); const canRetryWithRawUserMessage = compactWhitespace(String(addressInputMessage ?? "").toLowerCase()) !== compactWhitespace(String(userMessage ?? "").toLowerCase()); const retryAudit = { @@ -5272,10 +4857,13 @@ class AssistantService { const scopedFollowupContext = mergeFollowupContextWithOrganizationScope(carryMeta?.followupContext ?? null, sessionOrganizationScope.activeOrganization); if (scopedFollowupContext) { return this.addressQueryService.tryHandle(messageUsed, { - followupContext: scopedFollowupContext + followupContext: scopedFollowupContext, + analysisDateHint }); } - return this.addressQueryService.tryHandle(messageUsed); + return this.addressQueryService.tryHandle(messageUsed, { + analysisDateHint + }); }; if (shouldPreferContextualLane) { const contextualAddressLane = await runAddressLaneAttempt(addressInputMessage, carryover); @@ -5352,403 +4940,184 @@ class AssistantService { useMock: Boolean(payload.useMock) }; const normalized = await this.normalizerService.normalize(normalizePayload); - const companyAnchors = (0, companyAnchorResolver_1.resolveCompanyAnchors)(userMessage); - const initialBusinessScopeResolution = resolveBusinessScopeAlignment({ + const contextRuntime = (0, assistantDeepTurnContextRuntimeAdapter_1.buildAssistantDeepTurnRuntimeContext)({ userMessage, - companyAnchors, - normalized: normalized.normalized, - routeSummary: normalized.route_hint_summary + normalizedPayload: normalized.normalized, + routeSummary: normalized.route_hint_summary, + runtimeAnalysisContext, + followupUsage: followupBinding.usage, + resolveCompanyAnchors: companyAnchorResolver_1.resolveCompanyAnchors, + resolveBusinessScopeAlignment, + inferP0DomainFromMessage, + resolveTemporalGuard: assistantRuntimeGuards_1.resolveTemporalGuard, + resolveDomainPolarityGuard: assistantRuntimeGuards_1.resolveDomainPolarityGuard, + resolveClaimBoundAnchors: assistantClaimBoundEvidence_1.resolveClaimBoundAnchors, + resolveBusinessScopeFromLiveContext }); - const inferredDomainByMessage = inferP0DomainFromMessage(userMessage); - const focusDomainForGuards = inferredDomainByMessage === "settlements_60_62" || - inferredDomainByMessage === "vat_document_register_book" || - inferredDomainByMessage === "month_close_costs_20_44" || - inferredDomainByMessage === "fixed_asset_amortization" - ? inferredDomainByMessage - : null; - const temporalGuard = (0, assistantRuntimeGuards_1.resolveTemporalGuard)({ + const companyAnchors = contextRuntime.companyAnchors; + const focusDomainForGuards = contextRuntime.focusDomainForGuards; + const temporalGuard = contextRuntime.temporalGuard; + const domainPolarityGuardInitial = contextRuntime.domainPolarityGuardInitial; + const claimAnchorAudit = contextRuntime.claimAnchorAudit; + const businessScopeResolution = contextRuntime.businessScopeResolution; + const resolvedRouteSummary = contextRuntime.resolvedRouteSummary; + const liveTemporalHint = contextRuntime.liveTemporalHint; + const executionPlanRuntime = (0, assistantDeepTurnPlanRuntimeAdapter_1.buildAssistantDeepTurnExecutionPlan)({ + routeSummary: resolvedRouteSummary, + normalizedPayload: normalized.normalized, userMessage, - normalized: normalized.normalized, - companyAnchors - }); - const domainPolarityGuardInitial = (0, assistantRuntimeGuards_1.resolveDomainPolarityGuard)({ - userMessage, - companyAnchors, - focusDomainHint: focusDomainForGuards - }); - const claimAnchorAudit = (0, assistantClaimBoundEvidence_1.resolveClaimBoundAnchors)({ - userMessage, - companyAnchors, - focusDomainHint: focusDomainForGuards, - primaryPeriod: temporalGuard.effective_primary_period ?? temporalGuard.primary_period_window - }); - const businessScopeResolution = resolveBusinessScopeFromLiveContext({ - current: initialBusinessScopeResolution, + claimType: claimAnchorAudit.claim_type, temporalGuard, - claimType: claimAnchorAudit.claim_type, - focusDomainHint: focusDomainForGuards, - userMessage, - companyAnchors, - followupApplied: Boolean(followupBinding.usage?.applied) + domainPolarityGuardInitial, + extractRequirements, + toExecutionPlan, + enforceRbpLiveRoutePlan, + enforceFaLiveRoutePlan, + applyTemporalHintToExecutionPlan: assistantRuntimeGuards_1.applyTemporalHintToExecutionPlan, + applyPolarityHintToExecutionPlan: assistantRuntimeGuards_1.applyPolarityHintToExecutionPlan }); - const resolvedRouteSummary = businessScopeResolution.route_summary_resolved; - const requirementExtraction = extractRequirements(resolvedRouteSummary, normalized.normalized, userMessage); - let executionPlan = toExecutionPlan(resolvedRouteSummary, normalized.normalized, userMessage, requirementExtraction.byFragment); - const rbpRoutePlanEnforcement = enforceRbpLiveRoutePlan({ + const requirementExtraction = executionPlanRuntime.requirementExtraction; + const rbpRoutePlanEnforcement = executionPlanRuntime.rbpRoutePlanEnforcement; + const faRoutePlanEnforcement = executionPlanRuntime.faRoutePlanEnforcement; + const executionPlan = executionPlanRuntime.executionPlan; + const retrievalRuntime = await (0, assistantDeepTurnRetrievalRuntimeAdapter_1.executeAssistantDeepTurnRetrievalPlan)({ executionPlan, - claimType: claimAnchorAudit.claim_type, - temporalGuard + liveTemporalHint, + executeRouteRuntime: (route, fragmentText, options) => this.dataLayer.executeRouteRuntime(route, fragmentText, options), + mapNoRouteReason, + buildSkippedResult }); - executionPlan = rbpRoutePlanEnforcement.executionPlan; - const faRoutePlanEnforcement = enforceFaLiveRoutePlan({ - executionPlan, - claimType: claimAnchorAudit.claim_type, - temporalGuard - }); - executionPlan = faRoutePlanEnforcement.executionPlan; - executionPlan = (0, assistantRuntimeGuards_1.applyTemporalHintToExecutionPlan)(executionPlan, temporalGuard); - executionPlan = (0, assistantRuntimeGuards_1.applyPolarityHintToExecutionPlan)(executionPlan, domainPolarityGuardInitial); - const retrievalCalls = []; - const retrievalResultsRaw = []; - let retrievalResults = []; - for (const planItem of executionPlan) { - if (!planItem.should_execute) { - retrievalCalls.push({ - fragment_id: planItem.fragment_id, - requirement_ids: planItem.requirement_ids, - route: planItem.route, - status: "skipped", - query_text: planItem.fragment_text, - reason: mapNoRouteReason(planItem.no_route_reason) - }); - retrievalResults.push(buildSkippedResult(planItem)); - continue; - } - retrievalCalls.push({ - fragment_id: planItem.fragment_id, - requirement_ids: planItem.requirement_ids, - route: planItem.route, - status: "executed", - query_text: planItem.fragment_text, - reason: null - }); - try { - const raw = await this.dataLayer.executeRouteRuntime(planItem.route, planItem.fragment_text); - retrievalResultsRaw.push({ - fragment_id: planItem.fragment_id, - route: planItem.route, - raw_result: raw - }); - retrievalResults.push((0, retrievalResultNormalizer_1.normalizeRetrievalResult)(planItem.fragment_id, planItem.requirement_ids, planItem.route, raw)); - } - catch (error) { - const message = error instanceof Error ? error.message : String(error); - retrievalCalls[retrievalCalls.length - 1].status = "failed"; - retrievalCalls[retrievalCalls.length - 1].reason = message; - const rawError = { - status: "error", - result_type: "summary", - items: [], - summary: { - route: planItem.route - }, - evidence: [], - why_included: [], - selection_reason: [], - risk_factors: [], - business_interpretation: [], - confidence: "low", - limitations: ["Route executor failed."], - errors: [message] - }; - retrievalResultsRaw.push({ - fragment_id: planItem.fragment_id, - route: planItem.route, - raw_result: rawError - }); - retrievalResults.push((0, retrievalResultNormalizer_1.normalizeRetrievalResult)(planItem.fragment_id, planItem.requirement_ids, planItem.route, rawError)); - } - } - const polarityGuardResult = (0, assistantRuntimeGuards_1.applyDomainPolarityGuardToRetrievalResults)({ + const retrievalCalls = retrievalRuntime.retrievalCalls; + const retrievalResultsRaw = retrievalRuntime.retrievalResultsRaw; + let retrievalResults = retrievalRuntime.retrievalResults; + const guardRuntime = (0, assistantDeepTurnGuardRuntimeAdapter_1.applyAssistantDeepTurnRetrievalGuards)({ retrievalResults, - guard: domainPolarityGuardInitial - }); - retrievalResults = polarityGuardResult.retrievalResults; - const targetedEvidenceResult = (0, assistantClaimBoundEvidence_1.applyTargetedEvidenceAcquisition)({ - retrievalResults, - claimAudit: claimAnchorAudit - }); - retrievalResults = targetedEvidenceResult.retrievalResults; - const evidenceGateResult = (0, assistantRuntimeGuards_1.applyEvidenceAdmissibilityGate)({ - retrievalResults, - temporal: temporalGuard, - focusDomainHint: focusDomainForGuards, - polarity: polarityGuardResult.audit.polarity, + domainPolarityGuardInitial, + claimAnchorAudit, + temporalGuard, + focusDomainForGuards, companyAnchors, userMessage }); - retrievalResults = evidenceGateResult.retrievalResults; - const rbpLiveRouteAudit = collectRbpLiveRouteAudit({ + retrievalResults = guardRuntime.retrievalResults; + const polarityGuardResult = guardRuntime.polarityGuardResult; + const targetedEvidenceResult = guardRuntime.targetedEvidenceResult; + const evidenceGateResult = guardRuntime.evidenceGateResult; + const groundingRuntime = (0, assistantDeepTurnGroundingRuntimeAdapter_1.runAssistantDeepTurnGroundingRuntime)({ claimType: claimAnchorAudit.claim_type, retrievalResults, - planAudit: rbpRoutePlanEnforcement.audit - }); - const faLiveRouteAudit = collectFaLiveRouteAudit({ - claimType: claimAnchorAudit.claim_type, - retrievalResults, - planAudit: faRoutePlanEnforcement.audit - }); - const coverageEvaluation = evaluateCoverage(requirementExtraction.requirements, retrievalResults); - const groundingCheckBase = checkGrounding(userMessage, coverageEvaluation.requirements, coverageEvaluation.coverage, retrievalResults); - const groundedAnswerEligibilityGuard = (0, assistantRuntimeGuards_1.evaluateGroundedAnswerEligibility)({ - temporal: temporalGuard, - polarity: polarityGuardResult.audit, - evidence: evidenceGateResult.audit, - claimAnchors: claimAnchorAudit, + rbpPlanAudit: rbpRoutePlanEnforcement.audit, + faPlanAudit: faRoutePlanEnforcement.audit, + routeSummary: resolvedRouteSummary, + normalizedPayload: normalized.normalized, + userMessage, + requirementExtraction, + extractRequirements, + evaluateCoverage, + checkGrounding, + temporalGuard, + polarityAudit: polarityGuardResult.audit, + evidenceAudit: evidenceGateResult.audit, + claimAnchorAudit, targetedEvidenceHitRate: targetedEvidenceResult.audit.targeted_evidence_hit_rate, - businessScopeResolved: businessScopeResolution.business_scope_resolved + businessScopeResolved: businessScopeResolution.business_scope_resolved, + collectRbpLiveRouteAudit, + collectFaLiveRouteAudit }); - const groundingCheck = (0, assistantRuntimeGuards_1.applyEligibilityToGroundingCheck)(groundingCheckBase, groundedAnswerEligibilityGuard); - const focusDomainHint = followupBinding.usage?.applied - ? session.investigation_state?.followup_context?.active_domain ?? session.investigation_state?.focus.domain ?? null - : null; - const questionTypeClass = (0, questionTypeResolver_1.resolveQuestionType)(userMessage); - const hasPeriodInCompanyAnchors = (Array.isArray(companyAnchors?.dates) && companyAnchors.dates.some((item) => String(item ?? "").trim().length > 0)) || - (Array.isArray(companyAnchors?.periods) && companyAnchors.periods.some((item) => String(item ?? "").trim().length > 0)); - const normalizationPeriodExplicit = hasExplicitPeriodAnchorFromNormalized(normalized.normalized) || hasPeriodInCompanyAnchors; - const composition = (0, answerComposer_1.composeAssistantAnswer)({ + const rbpLiveRouteAudit = groundingRuntime.rbpLiveRouteAudit; + const faLiveRouteAudit = groundingRuntime.faLiveRouteAudit; + const coverageEvaluation = groundingRuntime.coverageEvaluation; + const groundedAnswerEligibilityGuard = groundingRuntime.groundedAnswerEligibilityGuard; + const groundingCheck = groundingRuntime.groundingCheck; + const deepTurnComposition = (0, assistantDeepTurnCompositionRuntimeAdapter_1.buildAssistantDeepTurnComposition)({ userMessage, routeSummary: resolvedRouteSummary, retrievalResults, requirements: coverageEvaluation.requirements, coverageReport: coverageEvaluation.coverage, groundingCheck, - focusDomainHint, - questionTypeHint: questionTypeClass, + followupUsage: followupBinding.usage, + investigationState: session.investigation_state, companyAnchors, - normalizationPeriodExplicit, - enableAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11, - enableProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1, - enableLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 + normalizedPayload: normalized.normalized, + featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11, + featureProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1, + featureLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1, + hasExplicitPeriodAnchor: (normalizedPayload) => hasExplicitPeriodAnchorFromNormalized(normalizedPayload) }); - const safeAssistantReplyBase = sanitizeOutgoingAssistantText(composition.assistant_reply, "Нужны уточнения для надежного ответа."); - const safeAssistantReply = String(safeAssistantReplyBase ?? "") - .replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "") - .replace(/\b(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "") - .trim(); - const answerStructureV11 = config_1.FEATURE_ASSISTANT_CONTRACTS_V11 - ? config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11 && composition.answer_structure_v11 - ? composition.answer_structure_v11 - : buildAnswerStructureV11({ - assistantReply: safeAssistantReply, - coverageReport: coverageEvaluation.coverage, - groundingCheck, - retrievalResults - }) - : null; - const investigationStateSnapshot = config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 && session.investigation_state - ? (0, investigationState_1.updateInvestigationState)({ - previous: session.investigation_state, - timestamp: new Date().toISOString(), - questionId: userItem.message_id, - userMessage, - routeSummary: resolvedRouteSummary, - requirements: coverageEvaluation.requirements, - coverageReport: coverageEvaluation.coverage, - retrievalResults, - replyType: composition.reply_type, - followupApplied: Boolean(followupBinding.usage?.applied) - }) - : null; - if (config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 && investigationStateSnapshot) { - this.sessions.setInvestigationState(sessionId, investigationStateSnapshot); - } - const debug = { - trace_id: normalized.trace_id, - prompt_version: normalized.prompt_version, - schema_version: normalized.schema_version, - fallback_type: composition.fallback_type, - route_summary: resolvedRouteSummary, - fragments: extractFragments(normalized.normalized), - requirements_extracted: coverageEvaluation.requirements, - coverage_report: coverageEvaluation.coverage, - routes: toDebugRoutes(resolvedRouteSummary), - retrieval_status: retrievalResults.map((item) => ({ - fragment_id: item.fragment_id, - requirement_ids: item.requirement_ids, - route: item.route, - status: item.status, - result_type: item.result_type - })), - retrieval_results: retrievalResults, - answer_grounding_check: groundingCheck, - dropped_intent_segments: extractDiscardedIntentSegments(normalized.normalized), - question_type_class: questionTypeClass, - company_anchors: companyAnchors, - business_scope_raw: businessScopeResolution.business_scope_raw, - business_scope_resolved: businessScopeResolution.business_scope_resolved, - company_grounding_applied: businessScopeResolution.company_grounding_applied, - scope_resolution_reason: businessScopeResolution.scope_resolution_reason, - company_scope_resolution_reason: businessScopeResolution.scope_resolution_reason, - raw_time_anchor: temporalGuard.raw_time_anchor, - raw_time_scope: temporalGuard.raw_time_scope, - resolved_time_anchor: temporalGuard.resolved_time_anchor, - resolved_primary_period: temporalGuard.resolved_primary_period, - effective_primary_period: temporalGuard.effective_primary_period, - temporal_guard_input: temporalGuard.temporal_guard_input, - temporal_alignment_status: temporalGuard.temporal_alignment_status, - temporal_resolution_source: temporalGuard.temporal_resolution_source, - temporal_guard_basis: temporalGuard.temporal_guard_basis, - temporal_guard_applied: temporalGuard.temporal_guard_applied, - temporal_guard_outcome: temporalGuard.temporal_guard_outcome, - temporal_guard: temporalGuard, - raw_numeric_tokens: polarityGuardResult.audit.raw_numeric_tokens, - classified_numeric_tokens: polarityGuardResult.audit.classified_numeric_tokens, - rejected_as_non_accounts: polarityGuardResult.audit.rejected_as_non_accounts, - resolved_account_anchors: polarityGuardResult.audit.resolved_account_anchors, - domain_polarity_guard: polarityGuardResult.audit, - claim_anchor_audit: claimAnchorAudit, - settlement_role: claimAnchorAudit.settlement_role ?? null, - settlement_role_resolution_reason: claimAnchorAudit.settlement_role_resolution_reason ?? [], - polarity_resolution_status: claimAnchorAudit.polarity_resolution_status ?? "not_applicable", - targeted_evidence_acquisition: targetedEvidenceResult.audit, - evidence_admissibility_gate: evidenceGateResult.audit, - ...(rbpLiveRouteAudit ? { rbp_live_route_audit: rbpLiveRouteAudit } : {}), - ...(faLiveRouteAudit ? { fa_live_route_audit: faLiveRouteAudit } : {}), - eligibility_time_basis: groundedAnswerEligibilityGuard.eligibility_time_basis, - grounded_answer_eligibility_guard: groundedAnswerEligibilityGuard, - ...(followupBinding.usage ? { followup_state_usage: followupBinding.usage } : {}), - problem_centric_answer_applied: composition.problem_centric_answer_applied ?? false, - problem_units_used_count: composition.problem_units_used_count ?? 0, - problem_answer_mode: composition.problem_answer_mode ?? "stage1_policy_v11", - ...(Array.isArray(composition.problem_unit_ids_used) && composition.problem_unit_ids_used.length > 0 - ? { - problem_unit_ids_used: composition.problem_unit_ids_used - } - : {}), - address_llm_predecompose_attempted: Boolean(addressRuntimeMetaForDeep?.attempted), - address_llm_predecompose_applied: Boolean(addressRuntimeMetaForDeep?.applied), - address_llm_predecompose_reason: addressRuntimeMetaForDeep?.reason ?? null, - address_llm_predecompose_provider: addressRuntimeMetaForDeep?.provider ?? null, - address_fallback_rule_hit: addressRuntimeMetaForDeep?.fallbackRuleHit ?? null, - address_tool_gate_decision: addressRuntimeMetaForDeep?.toolGateDecision ?? null, - address_tool_gate_reason: addressRuntimeMetaForDeep?.toolGateReason ?? null, - address_llm_predecompose_contract: addressRuntimeMetaForDeep?.predecomposeContract ?? null, - orchestration_contract_v1: addressRuntimeMetaForDeep?.orchestrationContract ?? null, - answer_structure_v11: answerStructureV11, - investigation_state_snapshot: investigationStateSnapshot, - normalized: normalized.normalized - }; - const assistantItem = { - message_id: `msg-${(0, nanoid_1.nanoid)(10)}`, - session_id: sessionId, - role: "assistant", - text: safeAssistantReply, - reply_type: composition.reply_type, - created_at: new Date().toISOString(), - trace_id: normalized.trace_id, - debug - }; - this.sessions.appendItem(sessionId, assistantItem); - const current = this.sessions.getSession(sessionId); - if (current) { - this.sessionLogger.persistSession(current); - } - const conversation = cloneItems(current?.items ?? []); - (0, log_1.logJson)({ - timestamp: new Date().toISOString(), - level: "info", - service: "assistant_loop", - message: "assistant_message_processed", + const questionTypeClass = deepTurnComposition.questionTypeClass; + const composition = deepTurnComposition.composition; + const packagingRuntime = (0, assistantDeepTurnPackagingRuntimeAdapter_1.runAssistantDeepTurnPackagingRuntime)({ + featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1, sessionId, - eventType: "assistant_message", - details: { - session_id: sessionId, - message_id: assistantItem.message_id, - user_message: userMessage, - normalizer_output: normalized.normalized, - execution_plan: executionPlan, - resolved_execution_state: extractExecutionState(normalized.normalized), - routes: toDebugRoutes(resolvedRouteSummary), - retrieval_calls: retrievalCalls, - retrieval_results_raw: retrievalResultsRaw, - retrieval_results_normalized: retrievalResults, - requirements_extracted: coverageEvaluation.requirements, - requirements_total: coverageEvaluation.coverage.requirements_total, - requirements_covered: coverageEvaluation.coverage.requirements_covered, - requirements_uncovered: coverageEvaluation.coverage.requirements_uncovered, - coverage_status: coverageEvaluation.coverage.requirements_total === coverageEvaluation.coverage.requirements_covered && - coverageEvaluation.coverage.requirements_uncovered.length === 0 && - coverageEvaluation.coverage.requirements_partially_covered.length === 0 - ? "full" - : "partial_or_limited", - answer_grounding_status: groundingCheck.status, - reply_semantic_type: composition.reply_type, - why_included_summary: groundingCheck.why_included_summary, - selection_reason_summary: groundingCheck.selection_reason_summary, - route_subject_match: groundingCheck.route_subject_match, - clarification_target: coverageEvaluation.coverage.clarification_needed_for, - dropped_intent_segments: extractDiscardedIntentSegments(normalized.normalized), - question_type_class: questionTypeClass, - company_anchors: companyAnchors, - business_scope_raw: businessScopeResolution.business_scope_raw, - business_scope_resolved: businessScopeResolution.business_scope_resolved, - company_grounding_applied: businessScopeResolution.company_grounding_applied, - scope_resolution_reason: businessScopeResolution.scope_resolution_reason, - company_scope_resolution_reason: businessScopeResolution.scope_resolution_reason, - raw_time_anchor: temporalGuard.raw_time_anchor, - raw_time_scope: temporalGuard.raw_time_scope, - resolved_time_anchor: temporalGuard.resolved_time_anchor, - resolved_primary_period: temporalGuard.resolved_primary_period, - effective_primary_period: temporalGuard.effective_primary_period, - temporal_guard_input: temporalGuard.temporal_guard_input, - temporal_alignment_status: temporalGuard.temporal_alignment_status, - temporal_resolution_source: temporalGuard.temporal_resolution_source, - temporal_guard_basis: temporalGuard.temporal_guard_basis, - temporal_guard_applied: temporalGuard.temporal_guard_applied, - temporal_guard_outcome: temporalGuard.temporal_guard_outcome, - temporal_guard: temporalGuard, - raw_numeric_tokens: polarityGuardResult.audit.raw_numeric_tokens, - classified_numeric_tokens: polarityGuardResult.audit.classified_numeric_tokens, - rejected_as_non_accounts: polarityGuardResult.audit.rejected_as_non_accounts, - resolved_account_anchors: polarityGuardResult.audit.resolved_account_anchors, - domain_polarity_guard: polarityGuardResult.audit, - claim_anchor_audit: claimAnchorAudit, - settlement_role: claimAnchorAudit.settlement_role ?? null, - settlement_role_resolution_reason: claimAnchorAudit.settlement_role_resolution_reason ?? [], - polarity_resolution_status: claimAnchorAudit.polarity_resolution_status ?? "not_applicable", - targeted_evidence_acquisition: targetedEvidenceResult.audit, - evidence_admissibility_gate: evidenceGateResult.audit, - ...(rbpLiveRouteAudit ? { rbp_live_route_audit: rbpLiveRouteAudit } : {}), - ...(faLiveRouteAudit ? { fa_live_route_audit: faLiveRouteAudit } : {}), - eligibility_time_basis: groundedAnswerEligibilityGuard.eligibility_time_basis, - grounded_answer_eligibility_guard: groundedAnswerEligibilityGuard, - ...(followupBinding.usage ? { followup_state_usage: followupBinding.usage } : {}), - problem_centric_answer_applied: composition.problem_centric_answer_applied ?? false, - problem_units_used_count: composition.problem_units_used_count ?? 0, - problem_answer_mode: composition.problem_answer_mode ?? "stage1_policy_v11", - ...(Array.isArray(composition.problem_unit_ids_used) && composition.problem_unit_ids_used.length > 0 - ? { - problem_unit_ids_used: composition.problem_unit_ids_used - } - : {}), - answer_structure_v11: answerStructureV11, - investigation_state_snapshot: investigationStateSnapshot, - fallback_type: composition.fallback_type, - assistant_reply: safeAssistantReply, - reply_type: composition.reply_type, - trace_id: normalized.trace_id - } + questionId: userItem.message_id, + userMessage, + normalized: { + trace_id: normalized.trace_id, + prompt_version: normalized.prompt_version, + schema_version: normalized.schema_version, + normalized: normalized.normalized + }, + normalizedQuestion: followupBinding.normalizedQuestion, + routeSummary: resolvedRouteSummary, + executionPlan, + requirementExtractionRequirements: requirementExtraction.requirements, + coverageEvaluationRequirements: coverageEvaluation.requirements, + coverageReport: coverageEvaluation.coverage, + groundingCheck, + retrievalCalls, + retrievalResultsRaw, + retrievalResults, + questionTypeClass, + companyAnchors, + runtimeAnalysisContext, + businessScopeResolution, + temporalGuard, + polarityAudit: polarityGuardResult.audit, + claimAnchorAudit, + targetedEvidenceAudit: targetedEvidenceResult.audit, + evidenceAdmissibilityGateAudit: evidenceGateResult.audit, + rbpLiveRouteAudit, + faLiveRouteAudit, + groundedAnswerEligibilityGuard, + followupStateUsage: followupBinding.usage, + followupApplied: Boolean(followupBinding.usage?.applied), + composition, + featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11, + featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11, + previousInvestigationState: session.investigation_state, + addressRuntimeMetaForDeep, + extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload), + buildDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary), + extractExecutionState: (normalizedPayload) => extractExecutionState(normalizedPayload), + sanitizeReply: (value, fallback) => sanitizeOutgoingAssistantText(value, fallback), + persistInvestigationState: (targetSessionId, snapshot) => this.sessions.setInvestigationState(targetSessionId, snapshot), + messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}` }); - return { - ok: true, - session_id: sessionId, - assistant_reply: safeAssistantReply, - reply_type: composition.reply_type, - conversation_item: assistantItem, + const safeAssistantReply = packagingRuntime.safeAssistantReply; + const debug = packagingRuntime.debug; + const assistantItem = packagingRuntime.assistantItem; + const deepAnalysisLogDetails = packagingRuntime.deepAnalysisLogDetails; + const commitResult = (0, assistantTurnCommitRuntimeAdapter_1.commitAssistantTurnAndLog)({ + sessionId, + assistantItem, + eventType: "assistant_message", + logDetails: deepAnalysisLogDetails, + appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item), + getSession: (targetSessionId) => this.sessions.getSession(targetSessionId), + persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState), + cloneConversation: (items) => cloneItems(items), + logEvent: (payload) => (0, log_1.logJson)(payload) + }); + const conversation = commitResult.conversation; + return (0, assistantDeepTurnResponseBuilder_1.buildAssistantDeepTurnSuccessResponse)({ + sessionId, + assistantReply: safeAssistantReply, + replyType: composition.reply_type, + conversationItem: assistantItem, debug, conversation - }; + }); } } exports.AssistantService = AssistantService; diff --git a/llm_normalizer/backend/dist/services/assistantTurnCommitRuntimeAdapter.js b/llm_normalizer/backend/dist/services/assistantTurnCommitRuntimeAdapter.js new file mode 100644 index 0000000..0e965cf --- /dev/null +++ b/llm_normalizer/backend/dist/services/assistantTurnCommitRuntimeAdapter.js @@ -0,0 +1,24 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.commitAssistantTurnAndLog = commitAssistantTurnAndLog; +function commitAssistantTurnAndLog(input) { + input.appendItem(input.sessionId, input.assistantItem); + const currentSession = input.getSession(input.sessionId); + if (currentSession) { + input.persistSession(currentSession); + } + const conversation = input.cloneConversation(currentSession?.items ?? []); + input.logEvent({ + timestamp: (input.nowIso ?? (() => new Date().toISOString()))(), + level: "info", + service: "assistant_loop", + message: "assistant_message_processed", + sessionId: input.sessionId, + eventType: input.eventType, + details: input.logDetails + }); + return { + currentSession, + conversation + }; +} diff --git a/llm_normalizer/backend/dist/services/evalService.js b/llm_normalizer/backend/dist/services/evalService.js index 1430358..59a44f9 100644 --- a/llm_normalizer/backend/dist/services/evalService.js +++ b/llm_normalizer/backend/dist/services/evalService.js @@ -169,6 +169,29 @@ function parseRawQuestions(rawQuestions) { .filter(Boolean); return byLine.length > 0 ? byLine : [text]; } +function normalizeAnalysisDate(value) { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!match) { + return null; + } + const year = Number(match[1]); + const month = Number(match[2]); + const day = Number(match[3]); + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + return null; + } + const candidate = new Date(Date.UTC(year, month - 1, day)); + if (candidate.getUTCFullYear() !== year || + candidate.getUTCMonth() + 1 !== month || + candidate.getUTCDate() !== day) { + return null; + } + return `${match[1]}-${match[2]}-${match[3]}`; +} function executionReadinessOf(fragment) { return "execution_readiness" in fragment ? fragment.execution_readiness : "executable"; } @@ -759,6 +782,13 @@ class EvalService { ...payload.normalizeConfig, userQuestion: item.raw_question, context: { + period_hint: payload.analysisDate ?? undefined, + analysis_context: payload.analysisDate + ? { + as_of_date: payload.analysisDate, + source: "eval_analysis_date" + } + : undefined, eval_label: runId, case_id: item.case_id, eval_mode: payload.mode @@ -1553,6 +1583,7 @@ class EvalService { const suite = parseAssistantSuiteFile(payload.caseSetFile); const suiteCases = suite.cases.filter((item) => !payload.caseIds || payload.caseIds.includes(item.case_id)); const runId = typeof payload.runId === "string" && payload.runId.trim().length > 0 ? payload.runId.trim() : `assistant-stage1-${(0, nanoid_1.nanoid)(10)}`; + const analysisDate = normalizeAnalysisDate(payload.analysisDate); const assistantService = new assistantService_1.AssistantService(this.normalizerService, new assistantSessionStore_1.AssistantSessionStore()); const diagnostics = []; let requestsTotal = 0; @@ -1579,6 +1610,15 @@ class EvalService { developerPrompt: payload.normalizeConfig.developerPrompt, domainPrompt: payload.normalizeConfig.domainPrompt, fewShotExamples: payload.normalizeConfig.fewShotExamples, + context: analysisDate + ? { + period_hint: analysisDate, + analysis_context: { + as_of_date: analysisDate, + source: "eval_analysis_date" + } + } + : undefined, useMock: payload.useMock })); turnResponses.push(response); @@ -1820,6 +1860,7 @@ class EvalService { eval_target: "assistant_stage1", mode: payload.mode, use_mock: Boolean(payload.useMock), + analysis_date: analysisDate, prompt_version: payload.normalizeConfig.promptVersion ?? null, suite_id: suite.suite_id, suite_version: suite.suite_version, @@ -1887,6 +1928,7 @@ class EvalService { const suite = parseAssistantStage2SuiteFile(payload.caseSetFile); const suiteCases = suite.cases.filter((item) => !payload.caseIds || payload.caseIds.includes(item.case_id)); const runId = typeof payload.runId === "string" && payload.runId.trim().length > 0 ? payload.runId.trim() : `assistant-stage2-${(0, nanoid_1.nanoid)(10)}`; + const analysisDate = normalizeAnalysisDate(payload.analysisDate); const assistantService = new assistantService_1.AssistantService(this.normalizerService, new assistantSessionStore_1.AssistantSessionStore()); const diagnostics = []; let requestsTotal = 0; @@ -1915,6 +1957,15 @@ class EvalService { developerPrompt: payload.normalizeConfig.developerPrompt, domainPrompt: payload.normalizeConfig.domainPrompt, fewShotExamples: payload.normalizeConfig.fewShotExamples, + context: analysisDate + ? { + period_hint: analysisDate, + analysis_context: { + as_of_date: analysisDate, + source: "eval_analysis_date" + } + } + : undefined, useMock: payload.useMock })); turnResponses.push(response); @@ -2090,6 +2141,7 @@ class EvalService { eval_target: "assistant_stage2", mode: payload.mode, use_mock: Boolean(payload.useMock), + analysis_date: analysisDate, prompt_version: payload.normalizeConfig.promptVersion ?? null, suite_id: suite.suite_id, suite_version: suite.suite_version, @@ -2172,6 +2224,7 @@ class EvalService { async run(payload) { const mode = payload.mode ?? "standard"; const evalTarget = payload.evalTarget ?? "normalizer"; + const analysisDate = normalizeAnalysisDate(payload.analysisDate); if (evalTarget === "assistant_stage1") { return this.runAssistantStage1({ normalizeConfig: payload.normalizeConfig, @@ -2180,6 +2233,7 @@ class EvalService { mode, caseSetFile: payload.caseSetFile, compareWithReportFile: payload.compareWithReportFile, + analysisDate: analysisDate ?? undefined, runId: payload.runId }); } @@ -2191,6 +2245,7 @@ class EvalService { mode, caseSetFile: payload.caseSetFile, compareWithReportFile: payload.compareWithReportFile, + analysisDate: analysisDate ?? undefined, runId: payload.runId }); } @@ -2231,6 +2286,7 @@ class EvalService { return this.runV2({ ...payload, mode, + analysisDate: analysisDate ?? undefined, cases: filtered }); } @@ -2256,6 +2312,13 @@ class EvalService { ...payload.normalizeConfig, userQuestion: item.raw_question, context: { + period_hint: analysisDate ?? undefined, + analysis_context: analysisDate + ? { + as_of_date: analysisDate, + source: "eval_analysis_date" + } + : undefined, expected_route: item.expected.route_hint, eval_label: runId, case_id: item.case_id, @@ -2366,6 +2429,7 @@ class EvalService { timestamp: new Date().toISOString(), mode, use_mock: Boolean(payload.useMock), + analysis_date: analysisDate, prompt_version: payload.normalizeConfig.promptVersion ?? null, dataset: { source: payload.caseSetFile ? "file" : "data/eval_cases/*.json", diff --git a/llm_normalizer/backend/dist/services/questionTypeResolver.js b/llm_normalizer/backend/dist/services/questionTypeResolver.js index 97474c7..7ec0da4 100644 --- a/llm_normalizer/backend/dist/services/questionTypeResolver.js +++ b/llm_normalizer/backend/dist/services/questionTypeResolver.js @@ -99,7 +99,7 @@ function resolveQuestionType(input) { if (bestType !== "unknown") { return bestType; } - if (/[?пјџ]/u.test(text)) { + if (/(?:\bwhy\b|почему|из-?за\s+чего|в\s+ч(?:е|ё)м\s+причина)/iu.test(text)) { return "why_breaks"; } return "unknown"; diff --git a/llm_normalizer/backend/src/routes/autoRuns.ts b/llm_normalizer/backend/src/routes/autoRuns.ts index 6f615de..6cb79ff 100644 --- a/llm_normalizer/backend/src/routes/autoRuns.ts +++ b/llm_normalizer/backend/src/routes/autoRuns.ts @@ -93,6 +93,7 @@ interface RunSummary { llm_provider: string | null; model: string | null; use_mock: boolean | null; + analysis_date: string | null; prompt_version: string | null; schema_version: string | null; suite_id: string | null; @@ -1012,6 +1013,7 @@ function buildRunSummary(run: IndexedRun): RunSummary { llm_provider: llmProvider, model, use_mock: toBooleanSafe(run.report.use_mock), + analysis_date: toStringSafe(run.report.analysis_date), prompt_version: toStringSafe(run.report.prompt_version), schema_version: toStringSafe(run.report.schema_version), suite_id: toStringSafe(run.report.suite_id), diff --git a/llm_normalizer/backend/src/routes/eval.ts b/llm_normalizer/backend/src/routes/eval.ts index 2a57be0..9ec0728 100644 --- a/llm_normalizer/backend/src/routes/eval.ts +++ b/llm_normalizer/backend/src/routes/eval.ts @@ -35,6 +35,7 @@ interface EvalAsyncJob { eval_target: EvalTarget; run_id: string; case_set_file: string | null; + analysis_date: string | null; total_cases: number; completed_cases: number; cases: EvalAsyncCaseInfo[]; @@ -131,6 +132,32 @@ function normalizeCaseIds(value: unknown): string[] | undefined { return normalized.length > 0 ? normalized : undefined; } +function normalizeAnalysisDate(value: unknown): string | undefined { + if (typeof value !== "string") { + return undefined; + } + const trimmed = value.trim(); + const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!match) { + return undefined; + } + const year = Number(match[1]); + const month = Number(match[2]); + const day = Number(match[3]); + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + return undefined; + } + const candidate = new Date(Date.UTC(year, month - 1, day)); + if ( + candidate.getUTCFullYear() !== year || + candidate.getUTCMonth() + 1 !== month || + candidate.getUTCDate() !== day + ) { + return undefined; + } + return `${match[1]}-${match[2]}-${match[3]}`; +} + function buildEvalPayloadFromBody(body: Record): { normalizeConfig: Omit; caseIds?: string[]; @@ -140,7 +167,11 @@ function buildEvalPayloadFromBody(body: Record): { rawQuestions?: string; evalTarget: EvalTarget; compareWithReportFile?: string; + analysisDate?: string; } { + const analysisDate = + normalizeAnalysisDate(body.analysis_date) ?? + normalizeAnalysisDate(body.analysisDate); return { normalizeConfig: (body.normalizeConfig ?? {}) as Omit, caseIds: normalizeCaseIds(body.caseIds), @@ -154,7 +185,8 @@ function buildEvalPayloadFromBody(body: Record): { ? body.compare_with_report_file : typeof body.comparisonBaselineReportFile === "string" ? body.comparisonBaselineReportFile - : undefined + : undefined, + analysisDate }; } @@ -300,6 +332,7 @@ function snapshotJob(job: EvalAsyncJob): Record { eval_target: job.eval_target, run_id: job.run_id, case_set_file: job.case_set_file, + analysis_date: job.analysis_date, total_cases: job.total_cases, completed_cases: job.completed_cases, error: job.error, @@ -314,7 +347,8 @@ function snapshotJob(job: EvalAsyncJob): Record { : toRecord(job.report.metrics) && typeof toRecord(job.report.metrics)?.score_index === "number" ? Number(toRecord(job.report.metrics)?.score_index) : null, - cases_total: typeof job.report.cases_total === "number" ? Number(job.report.cases_total) : null + cases_total: typeof job.report.cases_total === "number" ? Number(job.report.cases_total) : null, + analysis_date: toStringSafe(job.report.analysis_date) ?? job.analysis_date } : null }; @@ -377,6 +411,7 @@ export function buildEvalRouter(services: AppServices): Router { eval_target: payload.evalTarget, run_id: runId, case_set_file: runtimeCaseSetFile, + analysis_date: payload.analysisDate ?? null, total_cases: caseSeeds.length, completed_cases: 0, cases: caseSeeds.map((item) => ({ diff --git a/llm_normalizer/backend/src/services/addressFilterExtractor.ts b/llm_normalizer/backend/src/services/addressFilterExtractor.ts index f4a69fb..f9c1047 100644 --- a/llm_normalizer/backend/src/services/addressFilterExtractor.ts +++ b/llm_normalizer/backend/src/services/addressFilterExtractor.ts @@ -490,8 +490,20 @@ function extractLooseByAnchorValue(text: string): string | undefined { } const lowered = token.toLowerCase(); const stopWords = new Set([ + "какой", + "какая", + "какие", + "каких", + "каким", + "какими", + "каком", + "кто", + "что", + "мы", + "видим", "контрагенту", "контрагента", + "контрагентам", "контре", "компании", "компанию", @@ -499,10 +511,14 @@ function extractLooseByAnchorValue(text: string): string | undefined { "организацию", "поставщику", "поставщика", + "поставщикам", "клиенту", "клиента", + "клиентам", "покупателю", "покупателя", + "покупателям", + "заказчикам", "партнеру", "партнера", "договору", @@ -618,6 +634,9 @@ function isLikelyCounterpartyToken(rawToken: string): boolean { "какая", "какое", "каких", + "каким", + "какими", + "каком", "какому", "какую", "кто", @@ -632,6 +651,8 @@ function isLikelyCounterpartyToken(rawToken: string): boolean { "чья", "чей", "чью", + "мы", + "видим", "самый", "самая", "самое", @@ -686,10 +707,23 @@ function isLikelyCounterpartyToken(rawToken: string): boolean { "контрагент", "контрагенту", "контрагента", + "контрагентам", "компания", "компании", "организация", "организации", + "поставщикам", + "клиентам", + "покупателям", + "заказчикам", + "аванс", + "авансы", + "проблемный", + "проблемные", + "проблемным", + "закрытия", + "закрыть", + "закрыты", "год", "года", "г", @@ -795,7 +829,7 @@ function isLowQualityCounterpartyAnchorValue(rawValue: string): boolean { return true; } const questionCue = - /(?:кто|что|какой|какая|какие|какого|сколько|где|когда|почему|зачем|which|who|what|how\s+many)/iu.test(value) || + /(?:кто|что|какой|какая|какие|какого|каких|каким|какими|каком|сколько|где|когда|почему|зачем|which|who|what|how\s+many)/iu.test(value) || /[?]/u.test(String(rawValue ?? "")); const rankingCue = /(?:больше|меньше|сам(?:ый|ая|ое|ые)|крупн|жирн|максим|миним)/iu.test(value); const paymentCue = /(?:плат(?:ит|ят|еж|ёж|ежн|ежей|ежа)|денег|деньг|money|payment)/iu.test(value); diff --git a/llm_normalizer/backend/src/services/addressIntentResolver.ts b/llm_normalizer/backend/src/services/addressIntentResolver.ts index d23c02c..cac079e 100644 --- a/llm_normalizer/backend/src/services/addressIntentResolver.ts +++ b/llm_normalizer/backend/src/services/addressIntentResolver.ts @@ -667,6 +667,13 @@ function hasLifecycleSegmentationSignal(text: string): boolean { } function hasCounterpartyActivityLifecycleSignal(text: string): boolean { + const hasPaymentRiskLexeme = + /(?:не\s+плат(?:ит|ят|ил|или)|без\s+оплат|оплат(?:ы|а)?\s+нет|нет\s+оплат|задерж(?:ива|к)|просроч|долг|задолж)/iu.test( + text + ); + if (hasPaymentRiskLexeme) { + return false; + } if ((hasDocumentSignal(text) || hasBankOperationSignal(text)) && !hasLifecycleSegmentationSignal(text)) { return false; } @@ -768,6 +775,10 @@ function hasCustomerRevenueAndPaymentsSignal(text: string): boolean { ); const asksRevenueTotal = /(?:сколько|скока|скок).*(?:денег|выручк|доход|заработ|оборот)/iu.test(text); const asksOverallTurnover = /(?:общ(?:ий|ие|ая)\s+оборот|общ(?:ая|ий)\s+выручк|total\s+turnover|turnover\s+total)/iu.test(text); + const asksMajorShare = + /(?:основн(?:ую|ая|ые|ой)\s+част|больш(?:ую|ая|ие)\s+част|львин(?:ая|ую)\s+дол[яю]|ключев(?:ую|ая)\s+част)/iu.test( + text + ); const asksValue = /(?:доходн|выручк|приход|поступлен|входящ|зачислен|оплат|плат(?:еж|ёж|ежн|ежей|ежа|ит|ят)|деньг|денег|заработ|оборот|чек|сделк|бюджет|занес|занёс|принес|принёс|revenue|inflow|deal|turnover)/iu.test( text @@ -797,6 +808,9 @@ function hasCustomerRevenueAndPaymentsSignal(text: string): boolean { if (asksCounterpartySource && asksValue) { return true; } + if (!hasFuzzySupplierLexeme && (asksCustomerGroup || hasCounterpartyLexeme) && asksMajorShare && asksValue) { + return true; + } if (!hasFuzzySupplierLexeme && asksIncomingFlow && asksRankOrTop) { return true; } @@ -920,6 +934,71 @@ function hasOpenContractsListSignal(text: string): boolean { return true; } +function hasSupplierTailRiskSignal(text: string): boolean { + const hasSupplier = /(?:поставщик|supplier|vendor)/iu.test(text); + const hasTail = /(?:хвост|висят|незакрыт|задолж|долг|просроч)/iu.test(text); + const hasRisk = /(?:систематич|регулярн|проблем|тревог|не\s+разов|больше\s+похож)/iu.test(text); + const hasPeriodCue = /(?:на\s+конец\s+(?:месяц|период)|конец\s+месяц|пару\s+месяц|несколько\s+месяц)/iu.test(text); + return hasSupplier && hasTail && (hasRisk || hasPeriodCue); +} + +function hasReceivablesLatencyRiskSignal(text: string): boolean { + const hasBuyer = /(?:покупател|клиент|заказчик|customer|buyer)/iu.test(text); + const hasCounterparty = /(?:контрагент|counterparty|partner)/iu.test(text); + const hasPayment = /(?:оплат|платеж|платёж|payment)/iu.test(text); + const hasShipment = /(?:отправк|отгруз|реализ|shipment|delivery)/iu.test(text); + const hasDelay = /(?:длинн|долг|просроч|задерж|висят|тревог|too\s+long|late)/iu.test(text); + const hasNonPayment = /(?:не\s+плат(?:ит|ят|ил|или)|без\s+оплат|оплат(?:ы|а)?\s+нет|нет\s+оплат|неоплач)/iu.test(text); + const hasPeriodOrRiskCue = /(?:за\s+текущ|на\s+конец|тревог|просроч|задерж|долг|длинн)/iu.test(text); + const hasBetweenShipmentAndPayment = + /между[\s\S]{0,80}(?:отправк|отгруз|реализ)[\s\S]{0,80}(?:оплат|платеж|платёж|payment)/iu.test(text); + if (hasBuyer && hasPayment && ((hasShipment && hasDelay) || hasBetweenShipmentAndPayment)) { + return true; + } + return (hasBuyer || hasCounterparty) && hasNonPayment && hasPeriodOrRiskCue; +} + +function hasSettlementGapSignal(text: string): boolean { + const hasPayment = /(?:платеж|платёж|оплат|списани|поступлен|payment)/iu.test(text); + const hasDocument = /(?:док(?:и|умент|ументы|ументов)|docs?|documents?)/iu.test(text); + const hasAdvance = /(?:аванс|предоплат)/iu.test(text); + const hasNoDocumentForClosing = + /(?:нет|без)\s+(?:док(?:и|умент|ументы|ументов)|закрывающ)/iu.test(text) && + /(?:закрыти|взаиморасч|акт)/iu.test(text); + const hasNoDocumentForClosingReversed = + /(?:док(?:и|умент|ументы|ументов)|закрывающ)[\s\S]{0,48}(?:нет|без)/iu.test(text) && + /(?:закрыти|взаиморасч|акт)/iu.test(text); + const hasNoPayments = + /(?:нет|без)\s+(?:оплат|платеж|платёж|payment)/iu.test(text) || + /(?:оплат|платеж|платёж|payment)\s+нет/iu.test(text); + const hasDocsWithoutPayments = hasDocument && hasNoPayments; + const hasPaymentsWithoutClosingDocs = hasPayment && (hasNoDocumentForClosing || hasNoDocumentForClosingReversed); + const hasUnclosedAdvanceGap = + hasAdvance && + (/(?:не\s+закрыт|незакрыт|долго\s+не\s+закрыт|давно\s+не\s+закрыт)/iu.test(text) || + hasNoDocumentForClosing || + hasNoDocumentForClosingReversed); + return hasPaymentsWithoutClosingDocs || hasDocsWithoutPayments || hasUnclosedAdvanceGap; +} + +function hasReconciliationMismatchSignal(text: string): boolean { + const hasCounterparty = + /(?:контрагент|поставщик|клиент|покупател|customer|supplier|counterparty)/iu.test(text); + const hasReconciliationLexeme = /(?:акт(?:а|ом|ах)?\s+свер(?:к|ок)|свер(?:к|ок))/iu.test(text); + const hasMismatchLexeme = + /(?:не\s+совпад|несовпад|расхожд|расход|не\s+сход|несход|разъех|разниц|не\s+бь[её]т)/iu.test(text); + const hasBalanceLexeme = /(?:сальд|остат|баланс|saldo|balance)/iu.test(text); + const hasLookupVerb = /(?:покажи|выведи|найд[иь]|show|list)/iu.test(text); + const hasInterrogativeLookup = /(?:по\s+каким|у\s+кого|какие|какой|кто|где)/iu.test(text); + return ( + hasCounterparty && + hasReconciliationLexeme && + hasMismatchLexeme && + hasBalanceLexeme && + (hasLookupVerb || hasInterrogativeLookup) + ); +} + function isLikelyCounterpartyToken(rawToken: string): boolean { const token = String(rawToken ?? "").trim().toLowerCase(); if (!token || token.length < 2) { @@ -1273,6 +1352,38 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti }; } + if (hasSettlementGapSignal(text)) { + return { + intent: "list_open_contracts", + confidence: "medium", + reasons: ["settlement_gap_signal_detected"] + }; + } + + if (hasReconciliationMismatchSignal(text)) { + return { + intent: "list_open_contracts", + confidence: "medium", + reasons: ["reconciliation_mismatch_signal_detected"] + }; + } + + if (hasReceivablesLatencyRiskSignal(text)) { + return { + intent: "list_receivables_counterparties", + confidence: "medium", + reasons: ["receivables_payment_lag_signal_detected"] + }; + } + + if (hasSupplierTailRiskSignal(text)) { + return { + intent: "list_payables_counterparties", + confidence: "medium", + reasons: ["supplier_tail_risk_signal_detected"] + }; + } + if (hasDocumentsFormingBalanceSignal(text) && hasDocumentsFormingBalanceAccountAnchor(text)) { return { intent: "documents_forming_balance", @@ -1299,7 +1410,9 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti if ( hasAny(text, OPEN_ITEMS_HINTS) && - (text.includes("контраг") || text.includes("договор") || text.includes("контракт") || text.includes("counterparty") || text.includes("contract")) + /(?:контраг|договор|контракт|counterparty|contract|покупател|клиент|заказчик|customer|client|buyer|supplier|поставщик)/iu.test( + text + ) ) { return { intent: "open_items_by_counterparty_or_contract", diff --git a/llm_normalizer/backend/src/services/addressQueryService.ts b/llm_normalizer/backend/src/services/addressQueryService.ts index 4a5331f..555d8d6 100644 --- a/llm_normalizer/backend/src/services/addressQueryService.ts +++ b/llm_normalizer/backend/src/services/addressQueryService.ts @@ -30,6 +30,7 @@ interface NormalizedAddressRow { interface AddressTryHandleOptions { followupContext?: AddressFollowupContext | null; + analysisDateHint?: string | null; } const ACCOUNT_SCOPE_FIELDS_CHECKED = ["account_dt", "account_kt", "registrator", "analytics"] as const; @@ -121,6 +122,36 @@ function parseFiniteNumber(value: unknown): number | null { return null; } +function normalizeAnalysisDateHint(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + if (!trimmed) { + return null; + } + const strictDate = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); + const isoPrefix = strictDate ?? trimmed.match(/^(\d{4})-(\d{2})-(\d{2})T/i); + if (!isoPrefix) { + return null; + } + const year = Number(isoPrefix[1]); + const month = Number(isoPrefix[2]); + const day = Number(isoPrefix[3]); + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + return null; + } + const candidate = new Date(Date.UTC(year, month - 1, day)); + if ( + candidate.getUTCFullYear() !== year || + candidate.getUTCMonth() + 1 !== month || + candidate.getUTCDate() !== day + ) { + return null; + } + return `${isoPrefix[1]}-${isoPrefix[2]}-${isoPrefix[3]}`; +} + function valueAsString(value: unknown): string { if (value === null || value === undefined) { return ""; @@ -788,6 +819,58 @@ function runtimeReadinessForLimitedCategory(category: AddressLimitedReasonCatego return "UNKNOWN"; } +function normalizeLimitedReason(reason: string): string { + let normalized = String(reason ?? "").trim(); + if (!normalized) { + return "не хватает подтвержденных данных для уверенного вывода"; + } + + const replacements: Array<[RegExp, string]> = [ + [/address_query\s*v?1/giu, "текущий адресный режим"], + [/address\s*v1/giu, "текущий адресный режим"], + [/intent-specific\s+recipe/giu, "встроенный фильтр сценария"], + [/live\s+recipe/giu, "текущий сценарий выборки"], + [/materialized\s+live-строках/giu, "доступном срезе данных"], + [/live-выборке/giu, "выборке данных"], + [/live-данных/giu, "данных"], + [/deep-analysis/giu, "режим расширенной проверки"], + [/\blookup\b/giu, "поиск"], + [/\bintent\b/giu, "сценария"], + [/\brecipe\b/giu, "шаблон выборки"], + [/\byakor\b/giu, "ориентир"], + [/\banchor\b/giu, "ориентир"], + [/\s+/gu, " "] + ]; + + for (const [pattern, value] of replacements) { + normalized = normalized.replace(pattern, value); + } + + return normalized.trim(); +} + +function normalizeLimitedNextStep(nextStep: string): string { + let normalized = String(nextStep ?? "").trim(); + if (!normalized) { + return ""; + } + + const replacements: Array<[RegExp, string]> = [ + [/address_query\s*v?1/giu, "текущий адресный режим"], + [/deep-analysis/giu, "режим расширенной проверки"], + [/\bP0 intent\b/giu, "поддерживаемый сценарий"], + [/\bintent\b/giu, "сценарий"], + [/\blookup\b/giu, "поиск"], + [/\s+/gu, " "] + ]; + + for (const [pattern, value] of replacements) { + normalized = normalized.replace(pattern, value); + } + + return normalized.trim(); +} + interface RowStageDiagnostics { rawRowKeysSample: string[]; materializationDropReason: @@ -945,20 +1028,28 @@ function toLegacyMcpStatus( function composeLimitedReply(category: AddressLimitedReasonCategory, reason: string, nextStep?: string): string { const heading = category === "empty_match" - ? "В live-данных по текущему фильтру записи не найдены." + ? "По текущим условиям в доступном срезе данных совпадений не нашлось." : category === "missing_anchor" - ? "Для точного адресного поиска не хватает обязательного якоря." + ? "Чтобы ответить надежно, нужен более точный ориентир в запросе." : category === "recipe_visibility_gap" - ? "Текущий live recipe не дает нужную видимость данных для этого сценария." + ? "Запрос понятен, но текущий режим не дает нужной детализации." : category === "unsupported" - ? "Этот запрос не подходит под address_query V1." - : "Не удалось выполнить адресный live-запрос в V1."; + ? "Сейчас этот тип вопроса вне поддерживаемого контура адресного режима." + : "Не удалось завершить проверку в адресном режиме."; + const reasonLine = + category === "unsupported" + ? "Коротко: этот сценарий пока не поддержан в текущем адресном контуре." + : category === "missing_anchor" + ? "Коротко: в запросе не хватает конкретного ориентира (контрагент, договор или период)." + : category === "recipe_visibility_gap" + ? "Коротко: для уверенного ответа нужен более специализированный сценарий выборки." + : `Коротко: ${normalizeLimitedReason(reason)}.`; const lines = [ heading, - `Причина: ${reason}.` + reasonLine ]; if (nextStep) { - lines.push(`Что нужно уточнить: ${nextStep}.`); + lines.push(`Что можно сделать дальше: ${normalizeLimitedNextStep(nextStep)}.`); } return lines.join("\n"); } @@ -1057,7 +1148,24 @@ export class AddressQueryService { if (!decompose) { return null; } - const { mode, shape, intent, filters, baseReasons } = decompose; + const { mode, shape, intent, filters } = decompose; + const baseReasons = [...decompose.baseReasons]; + const analysisDate = normalizeAnalysisDateHint(options.analysisDateHint); + if (analysisDate) { + const hasTemporalFilter = Boolean( + (typeof filters.extracted_filters.period_from === "string" && filters.extracted_filters.period_from.trim().length > 0) || + (typeof filters.extracted_filters.period_to === "string" && filters.extracted_filters.period_to.trim().length > 0) || + (typeof filters.extracted_filters.as_of_date === "string" && filters.extracted_filters.as_of_date.trim().length > 0) + ); + if (!hasTemporalFilter) { + filters.extracted_filters = { + ...filters.extracted_filters, + as_of_date: analysisDate + }; + filters.warnings = [...new Set([...(filters.warnings ?? []), "as_of_date_from_analysis_context"])]; + baseReasons.push("as_of_date_from_analysis_context"); + } + } const composeOptionsFromFilters = (filterSet: AddressFilterSet) => ({ userMessage, periodFrom: typeof filterSet.period_from === "string" ? filterSet.period_from : undefined, @@ -1079,8 +1187,8 @@ export class AddressQueryService { rowsFetched: 0, rowsMatched: 0, category: "unsupported", - reasonText: "intent пока не поддержан в address V1", - nextStep: "переформулируйте вопрос как адресный lookup по счету/контрагенту/договору", + reasonText: "сценарий пока вне поддерживаемого контура текущего адресного режима", + nextStep: "могу проверить близкие сценарии: документы/платежи по контрагенту, договоры или остаток по счету", limitations: ["intent_not_supported_in_v1"], reasons: baseReasons }); @@ -1123,8 +1231,8 @@ export class AddressQueryService { rowsFetched: 0, rowsMatched: 0, category: "recipe_visibility_gap", - reasonText: "для intent пока нет recipe в address V1", - nextStep: "выберите поддерживаемый P0 intent или переключите запрос в deep-analysis", + reasonText: "для этого сценария пока нет готового шаблона выборки в текущем режиме", + nextStep: "можно выбрать близкий поддерживаемый сценарий или переключить запрос в режим расширенной проверки", limitations: ["recipe_not_available"], reasons: [...baseReasons, ...recipeSelection.selection_reason] }); diff --git a/llm_normalizer/backend/src/services/address_runtime/composeStage.ts b/llm_normalizer/backend/src/services/address_runtime/composeStage.ts index 65d85f9..c7751f8 100644 --- a/llm_normalizer/backend/src/services/address_runtime/composeStage.ts +++ b/llm_normalizer/backend/src/services/address_runtime/composeStage.ts @@ -1509,7 +1509,7 @@ export function composeFactualReply( if (intent === "list_open_contracts") { const contracts = contractCandidatesFromRows(rows); const lines = [ - "Собраны кандидаты по незакрытым договорным позициям (по live движениям 60/62/76).", + "Проверил потенциальные разрывы во взаиморасчетах (платежи без закрытия и документы без оплат).", `Строк движения: ${rows.length}.`, `Договорных кандидатов: ${contracts.length}.` ]; @@ -1525,6 +1525,36 @@ export function composeFactualReply( }; } + if (intent === "list_payables_counterparties") { + const lines = [ + "Проверил поставщиков с признаками незакрытых хвостов по взаиморасчетам (контур 60/76).", + `Строк в выборке: ${rows.length}.`, + ...(rows.length > 0 + ? ["Ниже примеры строк для ручной проверки."] + : ["Явных признаков системной задолженности по доступному срезу не найдено."]), + ...formatTopRows(rows, 6) + ]; + return { + responseType: "FACTUAL_LIST", + text: lines.join("\n") + }; + } + + if (intent === "list_receivables_counterparties") { + const lines = [ + "Проверил покупателей с признаками затянутой оплаты (контур 62/76).", + `Строк в выборке: ${rows.length}.`, + ...(rows.length > 0 + ? ["Ниже примеры строк, которые стоит проверить в первую очередь."] + : ["Явных признаков затяжной дебиторки по доступному срезу не найдено."]), + ...formatTopRows(rows, 6) + ]; + return { + responseType: "FACTUAL_LIST", + text: lines.join("\n") + }; + } + if (intent === "open_items_by_counterparty_or_contract") { const lines = [ "Собраны открытые позиции по указанному фильтру (контрагент/договор).", @@ -1628,14 +1658,7 @@ export function composeFactualReply( }; } - const title = - intent === "list_payables_counterparties" - ? "Срез обязательств (payables) собран по движениям с account scope 60/76." - : intent === "list_receivables_counterparties" - ? "Срез требований (receivables) собран по движениям с account scope 62/76." - : "Срез адресного запроса собран."; - - const lines = [title, `Строк отобрано: ${rows.length}.`, ...formatTopRows(rows, 6)]; + const lines = ["Срез адресного запроса собран.", `Строк отобрано: ${rows.length}.`, ...formatTopRows(rows, 6)]; return { responseType: "FACTUAL_LIST", text: lines.join("\n") diff --git a/llm_normalizer/backend/src/services/assistantAnswerPackageBuilder.ts b/llm_normalizer/backend/src/services/assistantAnswerPackageBuilder.ts new file mode 100644 index 0000000..5d58e04 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantAnswerPackageBuilder.ts @@ -0,0 +1,159 @@ +import { FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1 } from "../config"; +import type { AnswerGroundingCheck, RequirementCoverageReport, UnifiedRetrievalResult } from "../types/assistant"; +import { + ANSWER_STRUCTURE_SCHEMA_VERSION, + type AnswerStructureV11, + type EvidenceLimitationReasonCode +} from "../types/stage1Contracts"; + +export interface BuildAssistantAnswerStructureV11Input { + assistantReply: string; + coverageReport: RequirementCoverageReport; + groundingCheck: AnswerGroundingCheck; + retrievalResults: UnifiedRetrievalResult[]; + options?: { + enableEvidenceEnrichment?: boolean; + }; +} + +const EVIDENCE_LIMITATION_REASON_CODE_SET: ReadonlySet = new Set([ + "snapshot_only", + "heuristic_inference", + "missing_mechanism", + "weak_source_mapping", + "insufficient_detail", + "unknown" +]); + +function summarizeUnique(values: Array, limit = 6): string[] { + return Array.from(new Set(values.map((item) => String(item ?? "").trim()).filter(Boolean))).slice(0, limit); +} + +function isEvidenceLimitationReasonCode(value: string): value is EvidenceLimitationReasonCode { + return EVIDENCE_LIMITATION_REASON_CODE_SET.has(value as EvidenceLimitationReasonCode); +} + +function firstNonEmptyLine(text: string): string { + const line = String(text ?? "") + .split("\n") + .map((item) => item.trim()) + .find((item) => item.length > 0); + return (line ?? String(text ?? "")).slice(0, 220); +} + +function buildClaimEvidenceLinks( + retrievalResults: UnifiedRetrievalResult[] +): NonNullable { + const byClaim = new Map(); + for (const result of retrievalResults) { + for (const evidence of result.evidence) { + const claimRef = String(evidence.claim_ref ?? "").trim(); + if (!claimRef) { + continue; + } + const evidenceId = String(evidence.evidence_id ?? "").trim(); + if (!evidenceId) { + continue; + } + const current = byClaim.get(claimRef) ?? []; + current.push(evidenceId); + byClaim.set(claimRef, current); + } + } + return Array.from(byClaim.entries()) + .slice(0, 10) + .map(([claimRef, evidenceIds]) => ({ + claim_ref: claimRef, + evidence_ids: summarizeUnique(evidenceIds, 10) + })); +} + +export function buildAssistantAnswerStructureV11(input: BuildAssistantAnswerStructureV11Input): AnswerStructureV11 { + const evidenceIds = summarizeUnique( + input.retrievalResults.flatMap((item) => item.evidence.map((evidence) => evidence.evidence_id)), + 10 + ); + const mechanismNotes = summarizeUnique( + input.retrievalResults.flatMap((item) => + item.evidence + .map((evidence) => evidence.mechanism_note) + .filter((note): note is string => typeof note === "string" && note.trim().length > 0) + ), + 6 + ); + const sourceRefs = summarizeUnique( + input.retrievalResults.flatMap((item) => + item.evidence + .map((evidence) => evidence.source_ref?.canonical_ref) + .filter((value): value is string => typeof value === "string" && value.trim().length > 0) + ), + 8 + ); + const limitationReasonCodes: EvidenceLimitationReasonCode[] = summarizeUnique( + input.retrievalResults.flatMap((item) => + item.evidence.flatMap((evidence) => { + const code = evidence.limitation?.reason_code; + return typeof code === "string" && code.trim().length > 0 ? [code] : []; + }) + ), + 8 + ).filter(isEvidenceLimitationReasonCode); + const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults); + const limitations = summarizeUnique( + [...input.retrievalResults.flatMap((item) => item.limitations), ...input.groundingCheck.reasons], + 8 + ); + const clarificationQuestions = input.coverageReport.clarification_needed_for.map( + (item) => `Уточните требование ${item}.` + ); + const recommendedActions = summarizeUnique( + [ + ...input.coverageReport.requirements_uncovered.map((item) => `Проверить непокрытое требование ${item}.`), + ...input.coverageReport.requirements_partially_covered.map( + (item) => `Доуточнить частично покрытое требование ${item}.` + ) + ], + 6 + ); + const mechanismStatus: AnswerStructureV11["mechanism_block"]["status"] = + mechanismNotes.length === 0 + ? "unresolved" + : limitationReasonCodes.includes("missing_mechanism") || limitationReasonCodes.includes("heuristic_inference") + ? "limited" + : "grounded"; + const enableEvidenceEnrichment = + input.options?.enableEvidenceEnrichment ?? FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1; + + return { + schema_version: ANSWER_STRUCTURE_SCHEMA_VERSION, + answer_summary: firstNonEmptyLine(input.assistantReply), + direct_answer: input.assistantReply, + mechanism_block: { + status: mechanismStatus, + mechanism_notes: mechanismNotes, + limitation_reason_codes: limitationReasonCodes + }, + evidence_block: { + evidence_ids: evidenceIds, + source_refs: sourceRefs, + mechanism_notes: mechanismNotes, + coverage_note: + input.coverageReport.requirements_total === input.coverageReport.requirements_covered + ? "coverage_full_or_near_full" + : "coverage_partial_or_limited", + ...(enableEvidenceEnrichment && claimEvidenceLinks.length > 0 + ? { + claim_evidence_links: claimEvidenceLinks + } + : {}) + }, + uncertainty_block: { + open_uncertainties: input.groundingCheck.missing_requirements, + limitations + }, + next_step_block: { + recommended_actions: recommendedActions, + clarification_questions: clarificationQuestions + } + }; +} diff --git a/llm_normalizer/backend/src/services/assistantContractsBundleAssembler.ts b/llm_normalizer/backend/src/services/assistantContractsBundleAssembler.ts new file mode 100644 index 0000000..0e0c949 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantContractsBundleAssembler.ts @@ -0,0 +1,97 @@ +import type { + AssistantReplyType, + AssistantRequirement, + AnswerGroundingCheck, + RequirementCoverageReport, + UnifiedRetrievalResult +} from "../types/assistant"; +import type { NormalizedPayload, RouteHintSummary } from "../types/normalizer"; +import { + buildAssistantCoverageContractV1, + buildAssistantExecutionPlanContractV1, + buildAssistantQueryFrameContractV1, + classifyAssistantOutcomeClassV1, + type AssistantCoverageContractV1, + type AssistantEvidenceBundleContractV1, + type AssistantExecutionPlanContractV1, + type AssistantOutcomeClassV1, + type AssistantQueryFrameContractV1 +} from "./assistantOrchestrationContracts"; + +export interface AssistantContractsBundleV1 { + queryFrameContractV1: AssistantQueryFrameContractV1; + executionPlanContractV1: AssistantExecutionPlanContractV1; + outcomeClassV1: AssistantOutcomeClassV1; + coverageContractV1: AssistantCoverageContractV1; + assistantOrchestrationContractsV1: { + query_frame: AssistantQueryFrameContractV1; + execution_plan: AssistantExecutionPlanContractV1; + evidence_bundle: AssistantEvidenceBundleContractV1; + coverage: AssistantCoverageContractV1; + }; +} + +export function assembleAssistantContractsBundleV1(input: { + userMessage: string; + normalizedQuestion: string; + normalized: NormalizedPayload | null; + routeSummary: RouteHintSummary | null; + droppedIntentSegments: string[]; + analysisContext: { + as_of_date: string | null; + period_from: string | null; + period_to: string | null; + source: string | null; + snapshot_mode: "auto" | "force_snapshot" | "force_live"; + } | null; + executionPlan: Array<{ + fragment_id: string; + requirement_ids: string[]; + route: string; + should_execute: boolean; + no_route_reason?: string | null; + clarification_reason?: string | null; + }>; + requirements: AssistantRequirement[]; + evidenceBundleContractV1: AssistantEvidenceBundleContractV1; + replyType: AssistantReplyType; + coverageReport: RequirementCoverageReport; + grounding: AnswerGroundingCheck; + retrievalResults: UnifiedRetrievalResult[]; +}): AssistantContractsBundleV1 { + const queryFrameContractV1 = buildAssistantQueryFrameContractV1({ + userMessage: input.userMessage, + normalizedQuestion: input.normalizedQuestion, + normalized: input.normalized, + routeSummary: input.routeSummary, + droppedIntentSegments: input.droppedIntentSegments, + analysisContext: input.analysisContext + }); + const executionPlanContractV1 = buildAssistantExecutionPlanContractV1({ + executionPlan: input.executionPlan, + requirements: input.requirements + }); + const outcomeClassV1 = classifyAssistantOutcomeClassV1({ + replyType: input.replyType, + coverageReport: input.coverageReport, + grounding: input.grounding, + retrievalResults: input.retrievalResults + }); + const coverageContractV1 = buildAssistantCoverageContractV1({ + coverageReport: input.coverageReport, + grounding: input.grounding, + outcomeClass: outcomeClassV1 + }); + return { + queryFrameContractV1, + executionPlanContractV1, + outcomeClassV1, + coverageContractV1, + assistantOrchestrationContractsV1: { + query_frame: queryFrameContractV1, + execution_plan: executionPlanContractV1, + evidence_bundle: input.evidenceBundleContractV1, + coverage: coverageContractV1 + } + }; +} diff --git a/llm_normalizer/backend/src/services/assistantCoverageGrounding.ts b/llm_normalizer/backend/src/services/assistantCoverageGrounding.ts new file mode 100644 index 0000000..4ccfc38 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantCoverageGrounding.ts @@ -0,0 +1,421 @@ +import type { + AnswerGroundingCheck, + AssistantRequirement, + RequirementCoverageReport, + UnifiedRetrievalResult +} from "../types/assistant"; +import type { RouteHintSummary } from "../types/normalizer"; + +interface SubjectTokenRule { + critical: boolean; + patterns: string[]; + routes?: string[]; +} + +export interface AssistantRequirementExtractionResult { + requirements: AssistantRequirement[]; + byFragment: Map; +} + +function summarizeUnique(values: Array, limit = 6): string[] { + return Array.from(new Set(values.map((item) => String(item ?? "").trim()).filter(Boolean))).slice(0, limit); +} + +const SUBJECT_TOKEN_RULES: Record = { + nds: { + critical: true, + patterns: [ + "vat", + "accumulationregister", + "ндс", + "книгипокупок", + "книгипродаж", + "налогнадобавленнуюстоимость" + ] + }, + os: { + critical: true, + patterns: ["fixed_asset", "fixedasset", "основн", "амортиз"] + }, + saldo: { + critical: true, + patterns: ["balance", "saldo", "сальдо", "остат"] + }, + counterparty: { + critical: false, + patterns: [ + "counterparty", + "supplier", + "buyer", + "counterparty_id", + "journal_counterparty", + "document_has_counterparty", + "контрагент", + "поставщик", + "покупател" + ], + routes: ["hybrid_store_plus_live", "store_feature_risk", "store_canonical"] + }, + document: { + critical: false, + patterns: [ + "document", + "recorder", + "journal", + "document_refs_count", + "recorded_by_document", + "journal_refers_to_document", + "документ" + ], + routes: ["hybrid_store_plus_live", "store_feature_risk", "store_canonical", "live_mcp_drilldown"] + }, + anomaly: { + critical: false, + patterns: [ + "risk", + "risk_score", + "unknown_link_count", + "zero_guid", + "navigation_links", + "missing_counterparty_link", + "аномал", + "риск" + ], + routes: ["store_feature_risk", "batch_refresh_then_store"] + }, + chain: { + critical: false, + patterns: ["chain", "cross_entity_chain", "relation_types", "operations_count", "matched_counterparties", "цепоч"], + routes: ["hybrid_store_plus_live"] + } +}; + +function hasRegexMatch(corpus: string, pattern: RegExp): boolean { + try { + return pattern.test(corpus); + } catch { + return false; + } +} + +function evaluateSubjectTokenMatch( + token: string, + corpus: string, + executedRoutes: Set +): { + matched: boolean; + critical: boolean; +} { + if (token.startsWith("account_")) { + const account = token.slice("account_".length).trim(); + if (!account) { + return { matched: false, critical: true }; + } + const escaped = account.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + const accountPattern = new RegExp(`(^|[^0-9])${escaped}([^0-9]|$)`, "i"); + return { matched: hasRegexMatch(corpus, accountPattern), critical: true }; + } + const rule = SUBJECT_TOKEN_RULES[token]; + if (rule) { + const byPattern = rule.patterns.some((pattern) => corpus.includes(pattern)); + const byRoute = Array.isArray(rule.routes) ? rule.routes.some((route) => executedRoutes.has(route)) : false; + return { matched: byPattern || byRoute, critical: rule.critical }; + } + return { matched: corpus.includes(token), critical: false }; +} + +function evidenceCountForRequirement(requirementId: string, result: UnifiedRetrievalResult): number { + const evidence = Array.isArray(result.evidence) ? result.evidence : []; + if (evidence.length === 0) { + return 0; + } + const tagged = evidence.filter((item) => { + const claimRef = typeof item?.claim_ref === "string" ? item.claim_ref : ""; + return claimRef.toLowerCase() === `requirement:${String(requirementId).toLowerCase()}`; + }).length; + if (tagged > 0) { + return tagged; + } + if ( + Array.isArray(result.requirement_ids) && + result.requirement_ids.length === 1 && + result.requirement_ids[0] === requirementId + ) { + return evidence.length; + } + return 0; +} + +function hasSubstantiveCoverageForRequirement(requirementId: string, result: UnifiedRetrievalResult): boolean { + const evidenceCount = evidenceCountForRequirement(requirementId, result); + if (evidenceCount > 0) { + return true; + } + const problemUnitsCount = Array.isArray(result.problem_units) ? result.problem_units.length : 0; + const candidateEvidenceCount = Array.isArray(result.candidate_evidence) ? result.candidate_evidence.length : 0; + if (problemUnitsCount > 0 || candidateEvidenceCount > 0) { + if ( + Array.isArray(result.requirement_ids) && + result.requirement_ids.length === 1 && + result.requirement_ids[0] === requirementId + ) { + return true; + } + } + return false; +} + +export function extractRequirementsForRoute(input: { + routeSummary: RouteHintSummary | null; + userMessage: string; + fragmentTextById: Map; + extractSubjectTokens: (text: string) => string[]; +}): AssistantRequirementExtractionResult { + const byFragment = new Map(); + const requirements: AssistantRequirement[] = []; + + const pushRequirement = (item: { + requirement_id: string; + source_fragment_id: string | null; + requirement_text: string; + status: AssistantRequirement["status"]; + route: string | null; + }): void => { + const subjectTokens = input.extractSubjectTokens(item.requirement_text); + requirements.push({ + requirement_id: item.requirement_id, + source_fragment_id: item.source_fragment_id, + requirement_text: item.requirement_text, + subject_tokens: subjectTokens, + status: item.status, + route: item.route + }); + if (item.source_fragment_id) { + const current = byFragment.get(item.source_fragment_id) ?? []; + current.push(item.requirement_id); + byFragment.set(item.source_fragment_id, current); + } + }; + + if (!input.routeSummary) { + pushRequirement({ + requirement_id: "R1", + source_fragment_id: null, + requirement_text: input.userMessage, + status: "clarification_needed", + route: null + }); + return { requirements, byFragment }; + } + + if (input.routeSummary.mode === "legacy_v1") { + pushRequirement({ + requirement_id: "R1", + source_fragment_id: "F1", + requirement_text: input.userMessage, + status: "covered", + route: input.routeSummary.route_hint + }); + return { requirements, byFragment }; + } + + input.routeSummary.decisions.forEach((decision, index) => { + const requirementId = `R${index + 1}`; + const text = input.fragmentTextById.get(decision.fragment_id) ?? input.userMessage; + let status: AssistantRequirement["status"] = "covered"; + if (decision.route === "no_route") { + if (decision.no_route_reason === "out_of_scope") { + status = "out_of_scope"; + } else if (decision.no_route_reason === "insufficient_specificity") { + status = "clarification_needed"; + } else { + status = "uncovered"; + } + } + pushRequirement({ + requirement_id: requirementId, + source_fragment_id: decision.fragment_id, + requirement_text: text, + status, + route: decision.route === "no_route" ? null : decision.route + }); + }); + + return { requirements, byFragment }; +} + +export function evaluateCoverageForRequirements( + requirements: AssistantRequirement[], + retrievalResults: UnifiedRetrievalResult[] +): { + requirements: AssistantRequirement[]; + coverage: RequirementCoverageReport; +} { + const statusByRequirement = new Map>(); + for (const result of retrievalResults) { + for (const requirementId of result.requirement_ids) { + const list = statusByRequirement.get(requirementId) ?? []; + list.push({ + status: result.status, + substantive: hasSubstantiveCoverageForRequirement(requirementId, result) + }); + statusByRequirement.set(requirementId, list); + } + } + + const resolvedRequirements = requirements.map((requirement) => { + if (requirement.status === "out_of_scope" || requirement.status === "clarification_needed") { + return requirement; + } + const states = statusByRequirement.get(requirement.requirement_id) ?? []; + if (states.length === 0) { + return { ...requirement, status: "uncovered" as const }; + } + const hasAnySubstantive = states.some((item) => item.substantive); + if (!hasAnySubstantive) { + return { ...requirement, status: "uncovered" as const }; + } + const hasOk = states.some((item) => item.status === "ok"); + const hasPartial = states.some((item) => item.status === "partial"); + const hasEmpty = states.some((item) => item.status === "empty"); + const hasError = states.some((item) => item.status === "error"); + const hasWeakOk = states.some((item) => item.status === "ok" && !item.substantive); + const hasSubstantiveOk = states.some((item) => item.status === "ok" && item.substantive); + const hasSubstantivePartial = states.some((item) => item.status === "partial" && item.substantive); + if (hasSubstantiveOk && !hasSubstantivePartial && !hasWeakOk && !hasEmpty && !hasError) { + return { ...requirement, status: "covered" as const }; + } + if (hasSubstantiveOk || hasSubstantivePartial || hasOk || hasPartial) { + return { ...requirement, status: "partially_covered" as const }; + } + return { ...requirement, status: "uncovered" as const }; + }); + + const requirementsCovered = resolvedRequirements.filter((item) => item.status === "covered").length; + const requirementsUncovered = resolvedRequirements + .filter((item) => item.status === "uncovered") + .map((item) => item.requirement_id); + const requirementsPartiallyCovered = resolvedRequirements + .filter((item) => item.status === "partially_covered") + .map((item) => item.requirement_id); + const clarificationNeededFor = resolvedRequirements + .filter((item) => item.status === "clarification_needed") + .map((item) => item.requirement_id); + const outOfScopeRequirements = resolvedRequirements + .filter((item) => item.status === "out_of_scope") + .map((item) => item.requirement_id); + + return { + requirements: resolvedRequirements, + coverage: { + requirements_total: resolvedRequirements.length, + requirements_covered: requirementsCovered, + requirements_uncovered: requirementsUncovered, + requirements_partially_covered: requirementsPartiallyCovered, + clarification_needed_for: clarificationNeededFor, + out_of_scope_requirements: outOfScopeRequirements + } + }; +} + +export function checkGroundingForRequirements(input: { + userMessage: string; + requirements: AssistantRequirement[]; + coverage: RequirementCoverageReport; + retrievalResults: UnifiedRetrievalResult[]; + extractSubjectTokens: (text: string) => string[]; +}): AnswerGroundingCheck { + const whyIncludedSummary = summarizeUnique(input.retrievalResults.flatMap((item) => item.why_included)); + const selectionReasonSummary = summarizeUnique(input.retrievalResults.flatMap((item) => item.selection_reason)); + const hasMaterialResults = input.retrievalResults.some((item) => item.status === "ok" || item.status === "partial"); + const subjectTokens = input.extractSubjectTokens(input.userMessage); + const executedRoutes = new Set( + input.retrievalResults + .filter((item) => item.status !== "error") + .map((item) => item.route) + .filter(Boolean) + ); + const retrievalCorpus = JSON.stringify( + input.retrievalResults.map((item) => ({ + route: item.route, + result_type: item.result_type, + summary: item.summary, + items: item.items, + evidence: item.evidence, + why_included: item.why_included, + selection_reason: item.selection_reason, + risk_factors: item.risk_factors, + business_interpretation: item.business_interpretation + })) + ).toLowerCase(); + + const missingSubjectTokens: string[] = []; + const missingCriticalTokens: string[] = []; + for (const token of subjectTokens) { + const match = evaluateSubjectTokenMatch(token, retrievalCorpus, executedRoutes); + if (!match.matched) { + missingSubjectTokens.push(token); + if (match.critical) { + missingCriticalTokens.push(token); + } + } + } + + const onlyAccountCriticalMissing = + missingCriticalTokens.length > 0 && missingCriticalTokens.every((token) => token.startsWith("account_")); + const accountOnlyMismatchRecoverable = + hasMaterialResults && + input.coverage.requirements_covered > 0 && + onlyAccountCriticalMissing && + (whyIncludedSummary.length > 0 || selectionReasonSummary.length > 0); + const routeSubjectMatch = + !hasMaterialResults || missingCriticalTokens.length === 0 || accountOnlyMismatchRecoverable; + + let status: AnswerGroundingCheck["status"] = "grounded"; + const reasons: string[] = []; + if (!routeSubjectMatch) { + status = "route_mismatch_blocked"; + reasons.push( + `Ключевые ориентиры вопроса не подтверждены в найденных данных: ${missingCriticalTokens.join(", ")}` + ); + } else if (accountOnlyMismatchRecoverable) { + status = "partial"; + reasons.push( + `Часть счетных ориентиров не подтвердилась напрямую (${missingCriticalTokens.join(", ")}), но есть опора для ограниченного вывода.` + ); + } else if (input.coverage.requirements_covered === 0) { + status = "no_grounded_answer"; + reasons.push("Ни одно требование не получило подтвержденного покрытия."); + } else if ( + input.coverage.requirements_uncovered.length > 0 || + input.coverage.requirements_partially_covered.length > 0 || + input.coverage.clarification_needed_for.length > 0 || + input.coverage.out_of_scope_requirements.length > 0 + ) { + status = "partial"; + reasons.push("Вопрос покрыт частично: есть непокрытые или требующие уточнения требования."); + } + + if (whyIncludedSummary.length === 0) { + reasons.push("В текущей выборке не хватает явных подтверждений, почему записи попали в ответ."); + } + if (missingSubjectTokens.length > 0 && missingCriticalTokens.length === 0) { + reasons.push(`Часть контекста вопроса не подтверждена напрямую в найденных данных: ${missingSubjectTokens.join(", ")}`); + } + + const missingRequirements = [ + ...input.coverage.requirements_uncovered, + ...input.coverage.requirements_partially_covered, + ...input.coverage.clarification_needed_for, + ...input.coverage.out_of_scope_requirements + ]; + + return { + status, + route_subject_match: routeSubjectMatch, + missing_requirements: missingRequirements, + reasons, + why_included_summary: whyIncludedSummary, + selection_reason_summary: selectionReasonSummary + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDataLayer.ts b/llm_normalizer/backend/src/services/assistantDataLayer.ts index d729e39..b879470 100644 --- a/llm_normalizer/backend/src/services/assistantDataLayer.ts +++ b/llm_normalizer/backend/src/services/assistantDataLayer.ts @@ -93,6 +93,13 @@ interface LiveMcpCallExecution { error?: string | null; } +interface LiveTemporalHint { + as_of_date?: string | null; + period_from?: string | null; + period_to?: string | null; + source?: string | null; +} + type BroadnessLevel = "low" | "medium" | "high"; interface BroadQueryAssessment { @@ -262,6 +269,32 @@ function formatIsoDateUtc(date: Date): string { return `${year}-${month}-${day}`; } +function normalizeIsoDate(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!match) { + return null; + } + const year = Number(match[1]); + const month = Number(match[2]); + const day = Number(match[3]); + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + return null; + } + const candidate = new Date(Date.UTC(year, month - 1, day)); + if ( + candidate.getUTCFullYear() !== year || + candidate.getUTCMonth() + 1 !== month || + candidate.getUTCDate() !== day + ) { + return null; + } + return `${match[1]}-${match[2]}-${match[3]}`; +} + function monthEndFromIso(isoDate: string): string | null { const match = String(isoDate ?? "").match(/^(\d{4})-(\d{2})-(\d{2})$/); if (!match) { @@ -329,14 +362,28 @@ function hasFixedAssetAmortizationSignal(text: string): boolean { ); } -function buildLiveMcpCallPlan(route: string, fragmentText: string): LiveMcpCallPlan { +function buildLiveMcpCallPlan(route: string, fragmentText: string, temporalHint?: LiveTemporalHint | null): LiveMcpCallPlan { const semanticProfile = buildSemanticRetrievalProfile(fragmentText); const preferredDomainHint = inferRuntimeP0DomainHint(fragmentText); const periodScope = inferPeriodScope(fragmentText); - const primaryFrom = periodScope.from ?? "2020-07-01"; - const primaryTo = periodScope.to ?? monthEndFromIso(primaryFrom) ?? "2020-07-31"; - const carryFrom = shiftIsoDate(primaryFrom, -31) ?? primaryFrom; - const carryTo = shiftIsoDate(primaryTo, 31) ?? primaryTo; + const hintedAsOfDate = normalizeIsoDate(temporalHint?.as_of_date); + const hintedPeriodFrom = normalizeIsoDate(temporalHint?.period_from); + const hintedPeriodTo = normalizeIsoDate(temporalHint?.period_to); + const primaryFrom = periodScope.from ?? hintedPeriodFrom ?? hintedAsOfDate; + const primaryTo = + periodScope.to ?? + hintedPeriodTo ?? + (!periodScope.from && !hintedPeriodFrom && hintedAsOfDate ? hintedAsOfDate : primaryFrom ? monthEndFromIso(primaryFrom) ?? primaryFrom : null); + const carryFrom = primaryFrom ? shiftIsoDate(primaryFrom, -31) ?? primaryFrom : null; + const carryTo = primaryTo ? shiftIsoDate(primaryTo, 31) ?? primaryTo : null; + const buildPrimaryQuery = (limit: number): string => + primaryFrom && primaryTo + ? buildLiveRangeQuery(primaryFrom, primaryTo, limit) + : MCP_LIVE_MOVEMENTS_QUERY_TEMPLATE.replace("__LIMIT__", String(limit)); + const buildCarryQuery = (limit: number): string => + carryFrom && carryTo + ? buildLiveRangeQuery(carryFrom, carryTo, limit) + : buildPrimaryQuery(limit); const faClaim = preferredDomainHint === "fixed_asset_amortization" || @@ -352,7 +399,7 @@ function buildLiveMcpCallPlan(route: string, fragmentText: string): LiveMcpCallP { call_id: "find_amortization_documents_in_period", purpose: "seed_amortization_documents", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["01", "02", "08"] @@ -360,7 +407,7 @@ function buildLiveMcpCallPlan(route: string, fragmentText: string): LiveMcpCallP { call_id: "find_fixed_asset_movements_accounts_01_02", purpose: "collect_fa_object_movements", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["01", "02", "08"] @@ -368,7 +415,7 @@ function buildLiveMcpCallPlan(route: string, fragmentText: string): LiveMcpCallP { call_id: "find_fixed_asset_cards_expected_for_period", purpose: "build_expected_fa_set", - query: buildLiveRangeQuery(carryFrom, primaryTo, CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), + query: buildCarryQuery(CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), limit: CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["01", "02", "08"] @@ -376,7 +423,7 @@ function buildLiveMcpCallPlan(route: string, fragmentText: string): LiveMcpCallP { call_id: "match_expected_vs_actual_fa_coverage", purpose: "compare_expected_vs_actual_fa_coverage", - query: buildLiveRangeQuery(carryFrom, carryTo, CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), + query: buildCarryQuery(CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), limit: CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["01", "02", "08"] @@ -400,7 +447,7 @@ function buildLiveMcpCallPlan(route: string, fragmentText: string): LiveMcpCallP { call_id: "find_vat_source_documents_in_period", purpose: "seed_vat_source_documents", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["19", "68"] @@ -408,7 +455,7 @@ function buildLiveMcpCallPlan(route: string, fragmentText: string): LiveMcpCallP { call_id: "find_vat_invoice_links_in_period", purpose: "collect_invoice_links", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["19", "68"] @@ -416,7 +463,7 @@ function buildLiveMcpCallPlan(route: string, fragmentText: string): LiveMcpCallP { call_id: "find_vat_register_entries_in_period", purpose: "collect_vat_register_entries", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["19", "68"] @@ -424,7 +471,7 @@ function buildLiveMcpCallPlan(route: string, fragmentText: string): LiveMcpCallP { call_id: "find_vat_book_entries_in_period", purpose: "collect_vat_book_entries", - query: buildLiveRangeQuery(carryFrom, carryTo, CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), + query: buildCarryQuery(CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), limit: CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["19", "68"] @@ -464,7 +511,7 @@ function buildLiveMcpCallPlan(route: string, fragmentText: string): LiveMcpCallP { call_id: "find_rbp_writeoff_documents_in_period", purpose: "seed_writeoff_documents", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["97", "20", "25", "26", "44"] @@ -472,7 +519,7 @@ function buildLiveMcpCallPlan(route: string, fragmentText: string): LiveMcpCallP { call_id: "find_rbp_object_movements_account_97", purpose: "collect_rbp_object_movements", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["97"] @@ -480,7 +527,7 @@ function buildLiveMcpCallPlan(route: string, fragmentText: string): LiveMcpCallP { call_id: "find_month_close_entries_linked_to_rbp", purpose: "link_month_close_to_rbp", - query: buildLiveRangeQuery(primaryFrom, primaryTo, CLAIM_BOUND_PRIMARY_LIVE_LIMIT), + query: buildPrimaryQuery(CLAIM_BOUND_PRIMARY_LIVE_LIMIT), limit: CLAIM_BOUND_PRIMARY_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["97", "20", "25", "26", "44"] @@ -488,7 +535,7 @@ function buildLiveMcpCallPlan(route: string, fragmentText: string): LiveMcpCallP { call_id: "compute_end_period_residual_by_rbp_object", purpose: "collect_residual_tail_signals", - query: buildLiveRangeQuery(carryFrom, carryTo, CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), + query: buildCarryQuery(CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT), limit: CLAIM_BOUND_CARRY_WINDOW_LIVE_LIMIT, required_for_claim: true, account_scope_override: ["97", "20", "25", "26", "44"] @@ -1849,7 +1896,7 @@ function buildSemanticRetrievalProfile(fragmentText: string): SemanticRetrievalP pushMany(relationPatterns, ["invoice_to_vat", "document_to_posting"]); } if ( - /ос|основн(ые|ых)\s+сред|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|основн(ые|ых|ым)?\s+средств|fixed asset|amort|амортиз|амортиз/i.test( + /основн(ые|ых)\s+сред|(?:^|[^a-zа-яё])ос(?:$|[^a-zа-яё])|основн(ые|ых|ым)?\s+средств|fixed asset|amort|амортиз|амортиз/i.test( lower ) || hasFixedAssetAccountScope @@ -2855,7 +2902,13 @@ export class AssistantDataLayer { return enforceBroadQueryGuards(route, fragmentText, result); } - public async executeRouteRuntime(route: string, fragmentText: string): Promise { + public async executeRouteRuntime( + route: string, + fragmentText: string, + options?: { + temporalHint?: LiveTemporalHint | null; + } + ): Promise { const base = this.executeRoute(route, fragmentText); if (!FEATURE_ASSISTANT_MCP_RUNTIME_V1) { return base; @@ -2864,7 +2917,7 @@ export class AssistantDataLayer { return base; } - const liveOverlay = await this.fetchLiveMcpOverlay(route, fragmentText); + const liveOverlay = await this.fetchLiveMcpOverlay(route, fragmentText, options?.temporalHint); return this.mergeWithLiveOverlay(base, liveOverlay); } @@ -2922,9 +2975,13 @@ export class AssistantDataLayer { return merged; } - private async fetchLiveMcpOverlay(route: string, fragmentText: string): Promise { + private async fetchLiveMcpOverlay( + route: string, + fragmentText: string, + temporalHint?: LiveTemporalHint | null + ): Promise { const endpoint = this.buildMcpUrl("/api/execute_query"); - const livePlan = buildLiveMcpCallPlan(route, fragmentText); + const livePlan = buildLiveMcpCallPlan(route, fragmentText, temporalHint); const explicitAccountScope = extractAccountScopeFromText(fragmentText); const accountScope = livePlan.claim_type === "prove_fixed_asset_amortization_coverage" diff --git a/llm_normalizer/backend/src/services/assistantDebugPayloadAssembler.ts b/llm_normalizer/backend/src/services/assistantDebugPayloadAssembler.ts new file mode 100644 index 0000000..82bc018 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDebugPayloadAssembler.ts @@ -0,0 +1,160 @@ +import type { AssistantDebugPayload } from "../types/assistant"; + +type RetrievalStatusItem = AssistantDebugPayload["retrieval_status"][number]; + +export interface DeepAnalysisDebugPayloadInput { + traceId: string; + promptVersion: string; + schemaVersion: string; + fallbackType: unknown; + routeSummary: unknown; + fragments: unknown[]; + requirementsExtracted: unknown[]; + coverageReport: unknown; + routes: Array>; + retrievalStatus: RetrievalStatusItem[]; + retrievalResults: unknown[]; + groundingCheck: unknown; + droppedIntentSegments: string[]; + questionTypeClass: string; + companyAnchors: unknown; + runtimeAnalysisContext: { + active: boolean; + as_of_date: string | null; + period_from: string | null; + period_to: string | null; + source: string | null; + snapshot_mode: "auto" | "force_snapshot" | "force_live"; + }; + businessScopeResolution: { + business_scope_raw?: string[]; + business_scope_resolved?: string[]; + company_grounding_applied?: boolean; + scope_resolution_reason?: string[]; + }; + temporalGuard: Record; + polarityAudit: Record; + claimAnchorAudit: Record; + targetedEvidenceAudit: unknown; + evidenceAdmissibilityGateAudit: unknown; + rbpLiveRouteAudit: unknown | null; + faLiveRouteAudit: unknown | null; + groundedAnswerEligibilityGuard: Record; + followupStateUsage: unknown | null; + compositionDebug: { + problem_centric_answer_applied?: boolean; + problem_units_used_count?: number; + problem_answer_mode?: string; + problem_unit_ids_used?: string[]; + }; + addressRuntimeMetaForDeep: + | { + attempted?: boolean; + applied?: boolean; + reason?: string | null; + provider?: string | null; + fallbackRuleHit?: string | null; + toolGateDecision?: string | null; + toolGateReason?: string | null; + predecomposeContract?: unknown; + orchestrationContract?: unknown; + } + | null + | undefined; + outcomeClassV1: unknown; + assistantOrchestrationContractsV1: unknown; + answerStructureV11: unknown; + investigationStateSnapshot: unknown; + normalizedPayload: unknown; +} + +function toAnalysisContext(input: DeepAnalysisDebugPayloadInput["runtimeAnalysisContext"]): Record | null { + if (!input.active) { + return null; + } + return { + as_of_date: input.as_of_date, + period_from: input.period_from, + period_to: input.period_to, + source: input.source, + snapshot_mode: input.snapshot_mode + }; +} + +export function buildDeepAnalysisDebugPayload(input: DeepAnalysisDebugPayloadInput): Record { + const analysisContext = toAnalysisContext(input.runtimeAnalysisContext); + return { + trace_id: input.traceId, + prompt_version: input.promptVersion, + schema_version: input.schemaVersion, + fallback_type: input.fallbackType, + route_summary: input.routeSummary, + fragments: input.fragments, + requirements_extracted: input.requirementsExtracted, + coverage_report: input.coverageReport, + routes: input.routes, + retrieval_status: input.retrievalStatus, + retrieval_results: input.retrievalResults, + answer_grounding_check: input.groundingCheck, + dropped_intent_segments: input.droppedIntentSegments, + question_type_class: input.questionTypeClass, + company_anchors: input.companyAnchors, + analysis_context_applied: input.runtimeAnalysisContext.active, + analysis_context: analysisContext, + business_scope_raw: input.businessScopeResolution.business_scope_raw, + business_scope_resolved: input.businessScopeResolution.business_scope_resolved, + company_grounding_applied: input.businessScopeResolution.company_grounding_applied, + scope_resolution_reason: input.businessScopeResolution.scope_resolution_reason, + company_scope_resolution_reason: input.businessScopeResolution.scope_resolution_reason, + raw_time_anchor: input.temporalGuard.raw_time_anchor, + raw_time_scope: input.temporalGuard.raw_time_scope, + resolved_time_anchor: input.temporalGuard.resolved_time_anchor, + resolved_primary_period: input.temporalGuard.resolved_primary_period, + effective_primary_period: input.temporalGuard.effective_primary_period, + temporal_guard_input: input.temporalGuard.temporal_guard_input, + temporal_alignment_status: input.temporalGuard.temporal_alignment_status, + temporal_resolution_source: input.temporalGuard.temporal_resolution_source, + temporal_guard_basis: input.temporalGuard.temporal_guard_basis, + temporal_guard_applied: input.temporalGuard.temporal_guard_applied, + temporal_guard_outcome: input.temporalGuard.temporal_guard_outcome, + temporal_guard: input.temporalGuard, + raw_numeric_tokens: input.polarityAudit.raw_numeric_tokens, + classified_numeric_tokens: input.polarityAudit.classified_numeric_tokens, + rejected_as_non_accounts: input.polarityAudit.rejected_as_non_accounts, + resolved_account_anchors: input.polarityAudit.resolved_account_anchors, + domain_polarity_guard: input.polarityAudit, + claim_anchor_audit: input.claimAnchorAudit, + settlement_role: input.claimAnchorAudit.settlement_role ?? null, + settlement_role_resolution_reason: input.claimAnchorAudit.settlement_role_resolution_reason ?? [], + polarity_resolution_status: input.claimAnchorAudit.polarity_resolution_status ?? "not_applicable", + targeted_evidence_acquisition: input.targetedEvidenceAudit, + evidence_admissibility_gate: input.evidenceAdmissibilityGateAudit, + ...(input.rbpLiveRouteAudit ? { rbp_live_route_audit: input.rbpLiveRouteAudit } : {}), + ...(input.faLiveRouteAudit ? { fa_live_route_audit: input.faLiveRouteAudit } : {}), + eligibility_time_basis: input.groundedAnswerEligibilityGuard.eligibility_time_basis, + grounded_answer_eligibility_guard: input.groundedAnswerEligibilityGuard, + ...(input.followupStateUsage ? { followup_state_usage: input.followupStateUsage } : {}), + problem_centric_answer_applied: input.compositionDebug.problem_centric_answer_applied ?? false, + problem_units_used_count: input.compositionDebug.problem_units_used_count ?? 0, + problem_answer_mode: input.compositionDebug.problem_answer_mode ?? "stage1_policy_v11", + ...(Array.isArray(input.compositionDebug.problem_unit_ids_used) && input.compositionDebug.problem_unit_ids_used.length > 0 + ? { + problem_unit_ids_used: input.compositionDebug.problem_unit_ids_used + } + : {}), + address_llm_predecompose_attempted: Boolean(input.addressRuntimeMetaForDeep?.attempted), + address_llm_predecompose_applied: Boolean(input.addressRuntimeMetaForDeep?.applied), + address_llm_predecompose_reason: input.addressRuntimeMetaForDeep?.reason ?? null, + address_llm_predecompose_provider: input.addressRuntimeMetaForDeep?.provider ?? null, + address_fallback_rule_hit: input.addressRuntimeMetaForDeep?.fallbackRuleHit ?? null, + address_tool_gate_decision: input.addressRuntimeMetaForDeep?.toolGateDecision ?? null, + address_tool_gate_reason: input.addressRuntimeMetaForDeep?.toolGateReason ?? null, + address_llm_predecompose_contract: input.addressRuntimeMetaForDeep?.predecomposeContract ?? null, + orchestration_contract_v1: input.addressRuntimeMetaForDeep?.orchestrationContract ?? null, + assistant_outcome_class_v1: input.outcomeClassV1, + assistant_orchestration_contracts_v1: input.assistantOrchestrationContractsV1, + answer_structure_v11: input.answerStructureV11, + investigation_state_snapshot: input.investigationStateSnapshot, + normalized: input.normalizedPayload + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepResponseAssembler.ts b/llm_normalizer/backend/src/services/assistantDeepResponseAssembler.ts new file mode 100644 index 0000000..c4d1d78 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepResponseAssembler.ts @@ -0,0 +1,68 @@ +import { buildAssistantAnswerStructureV11 } from "./assistantAnswerPackageBuilder"; +import type { + AssistantConversationItem, + AssistantDebugPayload, + AssistantReplyType, + AnswerGroundingCheck, + RequirementCoverageReport, + UnifiedRetrievalResult +} from "../types/assistant"; +import type { AnswerStructureV11 } from "../types/stage1Contracts"; + +export interface DeepAnswerArtifacts { + safeAssistantReply: string; + answerStructureV11: AnswerStructureV11 | null; +} + +function stripTechnicalTail(text: string): string { + return String(text ?? "") + .replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "") + .replace(/\b(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "") + .trim(); +} + +export function buildDeepAnswerArtifacts(input: { + safeAssistantReplyBase: string; + featureContractsV11: boolean; + featureAnswerPolicyV11: boolean; + compositionAnswerStructureV11: AnswerStructureV11 | null | undefined; + coverageReport: RequirementCoverageReport; + groundingCheck: AnswerGroundingCheck; + retrievalResults: UnifiedRetrievalResult[]; +}): DeepAnswerArtifacts { + const safeAssistantReply = stripTechnicalTail(input.safeAssistantReplyBase); + const answerStructureV11 = input.featureContractsV11 + ? input.featureAnswerPolicyV11 && input.compositionAnswerStructureV11 + ? input.compositionAnswerStructureV11 + : buildAssistantAnswerStructureV11({ + assistantReply: safeAssistantReply, + coverageReport: input.coverageReport, + groundingCheck: input.groundingCheck, + retrievalResults: input.retrievalResults + }) + : null; + return { + safeAssistantReply, + answerStructureV11 + }; +} + +export function buildAssistantConversationItem(input: { + messageId: string; + sessionId: string; + text: string; + replyType: AssistantReplyType; + traceId: string | null; + debug: AssistantDebugPayload; +}): AssistantConversationItem { + return { + message_id: input.messageId, + session_id: input.sessionId, + role: "assistant", + text: input.text, + reply_type: input.replyType, + created_at: new Date().toISOString(), + trace_id: input.traceId, + debug: input.debug + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnCompositionRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantDeepTurnCompositionRuntimeAdapter.ts new file mode 100644 index 0000000..016872d --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnCompositionRuntimeAdapter.ts @@ -0,0 +1,77 @@ +import type { AssistantRequirement, AnswerGroundingCheck, RequirementCoverageReport, UnifiedRetrievalResult } from "../types/assistant"; +import type { NormalizeResponsePayload, RouteHintSummary } from "../types/normalizer"; +import type { InvestigationStateWithProblemUnits } from "../types/stage2ProblemUnits"; +import type { QuestionTypeClass } from "./questionTypeResolver"; +import { resolveQuestionType } from "./questionTypeResolver"; +import { composeAssistantAnswer } from "./answerComposer"; + +export interface BuildAssistantDeepTurnCompositionInput { + userMessage: string; + routeSummary: RouteHintSummary | null; + retrievalResults: UnifiedRetrievalResult[]; + requirements: AssistantRequirement[]; + coverageReport: RequirementCoverageReport; + groundingCheck: AnswerGroundingCheck; + followupUsage: unknown | null | undefined; + investigationState: InvestigationStateWithProblemUnits | null | undefined; + companyAnchors: unknown; + normalizedPayload: NormalizeResponsePayload["normalized"]; + featureAnswerPolicyV11: boolean; + featureProblemCentricAnswerV1: boolean; + featureLifecycleAnswerV1: boolean; + hasExplicitPeriodAnchor: (normalizedPayload: NormalizeResponsePayload["normalized"]) => boolean; + resolveQuestionTypeFn?: (input: string) => QuestionTypeClass; + composeAssistantAnswerFn?: typeof composeAssistantAnswer; +} + +export interface AssistantDeepTurnCompositionOutput { + focusDomainHint: string | null; + questionTypeClass: QuestionTypeClass; + hasPeriodInCompanyAnchors: boolean; + normalizationPeriodExplicit: boolean; + composition: ReturnType; +} + +export function buildAssistantDeepTurnComposition( + input: BuildAssistantDeepTurnCompositionInput +): AssistantDeepTurnCompositionOutput { + const resolveQuestionTypeSafe = input.resolveQuestionTypeFn ?? resolveQuestionType; + const composeAssistantAnswerSafe = input.composeAssistantAnswerFn ?? composeAssistantAnswer; + + const followupApplied = Boolean((input.followupUsage as { applied?: unknown } | null)?.applied); + const focusDomainHint = followupApplied + ? input.investigationState?.followup_context?.active_domain ?? input.investigationState?.focus.domain ?? null + : null; + const questionTypeClass = resolveQuestionTypeSafe(input.userMessage); + const companyAnchorSet = input.companyAnchors as { + dates?: unknown[]; + periods?: unknown[]; + } | null; + const hasPeriodInCompanyAnchors = + (Array.isArray(companyAnchorSet?.dates) && companyAnchorSet.dates.some((item) => String(item ?? "").trim().length > 0)) || + (Array.isArray(companyAnchorSet?.periods) && companyAnchorSet.periods.some((item) => String(item ?? "").trim().length > 0)); + const normalizationPeriodExplicit = input.hasExplicitPeriodAnchor(input.normalizedPayload) || hasPeriodInCompanyAnchors; + const composition = composeAssistantAnswerSafe({ + userMessage: input.userMessage, + routeSummary: input.routeSummary, + retrievalResults: input.retrievalResults, + requirements: input.requirements, + coverageReport: input.coverageReport, + groundingCheck: input.groundingCheck, + focusDomainHint, + questionTypeHint: questionTypeClass, + companyAnchors: input.companyAnchors as any, + normalizationPeriodExplicit, + enableAnswerPolicyV11: input.featureAnswerPolicyV11, + enableProblemCentricAnswerV1: input.featureProblemCentricAnswerV1, + enableLifecycleAnswerV1: input.featureLifecycleAnswerV1 + }); + + return { + focusDomainHint, + questionTypeClass, + hasPeriodInCompanyAnchors, + normalizationPeriodExplicit, + composition + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnContextRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantDeepTurnContextRuntimeAdapter.ts new file mode 100644 index 0000000..9a67c8f --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnContextRuntimeAdapter.ts @@ -0,0 +1,169 @@ +import type { NormalizeResponsePayload, RouteHintSummary } from "../types/normalizer"; + +const KNOWN_P0_DOMAINS = new Set([ + "settlements_60_62", + "vat_document_register_book", + "month_close_costs_20_44", + "fixed_asset_amortization" +]); + +function toAnalysisContext( + runtimeAnalysisContext: BuildAssistantDeepTurnRuntimeContextInput["runtimeAnalysisContext"] +): Record | null { + if (!runtimeAnalysisContext.active) { + return null; + } + return { + as_of_date: runtimeAnalysisContext.as_of_date, + period_from: runtimeAnalysisContext.period_from, + period_to: runtimeAnalysisContext.period_to, + source: runtimeAnalysisContext.source + }; +} + +export interface BuildAssistantDeepTurnRuntimeContextInput { + userMessage: string; + normalizedPayload: NormalizeResponsePayload["normalized"]; + routeSummary: RouteHintSummary | null; + runtimeAnalysisContext: { + active: boolean; + as_of_date: string | null; + period_from: string | null; + period_to: string | null; + source: string | null; + }; + followupUsage: unknown | null | undefined; + resolveCompanyAnchors: (userMessage: string) => unknown; + resolveBusinessScopeAlignment: (input: { + userMessage: string; + companyAnchors: unknown; + normalized: NormalizeResponsePayload["normalized"]; + routeSummary: RouteHintSummary | null; + }) => { + route_summary_resolved: RouteHintSummary | null; + [key: string]: unknown; + }; + inferP0DomainFromMessage: (userMessage: string) => string | null; + resolveTemporalGuard: (input: { + userMessage: string; + normalized: NormalizeResponsePayload["normalized"]; + companyAnchors: unknown; + analysisContext: Record | null; + }) => { + effective_primary_period?: unknown; + primary_period_window?: unknown; + [key: string]: unknown; + }; + resolveDomainPolarityGuard: (input: { + userMessage: string; + companyAnchors: unknown; + focusDomainHint: string | null; + }) => unknown; + resolveClaimBoundAnchors: (input: { + userMessage: string; + companyAnchors: unknown; + focusDomainHint: string | null; + primaryPeriod: unknown; + }) => { + claim_type: string; + [key: string]: unknown; + }; + resolveBusinessScopeFromLiveContext: (input: { + current: { + route_summary_resolved: RouteHintSummary | null; + [key: string]: unknown; + }; + temporalGuard: unknown; + claimType: string; + focusDomainHint: string | null; + userMessage: string; + companyAnchors: unknown; + followupApplied: boolean; + }) => { + route_summary_resolved: RouteHintSummary | null; + [key: string]: unknown; + }; +} + +export interface BuildAssistantDeepTurnRuntimeContextOutput { + companyAnchors: unknown; + initialBusinessScopeResolution: { + route_summary_resolved: RouteHintSummary | null; + [key: string]: unknown; + }; + inferredDomainByMessage: string | null; + focusDomainForGuards: string | null; + temporalGuard: { + effective_primary_period?: unknown; + primary_period_window?: unknown; + [key: string]: unknown; + }; + domainPolarityGuardInitial: unknown; + claimAnchorAudit: { + claim_type: string; + [key: string]: unknown; + }; + businessScopeResolution: { + route_summary_resolved: RouteHintSummary | null; + [key: string]: unknown; + }; + resolvedRouteSummary: RouteHintSummary | null; + liveTemporalHint: Record | null; +} + +export function buildAssistantDeepTurnRuntimeContext( + input: BuildAssistantDeepTurnRuntimeContextInput +): BuildAssistantDeepTurnRuntimeContextOutput { + const companyAnchors = input.resolveCompanyAnchors(input.userMessage); + const initialBusinessScopeResolution = input.resolveBusinessScopeAlignment({ + userMessage: input.userMessage, + companyAnchors, + normalized: input.normalizedPayload, + routeSummary: input.routeSummary + }); + const inferredDomainByMessage = input.inferP0DomainFromMessage(input.userMessage); + const focusDomainForGuards = + inferredDomainByMessage && KNOWN_P0_DOMAINS.has(inferredDomainByMessage) ? inferredDomainByMessage : null; + const analysisContext = toAnalysisContext(input.runtimeAnalysisContext); + const temporalGuard = input.resolveTemporalGuard({ + userMessage: input.userMessage, + normalized: input.normalizedPayload, + companyAnchors, + analysisContext + }); + const domainPolarityGuardInitial = input.resolveDomainPolarityGuard({ + userMessage: input.userMessage, + companyAnchors, + focusDomainHint: focusDomainForGuards + }); + const claimAnchorAudit = input.resolveClaimBoundAnchors({ + userMessage: input.userMessage, + companyAnchors, + focusDomainHint: focusDomainForGuards, + primaryPeriod: temporalGuard.effective_primary_period ?? temporalGuard.primary_period_window + }); + const businessScopeResolution = input.resolveBusinessScopeFromLiveContext({ + current: initialBusinessScopeResolution, + temporalGuard, + claimType: claimAnchorAudit.claim_type, + focusDomainHint: focusDomainForGuards, + userMessage: input.userMessage, + companyAnchors, + followupApplied: Boolean((input.followupUsage as { applied?: unknown } | null)?.applied) + }); + const resolvedRouteSummary = businessScopeResolution.route_summary_resolved; + const liveTemporalHint = toAnalysisContext(input.runtimeAnalysisContext); + + return { + companyAnchors, + initialBusinessScopeResolution, + inferredDomainByMessage, + focusDomainForGuards, + temporalGuard, + domainPolarityGuardInitial, + claimAnchorAudit, + businessScopeResolution, + resolvedRouteSummary, + liveTemporalHint + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnGroundingRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantDeepTurnGroundingRuntimeAdapter.ts new file mode 100644 index 0000000..cdd8f2e --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnGroundingRuntimeAdapter.ts @@ -0,0 +1,122 @@ +import type { + AnswerGroundingCheck, + AssistantRequirement, + RequirementCoverageReport, + UnifiedRetrievalResult +} from "../types/assistant"; +import type { NormalizeResponsePayload, RouteHintSummary } from "../types/normalizer"; +import type { AssistantRequirementExtractionResult, AssistantCoverageEvaluationResult } from "./assistantOrchestrationRuntimeAdapter"; +import { runAssistantCoverageGroundingPipeline } from "./assistantOrchestrationRuntimeAdapter"; +import type { AssistantDeepTurnGroundingEligibilityOutput } from "./assistantDeepTurnGuardRuntimeAdapter"; +import { applyAssistantDeepTurnGroundingEligibility } from "./assistantDeepTurnGuardRuntimeAdapter"; + +export interface AssistantDeepTurnGroundingRuntimeInput { + claimType: string; + retrievalResults: UnifiedRetrievalResult[]; + rbpPlanAudit: unknown; + faPlanAudit: unknown; + routeSummary: RouteHintSummary | null; + normalizedPayload: NormalizeResponsePayload["normalized"] | null | undefined; + userMessage: string; + requirementExtraction: AssistantRequirementExtractionResult; + extractRequirements: ( + routeSummary: RouteHintSummary | null, + normalized: NormalizeResponsePayload["normalized"] | null | undefined, + userMessage: string + ) => AssistantRequirementExtractionResult; + evaluateCoverage: ( + requirements: AssistantRequirement[], + retrievalResults: UnifiedRetrievalResult[] + ) => AssistantCoverageEvaluationResult; + checkGrounding: ( + userMessage: string, + requirements: AssistantRequirement[], + coverage: RequirementCoverageReport, + retrievalResults: UnifiedRetrievalResult[] + ) => AnswerGroundingCheck; + temporalGuard: unknown; + polarityAudit: unknown; + evidenceAudit: unknown; + claimAnchorAudit: unknown; + targetedEvidenceHitRate?: number | null; + businessScopeResolved?: string[] | null; + collectRbpLiveRouteAudit: (input: { + claimType: string; + retrievalResults: UnifiedRetrievalResult[]; + planAudit: unknown; + }) => unknown; + collectFaLiveRouteAudit: (input: { + claimType: string; + retrievalResults: UnifiedRetrievalResult[]; + planAudit: unknown; + }) => unknown; + runCoverageGroundingPipelineFn?: typeof runAssistantCoverageGroundingPipeline; + applyGroundingEligibilityFn?: (input: { + groundingCheckBase: AnswerGroundingCheck; + temporalGuard: unknown; + polarityAudit: unknown; + evidenceAudit: unknown; + claimAnchorAudit?: unknown; + targetedEvidenceHitRate?: number | null; + businessScopeResolved?: string[] | null; + }) => AssistantDeepTurnGroundingEligibilityOutput; +} + +export interface AssistantDeepTurnGroundingRuntimeOutput { + rbpLiveRouteAudit: unknown; + faLiveRouteAudit: unknown; + coverageEvaluation: AssistantCoverageEvaluationResult; + groundingCheckBase: AnswerGroundingCheck; + groundedAnswerEligibilityGuard: unknown; + groundingCheck: AnswerGroundingCheck; +} + +export function runAssistantDeepTurnGroundingRuntime( + input: AssistantDeepTurnGroundingRuntimeInput +): AssistantDeepTurnGroundingRuntimeOutput { + const runCoverageGroundingPipelineSafe = input.runCoverageGroundingPipelineFn ?? runAssistantCoverageGroundingPipeline; + const applyGroundingEligibilitySafe = + input.applyGroundingEligibilityFn ?? + ((payload) => applyAssistantDeepTurnGroundingEligibility(payload as any) as any); + + const rbpLiveRouteAudit = input.collectRbpLiveRouteAudit({ + claimType: input.claimType, + retrievalResults: input.retrievalResults, + planAudit: input.rbpPlanAudit + }); + const faLiveRouteAudit = input.collectFaLiveRouteAudit({ + claimType: input.claimType, + retrievalResults: input.retrievalResults, + planAudit: input.faPlanAudit + }); + const orchestrationRuntime = runCoverageGroundingPipelineSafe({ + routeSummary: input.routeSummary, + normalized: input.normalizedPayload, + userMessage: input.userMessage, + retrievalResults: input.retrievalResults, + requirementExtraction: input.requirementExtraction, + extractRequirements: input.extractRequirements, + evaluateCoverage: input.evaluateCoverage, + checkGrounding: input.checkGrounding + }); + const coverageEvaluation = orchestrationRuntime.coverageEvaluation; + const groundingCheckBase = orchestrationRuntime.groundingCheckBase; + const groundingEligibilityRuntime = applyGroundingEligibilitySafe({ + groundingCheckBase, + temporalGuard: input.temporalGuard, + polarityAudit: input.polarityAudit, + evidenceAudit: input.evidenceAudit, + claimAnchorAudit: input.claimAnchorAudit, + targetedEvidenceHitRate: input.targetedEvidenceHitRate, + businessScopeResolved: input.businessScopeResolved + }); + + return { + rbpLiveRouteAudit, + faLiveRouteAudit, + coverageEvaluation, + groundingCheckBase, + groundedAnswerEligibilityGuard: groundingEligibilityRuntime.groundedAnswerEligibilityGuard, + groundingCheck: groundingEligibilityRuntime.groundingCheck + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnGuardRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantDeepTurnGuardRuntimeAdapter.ts new file mode 100644 index 0000000..3d4ae51 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnGuardRuntimeAdapter.ts @@ -0,0 +1,112 @@ +import type { UnifiedRetrievalResult } from "../types/assistant"; +import { applyTargetedEvidenceAcquisition } from "./assistantClaimBoundEvidence"; +import { + applyDomainPolarityGuardToRetrievalResults, + applyEvidenceAdmissibilityGate, + applyEligibilityToGroundingCheck, + evaluateGroundedAnswerEligibility +} from "./assistantRuntimeGuards"; + +type GroundingCheckLike = { + status: string; + reasons: string[]; +}; + +type ApplyEligibilityToGroundingCheckFn = ( + groundingCheck: T, + eligibility: ReturnType +) => T; + +export interface AssistantDeepTurnRetrievalGuardPipelineInput { + retrievalResults: UnifiedRetrievalResult[]; + domainPolarityGuardInitial: Parameters[0]["guard"]; + claimAnchorAudit: Parameters[0]["claimAudit"]; + temporalGuard: Parameters[0]["temporal"]; + focusDomainForGuards: Parameters[0]["focusDomainHint"]; + companyAnchors?: Parameters[0]["companyAnchors"]; + userMessage: string; + applyDomainPolarityGuardFn?: typeof applyDomainPolarityGuardToRetrievalResults; + applyTargetedEvidenceFn?: typeof applyTargetedEvidenceAcquisition; + applyEvidenceAdmissibilityGateFn?: typeof applyEvidenceAdmissibilityGate; +} + +export interface AssistantDeepTurnRetrievalGuardPipelineOutput { + retrievalResults: UnifiedRetrievalResult[]; + polarityGuardResult: ReturnType; + targetedEvidenceResult: ReturnType; + evidenceGateResult: ReturnType; +} + +export function applyAssistantDeepTurnRetrievalGuards( + input: AssistantDeepTurnRetrievalGuardPipelineInput +): AssistantDeepTurnRetrievalGuardPipelineOutput { + const applyDomainPolarityGuardSafe = input.applyDomainPolarityGuardFn ?? applyDomainPolarityGuardToRetrievalResults; + const applyTargetedEvidenceSafe = input.applyTargetedEvidenceFn ?? applyTargetedEvidenceAcquisition; + const applyEvidenceAdmissibilityGateSafe = input.applyEvidenceAdmissibilityGateFn ?? applyEvidenceAdmissibilityGate; + + const polarityGuardResult = applyDomainPolarityGuardSafe({ + retrievalResults: input.retrievalResults, + guard: input.domainPolarityGuardInitial + }); + const targetedEvidenceResult = applyTargetedEvidenceSafe({ + retrievalResults: polarityGuardResult.retrievalResults, + claimAudit: input.claimAnchorAudit + }); + const evidenceGateResult = applyEvidenceAdmissibilityGateSafe({ + retrievalResults: targetedEvidenceResult.retrievalResults, + temporal: input.temporalGuard, + focusDomainHint: input.focusDomainForGuards, + polarity: polarityGuardResult.audit.polarity, + companyAnchors: input.companyAnchors, + userMessage: input.userMessage + }); + + return { + retrievalResults: evidenceGateResult.retrievalResults, + polarityGuardResult, + targetedEvidenceResult, + evidenceGateResult + }; +} + +export interface AssistantDeepTurnGroundingEligibilityInput { + groundingCheckBase: T; + temporalGuard: Parameters[0]["temporal"]; + polarityAudit: Parameters[0]["polarity"]; + evidenceAudit: Parameters[0]["evidence"]; + claimAnchorAudit?: Parameters[0]["claimAnchors"]; + targetedEvidenceHitRate?: number | null; + businessScopeResolved?: string[] | null; + evaluateGroundedAnswerEligibilityFn?: typeof evaluateGroundedAnswerEligibility; + applyEligibilityToGroundingCheckFn?: ApplyEligibilityToGroundingCheckFn; +} + +export interface AssistantDeepTurnGroundingEligibilityOutput { + groundedAnswerEligibilityGuard: ReturnType; + groundingCheck: T; +} + +export function applyAssistantDeepTurnGroundingEligibility( + input: AssistantDeepTurnGroundingEligibilityInput +): AssistantDeepTurnGroundingEligibilityOutput { + const evaluateGroundedAnswerEligibilitySafe = + input.evaluateGroundedAnswerEligibilityFn ?? evaluateGroundedAnswerEligibility; + const applyEligibilityToGroundingCheckSafe = + input.applyEligibilityToGroundingCheckFn ?? + ((groundingCheck, eligibility) => applyEligibilityToGroundingCheck(groundingCheck, eligibility)); + + const groundedAnswerEligibilityGuard = evaluateGroundedAnswerEligibilitySafe({ + temporal: input.temporalGuard, + polarity: input.polarityAudit, + evidence: input.evidenceAudit, + claimAnchors: input.claimAnchorAudit, + targetedEvidenceHitRate: input.targetedEvidenceHitRate, + businessScopeResolved: input.businessScopeResolved + }); + const groundingCheck = applyEligibilityToGroundingCheckSafe(input.groundingCheckBase, groundedAnswerEligibilityGuard); + + return { + groundedAnswerEligibilityGuard, + groundingCheck + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnInputBuilder.ts b/llm_normalizer/backend/src/services/assistantDeepTurnInputBuilder.ts new file mode 100644 index 0000000..8653dc4 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnInputBuilder.ts @@ -0,0 +1,112 @@ +import type { AssistantReplyType, AssistantRequirement, AnswerGroundingCheck, RequirementCoverageReport, UnifiedRetrievalResult } from "../types/assistant"; +import type { NormalizeResponsePayload, RouteHintSummary } from "../types/normalizer"; +import type { AnswerStructureV11 } from "../types/stage1Contracts"; +import type { AssistantDeepTurnPackagingInput } from "./assistantDeepTurnPackaging"; + +export interface AssistantDeepTurnInputBuilderArgs { + sessionId: string; + messageId: string; + userMessage: string; + normalized: { + trace_id: string; + prompt_version: string; + schema_version: string; + normalized: NormalizeResponsePayload["normalized"]; + }; + normalizedQuestion: string; + routeSummary: RouteHintSummary | null; + droppedIntentSegments: string[]; + analysisContextForContract: { + as_of_date: string | null; + period_from: string | null; + period_to: string | null; + source: string | null; + snapshot_mode: "auto" | "force_snapshot" | "force_live"; + } | null; + executionPlan: Array<{ + fragment_id: string; + requirement_ids: string[]; + route: string; + should_execute: boolean; + no_route_reason?: string | null; + clarification_reason?: string | null; + }>; + requirementExtractionRequirements: AssistantRequirement[]; + coverageEvaluationRequirements: AssistantRequirement[]; + coverageReport: RequirementCoverageReport; + groundingCheck: AnswerGroundingCheck; + retrievalCalls: Array>; + retrievalResultsRaw: unknown[]; + retrievalResults: UnifiedRetrievalResult[]; + routesForDebug: Array>; + resolvedExecutionState: unknown; + questionTypeClass: string; + companyAnchors: unknown; + runtimeAnalysisContext: { + active: boolean; + as_of_date: string | null; + period_from: string | null; + period_to: string | null; + source: string | null; + snapshot_mode: "auto" | "force_snapshot" | "force_live"; + }; + businessScopeResolution: { + business_scope_raw?: string[]; + business_scope_resolved?: string[]; + company_grounding_applied?: boolean; + scope_resolution_reason?: string[]; + }; + temporalGuard: Record; + polarityAudit: Record; + claimAnchorAudit: Record; + targetedEvidenceAudit: unknown; + evidenceAdmissibilityGateAudit: unknown; + rbpLiveRouteAudit: unknown | null; + faLiveRouteAudit: unknown | null; + groundedAnswerEligibilityGuard: Record; + followupStateUsage?: unknown; + composition: { + reply_type: AssistantReplyType; + fallback_type: unknown; + answer_structure_v11?: AnswerStructureV11 | null; + problem_centric_answer_applied?: boolean; + problem_units_used_count?: number; + problem_answer_mode?: string; + problem_unit_ids_used?: unknown; + }; + safeAssistantReplyBase: string; + featureContractsV11: boolean; + featureAnswerPolicyV11: boolean; + investigationStateSnapshot: unknown; + addressRuntimeMetaForDeep: + | { + attempted?: boolean; + applied?: boolean; + reason?: string | null; + provider?: string | null; + fallbackRuleHit?: string | null; + toolGateDecision?: string | null; + toolGateReason?: string | null; + predecomposeContract?: unknown; + orchestrationContract?: unknown; + } + | null + | undefined; +} + +export function buildAssistantDeepTurnPackagingInput(args: AssistantDeepTurnInputBuilderArgs): AssistantDeepTurnPackagingInput { + return { + ...args, + routesForDebug: Array.isArray(args.routesForDebug) ? args.routesForDebug : [], + followupStateUsage: args.followupStateUsage ?? null, + composition: { + reply_type: args.composition.reply_type, + fallback_type: args.composition.fallback_type, + answer_structure_v11: args.composition.answer_structure_v11 ?? null, + problem_centric_answer_applied: args.composition.problem_centric_answer_applied ?? false, + problem_units_used_count: args.composition.problem_units_used_count ?? 0, + problem_answer_mode: args.composition.problem_answer_mode ?? "stage1_policy_v11", + problem_unit_ids_used: Array.isArray(args.composition.problem_unit_ids_used) ? args.composition.problem_unit_ids_used : [] + } + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnPackaging.ts b/llm_normalizer/backend/src/services/assistantDeepTurnPackaging.ts new file mode 100644 index 0000000..536c413 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnPackaging.ts @@ -0,0 +1,250 @@ +import type { + AssistantConversationItem, + AssistantReplyType, + AssistantRequirement, + AnswerGroundingCheck, + RequirementCoverageReport, + UnifiedRetrievalResult +} from "../types/assistant"; +import type { NormalizeResponsePayload, RouteHintSummary } from "../types/normalizer"; +import type { AnswerStructureV11 } from "../types/stage1Contracts"; +import { + assembleAssistantEvidenceBundle, + type AssistantEvidenceBundleAssembly +} from "./assistantEvidenceBundleAssembler"; +import { assembleAssistantContractsBundleV1, type AssistantContractsBundleV1 } from "./assistantContractsBundleAssembler"; +import { buildDeepAnswerArtifacts, buildAssistantConversationItem, type DeepAnswerArtifacts } from "./assistantDeepResponseAssembler"; +import { buildDeepAnalysisDebugPayload } from "./assistantDebugPayloadAssembler"; +import { buildDeepAnalysisProcessedLogDetails } from "./assistantMessageLogAssembler"; + +export interface AssistantDeepTurnPackagingInput { + sessionId: string; + messageId: string; + userMessage: string; + normalized: { + trace_id: string; + prompt_version: string; + schema_version: string; + normalized: NormalizeResponsePayload["normalized"]; + }; + normalizedQuestion: string; + routeSummary: RouteHintSummary | null; + droppedIntentSegments: string[]; + analysisContextForContract: { + as_of_date: string | null; + period_from: string | null; + period_to: string | null; + source: string | null; + snapshot_mode: "auto" | "force_snapshot" | "force_live"; + } | null; + executionPlan: Array<{ + fragment_id: string; + requirement_ids: string[]; + route: string; + should_execute: boolean; + no_route_reason?: string | null; + clarification_reason?: string | null; + }>; + requirementExtractionRequirements: AssistantRequirement[]; + coverageEvaluationRequirements: AssistantRequirement[]; + coverageReport: RequirementCoverageReport; + groundingCheck: AnswerGroundingCheck; + retrievalCalls: Array>; + retrievalResultsRaw: unknown[]; + retrievalResults: UnifiedRetrievalResult[]; + routesForDebug: Array>; + resolvedExecutionState: unknown; + questionTypeClass: string; + companyAnchors: unknown; + runtimeAnalysisContext: { + active: boolean; + as_of_date: string | null; + period_from: string | null; + period_to: string | null; + source: string | null; + snapshot_mode: "auto" | "force_snapshot" | "force_live"; + }; + businessScopeResolution: { + business_scope_raw?: string[]; + business_scope_resolved?: string[]; + company_grounding_applied?: boolean; + scope_resolution_reason?: string[]; + }; + temporalGuard: Record; + polarityAudit: Record; + claimAnchorAudit: Record; + targetedEvidenceAudit: unknown; + evidenceAdmissibilityGateAudit: unknown; + rbpLiveRouteAudit: unknown | null; + faLiveRouteAudit: unknown | null; + groundedAnswerEligibilityGuard: Record; + followupStateUsage: unknown | null; + composition: { + reply_type: AssistantReplyType; + fallback_type: unknown; + answer_structure_v11?: AnswerStructureV11 | null; + problem_centric_answer_applied?: boolean; + problem_units_used_count?: number; + problem_answer_mode?: string; + problem_unit_ids_used?: string[]; + }; + safeAssistantReplyBase: string; + featureContractsV11: boolean; + featureAnswerPolicyV11: boolean; + investigationStateSnapshot: unknown; + addressRuntimeMetaForDeep: + | { + attempted?: boolean; + applied?: boolean; + reason?: string | null; + provider?: string | null; + fallbackRuleHit?: string | null; + toolGateDecision?: string | null; + toolGateReason?: string | null; + predecomposeContract?: unknown; + orchestrationContract?: unknown; + } + | null + | undefined; +} + +export interface AssistantDeepTurnPackagingOutput { + evidenceBundleAssembly: AssistantEvidenceBundleAssembly; + contractsBundleV1: AssistantContractsBundleV1; + deepAnswerArtifacts: DeepAnswerArtifacts; + debug: Record; + assistantItem: AssistantConversationItem; + deepAnalysisLogDetails: Record; +} + +export function assembleAssistantDeepTurnPackaging(input: AssistantDeepTurnPackagingInput): AssistantDeepTurnPackagingOutput { + const normalizedPayload = (input.normalized.normalized ?? null) as Record | null; + const normalizedFragments = Array.isArray(normalizedPayload?.["fragments"]) ? (normalizedPayload?.["fragments"] as unknown[]) : []; + const evidenceBundleAssembly = assembleAssistantEvidenceBundle({ + retrievalCalls: input.retrievalCalls, + retrievalResults: input.retrievalResults + }); + const contractsBundleV1 = assembleAssistantContractsBundleV1({ + userMessage: input.userMessage, + normalizedQuestion: input.normalizedQuestion, + normalized: input.normalized.normalized, + routeSummary: input.routeSummary, + droppedIntentSegments: input.droppedIntentSegments, + analysisContext: input.analysisContextForContract, + executionPlan: input.executionPlan, + requirements: input.requirementExtractionRequirements, + evidenceBundleContractV1: evidenceBundleAssembly.evidenceBundleContractV1, + replyType: input.composition.reply_type, + coverageReport: input.coverageReport, + grounding: input.groundingCheck, + retrievalResults: input.retrievalResults + }); + const deepAnswerArtifacts = buildDeepAnswerArtifacts({ + safeAssistantReplyBase: input.safeAssistantReplyBase, + featureContractsV11: input.featureContractsV11, + featureAnswerPolicyV11: input.featureAnswerPolicyV11, + compositionAnswerStructureV11: input.composition.answer_structure_v11 ?? null, + coverageReport: input.coverageReport, + groundingCheck: input.groundingCheck, + retrievalResults: input.retrievalResults + }); + const debug = buildDeepAnalysisDebugPayload({ + traceId: input.normalized.trace_id, + promptVersion: input.normalized.prompt_version, + schemaVersion: input.normalized.schema_version, + fallbackType: input.composition.fallback_type, + routeSummary: input.routeSummary, + fragments: normalizedFragments, + requirementsExtracted: input.coverageEvaluationRequirements, + coverageReport: input.coverageReport, + routes: input.routesForDebug, + retrievalStatus: evidenceBundleAssembly.retrievalStatus, + retrievalResults: input.retrievalResults, + groundingCheck: input.groundingCheck, + droppedIntentSegments: input.droppedIntentSegments, + questionTypeClass: input.questionTypeClass, + companyAnchors: input.companyAnchors, + runtimeAnalysisContext: input.runtimeAnalysisContext, + businessScopeResolution: input.businessScopeResolution, + temporalGuard: input.temporalGuard, + polarityAudit: input.polarityAudit, + claimAnchorAudit: input.claimAnchorAudit, + targetedEvidenceAudit: input.targetedEvidenceAudit, + evidenceAdmissibilityGateAudit: input.evidenceAdmissibilityGateAudit, + rbpLiveRouteAudit: input.rbpLiveRouteAudit, + faLiveRouteAudit: input.faLiveRouteAudit, + groundedAnswerEligibilityGuard: input.groundedAnswerEligibilityGuard, + followupStateUsage: input.followupStateUsage, + compositionDebug: { + problem_centric_answer_applied: input.composition.problem_centric_answer_applied ?? false, + problem_units_used_count: input.composition.problem_units_used_count ?? 0, + problem_answer_mode: input.composition.problem_answer_mode ?? "stage1_policy_v11", + problem_unit_ids_used: Array.isArray(input.composition.problem_unit_ids_used) ? input.composition.problem_unit_ids_used : [] + }, + addressRuntimeMetaForDeep: input.addressRuntimeMetaForDeep, + outcomeClassV1: contractsBundleV1.outcomeClassV1, + assistantOrchestrationContractsV1: contractsBundleV1.assistantOrchestrationContractsV1, + answerStructureV11: deepAnswerArtifacts.answerStructureV11, + investigationStateSnapshot: input.investigationStateSnapshot, + normalizedPayload: normalizedPayload + }); + const assistantItem = buildAssistantConversationItem({ + messageId: input.messageId, + sessionId: input.sessionId, + text: deepAnswerArtifacts.safeAssistantReply, + replyType: input.composition.reply_type, + traceId: input.normalized.trace_id, + debug: debug as any + }); + const deepAnalysisLogDetails = buildDeepAnalysisProcessedLogDetails({ + sessionId: input.sessionId, + messageId: input.messageId, + userMessage: input.userMessage, + normalizerOutput: input.normalized.normalized, + executionPlan: input.executionPlan, + resolvedExecutionState: input.resolvedExecutionState, + routes: input.routesForDebug, + retrievalCalls: input.retrievalCalls, + retrievalResultsRaw: input.retrievalResultsRaw, + retrievalResultsNormalized: input.retrievalResults, + requirementsExtracted: input.coverageEvaluationRequirements, + coverageReport: input.coverageReport, + groundingCheck: input.groundingCheck, + replyType: input.composition.reply_type, + droppedIntentSegments: input.droppedIntentSegments, + questionTypeClass: input.questionTypeClass, + companyAnchors: input.companyAnchors, + runtimeAnalysisContext: input.runtimeAnalysisContext, + businessScopeResolution: input.businessScopeResolution, + temporalGuard: input.temporalGuard, + polarityAudit: input.polarityAudit, + claimAnchorAudit: input.claimAnchorAudit, + targetedEvidenceAudit: input.targetedEvidenceAudit, + evidenceAdmissibilityGateAudit: input.evidenceAdmissibilityGateAudit, + rbpLiveRouteAudit: input.rbpLiveRouteAudit, + faLiveRouteAudit: input.faLiveRouteAudit, + groundedAnswerEligibilityGuard: input.groundedAnswerEligibilityGuard, + followupStateUsage: input.followupStateUsage, + compositionDebug: { + problem_centric_answer_applied: input.composition.problem_centric_answer_applied ?? false, + problem_units_used_count: input.composition.problem_units_used_count ?? 0, + problem_answer_mode: input.composition.problem_answer_mode ?? "stage1_policy_v11", + problem_unit_ids_used: Array.isArray(input.composition.problem_unit_ids_used) ? input.composition.problem_unit_ids_used : [], + fallback_type: input.composition.fallback_type as string + }, + outcomeClassV1: contractsBundleV1.outcomeClassV1, + assistantOrchestrationContractsV1: contractsBundleV1.assistantOrchestrationContractsV1, + answerStructureV11: deepAnswerArtifacts.answerStructureV11, + investigationStateSnapshot: input.investigationStateSnapshot, + assistantReply: deepAnswerArtifacts.safeAssistantReply, + traceId: input.normalized.trace_id + }); + return { + evidenceBundleAssembly, + contractsBundleV1, + deepAnswerArtifacts, + debug, + assistantItem, + deepAnalysisLogDetails + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnPackagingRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantDeepTurnPackagingRuntimeAdapter.ts new file mode 100644 index 0000000..e7460b9 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnPackagingRuntimeAdapter.ts @@ -0,0 +1,212 @@ +import { nanoid } from "nanoid"; +import type { + AssistantConversationItem, + AssistantRequirement, + AnswerGroundingCheck, + RequirementCoverageReport, + UnifiedRetrievalResult +} from "../types/assistant"; +import type { NormalizeResponsePayload, RouteHintSummary } from "../types/normalizer"; +import type { InvestigationStateWithProblemUnits } from "../types/stage2ProblemUnits"; +import type { AssistantDeepTurnInputBuilderArgs } from "./assistantDeepTurnInputBuilder"; +import { buildAssistantDeepTurnPackagingInput } from "./assistantDeepTurnInputBuilder"; +import { assembleAssistantDeepTurnPackaging } from "./assistantDeepTurnPackaging"; +import type { + AssistantAnalysisContextForContract, + AssistantRuntimeAnalysisContextForPrePackaging +} from "./assistantDeepTurnPrePackagingContext"; +import { buildAssistantDeepTurnPrePackagingContext } from "./assistantDeepTurnPrePackagingContext"; +import { + buildAssistantInvestigationStateSnapshot, + persistAssistantInvestigationStateSnapshot +} from "./assistantInvestigationStateRuntimeAdapter"; + +type AssistantDeepTurnCompositionForPackaging = AssistantDeepTurnInputBuilderArgs["composition"] & { + assistant_reply: string; +}; + +export interface AssistantDeepTurnPackagingRuntimeInput { + featureInvestigationStateV1: boolean; + sessionId: string; + questionId: string; + userMessage: string; + normalized: { + trace_id: string; + prompt_version: string; + schema_version: string; + normalized: NormalizeResponsePayload["normalized"]; + }; + normalizedQuestion: string; + routeSummary: RouteHintSummary | null; + executionPlan: AssistantDeepTurnInputBuilderArgs["executionPlan"]; + requirementExtractionRequirements: AssistantRequirement[]; + coverageEvaluationRequirements: AssistantRequirement[]; + coverageReport: RequirementCoverageReport; + groundingCheck: AnswerGroundingCheck; + retrievalCalls: Array>; + retrievalResultsRaw: unknown[]; + retrievalResults: UnifiedRetrievalResult[]; + questionTypeClass: string; + companyAnchors: unknown; + runtimeAnalysisContext: AssistantDeepTurnInputBuilderArgs["runtimeAnalysisContext"]; + businessScopeResolution: AssistantDeepTurnInputBuilderArgs["businessScopeResolution"]; + temporalGuard: Record; + polarityAudit: Record; + claimAnchorAudit: Record; + targetedEvidenceAudit: unknown; + evidenceAdmissibilityGateAudit: unknown; + rbpLiveRouteAudit: unknown | null; + faLiveRouteAudit: unknown | null; + groundedAnswerEligibilityGuard: Record; + followupStateUsage?: unknown; + followupApplied: boolean; + composition: AssistantDeepTurnCompositionForPackaging; + featureContractsV11: boolean; + featureAnswerPolicyV11: boolean; + previousInvestigationState: InvestigationStateWithProblemUnits | null | undefined; + addressRuntimeMetaForDeep: + | { + attempted?: boolean; + applied?: boolean; + reason?: string | null; + provider?: string | null; + fallbackRuleHit?: string | null; + toolGateDecision?: string | null; + toolGateReason?: string | null; + predecomposeContract?: unknown; + orchestrationContract?: unknown; + } + | null + | undefined; + extractDroppedIntentSegments: (normalizedPayload: NormalizeResponsePayload["normalized"]) => string[]; + buildDebugRoutes: (routeSummary: RouteHintSummary | null) => Array>; + extractExecutionState: (normalizedPayload: NormalizeResponsePayload["normalized"]) => unknown; + sanitizeReply: (value: string, fallback?: string) => string; + persistInvestigationState: (sessionId: string, snapshot: InvestigationStateWithProblemUnits) => void; + nowIso?: () => string; + messageIdFactory?: () => string; + buildPrePackagingContextFn?: typeof buildAssistantDeepTurnPrePackagingContext; + buildInvestigationStateSnapshotFn?: typeof buildAssistantInvestigationStateSnapshot; + persistInvestigationStateSnapshotFn?: typeof persistAssistantInvestigationStateSnapshot; + buildDeepTurnPackagingInputFn?: typeof buildAssistantDeepTurnPackagingInput; + assembleDeepTurnPackagingFn?: typeof assembleAssistantDeepTurnPackaging; +} + +export interface AssistantDeepTurnPackagingRuntimeOutput { + messageId: string; + investigationStateSnapshot: InvestigationStateWithProblemUnits | null; + droppedIntentSegments: string[]; + analysisContextForContract: AssistantAnalysisContextForContract | null; + routesForDebug: Array>; + resolvedExecutionState: unknown; + safeAssistantReplyBase: string; + safeAssistantReply: string; + debug: Record; + assistantItem: AssistantConversationItem; + deepAnalysisLogDetails: Record; +} + +export function runAssistantDeepTurnPackagingRuntime( + input: AssistantDeepTurnPackagingRuntimeInput +): AssistantDeepTurnPackagingRuntimeOutput { + const buildPrePackagingContextSafe = input.buildPrePackagingContextFn ?? buildAssistantDeepTurnPrePackagingContext; + const buildInvestigationStateSnapshotSafe = + input.buildInvestigationStateSnapshotFn ?? buildAssistantInvestigationStateSnapshot; + const persistInvestigationStateSnapshotSafe = + input.persistInvestigationStateSnapshotFn ?? persistAssistantInvestigationStateSnapshot; + const buildDeepTurnPackagingInputSafe = input.buildDeepTurnPackagingInputFn ?? buildAssistantDeepTurnPackagingInput; + const assembleDeepTurnPackagingSafe = input.assembleDeepTurnPackagingFn ?? assembleAssistantDeepTurnPackaging; + + const deepTurnPrePackagingContext = buildPrePackagingContextSafe({ + normalizedPayload: input.normalized.normalized, + routeSummary: input.routeSummary, + runtimeAnalysisContext: input.runtimeAnalysisContext as AssistantRuntimeAnalysisContextForPrePackaging, + assistantReply: input.composition.assistant_reply, + extractDroppedIntentSegments: input.extractDroppedIntentSegments, + buildDebugRoutes: input.buildDebugRoutes, + extractExecutionState: input.extractExecutionState, + sanitizeReply: input.sanitizeReply + }); + const investigationStateSnapshot = buildInvestigationStateSnapshotSafe({ + featureEnabled: input.featureInvestigationStateV1, + previousState: input.previousInvestigationState, + timestamp: input.nowIso ? input.nowIso() : new Date().toISOString(), + questionId: input.questionId, + userMessage: input.userMessage, + routeSummary: input.routeSummary, + requirements: input.coverageEvaluationRequirements, + coverageReport: input.coverageReport, + retrievalResults: input.retrievalResults, + replyType: input.composition.reply_type, + followupApplied: input.followupApplied + }); + persistInvestigationStateSnapshotSafe({ + featureEnabled: input.featureInvestigationStateV1, + sessionId: input.sessionId, + snapshot: investigationStateSnapshot, + persist: input.persistInvestigationState + }); + const messageId = input.messageIdFactory ? input.messageIdFactory() : `msg-${nanoid(10)}`; + const deepTurnPackagingInput = buildDeepTurnPackagingInputSafe({ + sessionId: input.sessionId, + messageId, + userMessage: input.userMessage, + normalized: input.normalized, + normalizedQuestion: input.normalizedQuestion, + routeSummary: input.routeSummary, + droppedIntentSegments: deepTurnPrePackagingContext.droppedIntentSegments, + analysisContextForContract: deepTurnPrePackagingContext.analysisContextForContract, + executionPlan: input.executionPlan, + requirementExtractionRequirements: input.requirementExtractionRequirements, + coverageEvaluationRequirements: input.coverageEvaluationRequirements, + coverageReport: input.coverageReport, + groundingCheck: input.groundingCheck, + retrievalCalls: input.retrievalCalls, + retrievalResultsRaw: input.retrievalResultsRaw, + retrievalResults: input.retrievalResults, + routesForDebug: deepTurnPrePackagingContext.routesForDebug, + resolvedExecutionState: deepTurnPrePackagingContext.resolvedExecutionState, + questionTypeClass: input.questionTypeClass, + companyAnchors: input.companyAnchors, + runtimeAnalysisContext: input.runtimeAnalysisContext, + businessScopeResolution: input.businessScopeResolution, + temporalGuard: input.temporalGuard, + polarityAudit: input.polarityAudit, + claimAnchorAudit: input.claimAnchorAudit, + targetedEvidenceAudit: input.targetedEvidenceAudit, + evidenceAdmissibilityGateAudit: input.evidenceAdmissibilityGateAudit, + rbpLiveRouteAudit: input.rbpLiveRouteAudit, + faLiveRouteAudit: input.faLiveRouteAudit, + groundedAnswerEligibilityGuard: input.groundedAnswerEligibilityGuard, + followupStateUsage: input.followupStateUsage, + composition: { + reply_type: input.composition.reply_type, + fallback_type: input.composition.fallback_type, + answer_structure_v11: input.composition.answer_structure_v11, + problem_centric_answer_applied: input.composition.problem_centric_answer_applied, + problem_units_used_count: input.composition.problem_units_used_count, + problem_answer_mode: input.composition.problem_answer_mode, + problem_unit_ids_used: input.composition.problem_unit_ids_used + }, + safeAssistantReplyBase: deepTurnPrePackagingContext.safeAssistantReplyBase, + featureContractsV11: input.featureContractsV11, + featureAnswerPolicyV11: input.featureAnswerPolicyV11, + investigationStateSnapshot, + addressRuntimeMetaForDeep: input.addressRuntimeMetaForDeep + }); + const deepTurnPackaging = assembleDeepTurnPackagingSafe(deepTurnPackagingInput); + + return { + messageId, + investigationStateSnapshot, + droppedIntentSegments: deepTurnPrePackagingContext.droppedIntentSegments, + analysisContextForContract: deepTurnPrePackagingContext.analysisContextForContract, + routesForDebug: deepTurnPrePackagingContext.routesForDebug, + resolvedExecutionState: deepTurnPrePackagingContext.resolvedExecutionState, + safeAssistantReplyBase: deepTurnPrePackagingContext.safeAssistantReplyBase, + safeAssistantReply: deepTurnPackaging.deepAnswerArtifacts.safeAssistantReply, + debug: deepTurnPackaging.debug, + assistantItem: deepTurnPackaging.assistantItem, + deepAnalysisLogDetails: deepTurnPackaging.deepAnalysisLogDetails + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnPlanRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantDeepTurnPlanRuntimeAdapter.ts new file mode 100644 index 0000000..8b0e482 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnPlanRuntimeAdapter.ts @@ -0,0 +1,91 @@ +import type { AssistantRequirement } from "../types/assistant"; +import type { NormalizeResponsePayload, RouteHintSummary } from "../types/normalizer"; +import type { AssistantExecutionPlanItem } from "./assistantQueryPlanning"; + +export interface AssistantRequirementExtractionLike { + requirements: AssistantRequirement[]; + byFragment: Map; +} + +export interface AssistantPlanEnforcementAuditLike { + executionPlan: AssistantExecutionPlanItem[]; + audit: unknown; +} + +export interface BuildAssistantDeepTurnExecutionPlanInput { + routeSummary: RouteHintSummary | null; + normalizedPayload: NormalizeResponsePayload["normalized"]; + userMessage: string; + claimType: string; + temporalGuard: unknown; + domainPolarityGuardInitial: unknown; + extractRequirements: ( + routeSummary: RouteHintSummary | null, + normalizedPayload: NormalizeResponsePayload["normalized"], + userMessage: string + ) => AssistantRequirementExtractionLike; + toExecutionPlan: ( + routeSummary: RouteHintSummary | null, + normalizedPayload: NormalizeResponsePayload["normalized"], + userMessage: string, + requirementByFragment: Map + ) => AssistantExecutionPlanItem[]; + enforceRbpLiveRoutePlan: (input: { + executionPlan: AssistantExecutionPlanItem[]; + claimType: string; + temporalGuard: unknown; + }) => AssistantPlanEnforcementAuditLike; + enforceFaLiveRoutePlan: (input: { + executionPlan: AssistantExecutionPlanItem[]; + claimType: string; + temporalGuard: unknown; + }) => AssistantPlanEnforcementAuditLike; + applyTemporalHintToExecutionPlan: ( + executionPlan: AssistantExecutionPlanItem[], + temporalGuard: unknown + ) => AssistantExecutionPlanItem[]; + applyPolarityHintToExecutionPlan: ( + executionPlan: AssistantExecutionPlanItem[], + domainPolarityGuardInitial: unknown + ) => AssistantExecutionPlanItem[]; +} + +export interface BuildAssistantDeepTurnExecutionPlanOutput { + requirementExtraction: AssistantRequirementExtractionLike; + executionPlan: AssistantExecutionPlanItem[]; + rbpRoutePlanEnforcement: AssistantPlanEnforcementAuditLike; + faRoutePlanEnforcement: AssistantPlanEnforcementAuditLike; +} + +export function buildAssistantDeepTurnExecutionPlan( + input: BuildAssistantDeepTurnExecutionPlanInput +): BuildAssistantDeepTurnExecutionPlanOutput { + const requirementExtraction = input.extractRequirements(input.routeSummary, input.normalizedPayload, input.userMessage); + let executionPlan = input.toExecutionPlan( + input.routeSummary, + input.normalizedPayload, + input.userMessage, + requirementExtraction.byFragment + ); + const rbpRoutePlanEnforcement = input.enforceRbpLiveRoutePlan({ + executionPlan, + claimType: input.claimType, + temporalGuard: input.temporalGuard + }); + executionPlan = rbpRoutePlanEnforcement.executionPlan; + const faRoutePlanEnforcement = input.enforceFaLiveRoutePlan({ + executionPlan, + claimType: input.claimType, + temporalGuard: input.temporalGuard + }); + executionPlan = faRoutePlanEnforcement.executionPlan; + executionPlan = input.applyTemporalHintToExecutionPlan(executionPlan, input.temporalGuard); + executionPlan = input.applyPolarityHintToExecutionPlan(executionPlan, input.domainPolarityGuardInitial); + + return { + requirementExtraction, + executionPlan, + rbpRoutePlanEnforcement, + faRoutePlanEnforcement + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnPrePackagingContext.ts b/llm_normalizer/backend/src/services/assistantDeepTurnPrePackagingContext.ts new file mode 100644 index 0000000..fd74aa3 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnPrePackagingContext.ts @@ -0,0 +1,57 @@ +import type { NormalizeResponsePayload, RouteHintSummary } from "../types/normalizer"; + +export interface AssistantRuntimeAnalysisContextForPrePackaging { + active: boolean; + as_of_date: string | null; + period_from: string | null; + period_to: string | null; + source: string | null; + snapshot_mode: "auto" | "force_snapshot" | "force_live"; +} + +export interface AssistantAnalysisContextForContract { + as_of_date: string | null; + period_from: string | null; + period_to: string | null; + source: string | null; + snapshot_mode: "auto" | "force_snapshot" | "force_live"; +} + +export interface BuildAssistantDeepTurnPrePackagingContextInput { + normalizedPayload: NormalizeResponsePayload["normalized"]; + routeSummary: RouteHintSummary | null; + runtimeAnalysisContext: AssistantRuntimeAnalysisContextForPrePackaging; + assistantReply: string; + extractDroppedIntentSegments: (normalizedPayload: NormalizeResponsePayload["normalized"]) => string[]; + buildDebugRoutes: (routeSummary: RouteHintSummary | null) => Array>; + extractExecutionState: (normalizedPayload: NormalizeResponsePayload["normalized"]) => unknown; + sanitizeReply: (value: string, fallback?: string) => string; +} + +export interface AssistantDeepTurnPrePackagingContext { + droppedIntentSegments: string[]; + analysisContextForContract: AssistantAnalysisContextForContract | null; + routesForDebug: Array>; + resolvedExecutionState: unknown; + safeAssistantReplyBase: string; +} + +export function buildAssistantDeepTurnPrePackagingContext( + input: BuildAssistantDeepTurnPrePackagingContextInput +): AssistantDeepTurnPrePackagingContext { + return { + droppedIntentSegments: input.extractDroppedIntentSegments(input.normalizedPayload), + analysisContextForContract: input.runtimeAnalysisContext.active + ? { + as_of_date: input.runtimeAnalysisContext.as_of_date, + period_from: input.runtimeAnalysisContext.period_from, + period_to: input.runtimeAnalysisContext.period_to, + source: input.runtimeAnalysisContext.source, + snapshot_mode: input.runtimeAnalysisContext.snapshot_mode + } + : null, + routesForDebug: input.buildDebugRoutes(input.routeSummary), + resolvedExecutionState: input.extractExecutionState(input.normalizedPayload), + safeAssistantReplyBase: input.sanitizeReply(input.assistantReply, "Нужны уточнения для надежного ответа.") + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnResponseBuilder.ts b/llm_normalizer/backend/src/services/assistantDeepTurnResponseBuilder.ts new file mode 100644 index 0000000..0e0e6ff --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnResponseBuilder.ts @@ -0,0 +1,29 @@ +import type { + AssistantConversationItem, + AssistantDebugPayload, + AssistantMessageResponsePayload, + AssistantReplyType +} from "../types/assistant"; + +export interface BuildAssistantDeepTurnSuccessResponseInput { + sessionId: string; + assistantReply: string; + replyType: AssistantReplyType; + conversationItem: AssistantConversationItem; + debug: AssistantDebugPayload | Record; + conversation: AssistantConversationItem[]; +} + +export function buildAssistantDeepTurnSuccessResponse( + input: BuildAssistantDeepTurnSuccessResponseInput +): AssistantMessageResponsePayload { + return { + ok: true, + session_id: input.sessionId, + assistant_reply: input.assistantReply, + reply_type: input.replyType, + conversation_item: input.conversationItem, + debug: input.debug as AssistantDebugPayload, + conversation: input.conversation + }; +} diff --git a/llm_normalizer/backend/src/services/assistantDeepTurnRetrievalRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantDeepTurnRetrievalRuntimeAdapter.ts new file mode 100644 index 0000000..834422a --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantDeepTurnRetrievalRuntimeAdapter.ts @@ -0,0 +1,131 @@ +import type { UnifiedRetrievalResult } from "../types/assistant"; +import type { AssistantExecutionPlanItem } from "./assistantQueryPlanning"; +import { normalizeRetrievalResult } from "./retrievalResultNormalizer"; + +export interface AssistantLiveTemporalHint { + as_of_date: string | null; + period_from: string | null; + period_to: string | null; + source: string | null; +} + +export interface AssistantRetrievalCallRecord { + fragment_id: string; + requirement_ids: string[]; + route: string; + status: "skipped" | "executed" | "failed"; + query_text: string; + reason: string | null; +} + +export interface AssistantRetrievalRawResultRecord { + fragment_id: string; + route: string; + raw_result: unknown; +} + +export interface AssistantDeepTurnRetrievalExecutionInput { + executionPlan: AssistantExecutionPlanItem[]; + liveTemporalHint: AssistantLiveTemporalHint | null; + executeRouteRuntime: ( + route: string, + fragmentText: string, + options: { + temporalHint: AssistantLiveTemporalHint | null; + } + ) => Promise; + mapNoRouteReason: (reason: string | null) => string; + buildSkippedResult: (item: AssistantExecutionPlanItem) => UnifiedRetrievalResult; + normalizeRetrievalResultFn?: typeof normalizeRetrievalResult; +} + +export interface AssistantDeepTurnRetrievalExecutionOutput { + retrievalCalls: AssistantRetrievalCallRecord[]; + retrievalResultsRaw: AssistantRetrievalRawResultRecord[]; + retrievalResults: UnifiedRetrievalResult[]; +} + +function buildRouteExecutorErrorRawResult(route: string, message: string): Record { + return { + status: "error", + result_type: "summary", + items: [], + summary: { + route + }, + evidence: [], + why_included: [], + selection_reason: [], + risk_factors: [], + business_interpretation: [], + confidence: "low", + limitations: ["Route executor failed."], + errors: [message] + }; +} + +export async function executeAssistantDeepTurnRetrievalPlan( + input: AssistantDeepTurnRetrievalExecutionInput +): Promise { + const normalizeRetrievalResultSafe = input.normalizeRetrievalResultFn ?? normalizeRetrievalResult; + const retrievalCalls: AssistantRetrievalCallRecord[] = []; + const retrievalResultsRaw: AssistantRetrievalRawResultRecord[] = []; + const retrievalResults: UnifiedRetrievalResult[] = []; + + for (const planItem of input.executionPlan) { + if (!planItem.should_execute) { + retrievalCalls.push({ + fragment_id: planItem.fragment_id, + requirement_ids: planItem.requirement_ids, + route: planItem.route, + status: "skipped", + query_text: planItem.fragment_text, + reason: input.mapNoRouteReason(planItem.no_route_reason) + }); + retrievalResults.push(input.buildSkippedResult(planItem)); + continue; + } + + retrievalCalls.push({ + fragment_id: planItem.fragment_id, + requirement_ids: planItem.requirement_ids, + route: planItem.route, + status: "executed", + query_text: planItem.fragment_text, + reason: null + }); + + try { + const raw = await input.executeRouteRuntime(planItem.route, planItem.fragment_text, { + temporalHint: input.liveTemporalHint + }); + retrievalResultsRaw.push({ + fragment_id: planItem.fragment_id, + route: planItem.route, + raw_result: raw + }); + retrievalResults.push( + normalizeRetrievalResultSafe(planItem.fragment_id, planItem.requirement_ids, planItem.route, raw as any) + ); + } catch (error) { + const message = error instanceof Error ? error.message : String(error); + retrievalCalls[retrievalCalls.length - 1].status = "failed"; + retrievalCalls[retrievalCalls.length - 1].reason = message; + const rawError = buildRouteExecutorErrorRawResult(planItem.route, message); + retrievalResultsRaw.push({ + fragment_id: planItem.fragment_id, + route: planItem.route, + raw_result: rawError + }); + retrievalResults.push( + normalizeRetrievalResultSafe(planItem.fragment_id, planItem.requirement_ids, planItem.route, rawError as any) + ); + } + } + + return { + retrievalCalls, + retrievalResultsRaw, + retrievalResults + }; +} diff --git a/llm_normalizer/backend/src/services/assistantEvidenceBundleAssembler.ts b/llm_normalizer/backend/src/services/assistantEvidenceBundleAssembler.ts new file mode 100644 index 0000000..70a6116 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantEvidenceBundleAssembler.ts @@ -0,0 +1,36 @@ +import type { AssistantDebugPayload, UnifiedRetrievalResult } from "../types/assistant"; +import { + buildAssistantEvidenceBundleContractV1, + type AssistantEvidenceBundleContractV1 +} from "./assistantOrchestrationContracts"; + +type RetrievalStatusItem = AssistantDebugPayload["retrieval_status"][number]; + +export interface AssistantEvidenceBundleAssembly { + evidenceBundleContractV1: AssistantEvidenceBundleContractV1; + retrievalStatus: RetrievalStatusItem[]; +} + +function buildRetrievalStatus(retrievalResults: UnifiedRetrievalResult[]): RetrievalStatusItem[] { + return retrievalResults.map((item) => ({ + fragment_id: item.fragment_id, + requirement_ids: item.requirement_ids, + route: item.route, + status: item.status, + result_type: item.result_type + })); +} + +export function assembleAssistantEvidenceBundle(input: { + retrievalCalls: Array>; + retrievalResults: UnifiedRetrievalResult[]; +}): AssistantEvidenceBundleAssembly { + const retrievalResults = Array.isArray(input.retrievalResults) ? input.retrievalResults : []; + return { + evidenceBundleContractV1: buildAssistantEvidenceBundleContractV1({ + retrievalCalls: Array.isArray(input.retrievalCalls) ? input.retrievalCalls : [], + retrievalResults + }), + retrievalStatus: buildRetrievalStatus(retrievalResults) + }; +} diff --git a/llm_normalizer/backend/src/services/assistantInvestigationStateRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantInvestigationStateRuntimeAdapter.ts new file mode 100644 index 0000000..f7a9414 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantInvestigationStateRuntimeAdapter.ts @@ -0,0 +1,55 @@ +import type { AssistantReplyType, AssistantRequirement, RequirementCoverageReport, UnifiedRetrievalResult } from "../types/assistant"; +import type { RouteHintSummary } from "../types/normalizer"; +import type { InvestigationStateWithProblemUnits } from "../types/stage2ProblemUnits"; +import { updateInvestigationState } from "./investigationState"; + +export interface BuildAssistantInvestigationStateSnapshotInput { + featureEnabled: boolean; + previousState: InvestigationStateWithProblemUnits | null | undefined; + timestamp: string; + questionId: string; + userMessage: string; + routeSummary: RouteHintSummary | null; + requirements: AssistantRequirement[]; + coverageReport: RequirementCoverageReport; + retrievalResults: UnifiedRetrievalResult[]; + replyType: AssistantReplyType; + followupApplied: boolean; +} + +export function buildAssistantInvestigationStateSnapshot( + input: BuildAssistantInvestigationStateSnapshotInput +): InvestigationStateWithProblemUnits | null { + if (!input.featureEnabled || !input.previousState) { + return null; + } + return updateInvestigationState({ + previous: input.previousState, + timestamp: input.timestamp, + questionId: input.questionId, + userMessage: input.userMessage, + routeSummary: input.routeSummary, + requirements: input.requirements, + coverageReport: input.coverageReport, + retrievalResults: input.retrievalResults, + replyType: input.replyType, + followupApplied: input.followupApplied + }); +} + +export interface PersistAssistantInvestigationStateSnapshotInput { + featureEnabled: boolean; + sessionId: string; + snapshot: InvestigationStateWithProblemUnits | null; + persist: (sessionId: string, snapshot: InvestigationStateWithProblemUnits) => void; +} + +export function persistAssistantInvestigationStateSnapshot( + input: PersistAssistantInvestigationStateSnapshotInput +): boolean { + if (!input.featureEnabled || !input.snapshot) { + return false; + } + input.persist(input.sessionId, input.snapshot); + return true; +} diff --git a/llm_normalizer/backend/src/services/assistantMessageLogAssembler.ts b/llm_normalizer/backend/src/services/assistantMessageLogAssembler.ts new file mode 100644 index 0000000..5882047 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantMessageLogAssembler.ts @@ -0,0 +1,159 @@ +import type { AnswerGroundingCheck, RequirementCoverageReport } from "../types/assistant"; + +export interface DeepAnalysisMessageLogDetailsInput { + sessionId: string; + messageId: string; + userMessage: string; + normalizerOutput: unknown; + executionPlan: Array>; + resolvedExecutionState: unknown; + routes: Array>; + retrievalCalls: Array>; + retrievalResultsRaw: unknown[]; + retrievalResultsNormalized: unknown[]; + requirementsExtracted: unknown[]; + coverageReport: RequirementCoverageReport; + groundingCheck: AnswerGroundingCheck; + replyType: string; + droppedIntentSegments: string[]; + questionTypeClass: string; + companyAnchors: unknown; + runtimeAnalysisContext: { + active: boolean; + as_of_date: string | null; + period_from: string | null; + period_to: string | null; + source: string | null; + snapshot_mode: "auto" | "force_snapshot" | "force_live"; + }; + businessScopeResolution: { + business_scope_raw?: string[]; + business_scope_resolved?: string[]; + company_grounding_applied?: boolean; + scope_resolution_reason?: string[]; + }; + temporalGuard: Record; + polarityAudit: Record; + claimAnchorAudit: Record; + targetedEvidenceAudit: unknown; + evidenceAdmissibilityGateAudit: unknown; + rbpLiveRouteAudit: unknown | null; + faLiveRouteAudit: unknown | null; + groundedAnswerEligibilityGuard: Record; + followupStateUsage: unknown | null; + compositionDebug: { + problem_centric_answer_applied?: boolean; + problem_units_used_count?: number; + problem_answer_mode?: string; + problem_unit_ids_used?: string[]; + fallback_type?: string; + }; + outcomeClassV1: unknown; + assistantOrchestrationContractsV1: unknown; + answerStructureV11: unknown; + investigationStateSnapshot: unknown; + assistantReply: string; + traceId: string; +} + +function toAnalysisContext(input: DeepAnalysisMessageLogDetailsInput["runtimeAnalysisContext"]): Record | null { + if (!input.active) { + return null; + } + return { + as_of_date: input.as_of_date, + period_from: input.period_from, + period_to: input.period_to, + source: input.source, + snapshot_mode: input.snapshot_mode + }; +} + +function resolveCoverageStatus(coverageReport: RequirementCoverageReport): "full" | "partial_or_limited" { + return coverageReport.requirements_total === coverageReport.requirements_covered && + coverageReport.requirements_uncovered.length === 0 && + coverageReport.requirements_partially_covered.length === 0 + ? "full" + : "partial_or_limited"; +} + +export function buildDeepAnalysisProcessedLogDetails(input: DeepAnalysisMessageLogDetailsInput): Record { + const analysisContext = toAnalysisContext(input.runtimeAnalysisContext); + return { + session_id: input.sessionId, + message_id: input.messageId, + user_message: input.userMessage, + normalizer_output: input.normalizerOutput, + execution_plan: input.executionPlan, + resolved_execution_state: input.resolvedExecutionState, + routes: input.routes, + retrieval_calls: input.retrievalCalls, + retrieval_results_raw: input.retrievalResultsRaw, + retrieval_results_normalized: input.retrievalResultsNormalized, + requirements_extracted: input.requirementsExtracted, + requirements_total: input.coverageReport.requirements_total, + requirements_covered: input.coverageReport.requirements_covered, + requirements_uncovered: input.coverageReport.requirements_uncovered, + coverage_status: resolveCoverageStatus(input.coverageReport), + answer_grounding_status: input.groundingCheck.status, + reply_semantic_type: input.replyType, + why_included_summary: input.groundingCheck.why_included_summary, + selection_reason_summary: input.groundingCheck.selection_reason_summary, + route_subject_match: input.groundingCheck.route_subject_match, + clarification_target: input.coverageReport.clarification_needed_for, + dropped_intent_segments: input.droppedIntentSegments, + question_type_class: input.questionTypeClass, + company_anchors: input.companyAnchors, + analysis_context_applied: input.runtimeAnalysisContext.active, + analysis_context: analysisContext, + business_scope_raw: input.businessScopeResolution.business_scope_raw, + business_scope_resolved: input.businessScopeResolution.business_scope_resolved, + company_grounding_applied: input.businessScopeResolution.company_grounding_applied, + scope_resolution_reason: input.businessScopeResolution.scope_resolution_reason, + company_scope_resolution_reason: input.businessScopeResolution.scope_resolution_reason, + raw_time_anchor: input.temporalGuard.raw_time_anchor, + raw_time_scope: input.temporalGuard.raw_time_scope, + resolved_time_anchor: input.temporalGuard.resolved_time_anchor, + resolved_primary_period: input.temporalGuard.resolved_primary_period, + effective_primary_period: input.temporalGuard.effective_primary_period, + temporal_guard_input: input.temporalGuard.temporal_guard_input, + temporal_alignment_status: input.temporalGuard.temporal_alignment_status, + temporal_resolution_source: input.temporalGuard.temporal_resolution_source, + temporal_guard_basis: input.temporalGuard.temporal_guard_basis, + temporal_guard_applied: input.temporalGuard.temporal_guard_applied, + temporal_guard_outcome: input.temporalGuard.temporal_guard_outcome, + temporal_guard: input.temporalGuard, + raw_numeric_tokens: input.polarityAudit.raw_numeric_tokens, + classified_numeric_tokens: input.polarityAudit.classified_numeric_tokens, + rejected_as_non_accounts: input.polarityAudit.rejected_as_non_accounts, + resolved_account_anchors: input.polarityAudit.resolved_account_anchors, + domain_polarity_guard: input.polarityAudit, + claim_anchor_audit: input.claimAnchorAudit, + settlement_role: input.claimAnchorAudit.settlement_role ?? null, + settlement_role_resolution_reason: input.claimAnchorAudit.settlement_role_resolution_reason ?? [], + polarity_resolution_status: input.claimAnchorAudit.polarity_resolution_status ?? "not_applicable", + targeted_evidence_acquisition: input.targetedEvidenceAudit, + evidence_admissibility_gate: input.evidenceAdmissibilityGateAudit, + ...(input.rbpLiveRouteAudit ? { rbp_live_route_audit: input.rbpLiveRouteAudit } : {}), + ...(input.faLiveRouteAudit ? { fa_live_route_audit: input.faLiveRouteAudit } : {}), + eligibility_time_basis: input.groundedAnswerEligibilityGuard.eligibility_time_basis, + grounded_answer_eligibility_guard: input.groundedAnswerEligibilityGuard, + ...(input.followupStateUsage ? { followup_state_usage: input.followupStateUsage } : {}), + problem_centric_answer_applied: input.compositionDebug.problem_centric_answer_applied ?? false, + problem_units_used_count: input.compositionDebug.problem_units_used_count ?? 0, + problem_answer_mode: input.compositionDebug.problem_answer_mode ?? "stage1_policy_v11", + ...(Array.isArray(input.compositionDebug.problem_unit_ids_used) && input.compositionDebug.problem_unit_ids_used.length > 0 + ? { + problem_unit_ids_used: input.compositionDebug.problem_unit_ids_used + } + : {}), + assistant_outcome_class_v1: input.outcomeClassV1, + assistant_orchestration_contracts_v1: input.assistantOrchestrationContractsV1, + answer_structure_v11: input.answerStructureV11, + investigation_state_snapshot: input.investigationStateSnapshot, + fallback_type: input.compositionDebug.fallback_type, + assistant_reply: input.assistantReply, + reply_type: input.replyType, + trace_id: input.traceId + }; +} diff --git a/llm_normalizer/backend/src/services/assistantOrchestrationContracts.ts b/llm_normalizer/backend/src/services/assistantOrchestrationContracts.ts new file mode 100644 index 0000000..0b0302f --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantOrchestrationContracts.ts @@ -0,0 +1,275 @@ +import type { + AssistantReplyType, + AssistantRequirement, + AnswerGroundingCheck, + RequirementCoverageReport, + UnifiedRetrievalResult +} from "../types/assistant"; +import type { NormalizedPayload, RouteHintSummary } from "../types/normalizer"; + +export type AssistantOutcomeClassV1 = + | "FULLY_ANSWERED" + | "PARTIALLY_ANSWERED" + | "BLOCKED_BY_AMBIGUITY" + | "BLOCKED_BY_MISSING_DATA" + | "BLOCKED_BY_TOOLING" + | "MISROUTED" + | "FAILED_TO_BIND_ENTITIES"; + +export interface AssistantAnalysisContextContractV1 { + as_of_date: string | null; + period_from: string | null; + period_to: string | null; + source: string | null; + snapshot_mode: "auto" | "force_snapshot" | "force_live"; +} + +export interface AssistantQueryFrameContractV1 { + schema_version: "assistant_query_frame_v1"; + original_user_question: string; + normalized_question: string; + route_summary_mode: RouteHintSummary["mode"] | "none"; + fragments_total: number; + dropped_intent_segments: string[]; + analysis_context: AssistantAnalysisContextContractV1 | null; +} + +export interface AssistantExecutionPlanStepContractV1 { + fragment_id: string; + route: string; + should_execute: boolean; + requirement_ids: string[]; + no_route_reason: string | null; + clarification_reason: string | null; +} + +export interface AssistantExecutionPlanContractV1 { + schema_version: "assistant_execution_plan_v1"; + steps: AssistantExecutionPlanStepContractV1[]; + requirements_total: number; +} + +export interface AssistantEvidenceBundleContractV1 { + schema_version: "assistant_evidence_bundle_v1"; + retrieval_calls_total: number; + retrieval_results_total: number; + retrieval_status_breakdown: { + ok: number; + partial: number; + empty: number; + error: number; + }; + evidence_total: number; + source_refs_total: number; + limitation_total: number; + error_total: number; +} + +export interface AssistantCoverageContractV1 { + schema_version: "assistant_coverage_contract_v1"; + coverage_report: RequirementCoverageReport; + grounding: AnswerGroundingCheck; + outcome_class: AssistantOutcomeClassV1; +} + +function normalizeSnapshotMode(value: unknown): "auto" | "force_snapshot" | "force_live" { + const token = String(value ?? "").trim(); + if (token === "force_snapshot" || token === "force_live") { + return token; + } + return "auto"; +} + +function extractFragmentsTotal(normalized: NormalizedPayload | null | undefined): number { + if (!normalized || typeof normalized !== "object") { + return 0; + } + const source = normalized as unknown as { fragments?: unknown }; + const fragments = source.fragments; + return Array.isArray(fragments) ? fragments.length : 0; +} + +function collectEvidenceTotals(retrievalResults: UnifiedRetrievalResult[]): { + evidence_total: number; + source_refs_total: number; + limitation_total: number; + error_total: number; +} { + let evidenceTotal = 0; + const sourceRefs = new Set(); + let limitationTotal = 0; + let errorTotal = 0; + + for (const result of retrievalResults) { + evidenceTotal += Array.isArray(result.evidence) ? result.evidence.length : 0; + limitationTotal += Array.isArray(result.limitations) ? result.limitations.length : 0; + errorTotal += Array.isArray(result.errors) ? result.errors.length : 0; + for (const evidence of result.evidence ?? []) { + const ref = String(evidence?.source_ref?.canonical_ref ?? "").trim(); + if (ref) { + sourceRefs.add(ref); + } + } + } + + return { + evidence_total: evidenceTotal, + source_refs_total: sourceRefs.size, + limitation_total: limitationTotal, + error_total: errorTotal + }; +} + +export function classifyAssistantOutcomeClassV1(input: { + replyType: AssistantReplyType; + coverageReport: RequirementCoverageReport; + grounding: AnswerGroundingCheck; + retrievalResults: UnifiedRetrievalResult[]; +}): AssistantOutcomeClassV1 { + const replyType = input.replyType; + const grounding = input.grounding; + const coverage = input.coverageReport; + const hasOnlyErrors = + input.retrievalResults.length > 0 && + input.retrievalResults.every((item) => item.status === "error"); + + if (replyType === "backend_error" || hasOnlyErrors) { + return "BLOCKED_BY_TOOLING"; + } + if (grounding.status === "route_mismatch_blocked" || replyType === "route_mismatch_blocked") { + return "MISROUTED"; + } + if (replyType === "clarification_required" || coverage.clarification_needed_for.length > 0) { + return "BLOCKED_BY_AMBIGUITY"; + } + if (replyType === "out_of_scope") { + return "BLOCKED_BY_AMBIGUITY"; + } + + const fullCoverage = + coverage.requirements_total > 0 && + coverage.requirements_total === coverage.requirements_covered && + coverage.requirements_uncovered.length === 0 && + coverage.requirements_partially_covered.length === 0 && + coverage.clarification_needed_for.length === 0 && + coverage.out_of_scope_requirements.length === 0; + if (fullCoverage && grounding.status === "grounded") { + return "FULLY_ANSWERED"; + } + + const hasAnyCoverage = + coverage.requirements_covered > 0 || + coverage.requirements_partially_covered.length > 0 || + grounding.status === "partial"; + if (hasAnyCoverage) { + return "PARTIALLY_ANSWERED"; + } + + const missingRequirementSignal = + grounding.missing_requirements.length > 0 || + coverage.requirements_uncovered.length > 0 || + coverage.requirements_total > 0; + const possibleBindingFailure = + replyType === "no_grounded_answer" && + missingRequirementSignal && + grounding.route_subject_match; + if (possibleBindingFailure) { + return "FAILED_TO_BIND_ENTITIES"; + } + + return "BLOCKED_BY_MISSING_DATA"; +} + +export function buildAssistantQueryFrameContractV1(input: { + userMessage: string; + normalizedQuestion: string; + normalized: NormalizedPayload | null | undefined; + routeSummary: RouteHintSummary | null; + droppedIntentSegments: string[]; + analysisContext?: { + as_of_date?: string | null; + period_from?: string | null; + period_to?: string | null; + source?: string | null; + snapshot_mode?: string | null; + } | null; +}): AssistantQueryFrameContractV1 { + const analysis = input.analysisContext + ? { + as_of_date: input.analysisContext.as_of_date ?? null, + period_from: input.analysisContext.period_from ?? null, + period_to: input.analysisContext.period_to ?? null, + source: input.analysisContext.source ?? null, + snapshot_mode: normalizeSnapshotMode(input.analysisContext.snapshot_mode) + } + : null; + + return { + schema_version: "assistant_query_frame_v1", + original_user_question: String(input.userMessage ?? ""), + normalized_question: String(input.normalizedQuestion ?? ""), + route_summary_mode: input.routeSummary?.mode ?? "none", + fragments_total: extractFragmentsTotal(input.normalized), + dropped_intent_segments: Array.isArray(input.droppedIntentSegments) ? [...input.droppedIntentSegments] : [], + analysis_context: analysis + }; +} + +export function buildAssistantExecutionPlanContractV1(input: { + executionPlan: Array<{ + fragment_id: string; + requirement_ids: string[]; + route: string; + should_execute: boolean; + no_route_reason?: string | null; + clarification_reason?: string | null; + }>; + requirements: AssistantRequirement[]; +}): AssistantExecutionPlanContractV1 { + return { + schema_version: "assistant_execution_plan_v1", + steps: (Array.isArray(input.executionPlan) ? input.executionPlan : []).map((item) => ({ + fragment_id: String(item.fragment_id ?? ""), + route: String(item.route ?? ""), + should_execute: Boolean(item.should_execute), + requirement_ids: Array.isArray(item.requirement_ids) ? [...item.requirement_ids] : [], + no_route_reason: item.no_route_reason ?? null, + clarification_reason: item.clarification_reason ?? null + })), + requirements_total: Array.isArray(input.requirements) ? input.requirements.length : 0 + }; +} + +export function buildAssistantEvidenceBundleContractV1(input: { + retrievalCalls: Array>; + retrievalResults: UnifiedRetrievalResult[]; +}): AssistantEvidenceBundleContractV1 { + const retrievalResults = Array.isArray(input.retrievalResults) ? input.retrievalResults : []; + const breakdown = { + ok: retrievalResults.filter((item) => item.status === "ok").length, + partial: retrievalResults.filter((item) => item.status === "partial").length, + empty: retrievalResults.filter((item) => item.status === "empty").length, + error: retrievalResults.filter((item) => item.status === "error").length + }; + const totals = collectEvidenceTotals(retrievalResults); + return { + schema_version: "assistant_evidence_bundle_v1", + retrieval_calls_total: Array.isArray(input.retrievalCalls) ? input.retrievalCalls.length : 0, + retrieval_results_total: retrievalResults.length, + retrieval_status_breakdown: breakdown, + ...totals + }; +} + +export function buildAssistantCoverageContractV1(input: { + coverageReport: RequirementCoverageReport; + grounding: AnswerGroundingCheck; + outcomeClass: AssistantOutcomeClassV1; +}): AssistantCoverageContractV1 { + return { + schema_version: "assistant_coverage_contract_v1", + coverage_report: input.coverageReport, + grounding: input.grounding, + outcome_class: input.outcomeClass + }; +} diff --git a/llm_normalizer/backend/src/services/assistantOrchestrationRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantOrchestrationRuntimeAdapter.ts new file mode 100644 index 0000000..72f8ec7 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantOrchestrationRuntimeAdapter.ts @@ -0,0 +1,66 @@ +import type { + AnswerGroundingCheck, + AssistantRequirement, + RequirementCoverageReport, + UnifiedRetrievalResult +} from "../types/assistant"; +import type { NormalizedPayload, RouteHintSummary } from "../types/normalizer"; + +export interface AssistantRequirementExtractionResult { + requirements: AssistantRequirement[]; + byFragment: Map; +} + +export interface AssistantCoverageEvaluationResult { + requirements: AssistantRequirement[]; + coverage: RequirementCoverageReport; +} + +export interface AssistantCoverageGroundingPipelineInput { + routeSummary: RouteHintSummary | null; + normalized: NormalizedPayload | null | undefined; + userMessage: string; + retrievalResults: UnifiedRetrievalResult[]; + requirementExtraction?: AssistantRequirementExtractionResult; + extractRequirements: ( + routeSummary: RouteHintSummary | null, + normalized: NormalizedPayload | null | undefined, + userMessage: string + ) => AssistantRequirementExtractionResult; + evaluateCoverage: ( + requirements: AssistantRequirement[], + retrievalResults: UnifiedRetrievalResult[] + ) => AssistantCoverageEvaluationResult; + checkGrounding: ( + userMessage: string, + requirements: AssistantRequirement[], + coverage: RequirementCoverageReport, + retrievalResults: UnifiedRetrievalResult[] + ) => AnswerGroundingCheck; +} + +export interface AssistantCoverageGroundingPipelineOutput { + requirementExtraction: AssistantRequirementExtractionResult; + coverageEvaluation: AssistantCoverageEvaluationResult; + groundingCheckBase: AnswerGroundingCheck; +} + +export function runAssistantCoverageGroundingPipeline( + input: AssistantCoverageGroundingPipelineInput +): AssistantCoverageGroundingPipelineOutput { + const requirementExtraction = + input.requirementExtraction ?? input.extractRequirements(input.routeSummary, input.normalized, input.userMessage); + const coverageEvaluation = input.evaluateCoverage(requirementExtraction.requirements, input.retrievalResults); + const groundingCheckBase = input.checkGrounding( + input.userMessage, + coverageEvaluation.requirements, + coverageEvaluation.coverage, + input.retrievalResults + ); + + return { + requirementExtraction, + coverageEvaluation, + groundingCheckBase + }; +} diff --git a/llm_normalizer/backend/src/services/assistantQueryPlanning.ts b/llm_normalizer/backend/src/services/assistantQueryPlanning.ts new file mode 100644 index 0000000..851948e --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantQueryPlanning.ts @@ -0,0 +1,133 @@ +import type { RouteHintSummary } from "../types/normalizer"; + +interface FragmentLike { + fragment_id?: string; + raw_fragment_text?: string; + normalized_fragment_text?: string; + account_hints?: unknown; +} + +export interface AssistantExecutionPlanItem { + fragment_id: string; + requirement_ids: string[]; + route: string; + should_execute: boolean; + fragment_text: string; + no_route_reason: string | null; + clarification_reason: string | null; +} + +function escapeRegex(value: string): string { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function enrichFragmentTextWithHints(fragment: FragmentLike, text: string): string { + const baseText = String(text ?? "").trim(); + const accountHints = Array.isArray(fragment.account_hints) + ? Array.from(new Set(fragment.account_hints.map((item) => String(item ?? "").trim()).filter((item) => item.length > 0))) + : []; + if (accountHints.length === 0) { + return baseText; + } + const hasAccountInText = accountHints.some((account) => new RegExp(`\\b${escapeRegex(account)}\\b`, "i").test(baseText)); + if (hasAccountInText) { + return baseText; + } + return `${baseText}, по счету ${accountHints.join(", ")}`; +} + +export function buildFragmentTextById(fragments: FragmentLike[]): Map { + const result = new Map(); + for (const item of fragments) { + if (!item || typeof item !== "object") { + continue; + } + const fragment = item as FragmentLike; + const fragmentId = typeof fragment.fragment_id === "string" ? fragment.fragment_id : ""; + if (!fragmentId) { + continue; + } + const text = + (typeof fragment.raw_fragment_text === "string" && fragment.raw_fragment_text.trim()) || + (typeof fragment.normalized_fragment_text === "string" && fragment.normalized_fragment_text.trim()) || + ""; + result.set(fragmentId, enrichFragmentTextWithHints(fragment, text)); + } + return result; +} + +export function buildExecutionPlanFromRoute(input: { + routeSummary: RouteHintSummary | null; + userMessage: string; + fragmentTextById: Map; + requirementByFragment: Map; +}): AssistantExecutionPlanItem[] { + if (!input.routeSummary) { + return []; + } + if (input.routeSummary.mode === "legacy_v1") { + return [ + { + fragment_id: "F1", + requirement_ids: input.requirementByFragment.get("F1") ?? ["R1"], + route: input.routeSummary.route_hint, + should_execute: true, + fragment_text: input.userMessage, + no_route_reason: null, + clarification_reason: null + } + ]; + } + return input.routeSummary.decisions.map((decision) => { + const text = input.fragmentTextById.get(decision.fragment_id) ?? input.userMessage; + if (decision.route === "no_route") { + return { + fragment_id: decision.fragment_id, + requirement_ids: input.requirementByFragment.get(decision.fragment_id) ?? [], + route: "no_route", + should_execute: false, + fragment_text: text, + no_route_reason: decision.no_route_reason ?? null, + clarification_reason: decision.clarification_reason ?? null + }; + } + return { + fragment_id: decision.fragment_id, + requirement_ids: input.requirementByFragment.get(decision.fragment_id) ?? [], + route: decision.route, + should_execute: true, + fragment_text: text, + no_route_reason: null, + clarification_reason: decision.clarification_reason ?? null + }; + }); +} + +export function buildDebugRoutesFromRoute(input: { + routeSummary: RouteHintSummary | null; + resolveLegacyRouteReason: (route: string) => string; +}): Array> { + if (!input.routeSummary) { + return []; + } + if (input.routeSummary.mode === "legacy_v1") { + return [ + { + fragment_id: "F1", + route: input.routeSummary.route_hint, + reason: input.resolveLegacyRouteReason(input.routeSummary.route_hint), + confidence: input.routeSummary.confidence, + intent_class: input.routeSummary.intent_class + } + ]; + } + return input.routeSummary.decisions.map((decision) => ({ + fragment_id: decision.fragment_id, + route: decision.route, + reason: decision.reason, + route_status: decision.route_status ?? null, + no_route_reason: decision.no_route_reason ?? null, + clarification_reason: decision.clarification_reason ?? null, + execution_readiness: decision.execution_readiness ?? null + })); +} diff --git a/llm_normalizer/backend/src/services/assistantRuntimeGuards.ts b/llm_normalizer/backend/src/services/assistantRuntimeGuards.ts index a97eeec..d7fbf2d 100644 --- a/llm_normalizer/backend/src/services/assistantRuntimeGuards.ts +++ b/llm_normalizer/backend/src/services/assistantRuntimeGuards.ts @@ -683,11 +683,97 @@ function toTemporalGuardInput(window: TemporalWindow | null, fallback: string | return value || null; } +function normalizeIsoDate(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!match) { + return null; + } + const year = Number(match[1]); + const month = Number(match[2]); + const day = Number(match[3]); + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + return null; + } + const candidate = new Date(Date.UTC(year, month - 1, day)); + if ( + candidate.getUTCFullYear() !== year || + candidate.getUTCMonth() + 1 !== month || + candidate.getUTCDate() !== day + ) { + return null; + } + return `${match[1]}-${match[2]}-${match[3]}`; +} + +function normalizeTemporalWindow(input: { + asOfDate?: unknown; + periodFrom?: unknown; + periodTo?: unknown; +}): TemporalWindow | null { + const asOfDate = normalizeIsoDate(input.asOfDate); + if (asOfDate) { + return { + from: asOfDate, + to: asOfDate, + granularity: "day" + }; + } + const from = normalizeIsoDate(input.periodFrom); + const to = normalizeIsoDate(input.periodTo); + if (!from || !to) { + return null; + } + return { + from, + to, + granularity: from === to ? "day" : "month" + }; +} + export function resolveTemporalGuard(input: { userMessage: string; normalized: NormalizedPayload | null | undefined; companyAnchors?: CompanyAnchorSet | null; + analysisContext?: { + as_of_date?: string | null; + period_from?: string | null; + period_to?: string | null; + source?: string | null; + } | null; }): TemporalGuardAudit { + const analysisWindow = normalizeTemporalWindow({ + asOfDate: input.analysisContext?.as_of_date, + periodFrom: input.analysisContext?.period_from, + periodTo: input.analysisContext?.period_to + }); + if (analysisWindow) { + const source = String(input.analysisContext?.source ?? "").trim() || "analysis_context"; + const guardInput = toTemporalGuardInput(analysisWindow, analysisWindow.from); + return { + raw_time_anchor: analysisWindow.from, + raw_time_scope: guardInput, + resolved_time_anchor: analysisWindow.granularity === "day" ? analysisWindow.from : null, + resolved_primary_period: analysisWindow, + effective_primary_period: analysisWindow, + temporal_guard_input: guardInput, + temporal_alignment_status: "aligned", + temporal_resolution_source: source, + temporal_guard_basis: "raw_time_scope_unlocked", + temporal_guard_applied: false, + temporal_guard_outcome: "passed", + primary_period_window: null, + allowed_context_window: null, + controlled_temporal_expansion_enabled: false, + context_expansion_reasons_allowed: ["prehistory", "carryover", "post_period_closure", "long_running_contract_context"], + normalized_anchor_drift_detected: false, + reason_codes: ["analysis_context_applied"] + }; + } + const rawAnchorText = collectRawTemporalAnchorText(input.userMessage, input.companyAnchors); const julyAnchor = resolveJulyAnchor(rawAnchorText); const normalizedAnchor = normalizedAnchorFromFragments(input.normalized); @@ -762,10 +848,15 @@ export function applyTemporalHintToExecutionPlan< return executionPlan; } const primaryWindow = temporal.effective_primary_period ?? temporal.primary_period_window; + const periodLabel = primaryWindow + ? `${primaryWindow.from}..${primaryWindow.to}` + : temporal.resolved_time_anchor + ? temporal.resolved_time_anchor + : "active_period"; const hint = primaryWindow?.granularity === "day" && temporal.resolved_time_anchor ? `primary period ${temporal.resolved_time_anchor}; controlled temporal expansion only for linked entities` - : `primary period July 2020 (${primaryWindow?.from ?? JULY_WINDOW.from}..${primaryWindow?.to ?? JULY_WINDOW.to}); controlled temporal expansion only for linked entities`; + : `primary period ${periodLabel}; controlled temporal expansion only for linked entities`; return executionPlan.map((item) => { if (!item.should_execute) { return item; @@ -1590,15 +1681,15 @@ export function applyEligibilityToGroundingCheck = { - admissible_evidence_count_zero: "Недостаточно допустимого evidence для обоснованного ответа.", - critical_domain_or_account_contradiction: "Есть критическое противоречие по domain/account scope.", - temporal_guard_failed_out_of_snapshot_window: "Temporal anchor вышел за окно company snapshot (июль 2020).", - temporal_guard_ambiguous_limited: "Temporal anchor не разрешен надежно в пределах company snapshot.", - business_scope_generic_unresolved: "Business scope остался generic и не подтвержден как company-specific для доказательного ответа.", - polarity_guard_limited_unresolved_polarity: "Не удалось надежно определить supplier/customer polarity.", - polarity_guard_blocked_conflict: "Обнаружен конфликт supplier/customer polarity в retrieval-контуре.", - claim_anchor_coverage_insufficient: "Недостаточно покрытия required anchors для claim-bound grounding.", - targeted_evidence_hit_rate_zero: "Targeted evidence acquisition не дал допустимых попаданий по claim target path." + admissible_evidence_count_zero: "Недостаточно подтвержденных данных для уверенного ответа.", + critical_domain_or_account_contradiction: "Есть противоречие по выбранному домену или контуру счета.", + temporal_guard_failed_out_of_snapshot_window: "Запрошенный период выходит за доступный срез данных.", + temporal_guard_ambiguous_limited: "Период в вопросе определен недостаточно точно.", + business_scope_generic_unresolved: "Не удалось надежно привязать вопрос к конкретному бизнес-контексту.", + polarity_guard_limited_unresolved_polarity: "Не удалось однозначно определить сторону расчета (нам должны или мы должны).", + polarity_guard_blocked_conflict: "В данных есть конфликт по стороне расчета.", + claim_anchor_coverage_insufficient: "Не хватает ключевых ориентиров в вопросе (период, объект или контрагент).", + targeted_evidence_hit_rate_zero: "Не хватило целевых подтверждений по выбранному сценарию." }; const reasons = [ ...(Array.isArray(groundingCheck.reasons) ? groundingCheck.reasons : []), diff --git a/llm_normalizer/backend/src/services/assistantService.ts b/llm_normalizer/backend/src/services/assistantService.ts index 3d67eab..8ad08a4 100644 --- a/llm_normalizer/backend/src/services/assistantService.ts +++ b/llm_normalizer/backend/src/services/assistantService.ts @@ -1,6 +1,5 @@ // @ts-nocheck import * as nanoid_1 from "nanoid"; -import * as stage1Contracts_1 from "../types/stage1Contracts"; import * as config_1 from "../config"; import * as log_1 from "../utils/log"; import * as answerComposer_1 from "./answerComposer"; @@ -8,7 +7,6 @@ import * as assistantDataLayer_1 from "./assistantDataLayer"; import * as assistantSessionLogger_1 from "./assistantSessionLogger"; import * as investigationState_1 from "./investigationState"; import * as retrievalResultNormalizer_1 from "./retrievalResultNormalizer"; -import * as questionTypeResolver_1 from "./questionTypeResolver"; import * as companyAnchorResolver_1 from "./companyAnchorResolver"; import * as assistantRuntimeGuards_1 from "./assistantRuntimeGuards"; import * as assistantClaimBoundEvidence_1 from "./assistantClaimBoundEvidence"; @@ -21,6 +19,17 @@ import * as openaiResponsesClient_1 from "./openaiResponsesClient"; import * as addressMcpClient_1 from "./addressMcpClient"; import * as capabilitiesRegistry_1 from "./capabilitiesRegistry"; import * as assistantCanon_1 from "./assistantCanon"; +import * as assistantCoverageGrounding_1 from "./assistantCoverageGrounding"; +import * as assistantDeepTurnResponseBuilder_1 from "./assistantDeepTurnResponseBuilder"; +import * as assistantDeepTurnCompositionRuntimeAdapter_1 from "./assistantDeepTurnCompositionRuntimeAdapter"; +import * as assistantDeepTurnContextRuntimeAdapter_1 from "./assistantDeepTurnContextRuntimeAdapter"; +import * as assistantDeepTurnGuardRuntimeAdapter_1 from "./assistantDeepTurnGuardRuntimeAdapter"; +import * as assistantDeepTurnGroundingRuntimeAdapter_1 from "./assistantDeepTurnGroundingRuntimeAdapter"; +import * as assistantDeepTurnPackagingRuntimeAdapter_1 from "./assistantDeepTurnPackagingRuntimeAdapter"; +import * as assistantDeepTurnPlanRuntimeAdapter_1 from "./assistantDeepTurnPlanRuntimeAdapter"; +import * as assistantDeepTurnRetrievalRuntimeAdapter_1 from "./assistantDeepTurnRetrievalRuntimeAdapter"; +import * as assistantQueryPlanning_1 from "./assistantQueryPlanning"; +import * as assistantTurnCommitRuntimeAdapter_1 from "./assistantTurnCommitRuntimeAdapter"; import iconv from "iconv-lite"; const DATA_SCOPE_CACHE_TTL_MS = 60_000; const dataScopeProbeCache = new Map(); @@ -79,6 +88,46 @@ function hasExplicitPeriodAnchorFromNormalized(normalized) { } return false; } +function normalizeIsoDate(value) { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!match) { + return null; + } + const year = Number(match[1]); + const month = Number(match[2]); + const day = Number(match[3]); + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + return null; + } + const candidate = new Date(Date.UTC(year, month - 1, day)); + if (candidate.getUTCFullYear() !== year || candidate.getUTCMonth() + 1 !== month || candidate.getUTCDate() !== day) { + return null; + } + return `${match[1]}-${match[2]}-${match[3]}`; +} +function resolveRuntimeAnalysisContext(context) { + const input = context && typeof context === "object" ? context : {}; + const analysis = input.analysis_context && typeof input.analysis_context === "object" ? input.analysis_context : {}; + const asOfDate = normalizeIsoDate(analysis.as_of_date) ?? normalizeIsoDate(input.period_hint); + const periodFrom = normalizeIsoDate(analysis.period_from); + const periodTo = normalizeIsoDate(analysis.period_to); + const sourceRaw = typeof analysis.source === "string" ? analysis.source.trim() : ""; + const source = sourceRaw || (asOfDate ? "period_hint_compat" : null); + const snapshotModeRaw = typeof analysis.snapshot_mode === "string" ? analysis.snapshot_mode.trim() : ""; + const snapshotMode = snapshotModeRaw === "force_snapshot" || snapshotModeRaw === "force_live" ? snapshotModeRaw : "auto"; + return { + as_of_date: asOfDate, + period_from: periodFrom, + period_to: periodTo, + source, + snapshot_mode: snapshotMode, + active: Boolean(asOfDate || (periodFrom && periodTo)) + }; +} function extractExecutionState(normalized) { const fragments = extractFragments(normalized); return fragments.map((item) => { @@ -317,22 +366,7 @@ function enrichFragmentTextWithHints(fragment, text) { return `${baseText}, по счету ${accountHints.join(", ")}`; } function fragmentTextById(normalized) { - const result = new Map(); - for (const item of extractFragments(normalized)) { - if (!item || typeof item !== "object") { - continue; - } - const fragment = item; - const fragmentId = typeof fragment.fragment_id === "string" ? fragment.fragment_id : ""; - if (!fragmentId) { - continue; - } - const text = (typeof fragment.raw_fragment_text === "string" && fragment.raw_fragment_text.trim()) || - (typeof fragment.normalized_fragment_text === "string" && fragment.normalized_fragment_text.trim()) || - ""; - result.set(fragmentId, enrichFragmentTextWithHints(fragment, text)); - } - return result; + return (0, assistantQueryPlanning_1.buildFragmentTextById)(extractFragments(normalized)); } function extractDiscardedIntentSegments(normalized) { if (!normalized || typeof normalized !== "object") { @@ -544,109 +578,20 @@ function extractSubjectTokens(text) { } function extractRequirements(routeSummary, normalized, userMessage) { const fragmentText = fragmentTextById(normalized); - const byFragment = new Map(); - const requirements = []; - const pushRequirement = (input) => { - const subjectTokens = extractSubjectTokens(input.requirement_text); - requirements.push({ - requirement_id: input.requirement_id, - source_fragment_id: input.source_fragment_id, - requirement_text: input.requirement_text, - subject_tokens: subjectTokens, - status: input.status, - route: input.route - }); - if (input.source_fragment_id) { - const current = byFragment.get(input.source_fragment_id) ?? []; - current.push(input.requirement_id); - byFragment.set(input.source_fragment_id, current); - } - }; - if (!routeSummary) { - pushRequirement({ - requirement_id: "R1", - source_fragment_id: null, - requirement_text: userMessage, - status: "clarification_needed", - route: null - }); - return { requirements, byFragment }; - } - if (routeSummary.mode === "legacy_v1") { - pushRequirement({ - requirement_id: "R1", - source_fragment_id: "F1", - requirement_text: userMessage, - status: "covered", - route: routeSummary.route_hint - }); - return { requirements, byFragment }; - } - routeSummary.decisions.forEach((decision, index) => { - const requirementId = `R${index + 1}`; - const text = fragmentText.get(decision.fragment_id) ?? userMessage; - let status = "covered"; - if (decision.route === "no_route") { - if (decision.no_route_reason === "out_of_scope") { - status = "out_of_scope"; - } - else if (decision.no_route_reason === "insufficient_specificity") { - status = "clarification_needed"; - } - else { - status = "uncovered"; - } - } - pushRequirement({ - requirement_id: requirementId, - source_fragment_id: decision.fragment_id, - requirement_text: text, - status, - route: decision.route === "no_route" ? null : decision.route - }); + return (0, assistantCoverageGrounding_1.extractRequirementsForRoute)({ + routeSummary, + userMessage, + fragmentTextById: fragmentText, + extractSubjectTokens }); - return { requirements, byFragment }; } function toExecutionPlan(routeSummary, normalized, userMessage, requirementByFragment) { - if (!routeSummary) { - return []; - } const fragmentText = fragmentTextById(normalized); - if (routeSummary.mode === "legacy_v1") { - return [ - { - fragment_id: "F1", - requirement_ids: requirementByFragment.get("F1") ?? ["R1"], - route: routeSummary.route_hint, - should_execute: true, - fragment_text: userMessage, - no_route_reason: null, - clarification_reason: null - } - ]; - } - return routeSummary.decisions.map((decision) => { - const text = fragmentText.get(decision.fragment_id) ?? userMessage; - if (decision.route === "no_route") { - return { - fragment_id: decision.fragment_id, - requirement_ids: requirementByFragment.get(decision.fragment_id) ?? [], - route: "no_route", - should_execute: false, - fragment_text: text, - no_route_reason: decision.no_route_reason ?? null, - clarification_reason: decision.clarification_reason ?? null - }; - } - return { - fragment_id: decision.fragment_id, - requirement_ids: requirementByFragment.get(decision.fragment_id) ?? [], - route: decision.route, - should_execute: true, - fragment_text: text, - no_route_reason: null, - clarification_reason: decision.clarification_reason ?? null - }; + return (0, assistantQueryPlanning_1.buildExecutionPlanFromRoute)({ + routeSummary, + userMessage, + fragmentTextById: fragmentText, + requirementByFragment }); } function enrichRbpFragmentForLive(fragmentText, temporalGuard) { @@ -961,29 +906,10 @@ function collectFaLiveRouteAudit(input) { }; } function toDebugRoutes(routeSummary) { - if (!routeSummary) { - return []; - } - if (routeSummary.mode === "legacy_v1") { - return [ - { - fragment_id: "F1", - route: routeSummary.route_hint, - reason: retrievalSummaryForRoute(routeSummary.route_hint), - confidence: routeSummary.confidence, - intent_class: routeSummary.intent_class - } - ]; - } - return routeSummary.decisions.map((decision) => ({ - fragment_id: decision.fragment_id, - route: decision.route, - reason: decision.reason, - route_status: decision.route_status ?? null, - no_route_reason: decision.no_route_reason ?? null, - clarification_reason: decision.clarification_reason ?? null, - execution_readiness: decision.execution_readiness ?? null - })); + return (0, assistantQueryPlanning_1.buildDebugRoutesFromRoute)({ + routeSummary, + resolveLegacyRouteReason: retrievalSummaryForRoute + }); } function buildSkippedResult(item) { return (0, retrievalResultNormalizer_1.normalizeRetrievalResult)(item.fragment_id, item.requirement_ids, "no_route", { @@ -1006,374 +932,23 @@ function buildSkippedResult(item) { errors: [] }); } -function summarizeUnique(values, limit = 6) { - return Array.from(new Set(values.map((item) => item.trim()).filter(Boolean))).slice(0, limit); -} -const SUBJECT_TOKEN_RULES = { - nds: { - critical: true, - patterns: [ - "vat", - "accumulationregister", - "\u043d\u0434\u0441", - "\u043a\u043d\u0438\u0433\u0438\u043f\u043e\u043a\u0443\u043f\u043e\u043a", - "\u043a\u043d\u0438\u0433\u0438\u043f\u0440\u043e\u0434\u0430\u0436", - "\u043d\u0430\u043b\u043e\u0433\u043d\u0430\u0434\u043e\u0431\u0430\u0432\u043b\u0435\u043d\u043d\u0443\u044e\u0441\u0442\u043e\u0438\u043c\u043e\u0441\u0442\u044c" - ] - }, - os: { - critical: true, - patterns: ["fixed_asset", "fixedasset", "\u043e\u0441\u043d\u043e\u0432\u043d", "\u0430\u043c\u043e\u0440\u0442\u0438\u0437"] - }, - saldo: { - critical: true, - patterns: ["balance", "saldo", "\u0441\u0430\u043b\u044c\u0434\u043e", "\u043e\u0441\u0442\u0430\u0442"] - }, - counterparty: { - critical: false, - patterns: [ - "counterparty", - "supplier", - "buyer", - "counterparty_id", - "journal_counterparty", - "document_has_counterparty", - "\u043a\u043e\u043d\u0442\u0440\u0430\u0433\u0435\u043d\u0442", - "\u043f\u043e\u0441\u0442\u0430\u0432\u0449\u0438\u043a", - "\u043f\u043e\u043a\u0443\u043f\u0430\u0442\u0435\u043b" - ], - routes: ["hybrid_store_plus_live", "store_feature_risk", "store_canonical"] - }, - document: { - critical: false, - patterns: [ - "document", - "recorder", - "journal", - "document_refs_count", - "recorded_by_document", - "journal_refers_to_document", - "\u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442" - ], - routes: ["hybrid_store_plus_live", "store_feature_risk", "store_canonical", "live_mcp_drilldown"] - }, - anomaly: { - critical: false, - patterns: [ - "risk", - "risk_score", - "unknown_link_count", - "zero_guid", - "navigation_links", - "missing_counterparty_link", - "\u0430\u043d\u043e\u043c\u0430\u043b", - "\u0440\u0438\u0441\u043a" - ], - routes: ["store_feature_risk", "batch_refresh_then_store"] - }, - chain: { - critical: false, - patterns: ["chain", "cross_entity_chain", "relation_types", "operations_count", "matched_counterparties", "\u0446\u0435\u043f\u043e\u0447"], - routes: ["hybrid_store_plus_live"] - } -}; -function hasRegexMatch(corpus, pattern) { - try { - return pattern.test(corpus); - } - catch { - return false; - } -} -function evaluateSubjectTokenMatch(token, corpus, executedRoutes) { - if (token.startsWith("account_")) { - const account = token.slice("account_".length).trim(); - if (!account) { - return { matched: false, critical: true }; - } - const escaped = account.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); - const accountPattern = new RegExp(`(^|[^0-9])${escaped}([^0-9]|$)`, "i"); - return { matched: hasRegexMatch(corpus, accountPattern), critical: true }; - } - const rule = SUBJECT_TOKEN_RULES[token]; - if (rule) { - const byPattern = rule.patterns.some((pattern) => corpus.includes(pattern)); - const byRoute = Array.isArray(rule.routes) ? rule.routes.some((route) => executedRoutes.has(route)) : false; - return { matched: byPattern || byRoute, critical: rule.critical }; - } - return { matched: corpus.includes(token), critical: false }; -} -function evidenceCountForRequirement(requirementId, result) { - const evidence = Array.isArray(result.evidence) ? result.evidence : []; - if (evidence.length === 0) { - return 0; - } - const tagged = evidence.filter((item) => { - const claimRef = typeof item?.claim_ref === "string" ? item.claim_ref : ""; - return claimRef.toLowerCase() === `requirement:${String(requirementId).toLowerCase()}`; - }).length; - if (tagged > 0) { - return tagged; - } - if (Array.isArray(result.requirement_ids) && - result.requirement_ids.length === 1 && - result.requirement_ids[0] === requirementId) { - return evidence.length; - } - return 0; -} -function hasSubstantiveCoverageForRequirement(requirementId, result) { - const evidenceCount = evidenceCountForRequirement(requirementId, result); - if (evidenceCount > 0) { - return true; - } - const problemUnitsCount = Array.isArray(result.problem_units) ? result.problem_units.length : 0; - const candidateEvidenceCount = Array.isArray(result.candidate_evidence) ? result.candidate_evidence.length : 0; - if (problemUnitsCount > 0 || candidateEvidenceCount > 0) { - if (Array.isArray(result.requirement_ids) && - result.requirement_ids.length === 1 && - result.requirement_ids[0] === requirementId) { - return true; - } - } - return false; -} function evaluateCoverage(requirements, retrievalResults) { - const statusByRequirement = new Map(); - for (const result of retrievalResults) { - for (const requirementId of result.requirement_ids) { - const list = statusByRequirement.get(requirementId) ?? []; - list.push({ - status: result.status, - substantive: hasSubstantiveCoverageForRequirement(requirementId, result) - }); - statusByRequirement.set(requirementId, list); - } - } - const resolvedRequirements = requirements.map((requirement) => { - if (requirement.status === "out_of_scope" || requirement.status === "clarification_needed") { - return requirement; - } - const states = statusByRequirement.get(requirement.requirement_id) ?? []; - if (states.length === 0) { - return { ...requirement, status: "uncovered" }; - } - const hasAnySubstantive = states.some((item) => item.substantive); - if (!hasAnySubstantive) { - return { ...requirement, status: "uncovered" }; - } - const hasOk = states.some((item) => item.status === "ok"); - const hasPartial = states.some((item) => item.status === "partial"); - const hasEmpty = states.some((item) => item.status === "empty"); - const hasError = states.some((item) => item.status === "error"); - const hasWeakOk = states.some((item) => item.status === "ok" && !item.substantive); - const hasSubstantiveOk = states.some((item) => item.status === "ok" && item.substantive); - const hasSubstantivePartial = states.some((item) => item.status === "partial" && item.substantive); - if (hasSubstantiveOk && !hasSubstantivePartial && !hasWeakOk && !hasEmpty && !hasError) { - return { ...requirement, status: "covered" }; - } - if (hasSubstantiveOk || hasSubstantivePartial || hasOk || hasPartial) { - return { ...requirement, status: "partially_covered" }; - } - return { ...requirement, status: "uncovered" }; - }); - const requirementsCovered = resolvedRequirements.filter((item) => item.status === "covered").length; - const requirementsUncovered = resolvedRequirements - .filter((item) => item.status === "uncovered") - .map((item) => item.requirement_id); - const requirementsPartiallyCovered = resolvedRequirements - .filter((item) => item.status === "partially_covered") - .map((item) => item.requirement_id); - const clarificationNeededFor = resolvedRequirements - .filter((item) => item.status === "clarification_needed") - .map((item) => item.requirement_id); - const outOfScopeRequirements = resolvedRequirements - .filter((item) => item.status === "out_of_scope") - .map((item) => item.requirement_id); - return { - requirements: resolvedRequirements, - coverage: { - requirements_total: resolvedRequirements.length, - requirements_covered: requirementsCovered, - requirements_uncovered: requirementsUncovered, - requirements_partially_covered: requirementsPartiallyCovered, - clarification_needed_for: clarificationNeededFor, - out_of_scope_requirements: outOfScopeRequirements - } - }; + return (0, assistantCoverageGrounding_1.evaluateCoverageForRequirements)(requirements, retrievalResults); } export function evaluateCoverageForTests(requirements, retrievalResults) { - return evaluateCoverage(requirements, retrievalResults); + return (0, assistantCoverageGrounding_1.evaluateCoverageForRequirements)(requirements, retrievalResults); } export function extractSubjectTokensForTests(text) { return extractSubjectTokens(text); } function checkGrounding(userMessage, requirements, coverage, retrievalResults) { - const whyIncludedSummary = summarizeUnique(retrievalResults.flatMap((item) => item.why_included)); - const selectionReasonSummary = summarizeUnique(retrievalResults.flatMap((item) => item.selection_reason)); - const hasMaterialResults = retrievalResults.some((item) => item.status === "ok" || item.status === "partial"); - const subjectTokens = extractSubjectTokens(userMessage); - const executedRoutes = new Set(retrievalResults - .filter((item) => item.status !== "error") - .map((item) => item.route) - .filter(Boolean)); - const retrievalCorpus = JSON.stringify(retrievalResults.map((item) => ({ - route: item.route, - result_type: item.result_type, - summary: item.summary, - items: item.items, - evidence: item.evidence, - why_included: item.why_included, - selection_reason: item.selection_reason, - risk_factors: item.risk_factors, - business_interpretation: item.business_interpretation - }))).toLowerCase(); - const missingSubjectTokens = []; - const missingCriticalTokens = []; - for (const token of subjectTokens) { - const match = evaluateSubjectTokenMatch(token, retrievalCorpus, executedRoutes); - if (!match.matched) { - missingSubjectTokens.push(token); - if (match.critical) { - missingCriticalTokens.push(token); - } - } - } - const onlyAccountCriticalMissing = missingCriticalTokens.length > 0 && missingCriticalTokens.every((token) => token.startsWith("account_")); - const accountOnlyMismatchRecoverable = hasMaterialResults && - coverage.requirements_covered > 0 && - onlyAccountCriticalMissing && - (whyIncludedSummary.length > 0 || selectionReasonSummary.length > 0); - const routeSubjectMatch = !hasMaterialResults || missingCriticalTokens.length === 0 || accountOnlyMismatchRecoverable; - let status = "grounded"; - const reasons = []; - if (!routeSubjectMatch) { - status = "route_mismatch_blocked"; - reasons.push(`Не подтверждены критичные предметные токены запроса: ${missingCriticalTokens.join(", ")}`); - } - else if (accountOnlyMismatchRecoverable) { - status = "partial"; - reasons.push(`Счет-токены не подтверждены напрямую (${missingCriticalTokens.join(", ")}), но есть релевантная опора для ограниченного вывода.`); - } - else if (coverage.requirements_covered === 0) { - status = "no_grounded_answer"; - reasons.push("Ни одно требование не получило подтвержденного покрытия."); - } - else if (coverage.requirements_uncovered.length > 0 || - coverage.requirements_partially_covered.length > 0 || - coverage.clarification_needed_for.length > 0 || - coverage.out_of_scope_requirements.length > 0) { - status = "partial"; - reasons.push("Вопрос покрыт частично: есть непокрытые или требующие уточнения требования."); - } - if (whyIncludedSummary.length === 0) { - reasons.push("Нет explainable-сигналов why_included в результатах выборки."); - } - if (missingSubjectTokens.length > 0 && missingCriticalTokens.length === 0) { - reasons.push(`Часть контекстных токенов не подтверждена напрямую: ${missingSubjectTokens.join(", ")}`); - } - const missingRequirements = [ - ...coverage.requirements_uncovered, - ...coverage.requirements_partially_covered, - ...coverage.clarification_needed_for, - ...coverage.out_of_scope_requirements - ]; - return { - status, - route_subject_match: routeSubjectMatch, - missing_requirements: missingRequirements, - reasons, - why_included_summary: whyIncludedSummary, - selection_reason_summary: selectionReasonSummary - }; -} -function firstNonEmptyLine(text) { - const line = text - .split("\n") - .map((item) => item.trim()) - .find((item) => item.length > 0); - return (line ?? text).slice(0, 220); -} -function buildClaimEvidenceLinks(retrievalResults) { - const byClaim = new Map(); - for (const result of retrievalResults) { - for (const evidence of result.evidence) { - const claimRef = String(evidence.claim_ref ?? "").trim(); - if (!claimRef) { - continue; - } - const evidenceId = String(evidence.evidence_id ?? "").trim(); - if (!evidenceId) { - continue; - } - const current = byClaim.get(claimRef) ?? []; - current.push(evidenceId); - byClaim.set(claimRef, current); - } - } - return Array.from(byClaim.entries()) - .slice(0, 10) - .map(([claimRef, evidenceIds]) => ({ - claim_ref: claimRef, - evidence_ids: summarizeUnique(evidenceIds, 10) - })); -} -function buildAnswerStructureV11(input) { - const evidenceIds = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence.map((evidence) => evidence.evidence_id)), 10); - const mechanismNotes = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence - .map((evidence) => evidence.mechanism_note) - .filter((note) => typeof note === "string" && note.trim().length > 0)), 6); - const sourceRefs = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence - .map((evidence) => evidence.source_ref?.canonical_ref) - .filter((value) => typeof value === "string" && value.trim().length > 0)), 8); - const limitationReasonCodes = summarizeUnique(input.retrievalResults.flatMap((item) => item.evidence - .flatMap((evidence) => { - const code = evidence.limitation?.reason_code; - return typeof code === "string" && code.trim().length > 0 ? [code] : []; - })), 8); - const claimEvidenceLinks = buildClaimEvidenceLinks(input.retrievalResults); - const limitations = summarizeUnique([...input.retrievalResults.flatMap((item) => item.limitations), ...input.groundingCheck.reasons], 8); - const clarificationQuestions = input.coverageReport.clarification_needed_for.map((item) => `Уточните требование ${item}.`); - const recommendedActions = summarizeUnique([ - ...input.coverageReport.requirements_uncovered.map((item) => `Проверить непокрытое требование ${item}.`), - ...input.coverageReport.requirements_partially_covered.map((item) => `Доуточнить частично покрытое требование ${item}.`) - ], 6); - const mechanismStatus = mechanismNotes.length === 0 - ? "unresolved" - : limitationReasonCodes.includes("missing_mechanism") || limitationReasonCodes.includes("heuristic_inference") - ? "limited" - : "grounded"; - return { - schema_version: stage1Contracts_1.ANSWER_STRUCTURE_SCHEMA_VERSION, - answer_summary: firstNonEmptyLine(input.assistantReply), - direct_answer: input.assistantReply, - mechanism_block: { - status: mechanismStatus, - mechanism_notes: mechanismNotes, - limitation_reason_codes: limitationReasonCodes - }, - evidence_block: { - evidence_ids: evidenceIds, - source_refs: sourceRefs, - mechanism_notes: mechanismNotes, - coverage_note: input.coverageReport.requirements_total === input.coverageReport.requirements_covered - ? "coverage_full_or_near_full" - : "coverage_partial_or_limited", - ...(config_1.FEATURE_ASSISTANT_EVIDENCE_ENRICHMENT_V1 && claimEvidenceLinks.length > 0 - ? { - claim_evidence_links: claimEvidenceLinks - } - : {}) - }, - uncertainty_block: { - open_uncertainties: input.groundingCheck.missing_requirements, - limitations - }, - next_step_block: { - recommended_actions: recommendedActions, - clarification_questions: clarificationQuestions - } - }; + return (0, assistantCoverageGrounding_1.checkGroundingForRequirements)({ + userMessage, + requirements, + coverage, + retrievalResults, + extractSubjectTokens + }); } const FOLLOWUP_ROUTE_HINTS = new Set(["store_canonical", "store_feature_risk", "hybrid_store_plus_live", "live_mcp_drilldown", "batch_refresh_then_store"]); const FOLLOWUP_ACTIVE_DOMAIN_ROUTE_MAP = { @@ -1611,6 +1186,14 @@ function buildFollowupStateBinding(input) { } if (periodHintFromState) { context.period_hint = periodHintFromState; + const existingAnalysisContext = context.analysis_context && typeof context.analysis_context === "object" ? context.analysis_context : {}; + if (!existingAnalysisContext.as_of_date) { + context.analysis_context = { + ...existingAnalysisContext, + as_of_date: periodHintFromState, + source: existingAnalysisContext.source ?? "followup_state_period_hint" + }; + } } const subject = withCappedLength(compactWhitespace(input.investigationState.focus.active_query_subject ?? ""), FOLLOWUP_SUBJECT_MAX); const businessContextPatch = ["followup_state_binding_v1"]; @@ -3555,7 +3138,7 @@ export function resolveAssistantOrchestrationDecision(input) { const baseToolGate = resolveAddressToolGateDecision(effectiveAddressUserMessage, followupContext, llmPreDecomposeMeta, rawUserMessage); const unsupportedAddressIntentFallbackToDeep = Boolean(!followupContext && baseToolGate?.runAddressLane && - modeDetection.mode === "address_query" && + modeDetection.mode !== "address_query" && intentResolution.intent === "unknown" && strongDataSignal); let runAddressLane = Boolean(baseToolGate?.runAddressLane); @@ -4778,6 +4361,7 @@ export class AssistantService { const userMessageRaw = String(payload.user_message ?? payload.message ?? "").trim(); const repairedUserMessage = compactWhitespace(repairAddressMojibake(userMessageRaw)); const userMessage = repairedUserMessage || userMessageRaw; + const runtimeAnalysisContext = resolveRuntimeAnalysisContext(payload?.context); const userItem = { message_id: `msg-${(0, nanoid_1.nanoid)(10)}`, session_id: sessionId, @@ -5185,6 +4769,7 @@ export class AssistantService { } if (orchestrationDecision.runAddressLane) { const shouldPreferContextualLane = Boolean(carryover?.followupContext); + const analysisDateHint = runtimeAnalysisContext.as_of_date ?? toNonEmptyString(payload?.context?.period_hint); const canRetryWithRawUserMessage = compactWhitespace(String(addressInputMessage ?? "").toLowerCase()) !== compactWhitespace(String(userMessage ?? "").toLowerCase()); const retryAudit = { @@ -5227,10 +4812,13 @@ export class AssistantService { const scopedFollowupContext = mergeFollowupContextWithOrganizationScope(carryMeta?.followupContext ?? null, sessionOrganizationScope.activeOrganization); if (scopedFollowupContext) { return this.addressQueryService.tryHandle(messageUsed, { - followupContext: scopedFollowupContext + followupContext: scopedFollowupContext, + analysisDateHint }); } - return this.addressQueryService.tryHandle(messageUsed); + return this.addressQueryService.tryHandle(messageUsed, { + analysisDateHint + }); }; if (shouldPreferContextualLane) { const contextualAddressLane = await runAddressLaneAttempt(addressInputMessage, carryover); @@ -5307,403 +4895,184 @@ export class AssistantService { useMock: Boolean(payload.useMock) }; const normalized = await this.normalizerService.normalize(normalizePayload); - const companyAnchors = (0, companyAnchorResolver_1.resolveCompanyAnchors)(userMessage); - const initialBusinessScopeResolution = resolveBusinessScopeAlignment({ + const contextRuntime = (0, assistantDeepTurnContextRuntimeAdapter_1.buildAssistantDeepTurnRuntimeContext)({ userMessage, - companyAnchors, - normalized: normalized.normalized, - routeSummary: normalized.route_hint_summary + normalizedPayload: normalized.normalized, + routeSummary: normalized.route_hint_summary, + runtimeAnalysisContext, + followupUsage: followupBinding.usage, + resolveCompanyAnchors: companyAnchorResolver_1.resolveCompanyAnchors, + resolveBusinessScopeAlignment, + inferP0DomainFromMessage, + resolveTemporalGuard: assistantRuntimeGuards_1.resolveTemporalGuard, + resolveDomainPolarityGuard: assistantRuntimeGuards_1.resolveDomainPolarityGuard, + resolveClaimBoundAnchors: assistantClaimBoundEvidence_1.resolveClaimBoundAnchors, + resolveBusinessScopeFromLiveContext }); - const inferredDomainByMessage = inferP0DomainFromMessage(userMessage); - const focusDomainForGuards = inferredDomainByMessage === "settlements_60_62" || - inferredDomainByMessage === "vat_document_register_book" || - inferredDomainByMessage === "month_close_costs_20_44" || - inferredDomainByMessage === "fixed_asset_amortization" - ? inferredDomainByMessage - : null; - const temporalGuard = (0, assistantRuntimeGuards_1.resolveTemporalGuard)({ + const companyAnchors = contextRuntime.companyAnchors; + const focusDomainForGuards = contextRuntime.focusDomainForGuards; + const temporalGuard = contextRuntime.temporalGuard; + const domainPolarityGuardInitial = contextRuntime.domainPolarityGuardInitial; + const claimAnchorAudit = contextRuntime.claimAnchorAudit; + const businessScopeResolution = contextRuntime.businessScopeResolution; + const resolvedRouteSummary = contextRuntime.resolvedRouteSummary; + const liveTemporalHint = contextRuntime.liveTemporalHint; + const executionPlanRuntime = (0, assistantDeepTurnPlanRuntimeAdapter_1.buildAssistantDeepTurnExecutionPlan)({ + routeSummary: resolvedRouteSummary, + normalizedPayload: normalized.normalized, userMessage, - normalized: normalized.normalized, - companyAnchors - }); - const domainPolarityGuardInitial = (0, assistantRuntimeGuards_1.resolveDomainPolarityGuard)({ - userMessage, - companyAnchors, - focusDomainHint: focusDomainForGuards - }); - const claimAnchorAudit = (0, assistantClaimBoundEvidence_1.resolveClaimBoundAnchors)({ - userMessage, - companyAnchors, - focusDomainHint: focusDomainForGuards, - primaryPeriod: temporalGuard.effective_primary_period ?? temporalGuard.primary_period_window - }); - const businessScopeResolution = resolveBusinessScopeFromLiveContext({ - current: initialBusinessScopeResolution, + claimType: claimAnchorAudit.claim_type, temporalGuard, - claimType: claimAnchorAudit.claim_type, - focusDomainHint: focusDomainForGuards, - userMessage, - companyAnchors, - followupApplied: Boolean(followupBinding.usage?.applied) + domainPolarityGuardInitial, + extractRequirements, + toExecutionPlan, + enforceRbpLiveRoutePlan, + enforceFaLiveRoutePlan, + applyTemporalHintToExecutionPlan: assistantRuntimeGuards_1.applyTemporalHintToExecutionPlan, + applyPolarityHintToExecutionPlan: assistantRuntimeGuards_1.applyPolarityHintToExecutionPlan }); - const resolvedRouteSummary = businessScopeResolution.route_summary_resolved; - const requirementExtraction = extractRequirements(resolvedRouteSummary, normalized.normalized, userMessage); - let executionPlan = toExecutionPlan(resolvedRouteSummary, normalized.normalized, userMessage, requirementExtraction.byFragment); - const rbpRoutePlanEnforcement = enforceRbpLiveRoutePlan({ + const requirementExtraction = executionPlanRuntime.requirementExtraction; + const rbpRoutePlanEnforcement = executionPlanRuntime.rbpRoutePlanEnforcement; + const faRoutePlanEnforcement = executionPlanRuntime.faRoutePlanEnforcement; + const executionPlan = executionPlanRuntime.executionPlan; + const retrievalRuntime = await (0, assistantDeepTurnRetrievalRuntimeAdapter_1.executeAssistantDeepTurnRetrievalPlan)({ executionPlan, - claimType: claimAnchorAudit.claim_type, - temporalGuard + liveTemporalHint, + executeRouteRuntime: (route, fragmentText, options) => this.dataLayer.executeRouteRuntime(route, fragmentText, options), + mapNoRouteReason, + buildSkippedResult }); - executionPlan = rbpRoutePlanEnforcement.executionPlan; - const faRoutePlanEnforcement = enforceFaLiveRoutePlan({ - executionPlan, - claimType: claimAnchorAudit.claim_type, - temporalGuard - }); - executionPlan = faRoutePlanEnforcement.executionPlan; - executionPlan = (0, assistantRuntimeGuards_1.applyTemporalHintToExecutionPlan)(executionPlan, temporalGuard); - executionPlan = (0, assistantRuntimeGuards_1.applyPolarityHintToExecutionPlan)(executionPlan, domainPolarityGuardInitial); - const retrievalCalls = []; - const retrievalResultsRaw = []; - let retrievalResults = []; - for (const planItem of executionPlan) { - if (!planItem.should_execute) { - retrievalCalls.push({ - fragment_id: planItem.fragment_id, - requirement_ids: planItem.requirement_ids, - route: planItem.route, - status: "skipped", - query_text: planItem.fragment_text, - reason: mapNoRouteReason(planItem.no_route_reason) - }); - retrievalResults.push(buildSkippedResult(planItem)); - continue; - } - retrievalCalls.push({ - fragment_id: planItem.fragment_id, - requirement_ids: planItem.requirement_ids, - route: planItem.route, - status: "executed", - query_text: planItem.fragment_text, - reason: null - }); - try { - const raw = await this.dataLayer.executeRouteRuntime(planItem.route, planItem.fragment_text); - retrievalResultsRaw.push({ - fragment_id: planItem.fragment_id, - route: planItem.route, - raw_result: raw - }); - retrievalResults.push((0, retrievalResultNormalizer_1.normalizeRetrievalResult)(planItem.fragment_id, planItem.requirement_ids, planItem.route, raw)); - } - catch (error) { - const message = error instanceof Error ? error.message : String(error); - retrievalCalls[retrievalCalls.length - 1].status = "failed"; - retrievalCalls[retrievalCalls.length - 1].reason = message; - const rawError = { - status: "error", - result_type: "summary", - items: [], - summary: { - route: planItem.route - }, - evidence: [], - why_included: [], - selection_reason: [], - risk_factors: [], - business_interpretation: [], - confidence: "low", - limitations: ["Route executor failed."], - errors: [message] - }; - retrievalResultsRaw.push({ - fragment_id: planItem.fragment_id, - route: planItem.route, - raw_result: rawError - }); - retrievalResults.push((0, retrievalResultNormalizer_1.normalizeRetrievalResult)(planItem.fragment_id, planItem.requirement_ids, planItem.route, rawError)); - } - } - const polarityGuardResult = (0, assistantRuntimeGuards_1.applyDomainPolarityGuardToRetrievalResults)({ + const retrievalCalls = retrievalRuntime.retrievalCalls; + const retrievalResultsRaw = retrievalRuntime.retrievalResultsRaw; + let retrievalResults = retrievalRuntime.retrievalResults; + const guardRuntime = (0, assistantDeepTurnGuardRuntimeAdapter_1.applyAssistantDeepTurnRetrievalGuards)({ retrievalResults, - guard: domainPolarityGuardInitial - }); - retrievalResults = polarityGuardResult.retrievalResults; - const targetedEvidenceResult = (0, assistantClaimBoundEvidence_1.applyTargetedEvidenceAcquisition)({ - retrievalResults, - claimAudit: claimAnchorAudit - }); - retrievalResults = targetedEvidenceResult.retrievalResults; - const evidenceGateResult = (0, assistantRuntimeGuards_1.applyEvidenceAdmissibilityGate)({ - retrievalResults, - temporal: temporalGuard, - focusDomainHint: focusDomainForGuards, - polarity: polarityGuardResult.audit.polarity, + domainPolarityGuardInitial, + claimAnchorAudit, + temporalGuard, + focusDomainForGuards, companyAnchors, userMessage }); - retrievalResults = evidenceGateResult.retrievalResults; - const rbpLiveRouteAudit = collectRbpLiveRouteAudit({ + retrievalResults = guardRuntime.retrievalResults; + const polarityGuardResult = guardRuntime.polarityGuardResult; + const targetedEvidenceResult = guardRuntime.targetedEvidenceResult; + const evidenceGateResult = guardRuntime.evidenceGateResult; + const groundingRuntime = (0, assistantDeepTurnGroundingRuntimeAdapter_1.runAssistantDeepTurnGroundingRuntime)({ claimType: claimAnchorAudit.claim_type, retrievalResults, - planAudit: rbpRoutePlanEnforcement.audit - }); - const faLiveRouteAudit = collectFaLiveRouteAudit({ - claimType: claimAnchorAudit.claim_type, - retrievalResults, - planAudit: faRoutePlanEnforcement.audit - }); - const coverageEvaluation = evaluateCoverage(requirementExtraction.requirements, retrievalResults); - const groundingCheckBase = checkGrounding(userMessage, coverageEvaluation.requirements, coverageEvaluation.coverage, retrievalResults); - const groundedAnswerEligibilityGuard = (0, assistantRuntimeGuards_1.evaluateGroundedAnswerEligibility)({ - temporal: temporalGuard, - polarity: polarityGuardResult.audit, - evidence: evidenceGateResult.audit, - claimAnchors: claimAnchorAudit, + rbpPlanAudit: rbpRoutePlanEnforcement.audit, + faPlanAudit: faRoutePlanEnforcement.audit, + routeSummary: resolvedRouteSummary, + normalizedPayload: normalized.normalized, + userMessage, + requirementExtraction, + extractRequirements, + evaluateCoverage, + checkGrounding, + temporalGuard, + polarityAudit: polarityGuardResult.audit, + evidenceAudit: evidenceGateResult.audit, + claimAnchorAudit, targetedEvidenceHitRate: targetedEvidenceResult.audit.targeted_evidence_hit_rate, - businessScopeResolved: businessScopeResolution.business_scope_resolved + businessScopeResolved: businessScopeResolution.business_scope_resolved, + collectRbpLiveRouteAudit, + collectFaLiveRouteAudit }); - const groundingCheck = (0, assistantRuntimeGuards_1.applyEligibilityToGroundingCheck)(groundingCheckBase, groundedAnswerEligibilityGuard); - const focusDomainHint = followupBinding.usage?.applied - ? session.investigation_state?.followup_context?.active_domain ?? session.investigation_state?.focus.domain ?? null - : null; - const questionTypeClass = (0, questionTypeResolver_1.resolveQuestionType)(userMessage); - const hasPeriodInCompanyAnchors = (Array.isArray(companyAnchors?.dates) && companyAnchors.dates.some((item) => String(item ?? "").trim().length > 0)) || - (Array.isArray(companyAnchors?.periods) && companyAnchors.periods.some((item) => String(item ?? "").trim().length > 0)); - const normalizationPeriodExplicit = hasExplicitPeriodAnchorFromNormalized(normalized.normalized) || hasPeriodInCompanyAnchors; - const composition = (0, answerComposer_1.composeAssistantAnswer)({ + const rbpLiveRouteAudit = groundingRuntime.rbpLiveRouteAudit; + const faLiveRouteAudit = groundingRuntime.faLiveRouteAudit; + const coverageEvaluation = groundingRuntime.coverageEvaluation; + const groundedAnswerEligibilityGuard = groundingRuntime.groundedAnswerEligibilityGuard; + const groundingCheck = groundingRuntime.groundingCheck; + const deepTurnComposition = (0, assistantDeepTurnCompositionRuntimeAdapter_1.buildAssistantDeepTurnComposition)({ userMessage, routeSummary: resolvedRouteSummary, retrievalResults, requirements: coverageEvaluation.requirements, coverageReport: coverageEvaluation.coverage, groundingCheck, - focusDomainHint, - questionTypeHint: questionTypeClass, + followupUsage: followupBinding.usage, + investigationState: session.investigation_state, companyAnchors, - normalizationPeriodExplicit, - enableAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11, - enableProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1, - enableLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1 + normalizedPayload: normalized.normalized, + featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11, + featureProblemCentricAnswerV1: config_1.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1, + featureLifecycleAnswerV1: config_1.FEATURE_ASSISTANT_LIFECYCLE_ANSWER_V1, + hasExplicitPeriodAnchor: (normalizedPayload) => hasExplicitPeriodAnchorFromNormalized(normalizedPayload) }); - const safeAssistantReplyBase = sanitizeOutgoingAssistantText(composition.assistant_reply, "Нужны уточнения для надежного ответа."); - const safeAssistantReply = String(safeAssistantReplyBase ?? "") - .replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "") - .replace(/\b(?:debug_payload_json|technical_breakdown_json)\b[\s\S]*$/gi, "") - .trim(); - const answerStructureV11 = config_1.FEATURE_ASSISTANT_CONTRACTS_V11 - ? config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11 && composition.answer_structure_v11 - ? composition.answer_structure_v11 - : buildAnswerStructureV11({ - assistantReply: safeAssistantReply, - coverageReport: coverageEvaluation.coverage, - groundingCheck, - retrievalResults - }) - : null; - const investigationStateSnapshot = config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 && session.investigation_state - ? (0, investigationState_1.updateInvestigationState)({ - previous: session.investigation_state, - timestamp: new Date().toISOString(), - questionId: userItem.message_id, - userMessage, - routeSummary: resolvedRouteSummary, - requirements: coverageEvaluation.requirements, - coverageReport: coverageEvaluation.coverage, - retrievalResults, - replyType: composition.reply_type, - followupApplied: Boolean(followupBinding.usage?.applied) - }) - : null; - if (config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1 && investigationStateSnapshot) { - this.sessions.setInvestigationState(sessionId, investigationStateSnapshot); - } - const debug = { - trace_id: normalized.trace_id, - prompt_version: normalized.prompt_version, - schema_version: normalized.schema_version, - fallback_type: composition.fallback_type, - route_summary: resolvedRouteSummary, - fragments: extractFragments(normalized.normalized), - requirements_extracted: coverageEvaluation.requirements, - coverage_report: coverageEvaluation.coverage, - routes: toDebugRoutes(resolvedRouteSummary), - retrieval_status: retrievalResults.map((item) => ({ - fragment_id: item.fragment_id, - requirement_ids: item.requirement_ids, - route: item.route, - status: item.status, - result_type: item.result_type - })), - retrieval_results: retrievalResults, - answer_grounding_check: groundingCheck, - dropped_intent_segments: extractDiscardedIntentSegments(normalized.normalized), - question_type_class: questionTypeClass, - company_anchors: companyAnchors, - business_scope_raw: businessScopeResolution.business_scope_raw, - business_scope_resolved: businessScopeResolution.business_scope_resolved, - company_grounding_applied: businessScopeResolution.company_grounding_applied, - scope_resolution_reason: businessScopeResolution.scope_resolution_reason, - company_scope_resolution_reason: businessScopeResolution.scope_resolution_reason, - raw_time_anchor: temporalGuard.raw_time_anchor, - raw_time_scope: temporalGuard.raw_time_scope, - resolved_time_anchor: temporalGuard.resolved_time_anchor, - resolved_primary_period: temporalGuard.resolved_primary_period, - effective_primary_period: temporalGuard.effective_primary_period, - temporal_guard_input: temporalGuard.temporal_guard_input, - temporal_alignment_status: temporalGuard.temporal_alignment_status, - temporal_resolution_source: temporalGuard.temporal_resolution_source, - temporal_guard_basis: temporalGuard.temporal_guard_basis, - temporal_guard_applied: temporalGuard.temporal_guard_applied, - temporal_guard_outcome: temporalGuard.temporal_guard_outcome, - temporal_guard: temporalGuard, - raw_numeric_tokens: polarityGuardResult.audit.raw_numeric_tokens, - classified_numeric_tokens: polarityGuardResult.audit.classified_numeric_tokens, - rejected_as_non_accounts: polarityGuardResult.audit.rejected_as_non_accounts, - resolved_account_anchors: polarityGuardResult.audit.resolved_account_anchors, - domain_polarity_guard: polarityGuardResult.audit, - claim_anchor_audit: claimAnchorAudit, - settlement_role: claimAnchorAudit.settlement_role ?? null, - settlement_role_resolution_reason: claimAnchorAudit.settlement_role_resolution_reason ?? [], - polarity_resolution_status: claimAnchorAudit.polarity_resolution_status ?? "not_applicable", - targeted_evidence_acquisition: targetedEvidenceResult.audit, - evidence_admissibility_gate: evidenceGateResult.audit, - ...(rbpLiveRouteAudit ? { rbp_live_route_audit: rbpLiveRouteAudit } : {}), - ...(faLiveRouteAudit ? { fa_live_route_audit: faLiveRouteAudit } : {}), - eligibility_time_basis: groundedAnswerEligibilityGuard.eligibility_time_basis, - grounded_answer_eligibility_guard: groundedAnswerEligibilityGuard, - ...(followupBinding.usage ? { followup_state_usage: followupBinding.usage } : {}), - problem_centric_answer_applied: composition.problem_centric_answer_applied ?? false, - problem_units_used_count: composition.problem_units_used_count ?? 0, - problem_answer_mode: composition.problem_answer_mode ?? "stage1_policy_v11", - ...(Array.isArray(composition.problem_unit_ids_used) && composition.problem_unit_ids_used.length > 0 - ? { - problem_unit_ids_used: composition.problem_unit_ids_used - } - : {}), - address_llm_predecompose_attempted: Boolean(addressRuntimeMetaForDeep?.attempted), - address_llm_predecompose_applied: Boolean(addressRuntimeMetaForDeep?.applied), - address_llm_predecompose_reason: addressRuntimeMetaForDeep?.reason ?? null, - address_llm_predecompose_provider: addressRuntimeMetaForDeep?.provider ?? null, - address_fallback_rule_hit: addressRuntimeMetaForDeep?.fallbackRuleHit ?? null, - address_tool_gate_decision: addressRuntimeMetaForDeep?.toolGateDecision ?? null, - address_tool_gate_reason: addressRuntimeMetaForDeep?.toolGateReason ?? null, - address_llm_predecompose_contract: addressRuntimeMetaForDeep?.predecomposeContract ?? null, - orchestration_contract_v1: addressRuntimeMetaForDeep?.orchestrationContract ?? null, - answer_structure_v11: answerStructureV11, - investigation_state_snapshot: investigationStateSnapshot, - normalized: normalized.normalized - }; - const assistantItem = { - message_id: `msg-${(0, nanoid_1.nanoid)(10)}`, - session_id: sessionId, - role: "assistant", - text: safeAssistantReply, - reply_type: composition.reply_type, - created_at: new Date().toISOString(), - trace_id: normalized.trace_id, - debug - }; - this.sessions.appendItem(sessionId, assistantItem); - const current = this.sessions.getSession(sessionId); - if (current) { - this.sessionLogger.persistSession(current); - } - const conversation = cloneItems(current?.items ?? []); - (0, log_1.logJson)({ - timestamp: new Date().toISOString(), - level: "info", - service: "assistant_loop", - message: "assistant_message_processed", + const questionTypeClass = deepTurnComposition.questionTypeClass; + const composition = deepTurnComposition.composition; + const packagingRuntime = (0, assistantDeepTurnPackagingRuntimeAdapter_1.runAssistantDeepTurnPackagingRuntime)({ + featureInvestigationStateV1: config_1.FEATURE_ASSISTANT_INVESTIGATION_STATE_V1, sessionId, - eventType: "assistant_message", - details: { - session_id: sessionId, - message_id: assistantItem.message_id, - user_message: userMessage, - normalizer_output: normalized.normalized, - execution_plan: executionPlan, - resolved_execution_state: extractExecutionState(normalized.normalized), - routes: toDebugRoutes(resolvedRouteSummary), - retrieval_calls: retrievalCalls, - retrieval_results_raw: retrievalResultsRaw, - retrieval_results_normalized: retrievalResults, - requirements_extracted: coverageEvaluation.requirements, - requirements_total: coverageEvaluation.coverage.requirements_total, - requirements_covered: coverageEvaluation.coverage.requirements_covered, - requirements_uncovered: coverageEvaluation.coverage.requirements_uncovered, - coverage_status: coverageEvaluation.coverage.requirements_total === coverageEvaluation.coverage.requirements_covered && - coverageEvaluation.coverage.requirements_uncovered.length === 0 && - coverageEvaluation.coverage.requirements_partially_covered.length === 0 - ? "full" - : "partial_or_limited", - answer_grounding_status: groundingCheck.status, - reply_semantic_type: composition.reply_type, - why_included_summary: groundingCheck.why_included_summary, - selection_reason_summary: groundingCheck.selection_reason_summary, - route_subject_match: groundingCheck.route_subject_match, - clarification_target: coverageEvaluation.coverage.clarification_needed_for, - dropped_intent_segments: extractDiscardedIntentSegments(normalized.normalized), - question_type_class: questionTypeClass, - company_anchors: companyAnchors, - business_scope_raw: businessScopeResolution.business_scope_raw, - business_scope_resolved: businessScopeResolution.business_scope_resolved, - company_grounding_applied: businessScopeResolution.company_grounding_applied, - scope_resolution_reason: businessScopeResolution.scope_resolution_reason, - company_scope_resolution_reason: businessScopeResolution.scope_resolution_reason, - raw_time_anchor: temporalGuard.raw_time_anchor, - raw_time_scope: temporalGuard.raw_time_scope, - resolved_time_anchor: temporalGuard.resolved_time_anchor, - resolved_primary_period: temporalGuard.resolved_primary_period, - effective_primary_period: temporalGuard.effective_primary_period, - temporal_guard_input: temporalGuard.temporal_guard_input, - temporal_alignment_status: temporalGuard.temporal_alignment_status, - temporal_resolution_source: temporalGuard.temporal_resolution_source, - temporal_guard_basis: temporalGuard.temporal_guard_basis, - temporal_guard_applied: temporalGuard.temporal_guard_applied, - temporal_guard_outcome: temporalGuard.temporal_guard_outcome, - temporal_guard: temporalGuard, - raw_numeric_tokens: polarityGuardResult.audit.raw_numeric_tokens, - classified_numeric_tokens: polarityGuardResult.audit.classified_numeric_tokens, - rejected_as_non_accounts: polarityGuardResult.audit.rejected_as_non_accounts, - resolved_account_anchors: polarityGuardResult.audit.resolved_account_anchors, - domain_polarity_guard: polarityGuardResult.audit, - claim_anchor_audit: claimAnchorAudit, - settlement_role: claimAnchorAudit.settlement_role ?? null, - settlement_role_resolution_reason: claimAnchorAudit.settlement_role_resolution_reason ?? [], - polarity_resolution_status: claimAnchorAudit.polarity_resolution_status ?? "not_applicable", - targeted_evidence_acquisition: targetedEvidenceResult.audit, - evidence_admissibility_gate: evidenceGateResult.audit, - ...(rbpLiveRouteAudit ? { rbp_live_route_audit: rbpLiveRouteAudit } : {}), - ...(faLiveRouteAudit ? { fa_live_route_audit: faLiveRouteAudit } : {}), - eligibility_time_basis: groundedAnswerEligibilityGuard.eligibility_time_basis, - grounded_answer_eligibility_guard: groundedAnswerEligibilityGuard, - ...(followupBinding.usage ? { followup_state_usage: followupBinding.usage } : {}), - problem_centric_answer_applied: composition.problem_centric_answer_applied ?? false, - problem_units_used_count: composition.problem_units_used_count ?? 0, - problem_answer_mode: composition.problem_answer_mode ?? "stage1_policy_v11", - ...(Array.isArray(composition.problem_unit_ids_used) && composition.problem_unit_ids_used.length > 0 - ? { - problem_unit_ids_used: composition.problem_unit_ids_used - } - : {}), - answer_structure_v11: answerStructureV11, - investigation_state_snapshot: investigationStateSnapshot, - fallback_type: composition.fallback_type, - assistant_reply: safeAssistantReply, - reply_type: composition.reply_type, - trace_id: normalized.trace_id - } + questionId: userItem.message_id, + userMessage, + normalized: { + trace_id: normalized.trace_id, + prompt_version: normalized.prompt_version, + schema_version: normalized.schema_version, + normalized: normalized.normalized + }, + normalizedQuestion: followupBinding.normalizedQuestion, + routeSummary: resolvedRouteSummary, + executionPlan, + requirementExtractionRequirements: requirementExtraction.requirements, + coverageEvaluationRequirements: coverageEvaluation.requirements, + coverageReport: coverageEvaluation.coverage, + groundingCheck, + retrievalCalls, + retrievalResultsRaw, + retrievalResults, + questionTypeClass, + companyAnchors, + runtimeAnalysisContext, + businessScopeResolution, + temporalGuard, + polarityAudit: polarityGuardResult.audit, + claimAnchorAudit, + targetedEvidenceAudit: targetedEvidenceResult.audit, + evidenceAdmissibilityGateAudit: evidenceGateResult.audit, + rbpLiveRouteAudit, + faLiveRouteAudit, + groundedAnswerEligibilityGuard, + followupStateUsage: followupBinding.usage, + followupApplied: Boolean(followupBinding.usage?.applied), + composition, + featureContractsV11: config_1.FEATURE_ASSISTANT_CONTRACTS_V11, + featureAnswerPolicyV11: config_1.FEATURE_ASSISTANT_ANSWER_POLICY_V11, + previousInvestigationState: session.investigation_state, + addressRuntimeMetaForDeep, + extractDroppedIntentSegments: (normalizedPayload) => extractDiscardedIntentSegments(normalizedPayload), + buildDebugRoutes: (routeSummary) => toDebugRoutes(routeSummary), + extractExecutionState: (normalizedPayload) => extractExecutionState(normalizedPayload), + sanitizeReply: (value, fallback) => sanitizeOutgoingAssistantText(value, fallback), + persistInvestigationState: (targetSessionId, snapshot) => this.sessions.setInvestigationState(targetSessionId, snapshot), + messageIdFactory: () => `msg-${(0, nanoid_1.nanoid)(10)}` }); - return { - ok: true, - session_id: sessionId, - assistant_reply: safeAssistantReply, - reply_type: composition.reply_type, - conversation_item: assistantItem, + const safeAssistantReply = packagingRuntime.safeAssistantReply; + const debug = packagingRuntime.debug; + const assistantItem = packagingRuntime.assistantItem; + const deepAnalysisLogDetails = packagingRuntime.deepAnalysisLogDetails; + const commitResult = (0, assistantTurnCommitRuntimeAdapter_1.commitAssistantTurnAndLog)({ + sessionId, + assistantItem, + eventType: "assistant_message", + logDetails: deepAnalysisLogDetails, + appendItem: (targetSessionId, item) => this.sessions.appendItem(targetSessionId, item), + getSession: (targetSessionId) => this.sessions.getSession(targetSessionId), + persistSession: (sessionState) => this.sessionLogger.persistSession(sessionState), + cloneConversation: (items) => cloneItems(items), + logEvent: (payload) => (0, log_1.logJson)(payload) + }); + const conversation = commitResult.conversation; + return (0, assistantDeepTurnResponseBuilder_1.buildAssistantDeepTurnSuccessResponse)({ + sessionId, + assistantReply: safeAssistantReply, + replyType: composition.reply_type, + conversationItem: assistantItem, debug, conversation - }; + }); } } diff --git a/llm_normalizer/backend/src/services/assistantTurnCommitRuntimeAdapter.ts b/llm_normalizer/backend/src/services/assistantTurnCommitRuntimeAdapter.ts new file mode 100644 index 0000000..0c004b6 --- /dev/null +++ b/llm_normalizer/backend/src/services/assistantTurnCommitRuntimeAdapter.ts @@ -0,0 +1,49 @@ +import type { AssistantConversationItem, AssistantSessionState } from "../types/assistant"; + +export interface CommitAssistantTurnAndLogInput { + sessionId: string; + assistantItem: AssistantConversationItem; + eventType: string; + logDetails: Record; + appendItem: (sessionId: string, item: AssistantConversationItem) => void; + getSession: (sessionId: string) => AssistantSessionState | null; + persistSession: (session: AssistantSessionState) => void; + cloneConversation: (items: AssistantConversationItem[]) => AssistantConversationItem[]; + logEvent: (payload: { + timestamp: string; + level: "info"; + service: "assistant_loop"; + message: "assistant_message_processed"; + sessionId: string; + eventType: string; + details: Record; + }) => void; + nowIso?: () => string; +} + +export interface CommitAssistantTurnAndLogOutput { + currentSession: AssistantSessionState | null; + conversation: AssistantConversationItem[]; +} + +export function commitAssistantTurnAndLog(input: CommitAssistantTurnAndLogInput): CommitAssistantTurnAndLogOutput { + input.appendItem(input.sessionId, input.assistantItem); + const currentSession = input.getSession(input.sessionId); + if (currentSession) { + input.persistSession(currentSession); + } + const conversation = input.cloneConversation(currentSession?.items ?? []); + input.logEvent({ + timestamp: (input.nowIso ?? (() => new Date().toISOString()))(), + level: "info", + service: "assistant_loop", + message: "assistant_message_processed", + sessionId: input.sessionId, + eventType: input.eventType, + details: input.logDetails + }); + return { + currentSession, + conversation + }; +} diff --git a/llm_normalizer/backend/src/services/evalService.ts b/llm_normalizer/backend/src/services/evalService.ts index 9599cd6..f237d7f 100644 --- a/llm_normalizer/backend/src/services/evalService.ts +++ b/llm_normalizer/backend/src/services/evalService.ts @@ -264,6 +264,32 @@ function parseRawQuestions(rawQuestions: string): string[] { return byLine.length > 0 ? byLine : [text]; } +function normalizeAnalysisDate(value: unknown): string | null { + if (typeof value !== "string") { + return null; + } + const trimmed = value.trim(); + const match = trimmed.match(/^(\d{4})-(\d{2})-(\d{2})$/); + if (!match) { + return null; + } + const year = Number(match[1]); + const month = Number(match[2]); + const day = Number(match[3]); + if (!Number.isFinite(year) || !Number.isFinite(month) || !Number.isFinite(day)) { + return null; + } + const candidate = new Date(Date.UTC(year, month - 1, day)); + if ( + candidate.getUTCFullYear() !== year || + candidate.getUTCMonth() + 1 !== month || + candidate.getUTCDate() !== day + ) { + return null; + } + return `${match[1]}-${match[2]}-${match[3]}`; +} + type V2FamilyFragment = | NormalizedQueryV2["fragments"][number] | NormalizedQueryV2_0_1["fragments"][number] @@ -936,6 +962,7 @@ export class EvalService { mode: EvalRunMode; caseSetFile?: string; rawQuestions?: string; + analysisDate?: string; cases: EvalInputCase[]; }): Promise> { const runId = `eval-${nanoid(10)}`; @@ -976,6 +1003,13 @@ export class EvalService { ...payload.normalizeConfig, userQuestion: item.raw_question, context: { + period_hint: payload.analysisDate ?? undefined, + analysis_context: payload.analysisDate + ? { + as_of_date: payload.analysisDate, + source: "eval_analysis_date" + } + : undefined, eval_label: runId, case_id: item.case_id, eval_mode: payload.mode @@ -1876,6 +1910,7 @@ export class EvalService { mode: EvalRunMode; caseSetFile?: string; compareWithReportFile?: string; + analysisDate?: string; runId?: string; }): Promise> { if (!FEATURE_ASSISTANT_ACCOUNTANT_EVAL_V1) { @@ -1889,6 +1924,7 @@ export class EvalService { const suite = parseAssistantSuiteFile(payload.caseSetFile); const suiteCases = suite.cases.filter((item) => !payload.caseIds || payload.caseIds.includes(item.case_id)); const runId = typeof payload.runId === "string" && payload.runId.trim().length > 0 ? payload.runId.trim() : `assistant-stage1-${nanoid(10)}`; + const analysisDate = normalizeAnalysisDate(payload.analysisDate); const assistantService = new AssistantService(this.normalizerService, new AssistantSessionStore()); const diagnostics: AssistantCaseDiagnostics[] = []; let requestsTotal = 0; @@ -1917,6 +1953,15 @@ export class EvalService { developerPrompt: payload.normalizeConfig.developerPrompt, domainPrompt: payload.normalizeConfig.domainPrompt, fewShotExamples: payload.normalizeConfig.fewShotExamples, + context: analysisDate + ? { + period_hint: analysisDate, + analysis_context: { + as_of_date: analysisDate, + source: "eval_analysis_date" + } + } + : undefined, useMock: payload.useMock })) as AssistantMessageResponsePayload; turnResponses.push(response); @@ -2153,6 +2198,7 @@ export class EvalService { eval_target: "assistant_stage1", mode: payload.mode, use_mock: Boolean(payload.useMock), + analysis_date: analysisDate, prompt_version: payload.normalizeConfig.promptVersion ?? null, suite_id: suite.suite_id, suite_version: suite.suite_version, @@ -2225,6 +2271,7 @@ export class EvalService { mode: EvalRunMode; caseSetFile?: string; compareWithReportFile?: string; + analysisDate?: string; runId?: string; }): Promise> { if (!FEATURE_ASSISTANT_STAGE2_EVAL_V1) { @@ -2238,6 +2285,7 @@ export class EvalService { const suite = parseAssistantStage2SuiteFile(payload.caseSetFile); const suiteCases = suite.cases.filter((item) => !payload.caseIds || payload.caseIds.includes(item.case_id)); const runId = typeof payload.runId === "string" && payload.runId.trim().length > 0 ? payload.runId.trim() : `assistant-stage2-${nanoid(10)}`; + const analysisDate = normalizeAnalysisDate(payload.analysisDate); const assistantService = new AssistantService(this.normalizerService, new AssistantSessionStore()); const diagnostics: AssistantStage2CaseDiagnostics[] = []; let requestsTotal = 0; @@ -2269,6 +2317,15 @@ export class EvalService { developerPrompt: payload.normalizeConfig.developerPrompt, domainPrompt: payload.normalizeConfig.domainPrompt, fewShotExamples: payload.normalizeConfig.fewShotExamples, + context: analysisDate + ? { + period_hint: analysisDate, + analysis_context: { + as_of_date: analysisDate, + source: "eval_analysis_date" + } + } + : undefined, useMock: payload.useMock })) as AssistantMessageResponsePayload; turnResponses.push(response); @@ -2446,6 +2503,7 @@ export class EvalService { eval_target: "assistant_stage2", mode: payload.mode, use_mock: Boolean(payload.useMock), + analysis_date: analysisDate, prompt_version: payload.normalizeConfig.promptVersion ?? null, suite_id: suite.suite_id, suite_version: suite.suite_version, @@ -2552,10 +2610,12 @@ export class EvalService { rawQuestions?: string; evalTarget?: EvalTarget; compareWithReportFile?: string; + analysisDate?: string; runId?: string; }): Promise> { const mode = payload.mode ?? "standard"; const evalTarget = payload.evalTarget ?? "normalizer"; + const analysisDate = normalizeAnalysisDate(payload.analysisDate); if (evalTarget === "assistant_stage1") { return this.runAssistantStage1({ @@ -2565,6 +2625,7 @@ export class EvalService { mode, caseSetFile: payload.caseSetFile, compareWithReportFile: payload.compareWithReportFile, + analysisDate: analysisDate ?? undefined, runId: payload.runId }); } @@ -2577,6 +2638,7 @@ export class EvalService { mode, caseSetFile: payload.caseSetFile, compareWithReportFile: payload.compareWithReportFile, + analysisDate: analysisDate ?? undefined, runId: payload.runId }); } @@ -2622,6 +2684,7 @@ export class EvalService { return this.runV2({ ...payload, mode, + analysisDate: analysisDate ?? undefined, cases: filtered }); } @@ -2651,6 +2714,13 @@ export class EvalService { ...payload.normalizeConfig, userQuestion: item.raw_question, context: { + period_hint: analysisDate ?? undefined, + analysis_context: analysisDate + ? { + as_of_date: analysisDate, + source: "eval_analysis_date" + } + : undefined, expected_route: item.expected.route_hint as NormalizeRequestPayload["context"] extends infer C ? C extends { expected_route?: infer R } ? R @@ -2779,6 +2849,7 @@ export class EvalService { timestamp: new Date().toISOString(), mode, use_mock: Boolean(payload.useMock), + analysis_date: analysisDate, prompt_version: payload.normalizeConfig.promptVersion ?? null, dataset: { source: payload.caseSetFile ? "file" : "data/eval_cases/*.json", diff --git a/llm_normalizer/backend/src/services/questionTypeResolver.ts b/llm_normalizer/backend/src/services/questionTypeResolver.ts index e4b73ca..e2c568a 100644 --- a/llm_normalizer/backend/src/services/questionTypeResolver.ts +++ b/llm_normalizer/backend/src/services/questionTypeResolver.ts @@ -125,7 +125,7 @@ export function resolveQuestionType(input: string): QuestionTypeClass { return bestType; } - if (/[?пјџ]/u.test(text)) { + if (/(?:\bwhy\b|почему|из-?за\s+чего|в\s+ч(?:е|ё)м\s+причина)/iu.test(text)) { return "why_breaks"; } diff --git a/llm_normalizer/backend/src/types/normalizer.ts b/llm_normalizer/backend/src/types/normalizer.ts index 0432054..b35563f 100644 --- a/llm_normalizer/backend/src/types/normalizer.ts +++ b/llm_normalizer/backend/src/types/normalizer.ts @@ -235,6 +235,14 @@ export interface RouteHintSummaryV2 { export type RouteHintSummary = RouteHintSummaryV1 | RouteHintSummaryV2; export type NormalizedPayload = NormalizedQueryV1 | NormalizedQueryV2 | NormalizedQueryV2_0_1 | NormalizedQueryV2_0_2; +export interface AnalysisContextV1 { + as_of_date?: string; + period_from?: string; + period_to?: string; + snapshot_mode?: "auto" | "force_snapshot" | "force_live"; + source?: string; +} + export interface NormalizeRequestPayload { llmProvider?: LlmProvider; apiKey?: string; @@ -250,6 +258,7 @@ export interface NormalizeRequestPayload { userQuestion: string; context?: { period_hint?: string; + analysis_context?: AnalysisContextV1; business_context?: string; expected_route?: RouteHint; eval_label?: string; diff --git a/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts b/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts index d985da3..12a08a1 100644 --- a/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts +++ b/llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts @@ -1654,6 +1654,11 @@ describe("address intent resolver expansion (M2.3a)", () => { expect(result.intent).toBe("customer_revenue_and_payments"); }); + it("resolves major-share revenue wording into customer revenue intent", () => { + const result = resolveAddressIntent("какие контрагенты принесли основную часть нашей выручки за отчетный период?"); + expect(result.intent).toBe("customer_revenue_and_payments"); + }); + it("resolves customer revenue intent from highest inflow slang wording", () => { const result = resolveAddressIntent("какие приходы самые высокие за все время"); expect(result.intent).toBe("customer_revenue_and_payments"); @@ -1725,6 +1730,74 @@ describe("address intent resolver expansion (M2.3a)", () => { const result = resolveAddressIntent("покажи документы по этому же договору"); expect(result.intent).toBe("list_documents_by_contract"); }); + + it("routes supplier tail-risk wording into payables intent", () => { + const result = resolveAddressIntent( + "Кто из поставщиков имеет хвосты с документами на конец месяца, которые уже больше похожи на систематическую проблему, а не на обычную задержку?" + ); + expect(result.intent).toBe("list_payables_counterparties"); + }); + + it("keeps out-of-scope supplier control wording as unknown intent", () => { + const result = resolveAddressIntent( + "Какие поставщики у нас уже пару месяцев сдают акты без приходок. Может, их надо проконтролировать отдельно чтоб не засорять бухгалтерию дальше?" + ); + expect(result.intent).toBe("unknown"); + }); + + it("routes long shipment-to-payment lag wording into receivables intent", () => { + const result = resolveAddressIntent( + "Где у нас висят покупатели со слишком длинным периодом между отправкой товара и его оплатой, и это уже вызывает тревогу?" + ); + expect(result.intent).toBe("list_receivables_counterparties"); + }); + + it("routes non-paying counterparties month-risk wording into receivables intent", () => { + const result = resolveAddressIntent( + "какие контрагенты пока вообще не платят за текущий месяц и это уже тревожный знак для нас?" + ); + expect(result.intent).toBe("list_receivables_counterparties"); + }); + + it("routes reconciliation mismatch wording into open contracts intent", () => { + const result = resolveAddressIntent( + "Покажи контрагентов, по которым сальдо скорее всего не совпадет с их актом сверки. Может, стоит поторопиться и запросить сверку?" + ); + expect(result.intent).toBe("list_open_contracts"); + }); + + it("routes reconciliation mismatch wording without explicit lookup verb into open contracts intent", () => { + const result = resolveAddressIntent( + "По каким поставщикам у нас сальдо явно расходится с тем, что они сами указывают в своих актах сверок?" + ); + expect(result.intent).toBe("list_open_contracts"); + }); + + it("routes payments-without-closing-docs wording into open contracts intent", () => { + const result = resolveAddressIntent( + "Где у нас есть платежи, но нет документов для закрытия взаиморасчетов? Это уже требует ручной проверки." + ); + expect(result.intent).toBe("list_open_contracts"); + }); + + it("routes documents-without-payments wording into open contracts intent", () => { + const result = resolveAddressIntent( + "По каким контрагентам документы есть, а оплат нет. Может, стоит взять на карандаш такие ситуации чтоб не тянуть дальше?" + ); + expect(result.intent).toBe("list_open_contracts"); + }); + + it("routes stale advances without closing docs wording into open contracts intent", () => { + const result = resolveAddressIntent( + "по каким поставщикам мы видим проблемные авансы, которые давно не закрыты документами?" + ); + expect(result.intent).toBe("list_open_contracts"); + }); + + it("routes buyers with open debt wording into open-items intent", () => { + const result = resolveAddressIntent("по каким покупателям у нас есть открытые задолженности на конец месяца?"); + expect(result.intent).toBe("open_items_by_counterparty_or_contract"); + }); }); describe("address filter extraction for balance drilldown", () => { @@ -1810,6 +1883,14 @@ describe("address filter extraction for balance drilldown", () => { expect(extracted.warnings).toContain("counterparty_anchor_dropped_low_quality"); }); + it("does not derive fake counterparty anchor for open-contracts stale-advance wording", () => { + const extracted = extractAddressFilters( + "по каким поставщикам мы видим проблемные авансы, которые давно не закрыты документами?", + "list_open_contracts" + ); + expect(extracted.extracted_filters.counterparty).toBeUndefined(); + }); + it("derives VAT forecast quarter-to-date window when plain date phrase is present", () => { const extracted = extractAddressFilters( "мож прикинусь плиз скока ндс надо заплатить на 15 марта 2020 года", @@ -2250,6 +2331,98 @@ describe("address filter extraction for balance drilldown", () => { }); describe("address query limited taxonomy and stage diagnostics", () => { + it("injects as_of_date from analysis context when user message has no explicit period", async () => { + const service = new AddressQueryService(); + const result = await service.tryHandle("Покажи контрагентов с незакрытыми хвостами", { + analysisDateHint: "2020-07-31" + }); + expect(result?.handled).toBe(true); + expect(result?.debug.extracted_filters?.as_of_date).toBe("2020-07-31"); + expect(Array.isArray(result?.debug.reasons)).toBe(true); + expect(result?.debug.reasons).toContain("as_of_date_from_analysis_context"); + }); + + it("returns soft out-of-scope reply without technical jargon for unsupported supplier-control wording", async () => { + const service = new AddressQueryService(); + const result = await service.tryHandle( + "Какие поставщики у нас уже пару месяцев сдают акты без приходок. Может, их надо проконтролировать отдельно чтоб не засорять бухгалтерию дальше?" + ); + expect(result?.handled).toBe(true); + expect(result?.response_type).toBe("LIMITED_WITH_REASON"); + expect(result?.debug.detected_intent).toBe("unknown"); + expect(result?.debug.limited_reason_category).toBe("unsupported"); + const reply = String(result?.reply_text ?? ""); + expect(reply.toLowerCase()).toContain("вне поддерживаемого контура"); + expect(reply).not.toMatch(/address_query|V1|lookup|materialized|якор/iu); + }); + + it("routes supplier tail-risk wording without forcing missing-anchor fallback", async () => { + const service = new AddressQueryService(); + const result = await service.tryHandle( + "Кто из поставщиков имеет хвосты с документами на конец месяца, которые уже больше похожи на систематическую проблему, а не на обычную задержку?" + ); + expect(result?.handled).toBe(true); + expect(result?.debug.detected_intent).toBe("list_payables_counterparties"); + expect(result?.debug.limited_reason_category).not.toBe("missing_anchor"); + expect(result?.debug.limited_reason_category).not.toBe("unsupported"); + }); + + it("routes shipment-to-payment lag wording into receivables lane without missing-anchor fallback", async () => { + const service = new AddressQueryService(); + const result = await service.tryHandle( + "Где у нас висят покупатели со слишком длинным периодом между отправкой товара и его оплатой, и это уже вызывает тревогу?" + ); + expect(result?.handled).toBe(true); + expect(result?.debug.detected_intent).toBe("list_receivables_counterparties"); + expect(result?.debug.limited_reason_category).not.toBe("missing_anchor"); + expect(result?.debug.limited_reason_category).not.toBe("unsupported"); + }); + + it("routes payments-without-closing-docs wording into open contracts lane", async () => { + const service = new AddressQueryService(); + const result = await service.tryHandle( + "Где у нас есть платежи, но нет документов для закрытия взаиморасчетов? Это уже требует ручной проверки." + ); + expect(result?.handled).toBe(true); + expect(result?.debug.detected_intent).toBe("list_open_contracts"); + expect(result?.debug.limited_reason_category).not.toBe("missing_anchor"); + expect(result?.debug.limited_reason_category).not.toBe("unsupported"); + }); + + it("routes stale advances wording into open contracts lane without missing-anchor fallback", async () => { + const service = new AddressQueryService(); + const result = await service.tryHandle( + "по каким поставщикам мы видим проблемные авансы, которые давно не закрыты документами?" + ); + expect(result?.handled).toBe(true); + expect(result?.debug.detected_intent).toBe("list_open_contracts"); + expect(result?.debug.limited_reason_category).not.toBe("missing_anchor"); + expect(result?.debug.limited_reason_category).not.toBe("unsupported"); + }); + + it("routes non-paying counterparties month-risk wording into receivables lane", async () => { + const service = new AddressQueryService(); + const result = await service.tryHandle( + "какие контрагенты пока вообще не платят за текущий месяц и это уже тревожный знак для нас?" + ); + expect(result?.handled).toBe(true); + expect(result?.debug.detected_intent).toBe("list_receivables_counterparties"); + expect(result?.debug.selected_recipe).toBe("address_movements_receivables_v1"); + expect(result?.debug.limited_reason_category).not.toBe("missing_anchor"); + expect(result?.debug.limited_reason_category).not.toBe("unsupported"); + }); + + it("routes documents-without-payments wording into open contracts lane", async () => { + const service = new AddressQueryService(); + const result = await service.tryHandle( + "По каким контрагентам документы есть, а оплат нет. Может, стоит взять на карандаш такие ситуации чтоб не тянуть дальше?" + ); + expect(result?.handled).toBe(true); + expect(result?.debug.detected_intent).toBe("list_open_contracts"); + expect(result?.debug.limited_reason_category).not.toBe("missing_anchor"); + expect(result?.debug.limited_reason_category).not.toBe("unsupported"); + }); + it("routes period coverage profile question into dedicated aggregate recipe", async () => { const service = new AddressQueryService(); const result = await service.tryHandle("За какие годы в базе есть данные?"); diff --git a/llm_normalizer/backend/tests/assistantAnswerPackageBuilder.test.ts b/llm_normalizer/backend/tests/assistantAnswerPackageBuilder.test.ts new file mode 100644 index 0000000..21322a2 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantAnswerPackageBuilder.test.ts @@ -0,0 +1,186 @@ +import { describe, expect, it } from "vitest"; +import { buildAssistantAnswerStructureV11 } from "../src/services/assistantAnswerPackageBuilder"; + +function buildRetrieval(input?: Partial): any { + return { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + status: "ok", + result_type: "summary", + items: [], + summary: {}, + evidence: [], + why_included: [], + selection_reason: [], + risk_factors: [], + business_interpretation: [], + confidence: "medium", + limitations: [], + errors: [], + ...input + }; +} + +describe("assistant answer package builder v11", () => { + it("builds baseline answer structure with unresolved mechanism", () => { + const structure = buildAssistantAnswerStructureV11({ + assistantReply: "Первая строка\nВторая строка", + coverageReport: { + requirements_total: 2, + requirements_covered: 1, + requirements_uncovered: ["R2"], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + groundingCheck: { + status: "partial", + route_subject_match: true, + missing_requirements: ["R2"], + reasons: ["limited coverage"], + why_included_summary: [], + selection_reason_summary: [] + }, + retrievalResults: [buildRetrieval()] + }); + + expect(structure.schema_version).toBe("answer_structure_v1_1"); + expect(structure.answer_summary).toBe("Первая строка"); + expect(structure.mechanism_block.status).toBe("unresolved"); + expect(structure.evidence_block.coverage_note).toBe("coverage_partial_or_limited"); + expect(structure.uncertainty_block.open_uncertainties).toEqual(["R2"]); + }); + + it("adds claim-evidence links when enrichment is explicitly enabled", () => { + const structure = buildAssistantAnswerStructureV11({ + assistantReply: "Ответ", + coverageReport: { + requirements_total: 1, + requirements_covered: 1, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + groundingCheck: { + status: "grounded", + route_subject_match: true, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + }, + retrievalResults: [ + buildRetrieval({ + evidence: [ + { + evidence_id: "ev-1", + claim_ref: "requirement:R1", + source_type: "retrieval_item", + source_ref: { + schema_version: "evidence_source_ref_v1", + namespace: "snapshot_2020", + entity: "document", + id: "doc-1", + period: "2020-07", + canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-07" + }, + pointer: { + fragment_id: "F1", + route: "hybrid_store_plus_live", + source: { + namespace: "snapshot_2020", + entity: "document", + id: "doc-1", + period: "2020-07" + }, + locator: { + field_path: "amount", + item_index: 0 + } + }, + evidence_kind: "mechanism_link", + mechanism_note: "trace confirmed", + confidence: "high", + limitation: null, + payload: {} + } + ] + }) + ], + options: { + enableEvidenceEnrichment: true + } + }); + + expect(Array.isArray(structure.evidence_block.claim_evidence_links)).toBe(true); + expect(structure.evidence_block.claim_evidence_links?.[0]?.claim_ref).toBe("requirement:R1"); + expect(structure.evidence_block.claim_evidence_links?.[0]?.evidence_ids).toContain("ev-1"); + }); + + it("omits claim-evidence links when enrichment is disabled", () => { + const structure = buildAssistantAnswerStructureV11({ + assistantReply: "Ответ", + coverageReport: { + requirements_total: 1, + requirements_covered: 1, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + groundingCheck: { + status: "grounded", + route_subject_match: true, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + }, + retrievalResults: [ + buildRetrieval({ + evidence: [ + { + evidence_id: "ev-1", + claim_ref: "requirement:R1", + source_type: "retrieval_item", + source_ref: { + schema_version: "evidence_source_ref_v1", + namespace: "snapshot_2020", + entity: "document", + id: "doc-1", + period: "2020-07", + canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-07" + }, + pointer: { + fragment_id: "F1", + route: "hybrid_store_plus_live", + source: { + namespace: "snapshot_2020", + entity: "document", + id: "doc-1", + period: "2020-07" + }, + locator: { + field_path: "amount", + item_index: 0 + } + }, + evidence_kind: "mechanism_link", + mechanism_note: "trace confirmed", + confidence: "high", + limitation: null, + payload: {} + } + ] + }) + ], + options: { + enableEvidenceEnrichment: false + } + }); + + expect(structure.evidence_block.claim_evidence_links).toBeUndefined(); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantContractsBundleAssembler.test.ts b/llm_normalizer/backend/tests/assistantContractsBundleAssembler.test.ts new file mode 100644 index 0000000..d40d302 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantContractsBundleAssembler.test.ts @@ -0,0 +1,123 @@ +import { describe, expect, it } from "vitest"; +import type { AssistantRequirement, UnifiedRetrievalResult } from "../src/types/assistant"; +import { buildAssistantEvidenceBundleContractV1 } from "../src/services/assistantOrchestrationContracts"; +import { assembleAssistantContractsBundleV1 } from "../src/services/assistantContractsBundleAssembler"; + +function buildRequirement(): AssistantRequirement { + return { + requirement_id: "R1", + source_fragment_id: "F1", + requirement_text: "req1", + subject_tokens: ["account_60.01"], + status: "covered", + route: "hybrid_store_plus_live" + }; +} + +function buildRetrieval(input?: Partial): UnifiedRetrievalResult { + return { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + status: "ok", + result_type: "summary", + items: [], + summary: {}, + evidence: [], + why_included: [], + selection_reason: [], + risk_factors: [], + business_interpretation: [], + confidence: "medium", + limitations: [], + errors: [], + ...input + }; +} + +describe("assistant contracts bundle assembler", () => { + it("assembles query/execution/coverage contracts with outcome class", () => { + const retrievalResults = [buildRetrieval({ status: "ok" })]; + const bundle = assembleAssistantContractsBundleV1({ + userMessage: "проверь хвосты по 60.01", + normalizedQuestion: "проверь хвосты по 60.01", + normalized: { + schema_version: "normalized_query_v2_0_2", + user_message_raw: "проверь хвосты по 60.01", + message_in_scope: true, + scope_confidence: "high", + contains_multiple_tasks: false, + fragments: [{ fragment_id: "F1" }], + discarded_fragments: [], + global_notes: { + needs_clarification: false, + clarification_reason: null + } + } as any, + routeSummary: { + mode: "deterministic_v2", + message_in_scope: true, + scope_confidence: "high", + planner: { + total_fragments: 1, + in_scope_fragments: 1, + out_of_scope_fragments: 0, + discarded_fragments: 0, + contains_multiple_tasks: false + }, + decisions: [], + fallback: { + type: "none", + message: null + } + }, + droppedIntentSegments: [], + analysisContext: { + as_of_date: "2020-07-31", + period_from: null, + period_to: null, + source: "eval_analysis_date", + snapshot_mode: "auto" + }, + executionPlan: [ + { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + should_execute: true, + no_route_reason: null, + clarification_reason: null + } + ], + requirements: [buildRequirement()], + evidenceBundleContractV1: buildAssistantEvidenceBundleContractV1({ + retrievalCalls: [{ route: "hybrid_store_plus_live" }], + retrievalResults + }), + replyType: "factual_with_explanation", + coverageReport: { + requirements_total: 1, + requirements_covered: 1, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + grounding: { + status: "grounded", + route_subject_match: true, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + }, + retrievalResults + }); + + expect(bundle.queryFrameContractV1.schema_version).toBe("assistant_query_frame_v1"); + expect(bundle.executionPlanContractV1.schema_version).toBe("assistant_execution_plan_v1"); + expect(bundle.coverageContractV1.schema_version).toBe("assistant_coverage_contract_v1"); + expect(bundle.outcomeClassV1).toBe("FULLY_ANSWERED"); + expect(bundle.assistantOrchestrationContractsV1.evidence_bundle.schema_version).toBe("assistant_evidence_bundle_v1"); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantCoverageGrounding.test.ts b/llm_normalizer/backend/tests/assistantCoverageGrounding.test.ts new file mode 100644 index 0000000..0225cb3 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantCoverageGrounding.test.ts @@ -0,0 +1,199 @@ +import { describe, expect, it } from "vitest"; +import { + checkGroundingForRequirements, + evaluateCoverageForRequirements, + extractRequirementsForRoute +} from "../src/services/assistantCoverageGrounding"; + +describe("assistant coverage-grounding module", () => { + it("extracts requirements from deterministic route summary", () => { + const extracted = extractRequirementsForRoute({ + routeSummary: { + mode: "deterministic_v2", + message_in_scope: true, + scope_confidence: "high", + planner: { + total_fragments: 2, + in_scope_fragments: 2, + out_of_scope_fragments: 0, + discarded_fragments: 0, + contains_multiple_tasks: false + }, + decisions: [ + { + fragment_id: "F1", + route: "no_route", + no_route_reason: "insufficient_specificity", + reason: "missing anchor" + }, + { + fragment_id: "F2", + route: "hybrid_store_plus_live", + reason: "ok route" + } + ], + fallback: { + type: "none", + message: null + } + } as any, + userMessage: "base question", + fragmentTextById: new Map([ + ["F1", "need more details"], + ["F2", "check account 60"] + ]), + extractSubjectTokens: (text) => (text.includes("60") ? ["account_60"] : ["counterparty"]) + }); + + expect(extracted.requirements).toHaveLength(2); + expect(extracted.requirements[0].status).toBe("clarification_needed"); + expect(extracted.requirements[0].route).toBeNull(); + expect(extracted.requirements[1].status).toBe("covered"); + expect(extracted.requirements[1].route).toBe("hybrid_store_plus_live"); + expect(extracted.byFragment.get("F2")).toEqual(["R2"]); + }); + + it("evaluates coverage from retrieval outcomes", () => { + const requirements = [ + { + requirement_id: "R1", + source_fragment_id: "F1", + requirement_text: "req1", + subject_tokens: ["account_60"], + status: "covered" as const, + route: "hybrid_store_plus_live" + }, + { + requirement_id: "R2", + source_fragment_id: "F2", + requirement_text: "req2", + subject_tokens: ["counterparty"], + status: "covered" as const, + route: "store_feature_risk" + } + ]; + const retrievalResults = [ + { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + status: "ok", + result_type: "summary", + items: [], + summary: {}, + evidence: [ + { + evidence_id: "ev-1", + claim_ref: "requirement:R1", + source_type: "retrieval_item", + source_ref: { + schema_version: "evidence_source_ref_v1", + namespace: "snapshot_2020", + entity: "document", + id: "doc-1", + period: "2020-07", + canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-07" + }, + pointer: { + fragment_id: "F1", + route: "hybrid_store_plus_live", + source: { + namespace: "snapshot_2020", + entity: "document", + id: "doc-1", + period: "2020-07" + }, + locator: { + field_path: "amount", + item_index: 0 + } + }, + evidence_kind: "mechanism_link", + mechanism_note: "ok", + confidence: "high", + limitation: null, + payload: {} + } + ], + why_included: ["why"], + selection_reason: ["sel"], + risk_factors: [], + business_interpretation: [], + confidence: "high", + limitations: [], + errors: [] + }, + { + fragment_id: "F2", + requirement_ids: ["R2"], + route: "store_feature_risk", + status: "empty", + result_type: "summary", + items: [], + summary: {}, + evidence: [], + why_included: [], + selection_reason: [], + risk_factors: [], + business_interpretation: [], + confidence: "low", + limitations: [], + errors: [] + } + ] as any; + + const evaluation = evaluateCoverageForRequirements(requirements as any, retrievalResults); + expect(evaluation.coverage.requirements_total).toBe(2); + expect(evaluation.coverage.requirements_covered).toBe(1); + expect(evaluation.coverage.requirements_uncovered).toContain("R2"); + expect(evaluation.requirements.find((item) => item.requirement_id === "R1")?.status).toBe("covered"); + }); + + it("produces route mismatch grounding when critical subject token is absent", () => { + const grounded = checkGroundingForRequirements({ + userMessage: "Проверь НДС цепочку", + requirements: [ + { + requirement_id: "R1", + source_fragment_id: "F1", + requirement_text: "vat chain", + subject_tokens: ["nds"], + status: "covered", + route: "hybrid_store_plus_live" + } + ] as any, + coverage: { + requirements_total: 1, + requirements_covered: 1, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + retrievalResults: [ + { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + status: "ok", + result_type: "summary", + items: [], + summary: { note: "no tax markers" }, + evidence: [], + why_included: [], + selection_reason: [], + risk_factors: [], + business_interpretation: [], + confidence: "medium", + limitations: [], + errors: [] + } + ] as any, + extractSubjectTokens: () => ["nds"] + }); + + expect(grounded.status).toBe("route_mismatch_blocked"); + expect(grounded.route_subject_match).toBe(false); + expect(grounded.reasons.some((item) => item.includes("Ключевые ориентиры вопроса"))).toBe(true); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDebugPayloadAssembler.test.ts b/llm_normalizer/backend/tests/assistantDebugPayloadAssembler.test.ts new file mode 100644 index 0000000..2c296c6 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDebugPayloadAssembler.test.ts @@ -0,0 +1,130 @@ +import { describe, expect, it } from "vitest"; +import { buildDeepAnalysisDebugPayload } from "../src/services/assistantDebugPayloadAssembler"; + +function baseInput() { + return { + traceId: "trace-1", + promptVersion: "normalizer_v2_0_2", + schemaVersion: "normalized_query_v2_0_2", + fallbackType: "none", + routeSummary: { mode: "deterministic_v2" }, + fragments: [{ fragment_id: "F1" }], + requirementsExtracted: [{ requirement_id: "R1", status: "covered" }], + coverageReport: { requirements_total: 1, requirements_covered: 1 }, + routes: [{ fragment_id: "F1", route: "hybrid_store_plus_live" }], + retrievalStatus: [ + { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + status: "ok", + result_type: "summary" + } + ], + retrievalResults: [{ fragment_id: "F1", status: "ok" }], + groundingCheck: { status: "grounded" }, + droppedIntentSegments: [], + questionTypeClass: "factual_lookup", + companyAnchors: { companies: ["demo"] }, + runtimeAnalysisContext: { + active: true, + as_of_date: "2020-07-31", + period_from: null, + period_to: null, + source: "eval_analysis_date", + snapshot_mode: "auto" as const + }, + businessScopeResolution: { + business_scope_raw: ["company_specific_accounting"], + business_scope_resolved: ["company_specific_accounting"], + company_grounding_applied: true, + scope_resolution_reason: ["resolved"] + }, + temporalGuard: { + raw_time_anchor: "2020-07", + raw_time_scope: "month", + resolved_time_anchor: "2020-07", + resolved_primary_period: { from: "2020-07-01", to: "2020-07-31", granularity: "day" }, + effective_primary_period: { from: "2020-07-01", to: "2020-07-31", granularity: "day" }, + temporal_guard_input: "2020-07", + temporal_alignment_status: "aligned", + temporal_resolution_source: "analysis_context", + temporal_guard_basis: "analysis_context", + temporal_guard_applied: true, + temporal_guard_outcome: "pass" + }, + polarityAudit: { + raw_numeric_tokens: ["60.01"], + classified_numeric_tokens: [{ token: "60.01" }], + rejected_as_non_accounts: [], + resolved_account_anchors: ["60.01"] + }, + claimAnchorAudit: { + settlement_role: "supplier", + settlement_role_resolution_reason: ["account_60_detected"], + polarity_resolution_status: "resolved" + }, + targetedEvidenceAudit: { targeted_evidence_hit_rate: 1 }, + evidenceAdmissibilityGateAudit: { admissible_evidence_count: 1 }, + rbpLiveRouteAudit: null, + faLiveRouteAudit: null, + groundedAnswerEligibilityGuard: { eligibility_time_basis: "analysis_context", eligible: true }, + followupStateUsage: null, + compositionDebug: { + problem_centric_answer_applied: true, + problem_units_used_count: 2, + problem_answer_mode: "stage3_lifecycle_aware_v1", + problem_unit_ids_used: ["pu-1", "pu-2"] + }, + addressRuntimeMetaForDeep: { + attempted: true, + applied: true, + reason: "ok", + provider: "openai", + fallbackRuleHit: null, + toolGateDecision: "run_address_lane", + toolGateReason: "detected", + predecomposeContract: { schema_version: "x" }, + orchestrationContract: { schema_version: "y" } + }, + outcomeClassV1: "FULLY_ANSWERED", + assistantOrchestrationContractsV1: { query_frame: {}, execution_plan: {}, evidence_bundle: {}, coverage: {} }, + answerStructureV11: { schema_version: "answer_structure_v1_1" }, + investigationStateSnapshot: { status: "active" }, + normalizedPayload: { schema_version: "normalized_query_v2_0_2" } + }; +} + +describe("assistant debug payload assembler", () => { + it("builds deep debug payload with analysis context and optional sections", () => { + const payload = buildDeepAnalysisDebugPayload(baseInput()); + + expect(payload.trace_id).toBe("trace-1"); + expect(payload.analysis_context_applied).toBe(true); + expect(payload.analysis_context).toMatchObject({ + as_of_date: "2020-07-31", + source: "eval_analysis_date" + }); + expect(payload.problem_unit_ids_used).toEqual(["pu-1", "pu-2"]); + expect(payload.address_llm_predecompose_applied).toBe(true); + expect(payload.assistant_outcome_class_v1).toBe("FULLY_ANSWERED"); + }); + + it("omits optional fields when they are not provided", () => { + const input = baseInput(); + input.runtimeAnalysisContext.active = false; + input.followupStateUsage = null; + input.compositionDebug.problem_unit_ids_used = []; + input.rbpLiveRouteAudit = null; + input.faLiveRouteAudit = null; + input.addressRuntimeMetaForDeep = null; + + const payload = buildDeepAnalysisDebugPayload(input); + + expect(payload.analysis_context).toBeNull(); + expect(Object.prototype.hasOwnProperty.call(payload, "followup_state_usage")).toBe(false); + expect(Object.prototype.hasOwnProperty.call(payload, "problem_unit_ids_used")).toBe(false); + expect(payload.address_llm_predecompose_applied).toBe(false); + expect(payload.address_llm_predecompose_contract).toBeNull(); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepResponseAssembler.test.ts b/llm_normalizer/backend/tests/assistantDeepResponseAssembler.test.ts new file mode 100644 index 0000000..88de5d4 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepResponseAssembler.test.ts @@ -0,0 +1,131 @@ +import { describe, expect, it } from "vitest"; +import { buildAssistantConversationItem, buildDeepAnswerArtifacts } from "../src/services/assistantDeepResponseAssembler"; + +describe("assistant deep response assembler", () => { + it("strips technical tail and builds fallback answer structure when missing in composition", () => { + const artifacts = buildDeepAnswerArtifacts({ + safeAssistantReplyBase: "Короткий ответ\n\ndebug_payload_json: {\"x\":1}", + featureContractsV11: true, + featureAnswerPolicyV11: true, + compositionAnswerStructureV11: null, + coverageReport: { + requirements_total: 1, + requirements_covered: 1, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + groundingCheck: { + status: "grounded", + route_subject_match: true, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + }, + retrievalResults: [] + }); + + expect(artifacts.safeAssistantReply).toBe("Короткий ответ"); + expect(artifacts.answerStructureV11?.schema_version).toBe("answer_structure_v1_1"); + }); + + it("uses provided composition answer structure and creates assistant conversation item", () => { + const provided = { + schema_version: "answer_structure_v1_1", + answer_summary: "sum", + direct_answer: "direct", + mechanism_block: { + status: "grounded" as const, + mechanism_notes: [], + limitation_reason_codes: [] + }, + evidence_block: { + evidence_ids: [], + mechanism_notes: [], + coverage_note: "ok" + }, + uncertainty_block: { + open_uncertainties: [], + limitations: [] + }, + next_step_block: { + recommended_actions: [], + clarification_questions: [] + } + }; + + const artifacts = buildDeepAnswerArtifacts({ + safeAssistantReplyBase: "Готово", + featureContractsV11: true, + featureAnswerPolicyV11: true, + compositionAnswerStructureV11: provided as any, + coverageReport: { + requirements_total: 1, + requirements_covered: 1, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + groundingCheck: { + status: "grounded", + route_subject_match: true, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + }, + retrievalResults: [] + }); + + expect(artifacts.answerStructureV11).toEqual(provided); + + const item = buildAssistantConversationItem({ + messageId: "msg-1", + sessionId: "asst-1", + text: artifacts.safeAssistantReply, + replyType: "factual", + traceId: "trace-1", + debug: { + trace_id: "trace-1", + prompt_version: "normalizer_v2_0_2", + schema_version: "normalized_query_v2_0_2", + fallback_type: "none", + route_summary: null, + fragments: [], + requirements_extracted: [], + coverage_report: { + requirements_total: 0, + requirements_covered: 0, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + routes: [], + retrieval_status: [], + retrieval_results: [], + answer_grounding_check: { + status: "no_grounded_answer", + route_subject_match: false, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + }, + dropped_intent_segments: [], + answer_structure_v11: null, + investigation_state_snapshot: null, + normalized: null + } as any + }); + + expect(item.message_id).toBe("msg-1"); + expect(item.session_id).toBe("asst-1"); + expect(item.reply_type).toBe("factual"); + expect(item.text).toBe("Готово"); + expect(typeof item.created_at).toBe("string"); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepTurnCompositionRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnCompositionRuntimeAdapter.test.ts new file mode 100644 index 0000000..e74766f --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnCompositionRuntimeAdapter.test.ts @@ -0,0 +1,126 @@ +import { describe, expect, it } from "vitest"; +import { buildAssistantDeepTurnComposition } from "../src/services/assistantDeepTurnCompositionRuntimeAdapter"; + +describe("assistant deep turn composition runtime adapter", () => { + it("uses followup domain hint and company-anchor period signal", () => { + let capturedInput: Record | null = null; + const output = buildAssistantDeepTurnComposition({ + userMessage: "проверь хвосты по 60.01", + routeSummary: null, + retrievalResults: [], + requirements: [], + coverageReport: { + requirements_total: 0, + requirements_covered: 0, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + groundingCheck: { + status: "no_grounded_answer", + route_subject_match: false, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + }, + followupUsage: { applied: true }, + investigationState: { + schema_version: "investigation_state_v1", + session_id: "asst-1", + status: "active", + turn_index: 1, + updated_at: "2026-04-10T10:00:00.000Z", + question_id: "msg-1", + question_scope_id: null, + scope_origin: null, + focus: { + domain: "settlements_60_62", + period: null, + primary_accounts: [], + active_query_subject: null + }, + narrowing_status: "unknown", + evidence_refs: [], + open_uncertainties: [], + last_answer_mode: null, + followup_context: null, + query_mode_hint: "direct_answer" + } as any, + companyAnchors: { + periods: ["2020-07"], + dates: [] + }, + normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any, + featureAnswerPolicyV11: true, + featureProblemCentricAnswerV1: true, + featureLifecycleAnswerV1: true, + hasExplicitPeriodAnchor: () => false, + resolveQuestionTypeFn: () => "factual_lookup", + composeAssistantAnswerFn: ((input: Record) => { + capturedInput = input; + return { + assistant_reply: "ok", + fallback_type: "none", + reply_type: "factual" + }; + }) as any + }); + + expect(output.focusDomainHint).toBe("settlements_60_62"); + expect(output.questionTypeClass).toBe("factual_lookup"); + expect(output.hasPeriodInCompanyAnchors).toBe(true); + expect(output.normalizationPeriodExplicit).toBe(true); + expect(output.composition.reply_type).toBe("factual"); + expect(capturedInput?.focusDomainHint).toBe("settlements_60_62"); + expect(capturedInput?.normalizationPeriodExplicit).toBe(true); + }); + + it("falls back to explicit period from normalized payload when anchors are absent", () => { + const output = buildAssistantDeepTurnComposition({ + userMessage: "проверь закрытие", + routeSummary: null, + retrievalResults: [], + requirements: [], + coverageReport: { + requirements_total: 0, + requirements_covered: 0, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + groundingCheck: { + status: "no_grounded_answer", + route_subject_match: false, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + }, + followupUsage: { applied: false }, + investigationState: null, + companyAnchors: { + periods: [], + dates: [] + }, + normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any, + featureAnswerPolicyV11: true, + featureProblemCentricAnswerV1: true, + featureLifecycleAnswerV1: true, + hasExplicitPeriodAnchor: () => true, + resolveQuestionTypeFn: () => "verification", + composeAssistantAnswerFn: (() => ({ + assistant_reply: "ok", + fallback_type: "none", + reply_type: "factual" + })) as any + }); + + expect(output.focusDomainHint).toBeNull(); + expect(output.questionTypeClass).toBe("verification"); + expect(output.hasPeriodInCompanyAnchors).toBe(false); + expect(output.normalizationPeriodExplicit).toBe(true); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepTurnContextRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnContextRuntimeAdapter.test.ts new file mode 100644 index 0000000..68d49ad --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnContextRuntimeAdapter.test.ts @@ -0,0 +1,106 @@ +import { describe, expect, it } from "vitest"; +import { buildAssistantDeepTurnRuntimeContext } from "../src/services/assistantDeepTurnContextRuntimeAdapter"; + +describe("assistant deep turn context runtime adapter", () => { + it("assembles context in deterministic order and propagates followup flag", () => { + const callOrder: string[] = []; + const companyAnchors = { accounts: ["60.01"] }; + const temporalGuard = { + effective_primary_period: { from: "2020-07-01", to: "2020-07-31" }, + primary_period_window: { from: "2020-07-01", to: "2020-07-31" } + }; + const businessScope = { route_summary_resolved: { mode: "deterministic_v2", decisions: [] as any[] } } as any; + + const output = buildAssistantDeepTurnRuntimeContext({ + userMessage: "почему не закрыт 60.01", + normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any, + routeSummary: { mode: "deterministic_v2", decisions: [] } as any, + runtimeAnalysisContext: { + active: true, + as_of_date: "2020-07-31", + period_from: null, + period_to: null, + source: "analysis_context" + }, + followupUsage: { applied: true }, + resolveCompanyAnchors: () => { + callOrder.push("anchors"); + return companyAnchors; + }, + resolveBusinessScopeAlignment: () => { + callOrder.push("scope_align"); + return businessScope; + }, + inferP0DomainFromMessage: () => { + callOrder.push("infer_domain"); + return "settlements_60_62"; + }, + resolveTemporalGuard: (input) => { + callOrder.push("temporal"); + expect(input.analysisContext).toEqual({ + as_of_date: "2020-07-31", + period_from: null, + period_to: null, + source: "analysis_context" + }); + return temporalGuard as any; + }, + resolveDomainPolarityGuard: (input) => { + callOrder.push("polarity"); + expect(input.focusDomainHint).toBe("settlements_60_62"); + return { polarity: "supplier_payable" }; + }, + resolveClaimBoundAnchors: (input) => { + callOrder.push("claim"); + expect(input.primaryPeriod).toEqual(temporalGuard.effective_primary_period); + return { claim_type: "prove_settlement_closure_state" } as any; + }, + resolveBusinessScopeFromLiveContext: (input) => { + callOrder.push("scope_live"); + expect(input.followupApplied).toBe(true); + return { + ...businessScope, + live_scope_used: true + } as any; + } + }); + + expect(callOrder).toEqual(["anchors", "scope_align", "infer_domain", "temporal", "polarity", "claim", "scope_live"]); + expect(output.companyAnchors).toBe(companyAnchors); + expect(output.focusDomainForGuards).toBe("settlements_60_62"); + expect(output.claimAnchorAudit.claim_type).toBe("prove_settlement_closure_state"); + expect(output.liveTemporalHint).toEqual({ + as_of_date: "2020-07-31", + period_from: null, + period_to: null, + source: "analysis_context" + }); + }); + + it("drops unknown inferred domain and disables live temporal hint when context is inactive", () => { + const output = buildAssistantDeepTurnRuntimeContext({ + userMessage: "какой-нибудь вопрос", + normalizedPayload: null as any, + routeSummary: null, + runtimeAnalysisContext: { + active: false, + as_of_date: null, + period_from: null, + period_to: null, + source: null + }, + followupUsage: null, + resolveCompanyAnchors: () => ({}), + resolveBusinessScopeAlignment: () => ({ route_summary_resolved: null }), + inferP0DomainFromMessage: () => "unknown_domain", + resolveTemporalGuard: () => ({ primary_period_window: null }), + resolveDomainPolarityGuard: () => ({ polarity: "not_applicable" }), + resolveClaimBoundAnchors: () => ({ claim_type: "unknown" } as any), + resolveBusinessScopeFromLiveContext: () => ({ route_summary_resolved: null }) + }); + + expect(output.focusDomainForGuards).toBeNull(); + expect(output.resolvedRouteSummary).toBeNull(); + expect(output.liveTemporalHint).toBeNull(); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepTurnGroundingRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnGroundingRuntimeAdapter.test.ts new file mode 100644 index 0000000..233a76c --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnGroundingRuntimeAdapter.test.ts @@ -0,0 +1,142 @@ +import { describe, expect, it } from "vitest"; +import { runAssistantDeepTurnGroundingRuntime } from "../src/services/assistantDeepTurnGroundingRuntimeAdapter"; + +describe("assistant deep turn grounding runtime adapter", () => { + it("runs audits, coverage-grounding pipeline and eligibility overlay in stable order", () => { + const callOrder: string[] = []; + const retrievalResults = [{ fragment_id: "F1" }] as any[]; + const coverageEvaluation = { + requirements: [{ requirement_id: "R1" }], + coverage: { + requirements_total: 1, + requirements_covered: 1, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + } + } as any; + const groundingCheckBase = { + status: "grounded_positive", + reasons: [], + route_subject_match: true, + missing_requirements: [], + why_included_summary: [], + selection_reason_summary: [] + } as any; + + const output = runAssistantDeepTurnGroundingRuntime({ + claimType: "prove_settlement_closure_state", + retrievalResults, + rbpPlanAudit: { rbp: true }, + faPlanAudit: { fa: true }, + routeSummary: { mode: "deterministic_v2", decisions: [] } as any, + normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any, + userMessage: "check", + requirementExtraction: { + requirements: [{ requirement_id: "R1" }] as any, + byFragment: new Map([["F1", ["R1"]]]) + } as any, + extractRequirements: (() => { + throw new Error("should not be called when requirementExtraction is provided"); + }) as any, + evaluateCoverage: (() => { + throw new Error("should not be called directly from adapter"); + }) as any, + checkGrounding: (() => { + throw new Error("should not be called directly from adapter"); + }) as any, + temporalGuard: { temporal_guard_outcome: "passed" } as any, + polarityAudit: { outcome: "passed" } as any, + evidenceAudit: { admissible_evidence_count: 2 } as any, + claimAnchorAudit: { claim_type: "prove_settlement_closure_state" } as any, + targetedEvidenceHitRate: 0.5, + businessScopeResolved: ["company_specific_accounting"], + collectRbpLiveRouteAudit: (input) => { + callOrder.push("rbp_audit"); + expect(input.planAudit).toEqual({ rbp: true }); + return { rbp_live: 1 }; + }, + collectFaLiveRouteAudit: (input) => { + callOrder.push("fa_audit"); + expect(input.planAudit).toEqual({ fa: true }); + return { fa_live: 1 }; + }, + runCoverageGroundingPipelineFn: ((input: Record) => { + callOrder.push("coverage_pipeline"); + expect(input.retrievalResults).toBe(retrievalResults); + return { + requirementExtraction: input.requirementExtraction, + coverageEvaluation, + groundingCheckBase + }; + }) as any, + applyGroundingEligibilityFn: ((input: Record) => { + callOrder.push("eligibility"); + expect(input.groundingCheckBase).toBe(groundingCheckBase); + return { + groundedAnswerEligibilityGuard: { + eligible: true + }, + groundingCheck: groundingCheckBase + }; + }) as any + }); + + expect(callOrder).toEqual(["rbp_audit", "fa_audit", "coverage_pipeline", "eligibility"]); + expect(output.rbpLiveRouteAudit).toEqual({ rbp_live: 1 }); + expect(output.faLiveRouteAudit).toEqual({ fa_live: 1 }); + expect(output.coverageEvaluation).toBe(coverageEvaluation); + expect(output.groundedAnswerEligibilityGuard).toEqual({ eligible: true }); + expect(output.groundingCheck).toBe(groundingCheckBase); + }); + + it("threads default pipeline output through without custom hooks", () => { + const output = runAssistantDeepTurnGroundingRuntime({ + claimType: "unknown", + retrievalResults: [], + rbpPlanAudit: null, + faPlanAudit: null, + routeSummary: null, + normalizedPayload: null as any, + userMessage: "q", + requirementExtraction: { + requirements: [], + byFragment: new Map() + } as any, + extractRequirements: () => ({ + requirements: [], + byFragment: new Map() + }) as any, + evaluateCoverage: () => ({ + requirements: [], + coverage: { + requirements_total: 0, + requirements_covered: 0, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + } + }), + checkGrounding: () => + ({ + status: "no_grounded_answer", + route_subject_match: false, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + }) as any, + temporalGuard: { temporal_guard_outcome: "passed", temporal_guard_basis: "none" } as any, + polarityAudit: { applied: false, outcome: "not_applicable" } as any, + evidenceAudit: { admissible_evidence_count: 0 } as any, + claimAnchorAudit: null, + collectRbpLiveRouteAudit: () => null, + collectFaLiveRouteAudit: () => null + }); + + expect(output.coverageEvaluation.requirements).toEqual([]); + expect(output.groundingCheck.status).toBe("no_grounded_answer"); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepTurnGuardRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnGuardRuntimeAdapter.test.ts new file mode 100644 index 0000000..45bf3e5 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnGuardRuntimeAdapter.test.ts @@ -0,0 +1,121 @@ +import { describe, expect, it } from "vitest"; +import { + applyAssistantDeepTurnGroundingEligibility, + applyAssistantDeepTurnRetrievalGuards +} from "../src/services/assistantDeepTurnGuardRuntimeAdapter"; + +describe("assistant deep turn guard runtime adapter", () => { + it("runs retrieval guards in expected order and threads outputs", () => { + const callOrder: string[] = []; + const seedResults = [{ fragment_id: "F1" }] as any[]; + const afterPolarity = [{ fragment_id: "P1" }] as any[]; + const afterTargeted = [{ fragment_id: "T1" }] as any[]; + const afterGate = [{ fragment_id: "G1" }] as any[]; + + const output = applyAssistantDeepTurnRetrievalGuards({ + retrievalResults: seedResults as any, + domainPolarityGuardInitial: { applied: true, polarity: "supplier_payable" } as any, + claimAnchorAudit: { claim_type: "prove_settlement_closure_state" } as any, + temporalGuard: { temporal_guard_outcome: "passed", temporal_guard_basis: "none" } as any, + focusDomainForGuards: "settlements_60_62" as any, + companyAnchors: { accounts: ["60.01"] } as any, + userMessage: "check settlements", + applyDomainPolarityGuardFn: ((input: Record) => { + callOrder.push("polarity"); + expect(input.retrievalResults).toBe(seedResults); + return { + retrievalResults: afterPolarity, + audit: { + applied: true, + polarity: "supplier_payable", + outcome: "passed", + reason_codes: [] + } + }; + }) as any, + applyTargetedEvidenceFn: ((input: Record) => { + callOrder.push("targeted"); + expect(input.retrievalResults).toBe(afterPolarity); + return { + retrievalResults: afterTargeted, + audit: { + targeted_evidence_hit_rate: 0.5, + reason_codes: [] + } + }; + }) as any, + applyEvidenceAdmissibilityGateFn: ((input: Record) => { + callOrder.push("gate"); + expect(input.retrievalResults).toBe(afterTargeted); + expect(input.polarity).toBe("supplier_payable"); + expect(input.userMessage).toBe("check settlements"); + return { + retrievalResults: afterGate, + audit: { + admissible_evidence_count: 2, + reason_codes: [] + } + }; + }) as any + }); + + expect(callOrder).toEqual(["polarity", "targeted", "gate"]); + expect(output.retrievalResults).toBe(afterGate); + expect(output.polarityGuardResult.retrievalResults).toBe(afterPolarity); + expect(output.targetedEvidenceResult.retrievalResults).toBe(afterTargeted); + expect(output.evidenceGateResult.retrievalResults).toBe(afterGate); + }); + + it("evaluates grounding eligibility and applies status overlay", () => { + const callOrder: string[] = []; + const groundingCheckBase = { + status: "grounded_positive", + reasons: ["base"], + route_subject_match: true + }; + + const output = applyAssistantDeepTurnGroundingEligibility({ + groundingCheckBase, + temporalGuard: { temporal_guard_outcome: "passed", temporal_guard_basis: "none" } as any, + polarityAudit: { applied: true, outcome: "passed", polarity: "supplier_payable" } as any, + evidenceAudit: { admissible_evidence_count: 0 } as any, + claimAnchorAudit: { claim_anchor_resolution_rate: 1, missing_anchors: [], required_anchors: [] } as any, + targetedEvidenceHitRate: 0, + businessScopeResolved: ["company_specific_accounting"], + evaluateGroundedAnswerEligibilityFn: ((input: Record) => { + callOrder.push("eligibility"); + expect(input.targetedEvidenceHitRate).toBe(0); + return { + eligible: false, + temporal_passed: true, + eligibility_time_basis: "none", + business_scope_passed: true, + polarity_passed: true, + claim_anchors_passed: true, + claim_anchor_resolution_rate: 1, + missing_required_anchors: 0, + admissible_evidence_count: 0, + critical_contradiction: false, + outcome: "limited_or_insufficient_evidence", + grounding_mode: "limited_or_insufficient_evidence", + reason_codes: ["admissible_evidence_count_zero"] + }; + }) as any, + applyEligibilityToGroundingCheckFn: ((check: Record, eligibility: Record) => { + callOrder.push("overlay"); + expect(check.status).toBe("grounded_positive"); + expect(eligibility.eligible).toBe(false); + return { + ...check, + status: "no_grounded_answer", + reasons: ["base", "not_enough_evidence"] + }; + }) as any + }); + + expect(callOrder).toEqual(["eligibility", "overlay"]); + expect(output.groundedAnswerEligibilityGuard.eligible).toBe(false); + expect(output.groundingCheck.status).toBe("no_grounded_answer"); + expect(output.groundingCheck.reasons).toEqual(["base", "not_enough_evidence"]); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepTurnInputBuilder.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnInputBuilder.test.ts new file mode 100644 index 0000000..e80bafd --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnInputBuilder.test.ts @@ -0,0 +1,140 @@ +import { describe, expect, it } from "vitest"; +import { buildAssistantDeepTurnPackagingInput, type AssistantDeepTurnInputBuilderArgs } from "../src/services/assistantDeepTurnInputBuilder"; + +function baseArgs(): AssistantDeepTurnInputBuilderArgs { + return { + sessionId: "asst-1", + messageId: "msg-1", + userMessage: "проверь 60.01", + normalized: { + trace_id: "trace-1", + prompt_version: "normalizer_v2_0_2", + schema_version: "normalized_query_v2_0_2", + normalized: { + schema_version: "normalized_query_v2_0_2", + user_message_raw: "проверь 60.01", + message_in_scope: true, + scope_confidence: "high", + contains_multiple_tasks: false, + fragments: [], + discarded_fragments: [], + global_notes: { + needs_clarification: false, + clarification_reason: null + } + } + }, + normalizedQuestion: "проверь 60.01", + routeSummary: null, + droppedIntentSegments: [], + analysisContextForContract: null, + executionPlan: [], + requirementExtractionRequirements: [], + coverageEvaluationRequirements: [], + coverageReport: { + requirements_total: 0, + requirements_covered: 0, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + groundingCheck: { + status: "no_grounded_answer", + route_subject_match: false, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + }, + retrievalCalls: [], + retrievalResultsRaw: [], + retrievalResults: [], + routesForDebug: [], + resolvedExecutionState: {}, + questionTypeClass: "factual_lookup", + companyAnchors: {}, + runtimeAnalysisContext: { + active: false, + as_of_date: null, + period_from: null, + period_to: null, + source: null, + snapshot_mode: "auto" + }, + businessScopeResolution: {}, + temporalGuard: {}, + polarityAudit: {}, + claimAnchorAudit: {}, + targetedEvidenceAudit: null, + evidenceAdmissibilityGateAudit: null, + rbpLiveRouteAudit: null, + faLiveRouteAudit: null, + groundedAnswerEligibilityGuard: {}, + followupStateUsage: undefined, + composition: { + reply_type: "factual", + fallback_type: "none" + }, + safeAssistantReplyBase: "ok", + featureContractsV11: true, + featureAnswerPolicyV11: true, + investigationStateSnapshot: null, + addressRuntimeMetaForDeep: null + }; +} + +describe("assistant deep turn input builder", () => { + it("applies stable defaults for optional composition and followup fields", () => { + const built = buildAssistantDeepTurnPackagingInput(baseArgs()); + + expect(built.followupStateUsage).toBeNull(); + expect(built.composition.answer_structure_v11).toBeNull(); + expect(built.composition.problem_centric_answer_applied).toBe(false); + expect(built.composition.problem_units_used_count).toBe(0); + expect(built.composition.problem_answer_mode).toBe("stage1_policy_v11"); + expect(built.composition.problem_unit_ids_used).toEqual([]); + }); + + it("preserves explicit composition fields and normalizes unit ids array", () => { + const args = baseArgs(); + args.followupStateUsage = { applied: true }; + args.composition.answer_structure_v11 = { + schema_version: "answer_structure_v1_1", + answer_summary: "sum", + direct_answer: "direct", + mechanism_block: { + status: "grounded", + mechanism_notes: [], + limitation_reason_codes: [] + }, + evidence_block: { + evidence_ids: [], + source_refs: [], + mechanism_notes: [], + coverage_note: "ok" + }, + uncertainty_block: { + open_uncertainties: [], + limitations: [] + }, + next_step_block: { + recommended_actions: [], + clarification_questions: [] + } + } as any; + args.composition.problem_centric_answer_applied = true; + args.composition.problem_units_used_count = 3; + args.composition.problem_answer_mode = "stage3_lifecycle_aware_v1"; + args.composition.problem_unit_ids_used = ["pu-1", "pu-2"]; + + const built = buildAssistantDeepTurnPackagingInput(args); + + expect(built.followupStateUsage).toEqual({ applied: true }); + expect(built.composition.answer_structure_v11?.schema_version).toBe("answer_structure_v1_1"); + expect(built.composition.problem_centric_answer_applied).toBe(true); + expect(built.composition.problem_units_used_count).toBe(3); + expect(built.composition.problem_answer_mode).toBe("stage3_lifecycle_aware_v1"); + expect(built.composition.problem_unit_ids_used).toEqual(["pu-1", "pu-2"]); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepTurnPackaging.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnPackaging.test.ts new file mode 100644 index 0000000..680ee51 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnPackaging.test.ts @@ -0,0 +1,179 @@ +import { describe, expect, it } from "vitest"; +import { assembleAssistantDeepTurnPackaging } from "../src/services/assistantDeepTurnPackaging"; + +function buildRetrieval() { + return { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + status: "ok", + result_type: "summary", + items: [], + summary: {}, + evidence: [], + why_included: [], + selection_reason: [], + risk_factors: [], + business_interpretation: [], + confidence: "medium", + limitations: [], + errors: [] + }; +} + +function baseInput() { + return { + sessionId: "asst-1", + messageId: "msg-1", + userMessage: "проверь хвосты по 60.01", + normalized: { + trace_id: "trace-1", + prompt_version: "normalizer_v2_0_2", + schema_version: "normalized_query_v2_0_2", + normalized: { + schema_version: "normalized_query_v2_0_2", + user_message_raw: "проверь хвосты по 60.01", + message_in_scope: true, + scope_confidence: "high", + contains_multiple_tasks: false, + fragments: [{ fragment_id: "F1" }], + discarded_fragments: [], + global_notes: { + needs_clarification: false, + clarification_reason: null + } + } + }, + normalizedQuestion: "проверь хвосты по 60.01", + routeSummary: { + mode: "deterministic_v2", + message_in_scope: true, + scope_confidence: "high", + planner: { + total_fragments: 1, + in_scope_fragments: 1, + out_of_scope_fragments: 0, + discarded_fragments: 0, + contains_multiple_tasks: false + }, + decisions: [], + fallback: { + type: "none", + message: null + } + }, + droppedIntentSegments: [], + analysisContextForContract: { + as_of_date: "2020-07-31", + period_from: null, + period_to: null, + source: "eval_analysis_date", + snapshot_mode: "auto" as const + }, + executionPlan: [ + { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + should_execute: true, + no_route_reason: null, + clarification_reason: null + } + ], + requirementExtractionRequirements: [ + { + requirement_id: "R1", + source_fragment_id: "F1", + requirement_text: "проверить хвосты 60.01", + subject_tokens: ["account_60.01"], + status: "covered", + route: "hybrid_store_plus_live" + } + ], + coverageEvaluationRequirements: [ + { + requirement_id: "R1", + source_fragment_id: "F1", + requirement_text: "проверить хвосты 60.01", + subject_tokens: ["account_60.01"], + status: "covered", + route: "hybrid_store_plus_live" + } + ], + coverageReport: { + requirements_total: 1, + requirements_covered: 1, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + groundingCheck: { + status: "grounded", + route_subject_match: true, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + }, + retrievalCalls: [{ route: "hybrid_store_plus_live" }], + retrievalResultsRaw: [buildRetrieval()], + retrievalResults: [buildRetrieval()], + routesForDebug: [{ fragment_id: "F1", route: "hybrid_store_plus_live" }], + resolvedExecutionState: { executable: 1 }, + questionTypeClass: "factual_lookup", + companyAnchors: { companies: ["demo"] }, + runtimeAnalysisContext: { + active: true, + as_of_date: "2020-07-31", + period_from: null, + period_to: null, + source: "eval_analysis_date", + snapshot_mode: "auto" as const + }, + businessScopeResolution: { + business_scope_raw: ["company_specific_accounting"], + business_scope_resolved: ["company_specific_accounting"], + company_grounding_applied: true, + scope_resolution_reason: ["resolved"] + }, + temporalGuard: { temporal_guard_applied: true }, + polarityAudit: { resolved_account_anchors: ["60.01"] }, + claimAnchorAudit: { settlement_role: "supplier" }, + targetedEvidenceAudit: { targeted_evidence_hit_rate: 1 }, + evidenceAdmissibilityGateAudit: { admissible_evidence_count: 1 }, + rbpLiveRouteAudit: null, + faLiveRouteAudit: null, + groundedAnswerEligibilityGuard: { eligible: true }, + followupStateUsage: null, + composition: { + reply_type: "factual" as const, + fallback_type: "none", + answer_structure_v11: null, + problem_centric_answer_applied: true, + problem_units_used_count: 1, + problem_answer_mode: "stage3_lifecycle_aware_v1", + problem_unit_ids_used: ["pu-1"] + }, + safeAssistantReplyBase: "Короткий ответ\n\ndebug_payload_json: {\"x\":1}", + featureContractsV11: true, + featureAnswerPolicyV11: true, + investigationStateSnapshot: { status: "active" }, + addressRuntimeMetaForDeep: null + }; +} + +describe("assistant deep turn packaging", () => { + it("assembles deep artifacts, debug payload and processed log in one call", () => { + const input = baseInput(); + const output = assembleAssistantDeepTurnPackaging(input as any); + + expect(output.deepAnswerArtifacts.safeAssistantReply).toBe("Короткий ответ"); + expect(output.contractsBundleV1.outcomeClassV1).toBe("FULLY_ANSWERED"); + expect(output.debug.trace_id).toBe("trace-1"); + expect(output.assistantItem.message_id).toBe("msg-1"); + expect(output.assistantItem.text).toBe("Короткий ответ"); + expect(output.deepAnalysisLogDetails.session_id).toBe("asst-1"); + expect(output.deepAnalysisLogDetails.message_id).toBe("msg-1"); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepTurnPackagingRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnPackagingRuntimeAdapter.test.ts new file mode 100644 index 0000000..25d774c --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnPackagingRuntimeAdapter.test.ts @@ -0,0 +1,184 @@ +import { describe, expect, it } from "vitest"; +import { runAssistantDeepTurnPackagingRuntime } from "../src/services/assistantDeepTurnPackagingRuntimeAdapter"; + +function buildBaseInput() { + return { + featureInvestigationStateV1: true, + sessionId: "asst-1", + questionId: "msg-user-1", + userMessage: "проверь кейс", + normalized: { + trace_id: "trace-1", + prompt_version: "p1", + schema_version: "s1", + normalized: { schema_version: "normalized_query_v2_0_2" } as any + }, + normalizedQuestion: "проверь кейс", + routeSummary: null, + executionPlan: [], + requirementExtractionRequirements: [], + coverageEvaluationRequirements: [], + coverageReport: { + requirements_total: 0, + requirements_covered: 0, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + groundingCheck: { + status: "no_grounded_answer", + route_subject_match: false, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + }, + retrievalCalls: [], + retrievalResultsRaw: [], + retrievalResults: [], + questionTypeClass: "factual_lookup", + companyAnchors: {}, + runtimeAnalysisContext: { + active: false, + as_of_date: null, + period_from: null, + period_to: null, + source: null, + snapshot_mode: "auto" as const + }, + businessScopeResolution: {}, + temporalGuard: {}, + polarityAudit: {}, + claimAnchorAudit: {}, + targetedEvidenceAudit: {}, + evidenceAdmissibilityGateAudit: {}, + rbpLiveRouteAudit: null, + faLiveRouteAudit: null, + groundedAnswerEligibilityGuard: {}, + followupStateUsage: null, + followupApplied: false, + composition: { + assistant_reply: "raw-reply", + reply_type: "factual" as const, + fallback_type: "none" + }, + featureContractsV11: true, + featureAnswerPolicyV11: true, + previousInvestigationState: null, + addressRuntimeMetaForDeep: null, + extractDroppedIntentSegments: () => [], + buildDebugRoutes: () => [], + extractExecutionState: () => null, + sanitizeReply: (value: string) => value, + persistInvestigationState: () => {}, + messageIdFactory: () => "msg-fixed" + }; +} + +describe("assistant deep turn packaging runtime adapter", () => { + it("executes pre-packaging, snapshot, persist, input-build and assembly in stable order", () => { + const callOrder: string[] = []; + let persistedByCallback = 0; + + const output = runAssistantDeepTurnPackagingRuntime({ + ...buildBaseInput(), + persistInvestigationState: () => { + persistedByCallback += 1; + }, + buildPrePackagingContextFn: (() => { + callOrder.push("pre"); + return { + droppedIntentSegments: ["F2_dropped"], + analysisContextForContract: null, + routesForDebug: [{ fragment_id: "F1" }], + resolvedExecutionState: [{ fragment_id: "F1", execution_readiness: "ready" }], + safeAssistantReplyBase: "safe-base" + }; + }) as any, + buildInvestigationStateSnapshotFn: (() => { + callOrder.push("snapshot"); + return { schema_version: "investigation_state_v1" }; + }) as any, + persistInvestigationStateSnapshotFn: ((input: Record) => { + callOrder.push("persist"); + (input.persist as Function)(input.sessionId, input.snapshot); + return true; + }) as any, + buildDeepTurnPackagingInputFn: ((input: Record) => { + callOrder.push("input"); + expect(input.messageId).toBe("msg-fixed"); + expect(input.droppedIntentSegments).toEqual(["F2_dropped"]); + expect(input.safeAssistantReplyBase).toBe("safe-base"); + return input; + }) as any, + assembleDeepTurnPackagingFn: (() => { + callOrder.push("assemble"); + return { + deepAnswerArtifacts: { + safeAssistantReply: "assistant-safe" + }, + debug: { ok: true }, + assistantItem: { + message_id: "msg-fixed", + session_id: "asst-1", + role: "assistant", + text: "assistant-safe", + reply_type: "factual", + created_at: "2026-04-10T10:00:00.000Z", + trace_id: "trace-1", + debug: null + }, + deepAnalysisLogDetails: { stage: "deep_analysis" } + }; + }) as any + }); + + expect(callOrder).toEqual(["pre", "snapshot", "persist", "input", "assemble"]); + expect(persistedByCallback).toBe(1); + expect(output.messageId).toBe("msg-fixed"); + expect(output.safeAssistantReply).toBe("assistant-safe"); + expect(output.debug).toEqual({ ok: true }); + expect(output.deepAnalysisLogDetails).toEqual({ stage: "deep_analysis" }); + }); + + it("does not persist investigation snapshot when feature is disabled", () => { + let persistedByCallback = 0; + + const output = runAssistantDeepTurnPackagingRuntime({ + ...buildBaseInput(), + featureInvestigationStateV1: false, + persistInvestigationState: () => { + persistedByCallback += 1; + }, + buildPrePackagingContextFn: (() => ({ + droppedIntentSegments: [], + analysisContextForContract: null, + routesForDebug: [], + resolvedExecutionState: null, + safeAssistantReplyBase: "safe-base" + })) as any, + buildDeepTurnPackagingInputFn: ((input: Record) => input) as any, + assembleDeepTurnPackagingFn: (() => ({ + deepAnswerArtifacts: { + safeAssistantReply: "assistant-safe" + }, + debug: {}, + assistantItem: { + message_id: "msg-fixed", + session_id: "asst-1", + role: "assistant", + text: "assistant-safe", + reply_type: "factual", + created_at: "2026-04-10T10:00:00.000Z", + trace_id: "trace-1", + debug: null + }, + deepAnalysisLogDetails: {} + })) as any + }); + + expect(output.investigationStateSnapshot).toBeNull(); + expect(persistedByCallback).toBe(0); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepTurnPlanRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnPlanRuntimeAdapter.test.ts new file mode 100644 index 0000000..3fe9b94 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnPlanRuntimeAdapter.test.ts @@ -0,0 +1,96 @@ +import { describe, expect, it } from "vitest"; +import { buildAssistantDeepTurnExecutionPlan } from "../src/services/assistantDeepTurnPlanRuntimeAdapter"; + +describe("assistant deep turn plan runtime adapter", () => { + it("builds execution plan through extraction, enforcement and guard hints in stable order", () => { + const callOrder: string[] = []; + const byFragment = new Map([["F1", ["R1"]]]); + const planInitial = [{ fragment_id: "F1", route: "store_canonical" }] as any[]; + const planAfterRbp = [{ fragment_id: "F1", route: "hybrid_store_plus_live" }] as any[]; + const planAfterFa = [{ fragment_id: "F1", route: "hybrid_store_plus_live", fa: true }] as any[]; + const planAfterTemporal = [{ fragment_id: "F1", route: "hybrid_store_plus_live", temporal: true }] as any[]; + const planAfterPolarity = [{ fragment_id: "F1", route: "hybrid_store_plus_live", temporal: true, polarity: true }] as any[]; + + const output = buildAssistantDeepTurnExecutionPlan({ + routeSummary: { mode: "deterministic_v2", decisions: [] } as any, + normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any, + userMessage: "check tails", + claimType: "prove_settlement_closure_state", + temporalGuard: { temporal_guard_outcome: "passed" } as any, + domainPolarityGuardInitial: { polarity: "supplier_payable" } as any, + extractRequirements: () => { + callOrder.push("extract"); + return { + requirements: [{ requirement_id: "R1" }] as any[], + byFragment + }; + }, + toExecutionPlan: (_routeSummary, _normalizedPayload, _userMessage, requirementByFragment) => { + callOrder.push("plan"); + expect(requirementByFragment).toBe(byFragment); + return planInitial as any; + }, + enforceRbpLiveRoutePlan: ({ executionPlan }) => { + callOrder.push("rbp"); + expect(executionPlan).toBe(planInitial); + return { + executionPlan: planAfterRbp as any, + audit: { rbp: true } + }; + }, + enforceFaLiveRoutePlan: ({ executionPlan }) => { + callOrder.push("fa"); + expect(executionPlan).toBe(planAfterRbp); + return { + executionPlan: planAfterFa as any, + audit: { fa: true } + }; + }, + applyTemporalHintToExecutionPlan: (executionPlan) => { + callOrder.push("temporal"); + expect(executionPlan).toBe(planAfterFa); + return planAfterTemporal as any; + }, + applyPolarityHintToExecutionPlan: (executionPlan) => { + callOrder.push("polarity"); + expect(executionPlan).toBe(planAfterTemporal); + return planAfterPolarity as any; + } + }); + + expect(callOrder).toEqual(["extract", "plan", "rbp", "fa", "temporal", "polarity"]); + expect(output.requirementExtraction.byFragment).toBe(byFragment); + expect(output.executionPlan).toBe(planAfterPolarity); + expect(output.rbpRoutePlanEnforcement.audit).toEqual({ rbp: true }); + expect(output.faRoutePlanEnforcement.audit).toEqual({ fa: true }); + }); + + it("preserves empty execution plan end-to-end", () => { + const output = buildAssistantDeepTurnExecutionPlan({ + routeSummary: null, + normalizedPayload: null as any, + userMessage: "noop", + claimType: "unknown", + temporalGuard: null, + domainPolarityGuardInitial: null, + extractRequirements: () => ({ + requirements: [], + byFragment: new Map() + }), + toExecutionPlan: () => [], + enforceRbpLiveRoutePlan: ({ executionPlan }) => ({ + executionPlan, + audit: { rbp: false } + }), + enforceFaLiveRoutePlan: ({ executionPlan }) => ({ + executionPlan, + audit: { fa: false } + }), + applyTemporalHintToExecutionPlan: (executionPlan) => executionPlan, + applyPolarityHintToExecutionPlan: (executionPlan) => executionPlan + }); + + expect(output.executionPlan).toEqual([]); + expect(output.requirementExtraction.requirements).toEqual([]); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepTurnPrePackagingContext.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnPrePackagingContext.test.ts new file mode 100644 index 0000000..e5f017d --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnPrePackagingContext.test.ts @@ -0,0 +1,60 @@ +import { describe, expect, it } from "vitest"; +import { buildAssistantDeepTurnPrePackagingContext } from "../src/services/assistantDeepTurnPrePackagingContext"; + +describe("assistant deep turn pre-packaging context", () => { + it("builds all pre-packaging fields with active analysis context", () => { + const output = buildAssistantDeepTurnPrePackagingContext({ + normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any, + routeSummary: null, + runtimeAnalysisContext: { + active: true, + as_of_date: "2020-07-31", + period_from: null, + period_to: null, + source: "eval_analysis_date", + snapshot_mode: "auto" + }, + assistantReply: "raw", + extractDroppedIntentSegments: () => ["segment_1"], + buildDebugRoutes: () => [{ fragment_id: "F1", route: "hybrid_store_plus_live" }], + extractExecutionState: () => ({ executable: 1 }), + sanitizeReply: (value, fallback) => `${value}::${fallback}` + }); + + expect(output.droppedIntentSegments).toEqual(["segment_1"]); + expect(output.analysisContextForContract).toEqual({ + as_of_date: "2020-07-31", + period_from: null, + period_to: null, + source: "eval_analysis_date", + snapshot_mode: "auto" + }); + expect(output.routesForDebug).toEqual([{ fragment_id: "F1", route: "hybrid_store_plus_live" }]); + expect(output.resolvedExecutionState).toEqual({ executable: 1 }); + expect(output.safeAssistantReplyBase).toContain("Нужны уточнения для надежного ответа."); + }); + + it("returns null analysis context when runtime context is inactive", () => { + const output = buildAssistantDeepTurnPrePackagingContext({ + normalizedPayload: { schema_version: "normalized_query_v2_0_2" } as any, + routeSummary: null, + runtimeAnalysisContext: { + active: false, + as_of_date: "2020-07-31", + period_from: "2020-07-01", + period_to: "2020-07-31", + source: "eval_analysis_date", + snapshot_mode: "auto" + }, + assistantReply: "ok", + extractDroppedIntentSegments: () => [], + buildDebugRoutes: () => [], + extractExecutionState: () => null, + sanitizeReply: (value) => value + }); + + expect(output.analysisContextForContract).toBeNull(); + expect(output.routesForDebug).toEqual([]); + expect(output.safeAssistantReplyBase).toBe("ok"); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepTurnResponseBuilder.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnResponseBuilder.test.ts new file mode 100644 index 0000000..8d90ccd --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnResponseBuilder.test.ts @@ -0,0 +1,69 @@ +import { describe, expect, it } from "vitest"; +import { buildAssistantDeepTurnSuccessResponse } from "../src/services/assistantDeepTurnResponseBuilder"; +import type { AssistantConversationItem } from "../src/types/assistant"; + +function buildAssistantItem(): AssistantConversationItem { + return { + message_id: "msg-1", + session_id: "asst-1", + role: "assistant", + text: "ok", + reply_type: "factual", + created_at: "2026-04-10T10:00:00.000Z", + trace_id: "trace-1", + debug: null + }; +} + +describe("assistant deep turn response builder", () => { + it("builds canonical assistant message response envelope", () => { + const assistantItem = buildAssistantItem(); + const response = buildAssistantDeepTurnSuccessResponse({ + sessionId: "asst-1", + assistantReply: "ответ", + replyType: "factual", + conversationItem: assistantItem, + debug: { + trace_id: "trace-1", + prompt_version: "normalizer_v2_0_2", + schema_version: "normalized_query_v2_0_2", + fallback_type: "none", + route_summary: null, + fragments: [], + requirements_extracted: [], + coverage_report: { + requirements_total: 0, + requirements_covered: 0, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + routes: [], + retrieval_status: [], + retrieval_results: [], + answer_grounding_check: { + status: "no_grounded_answer", + route_subject_match: false, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + }, + dropped_intent_segments: [], + answer_structure_v11: null, + investigation_state_snapshot: null, + normalized: null + } as any, + conversation: [assistantItem] + }); + + expect(response.ok).toBe(true); + expect(response.session_id).toBe("asst-1"); + expect(response.assistant_reply).toBe("ответ"); + expect(response.reply_type).toBe("factual"); + expect(response.conversation_item.message_id).toBe("msg-1"); + expect(response.conversation).toEqual([assistantItem]); + expect(response.debug.trace_id).toBe("trace-1"); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantDeepTurnRetrievalRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantDeepTurnRetrievalRuntimeAdapter.test.ts new file mode 100644 index 0000000..4a8e324 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantDeepTurnRetrievalRuntimeAdapter.test.ts @@ -0,0 +1,158 @@ +import { describe, expect, it } from "vitest"; +import type { AssistantExecutionPlanItem } from "../src/services/assistantQueryPlanning"; +import { executeAssistantDeepTurnRetrievalPlan } from "../src/services/assistantDeepTurnRetrievalRuntimeAdapter"; + +describe("assistant deep turn retrieval runtime adapter", () => { + it("handles skipped, executed and failed plan items with stable call records", async () => { + const executionPlan: AssistantExecutionPlanItem[] = [ + { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "no_route", + should_execute: false, + fragment_text: "clarify period", + no_route_reason: "insufficient_specificity", + clarification_reason: "domain_or_scope_unclear" + }, + { + fragment_id: "F2", + requirement_ids: ["R2"], + route: "store_canonical", + should_execute: true, + fragment_text: "show balances", + no_route_reason: null, + clarification_reason: null + }, + { + fragment_id: "F3", + requirement_ids: ["R3"], + route: "live_mcp_drilldown", + should_execute: true, + fragment_text: "tail check", + no_route_reason: null, + clarification_reason: null + } + ]; + + const normalizeCalls: Array<{ fragmentId: string; route: string; rawStatus: string | null }> = []; + + const output = await executeAssistantDeepTurnRetrievalPlan({ + executionPlan, + liveTemporalHint: null, + executeRouteRuntime: async (route) => { + if (route === "live_mcp_drilldown") { + throw new Error("route failed"); + } + return { + status: "ok", + result_type: "summary", + items: [], + summary: { route }, + evidence: [], + why_included: [], + selection_reason: [], + risk_factors: [], + business_interpretation: [], + confidence: "high", + limitations: [], + errors: [] + }; + }, + mapNoRouteReason: (reason) => (reason === "insufficient_specificity" ? "Needs clarification." : "No-route decision."), + buildSkippedResult: () => + ({ + fragment_id: "F1", + route: "no_route", + status: "partial" + }) as any, + normalizeRetrievalResultFn: ((fragmentId: string, _requirementIds: string[], route: string, raw: Record) => { + normalizeCalls.push({ + fragmentId, + route, + rawStatus: typeof raw.status === "string" ? raw.status : null + }); + return { + fragment_id: fragmentId, + route, + status: raw.status + } as any; + }) as any + }); + + expect(output.retrievalCalls).toEqual([ + { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "no_route", + status: "skipped", + query_text: "clarify period", + reason: "Needs clarification." + }, + { + fragment_id: "F2", + requirement_ids: ["R2"], + route: "store_canonical", + status: "executed", + query_text: "show balances", + reason: null + }, + { + fragment_id: "F3", + requirement_ids: ["R3"], + route: "live_mcp_drilldown", + status: "failed", + query_text: "tail check", + reason: "route failed" + } + ]); + expect(output.retrievalResultsRaw).toHaveLength(2); + expect((output.retrievalResultsRaw[0].raw_result as Record).status).toBe("ok"); + expect((output.retrievalResultsRaw[1].raw_result as Record).status).toBe("error"); + expect((output.retrievalResultsRaw[1].raw_result as Record).limitations).toEqual(["Route executor failed."]); + expect(output.retrievalResults).toHaveLength(3); + expect(output.retrievalResults[0].status).toBe("partial"); + expect(normalizeCalls).toEqual([ + { fragmentId: "F2", route: "store_canonical", rawStatus: "ok" }, + { fragmentId: "F3", route: "live_mcp_drilldown", rawStatus: "error" } + ]); + }); + + it("passes live temporal hint into route runtime execution", async () => { + const executionPlan: AssistantExecutionPlanItem[] = [ + { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + should_execute: true, + fragment_text: "check as of date", + no_route_reason: null, + clarification_reason: null + } + ]; + let capturedTemporalHint: Record | null = null; + + await executeAssistantDeepTurnRetrievalPlan({ + executionPlan, + liveTemporalHint: { + as_of_date: "2020-07-31", + period_from: null, + period_to: null, + source: "analysis_context" + }, + executeRouteRuntime: async (_route, _fragmentText, options) => { + capturedTemporalHint = options.temporalHint as unknown as Record; + return { status: "ok" }; + }, + mapNoRouteReason: () => "No-route decision.", + buildSkippedResult: (() => ({ status: "partial" })) as any, + normalizeRetrievalResultFn: (() => ({ status: "ok" })) as any + }); + + expect(capturedTemporalHint).toEqual({ + as_of_date: "2020-07-31", + period_from: null, + period_to: null, + source: "analysis_context" + }); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantEvidenceBundleAssembler.test.ts b/llm_normalizer/backend/tests/assistantEvidenceBundleAssembler.test.ts new file mode 100644 index 0000000..2d26e4f --- /dev/null +++ b/llm_normalizer/backend/tests/assistantEvidenceBundleAssembler.test.ts @@ -0,0 +1,105 @@ +import { describe, expect, it } from "vitest"; +import type { UnifiedRetrievalResult } from "../src/types/assistant"; +import { assembleAssistantEvidenceBundle } from "../src/services/assistantEvidenceBundleAssembler"; + +function buildRetrieval(input?: Partial): UnifiedRetrievalResult { + return { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + status: "ok", + result_type: "summary", + items: [], + summary: {}, + evidence: [], + why_included: [], + selection_reason: [], + risk_factors: [], + business_interpretation: [], + confidence: "medium", + limitations: [], + errors: [], + ...input + }; +} + +describe("assistant evidence bundle assembler", () => { + it("builds evidence contract and retrieval status from the same retrieval set", () => { + const assembled = assembleAssistantEvidenceBundle({ + retrievalCalls: [{ route: "hybrid_store_plus_live" }, { route: "store_canonical" }], + retrievalResults: [ + buildRetrieval({ + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + status: "ok", + evidence: [ + { + evidence_id: "ev-1", + claim_ref: "requirement:R1", + source_type: "retrieval_item", + source_ref: { + schema_version: "evidence_source_ref_v1", + namespace: "snapshot_2020", + entity: "document", + id: "doc-1", + period: "2020-07", + canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-07" + }, + pointer: { + fragment_id: "F1", + route: "hybrid_store_plus_live", + source: { + namespace: "snapshot_2020", + entity: "document", + id: "doc-1", + period: "2020-07" + }, + locator: { + field_path: "amount", + item_index: 0 + } + }, + evidence_kind: "mechanism_link", + mechanism_note: "signal", + confidence: "medium", + limitation: null, + payload: {} + } + ] + }), + buildRetrieval({ + fragment_id: "F2", + requirement_ids: ["R2"], + route: "store_canonical", + status: "error", + result_type: "list", + errors: ["timeout"] + }) + ] + }); + + expect(assembled.evidenceBundleContractV1.retrieval_calls_total).toBe(2); + expect(assembled.evidenceBundleContractV1.retrieval_results_total).toBe(2); + expect(assembled.evidenceBundleContractV1.retrieval_status_breakdown.ok).toBe(1); + expect(assembled.evidenceBundleContractV1.retrieval_status_breakdown.error).toBe(1); + expect(assembled.evidenceBundleContractV1.evidence_total).toBe(1); + expect(assembled.evidenceBundleContractV1.source_refs_total).toBe(1); + expect(assembled.retrievalStatus).toEqual([ + { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + status: "ok", + result_type: "summary" + }, + { + fragment_id: "F2", + requirement_ids: ["R2"], + route: "store_canonical", + status: "error", + result_type: "list" + } + ]); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantInvestigationStateRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantInvestigationStateRuntimeAdapter.test.ts new file mode 100644 index 0000000..f877c98 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantInvestigationStateRuntimeAdapter.test.ts @@ -0,0 +1,158 @@ +import { describe, expect, it } from "vitest"; +import type { RouteHintSummary } from "../src/types/normalizer"; +import type { UnifiedRetrievalResult } from "../src/types/assistant"; +import { createEmptyInvestigationState } from "../src/services/investigationState"; +import { + buildAssistantInvestigationStateSnapshot, + persistAssistantInvestigationStateSnapshot +} from "../src/services/assistantInvestigationStateRuntimeAdapter"; + +function buildRouteSummary(): RouteHintSummary { + return { + mode: "deterministic_v2", + message_in_scope: true, + scope_confidence: "high", + planner: { + total_fragments: 1, + in_scope_fragments: 1, + out_of_scope_fragments: 0, + discarded_fragments: 0, + contains_multiple_tasks: false + }, + decisions: [ + { + fragment_id: "F1", + domain_relevance: "in_scope", + business_scope: "company_specific_accounting", + candidate_labels: ["anomaly_probe"], + decision_flags: { + has_multi_entity_scope: false, + asks_for_chain_explanation: false, + asks_for_ranking_or_top: false, + asks_for_period_summary: false, + asks_for_rule_check: true, + asks_for_anomaly_scan: true, + asks_for_exact_object_trace: false, + asks_for_evidence: true, + mentions_period_close_context: false + }, + route: "store_feature_risk", + reason: "test-route" + } + ], + fallback: { + type: "none", + message: null + } + }; +} + +function buildRetrievalResult(): UnifiedRetrievalResult { + return { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "store_feature_risk", + status: "ok", + result_type: "summary", + items: [], + summary: {}, + evidence: [], + why_included: [], + selection_reason: [], + risk_factors: [], + business_interpretation: [], + confidence: "medium", + limitations: [], + errors: [] + }; +} + +describe("assistant investigation state runtime adapter", () => { + it("returns null and skips persist when feature disabled", () => { + const snapshot = buildAssistantInvestigationStateSnapshot({ + featureEnabled: false, + previousState: createEmptyInvestigationState("asst-1", "2026-04-10T10:00:00.000Z"), + timestamp: "2026-04-10T10:01:00.000Z", + questionId: "msg-1", + userMessage: "проверь 60.01", + routeSummary: buildRouteSummary(), + requirements: [], + coverageReport: { + requirements_total: 0, + requirements_covered: 0, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + retrievalResults: [], + replyType: "factual", + followupApplied: false + }); + expect(snapshot).toBeNull(); + + let persistCalled = false; + const persisted = persistAssistantInvestigationStateSnapshot({ + featureEnabled: false, + sessionId: "asst-1", + snapshot: null, + persist: () => { + persistCalled = true; + } + }); + expect(persisted).toBe(false); + expect(persistCalled).toBe(false); + }); + + it("builds snapshot and persists it when feature enabled", () => { + const previous = createEmptyInvestigationState("asst-2", "2026-04-10T10:00:00.000Z"); + const snapshot = buildAssistantInvestigationStateSnapshot({ + featureEnabled: true, + previousState: previous, + timestamp: "2026-04-10T10:01:00.000Z", + questionId: "msg-2", + userMessage: "проверь счет 60.01 за 2020-07", + routeSummary: buildRouteSummary(), + requirements: [ + { + requirement_id: "R1", + source_fragment_id: "F1", + requirement_text: "проверить счет 60.01", + subject_tokens: ["account_60.01"], + status: "covered", + route: "store_feature_risk" + } + ], + coverageReport: { + requirements_total: 1, + requirements_covered: 1, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + retrievalResults: [buildRetrievalResult()], + replyType: "factual", + followupApplied: false + }); + + expect(snapshot).not.toBeNull(); + expect(snapshot?.turn_index).toBe(1); + expect(snapshot?.question_id).toBe("msg-2"); + + let persistedSessionId: string | null = null; + let persistedQuestionId: string | null = null; + const persisted = persistAssistantInvestigationStateSnapshot({ + featureEnabled: true, + sessionId: "asst-2", + snapshot: snapshot, + persist: (sessionId, state) => { + persistedSessionId = sessionId; + persistedQuestionId = state.question_id; + } + }); + expect(persisted).toBe(true); + expect(persistedSessionId).toBe("asst-2"); + expect(persistedQuestionId).toBe("msg-2"); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantLivingRouter.test.ts b/llm_normalizer/backend/tests/assistantLivingRouter.test.ts index e55d0f6..97b2ff9 100644 --- a/llm_normalizer/backend/tests/assistantLivingRouter.test.ts +++ b/llm_normalizer/backend/tests/assistantLivingRouter.test.ts @@ -209,6 +209,33 @@ describe("assistant orchestration contract", () => { expect(decision.livingReason).toBe("address_lane_triggered"); }); + it("keeps explicit address-mode unknown-intent data query in address lane", () => { + const decision = resolveAssistantOrchestrationDecision({ + rawUserMessage: + "Покажи контрагентов, по которым сальдо скорее всего не совпадет с их актом сверки. Может, стоит поторопиться и запросить сверку?", + effectiveAddressUserMessage: + "Показать контрагентов с вероятным несогласием между сальдо и актом сверки. Рекомендовать запросить сверку.", + followupContext: null, + llmPreDecomposeMeta: { + applied: true, + llmCanonicalCandidateDetected: true, + predecomposeContract: { + mode: "address_query", + mode_confidence: "high", + intent: "unknown", + intent_confidence: "low" + } + } as any, + useMock: false + }); + + expect(decision.runAddressLane).toBe(true); + expect(decision.toolGateDecision).toBe("run_address_lane"); + expect(decision.livingMode).toBe("address_data"); + expect(decision.livingReason).toBe("address_lane_triggered"); + expect(decision.orchestrationContract?.unsupported_address_intent_fallback_to_deep).toBe(false); + }); + it("does not force address lane for deep-analysis unknown intent query with date-like token", () => { const decision = resolveAssistantOrchestrationDecision({ rawUserMessage: "найди какие либо ошибки на 21 мая 2022 года", diff --git a/llm_normalizer/backend/tests/assistantMessageLogAssembler.test.ts b/llm_normalizer/backend/tests/assistantMessageLogAssembler.test.ts new file mode 100644 index 0000000..7a904c5 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantMessageLogAssembler.test.ts @@ -0,0 +1,123 @@ +import { describe, expect, it } from "vitest"; +import { buildDeepAnalysisProcessedLogDetails } from "../src/services/assistantMessageLogAssembler"; + +function baseInput() { + return { + sessionId: "asst-1", + messageId: "msg-1", + userMessage: "проверь 60.01", + normalizerOutput: { schema_version: "normalized_query_v2_0_2" }, + executionPlan: [{ fragment_id: "F1", route: "hybrid_store_plus_live", should_execute: true }], + resolvedExecutionState: { executable: 1 }, + routes: [{ fragment_id: "F1", route: "hybrid_store_plus_live" }], + retrievalCalls: [{ route: "hybrid_store_plus_live" }], + retrievalResultsRaw: [{ status: "ok" }], + retrievalResultsNormalized: [{ status: "ok" }], + requirementsExtracted: [{ requirement_id: "R1" }], + coverageReport: { + requirements_total: 1, + requirements_covered: 1, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }, + groundingCheck: { + status: "grounded", + route_subject_match: true, + missing_requirements: [], + reasons: [], + why_included_summary: ["signal"], + selection_reason_summary: ["ranked"] + }, + replyType: "factual", + droppedIntentSegments: [], + questionTypeClass: "factual_lookup", + companyAnchors: { companies: ["demo"] }, + runtimeAnalysisContext: { + active: true, + as_of_date: "2020-07-31", + period_from: null, + period_to: null, + source: "eval_analysis_date", + snapshot_mode: "auto" as const + }, + businessScopeResolution: { + business_scope_raw: ["company_specific_accounting"], + business_scope_resolved: ["company_specific_accounting"], + company_grounding_applied: true, + scope_resolution_reason: ["resolved"] + }, + temporalGuard: { + raw_time_anchor: "2020-07", + raw_time_scope: "month", + resolved_time_anchor: "2020-07", + resolved_primary_period: { from: "2020-07-01", to: "2020-07-31", granularity: "day" }, + effective_primary_period: { from: "2020-07-01", to: "2020-07-31", granularity: "day" }, + temporal_guard_input: "2020-07", + temporal_alignment_status: "aligned", + temporal_resolution_source: "analysis_context", + temporal_guard_basis: "analysis_context", + temporal_guard_applied: true, + temporal_guard_outcome: "pass" + }, + polarityAudit: { + raw_numeric_tokens: ["60.01"], + classified_numeric_tokens: [{ token: "60.01" }], + rejected_as_non_accounts: [], + resolved_account_anchors: ["60.01"] + }, + claimAnchorAudit: { + settlement_role: "supplier", + settlement_role_resolution_reason: ["account_60_detected"], + polarity_resolution_status: "resolved" + }, + targetedEvidenceAudit: { targeted_evidence_hit_rate: 1 }, + evidenceAdmissibilityGateAudit: { admissible_evidence_count: 1 }, + rbpLiveRouteAudit: null, + faLiveRouteAudit: null, + groundedAnswerEligibilityGuard: { eligibility_time_basis: "analysis_context", eligible: true }, + followupStateUsage: null, + compositionDebug: { + problem_centric_answer_applied: true, + problem_units_used_count: 1, + problem_answer_mode: "stage3_lifecycle_aware_v1", + problem_unit_ids_used: ["pu-1"], + fallback_type: "none" + }, + outcomeClassV1: "FULLY_ANSWERED", + assistantOrchestrationContractsV1: { query_frame: {}, execution_plan: {}, evidence_bundle: {}, coverage: {} }, + answerStructureV11: { schema_version: "answer_structure_v1_1" }, + investigationStateSnapshot: { status: "active" }, + assistantReply: "ok", + traceId: "trace-1" + }; +} + +describe("assistant message log assembler", () => { + it("builds deep analysis log details and resolves full coverage status", () => { + const details = buildDeepAnalysisProcessedLogDetails(baseInput()); + expect(details.session_id).toBe("asst-1"); + expect(details.coverage_status).toBe("full"); + expect(details.analysis_context).toMatchObject({ + as_of_date: "2020-07-31" + }); + expect(details.problem_unit_ids_used).toEqual(["pu-1"]); + expect(details.reply_type).toBe("factual"); + }); + + it("marks partial coverage and omits optional sections when empty", () => { + const input = baseInput(); + input.coverageReport.requirements_covered = 0; + input.coverageReport.requirements_uncovered = ["R1"]; + input.runtimeAnalysisContext.active = false; + input.followupStateUsage = null; + input.compositionDebug.problem_unit_ids_used = []; + + const details = buildDeepAnalysisProcessedLogDetails(input); + expect(details.coverage_status).toBe("partial_or_limited"); + expect(details.analysis_context).toBeNull(); + expect(Object.prototype.hasOwnProperty.call(details, "followup_state_usage")).toBe(false); + expect(Object.prototype.hasOwnProperty.call(details, "problem_unit_ids_used")).toBe(false); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantOrchestrationContracts.test.ts b/llm_normalizer/backend/tests/assistantOrchestrationContracts.test.ts new file mode 100644 index 0000000..2692ef7 --- /dev/null +++ b/llm_normalizer/backend/tests/assistantOrchestrationContracts.test.ts @@ -0,0 +1,304 @@ +import { describe, expect, it } from "vitest"; +import type { AnswerGroundingCheck, RequirementCoverageReport, UnifiedRetrievalResult } from "../src/types/assistant"; +import type { RouteHintSummary } from "../src/types/normalizer"; +import { + buildAssistantCoverageContractV1, + buildAssistantEvidenceBundleContractV1, + buildAssistantExecutionPlanContractV1, + buildAssistantQueryFrameContractV1, + classifyAssistantOutcomeClassV1 +} from "../src/services/assistantOrchestrationContracts"; + +function buildCoverage(input?: Partial): RequirementCoverageReport { + return { + requirements_total: 1, + requirements_covered: 0, + requirements_uncovered: ["R1"], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [], + ...input + }; +} + +function buildGrounding(input?: Partial): AnswerGroundingCheck { + return { + status: "no_grounded_answer", + route_subject_match: true, + missing_requirements: ["R1"], + reasons: [], + why_included_summary: [], + selection_reason_summary: [], + ...input + }; +} + +function buildRetrieval(input?: Partial): UnifiedRetrievalResult { + return { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + status: "ok", + result_type: "summary", + items: [], + summary: {}, + evidence: [], + why_included: [], + selection_reason: [], + risk_factors: [], + business_interpretation: [], + confidence: "medium", + limitations: [], + errors: [], + ...input + }; +} + +function buildRouteSummary(): RouteHintSummary { + return { + mode: "deterministic_v2", + message_in_scope: true, + scope_confidence: "high", + planner: { + total_fragments: 2, + in_scope_fragments: 2, + out_of_scope_fragments: 0, + discarded_fragments: 0, + contains_multiple_tasks: false + }, + decisions: [], + fallback: { + type: "none", + message: null + } + }; +} + +describe("assistant orchestration contracts v1", () => { + it("builds query frame and execution plan contracts with normalized analysis context", () => { + const queryFrame = buildAssistantQueryFrameContractV1({ + userMessage: "Покажи хвосты по счету 60", + normalizedQuestion: "Покажи хвосты по счету 60", + normalized: { + schema_version: "normalized_query_v2_0_2", + user_message_raw: "Покажи хвосты по счету 60", + message_in_scope: true, + scope_confidence: "high", + contains_multiple_tasks: false, + fragments: [{ fragment_id: "F1" }, { fragment_id: "F2" }], + discarded_fragments: [], + global_notes: { + needs_clarification: false, + clarification_reason: null + } + } as any, + routeSummary: buildRouteSummary(), + droppedIntentSegments: ["лишний сегмент"], + analysisContext: { + as_of_date: "2020-07-31", + source: "eval_analysis_date", + snapshot_mode: "unexpected_mode" + } + }); + + const executionPlan = buildAssistantExecutionPlanContractV1({ + executionPlan: [ + { + fragment_id: "F1", + requirement_ids: ["R1"], + route: "hybrid_store_plus_live", + should_execute: true, + no_route_reason: null, + clarification_reason: null + }, + { + fragment_id: "F2", + requirement_ids: ["R2"], + route: "no_route", + should_execute: false, + no_route_reason: "insufficient_specificity", + clarification_reason: "need_period" + } + ], + requirements: [ + { + requirement_id: "R1", + source_fragment_id: "F1", + requirement_text: "req1", + subject_tokens: [], + status: "covered", + route: "hybrid_store_plus_live" + }, + { + requirement_id: "R2", + source_fragment_id: "F2", + requirement_text: "req2", + subject_tokens: [], + status: "clarification_needed", + route: null + } + ] + }); + + expect(queryFrame.schema_version).toBe("assistant_query_frame_v1"); + expect(queryFrame.route_summary_mode).toBe("deterministic_v2"); + expect(queryFrame.fragments_total).toBe(2); + expect(queryFrame.analysis_context?.as_of_date).toBe("2020-07-31"); + expect(queryFrame.analysis_context?.snapshot_mode).toBe("auto"); + expect(executionPlan.schema_version).toBe("assistant_execution_plan_v1"); + expect(executionPlan.steps).toHaveLength(2); + expect(executionPlan.requirements_total).toBe(2); + }); + + it("classifies fully answered and misrouted outcomes", () => { + const fullyAnswered = classifyAssistantOutcomeClassV1({ + replyType: "factual_with_explanation", + coverageReport: buildCoverage({ + requirements_total: 1, + requirements_covered: 1, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + }), + grounding: buildGrounding({ + status: "grounded", + missing_requirements: [] + }), + retrievalResults: [buildRetrieval({ status: "ok" })] + }); + + const misrouted = classifyAssistantOutcomeClassV1({ + replyType: "route_mismatch_blocked", + coverageReport: buildCoverage(), + grounding: buildGrounding({ + status: "route_mismatch_blocked" + }), + retrievalResults: [buildRetrieval({ status: "partial" })] + }); + + expect(fullyAnswered).toBe("FULLY_ANSWERED"); + expect(misrouted).toBe("MISROUTED"); + }); + + it("classifies tooling and entity-binding failures", () => { + const toolingBlocked = classifyAssistantOutcomeClassV1({ + replyType: "factual", + coverageReport: buildCoverage(), + grounding: buildGrounding(), + retrievalResults: [buildRetrieval({ status: "error" }), buildRetrieval({ status: "error" })] + }); + + const entityBindingFailure = classifyAssistantOutcomeClassV1({ + replyType: "no_grounded_answer", + coverageReport: buildCoverage({ + requirements_total: 1, + requirements_covered: 0, + requirements_uncovered: ["R1"] + }), + grounding: buildGrounding({ + status: "no_grounded_answer", + route_subject_match: true, + missing_requirements: ["R1"] + }), + retrievalResults: [buildRetrieval({ status: "empty" })] + }); + + expect(toolingBlocked).toBe("BLOCKED_BY_TOOLING"); + expect(entityBindingFailure).toBe("FAILED_TO_BIND_ENTITIES"); + }); + + it("builds evidence bundle and coverage contracts", () => { + const evidenceBundle = buildAssistantEvidenceBundleContractV1({ + retrievalCalls: [{ id: 1 }, { id: 2 }, { id: 3 }], + retrievalResults: [ + buildRetrieval({ + status: "ok", + evidence: [ + { + evidence_id: "ev-1", + claim_ref: "requirement:R1", + source_type: "retrieval_item", + source_ref: { + schema_version: "evidence_source_ref_v1", + namespace: "snapshot_2020", + entity: "document", + id: "doc-1", + period: "2020-07", + canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-07" + }, + pointer: { + fragment_id: "F1", + route: "hybrid_store_plus_live", + source: { + namespace: "snapshot_2020", + entity: "document", + id: "doc-1", + period: "2020-07" + }, + locator: { + field_path: "amount", + item_index: 0 + } + }, + evidence_kind: "mechanism_link", + mechanism_note: "signal", + confidence: "medium", + limitation: null, + payload: {} + } + ], + limitations: ["needs_extra_period"] + }), + buildRetrieval({ + status: "partial", + evidence: [], + errors: ["timeout"] + }), + buildRetrieval({ + status: "error", + evidence: [], + errors: ["mcp_unavailable"] + }) + ] + }); + + const outcomeClass = classifyAssistantOutcomeClassV1({ + replyType: "partial_coverage", + coverageReport: buildCoverage({ + requirements_total: 2, + requirements_covered: 1, + requirements_uncovered: ["R2"] + }), + grounding: buildGrounding({ + status: "partial", + route_subject_match: true, + missing_requirements: ["R2"] + }), + retrievalResults: [buildRetrieval({ status: "ok" }), buildRetrieval({ status: "partial" })] + }); + + const coverageContract = buildAssistantCoverageContractV1({ + coverageReport: buildCoverage({ + requirements_total: 2, + requirements_covered: 1, + requirements_uncovered: ["R2"] + }), + grounding: buildGrounding({ + status: "partial", + missing_requirements: ["R2"] + }), + outcomeClass + }); + + expect(evidenceBundle.retrieval_calls_total).toBe(3); + expect(evidenceBundle.retrieval_results_total).toBe(3); + expect(evidenceBundle.retrieval_status_breakdown.ok).toBe(1); + expect(evidenceBundle.retrieval_status_breakdown.partial).toBe(1); + expect(evidenceBundle.retrieval_status_breakdown.error).toBe(1); + expect(evidenceBundle.evidence_total).toBe(1); + expect(evidenceBundle.source_refs_total).toBe(1); + expect(evidenceBundle.error_total).toBe(2); + expect(coverageContract.outcome_class).toBe("PARTIALLY_ANSWERED"); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantOrchestrationRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantOrchestrationRuntimeAdapter.test.ts new file mode 100644 index 0000000..708c0db --- /dev/null +++ b/llm_normalizer/backend/tests/assistantOrchestrationRuntimeAdapter.test.ts @@ -0,0 +1,104 @@ +import { describe, expect, it, vi } from "vitest"; +import { runAssistantCoverageGroundingPipeline } from "../src/services/assistantOrchestrationRuntimeAdapter"; + +describe("assistant orchestration runtime adapter", () => { + it("runs requirement -> coverage -> grounding pipeline in order", () => { + const requirementExtraction = { + requirements: [ + { + requirement_id: "R1", + source_fragment_id: "F1", + requirement_text: "req", + subject_tokens: ["account_60"], + status: "covered" as const, + route: "hybrid_store_plus_live" + } + ], + byFragment: new Map([["F1", ["R1"]]]) + }; + const coverageEvaluation = { + requirements: requirementExtraction.requirements, + coverage: { + requirements_total: 1, + requirements_covered: 1, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + } + }; + const groundingCheck = { + status: "grounded" as const, + route_subject_match: true, + missing_requirements: [], + reasons: [], + why_included_summary: ["why"], + selection_reason_summary: ["selection"] + }; + + const extractRequirements = vi.fn(() => requirementExtraction); + const evaluateCoverage = vi.fn(() => coverageEvaluation); + const checkGrounding = vi.fn(() => groundingCheck); + + const output = runAssistantCoverageGroundingPipeline({ + routeSummary: null, + normalized: null, + userMessage: "test", + retrievalResults: [], + extractRequirements, + evaluateCoverage, + checkGrounding + }); + + expect(extractRequirements).toHaveBeenCalledTimes(1); + expect(evaluateCoverage).toHaveBeenCalledTimes(1); + expect(checkGrounding).toHaveBeenCalledTimes(1); + expect(output.requirementExtraction).toBe(requirementExtraction); + expect(output.coverageEvaluation).toBe(coverageEvaluation); + expect(output.groundingCheckBase).toBe(groundingCheck); + }); + + it("reuses precomputed requirement extraction when provided", () => { + const precomputed = { + requirements: [], + byFragment: new Map() + }; + const extractRequirements = vi.fn(() => { + throw new Error("extractRequirements should not be called"); + }); + const evaluateCoverage = vi.fn(() => ({ + requirements: [], + coverage: { + requirements_total: 0, + requirements_covered: 0, + requirements_uncovered: [], + requirements_partially_covered: [], + clarification_needed_for: [], + out_of_scope_requirements: [] + } + })); + const checkGrounding = vi.fn(() => ({ + status: "no_grounded_answer" as const, + route_subject_match: true, + missing_requirements: [], + reasons: [], + why_included_summary: [], + selection_reason_summary: [] + })); + + const output = runAssistantCoverageGroundingPipeline({ + routeSummary: null, + normalized: null, + userMessage: "test", + retrievalResults: [], + requirementExtraction: precomputed, + extractRequirements, + evaluateCoverage, + checkGrounding + }); + + expect(extractRequirements).not.toHaveBeenCalled(); + expect(evaluateCoverage).toHaveBeenCalledWith(precomputed.requirements, []); + expect(output.requirementExtraction).toBe(precomputed); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantQueryPlanning.test.ts b/llm_normalizer/backend/tests/assistantQueryPlanning.test.ts new file mode 100644 index 0000000..537bcec --- /dev/null +++ b/llm_normalizer/backend/tests/assistantQueryPlanning.test.ts @@ -0,0 +1,107 @@ +import { describe, expect, it } from "vitest"; +import { + buildDebugRoutesFromRoute, + buildExecutionPlanFromRoute, + buildFragmentTextById +} from "../src/services/assistantQueryPlanning"; + +describe("assistant query planning module", () => { + it("builds fragment text map with account hints enrichment", () => { + const map = buildFragmentTextById([ + { + fragment_id: "F1", + raw_fragment_text: "проверить хвосты", + normalized_fragment_text: "", + account_hints: ["60.01", "60.02"] + }, + { + fragment_id: "F2", + raw_fragment_text: "проверить 60.01 по поставщику", + normalized_fragment_text: "", + account_hints: ["60.01"] + } + ]); + + expect(map.get("F1")).toBe("проверить хвосты, по счету 60.01, 60.02"); + expect(map.get("F2")).toBe("проверить 60.01 по поставщику"); + }); + + it("builds deterministic execution plan from route summary", () => { + const executionPlan = buildExecutionPlanFromRoute({ + routeSummary: { + mode: "deterministic_v2", + message_in_scope: true, + scope_confidence: "high", + planner: { + total_fragments: 2, + in_scope_fragments: 2, + out_of_scope_fragments: 0, + discarded_fragments: 0, + contains_multiple_tasks: false + }, + decisions: [ + { + fragment_id: "F1", + route: "no_route", + no_route_reason: "insufficient_specificity", + clarification_reason: "missing anchor", + reason: "needs clarification" + }, + { + fragment_id: "F2", + route: "hybrid_store_plus_live", + reason: "route selected" + } + ], + fallback: { + type: "none", + message: null + } + } as any, + userMessage: "base question", + fragmentTextById: new Map([ + ["F1", "уточни период"], + ["F2", "проверь по 60.01"] + ]), + requirementByFragment: new Map([ + ["F1", ["R1"]], + ["F2", ["R2"]] + ]) + }); + + expect(executionPlan).toHaveLength(2); + expect(executionPlan[0]).toMatchObject({ + fragment_id: "F1", + route: "no_route", + should_execute: false, + no_route_reason: "insufficient_specificity", + clarification_reason: "missing anchor" + }); + expect(executionPlan[1]).toMatchObject({ + fragment_id: "F2", + route: "hybrid_store_plus_live", + should_execute: true, + no_route_reason: null + }); + }); + + it("builds legacy debug routes via resolver", () => { + const routes = buildDebugRoutesFromRoute({ + routeSummary: { + mode: "legacy_v1", + intent_class: "partner_reconciliation", + route_hint: "store_canonical", + confidence: "medium" + } as any, + resolveLegacyRouteReason: (route) => `legacy:${route}` + }); + + expect(routes).toHaveLength(1); + expect(routes[0]).toMatchObject({ + fragment_id: "F1", + route: "store_canonical", + reason: "legacy:store_canonical", + confidence: "medium" + }); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantTurnCommitRuntimeAdapter.test.ts b/llm_normalizer/backend/tests/assistantTurnCommitRuntimeAdapter.test.ts new file mode 100644 index 0000000..0848eea --- /dev/null +++ b/llm_normalizer/backend/tests/assistantTurnCommitRuntimeAdapter.test.ts @@ -0,0 +1,93 @@ +import { describe, expect, it } from "vitest"; +import type { AssistantConversationItem, AssistantSessionState } from "../src/types/assistant"; +import { commitAssistantTurnAndLog } from "../src/services/assistantTurnCommitRuntimeAdapter"; + +function buildAssistantItem(): AssistantConversationItem { + return { + message_id: "msg-1", + session_id: "asst-1", + role: "assistant", + text: "ok", + reply_type: "factual", + created_at: "2026-04-10T10:01:00.000Z", + trace_id: "trace-1", + debug: null + }; +} + +describe("assistant turn commit runtime adapter", () => { + it("appends item, persists existing session, clones conversation and logs event", () => { + const assistantItem = buildAssistantItem(); + const storedSession: AssistantSessionState = { + session_id: "asst-1", + updated_at: "2026-04-10T10:01:00.000Z", + items: [assistantItem], + investigation_state: null + }; + + const calls = { + append: 0, + persist: 0, + log: 0 + }; + let loggedPayload: Record | null = null; + + const result = commitAssistantTurnAndLog({ + sessionId: "asst-1", + assistantItem, + eventType: "assistant_message", + logDetails: { some: "details" }, + appendItem: () => { + calls.append += 1; + }, + getSession: () => storedSession, + persistSession: () => { + calls.persist += 1; + }, + cloneConversation: (items) => items.map((item) => ({ ...item, debug: item.debug ? { ...item.debug } : null })), + logEvent: (payload) => { + calls.log += 1; + loggedPayload = payload as unknown as Record; + }, + nowIso: () => "2026-04-10T10:02:00.000Z" + }); + + expect(calls.append).toBe(1); + expect(calls.persist).toBe(1); + expect(calls.log).toBe(1); + expect(result.currentSession?.session_id).toBe("asst-1"); + expect(result.conversation).toEqual([assistantItem]); + expect(result.conversation).not.toBe(storedSession.items); + expect(loggedPayload?.["sessionId"]).toBe("asst-1"); + expect(loggedPayload?.["eventType"]).toBe("assistant_message"); + expect(loggedPayload?.["message"]).toBe("assistant_message_processed"); + expect(loggedPayload?.["timestamp"]).toBe("2026-04-10T10:02:00.000Z"); + }); + + it("skips persist when session is missing and still logs with empty conversation", () => { + const assistantItem = buildAssistantItem(); + let persistCalled = false; + let logCalled = false; + + const result = commitAssistantTurnAndLog({ + sessionId: "asst-missing", + assistantItem, + eventType: "assistant_message", + logDetails: { x: 1 }, + appendItem: () => {}, + getSession: () => null, + persistSession: () => { + persistCalled = true; + }, + cloneConversation: (items) => items.map((item) => ({ ...item })), + logEvent: () => { + logCalled = true; + } + }); + + expect(persistCalled).toBe(false); + expect(logCalled).toBe(true); + expect(result.currentSession).toBeNull(); + expect(result.conversation).toEqual([]); + }); +}); diff --git a/llm_normalizer/backend/tests/assistantWave10SettlementCorrectiveRegression.test.ts b/llm_normalizer/backend/tests/assistantWave10SettlementCorrectiveRegression.test.ts index aea5abb..3ad8a05 100644 --- a/llm_normalizer/backend/tests/assistantWave10SettlementCorrectiveRegression.test.ts +++ b/llm_normalizer/backend/tests/assistantWave10SettlementCorrectiveRegression.test.ts @@ -11,7 +11,8 @@ const FLAG_KEYS = [ "FEATURE_ASSISTANT_ANSWER_POLICY_V11", "FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1", "FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1", - "FEATURE_ASSISTANT_PROBLEM_UNITS_V1" + "FEATURE_ASSISTANT_PROBLEM_UNITS_V1", + "FEATURE_ASSISTANT_ADDRESS_QUERY_V1" ] as const; const ORIGINAL_FLAGS: Record = Object.fromEntries( @@ -623,6 +624,7 @@ describe("wave10 settlement corrective regression", () => { process.env.FEATURE_ASSISTANT_PROBLEM_CENTRIC_ANSWER_V1 = "1"; process.env.FEATURE_ASSISTANT_PROBLEM_UNIT_CONTINUITY_V1 = "1"; process.env.FEATURE_ASSISTANT_PROBLEM_UNITS_V1 = "1"; + process.env.FEATURE_ASSISTANT_ADDRESS_QUERY_V1 = "0"; vi.resetModules(); const { createApp } = await import("../src/server"); diff --git a/llm_normalizer/backend/tests/questionTypeResolver.test.ts b/llm_normalizer/backend/tests/questionTypeResolver.test.ts index b8d504e..771f35d 100644 --- a/llm_normalizer/backend/tests/questionTypeResolver.test.ts +++ b/llm_normalizer/backend/tests/questionTypeResolver.test.ts @@ -31,4 +31,9 @@ describe("questionTypeResolver", () => { expect(resolveQuestionType("Почему не сходится 62.01/62.02?")) .toBe("why_breaks"); }); + + it("keeps generic non-why questions as unknown", () => { + expect(resolveQuestionType("Какие реализации стоит проверить заранее, чтобы не испортить отчетность за месяц?")) + .toBe("unknown"); + }); }); diff --git a/llm_normalizer/data/autorun_annotations/annotations.json b/llm_normalizer/data/autorun_annotations/annotations.json index dd8b61f..66c4fa7 100644 --- a/llm_normalizer/data/autorun_annotations/annotations.json +++ b/llm_normalizer/data/autorun_annotations/annotations.json @@ -9,11 +9,11 @@ "comment": "Тут брак не только в формулировке ответа, а в самой логике попадания в запрос:\n\nвопрос про поставщиков с хвостами на конец месяца;\nещё и про признак систематической проблемы, а не разовую задержку;\nв ответе показан один документ списания с расчётного счёта по одному контрагенту;\nне объяснено, почему этот документ вообще релевантен запросу;\nне раскрыто, как он связан с хвостами, концом месяца и системностью;\nнет декомпозиции запроса и нет явного ответа на его основную часть.\n\nТо есть система фактически подменила задачу: вместо поиска паттерна по поставщикам выдала одиночную находку без обоснования.\n\n\n\nОтвет не попал в суть запроса: вопрос был о поставщиках с системными хвостами на конец месяца, а показан одиночный документ списания без объяснения его связи с хвостами, периодом и систематичностью. Нужны декомпозиция запроса, явное обоснование выбора документа и комментарий о степени покрытия контекста.", "manual_case_decision": "needs_dialog_policy_fix", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T06:56:51.572Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-09T16:18:03.734Z", - "updated_at": "2026-04-09T16:18:03.734Z", + "updated_at": "2026-04-10T06:56:51.572Z", "context": { "message_id": "msg-hHz2ydMJOa", "trace_id": "address-z5C0-D-mMH", @@ -36,11 +36,11 @@ "comment": "Так, ну этот вопрос явно вне контура, тут ответ очень технический. Ну как бы юзеру вообще не надо знать ничего про адрес Query, это какая-то техническая хуета, ее нахуй не нужно вываливать. Вот. И просить переформулировать тоже не надо, потому что когда мы просим что-то переформулировать, это значит, мы что-то не поняли. Тут явно вопрос вне контура. Вот. Ну то есть мы как бы точно не будем разлиновку делать под этот запрос.", "manual_case_decision": "out_of_scope_but_answer_softly", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T06:55:53.649Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-09T18:11:35.236Z", - "updated_at": "2026-04-09T18:11:35.236Z", + "updated_at": "2026-04-10T06:55:53.649Z", "context": { "message_id": "msg-JqLbA9uuLs", "trace_id": "address-vyNV6r-l6T", @@ -63,11 +63,11 @@ "comment": "Так, ну смотри, короче, ответ очень опять очень технический, не хватает обязательного якоря. Тут как бы мы не должны ни про какие там якоря там обязательно говорить, что есть обязательный якорь. Ну как бы это знаешь, такая очень абстрактная история. Так, дальше ответ. Причина контрагент по указанному имени Алясу не найден. Да только кто-то вообще не уточняет ни про каких конкретных. Он как раз и требует тебе найти какого-то контрагента. Он не знает. Он вот тут вопрос как раз про поиски. Вот, и точно- точно неправильная просьба. Тут надо смотреть на диапазоны, наверное, между заключением договора и там какими-то оплатами. Вот, и предполагать, что это оно как раз. То, что очень длинные окна, там они могут на годы там быть, например, я не знаю. Ну, короче, сыро очень ответчик. Очень сыро ответил и вообще не по теме.", "manual_case_decision": "covered_but_bad_answer", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T06:55:49.687Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-09T18:15:01.488Z", - "updated_at": "2026-04-09T18:15:01.488Z", + "updated_at": "2026-04-10T06:55:49.687Z", "context": { "message_id": "msg-ONDlFjoxbU", "trace_id": "address-paNbrPvWpC", @@ -90,11 +90,11 @@ "comment": "Так мы вообще не должны вводить вот эту техническую историю про что сломано. Вот, я, если честно, скорее всего, надо просто ответить, что вопрос вне контекста.", "manual_case_decision": "out_of_scope_but_answer_softly", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T08:22:45.050Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-09T18:16:42.200Z", - "updated_at": "2026-04-09T18:16:42.200Z", + "updated_at": "2026-04-10T08:22:45.050Z", "context": { "message_id": "msg-ajecodZjyK", "trace_id": "CQZp9tGQTTowij", @@ -117,11 +117,11 @@ "comment": "Тут опять очевидно очень прямой и конкретный запрос, и мы его должны вроде, по идее, как отработать. Он у нас уже вроде должен отрабатываться. Если нет, то значит надо его... Так, надо расширить маршрутизацию. То есть глобально надо найти, короче, документы на какой-то период определенный, если мы говорим про срез. Надо найти документы, которые не закрыты. Вот и всё. Как бы это очевидно. Ну, как бы не закрытые доки просто ищем и выводим. Вот. Если таких нету, то, значит, отвечаем, что такая проблема отсутствует.", "manual_case_decision": "needs_routing_extension", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T06:54:00.659Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-09T18:18:02.949Z", - "updated_at": "2026-04-09T18:18:02.949Z", + "updated_at": "2026-04-10T06:54:00.659Z", "context": { "message_id": "msg-fIfli9GAKl", "trace_id": "address-0wCj3SAhfK", @@ -144,11 +144,11 @@ "comment": "Опять точно такая же проблема. Ну как бы очень технический ответ про якоря и про обязательства. Это уже обсуждали. И никакие уточнения, а почему модель просто какие-то уточнения, это противоречит вообще-то изначальному запросу. Запрос максимально простой. Надо найти документы, которые не оплачены. То есть договора мертвые, без оплат. Вот и всё. У нас точно такие есть, и их точно довольно-таки легко маршрутизировать, если они у нас не маршрутизируются.", "manual_case_decision": "needs_routing_extension", "annotation_author": "manual_reviewer", - "resolved": false, - "resolved_at": null, - "resolved_by": null, + "resolved": true, + "resolved_at": "2026-04-10T06:53:52.731Z", + "resolved_by": "manual_reviewer", "created_at": "2026-04-09T18:19:27.359Z", - "updated_at": "2026-04-09T20:06:33.494Z", + "updated_at": "2026-04-10T06:53:52.731Z", "context": { "message_id": "msg-uVQqIiArS7", "trace_id": "address-2NS8zxTvo5", @@ -160,5 +160,410 @@ "question_text": "По каким контрагентам документы есть, а оплат нет. Может, стоит взять на карандаш такие ситуации чтоб не тянуть дальше?", "answer_text": "Для точного адресного поиска не хватает обязательного якоря.\nПричина: контрагент по указанному имени/алиасу не найден в materialized live-строках.\nЧто нужно уточнить: уточните точное имя контрагента или добавьте ИНН." } + }, + { + "annotation_id": "ann-mnsosuko-nsv6qwt", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-001", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-001", + "message_index": 1, + "rating": 1, + "comment": "вопрос не отрабатываем но нужно обратить внимание что ассистент выдал список активности - то есть не понял контекст и дал совершенновне контекста выдачу", + "manual_case_decision": "out_of_scope_but_answer_softly", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:11:53.544Z", + "updated_at": "2026-04-10T09:11:53.544Z", + "context": { + "message_id": "msg-RDpaUn4py2", + "trace_id": "address-pQYUVty09a", + "reply_type": "factual", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "какие контрагенты пока вообще не платят за текущий месяц и это уже тревожный знак для нас?", + "answer_text": "Активные заказчики в выбранном периоде: 46.\nСобран профиль активности заказчиков (bank-doc activity aggregate).\nСтрок агрегата: 46.\n1. ГКУ | операций: 44 | последняя активность: 2019-12-25T16:09:27Z\n2. ГБУ \\Многофункциональный центр\\ | операций: 38 | последняя активность: 2018-12-04T12:00:01Z\n3. СБЕРБАНК, ПАО | операций: 36 | последняя активность: 2020-03-02T12:00:04Z\n4. Комитет государственных услуг г. Москвы | операций: 27 | последняя активность: 2015-05-19T12:00:00Z\n5. Группа | операций: 26 | последняя активность: 2021-11-10T12:00:07Z\n6. НОРТОН | операций: 14 | последняя активность: 2022-01-20T12:00:05Z\n7. МНН | операций: 14 | последняя активность: 2022-01-20T12:00:04Z\n8. МОСКОВСКИЙ ФИЛИАЛ ООО КБ\\МЕГАПОЛИС\\ | операций: 11 | последняя активность: 2019-07-31T12:00:22Z\n9. Департамент капитального ремонта города Москвы. | операций: 10 | последняя активность: 2019-09-16T11:00:00Z\n10. Департамент финансов города Москвы | операций: 10 | последняя активность: 2015-12-29T12:00:03Z\n11. Лайсвуд, ООО | операций: 7 | последняя активность: 2020-03-02T12:00:02Z\n12. Единая электронная торговая площадка, АО | операций: 7 | последняя активность: 2018-08-10T12:00:00Z\n13. ЭталонМебель | операций: 6 | последняя активность: 2020-04-27T12:00:00Z\n14. ИП Калинин Н.М. | операций: 5 | последняя активность: 2020-03-02T12:00:03Z\n15. Элмаш-К | операций: 5 | последняя активность: 2018-02-06T12:32:17Z\n16. ГТК-Интер, ООО | операций: 5 | последняя активность: 2016-07-07T12:00:00Z\n17. Гамма-мебель, ООО | операций: 4 | последняя активность: 2020-08-11T13:15:30Z\n18. Смарт | операций: 4 | последняя активность: 2020-02-07T12:00:03Z\n19. Сбербанк-АСТ, ЗАО | операций: 4 | последняя активность: 2017-07-26T12:00:00Z\n20. КБ \\НЕФТЯНОЙ АЛЬЯНС\\ (ПАО) (Расчетный) | операций: 4 | последняя активность: 2015-02-05T12:00:06Z\n21. ЗАО Ремонтно-строительная фирма «Ремстройсервис» | операций: 3 | последняя активность: 2020-06-03T00:00:02Z\n22. ПрофТренд,ООО | операций: 3 | последняя активность: 2020-05-15T12:00:02Z\n23. ГБПОУ г. Москвы \\Московская театральная школа Олега Табакова\\ | операций: 3 | последняя активность: 2019-10-25T12:00:00Z\n24. Газпром авиа, ООО | операций: 3 | последняя активность: 2016-11-29T12:00:01Z\n25. ФГКУ \\Центрреставрация\\ | операций: 3 | последняя активность: 2015-12-30T12:00:03Z\n26. РАЙМ, ООО | операций: 3 | последняя активность: 2015-08-13T12:00:00Z\n27. СервисКонсалт, ООО | операций: 2 | последняя активность: 2022-04-13T12:00:00Z\n30. УФК по г. Москве (ГУ - | операций: 2 | последняя активность: 2018-12-12T12:00:01Z\n31. Чепурнов П.Д. | операций: 1 | последняя активность: 2022-01-20T12:00:03Z\n32. «Олимпстрой» | операций: 1 | последняя активность: 2020-07-13T12:00:00Z\n33. КПК \\Столичная Сберегательная компания\\ | операций: 1 | последняя активность: 2019-04-17T12:00:00Z\n34. РТС-тендер | операций: 1 | последняя активность: 2018-12-17T00:00:00Z\n35. ФГБУ «УЗС» | операций: 1 | последняя активность: 2018-12-13T12:00:02Z\n36. АС \\ЕО | операций: 1 | последняя активность: 2018-01-15T13:25:36Z\n37. АкваСервис, ООО | операций: 1 | последняя активность: 2017-09-19T12:00:01Z\n38. ГБУ МФЦ города Москвы | операций: 1 | последняя активность: 2017-04-27T00:00:00Z\n39. \\Жуковка 51\\ | операций: 1 | последняя активность: 2017-04-10T16:50:38Z\n40. 0 | операций: 1 | последняя активность: 2017-03-02T12:00:00Z\n41. ФГБОУ ВО \\Московский государственный университет имени М.В. Ломоносова\\ | операций: 1 | последняя активность: 2016-11-25T00:00:00Z\n42. ДжетАэроКонцепт, ООО | операций: 1 | последняя активность: 2016-08-18T13:13:35Z\n43. УМ и автотранспорта, ЗАО | операций: 1 | последняя активность: 2015-12-24T12:00:00Z\n44. Межрегиональное операционное УФК | операций: 1 | последняя активность: 2015-11-12T12:00:00Z\n45. Банк ГПБ (АО) г. Москва | операций: 1 | последняя активность: 2015-10-30T12:00:03Z\n46. Спецстрой, ООО | операций: 1 | последняя активность: 2015-09-14T12:00:00Z" + } + }, + { + "annotation_id": "ann-mnsougqc-y9ow9kx", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-003", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-003", + "message_index": 1, + "rating": 1, + "comment": "на выбранный период можно показать не закрытые договора - очевидно что по контексту это можно упростить до домена открытых договоров", + "manual_case_decision": "needs_dialog_policy_fix", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:13:08.915Z", + "updated_at": "2026-04-10T09:13:08.915Z", + "context": { + "message_id": "msg-zs_PZOy4zu", + "trace_id": "address-KYzulAwWgo", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "какие покупатели пока не оплатили свои товары или услуги, хотя сроки давно прошли?", + "answer_text": "Чтобы ответить надежно, нужен более точный ориентир в запросе.\nКоротко: в запросе не хватает конкретного ориентира (контрагент, договор или период).\nЧто можно сделать дальше: уточните точное имя контрагента или добавьте ИНН." + } + }, + { + "annotation_id": "ann-mnsovwms-u47mj73", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-004", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-004", + "message_index": 1, + "rating": 1, + "comment": "мы разве не можем прокинуть маршрут по не закрытиым авансам на актами на дату рассмотрения???", + "manual_case_decision": "needs_routing_extension", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:14:16.180Z", + "updated_at": "2026-04-10T09:14:16.180Z", + "context": { + "message_id": "msg-XjaJdWuftN", + "trace_id": "address-m9ZE6DBr7R", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "у кого из контрагентов есть авансы, но никаких документов для их закрытия нет уже давно?", + "answer_text": "Чтобы ответить надежно, нужен более точный ориентир в запросе.\nКоротко: в запросе не хватает конкретного ориентира (контрагент, договор или период).\nЧто можно сделать дальше: уточните точное имя контрагента или добавьте ИНН." + } + }, + { + "annotation_id": "ann-mnsowef0-6cozamj", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-005", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-005", + "message_index": 1, + "rating": 3, + "comment": "вопрос не актуален", + "manual_case_decision": "out_of_scope_but_answer_softly", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:14:39.227Z", + "updated_at": "2026-04-10T09:14:39.227Z", + "context": { + "message_id": "msg-6N2xTrZA3p", + "trace_id": "address-XQrnJFW_v1", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "по каким поставщикам у нас сальдо явно расходится с тем, что они сами указывают в своих актах сверок?", + "answer_text": "Сейчас этот тип вопроса вне поддерживаемого контура адресного режима.\nКоротко: этот сценарий пока не поддержан в текущем адресном контуре.\nЧто можно сделать дальше: могу проверить близкие сценарии: документы/платежи по контрагенту, договоры или остаток по счету." + } + }, + { + "annotation_id": "ann-mnsoybd8-dirt1t4", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-007", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-007", + "message_index": 1, + "rating": 3, + "comment": "в чем проблема чекнуть это в 1с? это же не сложно вродже?", + "manual_case_decision": "needs_routing_extension", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:16:08.588Z", + "updated_at": "2026-04-10T09:16:08.588Z", + "context": { + "message_id": "msg-3otxbY6nSu", + "trace_id": "address-VpPvao4Vua", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "где у нас есть оплаты без закрытия взаиморасчетов, и это уже требует ручной проверки?", + "answer_text": "Чтобы ответить надежно, нужен более точный ориентир в запросе.\nКоротко: в запросе не хватает конкретного ориентира (контрагент, договор или период).\nЧто можно сделать дальше: уточните точное имя контрагента или добавьте ИНН." + } + }, + { + "annotation_id": "ann-mnsozu7p-nkxiafb", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-008", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-008", + "message_index": 1, + "rating": 1, + "comment": "тут надо вывести самых доходном контр агентов - мы уже это умееем но запрос распознался не правильно? где наша ллс констекстная аналитика? тут явно сильныо торчащий контекст", + "manual_case_decision": "covered_but_bad_answer", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:17:19.668Z", + "updated_at": "2026-04-10T09:17:19.668Z", + "context": { + "message_id": "msg-7Fi7216TqF", + "trace_id": "address-pL6IUlwjAP", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "какие контрагенты принесли основную часть нашей выручки за отчетный период?", + "answer_text": "Сейчас этот тип вопроса вне поддерживаемого контура адресного режима.\nКоротко: этот сценарий пока не поддержан в текущем адресном контуре.\nЧто можно сделать дальше: могу проверить близкие сценарии: документы/платежи по контрагенту, договоры или остаток по счету." + } + }, + { + "annotation_id": "ann-mnsp1696-tfzg0zn", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-009", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-009", + "message_index": 1, + "rating": 1, + "comment": "необходим вывод открытых договоров не закрытых актими или финальными выплатами", + "manual_case_decision": "needs_routing_extension", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:18:21.929Z", + "updated_at": "2026-04-10T09:18:21.929Z", + "context": { + "message_id": "msg-Rviqs5LOve", + "trace_id": "address-2YTJldRV28", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "у кого из покупателей есть открытые задолженности перед нами на конец месяца?", + "answer_text": "Чтобы ответить надежно, нужен более точный ориентир в запросе.\nКоротко: в запросе не хватает конкретного ориентира (контрагент, договор или период).\nЧто можно сделать дальше: уточните: account." + } + }, + { + "annotation_id": "ann-mnsp2n0r-57mu4w5", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-010", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-010", + "message_index": 1, + "rating": 1, + "comment": "какой еще инн? юзер задает максимально конкретыный вопрос. типа авансы закрыты дибо не закрыты - нужна конкретная адресная проверка по базе 1с", + "manual_case_decision": "needs_routing_extension", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:19:30.315Z", + "updated_at": "2026-04-10T09:19:30.315Z", + "context": { + "message_id": "msg-qyrc6Jek0h", + "trace_id": "address-K97ICzNyLf", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "по каким поставщикам мы видим проблемные авансы, которые давно не закрыты документами?", + "answer_text": "Чтобы ответить надежно, нужен более точный ориентир в запросе.\nКоротко: в запросе не хватает конкретного ориентира (контрагент, договор или период).\nЧто можно сделать дальше: уточните точное имя контрагента или добавьте ИНН." + } + }, + { + "annotation_id": "ann-mnsp39ao-4r7dme5", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-011", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-011", + "message_index": 1, + "rating": 3, + "comment": "не отрабатываем - почему выскачела техничка???? нужен мягкий отказ", + "manual_case_decision": "out_of_scope_but_answer_softly", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:19:59.184Z", + "updated_at": "2026-04-10T09:20:29.316Z", + "context": { + "message_id": "msg-2BXINs6O5A", + "trace_id": "iUWtcr5ZfOqMvU", + "reply_type": "clarification_required", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "какие реализации выглядят так, что их нужно проверить заранее, чтобы они не портили отчетность за месяц?", + "answer_text": "Коротко: Наиболее вероятный механизм проблемы подтвержден частично и требует первичной проверки. Что сломано:\n- Есть признаки проблемы, но без уточнений по периоду и объекту вывод ненадежен. Почему это похоже на проблему:\n- Сигнал проблемы есть, но механизм подтвержден не полностью. На чем это основано:\n- Опора частичная: часть требований покрыта не полностью.\n- Отдельно не подтверждено или покрыто частично: R1. Что проверить первым:\n- Уточните период проверки, чтобы подтвердить проблему без лишнего шума.\n- Проверьте связку документов и проводок по проблемному участку в указанном периоде.\n- Уточните период проверки (например, июль 2020).\n- Уточните счет или группу счетов (например, 19, 60, 62). Ограничения:\n- Ни одно требование не получило подтвержденного покрытия.\n- Нет explainable-сигналов why_included в результатах выборки.\n- Часть контекстных токенов не подтверждена напрямую: document.\n- Недостаточно покрытия required anchors для claim-bound grounding.\n- Недостаточно допустимого evidence для обоснованного ответа." + } + }, + { + "annotation_id": "ann-mnsp4rj2-4mkc3mq", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-012", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-012", + "message_index": 1, + "rating": 3, + "comment": "надо отрабатывать маршрут - вопрос простой и полезный", + "manual_case_decision": "candidate_for_implementation", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:21:09.469Z", + "updated_at": "2026-04-10T09:21:09.469Z", + "context": { + "message_id": "msg-h4Uw2x6woE", + "trace_id": "address-mhaJ03Mjei", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "где у нас есть отгрузки без документов для их закрытия и это уже требует внимания?", + "answer_text": "Чтобы ответить надежно, нужен более точный ориентир в запросе.\nКоротко: в запросе не хватает конкретного ориентира (контрагент, договор или период).\nЧто можно сделать дальше: уточните точное имя контрагента или добавьте ИНН." + } + }, + { + "annotation_id": "ann-mnsp75ud-vg45thy", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-014", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-014", + "message_index": 1, + "rating": 3, + "comment": "нельзя вообще даваать такие ответы - нужно технически отработать этот марштрут", + "manual_case_decision": "candidate_for_implementation", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:23:01.330Z", + "updated_at": "2026-04-10T09:23:01.330Z", + "context": { + "message_id": "msg-CBXmhYfjcw", + "trace_id": "XHuO-nGyt2K1Sc", + "reply_type": "clarification_required", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "у кого из поставщиков сальдо явно расходится с тем, что они сами указывают в своих актах сверок?", + "answer_text": "Коротко: Наиболее вероятный механизм проблемы подтвержден частично и требует первичной проверки. Что сломано:\n- Есть признаки проблемы, но без уточнений по периоду и объекту вывод ненадежен. Почему это похоже на проблему:\n- Сигнал проблемы есть, но механизм подтвержден не полностью. На чем это основано:\n- Опора частичная: часть требований покрыта не полностью.\n- Отдельно не подтверждено или покрыто частично: R1. Что проверить первым:\n- Уточните период проверки, чтобы подтвердить проблему без лишнего шума.\n- Проверьте связку документов и проводок по проблемному участку в указанном периоде.\n- Уточните период проверки (например, июль 2020).\n- Уточните счет или группу счетов (например, 19, 60, 62). Ограничения:\n- Ни одно требование не получило подтвержденного покрытия.\n- Нет explainable-сигналов why_included в результатах выборки.\n- Недостаточно покрытия required anchors для claim-bound grounding.\n- Недостаточно допустимого evidence для обоснованного ответа.\n- Targeted evidence acquisition не дал допустимых попаданий по claim target path." + } + }, + { + "annotation_id": "ann-mnspackb-bvpjxgg", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-015", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-015", + "message_index": 1, + "rating": 1, + "comment": "почему мы вообще отвечаем так шаблонно?? зачем нам ллм? может на три уровня на старте ллм поставить?разбор контекста - декомпозиция - и если не можем отработать то ответ уже человеческий в контексте? сейчас постояннно одинаковые ответы это бесит", + "manual_case_decision": "bad_test_case", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:25:30.011Z", + "updated_at": "2026-04-10T09:25:30.011Z", + "context": { + "message_id": "msg-H7JqG6Ni0g", + "trace_id": "address-fGojfD8Utf", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "где у нас есть закрытие счетов без подтверждающих документов и это уже требует ручной проверки?", + "answer_text": "Чтобы ответить надежно, нужен более точный ориентир в запросе.\nКоротко: в запросе не хватает конкретного ориентира (контрагент, договор или период).\nЧто можно сделать дальше: уточните точное имя контрагента или добавьте ИНН." + } + }, + { + "annotation_id": "ann-mnspbnq3-sffi895", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-016", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-016", + "message_index": 1, + "rating": 1, + "comment": "мы точно отработали этот кейс маршрутом - схуя он опять упал в шаблонный ответ - мы уже точно умеем выводить топ по даходам и суммам контрактов", + "manual_case_decision": "covered_but_bad_answer", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:26:31.131Z", + "updated_at": "2026-04-10T09:26:31.131Z", + "context": { + "message_id": "msg-WmJg1Kp0tt", + "trace_id": "address-jDze8Tv0JS", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "какие контрагенты принесли основную часть наших доходов за отчетный период?", + "answer_text": "Сейчас этот тип вопроса вне поддерживаемого контура адресного режима.\nКоротко: этот сценарий пока не поддержан в текущем адресном контуре.\nЧто можно сделать дальше: могу проверить близкие сценарии: документы/платежи по контрагенту, договоры или остаток по счету." + } + }, + { + "annotation_id": "ann-mnspdgd6-wsl6zrd", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-017", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-017", + "message_index": 1, + "rating": 1, + "comment": "очень важный кейс - надо отрабатывать - причем потенциально мы должны это умееть - ут надо показать кто из заказчиков сидит с отрытыми договорами на дату рассмотрения", + "manual_case_decision": "candidate_for_implementation", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:27:54.906Z", + "updated_at": "2026-04-10T09:27:54.906Z", + "context": { + "message_id": "msg-3RW8I_1Y4f", + "trace_id": "address-3FNYpnavQE", + "reply_type": "partial_coverage", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "по каким покупателям у нас есть открытые задолженности на конец месяца?", + "answer_text": "Чтобы ответить надежно, нужен более точный ориентир в запросе.\nКоротко: в запросе не хватает конкретного ориентира (контрагент, договор или период).\nЧто можно сделать дальше: уточните: account." + } + }, + { + "annotation_id": "ann-mnspfjt5-06xlk7j", + "run_id": "assistant-stage1-UMKkFYfg2L", + "case_id": "AUTO-020", + "session_id": "assistant-stage1-UMKkFYfg2L-AUTO-020", + "message_index": 1, + "rating": 1, + "comment": "почему показаны актиные заказчики? вопрос был про не закрытые актими и приходами денег договора на момент рассмотрения", + "manual_case_decision": "covered_but_bad_answer", + "annotation_author": "manual_reviewer", + "resolved": false, + "resolved_at": null, + "resolved_by": null, + "created_at": "2026-04-10T09:29:32.681Z", + "updated_at": "2026-04-10T09:29:32.681Z", + "context": { + "message_id": "msg-XsyFjMyJS2", + "trace_id": "address-xexv2UQ5mP", + "reply_type": "factual", + "eval_target": "assistant_stage1", + "prompt_version": "address_query_runtime_v1", + "domain": null, + "query_class": null, + "question_text": "какие контрагенты пока вообще не платят за текущий месяц и это тревожный знак для нас?", + "answer_text": "Активные заказчики в выбранном периоде: 46.\nСобран профиль активности заказчиков (bank-doc activity aggregate).\nСтрок агрегата: 46.\n1. ГКУ | операций: 44 | последняя активность: 2019-12-25T16:09:27Z\n2. ГБУ \\Многофункциональный центр\\ | операций: 38 | последняя активность: 2018-12-04T12:00:01Z\n3. СБЕРБАНК, ПАО | операций: 36 | последняя активность: 2020-03-02T12:00:04Z\n4. Комитет государственных услуг г. Москвы | операций: 27 | последняя активность: 2015-05-19T12:00:00Z\n5. Группа | операций: 26 | последняя активность: 2021-11-10T12:00:07Z\n6. НОРТОН | операций: 14 | последняя активность: 2022-01-20T12:00:05Z\n7. МНН | операций: 14 | последняя активность: 2022-01-20T12:00:04Z\n8. МОСКОВСКИЙ ФИЛИАЛ ООО КБ\\МЕГАПОЛИС\\ | операций: 11 | последняя активность: 2019-07-31T12:00:22Z\n9. Департамент капитального ремонта города Москвы. | операций: 10 | последняя активность: 2019-09-16T11:00:00Z\n10. Департамент финансов города Москвы | операций: 10 | последняя активность: 2015-12-29T12:00:03Z\n11. Лайсвуд, ООО | операций: 7 | последняя активность: 2020-03-02T12:00:02Z\n12. Единая электронная торговая площадка, АО | операций: 7 | последняя активность: 2018-08-10T12:00:00Z\n13. ЭталонМебель | операций: 6 | последняя активность: 2020-04-27T12:00:00Z\n14. ИП Калинин Н.М. | операций: 5 | последняя активность: 2020-03-02T12:00:03Z\n15. Элмаш-К | операций: 5 | последняя активность: 2018-02-06T12:32:17Z\n16. ГТК-Интер, ООО | операций: 5 | последняя активность: 2016-07-07T12:00:00Z\n17. Гамма-мебель, ООО | операций: 4 | последняя активность: 2020-08-11T13:15:30Z\n18. Смарт | операций: 4 | последняя активность: 2020-02-07T12:00:03Z\n19. Сбербанк-АСТ, ЗАО | операций: 4 | последняя активность: 2017-07-26T12:00:00Z\n20. КБ \\НЕФТЯНОЙ АЛЬЯНС\\ (ПАО) (Расчетный) | операций: 4 | последняя активность: 2015-02-05T12:00:06Z\n21. ЗАО Ремонтно-строительная фирма «Ремстройсервис» | операций: 3 | последняя активность: 2020-06-03T00:00:02Z\n22. ПрофТренд,ООО | операций: 3 | последняя активность: 2020-05-15T12:00:02Z\n23. ГБПОУ г. Москвы \\Московская театральная школа Олега Табакова\\ | операций: 3 | последняя активность: 2019-10-25T12:00:00Z\n24. Газпром авиа, ООО | операций: 3 | последняя активность: 2016-11-29T12:00:01Z\n25. ФГКУ \\Центрреставрация\\ | операций: 3 | последняя активность: 2015-12-30T12:00:03Z\n26. РАЙМ, ООО | операций: 3 | последняя активность: 2015-08-13T12:00:00Z\n27. СервисКонсалт, ООО | операций: 2 | последняя активность: 2022-04-13T12:00:00Z\n30. УФК по г. Москве (ГУ - | операций: 2 | последняя активность: 2018-12-12T12:00:01Z\n31. Чепурнов П.Д. | операций: 1 | последняя активность: 2022-01-20T12:00:03Z\n32. «Олимпстрой» | операций: 1 | последняя активность: 2020-07-13T12:00:00Z\n33. КПК \\Столичная Сберегательная компания\\ | операций: 1 | последняя активность: 2019-04-17T12:00:00Z\n34. РТС-тендер | операций: 1 | последняя активность: 2018-12-17T00:00:00Z\n35. ФГБУ «УЗС» | операций: 1 | последняя активность: 2018-12-13T12:00:02Z\n36. АС \\ЕО | операций: 1 | последняя активность: 2018-01-15T13:25:36Z\n37. АкваСервис, ООО | операций: 1 | последняя активность: 2017-09-19T12:00:01Z\n38. ГБУ МФЦ города Москвы | операций: 1 | последняя активность: 2017-04-27T00:00:00Z\n39. \\Жуковка 51\\ | операций: 1 | последняя активность: 2017-04-10T16:50:38Z\n40. 0 | операций: 1 | последняя активность: 2017-03-02T12:00:00Z\n41. ФГБОУ ВО \\Московский государственный университет имени М.В. Ломоносова\\ | операций: 1 | последняя активность: 2016-11-25T00:00:00Z\n42. ДжетАэроКонцепт, ООО | операций: 1 | последняя активность: 2016-08-18T13:13:35Z\n43. УМ и автотранспорта, ЗАО | операций: 1 | последняя активность: 2015-12-24T12:00:00Z\n44. Межрегиональное операционное УФК | операций: 1 | последняя активность: 2015-11-12T12:00:00Z\n45. Банк ГПБ (АО) г. Москва | операций: 1 | последняя активность: 2015-10-30T12:00:03Z\n46. Спецстрой, ООО | операций: 1 | последняя активность: 2015-09-14T12:00:00Z" + } } ] \ No newline at end of file diff --git a/llm_normalizer/data/autorun_generators/history.json b/llm_normalizer/data/autorun_generators/history.json index 05f8fc1..b843163 100644 --- a/llm_normalizer/data/autorun_generators/history.json +++ b/llm_normalizer/data/autorun_generators/history.json @@ -1,4 +1,84 @@ [ + { + "generation_id": "gen-mnsolawk-vugqyoc", + "created_at": "2026-04-10T09:06:01.461Z", + "mode": "qwen_seed", + "count": 20, + "domain": null, + "questions": [ + "какие контрагенты пока вообще не платят за текущий месяц и это уже тревожный знак для нас?", + "где у нас есть отгрузки без закрытия по ним счетов, и это выглядит как серьезная проблема?", + "какие покупатели пока не оплатили свои товары или услуги, хотя сроки давно прошли?", + "у кого из контрагентов есть авансы, но никаких документов для их закрытия нет уже давно?", + "по каким поставщикам у нас сальдо явно расходится с тем, что они сами указывают в своих актах сверок?", + "какие реализации на конец периода выглядят так, будто будут портить картину по выручке из-за незакрытых счетов или документов?", + "где у нас есть оплаты без закрытия взаиморасчетов, и это уже требует ручной проверки?", + "какие контрагенты принесли основную часть нашей выручки за отчетный период?", + "у кого из покупателей есть открытые задолженности перед нами на конец месяца?", + "по каким поставщикам мы видим проблемные авансы, которые давно не закрыты документами?", + "какие реализации выглядят так, что их нужно проверить заранее, чтобы они не портили отчетность за месяц?", + "где у нас есть отгрузки без документов для их закрытия и это уже требует внимания?", + "кто из контрагентов пока не оплатил свои товары или услуги хотя сроки давно прошли?", + "у кого из поставщиков сальдо явно расходится с тем, что они сами указывают в своих актах сверок?", + "где у нас есть закрытие счетов без подтверждающих документов и это уже требует ручной проверки?", + "какие контрагенты принесли основную часть наших доходов за отчетный период?", + "по каким покупателям у нас есть открытые задолженности на конец месяца?", + "у кого из поставщиков есть авансы, но никаких документов для их закрытия нет уже давно?", + "где у нас есть реализации, которые выглядят так, будто они зависли и будут портить картину по выручке, если их не проверить заранее?", + "какие контрагенты пока вообще не платят за текущий месяц и это тревожный знак для нас?" + ], + "generated_by": "manual_reviewer", + "saved_case_set_file": "assistant_autogen_qwen_seed_20260410090601_gen-mnsolawk-vugqyoc.json", + "context": { + "llm_provider": "local", + "model": "Qwen2.5 14B Instruct 1M", + "assistant_prompt_version": "address_query_runtime_v1", + "decomposition_prompt_version": "normalizer_v2_0_2", + "prompt_fingerprint": "Ты semantic-normalizer для бухгалтерского ассистента NDC.\nТвоя роль: только нормализация запроса пользователя в строгий JSON-контракт.\n\nЖесткие правила:\n1) Не давай бухгалтерский ответ по сути вопроса.\n2) Возвращай только JSON без markdown и пояснений.\n3) JSON обязан соответствовать переданной schema normalized_query_v1.\n4) Если период не указан, не выдумывай его; отмечай ambiguity.\n5) Для цепочек документов/проводок/оплат поднимай causal и cross-entity признаки.\n6) Для точечного object trace (номер/строка/ref) поднимай needs_exact_object_trace=true.\n7) Используй терминологию NDC.\nYou are semantic-normalizer for accounting assistant NDC.\nReturn strict JSON only, no markdown, no comments.\n\nTarget schema: normalized_query_v2_0_2.\n\nCore behavior (v2.0.2):\n1. Decompose message into semantic fragments.\n2. Classify fragment domain relevance and business scope.\n3. Fill route-critical flags and ", + "autogen_personality_id": "general", + "autogen_personality_prompt": "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл. акцент на контрагентов \\ нсд \\ счета \\ общий ваывод по компании - контрагенты заказчиким скока денег кто принес и тп" + } + }, + { + "generation_id": "gen-mnsmzzcr-ffcz48q", + "created_at": "2026-04-10T08:21:27.099Z", + "mode": "qwen_seed", + "count": 20, + "domain": null, + "questions": [ + "какие контрагенты больше всего задерживают платежи за последний месяц и это уже выходит за рамки обычной просрочки?", + "есть ли поставщики у которых на конец периода есть непроведенные документы, которые мешают закрыть взаиморасчеты?", + "какие покупатели долгое время не отвечают на наши претензии про несоответствие сальдо в сверке?", + "покажи контрагентов где есть оплаты, но документов для их закрытия нет и это уже требует ручного вмешательства?", + "у кого из поставщиков долгое время висят авансы без каких либо движений по ним?", + "какие реализации на конец месяца выглядят так, будто они зависли и могут повлиять на финальные показатели отчетности?", + "какие покупатели больше всего задерживают оплату за товары/услуги?", + "покажи контрагентов где документы есть но платежей для закрытия взаиморасчётов не видно и это уже требует проверки руками?", + "какие поставщики на конец месяца ещё не закрыли все накладные за текущий период?", + "какие контрагенты имеют большое сальдо, которое скорее всего не будет совпадать в акте сверки если его запросить прямо сейчас?", + "есть ли зависшие авансы, которые уже давно надо было либо закрыть, либо хотя бы перепроверить руками?", + "какие отгрузки выглядят так будто проблема не только в том что клиент не оплатил но и в том что сама связка документов собрана криво?", + "у кого из контрагентов есть накладные которые долго не закрываются по причине того что нет соответствующих платежей?", + "какие покупатели на конец периода уже давно не отвечают на наши претензии про несоответствие сальдо в сверке?", + "какие контрагенты имеют большое сальдо которое скорее всего не будет совпадать в акте сверки если его запросить прямо сейчас?", + "у кого из поставщиков долгое время висят авансы без каких либо движений по ним и это уже требует проверки руками?", + "есть ли зависшие накладные которые могут повлиять на финальные показатели отчетности если их не закрыть заранее?", + "какие поставщики на конец месяца ещё не закрыли все накладные за текущий период и это уже требует проверки руками?", + "какие покупатели долгое время не отвечают на наши претензии про несоответствие сальдо в сверке и это уже требует ручного вмешательства?", + "йо По каким поставщикам у нас на конец месяца остались хвосты, которые уже не похожи на обычную задержку документов, а выглядят как реальная проблема в цепочке?" + ], + "generated_by": "manual_reviewer", + "saved_case_set_file": "assistant_autogen_qwen_seed_20260410082127_gen-mnsmzzcr-ffcz48q.json", + "context": { + "llm_provider": "local", + "model": "Qwen2.5 14B Instruct 1M", + "assistant_prompt_version": "address_query_runtime_v1", + "decomposition_prompt_version": "normalizer_v2_0_2", + "prompt_fingerprint": "Ты semantic-normalizer для бухгалтерского ассистента NDC.\nТвоя роль: только нормализация запроса пользователя в строгий JSON-контракт.\n\nЖесткие правила:\n1) Не давай бухгалтерский ответ по сути вопроса.\n2) Возвращай только JSON без markdown и пояснений.\n3) JSON обязан соответствовать переданной schema normalized_query_v1.\n4) Если период не указан, не выдумывай его; отмечай ambiguity.\n5) Для цепочек документов/проводок/оплат поднимай causal и cross-entity признаки.\n6) Для точечного object trace (номер/строка/ref) поднимай needs_exact_object_trace=true.\n7) Используй терминологию NDC.\nYou are semantic-normalizer for accounting assistant NDC.\nReturn strict JSON only, no markdown, no comments.\n\nTarget schema: normalized_query_v2_0_2.\n\nCore behavior (v2.0.2):\n1. Decompose message into semantic fragments.\n2. Classify fragment domain relevance and business scope.\n3. Fill route-critical flags and ", + "autogen_personality_id": "general", + "autogen_personality_prompt": "Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл. акцент на контрагентов \\ нсд \\ счета \\ общий ваывод по компании - контрагенты заказчиким скока денег кто принес и тп" + } + }, { "generation_id": "gen-mnrvs132-1dewq5r", "created_at": "2026-04-09T19:39:26.463Z", diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260410082127_gen-mnsmzzcr-ffcz48q.json b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260410082127_gen-mnsmzzcr-ffcz48q.json new file mode 100644 index 0000000..360fd27 --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260410082127_gen-mnsmzzcr-ffcz48q.json @@ -0,0 +1,334 @@ +{ + "suite_id": "assistant_autogen_gen-mnsmzzcr-ffcz48q", + "suite_version": "0.1.0", + "schema_version": "assistant_autogen_suite_v0_1", + "generated_at": "2026-04-10T08:21:27.099Z", + "generation_id": "gen-mnsmzzcr-ffcz48q", + "mode": "qwen_seed", + "domain": null, + "scenario_count": 20, + "case_ids": [ + "AUTO-001", + "AUTO-002", + "AUTO-003", + "AUTO-004", + "AUTO-005", + "AUTO-006", + "AUTO-007", + "AUTO-008", + "AUTO-009", + "AUTO-010", + "AUTO-011", + "AUTO-012", + "AUTO-013", + "AUTO-014", + "AUTO-015", + "AUTO-016", + "AUTO-017", + "AUTO-018", + "AUTO-019", + "AUTO-020" + ], + "cases": [ + { + "case_id": "AUTO-001", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты больше всего задерживают платежи за последний месяц и это уже выходит за рамки обычной просрочки?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-002", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "есть ли поставщики у которых на конец периода есть непроведенные документы, которые мешают закрыть взаиморасчеты?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-003", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие покупатели долгое время не отвечают на наши претензии про несоответствие сальдо в сверке?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-004", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "покажи контрагентов где есть оплаты, но документов для их закрытия нет и это уже требует ручного вмешательства?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-005", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из поставщиков долгое время висят авансы без каких либо движений по ним?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-006", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие реализации на конец месяца выглядят так, будто они зависли и могут повлиять на финальные показатели отчетности?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-007", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие покупатели больше всего задерживают оплату за товары/услуги?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-008", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "покажи контрагентов где документы есть но платежей для закрытия взаиморасчётов не видно и это уже требует проверки руками?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-009", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие поставщики на конец месяца ещё не закрыли все накладные за текущий период?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-010", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты имеют большое сальдо, которое скорее всего не будет совпадать в акте сверки если его запросить прямо сейчас?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-011", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "есть ли зависшие авансы, которые уже давно надо было либо закрыть, либо хотя бы перепроверить руками?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-012", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие отгрузки выглядят так будто проблема не только в том что клиент не оплатил но и в том что сама связка документов собрана криво?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-013", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из контрагентов есть накладные которые долго не закрываются по причине того что нет соответствующих платежей?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-014", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие покупатели на конец периода уже давно не отвечают на наши претензии про несоответствие сальдо в сверке?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-015", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты имеют большое сальдо которое скорее всего не будет совпадать в акте сверки если его запросить прямо сейчас?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-016", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из поставщиков долгое время висят авансы без каких либо движений по ним и это уже требует проверки руками?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-017", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "есть ли зависшие накладные которые могут повлиять на финальные показатели отчетности если их не закрыть заранее?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-018", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие поставщики на конец месяца ещё не закрыли все накладные за текущий период и это уже требует проверки руками?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-019", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие покупатели долгое время не отвечают на наши претензии про несоответствие сальдо в сверке и это уже требует ручного вмешательства?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-020", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "йо По каким поставщикам у нас на конец месяца остались хвосты, которые уже не похожи на обычную задержку документов, а выглядят как реальная проблема в цепочке?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260410090601_gen-mnsolawk-vugqyoc.json b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260410090601_gen-mnsolawk-vugqyoc.json new file mode 100644 index 0000000..06304c0 --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_qwen_seed_20260410090601_gen-mnsolawk-vugqyoc.json @@ -0,0 +1,334 @@ +{ + "suite_id": "assistant_autogen_gen-mnsolawk-vugqyoc", + "suite_version": "0.1.0", + "schema_version": "assistant_autogen_suite_v0_1", + "generated_at": "2026-04-10T09:06:01.460Z", + "generation_id": "gen-mnsolawk-vugqyoc", + "mode": "qwen_seed", + "domain": null, + "scenario_count": 20, + "case_ids": [ + "AUTO-001", + "AUTO-002", + "AUTO-003", + "AUTO-004", + "AUTO-005", + "AUTO-006", + "AUTO-007", + "AUTO-008", + "AUTO-009", + "AUTO-010", + "AUTO-011", + "AUTO-012", + "AUTO-013", + "AUTO-014", + "AUTO-015", + "AUTO-016", + "AUTO-017", + "AUTO-018", + "AUTO-019", + "AUTO-020" + ], + "cases": [ + { + "case_id": "AUTO-001", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты пока вообще не платят за текущий месяц и это уже тревожный знак для нас?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-002", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "где у нас есть отгрузки без закрытия по ним счетов, и это выглядит как серьезная проблема?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-003", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие покупатели пока не оплатили свои товары или услуги, хотя сроки давно прошли?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-004", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из контрагентов есть авансы, но никаких документов для их закрытия нет уже давно?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-005", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "по каким поставщикам у нас сальдо явно расходится с тем, что они сами указывают в своих актах сверок?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-006", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие реализации на конец периода выглядят так, будто будут портить картину по выручке из-за незакрытых счетов или документов?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-007", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "где у нас есть оплаты без закрытия взаиморасчетов, и это уже требует ручной проверки?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-008", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты принесли основную часть нашей выручки за отчетный период?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-009", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из покупателей есть открытые задолженности перед нами на конец месяца?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-010", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "по каким поставщикам мы видим проблемные авансы, которые давно не закрыты документами?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-011", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие реализации выглядят так, что их нужно проверить заранее, чтобы они не портили отчетность за месяц?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-012", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "где у нас есть отгрузки без документов для их закрытия и это уже требует внимания?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-013", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "кто из контрагентов пока не оплатил свои товары или услуги хотя сроки давно прошли?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-014", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из поставщиков сальдо явно расходится с тем, что они сами указывают в своих актах сверок?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-015", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "где у нас есть закрытие счетов без подтверждающих документов и это уже требует ручной проверки?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-016", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты принесли основную часть наших доходов за отчетный период?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-017", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "по каким покупателям у нас есть открытые задолженности на конец месяца?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-018", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из поставщиков есть авансы, но никаких документов для их закрытия нет уже давно?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-019", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "где у нас есть реализации, которые выглядят так, будто они зависли и будут портить картину по выручке, если их не проверить заранее?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + }, + { + "case_id": "AUTO-020", + "scenario_tag": "qwen_seed_general", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты пока вообще не платят за текущий месяц и это тревожный знак для нас?" + } + ], + "expected_hints": { + "expected_reply_type": null, + "expected_degraded_to": null + } + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_runtime_job-7dfZNiKKmG.json b/llm_normalizer/data/eval_cases/assistant_autogen_runtime_job-7dfZNiKKmG.json new file mode 100644 index 0000000..1a4749b --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_runtime_job-7dfZNiKKmG.json @@ -0,0 +1,250 @@ +{ + "suite_id": "assistant_autogen_runtime_job-7dfZNiKKmG", + "suite_version": "0.1.0", + "schema_version": "assistant_autogen_runtime_v0_1", + "scenario_count": 20, + "case_ids": [ + "AUTO-001", + "AUTO-002", + "AUTO-003", + "AUTO-004", + "AUTO-005", + "AUTO-006", + "AUTO-007", + "AUTO-008", + "AUTO-009", + "AUTO-010", + "AUTO-011", + "AUTO-012", + "AUTO-013", + "AUTO-014", + "AUTO-015", + "AUTO-016", + "AUTO-017", + "AUTO-018", + "AUTO-019", + "AUTO-020" + ], + "cases": [ + { + "case_id": "AUTO-001", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты пока вообще не платят за текущий месяц и это уже тревожный знак для нас?" + } + ] + }, + { + "case_id": "AUTO-002", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "где у нас есть отгрузки без закрытия по ним счетов, и это выглядит как серьезная проблема?" + } + ] + }, + { + "case_id": "AUTO-003", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие покупатели пока не оплатили свои товары или услуги, хотя сроки давно прошли?" + } + ] + }, + { + "case_id": "AUTO-004", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из контрагентов есть авансы, но никаких документов для их закрытия нет уже давно?" + } + ] + }, + { + "case_id": "AUTO-005", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "по каким поставщикам у нас сальдо явно расходится с тем, что они сами указывают в своих актах сверок?" + } + ] + }, + { + "case_id": "AUTO-006", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие реализации на конец периода выглядят так, будто будут портить картину по выручке из-за незакрытых счетов или документов?" + } + ] + }, + { + "case_id": "AUTO-007", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "где у нас есть оплаты без закрытия взаиморасчетов, и это уже требует ручной проверки?" + } + ] + }, + { + "case_id": "AUTO-008", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты принесли основную часть нашей выручки за отчетный период?" + } + ] + }, + { + "case_id": "AUTO-009", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из покупателей есть открытые задолженности перед нами на конец месяца?" + } + ] + }, + { + "case_id": "AUTO-010", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "по каким поставщикам мы видим проблемные авансы, которые давно не закрыты документами?" + } + ] + }, + { + "case_id": "AUTO-011", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие реализации выглядят так, что их нужно проверить заранее, чтобы они не портили отчетность за месяц?" + } + ] + }, + { + "case_id": "AUTO-012", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "где у нас есть отгрузки без документов для их закрытия и это уже требует внимания?" + } + ] + }, + { + "case_id": "AUTO-013", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "кто из контрагентов пока не оплатил свои товары или услуги хотя сроки давно прошли?" + } + ] + }, + { + "case_id": "AUTO-014", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из поставщиков сальдо явно расходится с тем, что они сами указывают в своих актах сверок?" + } + ] + }, + { + "case_id": "AUTO-015", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "где у нас есть закрытие счетов без подтверждающих документов и это уже требует ручной проверки?" + } + ] + }, + { + "case_id": "AUTO-016", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты принесли основную часть наших доходов за отчетный период?" + } + ] + }, + { + "case_id": "AUTO-017", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "по каким покупателям у нас есть открытые задолженности на конец месяца?" + } + ] + }, + { + "case_id": "AUTO-018", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из поставщиков есть авансы, но никаких документов для их закрытия нет уже давно?" + } + ] + }, + { + "case_id": "AUTO-019", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "где у нас есть реализации, которые выглядят так, будто они зависли и будут портить картину по выручке, если их не проверить заранее?" + } + ] + }, + { + "case_id": "AUTO-020", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты пока вообще не платят за текущий месяц и это тревожный знак для нас?" + } + ] + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_runtime_job-NCdNOETmqd.json b/llm_normalizer/data/eval_cases/assistant_autogen_runtime_job-NCdNOETmqd.json new file mode 100644 index 0000000..d9b5e35 --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_runtime_job-NCdNOETmqd.json @@ -0,0 +1,250 @@ +{ + "suite_id": "assistant_autogen_runtime_job-NCdNOETmqd", + "suite_version": "0.1.0", + "schema_version": "assistant_autogen_runtime_v0_1", + "scenario_count": 20, + "case_ids": [ + "AUTO-001", + "AUTO-002", + "AUTO-003", + "AUTO-004", + "AUTO-005", + "AUTO-006", + "AUTO-007", + "AUTO-008", + "AUTO-009", + "AUTO-010", + "AUTO-011", + "AUTO-012", + "AUTO-013", + "AUTO-014", + "AUTO-015", + "AUTO-016", + "AUTO-017", + "AUTO-018", + "AUTO-019", + "AUTO-020" + ], + "cases": [ + { + "case_id": "AUTO-001", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты больше всего задерживают платежи за последний месяц и это уже выходит за рамки обычной просрочки?" + } + ] + }, + { + "case_id": "AUTO-002", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "есть ли поставщики у которых на конец периода есть непроведенные документы, которые мешают закрыть взаиморасчеты?" + } + ] + }, + { + "case_id": "AUTO-003", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие покупатели долгое время не отвечают на наши претензии про несоответствие сальдо в сверке?" + } + ] + }, + { + "case_id": "AUTO-004", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "покажи контрагентов где есть оплаты, но документов для их закрытия нет и это уже требует ручного вмешательства?" + } + ] + }, + { + "case_id": "AUTO-005", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из поставщиков долгое время висят авансы без каких либо движений по ним?" + } + ] + }, + { + "case_id": "AUTO-006", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие реализации на конец месяца выглядят так, будто они зависли и могут повлиять на финальные показатели отчетности?" + } + ] + }, + { + "case_id": "AUTO-007", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие покупатели больше всего задерживают оплату за товары/услуги?" + } + ] + }, + { + "case_id": "AUTO-008", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "покажи контрагентов где документы есть но платежей для закрытия взаиморасчётов не видно и это уже требует проверки руками?" + } + ] + }, + { + "case_id": "AUTO-009", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие поставщики на конец месяца ещё не закрыли все накладные за текущий период?" + } + ] + }, + { + "case_id": "AUTO-010", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты имеют большое сальдо, которое скорее всего не будет совпадать в акте сверки если его запросить прямо сейчас?" + } + ] + }, + { + "case_id": "AUTO-011", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "есть ли зависшие авансы, которые уже давно надо было либо закрыть, либо хотя бы перепроверить руками?" + } + ] + }, + { + "case_id": "AUTO-012", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие отгрузки выглядят так будто проблема не только в том что клиент не оплатил но и в том что сама связка документов собрана криво?" + } + ] + }, + { + "case_id": "AUTO-013", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из контрагентов есть накладные которые долго не закрываются по причине того что нет соответствующих платежей?" + } + ] + }, + { + "case_id": "AUTO-014", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие покупатели на конец периода уже давно не отвечают на наши претензии про несоответствие сальдо в сверке?" + } + ] + }, + { + "case_id": "AUTO-015", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие контрагенты имеют большое сальдо которое скорее всего не будет совпадать в акте сверки если его запросить прямо сейчас?" + } + ] + }, + { + "case_id": "AUTO-016", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "у кого из поставщиков долгое время висят авансы без каких либо движений по ним и это уже требует проверки руками?" + } + ] + }, + { + "case_id": "AUTO-017", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "есть ли зависшие накладные которые могут повлиять на финальные показатели отчетности если их не закрыть заранее?" + } + ] + }, + { + "case_id": "AUTO-018", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие поставщики на конец месяца ещё не закрыли все накладные за текущий период и это уже требует проверки руками?" + } + ] + }, + { + "case_id": "AUTO-019", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "какие покупатели долгое время не отвечают на наши претензии про несоответствие сальдо в сверке и это уже требует ручного вмешательства?" + } + ] + }, + { + "case_id": "AUTO-020", + "scenario_tag": "autogen_runtime", + "question_type": "direct", + "broadness_level": "medium", + "turns": [ + { + "user_message": "йо По каким поставщикам у нас на конец месяца остались хвосты, которые уже не похожи на обычную задержку документов, а выглядят как реальная проблема в цепочке?" + } + ] + } + ] +} \ No newline at end of file diff --git a/llm_normalizer/frontend/dist/assets/index-BfGkpjEM.css b/llm_normalizer/frontend/dist/assets/index-BfGkpjEM.css new file mode 100644 index 0000000..189933b --- /dev/null +++ b/llm_normalizer/frontend/dist/assets/index-BfGkpjEM.css @@ -0,0 +1 @@ +@import"https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&family=Space+Grotesk:wght@500;700&display=swap";:root{--rgb-background: 16, 16, 19;--rgb-surface-main: 26, 26, 31;--rgb-surface-horizontal: 32, 32, 38;--rgb-surface-focus: 40, 40, 47;--rgb-active: 228, 142, 92;--rgb-active-text: 18, 18, 18;--rgb-text-main: 240, 240, 240;--rgb-text-muted: 166, 166, 170;--rgb-danger: 255, 126, 126;--rgb-scrollbar-track: 31, 31, 36;--rgb-scrollbar-thumb: 74, 74, 82;--rgb-scrollbar-thumb-hover: 90, 90, 100;--mode-column-width: 440px;--mode-toggle-width: 188px;--bg-main: rgb(var(--rgb-background));--bg-soft: rgb(var(--rgb-surface-main));--bg-panel: rgb(var(--rgb-surface-main));--bg-panel-accent: rgb(var(--rgb-surface-horizontal));--surface-horizontal: rgb(var(--rgb-surface-horizontal));--surface-focus: rgb(var(--rgb-surface-focus));--line: transparent;--line-strong: rgba(var(--rgb-active), .48);--text-main: rgb(var(--rgb-text-main));--text-muted: rgb(var(--rgb-text-muted));--lime-main: rgb(var(--rgb-text-main));--lime-press: rgb(var(--rgb-text-main));--danger: rgb(var(--rgb-danger));--radius-lg: 20px;--radius-md: 14px;--shadow: none;--autoruns-col-width: var(--mode-column-width)}*{box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(var(--rgb-scrollbar-thumb)) rgb(var(--rgb-scrollbar-track))}*::-webkit-scrollbar{width:10px;height:10px}*::-webkit-scrollbar-track{background:rgb(var(--rgb-scrollbar-track))}*::-webkit-scrollbar-thumb{background:rgb(var(--rgb-scrollbar-thumb));border-radius:999px;border:2px solid rgb(var(--rgb-scrollbar-track))}*::-webkit-scrollbar-thumb:hover{background:rgb(var(--rgb-scrollbar-thumb-hover))}html,body,#root{margin:0;min-height:100dvh;font-family:Manrope,Segoe UI,sans-serif;background:var(--bg-main);color:var(--text-main)}.app-root{max-width:1720px;margin:0 auto;padding:12px 16px 16px}.app-root.app-root-autoruns{max-width:none;width:100%;min-height:100dvh;max-height:100dvh;display:flex;flex-direction:column;overflow:hidden}.app-topbar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 12px;padding:0;min-height:38px}.layout-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:16px}.layout-grid.layout-grid-autoruns,.layout-grid.layout-grid-mode-columns{min-height:0;flex:1 1 auto;grid-template-columns:minmax(0,1fr)}.mode-switch-row{display:flex;gap:8px;margin:0;padding:0}.mode-switch-row.mode-switch-row-right{margin-left:auto;justify-content:flex-end;max-width:72%;overflow-x:auto;overflow-y:hidden;padding-bottom:2px}.mode-switch-row .tab{white-space:nowrap}.mode-switch-row.mode-switch-row-right .tab{width:var(--mode-toggle-width);min-width:var(--mode-toggle-width);text-align:center}.mode-columns{display:flex;gap:12px;width:100%;min-height:0;flex:1 1 auto;overflow-x:auto;overflow-y:hidden;padding-bottom:4px}.mode-col{flex:0 0 var(--mode-column-width);width:var(--mode-column-width);min-height:0;height:100%;display:flex}.mode-col.mode-col-wide,.mode-col.mode-col-xwide{flex-basis:var(--mode-column-width);width:var(--mode-column-width)}.mode-col .panel-frame{width:100%;height:100%}.mode-col .panel-body{min-height:0;overflow:auto}.mode-columns-empty{min-width:360px;border-radius:14px;background:rgb(var(--rgb-surface-main));color:var(--text-muted);padding:14px}.panel-frame{grid-column:span 12;border:none;border-radius:var(--radius-lg);background:var(--bg-panel);overflow:hidden;box-shadow:none;animation:rise .4s ease-out;display:flex;flex-direction:column;min-height:0}.panel-header{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;padding:14px 18px 10px;border-bottom:none;background:var(--bg-panel-accent)}.panel-header h2{margin:0;font-size:1.02rem;letter-spacing:.02em}.panel-header p{margin:6px 0 0;font-size:.85rem;color:var(--text-muted)}.panel-body{padding:10px 12px 12px;min-height:0}.app-root-autoruns .autoruns-frame{height:100%}.app-root-autoruns .autoruns-frame .panel-body{flex:1 1 auto;overflow:hidden;display:flex;flex-direction:column;gap:10px;padding:10px 12px 12px;background:rgb(var(--rgb-background))}.status-chip{border:none;border-radius:999px;padding:4px 10px;color:var(--lime-main);font-size:.78rem;background:rgb(var(--rgb-surface-focus))}.assistant-toolbar{display:grid;gap:8px}.assistant-toolbar-actions{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));align-items:center;gap:8px}.assistant-toolbar-meta{display:flex;align-items:center;justify-content:space-between;gap:8px}.assistant-toolbar-meta-right{margin-left:auto;display:flex;align-items:center;justify-content:flex-end;gap:8px;min-width:0;flex-wrap:wrap}.assistant-live-status{color:var(--text-muted);font-size:.8rem;white-space:nowrap}.assistant-toolbar-error{margin:0}.assistant-copy-btn{width:100%;justify-self:stretch;background:transparent;border-color:transparent;color:var(--text-main);font-size:.6rem;line-height:1.1;white-space:nowrap;text-align:center;letter-spacing:0;padding:6px 8px;box-shadow:none;transform:none}.assistant-copy-btn:hover{background:rgb(var(--rgb-surface-focus));filter:none;box-shadow:none;transform:none}.assistant-copy-feedback{font-size:.76rem;color:var(--text-muted)}.assistant-copy-feedback.success{color:var(--lime-main)}.assistant-copy-feedback.error{color:var(--danger)}input,select,textarea,button{font-family:Manrope,sans-serif}label{display:flex;flex-direction:column;gap:6px;color:var(--text-muted);font-size:.84rem}input,select,textarea{border:none;border-radius:var(--radius-md);background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:10px 12px;outline:none;transition:background-color .18s ease}input:focus,select:focus,textarea:focus{border-color:transparent;box-shadow:none;outline:none;background:rgb(var(--rgb-surface-focus))}textarea{resize:vertical;min-height:86px}button{border:none;border-radius:999px;background:rgb(var(--rgb-surface-horizontal));color:rgb(var(--rgb-text-main));font-weight:700;font-size:.83rem;letter-spacing:.02em;cursor:pointer;padding:9px 14px;transition:background .2s ease,color .2s ease;outline:none;box-shadow:none}button:hover{border-color:transparent;background:rgb(var(--rgb-surface-focus))}button:disabled{opacity:.52;cursor:not-allowed}.button-row{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:12px}.checkbox-row{flex-direction:row;align-items:center;gap:8px;color:var(--text-main)}.grid-two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.prompt-manager-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}.full-width{grid-column:1 / -1}.tab-row{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px}.tab{background:rgb(var(--rgb-surface-main));color:var(--text-main);border:none}.tab.active{border-color:transparent;background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.assistant-chat-list{flex:1 1 auto;min-height:0;overflow:auto;overscroll-behavior:contain;display:flex;flex-direction:column;gap:8px;padding:10px;border:none;border-radius:12px;background:rgb(var(--rgb-surface-horizontal))}.assistant-chat-list .assistant-msg:first-child{margin-top:auto}.assistant-empty{padding:18px;text-align:center}.assistant-msg{border:none;border-radius:12px;background:rgb(var(--rgb-surface-focus));padding:8px 10px;display:grid;gap:6px}.assistant-msg.user{margin-left:12%;border-color:transparent;background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.assistant-msg.assistant{margin-right:12%;border-color:transparent}.assistant-msg.user .assistant-msg-head{color:rgba(var(--rgb-active-text),.9)}.assistant-msg.user .assistant-msg-body{color:rgb(var(--rgb-active-text))}.assistant-msg-head{display:flex;justify-content:space-between;gap:8px;margin-bottom:0;font-size:.74rem;color:var(--text-muted)}.assistant-msg-body{white-space:pre-wrap;line-height:1.35;font-size:.84rem}.assistant-trace{margin-top:6px;color:var(--text-muted);font-size:.75rem}.assistant-debug{margin-top:8px}.assistant-debug summary{cursor:pointer;color:var(--lime-main);font-size:.8rem}.assistant-compose{margin-top:0;display:grid;gap:8px;flex:0 0 auto}.assistant-send-row{align-items:center;margin-top:2px}.assistant-send-btn{margin-left:auto}.app-root-autoruns .assistant-panel-frame .panel-header{position:sticky;top:-12px;z-index:8;margin:-12px -12px 0;padding:12px 12px 10px;background:rgb(var(--rgb-surface-main))}.app-root-autoruns .assistant-panel-frame{overflow:visible}.app-root-autoruns .assistant-panel-frame .panel-body{flex:1 1 auto;padding:0 12px 12px;display:flex;flex-direction:column;min-height:0;overflow:hidden}.app-root-autoruns .assistant-panel-frame .assistant-live-shell{flex:1 1 auto;min-height:0;padding:12px;border-radius:14px;background:rgb(var(--rgb-background));display:flex;flex-direction:column;gap:10px}.app-root-autoruns .assistant-panel-frame .assistant-chat-list{overflow-y:auto}.app-root-autoruns .assistant-panel-frame .panel-header h2{margin:0;font-size:.95rem}.json-view{margin:0;width:100%;min-height:180px;max-height:420px;overflow:auto;background:rgb(var(--rgb-surface-horizontal));border:none;border-radius:var(--radius-md);padding:12px;color:rgb(var(--rgb-text-main));font-family:JetBrains Mono,Consolas,monospace;font-size:.78rem;line-height:1.45}.metrics-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px}.metrics-grid div{background:rgba(var(--rgb-surface-main),.8);border:none;border-radius:12px;padding:10px;display:flex;flex-direction:column;gap:4px}.metrics-grid span{color:var(--text-muted);font-size:.75rem}.metrics-grid strong{font-size:.84rem;color:var(--lime-main)}.history-list{display:grid;gap:8px;max-height:340px;overflow:auto}.history-item{width:100%;text-align:left;border-radius:12px;border:none;background:rgb(var(--rgb-surface-main));color:var(--text-main);padding:10px}.history-item p{margin:8px 0;color:var(--text-muted);font-size:.82rem}.history-item.selected{border-color:var(--line-strong)}.history-row{display:flex;justify-content:space-between;gap:8px;font-size:.76rem;color:var(--text-muted)}.runtime-grid{display:grid;grid-template-columns:1.2fr 1fr;gap:12px}.runtime-stack{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}.runtime-details{display:grid;gap:12px}.runtime-runs{max-height:360px;overflow:auto;display:grid;gap:8px}.eval-report-wrap{position:relative}.copy-cube-button{position:absolute;right:10px;bottom:10px;width:34px;height:34px;border-radius:10px;padding:0;min-width:34px;display:grid;place-items:center;font-size:.92rem;line-height:1}.muted{color:var(--text-muted)}.diff-summary{margin-top:10px;font-size:.82rem;color:var(--lime-main)}.error-text{margin-top:10px;color:var(--danger);font-size:.84rem}.autoruns-columns{display:flex;gap:12px;width:100%;min-height:0;flex:1 1 auto;overflow-x:auto;overflow-y:hidden;padding-bottom:4px}.autoruns-col{flex:0 0 var(--mode-column-width);width:var(--mode-column-width);height:100%;min-height:0;overflow:auto;border:none;border-radius:14px;background:rgb(var(--rgb-surface-main));padding:12px;scrollbar-gutter:stable}.autoruns-assistant-live-col{background:rgb(var(--rgb-surface-main));padding:12px;overflow:hidden;scrollbar-gutter:auto}.autoruns-assistant-live-col .panel-frame{height:100%;background:rgb(var(--rgb-surface-main))}.autoruns-col h3{margin:0;font-size:.95rem}.autoruns-col h4{margin:12px 0 8px;font-size:.82rem;color:var(--text-muted)}.autoruns-col-header{position:sticky;top:-12px;z-index:8;margin:-12px -12px 10px;padding:12px 12px 10px;background:rgb(var(--rgb-surface-main))}.autoruns-col-header .tab-row{margin:8px 0 0}.autoruns-col-header .autoruns-dialog-toolbar{margin-top:8px}.autoruns-form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.autoruns-meta-list{display:grid;gap:8px}.autoruns-meta-list>div{display:flex;justify-content:space-between;gap:8px;border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px 9px;font-size:.79rem}.autoruns-meta-list span{color:var(--text-muted)}.autoruns-prompt-details summary{cursor:pointer;color:var(--text-main);font-size:.8rem;margin-bottom:8px}.autoruns-prompt-details textarea{min-height:68px}.autoruns-stats-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-bottom:10px}.autoruns-stats-grid>div{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px;display:grid;gap:3px}.autoruns-stats-grid span{color:var(--text-muted);font-size:.74rem}.autoruns-stats-grid strong{color:var(--lime-main);font-size:.84rem}.autoruns-run-list{display:grid;gap:8px;max-height:none;min-height:0;flex:1 1 auto;overflow:auto;padding-right:2px}.autoruns-run-item{width:100%;text-align:left;border-radius:12px;border:none;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:10px;display:grid;gap:5px;transition:background-color .2s ease,box-shadow .2s ease}.autoruns-run-item.selected{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text));box-shadow:none}.autoruns-run-item.selected .autoruns-run-meta{color:rgba(var(--rgb-active-text),.95)}.autoruns-run-head,.autoruns-run-foot{display:flex;justify-content:space-between;gap:8px;font-size:.76rem}.autoruns-run-meta{color:var(--text-muted);font-size:.75rem;word-break:break-word}.autoruns-dialog-toolbar{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.autoruns-case-list{margin-top:8px;display:grid;gap:6px;max-height:180px;overflow:auto}.autoruns-case-item{width:100%;text-align:left;border-radius:10px;border:none;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:7px 8px;display:flex;justify-content:space-between;gap:6px;font-size:.76rem}.autoruns-case-item.selected{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text));box-shadow:none}.autoruns-dialog-view{margin-top:10px;border:none;border-radius:12px;background:rgb(var(--rgb-surface-horizontal));padding:10px;max-height:none;min-height:0;flex:1 1 auto;overflow:auto;display:grid;gap:8px}.autoruns-msg{border:none;border-radius:12px;background:rgb(var(--rgb-surface-focus));padding:8px 10px;display:grid;gap:6px}.autoruns-msg header,.autoruns-msg footer{display:flex;justify-content:space-between;gap:8px;font-size:.74rem;color:var(--text-muted)}.autoruns-msg-head-actions{display:flex;align-items:center;gap:8px}.autoruns-msg-case-tag{display:inline-flex;align-items:center;border-radius:999px;padding:2px 8px;font-size:.7rem;line-height:1;color:rgb(var(--rgb-active-text));background:rgba(var(--rgb-active),.24)}.autoruns-msg p{margin:0;white-space:pre-wrap;line-height:1.35;font-size:.84rem}.autoruns-comment-icon{border:none;background:transparent;color:rgb(var(--rgb-text-main));border-radius:0;min-width:20px;min-height:20px;width:20px;height:20px;padding:0;line-height:1;box-shadow:none;transform:none;display:inline-flex;align-items:center;justify-content:center}.autoruns-comment-icon:hover{background:transparent;color:rgb(var(--rgb-active));box-shadow:none;transform:none}.autoruns-comment-icon.commented{color:rgb(var(--rgb-active-text));background:transparent;box-shadow:none}.comment-icon-svg{width:20px;height:20px;stroke:currentColor;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;fill:none}.comment-icon-svg .comment-icon-dot{fill:currentColor}.comment-icon-svg.commented{fill:rgb(var(--rgb-active));stroke:rgb(var(--rgb-active))}.autoruns-comment-icon.commented .comment-icon-dot{fill:rgb(var(--rgb-active-text))}.autoruns-resolve-toggle{border:none;background:rgb(var(--rgb-surface-focus));color:rgb(var(--rgb-text-main));border-radius:999px;width:22px;height:22px;min-width:22px;min-height:22px;padding:0;display:inline-flex;align-items:center;justify-content:center;box-shadow:none;transform:none}.autoruns-resolve-toggle:hover{background:rgb(var(--rgb-surface-focus));color:rgb(var(--rgb-active));box-shadow:none;transform:none}.autoruns-resolve-toggle:disabled{opacity:.55;cursor:wait}.autoruns-resolve-toggle.resolved{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.resolve-icon-svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.resolve-icon-svg.resolved{fill:currentColor}.autoruns-msg-annotation{display:grid;gap:4px;border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:7px 8px;font-size:.78rem}.autoruns-comments-list{display:grid;gap:8px;max-height:none;min-height:0;flex:1 1 auto;overflow:auto;padding-right:2px;margin-top:6px}.autoruns-autogen-list{display:grid;gap:8px;max-height:none;min-height:0;overflow:auto;padding-right:2px}.autoruns-autogen-item{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px;display:grid;gap:5px;cursor:pointer}.autoruns-autogen-item.selected{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.autoruns-autogen-item.selected .autoruns-run-meta,.autoruns-autogen-item.selected p{color:rgba(var(--rgb-active-text),.95)}.autoruns-autogen-item header{display:flex;justify-content:space-between;gap:8px;font-size:.76rem}.autoruns-autogen-item p{margin:0;color:var(--text-muted);white-space:pre-wrap;font-size:.8rem}.autoruns-generated-questions{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px;display:grid;gap:8px}.autoruns-generated-questions-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.autoruns-generated-questions-list{display:grid;gap:6px;max-height:220px;overflow:auto;padding-right:2px}.autoruns-generated-question-item{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;border:none;border-radius:9px;background:rgb(var(--rgb-surface-focus));padding:6px 8px;font-size:.78rem}.autoruns-generated-question-item span{white-space:pre-wrap}.autoruns-remove-question-btn{flex:0 0 auto;border:none;border-radius:7px;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);min-width:24px;height:24px;font-size:.75rem;line-height:1}.autoruns-comment-item{width:100%;text-align:left;border-radius:12px;border:none;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:9px;display:grid;gap:6px;cursor:pointer}.autoruns-comment-item p{margin:0;white-space:pre-wrap;color:var(--text-muted);font-size:.79rem}.autoruns-comment-item.selected{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.autoruns-comment-item.selected p,.autoruns-comment-item.selected .autoruns-run-meta,.autoruns-comment-item.selected .muted{color:rgba(var(--rgb-active-text),.94)}.autoruns-comment-item.selected .autoruns-resolve-toggle{background:rgba(var(--rgb-active-text),.18);color:rgb(var(--rgb-active-text))}.autoruns-comment-head{display:flex;justify-content:space-between;align-items:center;gap:8px;font-size:.75rem}.autoruns-comment-head-actions{display:inline-flex;align-items:center;gap:8px}.autoruns-comment-filter-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:10px;margin-bottom:8px}.autoruns-resolved-filter-toggle{min-height:38px;white-space:nowrap}.autoruns-msg.assistant{margin-right:12%}.autoruns-msg.user{margin-left:12%;border-color:transparent;background:rgb(var(--rgb-surface-focus))}.autoruns-decomposition-list{margin:0;padding-left:18px;display:grid;gap:7px;font-size:.8rem}.autoruns-comment-modal-backdrop{position:fixed;inset:0;background:rgba(var(--rgb-background),.74);display:grid;place-items:center;z-index:1800;padding:12px}.autoruns-comment-modal{width:min(660px,100%);border:none;border-radius:16px;background:rgb(var(--rgb-surface-horizontal));box-shadow:var(--shadow);padding:14px;display:grid;gap:10px}.autoruns-comment-modal h3{margin:0;font-size:.95rem}.autoruns-comment-quote{margin:0;border:none;border-radius:10px;background:rgb(var(--rgb-surface-focus));padding:8px;white-space:pre-wrap;max-height:150px;overflow:auto;font-size:.82rem}.autoruns-rating-row{display:flex;gap:8px}.autoruns-rating-dot{width:34px;height:34px;border-radius:999px;padding:0;border:none;background:rgb(var(--rgb-surface-focus));color:var(--text-muted);font-size:.95rem;box-shadow:none;transform:none}.autoruns-rating-dot:hover{border-color:var(--line-strong);box-shadow:none;transform:none}.autoruns-rating-dot.active{border-color:var(--line-strong);color:rgb(var(--rgb-active-text));background:rgb(var(--rgb-active))}.autoruns-coverage-list{display:grid;gap:8px}.autoruns-coverage-item{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px}.autoruns-coverage-head{display:flex;justify-content:space-between;gap:8px;font-size:.76rem;margin-bottom:5px}.autoruns-coverage-head span{color:var(--text-muted)}.autoruns-coverage-bar{height:7px;border-radius:999px;background:rgb(var(--rgb-surface-focus));overflow:hidden}.autoruns-coverage-bar>div{height:100%;border-radius:999px;background:rgb(var(--rgb-active))}@media(max-width:1200px){:root{--mode-column-width: 400px}.metrics-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(max-width:920px){:root{--mode-column-width: 360px}.grid-two,.runtime-grid,.runtime-stack{grid-template-columns:1fr}.metrics-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.autoruns-form-grid,.autoruns-dialog-toolbar,.autoruns-stats-grid,.autoruns-comment-filter-row{grid-template-columns:1fr}}@media(max-width:640px){:root{--mode-column-width: 320px}.app-root{padding:18px 12px 24px}.metrics-grid{grid-template-columns:1fr}}@keyframes rise{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}} diff --git a/llm_normalizer/frontend/dist/assets/index-DMMD5-xN.js b/llm_normalizer/frontend/dist/assets/index-DMMD5-xN.js new file mode 100644 index 0000000..4ffd1b1 --- /dev/null +++ b/llm_normalizer/frontend/dist/assets/index-DMMD5-xN.js @@ -0,0 +1,13 @@ +(function(){const h=document.createElement("link").relList;if(h&&h.supports&&h.supports("modulepreload"))return;for(const P of document.querySelectorAll('link[rel="modulepreload"]'))D(P);new MutationObserver(P=>{for(const z of P)if(z.type==="childList")for(const K of z.addedNodes)K.tagName==="LINK"&&K.rel==="modulepreload"&&D(K)}).observe(document,{childList:!0,subtree:!0});function p(P){const z={};return P.integrity&&(z.integrity=P.integrity),P.referrerPolicy&&(z.referrerPolicy=P.referrerPolicy),P.crossOrigin==="use-credentials"?z.credentials="include":P.crossOrigin==="anonymous"?z.credentials="omit":z.credentials="same-origin",z}function D(P){if(P.ep)return;P.ep=!0;const z=p(P);fetch(P.href,z)}})();function Ic(i){return i&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i}var Pi={exports:{}},Bs={},Ri={exports:{}},me={};var uc;function hf(){if(uc)return me;uc=1;var i=Symbol.for("react.element"),h=Symbol.for("react.portal"),p=Symbol.for("react.fragment"),D=Symbol.for("react.strict_mode"),P=Symbol.for("react.profiler"),z=Symbol.for("react.provider"),K=Symbol.for("react.context"),ee=Symbol.for("react.forward_ref"),U=Symbol.for("react.suspense"),M=Symbol.for("react.memo"),G=Symbol.for("react.lazy"),L=Symbol.iterator;function H(d){return d===null||typeof d!="object"?null:(d=L&&d[L]||d["@@iterator"],typeof d=="function"?d:null)}var re={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},Ee=Object.assign,te={};function se(d,j,J){this.props=d,this.context=j,this.refs=te,this.updater=J||re}se.prototype.isReactComponent={},se.prototype.setState=function(d,j){if(typeof d!="object"&&typeof d!="function"&&d!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,d,j,"setState")},se.prototype.forceUpdate=function(d){this.updater.enqueueForceUpdate(this,d,"forceUpdate")};function b(){}b.prototype=se.prototype;function oe(d,j,J){this.props=d,this.context=j,this.refs=te,this.updater=J||re}var Se=oe.prototype=new b;Se.constructor=oe,Ee(Se,se.prototype),Se.isPureReactComponent=!0;var le=Array.isArray,Ce=Object.prototype.hasOwnProperty,Ae={current:null},$e={key:!0,ref:!0,__self:!0,__source:!0};function Qe(d,j,J){var ae,ue={},ce=null,ve=null;if(j!=null)for(ae in j.ref!==void 0&&(ve=j.ref),j.key!==void 0&&(ce=""+j.key),j)Ce.call(j,ae)&&!$e.hasOwnProperty(ae)&&(ue[ae]=j[ae]);var he=arguments.length-2;if(he===1)ue.children=J;else if(1>>1,j=T[d];if(0>>1;dP(ue,A))ceP(ve,ue)?(T[d]=ve,T[ce]=A,d=ce):(T[d]=ue,T[ae]=A,d=ae);else if(ceP(ve,A))T[d]=ve,T[ce]=A,d=ce;else break e}}return Q}function P(T,Q){var A=T.sortIndex-Q.sortIndex;return A!==0?A:T.id-Q.id}if(typeof performance=="object"&&typeof performance.now=="function"){var z=performance;i.unstable_now=function(){return z.now()}}else{var K=Date,ee=K.now();i.unstable_now=function(){return K.now()-ee}}var U=[],M=[],G=1,L=null,H=3,re=!1,Ee=!1,te=!1,se=typeof setTimeout=="function"?setTimeout:null,b=typeof clearTimeout=="function"?clearTimeout:null,oe=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function Se(T){for(var Q=p(M);Q!==null;){if(Q.callback===null)D(M);else if(Q.startTime<=T)D(M),Q.sortIndex=Q.expirationTime,h(U,Q);else break;Q=p(M)}}function le(T){if(te=!1,Se(T),!Ee)if(p(U)!==null)Ee=!0,Ne(Ce);else{var Q=p(M);Q!==null&&fe(le,Q.startTime-T)}}function Ce(T,Q){Ee=!1,te&&(te=!1,b(Qe),Qe=-1),re=!0;var A=H;try{for(Se(Q),L=p(U);L!==null&&(!(L.expirationTime>Q)||T&&!at());){var d=L.callback;if(typeof d=="function"){L.callback=null,H=L.priorityLevel;var j=d(L.expirationTime<=Q);Q=i.unstable_now(),typeof j=="function"?L.callback=j:L===p(U)&&D(U),Se(Q)}else D(U);L=p(U)}if(L!==null)var J=!0;else{var ae=p(M);ae!==null&&fe(le,ae.startTime-Q),J=!1}return J}finally{L=null,H=A,re=!1}}var Ae=!1,$e=null,Qe=-1,Ze=5,it=-1;function at(){return!(i.unstable_now()-itT||125d?(T.sortIndex=A,h(M,T),p(U)===null&&T===p(M)&&(te?(b(Qe),Qe=-1):te=!0,fe(le,A-d))):(T.sortIndex=j,h(U,T),Ee||re||(Ee=!0,Ne(Ce))),T},i.unstable_shouldYield=at,i.unstable_wrapCallback=function(T){var Q=H;return function(){var A=H;H=Q;try{return T.apply(this,arguments)}finally{H=A}}}})(Li)),Li}var mc;function _f(){return mc||(mc=1,Mi.exports=xf()),Mi.exports}var hc;function Sf(){if(hc)return wt;hc=1;var i=Ui(),h=_f();function p(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),U=Object.prototype.hasOwnProperty,M=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,G={},L={};function H(e){return U.call(L,e)?!0:U.call(G,e)?!1:M.test(e)?L[e]=!0:(G[e]=!0,!1)}function re(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function Ee(e,t,n,r){if(t===null||typeof t>"u"||re(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function te(e,t,n,r,s,l,u){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=s,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=l,this.removeEmptyString=u}var se={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){se[e]=new te(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];se[t]=new te(t,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){se[e]=new te(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){se[e]=new te(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){se[e]=new te(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){se[e]=new te(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){se[e]=new te(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){se[e]=new te(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){se[e]=new te(e,5,!1,e.toLowerCase(),null,!1,!1)});var b=/[\-:]([a-z])/g;function oe(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(b,oe);se[t]=new te(t,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(b,oe);se[t]=new te(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(b,oe);se[t]=new te(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){se[e]=new te(e,1,!1,e.toLowerCase(),null,!1,!1)}),se.xlinkHref=new te("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){se[e]=new te(e,1,!1,e.toLowerCase(),null,!0,!0)});function Se(e,t,n,r){var s=se.hasOwnProperty(t)?se[t]:null;(s!==null?s.type!==0:r||!(2c||s[u]!==l[c]){var f=` +`+s[u].replace(" at new "," at ");return e.displayName&&f.includes("")&&(f=f.replace("",e.displayName)),f}while(1<=u&&0<=c);break}}}finally{J=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?j(e):""}function ue(e){switch(e.tag){case 5:return j(e.type);case 16:return j("Lazy");case 13:return j("Suspense");case 19:return j("SuspenseList");case 0:case 2:case 15:return e=ae(e.type,!1),e;case 11:return e=ae(e.type.render,!1),e;case 1:return e=ae(e.type,!0),e;default:return""}}function ce(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case $e:return"Fragment";case Ae:return"Portal";case Ze:return"Profiler";case Qe:return"StrictMode";case We:return"Suspense";case be:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case at:return(e.displayName||"Context")+".Consumer";case it:return(e._context.displayName||"Context")+".Provider";case mt:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case ge:return t=e.displayName||null,t!==null?t:ce(e.type)||"Memo";case Ne:t=e._payload,e=e._init;try{return ce(e(t))}catch{}}return null}function ve(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return ce(t);case 8:return t===Qe?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function he(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function Z(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function we(e){var t=Z(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var s=n.get,l=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return s.call(this)},set:function(u){r=""+u,l.call(this,u)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(u){r=""+u},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function jt(e){e._valueTracker||(e._valueTracker=we(e))}function kn(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=Z(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function an(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function un(e,t){var n=t.checked;return A({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Un(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=he(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function Lt(e,t){t=t.checked,t!=null&&Se(e,"checked",t,!1)}function It(e,t){Lt(e,t);var n=he(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Jt(e,t.type,n):t.hasOwnProperty("defaultValue")&&Jt(e,t.type,he(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Cn(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Jt(e,t,n){(t!=="number"||an(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var kt=Array.isArray;function At(e,t,n,r){if(e=e.options,t){t={};for(var s=0;s"+t.valueOf().toString()+"",t=Ot.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Ct(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var Xt={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Fe=["Webkit","ms","Moz","O"];Object.keys(Xt).forEach(function(e){Fe.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),Xt[t]=Xt[e]})});function fn(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||Xt.hasOwnProperty(e)&&Xt[e]?(""+t).trim():t+"px"}function pn(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,s=fn(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,s):e[n]=s}}var Or=A({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function zt(e,t){if(t){if(Or[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(p(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(p(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(p(61))}if(t.style!=null&&typeof t.style!="object")throw Error(p(62))}}function mn(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var $t=null;function hn(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var gn=null,Zt=null,ht=null;function hr(e){if(e=Ns(e)){if(typeof gn!="function")throw Error(p(280));var t=e.stateNode;t&&(t=oo(t),gn(e.stateNode,e.type,t))}}function en(e){Zt?ht?ht.push(e):ht=[e]:Zt=e}function Qn(){if(Zt){var e=Zt,t=ht;if(ht=Zt=null,hr(e),t)for(e=0;e>>=0,e===0?32:31-(Ur(e)/Yo|0)|0}var Sr=64,wr=4194304;function qn(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function a(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,s=e.suspendedLanes,l=e.pingedLanes,u=n&268435455;if(u!==0){var c=u&~s;c!==0?r=qn(c):(l&=u,l!==0&&(r=qn(l)))}else u=n&~s,u!==0?r=qn(u):l!==0&&(r=qn(l));if(r===0)return 0;if(t!==0&&t!==r&&(t&s)===0&&(s=r&-r,l=t&-t,s>=l||s===16&&(l&4194240)!==0))return t;if((r&4)!==0&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function Vt(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-pe(t),e[t]=n}function Gn(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=vs),na=" ",ra=!1;function sa(e,t){switch(e){case"keyup":return ud.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function oa(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Vr=!1;function dd(e,t){switch(e){case"compositionend":return oa(t);case"keypress":return t.which!==32?null:(ra=!0,na);case"textInput":return e=t.data,e===na&&ra?null:e;default:return null}}function fd(e,t){if(Vr)return e==="compositionend"||!al&&sa(e,t)?(e=Ji(),Gs=nl=Zn=null,Vr=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=fa(n)}}function ma(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?ma(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function ha(){for(var e=window,t=an();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=an(e.document)}return t}function dl(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function Sd(e){var t=ha(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&ma(n.ownerDocument.documentElement,n)){if(r!==null&&dl(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var s=n.textContent.length,l=Math.min(r.start,s);r=r.end===void 0?l:Math.min(r.end,s),!e.extend&&l>r&&(s=r,r=l,l=s),s=pa(n,l);var u=pa(n,r);s&&u&&(e.rangeCount!==1||e.anchorNode!==s.node||e.anchorOffset!==s.offset||e.focusNode!==u.node||e.focusOffset!==u.offset)&&(t=t.createRange(),t.setStart(s.node,s.offset),e.removeAllRanges(),l>r?(e.addRange(t),e.extend(u.node,u.offset)):(t.setEnd(u.node,u.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Qr=null,fl=null,Ss=null,pl=!1;function ga(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;pl||Qr==null||Qr!==an(r)||(r=Qr,"selectionStart"in r&&dl(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),Ss&&_s(Ss,r)||(Ss=r,r=no(fl,"onSelect"),0br||(e.current=Cl[br],Cl[br]=null,br--)}function Te(e,t){br++,Cl[br]=e.current,e.current=t}var rr={},nt=nr(rr),vt=nr(!1),Cr=rr;function Jr(e,t){var n=e.type.contextTypes;if(!n)return rr;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var s={},l;for(l in n)s[l]=t[l];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=s),s}function yt(e){return e=e.childContextTypes,e!=null}function lo(){Ie(vt),Ie(nt)}function Ma(e,t,n){if(nt.current!==rr)throw Error(p(168));Te(nt,t),Te(vt,n)}function La(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var s in r)if(!(s in t))throw Error(p(108,ve(e)||"Unknown",s));return A({},n,r)}function io(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||rr,Cr=nt.current,Te(nt,e),Te(vt,vt.current),!0}function Ia(e,t,n){var r=e.stateNode;if(!r)throw Error(p(169));n?(e=La(e,t,Cr),r.__reactInternalMemoizedMergedChildContext=e,Ie(vt),Ie(nt),Te(nt,e)):Ie(vt),Te(vt,n)}var Ln=null,ao=!1,Nl=!1;function Aa(e){Ln===null?Ln=[e]:Ln.push(e)}function Id(e){ao=!0,Aa(e)}function sr(){if(!Nl&&Ln!==null){Nl=!0;var e=0,t=ke;try{var n=Ln;for(ke=1;e>=u,s-=u,In=1<<32-pe(t)+s|n<ne?(Xe=X,X=null):Xe=X.sibling;var xe=k(y,X,x[ne],R);if(xe===null){X===null&&(X=Xe);break}e&&X&&xe.alternate===null&&t(y,X),m=l(xe,m,ne),Y===null?W=xe:Y.sibling=xe,Y=xe,X=Xe}if(ne===x.length)return n(y,X),De&&Er(y,ne),W;if(X===null){for(;nene?(Xe=X,X=null):Xe=X.sibling;var pr=k(y,X,xe.value,R);if(pr===null){X===null&&(X=Xe);break}e&&X&&pr.alternate===null&&t(y,X),m=l(pr,m,ne),Y===null?W=pr:Y.sibling=pr,Y=pr,X=Xe}if(xe.done)return n(y,X),De&&Er(y,ne),W;if(X===null){for(;!xe.done;ne++,xe=x.next())xe=E(y,xe.value,R),xe!==null&&(m=l(xe,m,ne),Y===null?W=xe:Y.sibling=xe,Y=xe);return De&&Er(y,ne),W}for(X=r(y,X);!xe.done;ne++,xe=x.next())xe=$(X,y,ne,xe.value,R),xe!==null&&(e&&xe.alternate!==null&&X.delete(xe.key===null?ne:xe.key),m=l(xe,m,ne),Y===null?W=xe:Y.sibling=xe,Y=xe);return e&&X.forEach(function(mf){return t(y,mf)}),De&&Er(y,ne),W}function He(y,m,x,R){if(typeof x=="object"&&x!==null&&x.type===$e&&x.key===null&&(x=x.props.children),typeof x=="object"&&x!==null){switch(x.$$typeof){case Ce:e:{for(var W=x.key,Y=m;Y!==null;){if(Y.key===W){if(W=x.type,W===$e){if(Y.tag===7){n(y,Y.sibling),m=s(Y,x.props.children),m.return=y,y=m;break e}}else if(Y.elementType===W||typeof W=="object"&&W!==null&&W.$$typeof===Ne&&Ua(W)===Y.type){n(y,Y.sibling),m=s(Y,x.props),m.ref=Es(y,Y,x),m.return=y,y=m;break e}n(y,Y);break}else t(y,Y);Y=Y.sibling}x.type===$e?(m=Dr(x.props.children,y.mode,R,x.key),m.return=y,y=m):(R=zo(x.type,x.key,x.props,null,y.mode,R),R.ref=Es(y,m,x),R.return=y,y=R)}return u(y);case Ae:e:{for(Y=x.key;m!==null;){if(m.key===Y)if(m.tag===4&&m.stateNode.containerInfo===x.containerInfo&&m.stateNode.implementation===x.implementation){n(y,m.sibling),m=s(m,x.children||[]),m.return=y,y=m;break e}else{n(y,m);break}else t(y,m);m=m.sibling}m=ji(x,y.mode,R),m.return=y,y=m}return u(y);case Ne:return Y=x._init,He(y,m,Y(x._payload),R)}if(kt(x))return B(y,m,x,R);if(Q(x))return V(y,m,x,R);po(y,x)}return typeof x=="string"&&x!==""||typeof x=="number"?(x=""+x,m!==null&&m.tag===6?(n(y,m.sibling),m=s(m,x),m.return=y,y=m):(n(y,m),m=wi(x,y.mode,R),m.return=y,y=m),u(y)):n(y,m)}return He}var es=Ba(!0),Ha=Ba(!1),mo=nr(null),ho=null,ts=null,Ll=null;function Il(){Ll=ts=ho=null}function Al(e){var t=mo.current;Ie(mo),e._currentValue=t}function Dl(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function ns(e,t){ho=e,Ll=ts=null,e=e.dependencies,e!==null&&e.firstContext!==null&&((e.lanes&t)!==0&&(xt=!0),e.firstContext=null)}function Kt(e){var t=e._currentValue;if(Ll!==e)if(e={context:e,memoizedValue:t,next:null},ts===null){if(ho===null)throw Error(p(308));ts=e,ho.dependencies={lanes:0,firstContext:e}}else ts=ts.next=e;return t}var Pr=null;function Ol(e){Pr===null?Pr=[e]:Pr.push(e)}function Va(e,t,n,r){var s=t.interleaved;return s===null?(n.next=n,Ol(t)):(n.next=s.next,s.next=n),t.interleaved=n,Dn(e,r)}function Dn(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var or=!1;function zl(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function Qa(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function On(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function lr(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,(ye&2)!==0){var s=r.pending;return s===null?t.next=t:(t.next=s.next,s.next=t),r.pending=t,Dn(e,n)}return s=r.interleaved,s===null?(t.next=t,Ol(r)):(t.next=s.next,s.next=t),r.interleaved=t,Dn(e,n)}function go(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,jr(e,n)}}function Wa(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var s=null,l=null;if(n=n.firstBaseUpdate,n!==null){do{var u={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};l===null?s=l=u:l=l.next=u,n=n.next}while(n!==null);l===null?s=l=t:l=l.next=t}else s=l=t;n={baseState:r.baseState,firstBaseUpdate:s,lastBaseUpdate:l,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function vo(e,t,n,r){var s=e.updateQueue;or=!1;var l=s.firstBaseUpdate,u=s.lastBaseUpdate,c=s.shared.pending;if(c!==null){s.shared.pending=null;var f=c,_=f.next;f.next=null,u===null?l=_:u.next=_,u=f;var C=e.alternate;C!==null&&(C=C.updateQueue,c=C.lastBaseUpdate,c!==u&&(c===null?C.firstBaseUpdate=_:c.next=_,C.lastBaseUpdate=f))}if(l!==null){var E=s.baseState;u=0,C=_=f=null,c=l;do{var k=c.lane,$=c.eventTime;if((r&k)===k){C!==null&&(C=C.next={eventTime:$,lane:0,tag:c.tag,payload:c.payload,callback:c.callback,next:null});e:{var B=e,V=c;switch(k=t,$=n,V.tag){case 1:if(B=V.payload,typeof B=="function"){E=B.call($,E,k);break e}E=B;break e;case 3:B.flags=B.flags&-65537|128;case 0:if(B=V.payload,k=typeof B=="function"?B.call($,E,k):B,k==null)break e;E=A({},E,k);break e;case 2:or=!0}}c.callback!==null&&c.lane!==0&&(e.flags|=64,k=s.effects,k===null?s.effects=[c]:k.push(c))}else $={eventTime:$,lane:k,tag:c.tag,payload:c.payload,callback:c.callback,next:null},C===null?(_=C=$,f=E):C=C.next=$,u|=k;if(c=c.next,c===null){if(c=s.shared.pending,c===null)break;k=c,c=k.next,k.next=null,s.lastBaseUpdate=k,s.shared.pending=null}}while(!0);if(C===null&&(f=E),s.baseState=f,s.firstBaseUpdate=_,s.lastBaseUpdate=C,t=s.shared.interleaved,t!==null){s=t;do u|=s.lane,s=s.next;while(s!==t)}else l===null&&(s.shared.lanes=0);Mr|=u,e.lanes=u,e.memoizedState=E}}function Ka(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=Hl.transition;Hl.transition={};try{e(!1),t()}finally{ke=n,Hl.transition=r}}function du(){return qt().memoizedState}function zd(e,t,n){var r=cr(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},fu(e))pu(t,n);else if(n=Va(e,t,n,r),n!==null){var s=pt();ln(n,e,r,s),mu(n,t,r)}}function $d(e,t,n){var r=cr(e),s={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(fu(e))pu(t,s);else{var l=e.alternate;if(e.lanes===0&&(l===null||l.lanes===0)&&(l=t.lastRenderedReducer,l!==null))try{var u=t.lastRenderedState,c=l(u,n);if(s.hasEagerState=!0,s.eagerState=c,tn(c,u)){var f=t.interleaved;f===null?(s.next=s,Ol(t)):(s.next=f.next,f.next=s),t.interleaved=s;return}}catch{}n=Va(e,t,s,r),n!==null&&(s=pt(),ln(n,e,r,s),mu(n,t,r))}}function fu(e){var t=e.alternate;return e===ze||t!==null&&t===ze}function pu(e,t){Ms=_o=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function mu(e,t,n){if((n&4194240)!==0){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,jr(e,n)}}var jo={readContext:Kt,useCallback:rt,useContext:rt,useEffect:rt,useImperativeHandle:rt,useInsertionEffect:rt,useLayoutEffect:rt,useMemo:rt,useReducer:rt,useRef:rt,useState:rt,useDebugValue:rt,useDeferredValue:rt,useTransition:rt,useMutableSource:rt,useSyncExternalStore:rt,useId:rt,unstable_isNewReconciler:!1},Fd={readContext:Kt,useCallback:function(e,t){return Sn().memoizedState=[e,t===void 0?null:t],e},useContext:Kt,useEffect:ru,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,So(4194308,4,lu.bind(null,t,e),n)},useLayoutEffect:function(e,t){return So(4194308,4,e,t)},useInsertionEffect:function(e,t){return So(4,2,e,t)},useMemo:function(e,t){var n=Sn();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=Sn();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=zd.bind(null,ze,e),[r.memoizedState,e]},useRef:function(e){var t=Sn();return e={current:e},t.memoizedState=e},useState:tu,useDebugValue:bl,useDeferredValue:function(e){return Sn().memoizedState=e},useTransition:function(){var e=tu(!1),t=e[0];return e=Od.bind(null,e[1]),Sn().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=ze,s=Sn();if(De){if(n===void 0)throw Error(p(407));n=n()}else{if(n=t(),Ye===null)throw Error(p(349));(Tr&30)!==0||Ja(r,t,n)}s.memoizedState=n;var l={value:n,getSnapshot:t};return s.queue=l,ru(Xa.bind(null,r,l,e),[e]),r.flags|=2048,As(9,Ya.bind(null,r,l,n,t),void 0,null),n},useId:function(){var e=Sn(),t=Ye.identifierPrefix;if(De){var n=An,r=In;n=(r&~(1<<32-pe(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Ls++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=u.createElement(n,{is:r.is}):(e=u.createElement(n),n==="select"&&(u=e,r.multiple?u.multiple=!0:r.size&&(u.size=r.size))):e=u.createElementNS(e,n),e[xn]=t,e[Cs]=r,Au(e,t,!1,!1),t.stateNode=e;e:{switch(u=mn(n,r),n){case"dialog":Le("cancel",e),Le("close",e),s=r;break;case"iframe":case"object":case"embed":Le("load",e),s=r;break;case"video":case"audio":for(s=0;sis&&(t.flags|=128,r=!0,Ds(l,!1),t.lanes=4194304)}else{if(!r)if(e=yo(u),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),Ds(l,!0),l.tail===null&&l.tailMode==="hidden"&&!u.alternate&&!De)return st(t),null}else 2*_e()-l.renderingStartTime>is&&n!==1073741824&&(t.flags|=128,r=!0,Ds(l,!1),t.lanes=4194304);l.isBackwards?(u.sibling=t.child,t.child=u):(n=l.last,n!==null?n.sibling=u:t.child=u,l.last=u)}return l.tail!==null?(t=l.tail,l.rendering=t,l.tail=t.sibling,l.renderingStartTime=_e(),t.sibling=null,n=Oe.current,Te(Oe,r?n&1|2:n&1),t):(st(t),null);case 22:case 23:return xi(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&(t.mode&1)!==0?(Tt&1073741824)!==0&&(st(t),t.subtreeFlags&6&&(t.flags|=8192)):st(t),null;case 24:return null;case 25:return null}throw Error(p(156,t.tag))}function qd(e,t){switch(Pl(t),t.tag){case 1:return yt(t.type)&&lo(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return rs(),Ie(vt),Ie(nt),Bl(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 5:return Fl(t),null;case 13:if(Ie(Oe),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(p(340));Zr()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Ie(Oe),null;case 4:return rs(),null;case 10:return Al(t.type._context),null;case 22:case 23:return xi(),null;case 24:return null;default:return null}}var Eo=!1,ot=!1,Gd=typeof WeakSet=="function"?WeakSet:Set,F=null;function os(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Ue(e,t,r)}else n.current=null}function ii(e,t,n){try{n()}catch(r){Ue(e,t,r)}}var zu=!1;function bd(e,t){if(xl=Ks,e=ha(),dl(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var s=r.anchorOffset,l=r.focusNode;r=r.focusOffset;try{n.nodeType,l.nodeType}catch{n=null;break e}var u=0,c=-1,f=-1,_=0,C=0,E=e,k=null;t:for(;;){for(var $;E!==n||s!==0&&E.nodeType!==3||(c=u+s),E!==l||r!==0&&E.nodeType!==3||(f=u+r),E.nodeType===3&&(u+=E.nodeValue.length),($=E.firstChild)!==null;)k=E,E=$;for(;;){if(E===e)break t;if(k===n&&++_===s&&(c=u),k===l&&++C===r&&(f=u),($=E.nextSibling)!==null)break;E=k,k=E.parentNode}E=$}n=c===-1||f===-1?null:{start:c,end:f}}else n=null}n=n||{start:0,end:0}}else n=null;for(_l={focusedElem:e,selectionRange:n},Ks=!1,F=t;F!==null;)if(t=F,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,F=e;else for(;F!==null;){t=F;try{var B=t.alternate;if((t.flags&1024)!==0)switch(t.tag){case 0:case 11:case 15:break;case 1:if(B!==null){var V=B.memoizedProps,He=B.memoizedState,y=t.stateNode,m=y.getSnapshotBeforeUpdate(t.elementType===t.type?V:rn(t.type,V),He);y.__reactInternalSnapshotBeforeUpdate=m}break;case 3:var x=t.stateNode.containerInfo;x.nodeType===1?x.textContent="":x.nodeType===9&&x.documentElement&&x.removeChild(x.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(p(163))}}catch(R){Ue(t,t.return,R)}if(e=t.sibling,e!==null){e.return=t.return,F=e;break}F=t.return}return B=zu,zu=!1,B}function Os(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var s=r=r.next;do{if((s.tag&e)===e){var l=s.destroy;s.destroy=void 0,l!==void 0&&ii(t,n,l)}s=s.next}while(s!==r)}}function Po(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function ai(e){var t=e.ref;if(t!==null){var n=e.stateNode;e.tag,e=n,typeof t=="function"?t(e):t.current=e}}function $u(e){var t=e.alternate;t!==null&&(e.alternate=null,$u(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[xn],delete t[Cs],delete t[kl],delete t[Md],delete t[Ld])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Fu(e){return e.tag===5||e.tag===3||e.tag===4}function Uu(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Fu(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function ui(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=so));else if(r!==4&&(e=e.child,e!==null))for(ui(e,t,n),e=e.sibling;e!==null;)ui(e,t,n),e=e.sibling}function ci(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ci(e,t,n),e=e.sibling;e!==null;)ci(e,t,n),e=e.sibling}var et=null,sn=!1;function ir(e,t,n){for(n=n.child;n!==null;)Bu(e,t,n),n=n.sibling}function Bu(e,t,n){if(I&&typeof I.onCommitFiberUnmount=="function")try{I.onCommitFiberUnmount(v,n)}catch{}switch(n.tag){case 5:ot||os(n,t);case 6:var r=et,s=sn;et=null,ir(e,t,n),et=r,sn=s,et!==null&&(sn?(e=et,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):et.removeChild(n.stateNode));break;case 18:et!==null&&(sn?(e=et,n=n.stateNode,e.nodeType===8?jl(e.parentNode,n):e.nodeType===1&&jl(e,n),ms(e)):jl(et,n.stateNode));break;case 4:r=et,s=sn,et=n.stateNode.containerInfo,sn=!0,ir(e,t,n),et=r,sn=s;break;case 0:case 11:case 14:case 15:if(!ot&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){s=r=r.next;do{var l=s,u=l.destroy;l=l.tag,u!==void 0&&((l&2)!==0||(l&4)!==0)&&ii(n,t,u),s=s.next}while(s!==r)}ir(e,t,n);break;case 1:if(!ot&&(os(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(c){Ue(n,t,c)}ir(e,t,n);break;case 21:ir(e,t,n);break;case 22:n.mode&1?(ot=(r=ot)||n.memoizedState!==null,ir(e,t,n),ot=r):ir(e,t,n);break;default:ir(e,t,n)}}function Hu(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Gd),t.forEach(function(r){var s=sf.bind(null,e,r);n.has(r)||(n.add(r),r.then(s,s))})}}function on(e,t){var n=t.deletions;if(n!==null)for(var r=0;rs&&(s=u),r&=~l}if(r=s,r=_e()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Yd(r/1960))-r,10e?16:e,ur===null)var r=!1;else{if(e=ur,ur=null,Io=0,(ye&6)!==0)throw Error(p(331));var s=ye;for(ye|=4,F=e.current;F!==null;){var l=F,u=l.child;if((F.flags&16)!==0){var c=l.deletions;if(c!==null){for(var f=0;f_e()-pi?Ir(e,0):fi|=n),St(e,t)}function tc(e,t){t===0&&((e.mode&1)===0?t=1:(t=wr,wr<<=1,(wr&130023424)===0&&(wr=4194304)));var n=pt();e=Dn(e,t),e!==null&&(Vt(e,t,n),St(e,n))}function rf(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),tc(e,n)}function sf(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,s=e.memoizedState;s!==null&&(n=s.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(p(314))}r!==null&&r.delete(t),tc(e,n)}var nc;nc=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||vt.current)xt=!0;else{if((e.lanes&n)===0&&(t.flags&128)===0)return xt=!1,Wd(e,t,n);xt=(e.flags&131072)!==0}else xt=!1,De&&(t.flags&1048576)!==0&&Da(t,co,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;No(e,t),e=t.pendingProps;var s=Jr(t,nt.current);ns(t,n),s=Ql(null,t,r,e,s,n);var l=Wl();return t.flags|=1,typeof s=="object"&&s!==null&&typeof s.render=="function"&&s.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,yt(r)?(l=!0,io(t)):l=!1,t.memoizedState=s.state!==null&&s.state!==void 0?s.state:null,zl(t),s.updater=ko,t.stateNode=s,s._reactInternals=t,Yl(t,r,e,n),t=ti(null,t,r,!0,l,n)):(t.tag=0,De&&l&&El(t),ft(null,t,s,n),t=t.child),t;case 16:r=t.elementType;e:{switch(No(e,t),e=t.pendingProps,s=r._init,r=s(r._payload),t.type=r,s=t.tag=lf(r),e=rn(r,e),s){case 0:t=ei(null,t,r,e,n);break e;case 1:t=Pu(null,t,r,e,n);break e;case 11:t=ju(null,t,r,e,n);break e;case 14:t=ku(null,t,r,rn(r.type,e),n);break e}throw Error(p(306,r,""))}return t;case 0:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:rn(r,s),ei(e,t,r,s,n);case 1:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:rn(r,s),Pu(e,t,r,s,n);case 3:e:{if(Ru(t),e===null)throw Error(p(387));r=t.pendingProps,l=t.memoizedState,s=l.element,Qa(e,t),vo(t,r,null,n);var u=t.memoizedState;if(r=u.element,l.isDehydrated)if(l={element:r,isDehydrated:!1,cache:u.cache,pendingSuspenseBoundaries:u.pendingSuspenseBoundaries,transitions:u.transitions},t.updateQueue.baseState=l,t.memoizedState=l,t.flags&256){s=ss(Error(p(423)),t),t=Tu(e,t,r,n,s);break e}else if(r!==s){s=ss(Error(p(424)),t),t=Tu(e,t,r,n,s);break e}else for(Rt=tr(t.stateNode.containerInfo.firstChild),Pt=t,De=!0,nn=null,n=Ha(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Zr(),r===s){t=zn(e,t,n);break e}ft(e,t,r,n)}t=t.child}return t;case 5:return qa(t),e===null&&Tl(t),r=t.type,s=t.pendingProps,l=e!==null?e.memoizedProps:null,u=s.children,Sl(r,s)?u=null:l!==null&&Sl(r,l)&&(t.flags|=32),Eu(e,t),ft(e,t,u,n),t.child;case 6:return e===null&&Tl(t),null;case 13:return Mu(e,t,n);case 4:return $l(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=es(t,null,r,n):ft(e,t,r,n),t.child;case 11:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:rn(r,s),ju(e,t,r,s,n);case 7:return ft(e,t,t.pendingProps,n),t.child;case 8:return ft(e,t,t.pendingProps.children,n),t.child;case 12:return ft(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,s=t.pendingProps,l=t.memoizedProps,u=s.value,Te(mo,r._currentValue),r._currentValue=u,l!==null)if(tn(l.value,u)){if(l.children===s.children&&!vt.current){t=zn(e,t,n);break e}}else for(l=t.child,l!==null&&(l.return=t);l!==null;){var c=l.dependencies;if(c!==null){u=l.child;for(var f=c.firstContext;f!==null;){if(f.context===r){if(l.tag===1){f=On(-1,n&-n),f.tag=2;var _=l.updateQueue;if(_!==null){_=_.shared;var C=_.pending;C===null?f.next=f:(f.next=C.next,C.next=f),_.pending=f}}l.lanes|=n,f=l.alternate,f!==null&&(f.lanes|=n),Dl(l.return,n,t),c.lanes|=n;break}f=f.next}}else if(l.tag===10)u=l.type===t.type?null:l.child;else if(l.tag===18){if(u=l.return,u===null)throw Error(p(341));u.lanes|=n,c=u.alternate,c!==null&&(c.lanes|=n),Dl(u,n,t),u=l.sibling}else u=l.child;if(u!==null)u.return=l;else for(u=l;u!==null;){if(u===t){u=null;break}if(l=u.sibling,l!==null){l.return=u.return,u=l;break}u=u.return}l=u}ft(e,t,s.children,n),t=t.child}return t;case 9:return s=t.type,r=t.pendingProps.children,ns(t,n),s=Kt(s),r=r(s),t.flags|=1,ft(e,t,r,n),t.child;case 14:return r=t.type,s=rn(r,t.pendingProps),s=rn(r.type,s),ku(e,t,r,s,n);case 15:return Cu(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:rn(r,s),No(e,t),t.tag=1,yt(r)?(e=!0,io(t)):e=!1,ns(t,n),gu(t,r,s),Yl(t,r,s,n),ti(null,t,r,!0,e,n);case 19:return Iu(e,t,n);case 22:return Nu(e,t,n)}throw Error(p(156,t.tag))};function rc(e,t){return xr(e,t)}function of(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function bt(e,t,n,r){return new of(e,t,n,r)}function Si(e){return e=e.prototype,!(!e||!e.isReactComponent)}function lf(e){if(typeof e=="function")return Si(e)?1:0;if(e!=null){if(e=e.$$typeof,e===mt)return 11;if(e===ge)return 14}return 2}function fr(e,t){var n=e.alternate;return n===null?(n=bt(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function zo(e,t,n,r,s,l){var u=2;if(r=e,typeof e=="function")Si(e)&&(u=1);else if(typeof e=="string")u=5;else e:switch(e){case $e:return Dr(n.children,s,l,t);case Qe:u=8,s|=8;break;case Ze:return e=bt(12,n,t,s|2),e.elementType=Ze,e.lanes=l,e;case We:return e=bt(13,n,t,s),e.elementType=We,e.lanes=l,e;case be:return e=bt(19,n,t,s),e.elementType=be,e.lanes=l,e;case fe:return $o(n,s,l,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case it:u=10;break e;case at:u=9;break e;case mt:u=11;break e;case ge:u=14;break e;case Ne:u=16,r=null;break e}throw Error(p(130,e==null?e:typeof e,""))}return t=bt(u,n,t,s),t.elementType=e,t.type=r,t.lanes=l,t}function Dr(e,t,n,r){return e=bt(7,e,r,t),e.lanes=n,e}function $o(e,t,n,r){return e=bt(22,e,r,t),e.elementType=fe,e.lanes=n,e.stateNode={isHidden:!1},e}function wi(e,t,n){return e=bt(6,e,null,t),e.lanes=n,e}function ji(e,t,n){return t=bt(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function af(e,t,n,r,s){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=gt(0),this.expirationTimes=gt(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=gt(0),this.identifierPrefix=r,this.onRecoverableError=s,this.mutableSourceEagerHydrationData=null}function ki(e,t,n,r,s,l,u,c,f){return e=new af(e,t,n,c,f),t===1?(t=1,l===!0&&(t|=8)):t=0,l=bt(3,null,null,t),e.current=l,l.stateNode=e,l.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},zl(l),e}function uf(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(h){console.error(h)}}return i(),Ti.exports=Sf(),Ti.exports}var vc;function jf(){if(vc)return Wo;vc=1;var i=wf();return Wo.createRoot=i.createRoot,Wo.hydrateRoot=i.hydrateRoot,Wo}var kf=jf();const Cf=Ic(kf),Nf="/api";async function Re(i,h){const p=await fetch(`${Nf}${i}`,{...h,headers:{"Content-Type":"application/json",...h?.headers??{}}}),D=await p.json();if(!p.ok){const P=D.error?.message??"Ошибка запроса";throw new Error(P)}return D}const Me={async listModels(i){return Re("/llm/models",{method:"POST",body:JSON.stringify({llmProvider:i.llmProvider,apiKey:i.apiKey,model:i.model,baseUrl:i.baseUrl})})},async testConnection(i){return Re("/llm/test-connection",{method:"POST",body:JSON.stringify({llmProvider:i.llmProvider,apiKey:i.apiKey,model:i.model,baseUrl:i.baseUrl})})},async normalize(i){return Re("/normalize",{method:"POST",body:JSON.stringify({llmProvider:i.connection.llmProvider,apiKey:i.connection.apiKey,model:i.connection.model,baseUrl:i.connection.baseUrl,temperature:i.connection.temperature,maxOutputTokens:i.connection.maxOutputTokens,promptVersion:i.promptVersion,systemPrompt:i.prompts.systemPrompt,developerPrompt:i.prompts.developerPrompt,domainPrompt:i.prompts.domainPrompt,fewShotExamples:i.prompts.fewShotExamples,userQuestion:i.query.userQuestion,context:{period_hint:i.query.periodHint??"",business_context:i.query.businessContext??"",expected_route:i.query.expectedRoute??""},saveAsTestCase:!!i.saveAsTestCase,useMock:!!i.useMock})})},async loadHistory(){return Re("/history")},async loadTrace(i){return Re(`/history/${i}`)},async loadPresets(){return Re("/presets")},async savePreset(i){return Re("/presets/save",{method:"POST",body:JSON.stringify(i)})},async runEval(i){return Re("/eval/run",{method:"POST",body:JSON.stringify({normalizeConfig:{llmProvider:i.connection.llmProvider,apiKey:i.connection.apiKey,model:i.connection.model,baseUrl:i.connection.baseUrl,temperature:i.connection.temperature,maxOutputTokens:i.connection.maxOutputTokens,promptVersion:i.promptVersion,systemPrompt:i.prompts.systemPrompt,developerPrompt:i.prompts.developerPrompt,domainPrompt:i.prompts.domainPrompt,fewShotExamples:i.prompts.fewShotExamples},caseIds:i.caseIds,useMock:!!i.useMock,mode:i.mode??"standard",caseSetFile:i.caseSetFile,rawQuestions:i.rawQuestions,eval_target:i.evalTarget,compare_with_report_file:i.compareWithReportFile,analysis_date:i.analysisDate})})},async startEvalRunAsync(i){return Re("/eval/run-async/start",{method:"POST",body:JSON.stringify({normalizeConfig:{llmProvider:i.connection.llmProvider,apiKey:i.connection.apiKey,model:i.connection.model,baseUrl:i.connection.baseUrl,temperature:i.connection.temperature,maxOutputTokens:i.connection.maxOutputTokens,promptVersion:i.promptVersion,systemPrompt:i.prompts.systemPrompt,developerPrompt:i.prompts.developerPrompt,domainPrompt:i.prompts.domainPrompt,fewShotExamples:i.prompts.fewShotExamples},caseIds:i.caseIds,useMock:!!i.useMock,mode:i.mode??"standard",caseSetFile:i.caseSetFile,rawQuestions:i.rawQuestions,eval_target:i.evalTarget,compare_with_report_file:i.compareWithReportFile,questions:i.questions,analysis_date:i.analysisDate})})},async loadEvalRunAsyncStatus(i){return Re(`/eval/run-async/${encodeURIComponent(i)}`)},async startRun(){return Re("/accounting-agent/v1/runs/start",{method:"POST",body:JSON.stringify({initiator:"ndc_operator",source:"gui"})})},async finishRun(i){return Re("/accounting-agent/v1/runs/finish",{method:"POST",body:JSON.stringify({runId:i,status:"DONE",source:"gui",reason:"Остановлено оператором из GUI"})})},async listRuns(){return Re("/accounting-agent/v1/runs")},async listResults(){return Re("/accounting-agent/v1/results")},async runTrace(i){return Re(`/accounting-agent/v1/trace/run/${i}`)},async sendAssistantMessage(i){return Re("/assistant/message",{method:"POST",body:JSON.stringify({session_id:i.sessionId??"",mode:"assistant",message:i.userMessage,user_message:i.userMessage,llmProvider:i.connection.llmProvider,apiKey:i.connection.apiKey,model:i.connection.model,baseUrl:i.connection.baseUrl,temperature:i.connection.temperature,maxOutputTokens:i.connection.maxOutputTokens,promptVersion:i.promptVersion??"address_query_runtime_v1",systemPrompt:i.prompts.systemPrompt,developerPrompt:i.prompts.developerPrompt,domainPrompt:i.prompts.domainPrompt,fewShotExamples:i.prompts.fewShotExamples,context:{period_hint:i.context?.periodHint??"",business_context:i.context?.businessContext??""},useMock:!!i.useMock})})},async loadAssistantSession(i){return Re(`/assistant/session/${i}`)},async loadAutoRunsHistory(i){const h=new URLSearchParams;i?.from&&h.set("from",i.from),i?.to&&h.set("to",i.to),i?.target&&h.set("target",i.target),i?.mode&&h.set("mode",i.mode),i?.use_mock&&h.set("use_mock",i.use_mock),i?.prompt_contains&&h.set("prompt_contains",i.prompt_contains),typeof i?.limit=="number"&&h.set("limit",String(i.limit)),typeof i?.scan_limit=="number"&&h.set("scan_limit",String(i.scan_limit));const p=h.toString();return Re(`/autoruns/history${p?`?${p}`:""}`)},async loadAutoRunDetail(i){return Re(`/autoruns/history/${encodeURIComponent(i)}`)},async loadAutoRunCaseDialog(i,h){return Re(`/autoruns/history/${encodeURIComponent(i)}/case/${encodeURIComponent(h)}/dialog`)},async loadAutoRunAnnotations(i){const h=new URLSearchParams;i?.run_id&&h.set("run_id",i.run_id),i?.case_id&&h.set("case_id",i.case_id),typeof i?.min_rating=="number"&&h.set("min_rating",String(i.min_rating)),i?.manual_case_decision&&h.set("manual_case_decision",i.manual_case_decision),typeof i?.limit=="number"&&h.set("limit",String(i.limit));const p=h.toString();return Re(`/autoruns/annotations${p?`?${p}`:""}`)},async saveAutoRunAnnotation(i){return Re("/autoruns/annotations",{method:"POST",body:JSON.stringify(i)})},async updateAutoRunAnnotation(i){return Re(`/autoruns/annotations/${encodeURIComponent(i.annotation_id)}`,{method:"PATCH",body:JSON.stringify({resolved:i.resolved,resolved_by:i.resolved_by})})},async loadAutoRunPostAnalysis(i){const h=new URLSearchParams;i?.run_id&&h.set("run_id",i.run_id),typeof i?.limit_per_queue=="number"&&h.set("limit_per_queue",String(i.limit_per_queue)),typeof i?.annotation_limit=="number"&&h.set("annotation_limit",String(i.annotation_limit)),typeof i?.scan_limit=="number"&&h.set("scan_limit",String(i.scan_limit)),i?.from&&h.set("from",i.from),i?.to&&h.set("to",i.to),i?.target&&h.set("target",i.target),i?.mode&&h.set("mode",i.mode),i?.use_mock&&h.set("use_mock",i.use_mock),i?.prompt_contains&&h.set("prompt_contains",i.prompt_contains);const p=h.toString();return Re(`/autoruns/post-analysis${p?`?${p}`:""}`)},async loadAutoRunAutogenHistory(i){const h=new URLSearchParams;i?.mode&&h.set("mode",i.mode),typeof i?.limit=="number"&&h.set("limit",String(i.limit));const p=h.toString();return Re(`/autoruns/autogen/history${p?`?${p}`:""}`)},async loadAutoRunAutogenPersonalityCatalog(){return Re("/autoruns/autogen/personality-catalog")},async generateAutoRunQuestions(i){return Re("/autoruns/autogen/generate",{method:"POST",body:JSON.stringify(i)})}};function Mt({value:i}){return o.jsx("pre",{className:"json-view",children:JSON.stringify(i??{},null,2)})}function jn({title:i,subtitle:h,actions:p,className:D,hideHeader:P,children:z}){return o.jsxs("section",{className:D?`panel-frame ${D}`:"panel-frame",children:[P?null:o.jsxs("header",{className:"panel-header",children:[o.jsxs("div",{children:[o.jsx("h2",{children:i}),h?o.jsx("p",{children:h}):null]}),p?o.jsx("div",{className:"panel-actions",children:p}):null]}),o.jsx("div",{className:"panel-body",children:z})]})}function Ef(i){const h=new Date(i);return Number.isNaN(h.getTime())?i:h.toLocaleString("ru-RU")}function Pf({sessionId:i,conversation:h,statusText:p,errorMessage:D,useMock:P,appLogs:z}){const K=h.filter(M=>M.role==="assistant").length,ee=h.filter(M=>M.role==="user").length,U=h.length>0?h[h.length-1]:null;return o.jsxs(jn,{title:"SAM",subtitle:"System Assistant Monitor: срез по текущей сессии и логам.",children:[o.jsxs("div",{className:"metrics-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"session_id"}),o.jsx("strong",{children:i||"новая сессия"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"mock_mode"}),o.jsx("strong",{children:P?"on":"off"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"сообщений пользователя"}),o.jsx("strong",{children:ee})]}),o.jsxs("div",{children:[o.jsx("span",{children:"ответов ассистента"}),o.jsx("strong",{children:K})]}),o.jsxs("div",{children:[o.jsx("span",{children:"статус"}),o.jsx("strong",{children:p||"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"ошибка"}),o.jsx("strong",{children:D||"нет"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"последнее сообщение"}),o.jsx("strong",{children:U?.created_at?Ef(U.created_at):"нет данных"})]})]}),o.jsx("h3",{style:{marginTop:12},children:"Последние системные логи"}),o.jsx(Mt,{value:z.slice(0,120)})]})}const Rf=/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json|debug_payload|technical_breakdown)\b/i,Tf=[/\b(?:debug_payload_json|technical_breakdown_json)\b/i,/\b(?:route_summary|semantic_profile|domain_scope|relation_patterns|account_scope)\b/i,/\b(?:coverage_report|retrieval_status|problem_unit_state|candidate_evidence)\b/i,/\b(?:graph_domain_scope|graph_runtime|selection_reason|why_included)\b/i];function Mf(i){try{return JSON.stringify(i,null,2)}catch{return String(i)}}function Lf(i){const h=String(i??""),p=h.match(Rf);return(p?h.slice(0,p.index):h).replace(/###\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json)[\s\S]*?(?:```[\s\S]*?```|$)/gi,"").replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json)\b[\s\S]*$/gi,"").split(/\r?\n/g).map(K=>K.trimEnd()).filter(K=>K.trim().length>0).filter(K=>!Tf.some(ee=>ee.test(K))).join(` +`).trim()}function If(i,h,p="default"){const D=p==="technical",P=[];P.push("# Assistant conversation export"),P.push(`session_id: ${i||"n/a"}`),P.push(`export_mode: ${p}`),P.push(`exported_at: ${new Date().toISOString()}`),P.push("");for(let z=0;z{L.current&&H.current&&(L.current.scrollTop=L.current.scrollHeight)},[h]),S.useEffect(()=>()=>{re.current!==null&&window.clearTimeout(re.current)},[]);async function oe(le){if(h.length===0)return;const Ce=If(i,h,le),Ae=await Of(Ce);b(le==="technical"?"тех":"чат"),te(Ae?"success":"error"),re.current!==null&&window.clearTimeout(re.current),re.current=window.setTimeout(()=>{te("idle")},2200)}function Se(){if(!L.current)return;const le=L.current,Ce=le.scrollHeight-le.scrollTop-le.clientHeight;H.current=Ce<16}return o.jsx(jn,{className:"assistant-panel-frame",title:"Режим ассистента",children:o.jsxs("div",{className:"assistant-live-shell",children:[o.jsxs("div",{className:"assistant-toolbar",children:[o.jsxs("div",{className:"assistant-toolbar-actions",children:[o.jsx("button",{type:"button",className:"assistant-copy-btn",onClick:()=>{oe("default")},disabled:h.length===0,title:"Экспорт только user-facing чата",children:"Скопировать чат"}),o.jsx("button",{type:"button",className:"assistant-copy-btn",onClick:()=>{oe("technical")},disabled:h.length===0,title:"Технический экспорт с debug payload",children:"Скопировать техчат"}),o.jsx("button",{type:"button",className:"assistant-copy-btn",onClick:()=>ee(),disabled:U&&h.length===0,children:"Сбросить сессию"})]}),o.jsxs("div",{className:"assistant-toolbar-meta",children:[i?o.jsx("span",{className:"status-chip",children:`session: ${i}`}):null,o.jsxs("div",{className:"assistant-toolbar-meta-right",children:[M?o.jsx("span",{className:"assistant-live-status",children:M}):null,Ee==="success"?o.jsxs("span",{className:"assistant-copy-feedback success",children:["Скопировано (",se,")"]}):null,Ee==="error"?o.jsx("span",{className:"assistant-copy-feedback error",children:"Ошибка копирования"}):null]})]}),G?o.jsx("p",{className:"error-text assistant-toolbar-error",children:G}):null]}),o.jsx("div",{ref:L,className:"assistant-chat-list",onScroll:Se,children:h.map(le=>o.jsxs("article",{className:`assistant-msg ${le.role}`,children:[o.jsxs("header",{className:"assistant-msg-head",children:[o.jsx("strong",{children:Af(le.role)}),o.jsx("span",{children:Df(le.created_at)})]}),o.jsx("div",{className:"assistant-msg-body",children:le.text}),le.role==="assistant"&&le.debug?o.jsxs("details",{className:"assistant-debug",children:[o.jsx("summary",{children:"Показать технический разбор"}),o.jsx(Mt,{value:le.debug})]}):null]},le.message_id))}),o.jsxs("div",{className:"assistant-compose",children:[o.jsxs("label",{className:"full-width",children:["Сообщение",o.jsx("textarea",{value:p,onChange:le=>D(le.target.value),rows:4,placeholder:"Введите вопрос к данным компании..."})]}),o.jsxs("div",{className:"button-row assistant-send-row",children:[o.jsxs("label",{className:"checkbox-row",children:[o.jsx("input",{type:"checkbox",checked:P,onChange:le=>z(le.target.checked)}),"Mock-режим"]}),o.jsx("button",{type:"button",className:"assistant-send-btn",onClick:()=>K(),disabled:U||!p.trim(),children:U?"Выполняю...":"Отправить"})]})]})]})})}const Ii={fromLocal:"",toLocal:"",target:"all",mode:"all",useMock:"any",promptContains:"",limit:120},Ko="needs_dialog_policy_fix",lt="__all__",Jo="__live__:",yc="ndc_autoruns_ui_config_v1",xc="ndc-autoruns-save",Ai=["Анализ запроса","Получение данных","Подготовка ответа"],Fi=[{id:"general",label:"Общий контур",domain:"",defaultPrompt:"Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл."}];function zf(i=Fi){return i.reduce((h,p)=>(h[p.id]=p.defaultPrompt,h),{})}const _c={mode:"codex_creative",count:24,personalityId:"general",personalityPrompts:zf(),persistToEvalCases:!0,generatedBy:"manual_reviewer"};function Di(i){const h=String(i??"").trim();return/^\d{4}-\d{2}-\d{2}$/.test(h)?h:""}function $f(i){const h=i.getFullYear(),p=String(i.getMonth()+1).padStart(2,"0"),D=String(i.getDate()).padStart(2,"0"),P=String(i.getHours()).padStart(2,"0"),z=String(i.getMinutes()).padStart(2,"0");return`${h}-${p}-${D}T${P}:${z}`}function Sc(){const i=new Date;return i.setDate(i.getDate()-14),$f(i)}function qo(i){if(!i.trim())return;const h=Date.parse(i);if(Number.isFinite(h))return new Date(h).toISOString()}function mr(i){if(!i)return"нет данных";const h=Date.parse(i);return Number.isFinite(h)?new Date(h).toLocaleString("ru-RU"):i}function Ff(i,h){return h<=0?0:Math.max(0,Math.min(100,Number((i/h*100).toFixed(1))))}function Go(i){return typeof i!="number"?"нет данных":`${i.toFixed(1)}%`}function Uf(i){return i==="assistant_stage1"?"assistant/s1":i==="assistant_stage2"?"assistant/s2":i==="assistant_p0"?"assistant/p0":i}function wc(i){return i==="up"?"Рост":i==="down"?"Регресс":"Без изменений"}function Bf(i,h){return i.find(p=>p.case_id===h)??null}function jc(i){const h=Math.max(1,Math.min(5,Math.round(i)));return`${"●".repeat(h)}${"○".repeat(5-h)}`}function kc(i){return i.length===0?o.jsx("p",{className:"muted",children:"Покрытие доменов пока не сформировано."}):o.jsx("div",{className:"autoruns-coverage-list",children:i.map(h=>{const p=Ff(h.closed_cases,h.total_cases);return o.jsxs("div",{className:"autoruns-coverage-item",children:[o.jsxs("div",{className:"autoruns-coverage-head",children:[o.jsx("strong",{children:h.domain}),o.jsxs("span",{children:[h.closed_cases,"/",h.total_cases," (",p,"%)"]})]}),o.jsx("div",{className:"autoruns-coverage-bar",children:o.jsx("div",{style:{width:`${p}%`}})})]},h.domain)})})}function bo(i){return`${Jo}${i}`}function us(i){return i.startsWith(Jo)}function Cc(i){return i.startsWith(Jo)?i.slice(Jo.length):""}function Dc(i){const h=i.report_summary?.run_timestamp??i.created_at,p=Math.max(0,i.total_cases-i.completed_cases);return{run_id:bo(i.job_id),eval_target:i.eval_target,run_timestamp:h,mode:"single-pass-strict",llm_provider:null,model:null,use_mock:null,analysis_date:i.report_summary?.analysis_date??i.analysis_date??null,prompt_version:null,schema_version:null,suite_id:i.case_set_file,cases_total:i.total_cases,requests_total:null,report_path:`async_job:${i.job_id}`,score_index:i.report_summary?.score_index??null,blocking_failures:0,quality_failures:0,closed_cases:i.completed_cases,open_cases:p,domain_coverage:[{domain:"runtime",total_cases:i.total_cases,closed_cases:i.completed_cases}]}}function Hs(i,h){const p=Dc(i),D=i.cases.map(G=>({case_id:G.case_id,domain:null,query_class:null,status:G.status==="completed"?"closed":G.status==="failed"?"open":"unknown",score_index:null,trace_id:null,reply_type:null,session_id:`${i.run_id}-${G.case_id}`,dialog_available:G.messages.length>0,commented_count:0,latest_annotation_at:null,avg_rating:null,checks:null,metric_subscores:null})),z=h!==lt&&D.some(G=>G.case_id===h)?h:D.length>0?lt:"",K={ok:!0,run:p,coverage:{closed_cases:i.completed_cases,open_cases:Math.max(0,i.total_cases-i.completed_cases),domain_coverage:[{domain:"runtime",total_cases:i.total_cases,closed_cases:i.completed_cases}]},cases:D,annotations_summary:{total:0},report:i.report_summary?{run_id:i.report_summary.run_id,run_timestamp:i.report_summary.run_timestamp,score_index:i.report_summary.score_index,cases_total:i.report_summary.cases_total,analysis_date:i.report_summary.analysis_date??i.analysis_date??null}:{}},ee=[];let U=0;if(z===lt)for(const G of i.cases)for(let L=0;LL.case_id===z)??null;for(let L=0;L<(G?.messages.length??0);L+=1){const H=G?.messages[L];H&&ee.push({...H,message_index:L,case_id:z,case_message_index:L,commented:!1,annotation:null})}}const M={ok:!0,run_id:p.run_id,case_id:z,source:"assistant_session",session_id:z===lt?`${i.run_id}::__all__`:`${i.run_id}-${z}`,messages:ee,decomposition:[],assistant_mode:{status:i.status,completed_cases:i.completed_cases,total_cases:i.total_cases},annotations:[]};return{detail:K,dialog:M,caseId:z}}function Hf({commented:i}){const h=i?"comment-icon-svg commented":"comment-icon-svg";return o.jsxs("svg",{className:h,viewBox:"0 0 24 24","aria-hidden":"true",focusable:"false",children:[o.jsx("path",{d:"M5 6.5h14v9H11.5l-4.5 3v-3H5z"}),o.jsx("circle",{className:"comment-icon-dot",cx:"9",cy:"11",r:"1.05"}),o.jsx("circle",{className:"comment-icon-dot",cx:"12",cy:"11",r:"1.05"}),o.jsx("circle",{className:"comment-icon-dot",cx:"15",cy:"11",r:"1.05"})]})}function Nc({resolved:i}){return o.jsxs("svg",{className:i?"resolve-icon-svg resolved":"resolve-icon-svg",viewBox:"0 0 16 16","aria-hidden":"true",focusable:"false",children:[o.jsx("circle",{cx:"8",cy:"8",r:"6.2"}),i?o.jsx("path",{d:"M5.1 8.2 7.2 10.3 11 6.5"}):null]})}function Vf({connection:i,prompts:h,assistantPromptVersion:p,decompositionPromptVersion:D,showAssistantMode:P,showDecompositionMode:z,showProgressMode:K,showCommentsMode:ee,onLog:U}){const[M,G]=S.useState({...Ii,fromLocal:Sc()}),[L,H]=S.useState(""),[re,Ee]=S.useState(null),[te,se]=S.useState(null),[b,oe]=S.useState(null),[Se,le]=S.useState([]),[Ce,Ae]=S.useState("all"),[$e,Qe]=S.useState(!1),[Ze,it]=S.useState(null),[at,mt]=S.useState([]),[We,be]=S.useState(""),[ge,Ne]=S.useState(""),[fe,T]=S.useState(""),[Q,A]=S.useState(Fi),[d,j]=S.useState(_c),[J,ae]=S.useState([]),[ue,ce]=S.useState(""),[ve,he]=S.useState([]),[Z,we]=S.useState(null),[jt,kn]=S.useState(null),[an,un]=S.useState(!1),[Un,Lt]=S.useState(!1),[It,Cn]=S.useState(!1),[Jt,kt]=S.useState(!1),[At,Yt]=S.useState(!1),[Bn,cn]=S.useState(!1),[Hn,dn]=S.useState(!1),[Dt,Ot]=S.useState(!1),[Vn,Ct]=S.useState(""),[Xt,Fe]=S.useState(""),[fn,pn]=S.useState(""),[Or,zt]=S.useState([]),[mn,$t]=S.useState(""),[hn,gn]=S.useState(!1),[Zt,ht]=S.useState(!1),[hr,en]=S.useState(""),[Qn,Nn]=S.useState(""),[Wn,Nt]=S.useState(String(Ii.limit)),[gr,ut]=S.useState(String(_c.count)),[ie,Ke]=S.useState({open:!1,caseId:"",caseMessageIndex:-1,messageIndex:-1,rating:3,comment:"",manualCaseDecision:Ko,annotationAuthor:"manual_reviewer",saving:!1,error:""}),En=S.useRef(!1),ct=S.useRef(null),Ft=S.useMemo(()=>Q.find(a=>a.id===d.personalityId)??Q[0]??Fi[0],[d.personalityId,Q]),q=S.useMemo(()=>J.find(a=>a.generation_id===ue)??J[0]??null,[J,ue]),vn=te?Bf(te.cases,fe):null,Be=S.useMemo(()=>$e?Se.filter(a=>!a.resolved):Se,[Se,$e]),je=Be.find(a=>a.annotation_id===We)??null,Pn=b?.messages.find(a=>a.message_index===ie.messageIndex)??null,Ut=S.useMemo(()=>{if(!b||ie.messageIndex<0)return null;for(let a=ie.messageIndex-1;a>=0;a-=1){const g=b.messages[a];if(g?.role==="user")return g}return null},[ie.messageIndex,b]),vr=S.useMemo(()=>{if(Be.length===0)return null;const a=Be.reduce((g,w)=>g+w.rating,0)/Be.length;return Number(a.toFixed(2))},[Be]),Rn=S.useMemo(()=>{const a=[...re?.items??[]];return Z&&a.unshift(Dc(Z)),ge&&!a.some(g=>g.run_id===ge)&&te?.run&&a.unshift(te.run),a},[Z,re?.items,te?.run,ge]),de=S.useCallback(a=>{U?.(`[autoruns] ${a}`)},[U]);function zr(){let a=0;en(Ai[0]);const g=window.setInterval(()=>{a=Math.min(a+1,Ai.length-1),en(Ai[a])},650);return()=>window.clearInterval(g)}const yr=S.useCallback(()=>{pn(""),zt([]),$t(""),en(""),Nn(""),de("Live-чат ассистента в истории автопрогонов сброшен.")},[de]),xr=S.useCallback(async()=>{const a=mn.trim();if(!a)return;ht(!0),Nn(""),$t(""),zt(w=>[...w,{message_id:`autoruns-live-${Date.now()}`,session_id:fn||"pending",role:"user",text:a,reply_type:null,created_at:new Date().toISOString(),trace_id:null,debug:null}]);const g=zr();try{const w=await Me.sendAssistantMessage({connection:i,prompts:h,userMessage:a,sessionId:fn||void 0,promptVersion:p,useMock:hn});pn(w.session_id),zt(w.conversation),en("Ответ готов"),de(`Live-ответ ассистента получен: trace=${w.debug.trace_id}`)}catch(w){const O=w instanceof Error?w.message:String(w);Nn(O),en("Ошибка ассистента"),de(`Live-чат ассистента: ошибка отправки сообщения: ${O}`)}finally{g(),ht(!1)}},[mn,fn,hn,p,i,de,h]),Kn=S.useCallback(a=>{const g=a.trim();if(!g){Nt(String(M.limit));return}if(!/^\d+$/.test(g)){Nt(String(M.limit));return}const w=Number.parseInt(g,10);if(!Number.isFinite(w)){Nt(String(M.limit));return}const O=Math.max(1,Math.min(500,w));O!==M.limit&&G(Pe=>({...Pe,limit:O})),Nt(String(O))},[M.limit]),_r=S.useCallback(a=>{const g=a.trim();if(!g){ut(String(d.count));return}if(!/^\d+$/.test(g)){ut(String(d.count));return}const w=Number.parseInt(g,10);if(!Number.isFinite(w)){ut(String(d.count));return}const O=Math.max(1,Math.min(200,w));O!==d.count&&j(Pe=>({...Pe,count:O})),ut(String(O))},[d.count]),Tn=S.useCallback(async()=>{Ot(!0);try{const a=await Me.loadAutoRunAnnotations({limit:800,manual_case_decision:Ce});le(a.items),it(a.manual_case_decision_schema??null),mt(a.available_manual_case_decisions??[]),be(g=>a.items.length===0?"":a.items.some(w=>w.annotation_id===g)?g:a.items[0].annotation_id)}catch(a){de(`Annotations load error: ${a instanceof Error?a.message:String(a)}`)}finally{Ot(!1)}},[Ce,de]),_e=S.useCallback(async()=>{kt(!0);try{const a=await Me.loadAutoRunAutogenHistory({limit:180});ae(a.items)}catch(a){de(`Autogen history load error: ${a instanceof Error?a.message:String(a)}`)}finally{kt(!1)}},[de]),$r=S.useCallback(async()=>{try{const g=(await Me.loadAutoRunAutogenPersonalityCatalog()).items.map(w=>({id:String(w.id??"").trim(),label:String(w.label??"").trim(),domain:typeof w.domain=="string"?w.domain.trim():"",defaultPrompt:String(w.default_prompt??"").trim()})).filter(w=>w.id.length>0&&w.label.length>0);if(g.length===0)return;A(g.map(w=>({id:w.id,label:w.label,domain:w.domain||"",defaultPrompt:w.defaultPrompt||"Генерируй реалистичные вопросы бухгалтера по выбранному профилю. Не выдумывай непокрытые возможности."})))}catch(a){de(`Autogen personality catalog load error: ${a instanceof Error?a.message:String(a)}`)}},[de]),dt=S.useCallback(async()=>{Cn(!0);try{const a=await Me.loadAutoRunPostAnalysis({run_id:ge&&!us(ge)?ge:void 0,limit_per_queue:30,annotation_limit:1500,from:qo(M.fromLocal),to:qo(M.toLocal),target:M.target,mode:M.mode,use_mock:M.useMock,prompt_contains:M.promptContains.trim()||void 0});kn(a)}catch(a){de(`Post-analysis load error: ${a instanceof Error?a.message:String(a)}`),kn(null)}finally{Cn(!1)}},[M.fromLocal,M.mode,M.promptContains,M.target,M.toLocal,M.useMock,de,ge]),Fr=S.useCallback(async()=>{un(!0),Fe("");try{const a=d.personalityPrompts[d.personalityId]??"",g=[h.systemPrompt,h.developerPrompt,h.domainPrompt,h.schemaNotes,h.fewShotExamples].join(` +`).slice(0,900),w=await Me.generateAutoRunQuestions({mode:d.mode,count:d.count,domain:Ft.domain||void 0,persist_to_eval_cases:d.persistToEvalCases,generated_by:d.generatedBy.trim()||void 0,llm:{llm_provider:i.llmProvider,api_key:i.apiKey,model:i.model,base_url:i.baseUrl,temperature:i.temperature,max_output_tokens:i.maxOutputTokens},context:{llm_provider:i.llmProvider,model:i.model,assistant_prompt_version:p,decomposition_prompt_version:D,prompt_fingerprint:g,autogen_personality_id:Ft.id,autogen_personality_prompt:a.trim()||void 0}});de(`Generated ${w.generation.count} questions (${w.generation.mode}) id=${w.generation.generation_id}`+(w.generation.saved_case_set_file?` saved=${w.generation.saved_case_set_file}`:"")),ce(w.generation.generation_id),he([...w.generation.questions??[]]),await _e()}catch(a){const g=a instanceof Error?a.message:String(a);Fe(`Автогенерация: ${g}`),de(`Autogen generate error: ${g}`)}finally{un(!1)}},[p,d.count,d.generatedBy,d.mode,d.personalityId,d.personalityPrompts,d.persistToEvalCases,i.apiKey,i.baseUrl,i.llmProvider,i.maxOutputTokens,i.model,i.temperature,D,_e,de,h.developerPrompt,h.domainPrompt,h.fewShotExamples,h.schemaNotes,h.systemPrompt,Ft.domain,Ft.id]),Bt=S.useCallback(async(a,g)=>{if(us(a)){const w=Cc(a);if(Z&&Z.job_id===w){const O=Hs(Z,g);Ne(a),T(O.caseId),oe(O.dialog);return}oe(null);return}dn(!0);try{const w=await Me.loadAutoRunCaseDialog(a,g);oe(w)}catch(w){const O=w instanceof Error?w.message:String(w);Fe(`Диалог кейса: ${O}`),oe(null),de(`Dialog load error for ${a}/${g}: ${O}`)}finally{dn(!1)}},[Z,de]),Ht=S.useCallback(async(a,g)=>{if(us(a)){const w=Cc(a);if(Z&&Z.job_id===w){const O=Hs(Z,g??lt);Ne(a),T(O.caseId),se(O.detail),oe(O.dialog);return}Ne(a),T(""),se(null),oe(null);return}cn(!0);try{const w=await Me.loadAutoRunDetail(a);se(w);const O=(g&&(g===lt||w.cases.some(Pe=>Pe.case_id===g))?g:"")||(w.cases.length>0?lt:"")||"";Ne(a),T(O),O?await Bt(a,O):oe(null)}catch(w){const O=w instanceof Error?w.message:String(w);Fe(`Детализация прогона: ${O}`),se(null),oe(null),de(`Run detail load error for ${a}: ${O}`)}finally{cn(!1)}},[Z,Bt,de]),yn=S.useCallback(async a=>{Yt(!0),Fe("");try{const g=await Me.loadAutoRunsHistory({from:qo(M.fromLocal),to:qo(M.toLocal),target:M.target,mode:M.mode,use_mock:M.useMock,prompt_contains:M.promptContains.trim()||void 0,limit:M.limit});if(Ee(g),g.items.length===0){Ne(""),T(""),se(null),oe(null);return}const w=a?.keepSelection??!0,O=a?.preferredRunId??"",Pe=a?.preferredCaseId??"",gt=w&&O&&g.items.some(Vt=>Vt.run_id===O)?O:g.items[0].run_id;await Ht(gt,w?Pe:void 0),dt()}catch(g){const w=g instanceof Error?g.message:String(g);Fe(`История прогонов: ${w}`),de(`History load error: ${w}`)}finally{Yt(!1)}},[M.fromLocal,M.limit,M.mode,M.promptContains,M.target,M.toLocal,M.useMock,dt,Ht,de]),v=S.useCallback(()=>{ct.current!==null&&(window.clearTimeout(ct.current),ct.current=null)},[]),I=S.useCallback(async a=>{try{const g=await Me.loadEvalRunAsyncStatus(a);we(g.job);const w=bo(a);if(ge===w){const O=Hs(g.job,fe||lt);se(O.detail),oe(O.dialog),T(O.caseId)}if(g.job.status==="completed"){v(),Lt(!1);const O=g.job.report_summary?.run_id??g.job.run_id;await yn({keepSelection:!0,preferredRunId:O||ge,preferredCaseId:lt}),await _e(),we(null);return}if(g.job.status==="failed"){v(),Lt(!1),Fe(`Запуск прогонов: ${g.job.error??"неизвестная ошибка"}`),de(`Autogen async run failed: ${g.job.error??"unknown error"}`);return}v(),ct.current=window.setTimeout(()=>{I(a)},500)}catch(g){v(),Lt(!1);const w=g instanceof Error?g.message:String(g);Fe(`Запуск прогонов: ${w}`),de(`Autogen async status error: ${w}`)}},[_e,yn,de,fe,ge,v]),N=S.useCallback(async()=>{v(),Lt(!0),Fe("");try{const a=q;if(!a)throw new Error("История автогенерации пуста. Сначала сгенерируйте пачку вопросов.");const g=ve.map(jr=>jr.trim()).filter(jr=>jr.length>0);if(g.length===0)throw new Error("Нет вопросов для запуска: список пустой после ручного редактирования.");const w=M.useMock==="true",O=Di(L),gt=(await Me.startEvalRunAsync({connection:i,prompts:h,promptVersion:p,mode:"single-pass-strict",caseSetFile:a.saved_case_set_file??void 0,useMock:w,evalTarget:"assistant_stage1",questions:g,analysisDate:O||void 0})).job;we(gt);const Vt=bo(gt.job_id),Gn=Hs(gt,lt);Ne(Vt),T(Gn.caseId),se(Gn.detail),oe(Gn.dialog),de(`Запущен async-прогон job=${gt.job_id}, run_id=${gt.run_id}, вопросов=${g.length}`+(a.saved_case_set_file?`, base_case_set=${a.saved_case_set_file}`:"")+(O?`, analysis_date=${O}`:", analysis_date=current_state")),I(gt.job_id)}catch(a){const g=a instanceof Error?a.message:String(a);Fe(`Запуск прогонов: ${g}`),de(`Autogen run error: ${g}`),Lt(!1)}},[L,p,i,ve,M.useMock,de,I,h,q,v]),pe=S.useCallback(a=>{if(a.role!=="assistant")return;const g=a.case_id??fe,w=a.case_message_index??a.message_index;Ke({open:!0,caseId:g,caseMessageIndex:w,messageIndex:a.message_index,rating:a.annotation?.rating??3,comment:a.annotation?.comment??"",manualCaseDecision:a.annotation?.manual_case_decision??Ko,annotationAuthor:a.annotation?.annotation_author??d.generatedBy,saving:!1,error:""})},[d.generatedBy,fe]),Ur=S.useCallback(a=>{Ke(g=>g.saving&&!a?.force?g:{open:!1,caseId:"",caseMessageIndex:-1,messageIndex:-1,rating:3,comment:"",manualCaseDecision:Ko,annotationAuthor:d.generatedBy,saving:!1,error:""})},[d.generatedBy]),Yo=S.useCallback(async()=>{const a=ge,g=ie.caseId,w=ie.caseMessageIndex;if(!(!a||!g||w<0)){if(us(a)){Ke(O=>({...O,error:"Комментарий можно сохранить после завершения прогона."}));return}if(!ie.comment.trim()){Ke(O=>({...O,error:"Добавьте комментарий."}));return}Ke(O=>({...O,saving:!0,error:""}));try{await Me.saveAutoRunAnnotation({run_id:a,case_id:g,message_index:w,rating:ie.rating,comment:ie.comment.trim(),manual_case_decision:ie.manualCaseDecision,annotation_author:ie.annotationAuthor.trim()||void 0}),Ur({force:!0}),Promise.all([Ht(a,fe),Tn(),dt()]).catch(O=>{const Pe=O instanceof Error?O.message:String(O);Fe(`Обновление после комментария: ${Pe}`),de(`Comment refresh error: ${Pe}`)})}catch(O){Ke(Pe=>({...Pe,saving:!1,error:O instanceof Error?O.message:String(O)}))}}},[Ur,ie.annotationAuthor,ie.caseId,ie.caseMessageIndex,ie.comment,ie.manualCaseDecision,ie.rating,Tn,dt,Ht,de,fe,ge]),Vs=S.useCallback(a=>{le(g=>g.map(w=>w.annotation_id===a.annotation_id?{...w,...a}:w)),oe(g=>g&&{...g,annotations:g.annotations.map(w=>w.annotation_id===a.annotation_id?a:w),messages:g.messages.map(w=>!w.annotation||w.annotation.annotation_id!==a.annotation_id?w:{...w,commented:!0,annotation:a})})},[]),Sr=S.useCallback(async(a,g)=>{if(a.annotation_id){if(us(a.run_id)){Fe("Статус выполнения можно менять только для завершённых прогонов.");return}Ct(a.annotation_id);try{const w=await Me.updateAutoRunAnnotation({annotation_id:a.annotation_id,resolved:g,resolved_by:d.generatedBy||void 0});Vs(w.annotation),dt()}catch(w){const O=w instanceof Error?w.message:String(w);Fe(`Смена статуса кейса: ${O}`),de(`Annotation resolve toggle error: ${O}`)}finally{Ct("")}}},[Vs,d.generatedBy,dt,de]),wr=S.useCallback(async a=>{be(a.annotation_id),await Ht(a.run_id,a.case_id),re?.items.some(g=>g.run_id===a.run_id)||Fe("Комментарий относится к прогону вне текущего фильтра. Детали загружены напрямую.")},[re?.items,Ht]);S.useEffect(()=>{En.current||(En.current=!0,yn({keepSelection:!1}),_e(),$r(),dt())},[_e,$r,yn,dt]),S.useEffect(()=>{En.current&&Tn()},[Ce,Tn]),S.useEffect(()=>{be(a=>Be.length===0?"":Be.some(g=>g.annotation_id===a)?a:Be[0].annotation_id)},[Be]),S.useEffect(()=>{ce(a=>J.length===0?"":a&&J.some(g=>g.generation_id===a)?a:J[0].generation_id)},[J]),S.useEffect(()=>{if(!q){he([]);return}he([...q.questions])},[q?.generation_id]),S.useEffect(()=>{Nt(String(M.limit))},[M.limit]),S.useEffect(()=>{ut(String(d.count))},[d.count]),S.useEffect(()=>{if(!Z)return;const a=bo(Z.job_id);if(ge!==a)return;const g=Hs(Z,fe||lt);se(g.detail),oe(g.dialog),T(g.caseId)},[Z,fe,ge]),S.useEffect(()=>()=>{v()},[v]),S.useEffect(()=>{Q.length!==0&&j(a=>{let g=!1;const w={...a.personalityPrompts};for(const Pe of Q)(typeof w[Pe.id]!="string"||w[Pe.id].trim().length===0)&&(w[Pe.id]=Pe.defaultPrompt,g=!0);let O=a.personalityId;return Q.some(Pe=>Pe.id===a.personalityId)||(O=Q[0].id,g=!0),g?{...a,personalityId:O,personalityPrompts:w}:a})},[Q]),S.useEffect(()=>{const a=localStorage.getItem(yc);if(a)try{const g=JSON.parse(a);if(g.filters){const w=g.filters;G(O=>({...O,...w,limit:typeof w.limit=="number"?Math.max(1,Math.min(500,w.limit)):O.limit}))}typeof g.analysisDate=="string"&&H(Di(g.analysisDate)),g.autoGenSettings&&j(w=>{const O={...w.personalityPrompts},Pe=g.autoGenSettings?.personalityPrompts??{};for(const[Vt,Gn]of Object.entries(Pe))typeof Gn=="string"&&Vt.trim().length>0&&(O[Vt.trim()]=Gn);const gt=typeof g.autoGenSettings?.personalityId=="string"&&g.autoGenSettings.personalityId.trim().length>0?g.autoGenSettings.personalityId.trim():w.personalityId;return{...w,mode:g.autoGenSettings?.mode==="codex_creative"||g.autoGenSettings?.mode==="qwen_seed"?g.autoGenSettings.mode:w.mode,count:typeof g.autoGenSettings?.count=="number"?Math.max(1,Math.min(200,g.autoGenSettings.count)):w.count,personalityId:gt,personalityPrompts:O,persistToEvalCases:typeof g.autoGenSettings?.persistToEvalCases=="boolean"?g.autoGenSettings.persistToEvalCases:w.persistToEvalCases,generatedBy:typeof g.autoGenSettings?.generatedBy=="string"?g.autoGenSettings.generatedBy:w.generatedBy}}),(g.annotationDecisionFilter==="all"||typeof g.annotationDecisionFilter=="string"&&g.annotationDecisionFilter.length>0)&&Ae(g.annotationDecisionFilter),typeof g.hideResolvedAnnotations=="boolean"&&Qe(g.hideResolvedAnnotations)}catch{}},[]);const qn=S.useCallback(()=>{const a={filters:M,analysisDate:L,autoGenSettings:{mode:d.mode,count:d.count,personalityId:d.personalityId,personalityPrompts:d.personalityPrompts,persistToEvalCases:d.persistToEvalCases,generatedBy:d.generatedBy},annotationDecisionFilter:Ce,hideResolvedAnnotations:$e};localStorage.setItem(yc,JSON.stringify(a))},[L,Ce,d,M,$e]);return S.useEffect(()=>{const a=()=>{qn(),de("Сохранены настройки панели автопрогонов.")};return window.addEventListener(xc,a),()=>{window.removeEventListener(xc,a)}},[de,qn]),o.jsxs(jn,{className:"autoruns-frame",title:"",hideHeader:!0,children:[o.jsxs("div",{className:"autoruns-columns",children:[o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Настройки"})}),o.jsx("h4",{children:"Настройки выборки"}),o.jsxs("div",{className:"autoruns-form-grid",children:[o.jsxs("label",{children:["Дата с",o.jsx("input",{type:"datetime-local",value:M.fromLocal,onChange:a=>G(g=>({...g,fromLocal:a.target.value}))})]}),o.jsxs("label",{children:["Дата по",o.jsx("input",{type:"datetime-local",value:M.toLocal,onChange:a=>G(g=>({...g,toLocal:a.target.value}))})]}),o.jsxs("label",{children:["Целевой контур",o.jsxs("select",{value:M.target,onChange:a=>G(g=>({...g,target:a.target.value})),children:[o.jsx("option",{value:"all",children:"все"}),(re?.available.targets??[]).map(a=>o.jsx("option",{value:a,children:a},a))]})]}),o.jsxs("label",{children:["Режим",o.jsxs("select",{value:M.mode,onChange:a=>G(g=>({...g,mode:a.target.value})),children:[o.jsx("option",{value:"all",children:"все"}),(re?.available.modes??[]).map(a=>o.jsx("option",{value:a,children:a},a))]})]}),o.jsxs("label",{children:["Использовать mock",o.jsxs("select",{value:M.useMock,onChange:a=>G(g=>({...g,useMock:a.target.value})),children:[o.jsx("option",{value:"any",children:"любой"}),o.jsx("option",{value:"true",children:"да"}),o.jsx("option",{value:"false",children:"нет"})]})]}),o.jsxs("label",{children:["Лимит",o.jsx("input",{type:"number",min:1,max:500,value:Wn,onChange:a=>{const g=a.target.value;(g===""||/^\d+$/.test(g))&&Nt(g)},onBlur:a=>Kn(a.target.value),onKeyDown:a=>{a.key==="Enter"&&Kn(a.target.value)}})]}),o.jsxs("label",{className:"full-width",children:["Версия промпта содержит",o.jsx("input",{value:M.promptContains,onChange:a=>G(g=>({...g,promptContains:a.target.value})),placeholder:"normalizer_v2_0_2 / address_query_runtime_v1",list:"autoruns-prompt-versions"})]})]}),o.jsx("datalist",{id:"autoruns-prompt-versions",children:(re?.available.prompt_versions??[]).map(a=>o.jsx("option",{value:a},a))}),o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",disabled:At,onClick:()=>{yn({keepSelection:!1})},children:At?"Обновляю...":"Применить"}),o.jsx("button",{type:"button",className:"tab",onClick:()=>{G({...Ii,fromLocal:Sc()}),Fe("")},children:"Сбросить фильтры"})]}),o.jsx("h4",{children:"Контур генерации"}),o.jsxs("div",{className:"autoruns-meta-list",children:[o.jsxs("div",{children:[o.jsx("span",{children:"Провайдер:"}),o.jsx("strong",{children:i.llmProvider})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Модель:"}),o.jsx("strong",{children:i.model||"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Промпт ассистента:"}),o.jsx("strong",{children:p})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Промпт декомпозиции:"}),o.jsx("strong",{children:D})]})]}),o.jsx("h4",{children:"Автогенерация вопросов"}),o.jsx("p",{className:"muted",children:"`qwen_seed` использует текущую LLM-модель из активного контура подключения (та же модель, что и для ответов ассистента)."}),o.jsxs("div",{className:"autoruns-form-grid",children:[o.jsxs("label",{children:["Режим генерации",o.jsxs("select",{value:d.mode,onChange:a=>j(g=>({...g,mode:a.target.value})),children:[o.jsx("option",{value:"codex_creative",children:"codex_creative"}),o.jsx("option",{value:"qwen_seed",children:"qwen_seed"})]})]}),o.jsxs("label",{children:["Кол-во",o.jsx("input",{type:"number",min:1,max:200,value:gr,onChange:a=>{const g=a.target.value;(g===""||/^\d+$/.test(g))&&ut(g)},onBlur:a=>_r(a.target.value),onKeyDown:a=>{a.key==="Enter"&&_r(a.target.value)}})]}),o.jsxs("label",{children:["Личность автогенерации",o.jsx("select",{value:d.personalityId,onChange:a=>j(g=>({...g,personalityId:a.target.value})),children:Q.map(a=>o.jsx("option",{value:a.id,children:a.label},a.id))})]}),o.jsxs("label",{children:["Кто генерирует",o.jsx("input",{value:d.generatedBy,onChange:a=>j(g=>({...g,generatedBy:a.target.value})),placeholder:"manual_reviewer"})]}),o.jsxs("label",{className:"full-width",children:["Промпт личности",o.jsx("textarea",{value:d.personalityPrompts[d.personalityId]??"",onChange:a=>j(g=>({...g,personalityPrompts:{...g.personalityPrompts,[g.personalityId]:a.target.value}})),placeholder:"Текст промпта для выбранной личности автогенерации"})]}),o.jsxs("label",{className:"checkbox-row",children:[o.jsx("input",{type:"checkbox",checked:d.persistToEvalCases,onChange:a=>j(g=>({...g,persistToEvalCases:a.target.checked}))}),"Сохранять кейс-сет в `eval_cases`"]})]}),o.jsxs("div",{className:"autoruns-form-grid",children:[o.jsxs("label",{children:["Дата анализа (срез)",o.jsx("input",{type:"date",value:L,onChange:a=>H(Di(a.target.value))})]}),o.jsx("div",{className:"button-row",children:o.jsx("button",{type:"button",className:"tab",disabled:!L,onClick:()=>H(""),children:"Сбросить дату среза"})})]}),o.jsx("p",{className:"muted",children:"Если дата среза задана, автопрогон анализирует данные на эту дату. Если поле пустое, используется текущее состояние."}),o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",disabled:an,onClick:()=>{Fr()},children:an?"Генерирую...":"Сгенерировать пачку"}),o.jsx("button",{type:"button",className:"tab",disabled:Jt,onClick:()=>{_e()},children:Jt?"Обновляю...":"Обновить историю"}),o.jsx("button",{type:"button",className:"tab",disabled:Un||ve.length===0,onClick:()=>{N()},children:Un?"Запускаю...":"Запустить прогоны"})]}),o.jsx("div",{className:"autoruns-form-grid",children:o.jsxs("label",{className:"full-width",children:["Кейс-сет для запуска",o.jsxs("select",{value:ue,onChange:a=>ce(a.target.value),disabled:J.length===0,children:[J.length===0?o.jsx("option",{value:"",children:"нет генераций"}):null,J.map(a=>o.jsxs("option",{value:a.generation_id,children:[mr(a.created_at)," | ",a.mode," | ",a.count," | ",a.saved_case_set_file??"без файла"]},a.generation_id))]})]})}),o.jsxs("div",{className:"autoruns-generated-questions",children:[o.jsxs("div",{className:"autoruns-generated-questions-head",children:[o.jsxs("strong",{children:["Вопросы к запуску: ",ve.length]}),o.jsx("button",{type:"button",className:"tab",onClick:()=>he([...q?.questions??[]]),disabled:!q,children:"Восстановить"})]}),ve.length===0?o.jsx("p",{className:"muted",children:"Список вопросов пуст. Сгенерируйте пачку или восстановите из выбранной генерации."}):o.jsx("div",{className:"autoruns-generated-questions-list",children:ve.map((a,g)=>o.jsxs("div",{className:"autoruns-generated-question-item",children:[o.jsxs("span",{children:[g+1,". ",a]}),o.jsx("button",{type:"button",className:"autoruns-remove-question-btn",onClick:()=>he(w=>w.filter((O,Pe)=>Pe!==g)),title:"Удалить вопрос из запуска","aria-label":"Удалить вопрос из запуска",children:"X"})]},`${g}-${a.slice(0,24)}`))})]}),o.jsx("p",{className:"muted",children:"Запуск выполняет `assistant_stage1` eval по выбранному кейс-сету."}),o.jsxs("div",{className:"autoruns-autogen-list",children:[Jt?o.jsx("p",{className:"muted",children:"Загружаю историю автогенераций..."}):null,!Jt&&J.length===0?o.jsx("p",{className:"muted",children:"История автогенераций пока пустая."}):null,J.slice(0,30).map(a=>o.jsxs("article",{className:ue===a.generation_id?"autoruns-autogen-item selected":"autoruns-autogen-item",onClick:()=>ce(a.generation_id),children:[o.jsxs("header",{children:[o.jsx("strong",{children:mr(a.created_at)}),o.jsx("span",{children:a.mode})]}),o.jsxs("div",{className:"autoruns-run-meta",children:["id=",a.generation_id," | count=",a.count]}),o.jsxs("div",{className:"autoruns-run-meta",children:["домен=",a.domain??"общий",a.generated_by?` | автор=${a.generated_by}`:""]}),a.saved_case_set_file?o.jsxs("div",{className:"autoruns-run-meta",children:["кейс-сет=",a.saved_case_set_file]}):null,(a.questions??[]).length>0?o.jsx("p",{children:a.questions[0]}):null]},a.generation_id))]}),o.jsxs("details",{className:"autoruns-prompt-details",children:[o.jsx("summary",{children:"Копия активного промпта (только чтение)"}),o.jsxs("label",{children:["Системный",o.jsx("textarea",{readOnly:!0,value:h.systemPrompt})]}),o.jsxs("label",{children:["Разработчика",o.jsx("textarea",{readOnly:!0,value:h.developerPrompt})]}),o.jsxs("label",{children:["Доменный",o.jsx("textarea",{readOnly:!0,value:h.domainPrompt})]}),o.jsxs("label",{children:["Заметки по схеме",o.jsx("textarea",{readOnly:!0,value:h.schemaNotes})]}),o.jsxs("label",{children:["Примеры few-shot",o.jsx("textarea",{readOnly:!0,value:h.fewShotExamples})]})]}),Xt?o.jsx("p",{className:"error-text",children:Xt}):null]}),o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Выдача прогонов"})}),o.jsxs("div",{className:"autoruns-stats-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"Всего"}),o.jsx("strong",{children:(re?.stats.runs_total??0)+(Z?1:0)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Средний score"}),o.jsx("strong",{children:Go(re?.stats.avg_score_index??null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Тренд"}),o.jsx("strong",{children:re?wc(re.stats.trend):"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Блокеры"}),o.jsx("strong",{children:re?.stats.blocking_runs??0})]})]}),o.jsxs("div",{className:"autoruns-run-list",children:[Rn.map(a=>o.jsxs("button",{type:"button",className:ge===a.run_id?"autoruns-run-item selected":"autoruns-run-item",onClick:()=>{Ht(a.run_id)},children:[o.jsxs("div",{className:"autoruns-run-head",children:[o.jsx("strong",{children:mr(a.run_timestamp)}),o.jsx("span",{children:Uf(a.eval_target)})]}),o.jsx("div",{className:"autoruns-run-meta",children:a.run_id}),o.jsxs("div",{className:"autoruns-run-meta",children:["режим=",a.mode??"нет данных"," | mock=",String(a.use_mock)]}),o.jsxs("div",{className:"autoruns-run-meta",children:["analysis_date=",a.analysis_date??"current_state"]}),a.llm_provider||a.model?o.jsxs("div",{className:"autoruns-run-meta",children:["llm=",a.llm_provider??"нет данных"," | модель=",a.model??"нет данных"]}):null,o.jsxs("div",{className:"autoruns-run-meta",children:["промпт=",a.prompt_version??"нет данных"]}),o.jsxs("div",{className:"autoruns-run-foot",children:[o.jsxs("span",{children:["оценка: ",Go(a.score_index)]}),o.jsxs("span",{children:["закрыто/открыто: ",a.closed_cases,"/",a.open_cases]})]}),o.jsxs("div",{className:"autoruns-run-foot",children:[o.jsxs("span",{children:["блокеры: ",a.blocking_failures]}),o.jsxs("span",{children:["качество: ",a.quality_failures]})]})]},a.run_id)),Rn.length===0?o.jsx("p",{className:"muted",children:"За выбранный диапазон прогонов нет."}):null]})]}),o.jsxs("section",{className:"autoruns-col",children:[o.jsxs("div",{className:"autoruns-col-header",children:[o.jsx("h3",{children:"Диалог прогона"}),o.jsxs("div",{className:"autoruns-dialog-toolbar",children:[o.jsxs("label",{children:["Прогон",o.jsx("select",{value:ge,onChange:a=>{const g=a.target.value;Ht(g)},children:Rn.map(a=>o.jsxs("option",{value:a.run_id,children:[mr(a.run_timestamp)," | ",a.run_id]},a.run_id))})]}),o.jsxs("label",{children:["Кейс",o.jsxs("select",{value:fe,onChange:a=>{const g=a.target.value;T(g),ge&&g&&Bt(ge,g)},children:[(te?.cases.length??0)>0?o.jsx("option",{value:lt,children:"ВСЕ кейсы подряд"}):null,(te?.cases??[]).map(a=>o.jsxs("option",{value:a.case_id,children:[a.case_id," | ",a.status]},a.case_id))]})]})]})]}),o.jsxs("div",{className:"autoruns-case-list",children:[(te?.cases.length??0)>0?o.jsxs("button",{type:"button",className:fe===lt?"autoruns-case-item selected":"autoruns-case-item",onClick:()=>{T(lt),ge&&Bt(ge,lt)},children:[o.jsx("span",{children:"ВСЕ кейсы подряд"}),o.jsx("span",{children:te?.cases.length})]},lt):null,(te?.cases??[]).map(a=>o.jsxs("button",{type:"button",className:fe===a.case_id?"autoruns-case-item selected":"autoruns-case-item",onClick:()=>{T(a.case_id),ge&&Bt(ge,a.case_id)},children:[o.jsx("span",{children:a.case_id}),o.jsxs("span",{children:[a.status,a.commented_count>0?` | комм=${a.commented_count}`:""]})]},a.case_id))]}),o.jsxs("div",{className:"autoruns-dialog-view",children:[Hn||Bn?o.jsx("p",{className:"muted",children:"Загружаю диалог..."}):null,!Hn&&!Bn&&(b?.messages.length??0)===0?o.jsx("p",{className:"muted",children:"Диалог для этого прогона не найден."}):null,(b?.messages??[]).map((a,g)=>{const w=a.role==="assistant"?"assistant":"user";return o.jsxs("article",{className:`autoruns-msg ${w}`,children:[o.jsxs("header",{children:[o.jsx("strong",{children:w==="assistant"?"Система":"Модель/вопрос"}),o.jsxs("div",{className:"autoruns-msg-head-actions",children:[a.case_id?o.jsx("span",{className:"autoruns-msg-case-tag",children:a.case_id}):null,o.jsx("span",{children:a.created_at?mr(a.created_at):"нет данных"}),w==="assistant"&&!us(ge)?o.jsxs(o.Fragment,{children:[o.jsx("button",{type:"button",className:a.commented?"autoruns-comment-icon commented":"autoruns-comment-icon",onClick:()=>pe(a),title:"\\u041a\\u043e\\u043c\\u043c\\u0435\\u043d\\u0442\\u0438\\u0440\\u043e\\u0432\\u0430\\u0442\\u044c \\u043e\\u0442\\u0432\\u0435\\u0442 \\u0441\\u0438\\u0441\\u0442\\u0435\\u043c\\u044b","aria-label":"\\u041a\\u043e\\u043c\\u043c\\u0435\\u043d\\u0442\\u0438\\u0440\\u043e\\u0432\\u0430\\u0442\\u044c \\u043e\\u0442\\u0432\\u0435\\u0442 \\u0441\\u0438\\u0441\\u0442\\u0435\\u043c\\u044b",children:o.jsx(Hf,{commented:a.commented})}),a.annotation?o.jsx("button",{type:"button",className:a.annotation.resolved?"autoruns-resolve-toggle resolved":"autoruns-resolve-toggle",onClick:()=>{Sr(a.annotation,!a.annotation.resolved)},disabled:Vn===a.annotation.annotation_id,title:a.annotation.resolved?"Отметить кейс как невыполненный":"Отметить кейс как выполненный","aria-label":a.annotation.resolved?"Отметить кейс как невыполненный":"Отметить кейс как выполненный",children:o.jsx(Nc,{resolved:a.annotation.resolved})}):null]}):null]})]}),o.jsx("p",{children:a.text}),w==="assistant"&&a.annotation?o.jsxs("div",{className:"autoruns-msg-annotation",children:[o.jsx("strong",{children:jc(a.annotation.rating)}),o.jsx("span",{children:a.annotation.comment}),o.jsxs("span",{className:"muted",children:[a.annotation.manual_case_decision,a.annotation.annotation_author?` | ${a.annotation.annotation_author}`:""]})]}):null,(a.trace_id||a.reply_type)&&o.jsxs("footer",{children:[a.trace_id?o.jsxs("span",{children:["trace=",a.trace_id]}):null,a.reply_type?o.jsxs("span",{children:["reply_type=",a.reply_type]}):null]})]},a.message_id??`${w}-${g}`)})]})]}),P?o.jsx("div",{className:"autoruns-col autoruns-assistant-live-col",children:o.jsx(Ac,{sessionId:fn,conversation:Or,inputValue:mn,onInputChange:$t,useMock:hn,onUseMockChange:gn,onSend:xr,onClear:yr,busy:Zt,statusText:hr,errorMessage:Qn})}):null,z?o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Режим декомпозиции"})}),o.jsxs("div",{className:"autoruns-meta-list",children:[o.jsxs("div",{children:[o.jsx("span",{children:"кейс:"}),o.jsx("strong",{children:vn?.case_id??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"домен:"}),o.jsx("strong",{children:vn?.domain??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"класс запроса:"}),o.jsx("strong",{children:vn?.query_class??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"trace:"}),o.jsx("strong",{children:vn?.trace_id??"нет данных"})]})]}),o.jsx("h4",{children:"Шаги декомпозиции"}),(b?.decomposition.length??0)>0?o.jsx("ol",{className:"autoruns-decomposition-list",children:(b?.decomposition??[]).map((a,g)=>o.jsx("li",{children:a},`${g}-${a.slice(0,24)}`))}):o.jsx("p",{className:"muted",children:"В логах кейса нет явной декомпозиции."})]}):null,K?o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Прогресс / регресс"})}),o.jsxs("div",{className:"autoruns-stats-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"Последний score"}),o.jsx("strong",{children:Go(re?.stats.latest_score_index??null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Предыдущий"}),o.jsx("strong",{children:Go(re?.stats.previous_score_index??null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Тренд"}),o.jsx("strong",{children:re?wc(re.stats.trend):"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Пробелы качества"}),o.jsx("strong",{children:re?.stats.quality_gap_runs??0})]})]}),o.jsx("h4",{children:"Покрытие доменов (история)"}),kc(re?.stats.domain_coverage??[]),o.jsx("h4",{style:{marginTop:14},children:"Покрытие доменов (выбранный прогон)"}),kc(te?.coverage.domain_coverage??[]),o.jsx("h4",{style:{marginTop:14},children:"Очереди фиксов пост-анализа"}),It?o.jsx("p",{className:"muted",children:"Собираю пост-анализ..."}):null,It?null:o.jsx("div",{className:"autoruns-stats-grid",children:Object.entries(jt?.post_analysis.stats.by_queue??{}).map(([a,g])=>o.jsxs("div",{children:[o.jsx("span",{children:a}),o.jsx("strong",{children:g})]},a))}),o.jsxs("div",{className:"autoruns-autogen-list",children:[(jt?.post_analysis.recommended_regression_candidates??[]).slice(0,12).map(a=>o.jsxs("article",{className:"autoruns-autogen-item",children:[o.jsxs("header",{children:[o.jsx("strong",{children:a.manual_case_decision}),o.jsxs("span",{children:[a.rating,"/5"]})]}),o.jsxs("div",{className:"autoruns-run-meta",children:[a.domain??"неизвестно"," / ",a.query_class??"неизвестно"]}),o.jsx("p",{children:a.comment})]},a.annotation_id)),!It&&(jt?.post_analysis.recommended_regression_candidates.length??0)===0?o.jsx("p",{className:"muted",children:"Рекомендованных кандидатов пока нет."}):null]})]}):null,ee?o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Комментарии"})}),o.jsx("h4",{children:"Размеченные ответы"}),o.jsxs("div",{className:"autoruns-comment-filter-row",children:[o.jsxs("label",{children:["Фильтр решений",o.jsxs("select",{value:Ce,onChange:a=>Ae(a.target.value),children:[o.jsx("option",{value:"all",children:"все"}),(at.length>0?at:Ze?.enum??[]).map(a=>o.jsx("option",{value:a,children:String(Ze?.labels?.[a]??a)},a))]})]}),o.jsx("button",{type:"button",className:"tab autoruns-resolved-filter-toggle",onClick:()=>Qe(a=>!a),children:$e?"Показать выполненные":"Скрыть выполненные"})]}),o.jsxs("div",{className:"autoruns-stats-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"Комментариев"}),o.jsx("strong",{children:Be.length})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Средний рейтинг"}),o.jsx("strong",{children:vr===null?"нет данных":`${vr.toFixed(2)} / 5`})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Последний"}),o.jsx("strong",{children:Be.length>0?mr(Be[0].updated_at):"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Статус"}),o.jsx("strong",{children:Dt?"обновляю":"готово"})]})]}),o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",disabled:Dt,onClick:()=>{Tn()},children:Dt?"Обновляю...":"Обновить список"}),o.jsx("button",{type:"button",className:"tab",disabled:It,onClick:()=>{dt()},children:It?"Идет пост-анализ...":"Обновить пост-анализ"})]}),o.jsxs("div",{className:"autoruns-comments-list",children:[Dt?o.jsx("p",{className:"muted",children:"Загружаю комментарии..."}):null,!Dt&&Be.length===0?o.jsx("p",{className:"muted",children:Se.length===0?"Пока нет откомментированных ответов.":"Нет открытых кейсов по текущему фильтру."}):null,Be.map(a=>o.jsxs("article",{className:We===a.annotation_id?"autoruns-comment-item selected":"autoruns-comment-item",onClick:()=>{wr(a)},role:"button",tabIndex:0,onKeyDown:g=>{(g.key==="Enter"||g.key===" ")&&(g.preventDefault(),wr(a))},children:[o.jsxs("div",{className:"autoruns-comment-head",children:[o.jsx("strong",{children:jc(a.rating)}),o.jsxs("div",{className:"autoruns-comment-head-actions",children:[o.jsx("span",{children:mr(a.updated_at)}),o.jsx("button",{type:"button",className:a.resolved?"autoruns-resolve-toggle resolved":"autoruns-resolve-toggle",onClick:g=>{g.preventDefault(),g.stopPropagation(),Sr(a,!a.resolved)},disabled:Vn===a.annotation_id,title:a.resolved?"Отметить кейс как невыполненный":"Отметить кейс как выполненный","aria-label":a.resolved?"Отметить кейс как невыполненный":"Отметить кейс как выполненный",children:o.jsx(Nc,{resolved:a.resolved})})]})]}),o.jsx("div",{className:"autoruns-run-meta",children:a.run_id}),o.jsxs("div",{className:"autoruns-run-meta",children:["case=",a.case_id," | msg=",a.message_index]}),o.jsxs("div",{className:"autoruns-run-meta",children:["decision=",a.manual_case_decision,a.annotation_author?` | author=${a.annotation_author}`:""]}),a.resolved_at?o.jsxs("div",{className:"autoruns-run-meta",children:["выполнено",": ",mr(a.resolved_at),a.resolved_by?` | by=${a.resolved_by}`:""]}):null,a.context.question_text?o.jsxs("p",{children:["Q: ",a.context.question_text]}):null,a.context.answer_text?o.jsxs("p",{children:["A: ",a.context.answer_text]}):null,o.jsx("p",{children:a.comment})]},a.annotation_id))]}),je?o.jsxs(o.Fragment,{children:[o.jsx("h4",{children:"Тех-контекст брака"}),o.jsxs("div",{className:"autoruns-meta-list",children:[o.jsxs("div",{children:[o.jsx("span",{children:"trace:"}),o.jsx("strong",{children:je.technical_context.trace_id??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"reply_type:"}),o.jsx("strong",{children:je.technical_context.reply_type??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"domain:"}),o.jsx("strong",{children:je.technical_context.domain??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"query_class:"}),o.jsx("strong",{children:je.technical_context.query_class??"нет данных"})]})]}),o.jsx("h4",{children:"JSON разбор"}),o.jsx(Mt,{value:{annotation_id:je.annotation_id,run_id:je.run_id,case_id:je.case_id,message_index:je.message_index,rating:je.rating,comment:je.comment,manual_case_decision:je.manual_case_decision,annotation_author:je.annotation_author,resolved:je.resolved,resolved_at:je.resolved_at,resolved_by:je.resolved_by,context:je.context,technical_context:je.technical_context,case_summary:je.case_summary?{case_id:je.case_summary.case_id,domain:je.case_summary.domain,query_class:je.case_summary.query_class,checks:je.case_summary.checks,metric_subscores:je.case_summary.metric_subscores}:null}})]}):null]}):null]}),ie.open?o.jsx("div",{className:"autoruns-comment-modal-backdrop",onClick:a=>{a.target===a.currentTarget&&Ur()},children:o.jsxs("div",{className:"autoruns-comment-modal",children:[o.jsx("h3",{children:"Комментарий к ответу системы"}),o.jsx("p",{className:"muted",children:"Оцените ответ по 5-балльной шкале и добавьте комментарий по браку."}),Pn?o.jsxs(o.Fragment,{children:[o.jsxs("details",{className:"autoruns-prompt-details",open:!0,children:[o.jsx("summary",{children:"Вопрос пользователя"}),o.jsx("p",{className:"autoruns-comment-quote",children:Ut?.text??"Вопрос в диалоге не найден."})]}),o.jsxs("details",{className:"autoruns-prompt-details",open:!0,children:[o.jsx("summary",{children:"Ответ системы"}),o.jsx("p",{className:"autoruns-comment-quote",children:Pn.text})]})]}):null,o.jsx("div",{className:"autoruns-rating-row",role:"group","aria-label":"Рейтинг ответа",children:[1,2,3,4,5].map(a=>o.jsx("button",{type:"button",className:ie.rating>=a?"autoruns-rating-dot active":"autoruns-rating-dot",onClick:()=>Ke(g=>({...g,rating:a})),disabled:ie.saving,"aria-label":`Оценка ${a}`,children:ie.rating>=a?"●":"○"},a))}),o.jsxs("div",{className:"autoruns-form-grid",children:[o.jsxs("label",{children:["Решение по кейсу",o.jsx("select",{value:ie.manualCaseDecision,onChange:a=>Ke(g=>({...g,manualCaseDecision:a.target.value})),disabled:ie.saving,children:(at.length>0?at:Ze?.enum??[Ko]).map(a=>o.jsx("option",{value:a,children:String(Ze?.labels?.[a]??a)},a))})]}),o.jsxs("label",{children:["Автор комментария",o.jsx("input",{value:ie.annotationAuthor,onChange:a=>Ke(g=>({...g,annotationAuthor:a.target.value})),placeholder:"manual_reviewer",disabled:ie.saving})]})]}),o.jsxs("label",{children:["Комментарий",o.jsx("textarea",{value:ie.comment,onChange:a=>Ke(g=>({...g,comment:a.target.value})),placeholder:"Почему ответ бракованный, что именно пошло не так, какие технические детали проверить.",rows:4,disabled:ie.saving})]}),ie.error?o.jsx("p",{className:"error-text",children:ie.error}):null,o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",onClick:()=>{Yo()},disabled:ie.saving,children:ie.saving?"Сохраняю...":"Готово"}),o.jsx("button",{type:"button",className:"tab",onClick:()=>Ur(),disabled:ie.saving,children:"Отмена"})]})]})}):null]})}function Ec({value:i,modelOptions:h,modelsBusy:p,onChange:D,onReloadModels:P,onTestConnection:z,onSaveLocalConfig:K,lastStatus:ee,busy:U}){const M=i.llmProvider==="local",G=h.includes(i.model),[L,H]=S.useState(String(i.temperature)),[re,Ee]=S.useState(String(i.maxOutputTokens));S.useEffect(()=>{H(String(i.temperature))},[i.temperature]),S.useEffect(()=>{Ee(String(i.maxOutputTokens))},[i.maxOutputTokens]);const te=b=>{const oe=b.replace(",",".").trim();if(!oe){H(String(i.temperature));return}const Se=Number(oe);if(!Number.isFinite(Se)){H(String(i.temperature));return}D({...i,temperature:Se}),H(String(Se))},se=b=>{const oe=b.trim();if(!oe){Ee(String(i.maxOutputTokens));return}const Se=Number.parseInt(oe,10);if(!Number.isFinite(Se)||Se<=0){Ee(String(i.maxOutputTokens));return}D({...i,maxOutputTokens:Se}),Ee(String(Se))};return o.jsxs(jn,{title:"LLM Connection",subtitle:"Switch between OpenAI cloud and local OpenAI-compatible server.",actions:o.jsx("span",{className:"status-chip",children:ee||"Status: not checked"}),children:[o.jsxs("div",{className:"grid-two",children:[o.jsxs("label",{children:["Provider",o.jsxs("select",{value:i.llmProvider,onChange:b=>{const oe=b.target.value==="local"?"local":"openai";D({...i,llmProvider:oe,baseUrl:oe==="local"?"http://127.0.0.1:1234/v1":"https://api.openai.com/v1"})},children:[o.jsx("option",{value:"openai",children:"OpenAI (token)"}),o.jsx("option",{value:"local",children:"Local (LM Studio / OpenAI-compatible)"})]})]}),o.jsxs("label",{children:["Model",o.jsxs("select",{value:G?i.model:"__manual__",onChange:b=>{const oe=b.target.value;oe!=="__manual__"&&D({...i,model:oe})},children:[o.jsx("option",{value:"__manual__",children:"Manual input"}),h.map(b=>o.jsx("option",{value:b,children:b},b))]})]}),o.jsxs("label",{children:["Model ID (manual)",o.jsx("input",{value:i.model,onChange:b=>D({...i,model:b.target.value}),placeholder:"qwen2.5-14b-instruct or lmstudio loaded model id"})]}),M?null:o.jsxs("label",{className:"full-width",children:["OpenAI API Key",o.jsx("input",{type:"password",value:i.apiKey,onChange:b=>D({...i,apiKey:b.target.value}),placeholder:"sk-..."})]}),o.jsxs("label",{className:M?"full-width":void 0,children:[M?"Local server base URL":"Base URL",o.jsx("input",{value:i.baseUrl,onChange:b=>D({...i,baseUrl:b.target.value}),placeholder:M?"http://127.0.0.1:1234/v1":"https://api.openai.com/v1"})]}),o.jsxs("label",{children:["Temperature",o.jsx("input",{type:"number",step:"0.1",value:L,onChange:b=>H(b.target.value),onBlur:b=>te(b.target.value),onKeyDown:b=>{b.key==="Enter"&&te(b.target.value)}})]}),o.jsxs("label",{children:["Max output tokens",o.jsx("input",{type:"number",value:re,onChange:b=>Ee(b.target.value),onBlur:b=>se(b.target.value),onKeyDown:b=>{b.key==="Enter"&&se(b.target.value)}})]})]}),o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",onClick:()=>K(),children:"Save local config"}),o.jsx("button",{type:"button",onClick:()=>P(),disabled:U||p,children:p?"Loading models...":"Load model list"}),o.jsx("button",{type:"button",onClick:()=>z(),disabled:U,children:U?"Checking...":"Test connection"})]})]})}function Qf({items:i,onRefresh:h,onOpenTrace:p}){return o.jsx(jn,{title:"История нормализаций",subtitle:"Короткий вопрос, confidence, route hint и статус валидации.",actions:o.jsx("button",{type:"button",onClick:()=>h(),children:"Обновить"}),children:o.jsxs("div",{className:"history-list",children:[i.length===0?o.jsx("p",{className:"muted",children:"История пока пустая."}):null,i.map(D=>o.jsxs("button",{type:"button",className:"history-item",onClick:()=>p(D.trace_id),children:[o.jsxs("div",{className:"history-row",children:[o.jsx("strong",{children:D.route_hint??"route: n/a"}),o.jsx("span",{children:D.validation_passed?"schema: ok":"schema: fail"})]}),o.jsx("p",{children:D.question_short}),o.jsxs("div",{className:"history-row",children:[o.jsx("span",{children:D.model}),o.jsx("span",{children:new Date(D.timestamp).toLocaleString("ru-RU")})]})]},D.trace_id))]})})}function Fn(i){return i==null||i===""?"—":String(i)}function Wf({result:i}){return o.jsx(jn,{title:"Runtime метрики",subtitle:"trace_id, токены, latency и статус валидации.",children:o.jsxs("div",{className:"metrics-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"trace_id"}),o.jsx("strong",{children:Fn(i?.trace_id)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"request_started_at"}),o.jsx("strong",{children:Fn(i?new Date(Date.now()-i.latency_ms).toISOString():null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"request_finished_at"}),o.jsx("strong",{children:Fn(i?new Date().toISOString():null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"latency_ms"}),o.jsx("strong",{children:Fn(i?.latency_ms)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"input_tokens"}),o.jsx("strong",{children:Fn(i?.usage?.input_tokens)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"output_tokens"}),o.jsx("strong",{children:Fn(i?.usage?.output_tokens)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"total_tokens"}),o.jsx("strong",{children:Fn(i?.usage?.total_tokens)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"validation_status"}),o.jsx("strong",{children:i?.validation?.passed?"passed":"failed"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"prompt_version"}),o.jsx("strong",{children:Fn(i?.prompt_version)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"schema_version"}),o.jsx("strong",{children:Fn(i?.schema_version)})]})]})})}const Kf={normalized:"Normalized JSON",fragments:"Fragment View",scope:"Scope View",flags:"Flags View",route:"Route Simulation",raw:"Raw model output",validation:"Validation",logs:"Logs"};function qf(i){return i&&typeof i=="object"?i:null}function Gf({tab:i,onTabChange:h,result:p,appLogs:D}){const P=["normalized","fragments","scope","flags","route","raw","validation","logs"],z=qf(p?.normalized),K=String(z?.schema_version??""),ee=K==="normalized_query_v2"||K==="normalized_query_v2_0_1"||K==="normalized_query_v2_0_2",U=ee?{fragments:z?.fragments??[],discarded_fragments:z?.discarded_fragments??[]}:{note:"Fragment View доступен для normalized_query_v2."},M=ee?{message_in_scope:z?.message_in_scope??null,scope_confidence:z?.scope_confidence??null,contains_multiple_tasks:z?.contains_multiple_tasks??null,global_notes:z?.global_notes??null}:{note:"Scope View доступен для normalized_query_v2."},G=ee?Array.isArray(z?.fragments)?(z?.fragments).map(L=>({fragment_id:L.fragment_id??null,domain_relevance:L.domain_relevance??null,candidate_labels:L.candidate_labels??[],execution_readiness:L.execution_readiness??null,clarification_reason:L.clarification_reason??null,soft_assumption_used:L.soft_assumption_used??[],route_status:L.route_status??null,no_route_reason:L.no_route_reason??null,flags:L.flags??{}})):[]:{note:"Flags View доступен для normalized_query_v2."};return o.jsxs(jn,{title:"Выходные данные",subtitle:"Structured output и диагностические вкладки.",children:[o.jsx("div",{className:"tab-row",children:P.map(L=>o.jsx("button",{type:"button",className:i===L?"tab active":"tab",onClick:()=>h(L),children:Kf[L]},L))}),i==="normalized"?o.jsx(Mt,{value:p?.normalized??{note:"Нет данных."}}):null,i==="fragments"?o.jsx(Mt,{value:U}):null,i==="scope"?o.jsx(Mt,{value:M}):null,i==="flags"?o.jsx(Mt,{value:G}):null,i==="route"?o.jsx(Mt,{value:p?.route_hint_summary??{note:"Нет данных."}}):null,i==="raw"?o.jsx(Mt,{value:p?.raw_model_output??{note:"Нет данных."}}):null,i==="validation"?o.jsx(Mt,{value:p?.validation??{note:"Нет данных."}}):null,i==="logs"?o.jsx(Mt,{value:D}):null]})}function Pc({value:i,onChange:h,presets:p,selectedPresetId:D,onSelectPreset:P,onLoadPreset:z,onSavePreset:K,onResetDefaults:ee,onDiffPrevious:U,presetName:M,onPresetNameChange:G,diffSummary:L}){return o.jsxs(jn,{title:"Prompt Manager",subtitle:"Системный, developer и domain уровни управляются отдельно.",children:[o.jsxs("div",{className:"prompt-manager-grid",children:[o.jsxs("label",{children:["Системный prompt",o.jsx("textarea",{value:i.systemPrompt,onChange:H=>h({...i,systemPrompt:H.target.value}),rows:6})]}),o.jsxs("label",{children:["Developer / Instruction prompt",o.jsx("textarea",{value:i.developerPrompt,onChange:H=>h({...i,developerPrompt:H.target.value}),rows:6})]}),o.jsxs("label",{children:["Domain prompt",o.jsx("textarea",{value:i.domainPrompt,onChange:H=>h({...i,domainPrompt:H.target.value}),rows:6})]}),o.jsxs("label",{children:["Schema notes",o.jsx("textarea",{value:i.schemaNotes,onChange:H=>h({...i,schemaNotes:H.target.value}),rows:6})]}),o.jsxs("label",{className:"full-width",children:["Few-shot examples",o.jsx("textarea",{value:i.fewShotExamples,onChange:H=>h({...i,fewShotExamples:H.target.value}),rows:8})]})]}),o.jsxs("div",{className:"button-row",children:[o.jsxs("select",{value:D,onChange:H=>P(H.target.value),children:[o.jsx("option",{value:"",children:"Выберите preset..."}),p.map(H=>o.jsx("option",{value:H.id,children:H.name},H.id))]}),o.jsx("button",{type:"button",onClick:()=>z(),children:"Загрузить preset"}),o.jsx("input",{value:M,onChange:H=>G(H.target.value),placeholder:"Имя для сохранения"}),o.jsx("button",{type:"button",onClick:()=>K(),children:"Сохранить preset"}),o.jsx("button",{type:"button",onClick:()=>U(),children:"Diff с предыдущим"}),o.jsx("button",{type:"button",onClick:()=>ee(),children:"Сбросить к default"})]}),L?o.jsx("p",{className:"diff-summary",children:L}):null]})}function bf({value:i,onChange:h,onApplyBatchFormat:p,onNormalize:D,busy:P,useMock:z,onUseMockChange:K,errorMessage:ee}){return o.jsxs(jn,{title:"Запрос пользователя",subtitle:"NDC semantic front-end: нормализуем, но не отвечаем за бухгалтерскую суть.",children:[o.jsxs("div",{className:"grid-two",children:[o.jsxs("label",{className:"full-width",children:["Raw user question",o.jsx("textarea",{value:i.userQuestion,onChange:U=>h({...i,userQuestion:U.target.value}),rows:6,placeholder:"Например: По каким покупателям у нас на конец июня висят отгрузки без оплаты..."})]}),o.jsxs("label",{className:"full-width",children:["Batch queries (`;` separator)",o.jsx("textarea",{value:i.batchQuestionsRaw,onChange:U=>h({...i,batchQuestionsRaw:U.target.value}),onBlur:()=>p(),rows:8,placeholder:"Вопрос 1; Вопрос 2; Вопрос 3"})]}),o.jsxs("label",{children:["Optional period context",o.jsx("input",{value:i.periodHint,onChange:U=>h({...i,periodHint:U.target.value})})]}),o.jsxs("label",{children:["Optional business context",o.jsx("input",{value:i.businessContext,onChange:U=>h({...i,businessContext:U.target.value})})]}),o.jsxs("label",{children:["Optional expected route (eval)",o.jsx("input",{value:i.expectedRoute,onChange:U=>h({...i,expectedRoute:U.target.value})})]})]}),o.jsxs("div",{className:"button-row",children:[o.jsxs("label",{className:"checkbox-row",children:[o.jsx("input",{type:"checkbox",checked:z,onChange:U=>K(U.target.checked)}),"Mock-режим (без вызова OpenAI)"]}),o.jsx("button",{type:"button",onClick:()=>p(),disabled:P||!i.batchQuestionsRaw.trim(),children:"Применить `;` в переносы"}),o.jsx("button",{type:"button",onClick:()=>D(!1),disabled:P||!i.userQuestion.trim(),children:P?"Нормализуем...":"Normalize"}),o.jsx("button",{type:"button",onClick:()=>D(!0),disabled:P||!i.userQuestion.trim(),children:P?"Сохраняем...":"Normalize + Save as test case"})]}),ee?o.jsx("p",{className:"error-text",children:ee}):null]})}function Jf({runs:i,selectedRunId:h,onSelectRun:p,onStartRun:D,onFinishRun:P,onRefreshRuns:z,onRunEval:K,onCopyEvalReport:ee,evalBusy:U,traceItems:M,evalReport:G}){return o.jsxs(jn,{title:"NDC Run Monitor",subtitle:"Важно: кнопка Запустить run создает только run-сущность. Кнопка eval запускает batch-проверку normalizer v2.0.2.",children:[o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",onClick:()=>D(),children:"Запустить run"}),o.jsx("button",{type:"button",onClick:()=>P(),disabled:!h,children:"Завершить выбранный run"}),o.jsx("button",{type:"button",onClick:()=>z(),children:"Обновить runs"}),o.jsx("button",{type:"button",onClick:()=>K(),disabled:U,children:U?"Идет eval v2.0.2...":"Запустить eval v2.0.2"})]}),o.jsxs("div",{className:"runtime-stack",children:[o.jsxs("div",{className:"runtime-runs",children:[i.map(L=>o.jsxs("button",{type:"button",className:h===L.runId?"history-item selected":"history-item",onClick:()=>p(L.runId),children:[o.jsxs("div",{className:"history-row",children:[o.jsx("strong",{children:L.status}),o.jsx("span",{children:L.runId})]}),o.jsxs("div",{className:"history-row",children:[o.jsx("span",{children:L.sessionId}),o.jsx("span",{children:new Date(L.updatedAt).toLocaleString("ru-RU")})]})]},L.runId)),i.length===0?o.jsx("p",{className:"muted",children:"Нет активных запусков."}):null]}),o.jsxs("div",{className:"runtime-details",children:[o.jsx("h3",{children:"Trace выбранного run"}),o.jsx(Mt,{value:M}),o.jsxs("div",{className:"eval-report-wrap",children:[o.jsx("h3",{style:{marginTop:12},children:"Отчет eval"}),o.jsx(Mt,{value:G??{note:"Eval пока не запускался"}}),o.jsx("button",{type:"button",className:"copy-cube-button",title:"Скопировать отчет eval",onClick:()=>ee(),children:"⧉"})]})]})]})]})}const Yf={llmProvider:"openai",apiKey:"",model:"gpt-4o-mini",baseUrl:"https://api.openai.com/v1",temperature:0,maxOutputTokens:700},Rc={systemPrompt:"Ты semantic-normalizer для бухгалтерского ассистента NDC. Возвращай только JSON по схеме normalized_query_v2_0_2.",developerPrompt:"Сначала делай decomposition сообщения на task fragments, затем определяй domain scope и route-critical flags. Для каждого fragment заполняй execution_readiness + route_status + no_route_reason. Если fragment routable, не оставляй его в no_route.",domainPrompt:"Контур: данные текущего предприятия в 1С/NDC. In-scope: документы, проводки, взаиморасчеты, остатки, периодное закрытие, аномалии и контрольные проверки. Out-of-scope: общая теория, законы и оффтоп.",schemaNotes:"schema_version: normalized_query_v2_0_2. Строгий JSON без дополнительных полей.",fewShotExamples:"Q: Проверь по поставщикам хвосты и разложи цепочку документов/оплат. => fragment in_scope, flags: multi_entity + chain_explanation. Q: Как вообще по ФСБУ? => out_of_scope/generic_accounting."},Xf={userQuestion:"",batchQuestionsRaw:"",periodHint:"",businessContext:"",expectedRoute:""},Oi={colors:{backgroundRgb:"18, 18, 18",mainSurfaceRgb:"25, 25, 25",horizontalSurfaceRgb:"30, 30, 30",focusSurfaceRgb:"35, 35, 35",activeRgb:"167, 59, 255",activeTextRgb:"240, 240, 240",textMainRgb:"240, 240, 240",textMutedRgb:"166, 166, 166",dangerRgb:"126, 126, 126",scrollbarTrackRgb:"20, 20, 20",scrollbarThumbRgb:"30, 30, 30",scrollbarThumbHoverRgb:"30, 50, 30"},layout:{modeColumnWidthPx:406,modeToggleWidthPx:188}},Tc="ndc_normalizer_session_config_v1",Mc="ndc_autoruns_layout_config_v1",Zf="ndc-autoruns-save",zi=["Анализ запроса","Получение данных","Подготовка ответа"],ep="assistant",$i="normalizer_v2_0_2",Lc="address_query_runtime_v1",tp=["normalized","fragments","scope","flags","route","raw","validation","logs"];function np(i){return`[${new Date().toLocaleTimeString("ru-RU")}] ${i}`}function rp(i,h){if(!h)return"Previous preset is not selected.";const D=["systemPrompt","developerPrompt","domainPrompt","schemaNotes","fewShotExamples"].filter(P=>i[P]!==h[P]).map(P=>`${P}: ${Math.abs(i[P].length-h[P].length)} chars delta`);return D.length===0?"No changes against previous preset.":`Changed fields: ${D.length}. ${D.join(" | ")}`}function sp(){const[i,h]=S.useState(Yf),[p,D]=S.useState(Rc),[P,z]=S.useState(Xf),[K,ee]=S.useState(null),[U,M]=S.useState([]),[G,L]=S.useState([]),[H,re]=S.useState("normalized"),[Ee,te]=S.useState(!1),[se,b]=S.useState(!1),[oe,Se]=S.useState([]),[le,Ce]=S.useState(""),[Ae,$e]=S.useState([]),[Qe,Ze]=S.useState(""),[it,at]=S.useState("NDC custom preset"),[mt,We]=S.useState(null),[be,ge]=S.useState(""),[Ne,fe]=S.useState(!1),[T,Q]=S.useState([]),[A,d]=S.useState(""),[j,J]=S.useState([]),[ae,ue]=S.useState(!1),[ce,ve]=S.useState(null),[he,Z]=S.useState(""),[we,jt]=S.useState(ep),[kn,an]=S.useState(!0),[un,Un]=S.useState(!0),[Lt,It]=S.useState(!0),[Cn,Jt]=S.useState(!0),[kt,At]=S.useState(!0),[Yt,Bn]=S.useState(!0),[cn,Hn]=S.useState(!0),[dn,Dt]=S.useState(!0),[Ot,Vn]=S.useState(!0),[Ct,Xt]=S.useState(!0),[Fe,fn]=S.useState(!0),[pn,Or]=S.useState(!0),[zt,mn]=S.useState(!0),[$t,hn]=S.useState(!0),[gn,Zt]=S.useState(!0),[ht,hr]=S.useState(""),[en,Qn]=S.useState([]),[Nn,Wn]=S.useState(""),[Nt,gr]=S.useState(!1),[ut,ie]=S.useState(""),[Ke,En]=S.useState(""),ct=S.useRef(!1),Ft=S.useRef(!1);S.useEffect(()=>{const v=document.documentElement,{colors:I}=Oi;v.style.setProperty("--rgb-background",I.backgroundRgb),v.style.setProperty("--rgb-surface-main",I.mainSurfaceRgb),v.style.setProperty("--rgb-surface-horizontal",I.horizontalSurfaceRgb),v.style.setProperty("--rgb-surface-focus",I.focusSurfaceRgb),v.style.setProperty("--rgb-active",I.activeRgb),v.style.setProperty("--rgb-active-text",I.activeTextRgb),v.style.setProperty("--rgb-text-main",I.textMainRgb),v.style.setProperty("--rgb-text-muted",I.textMutedRgb),v.style.setProperty("--rgb-danger",I.dangerRgb),v.style.setProperty("--rgb-scrollbar-track",I.scrollbarTrackRgb),v.style.setProperty("--rgb-scrollbar-thumb",I.scrollbarThumbRgb),v.style.setProperty("--rgb-scrollbar-thumb-hover",I.scrollbarThumbHoverRgb),v.style.setProperty("--mode-column-width",`${Oi.layout.modeColumnWidthPx}px`),v.style.setProperty("--mode-toggle-width",`${Oi.layout.modeToggleWidthPx}px`)},[]);const q=v=>{L(I=>[np(v),...I].slice(0,300))};function vn(){let v=0;ie(zi[0]);const I=window.setInterval(()=>{v=Math.min(v+1,zi.length-1),ie(zi[v])},650);return()=>window.clearInterval(I)}S.useEffect(()=>{const v=localStorage.getItem(Tc);if(v)try{const N=JSON.parse(v);h(pe=>({...pe,llmProvider:N.llmProvider==="local"?"local":"openai",model:N.model??pe.model,baseUrl:N.baseUrl??pe.baseUrl,temperature:N.temperature??pe.temperature,maxOutputTokens:N.maxOutputTokens??pe.maxOutputTokens}))}catch{}const I=localStorage.getItem(Mc);if(I)try{const N=JSON.parse(I);(N.uiMode==="assistant"||N.uiMode==="decomposition"||N.uiMode==="autoruns")&&jt(N.uiMode),N.activeTab&&tp.includes(N.activeTab)&&re(N.activeTab),typeof N.showAutorunsAssistantMode=="boolean"&&an(N.showAutorunsAssistantMode),typeof N.showAutorunsDecompositionMode=="boolean"&&Un(N.showAutorunsDecompositionMode),typeof N.showAutorunsProgressMode=="boolean"&&It(N.showAutorunsProgressMode),typeof N.showAutorunsCommentsMode=="boolean"&&Jt(N.showAutorunsCommentsMode),typeof N.showAssistantConnectionMode=="boolean"&&At(N.showAssistantConnectionMode),typeof N.showAssistantPromptMode=="boolean"&&Bn(N.showAssistantPromptMode),typeof N.showAssistantChatMode=="boolean"&&Hn(N.showAssistantChatMode),typeof N.showAssistantSamMode=="boolean"&&Dt(N.showAssistantSamMode),typeof N.showDecompositionConnectionMode=="boolean"&&Vn(N.showDecompositionConnectionMode),typeof N.showDecompositionPromptMode=="boolean"&&Xt(N.showDecompositionPromptMode),typeof N.showDecompositionQueryMode=="boolean"&&fn(N.showDecompositionQueryMode),typeof N.showDecompositionOutputMode=="boolean"&&Or(N.showDecompositionOutputMode),typeof N.showDecompositionMetricsMode=="boolean"&&mn(N.showDecompositionMetricsMode),typeof N.showDecompositionHistoryMode=="boolean"&&hn(N.showDecompositionHistoryMode),typeof N.showDecompositionRuntimeMode=="boolean"&&Zt(N.showDecompositionRuntimeMode),N.prompts&&(D(pe=>({...pe,...N.prompts})),Ft.current=!0)}catch{}Be(),je(),Pn()},[]);async function Be(){try{const v=await Me.loadHistory();M(v.items??[])}catch(v){q(`History load error: ${v instanceof Error?v.message:String(v)}`)}}async function je(){try{const I=(await Me.loadPresets()).presets??[];if($e(I),Ft.current){ct.current=!0;return}if(ct.current)return;const N=I.find(pe=>pe.prompt_version===$i)??I.find(pe=>pe.id==="default-normalizer-v2_0_2");if(!N){ct.current=!0,q(`Preset autoload skipped: ${$i} not found.`);return}Ze(N.id),We(p),D({systemPrompt:N.systemPrompt,developerPrompt:N.developerPrompt,domainPrompt:N.domainPrompt,schemaNotes:N.schemaNotes??"",fewShotExamples:N.fewShotExamples??""}),ct.current=!0,q(`Preset autoloaded: ${N.name} (${N.prompt_version}).`)}catch(v){q(`Presets load error: ${v instanceof Error?v.message:String(v)}`)}}async function Pn(){try{const v=await Me.listRuns();Q(v.items??[])}catch(v){q(`Runs load error: ${v instanceof Error?v.message:String(v)}`)}}function Ut(){localStorage.setItem(Tc,JSON.stringify({model:i.model,llmProvider:i.llmProvider,baseUrl:i.baseUrl,temperature:i.temperature,maxOutputTokens:i.maxOutputTokens})),q("Local config saved (without API key).")}function vr(){localStorage.setItem(Mc,JSON.stringify({uiMode:we,activeTab:H,showAutorunsAssistantMode:kn,showAutorunsDecompositionMode:un,showAutorunsProgressMode:Lt,showAutorunsCommentsMode:Cn,showAssistantConnectionMode:kt,showAssistantPromptMode:Yt,showAssistantChatMode:cn,showAssistantSamMode:dn,showDecompositionConnectionMode:Ot,showDecompositionPromptMode:Ct,showDecompositionQueryMode:Fe,showDecompositionOutputMode:pn,showDecompositionMetricsMode:zt,showDecompositionHistoryMode:$t,showDecompositionRuntimeMode:gn,prompts:p})),window.dispatchEvent(new CustomEvent(Zf)),q("UI layout and prompts saved.")}async function Rn(){te(!0),Z("");try{const v=await Me.testConnection(i);v.provider==="local"?v.model_found===!0?(Ce(`LOCAL OK - ${v.model}`),q(`Local model is available: ${v.model} (catalog size=${v.models_count??"n/a"}).`)):v.model_found===!1?(Ce(`LOCAL OK, model not loaded - ${v.model}`),q(`Local server is reachable, but model '${v.model}' is not in loaded catalog. Use 'Load model list' and select one of loaded models.`)):(Ce(`LOCAL OK (model list unavailable) - ${v.model}`),q("Local server is reachable, but model catalog could not be verified.")):(Ce(`OPENAI OK - ${v.model}`),q(`OpenAI connection ok: ${v.model}`))}catch(v){const I=v instanceof Error?v.message:String(v);Ce("Connection error"),Z(`Test connection: ${I}`),q(`Test connection error: ${I}`)}finally{te(!1)}}async function de(){b(!0);try{const I=(await Me.listModels(i)).models??[];Se(I),I.length>0&&h(N=>N.model&&I.includes(N.model)?N:{...N,model:I[0]}),q(`Model catalog loaded (${i.llmProvider}): ${I.length} items.`)}catch(v){const I=v instanceof Error?v.message:String(v);q(`Load model list error: ${I}`)}finally{b(!1)}}S.useEffect(()=>{Se([])},[i.llmProvider,i.baseUrl]);async function zr(v){te(!0),Z("");try{const I=await Me.normalize({connection:i,prompts:p,promptVersion:"normalizer_v2_0_2",query:{userQuestion:P.userQuestion,periodHint:P.periodHint,businessContext:P.businessContext,expectedRoute:P.expectedRoute},saveAsTestCase:v,useMock:Ne});ee(I),re("normalized"),q(`Normalize done: trace=${I.trace_id}, validation=${I.validation.passed?"passed":"failed"}`),Be()}catch(I){const N=I instanceof Error?I.message:String(I);Z(`Normalize: ${N}`),q(`Normalize error: ${N}`)}finally{te(!1)}}function yr(){const v=Ae.find(I=>I.id===Qe);if(!v){q("Preset is not selected.");return}We(p),D({systemPrompt:v.systemPrompt,developerPrompt:v.developerPrompt,domainPrompt:v.domainPrompt,schemaNotes:v.schemaNotes??"",fewShotExamples:v.fewShotExamples??""}),q(`Preset loaded: ${v.name}`)}async function xr(){try{await Me.savePreset({name:it||"NDC preset",prompt_version:"normalizer_v2_0_2",systemPrompt:p.systemPrompt,developerPrompt:p.developerPrompt,domainPrompt:p.domainPrompt,schemaNotes:p.schemaNotes,fewShotExamples:p.fewShotExamples}),q("Preset saved."),await je()}catch(v){q(`Preset save error: ${v instanceof Error?v.message:String(v)}`)}}function Kn(){D(Rc),q("Prompt panel reset to defaults.")}function _r(){const v=rp(p,mt);ge(v),q(v)}function Tn(){const v=P.batchQuestionsRaw.split(";").map(I=>I.trim()).filter(Boolean).join(` + +`);v&&(z(I=>({...I,batchQuestionsRaw:v})),q("Batch field formatted: `;` converted to blank-line separators."))}async function _e(v){try{const N=(await Me.loadTrace(v)).trace,pe=N.parsed_normalized_json??null;ee({trace_id:String(N.trace_id??v),ok:!!N.validation_result?.passed,normalized:pe,route_hint_summary:N.route_hint_summary??(pe?{route_hint:pe.route_hint??null,confidence:pe.confidence?.route_hint??null}:null),raw_model_output:N.raw_model_response??{},validation:N.validation_result??{passed:!1,errors:["validation not found"]},usage:N.usage??{input_tokens:0,output_tokens:0,total_tokens:0},latency_ms:Number(N.latency_ms??0),prompt_version:String(N.prompt_version??"unknown"),schema_version:String(N.schema_version??"unknown")}),re("raw"),Z(""),q(`Trace opened: ${v}`)}catch(I){const N=I instanceof Error?I.message:String(I);Z(`Trace: ${N}`),q(`Trace open error ${v}: ${N}`)}}async function $r(){try{const v=await Me.startRun();d(v.run.runId),q(`Run started: ${v.run.runId}`),q("Tip: start run does not execute normalize by itself. Use 'Run eval v2.0.2' button."),await Pn()}catch(v){q(`Run start error: ${v instanceof Error?v.message:String(v)}`)}}async function dt(){if(A)try{await Me.finishRun(A),q(`Run finished: ${A}`),await Pn()}catch(v){q(`Run finish error: ${v instanceof Error?v.message:String(v)}`)}}async function Fr(){ue(!0),Z("");try{q("Starting eval in v2 contour.");const v=P.batchQuestionsRaw.trim()||P.userQuestion.trim();if(!v)throw new Error("Fill batch field or Raw user question first.");const I=await Me.runEval({connection:i,prompts:p,promptVersion:"normalizer_v2_0_2",mode:"single-pass-strict",rawQuestions:v,useMock:Ne});ve(I.report),q("Eval v2.0.2 run finished.");const N=I.report;if(N.run_id&&q(`Eval run id: ${N.run_id}`),N.metrics){const pe=N.metrics;q(`Eval metrics v2.0.2: schema=${pe.schema_validation_pass_rate??"n/a"}%, route_accuracy=${pe.route_resolution_accuracy??"n/a"}%, no_route_precision=${pe.no_route_precision??"n/a"}%, state_consistency=${pe.execution_state_consistency_rate??"n/a"}%`)}await Be()}catch(v){const I=v instanceof Error?v.message:String(v);I.includes("Legacy eval runner supports normalized_query_v1 only")?(ve({status:"plan_only",prompt_version:"normalizer_v2",reason:"backend eval runner is still legacy-v1 only",plan_file:"reports/v2_pilot_eval_plan.md",next_steps:["run cheap mock sanity for schema/fragment/scope","run small real batch (10-15 messages, temperature=0)","run challenge-30 replay with v2 metrics"]}),q("Backend is legacy-only for eval right now. Showing v2 pilot plan.")):(Z(`Eval: ${I}`),q(`Eval run error: ${I}`))}finally{ue(!1)}}async function Bt(){try{const v=JSON.stringify(ce??{},null,2);await navigator.clipboard.writeText(v),q("Eval report copied to clipboard.")}catch(v){q(`Eval report copy error: ${v instanceof Error?v.message:String(v)}`)}}function Ht(){hr(""),Qn([]),Wn(""),ie(""),En(""),q("Assistant session reset.")}async function yn(){const v=Nn.trim();if(!v)return;gr(!0),En(""),Wn(""),Qn(N=>[...N,{message_id:`local-${Date.now()}`,session_id:ht||"pending",role:"user",text:v,reply_type:null,created_at:new Date().toISOString(),trace_id:null,debug:null}]);const I=vn();try{const N=await Me.sendAssistantMessage({connection:i,prompts:p,userMessage:v,sessionId:ht||void 0,promptVersion:Lc,useMock:Ne});hr(N.session_id),Qn(N.conversation),ie("Ответ готов"),q(`Assistant reply received: trace=${N.debug.trace_id}`)}catch(N){const pe=N instanceof Error?N.message:String(N);En(pe),ie("Ошибка ассистента"),q(`Assistant error: ${pe}`)}finally{I(),gr(!1)}}return S.useEffect(()=>{if(!A){J([]);return}Me.runTrace(A).then(v=>J(v.items)).catch(v=>q(`Run trace error: ${v instanceof Error?v.message:String(v)}`))},[A]),o.jsxs("main",{className:`app-root ${we==="assistant"||we==="decomposition"||we==="autoruns"?"app-root-autoruns":""}`,children:[o.jsxs("header",{className:"app-topbar",children:[o.jsxs("div",{className:"mode-switch-row",children:[o.jsx("button",{type:"button",className:we==="assistant"?"tab active":"tab",onClick:()=>jt("assistant"),children:"Ассистент"}),o.jsx("button",{type:"button",className:we==="decomposition"?"tab active":"tab",onClick:()=>jt("decomposition"),children:"Декомпозиция"}),o.jsx("button",{type:"button",className:we==="autoruns"?"tab active":"tab",onClick:()=>jt("autoruns"),children:"История автопрогонов"}),o.jsx("button",{type:"button",className:"tab",onClick:vr,children:"Сохранить"})]}),we==="assistant"?o.jsxs("div",{className:"mode-switch-row mode-switch-row-right",children:[o.jsx("button",{type:"button",className:kt?"tab active":"tab",onClick:()=>At(v=>!v),children:"LLM Connector"}),o.jsx("button",{type:"button",className:Yt?"tab active":"tab",onClick:()=>Bn(v=>!v),children:"Prompt Manager"}),o.jsx("button",{type:"button",className:cn?"tab active":"tab",onClick:()=>Hn(v=>!v),children:"Режим ассистента"}),o.jsx("button",{type:"button",className:dn?"tab active":"tab",onClick:()=>Dt(v=>!v),children:"SAM"})]}):we==="decomposition"?o.jsxs("div",{className:"mode-switch-row mode-switch-row-right",children:[o.jsx("button",{type:"button",className:Ot?"tab active":"tab",onClick:()=>Vn(v=>!v),children:"LLM"}),o.jsx("button",{type:"button",className:Ct?"tab active":"tab",onClick:()=>Xt(v=>!v),children:"Prompt"}),o.jsx("button",{type:"button",className:Fe?"tab active":"tab",onClick:()=>fn(v=>!v),children:"Запрос"}),o.jsx("button",{type:"button",className:pn?"tab active":"tab",onClick:()=>Or(v=>!v),children:"Выход"}),o.jsx("button",{type:"button",className:zt?"tab active":"tab",onClick:()=>mn(v=>!v),children:"Метрики"}),o.jsx("button",{type:"button",className:$t?"tab active":"tab",onClick:()=>hn(v=>!v),children:"История"}),o.jsx("button",{type:"button",className:gn?"tab active":"tab",onClick:()=>Zt(v=>!v),children:"NDC Run Monitor"})]}):we==="autoruns"?o.jsxs("div",{className:"mode-switch-row mode-switch-row-right",children:[o.jsx("button",{type:"button",className:kn?"tab active":"tab",onClick:()=>an(v=>!v),children:"Режим ассистента"}),o.jsx("button",{type:"button",className:un?"tab active":"tab",onClick:()=>Un(v=>!v),children:"Режим декомпозиции"}),o.jsx("button",{type:"button",className:Lt?"tab active":"tab",onClick:()=>It(v=>!v),children:"Прогресс/регресс"}),o.jsx("button",{type:"button",className:Cn?"tab active":"tab",onClick:()=>Jt(v=>!v),children:"Комментарии"})]}):null]}),we==="assistant"?o.jsx("div",{className:"layout-grid layout-grid-mode-columns",children:o.jsxs("div",{className:"mode-columns",children:[kt?o.jsx("div",{className:"mode-col",children:o.jsx(Ec,{value:i,modelOptions:oe,modelsBusy:se,onChange:h,onReloadModels:de,onSaveLocalConfig:Ut,onTestConnection:Rn,lastStatus:le,busy:Ee||Nt})}):null,Yt?o.jsx("div",{className:"mode-col mode-col-wide",children:o.jsx(Pc,{value:p,onChange:D,presets:Ae,selectedPresetId:Qe,onSelectPreset:Ze,onLoadPreset:yr,onSavePreset:xr,onResetDefaults:Kn,onDiffPrevious:_r,presetName:it,onPresetNameChange:at,diffSummary:be})}):null,cn?o.jsx("div",{className:"mode-col mode-col-xwide",children:o.jsx(Ac,{sessionId:ht,conversation:en,inputValue:Nn,onInputChange:Wn,useMock:Ne,onUseMockChange:fe,onSend:yn,onClear:Ht,busy:Nt,statusText:ut,errorMessage:Ke})}):null,dn?o.jsx("div",{className:"mode-col",children:o.jsx(Pf,{sessionId:ht,conversation:en,statusText:ut,errorMessage:Ke,useMock:Ne,appLogs:G})}):null,!kt&&!Yt&&!cn&&!dn?o.jsx("div",{className:"mode-columns-empty",children:"Все панели режима ассистента скрыты. Включите нужные блоки справа в шапке."}):null]})}):we==="decomposition"?o.jsx("div",{className:"layout-grid layout-grid-mode-columns",children:o.jsxs("div",{className:"mode-columns",children:[Ot?o.jsx("div",{className:"mode-col",children:o.jsx(Ec,{value:i,modelOptions:oe,modelsBusy:se,onChange:h,onReloadModels:de,onSaveLocalConfig:Ut,onTestConnection:Rn,lastStatus:le,busy:Ee})}):null,Ct?o.jsx("div",{className:"mode-col mode-col-wide",children:o.jsx(Pc,{value:p,onChange:D,presets:Ae,selectedPresetId:Qe,onSelectPreset:Ze,onLoadPreset:yr,onSavePreset:xr,onResetDefaults:Kn,onDiffPrevious:_r,presetName:it,onPresetNameChange:at,diffSummary:be})}):null,Fe?o.jsx("div",{className:"mode-col",children:o.jsx(bf,{value:P,onChange:z,onApplyBatchFormat:Tn,onNormalize:zr,busy:Ee,useMock:Ne,onUseMockChange:fe,errorMessage:he})}):null,pn?o.jsx("div",{className:"mode-col mode-col-xwide",children:o.jsx(Gf,{tab:H,onTabChange:re,result:K,appLogs:G})}):null,zt?o.jsx("div",{className:"mode-col",children:o.jsx(Wf,{result:K})}):null,$t?o.jsx("div",{className:"mode-col",children:o.jsx(Qf,{items:U,onRefresh:Be,onOpenTrace:_e})}):null,gn?o.jsx("div",{className:"mode-col mode-col-xwide",children:o.jsx(Jf,{runs:T,selectedRunId:A,onSelectRun:d,onStartRun:$r,onFinishRun:dt,onRefreshRuns:Pn,onRunEval:Fr,onCopyEvalReport:Bt,evalBusy:ae,traceItems:j,evalReport:ce})}):null,!Ot&&!Ct&&!Fe&&!pn&&!zt&&!$t&&!gn?o.jsx("div",{className:"mode-columns-empty",children:"Все панели режима декомпозиции скрыты. Включите нужные блоки справа в шапке."}):null]})}):o.jsx("div",{className:"layout-grid layout-grid-autoruns",children:o.jsx(Vf,{connection:i,prompts:p,assistantPromptVersion:Lc,decompositionPromptVersion:$i,showAssistantMode:kn,showDecompositionMode:un,showProgressMode:Lt,showCommentsMode:Cn,onLog:q})})]})}Cf.createRoot(document.getElementById("root")).render(o.jsx(yf.StrictMode,{children:o.jsx(sp,{})})); diff --git a/llm_normalizer/frontend/dist/assets/index-DNcr9aV9.js b/llm_normalizer/frontend/dist/assets/index-DNcr9aV9.js deleted file mode 100644 index b5c9f90..0000000 --- a/llm_normalizer/frontend/dist/assets/index-DNcr9aV9.js +++ /dev/null @@ -1,13 +0,0 @@ -(function(){const h=document.createElement("link").relList;if(h&&h.supports&&h.supports("modulepreload"))return;for(const E of document.querySelectorAll('link[rel="modulepreload"]'))O(E);new MutationObserver(E=>{for(const D of E)if(D.type==="childList")for(const K of D.addedNodes)K.tagName==="LINK"&&K.rel==="modulepreload"&&O(K)}).observe(document,{childList:!0,subtree:!0});function p(E){const D={};return E.integrity&&(D.integrity=E.integrity),E.referrerPolicy&&(D.referrerPolicy=E.referrerPolicy),E.crossOrigin==="use-credentials"?D.credentials="include":E.crossOrigin==="anonymous"?D.credentials="omit":D.credentials="same-origin",D}function O(E){if(E.ep)return;E.ep=!0;const D=p(E);fetch(E.href,D)}})();function Cc(i){return i&&i.__esModule&&Object.prototype.hasOwnProperty.call(i,"default")?i.default:i}var Si={exports:{}},Ts={},wi={exports:{}},ue={};var tc;function ff(){if(tc)return ue;tc=1;var i=Symbol.for("react.element"),h=Symbol.for("react.portal"),p=Symbol.for("react.fragment"),O=Symbol.for("react.strict_mode"),E=Symbol.for("react.profiler"),D=Symbol.for("react.provider"),K=Symbol.for("react.context"),Z=Symbol.for("react.forward_ref"),B=Symbol.for("react.suspense"),I=Symbol.for("react.memo"),q=Symbol.for("react.lazy"),T=Symbol.iterator;function H(g){return g===null||typeof g!="object"?null:(g=T&&g[T]||g["@@iterator"],typeof g=="function"?g:null)}var fe={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},ye=Object.assign,ne={};function X(g,j,ee){this.props=g,this.context=j,this.refs=ne,this.updater=ee||fe}X.prototype.isReactComponent={},X.prototype.setState=function(g,j){if(typeof g!="object"&&typeof g!="function"&&g!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,g,j,"setState")},X.prototype.forceUpdate=function(g){this.updater.enqueueForceUpdate(this,g,"forceUpdate")};function G(){}G.prototype=X.prototype;function se(g,j,ee){this.props=g,this.context=j,this.refs=ne,this.updater=ee||fe}var Se=se.prototype=new G;Se.constructor=se,ye(Se,X.prototype),Se.isPureReactComponent=!0;var Le=Array.isArray,Oe=Object.prototype.hasOwnProperty,$e={current:null},Ue={key:!0,ref:!0,__self:!0,__source:!0};function Ge(g,j,ee){var oe,le={},ae=null,re=null;if(j!=null)for(oe in j.ref!==void 0&&(re=j.ref),j.key!==void 0&&(ae=""+j.key),j)Oe.call(j,oe)&&!Ue.hasOwnProperty(oe)&&(le[oe]=j[oe]);var he=arguments.length-2;if(he===1)le.children=ee;else if(1>>1,j=A[g];if(0>>1;gE(le,L))aeE(re,le)?(A[g]=re,A[ae]=L,g=ae):(A[g]=le,A[oe]=L,g=oe);else if(aeE(re,L))A[g]=re,A[ae]=L,g=ae;else break e}}return R}function E(A,R){var L=A.sortIndex-R.sortIndex;return L!==0?L:A.id-R.id}if(typeof performance=="object"&&typeof performance.now=="function"){var D=performance;i.unstable_now=function(){return D.now()}}else{var K=Date,Z=K.now();i.unstable_now=function(){return K.now()-Z}}var B=[],I=[],q=1,T=null,H=3,fe=!1,ye=!1,ne=!1,X=typeof setTimeout=="function"?setTimeout:null,G=typeof clearTimeout=="function"?clearTimeout:null,se=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function Se(A){for(var R=p(I);R!==null;){if(R.callback===null)O(I);else if(R.startTime<=A)O(I),R.sortIndex=R.expirationTime,h(B,R);else break;R=p(I)}}function Le(A){if(ne=!1,Se(A),!ye)if(p(B)!==null)ye=!0,xe(Oe);else{var R=p(I);R!==null&&me(Le,R.startTime-A)}}function Oe(A,R){ye=!1,ne&&(ne=!1,G(Ge),Ge=-1),fe=!0;var L=H;try{for(Se(R),T=p(B);T!==null&&(!(T.expirationTime>R)||A&&!Ct());){var g=T.callback;if(typeof g=="function"){T.callback=null,H=T.priorityLevel;var j=g(T.expirationTime<=R);R=i.unstable_now(),typeof j=="function"?T.callback=j:T===p(B)&&O(B),Se(R)}else O(B);T=p(B)}if(T!==null)var ee=!0;else{var oe=p(I);oe!==null&&me(Le,oe.startTime-R),ee=!1}return ee}finally{T=null,H=L,fe=!1}}var $e=!1,Ue=null,Ge=-1,Ze=5,ft=-1;function Ct(){return!(i.unstable_now()-ftA||125g?(A.sortIndex=L,h(I,A),p(B)===null&&A===p(I)&&(ne?(G(Ge),Ge=-1):ne=!0,me(Le,L-g))):(A.sortIndex=j,h(B,A),ye||fe||(ye=!0,xe(Oe))),A},i.unstable_shouldYield=Ct,i.unstable_wrapCallback=function(A){var R=H;return function(){var L=H;H=R;try{return A.apply(this,arguments)}finally{H=L}}}})(Ci)),Ci}var lc;function vf(){return lc||(lc=1,ki.exports=gf()),ki.exports}var ic;function yf(){if(ic)return kt;ic=1;var i=Mi(),h=vf();function p(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,n=1;n"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),B=Object.prototype.hasOwnProperty,I=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,q={},T={};function H(e){return B.call(T,e)?!0:B.call(q,e)?!1:I.test(e)?T[e]=!0:(q[e]=!0,!1)}function fe(e,t,n,r){if(n!==null&&n.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return r?!1:n!==null?!n.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function ye(e,t,n,r){if(t===null||typeof t>"u"||fe(e,t,n,r))return!0;if(r)return!1;if(n!==null)switch(n.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function ne(e,t,n,r,s,l,a){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=r,this.attributeNamespace=s,this.mustUseProperty=n,this.propertyName=e,this.type=t,this.sanitizeURL=l,this.removeEmptyString=a}var X={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){X[e]=new ne(e,0,!1,e,null,!1,!1)}),[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];X[t]=new ne(t,1,!1,e[1],null,!1,!1)}),["contentEditable","draggable","spellCheck","value"].forEach(function(e){X[e]=new ne(e,2,!1,e.toLowerCase(),null,!1,!1)}),["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){X[e]=new ne(e,2,!1,e,null,!1,!1)}),"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){X[e]=new ne(e,3,!1,e.toLowerCase(),null,!1,!1)}),["checked","multiple","muted","selected"].forEach(function(e){X[e]=new ne(e,3,!0,e,null,!1,!1)}),["capture","download"].forEach(function(e){X[e]=new ne(e,4,!1,e,null,!1,!1)}),["cols","rows","size","span"].forEach(function(e){X[e]=new ne(e,6,!1,e,null,!1,!1)}),["rowSpan","start"].forEach(function(e){X[e]=new ne(e,5,!1,e.toLowerCase(),null,!1,!1)});var G=/[\-:]([a-z])/g;function se(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(G,se);X[t]=new ne(t,1,!1,e,null,!1,!1)}),"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(G,se);X[t]=new ne(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)}),["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(G,se);X[t]=new ne(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)}),["tabIndex","crossOrigin"].forEach(function(e){X[e]=new ne(e,1,!1,e.toLowerCase(),null,!1,!1)}),X.xlinkHref=new ne("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1),["src","href","action","formAction"].forEach(function(e){X[e]=new ne(e,1,!1,e.toLowerCase(),null,!0,!0)});function Se(e,t,n,r){var s=X.hasOwnProperty(t)?X[t]:null;(s!==null?s.type!==0:r||!(2c||s[a]!==l[c]){var d=` -`+s[a].replace(" at new "," at ");return e.displayName&&d.includes("")&&(d=d.replace("",e.displayName)),d}while(1<=a&&0<=c);break}}}finally{ee=!1,Error.prepareStackTrace=n}return(e=e?e.displayName||e.name:"")?j(e):""}function le(e){switch(e.tag){case 5:return j(e.type);case 16:return j("Lazy");case 13:return j("Suspense");case 19:return j("SuspenseList");case 0:case 2:case 15:return e=oe(e.type,!1),e;case 11:return e=oe(e.type.render,!1),e;case 1:return e=oe(e.type,!0),e;default:return""}}function ae(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Ue:return"Fragment";case $e:return"Portal";case Ze:return"Profiler";case Ge:return"StrictMode";case ie:return"Suspense";case Be:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case Ct:return(e.displayName||"Context")+".Consumer";case ft:return(e._context.displayName||"Context")+".Provider";case rt:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case Re:return t=e.displayName||null,t!==null?t:ae(e.type)||"Memo";case xe:t=e._payload,e=e._init;try{return ae(e(t))}catch{}}return null}function re(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return ae(t);case 8:return t===Ge?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function he(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function ce(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function ke(e){var t=ce(e)?"checked":"value",n=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),r=""+e[t];if(!e.hasOwnProperty(t)&&typeof n<"u"&&typeof n.get=="function"&&typeof n.set=="function"){var s=n.get,l=n.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return s.call(this)},set:function(a){r=""+a,l.call(this,a)}}),Object.defineProperty(e,t,{enumerable:n.enumerable}),{getValue:function(){return r},setValue:function(a){r=""+a},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function zt(e){e._valueTracker||(e._valueTracker=ke(e))}function wn(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var n=t.getValue(),r="";return e&&(r=ce(e)?e.checked?"true":"false":e.value),e=r,e!==n?(t.setValue(e),!0):!1}function cn(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Nt(e,t){var n=t.checked;return L({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:n??e._wrapperState.initialChecked})}function Jt(e,t){var n=t.defaultValue==null?"":t.defaultValue,r=t.checked!=null?t.checked:t.defaultChecked;n=he(t.value!=null?t.value:n),e._wrapperState={initialChecked:r,initialValue:n,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function jn(e,t){t=t.checked,t!=null&&Se(e,"checked",t,!1)}function bt(e,t){jn(e,t);var n=he(t.value),r=t.type;if(n!=null)r==="number"?(n===0&&e.value===""||e.value!=n)&&(e.value=""+n):e.value!==""+n&&(e.value=""+n);else if(r==="submit"||r==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Cn(e,t.type,n):t.hasOwnProperty("defaultValue")&&Cn(e,t.type,he(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function kn(e,t,n){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var r=t.type;if(!(r!=="submit"&&r!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,n||t===e.value||(e.value=t),e.defaultValue=t}n=e.name,n!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,n!==""&&(e.name=n)}function Cn(e,t,n){(t!=="number"||cn(e.ownerDocument)!==e)&&(n==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+n&&(e.defaultValue=""+n))}var Et=Array.isArray;function Dt(e,t,n,r){if(e=e.options,t){t={};for(var s=0;s"+t.valueOf().toString()+"",t=$t.firstChild;e.firstChild;)e.removeChild(e.firstChild);for(;t.firstChild;)e.appendChild(t.firstChild)}});function Ee(e,t){if(t){var n=e.firstChild;if(n&&n===e.lastChild&&n.nodeType===3){n.nodeValue=t;return}}e.textContent=t}var pn={animationIterationCount:!0,aspectRatio:!0,borderImageOutset:!0,borderImageSlice:!0,borderImageWidth:!0,boxFlex:!0,boxFlexGroup:!0,boxOrdinalGroup:!0,columnCount:!0,columns:!0,flex:!0,flexGrow:!0,flexPositive:!0,flexShrink:!0,flexNegative:!0,flexOrder:!0,gridArea:!0,gridRow:!0,gridRowEnd:!0,gridRowSpan:!0,gridRowStart:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnSpan:!0,gridColumnStart:!0,fontWeight:!0,lineClamp:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,tabSize:!0,widows:!0,zIndex:!0,zoom:!0,fillOpacity:!0,floodOpacity:!0,stopOpacity:!0,strokeDasharray:!0,strokeDashoffset:!0,strokeMiterlimit:!0,strokeOpacity:!0,strokeWidth:!0},Pt=["Webkit","ms","Moz","O"];Object.keys(pn).forEach(function(e){Pt.forEach(function(t){t=t+e.charAt(0).toUpperCase()+e.substring(1),pn[t]=pn[e]})});function cr(e,t,n){return t==null||typeof t=="boolean"||t===""?"":n||typeof t!="number"||t===0||pn.hasOwnProperty(e)&&pn[e]?(""+t).trim():t+"px"}function gt(e,t){e=e.style;for(var n in t)if(t.hasOwnProperty(n)){var r=n.indexOf("--")===0,s=cr(n,t[n],r);n==="float"&&(n="cssFloat"),r?e.setProperty(n,s):e[n]=s}}var de=L({menuitem:!0},{area:!0,base:!0,br:!0,col:!0,embed:!0,hr:!0,img:!0,input:!0,keygen:!0,link:!0,meta:!0,param:!0,source:!0,track:!0,wbr:!0});function We(e,t){if(t){if(de[e]&&(t.children!=null||t.dangerouslySetInnerHTML!=null))throw Error(p(137,e));if(t.dangerouslySetInnerHTML!=null){if(t.children!=null)throw Error(p(60));if(typeof t.dangerouslySetInnerHTML!="object"||!("__html"in t.dangerouslySetInnerHTML))throw Error(p(61))}if(t.style!=null&&typeof t.style!="object")throw Error(p(62))}}function mn(e,t){if(e.indexOf("-")===-1)return typeof t.is=="string";switch(e){case"annotation-xml":case"color-profile":case"font-face":case"font-face-src":case"font-face-uri":case"font-face-format":case"font-face-name":case"missing-glyph":return!1;default:return!0}}var Rt=null;function Zt(e){return e=e.target||e.srcElement||window,e.correspondingUseElement&&(e=e.correspondingUseElement),e.nodeType===3?e.parentNode:e}var st=null,Ft=null,Je=null;function Ve(e){if(e=hs(e)){if(typeof st!="function")throw Error(p(280));var t=e.stateNode;t&&(t=Ys(t),st(e.stateNode,e.type,t))}}function je(e){Ft?Je?Je.push(e):Je=[e]:Ft=e}function En(){if(Ft){var e=Ft,t=Je;if(Je=Ft=null,Ve(e),t)for(e=0;e>>=0,e===0?32:31-(Ec(e)/Pc|0)|0}var Ls=64,As=4194304;function Yr(e){switch(e&-e){case 1:return 1;case 2:return 2;case 4:return 4;case 8:return 8;case 16:return 16;case 32:return 32;case 64:case 128:case 256:case 512:case 1024:case 2048:case 4096:case 8192:case 16384:case 32768:case 65536:case 131072:case 262144:case 524288:case 1048576:case 2097152:return e&4194240;case 4194304:case 8388608:case 16777216:case 33554432:case 67108864:return e&130023424;case 134217728:return 134217728;case 268435456:return 268435456;case 536870912:return 536870912;case 1073741824:return 1073741824;default:return e}}function Os(e,t){var n=e.pendingLanes;if(n===0)return 0;var r=0,s=e.suspendedLanes,l=e.pingedLanes,a=n&268435455;if(a!==0){var c=a&~s;c!==0?r=Yr(c):(l&=a,l!==0&&(r=Yr(l)))}else a=n&~s,a!==0?r=Yr(a):l!==0&&(r=Yr(l));if(r===0)return 0;if(t!==0&&t!==r&&(t&s)===0&&(s=r&-r,l=t&-t,s>=l||s===16&&(l&4194240)!==0))return t;if((r&4)!==0&&(r|=n&16),t=e.entangledLanes,t!==0)for(e=e.entanglements,t&=r;0n;n++)t.push(e);return t}function Xr(e,t,n){e.pendingLanes|=t,t!==536870912&&(e.suspendedLanes=0,e.pingedLanes=0),e=e.eventTimes,t=31-pe(t),e[t]=n}function Ic(e,t){var n=e.pendingLanes&~t;e.pendingLanes=t,e.suspendedLanes=0,e.pingedLanes=0,e.expiredLanes&=t,e.mutableReadLanes&=t,e.entangledLanes&=t,t=e.entanglements;var r=e.eventTimes;for(e=e.expirationTimes;0=ls),Gi=" ",Ji=!1;function bi(e,t){switch(e){case"keyup":return ld.indexOf(t.keyCode)!==-1;case"keydown":return t.keyCode!==229;case"keypress":case"mousedown":case"focusout":return!0;default:return!1}}function Yi(e){return e=e.detail,typeof e=="object"&&"data"in e?e.data:null}var Tr=!1;function ad(e,t){switch(e){case"compositionend":return Yi(t);case"keypress":return t.which!==32?null:(Ji=!0,Gi);case"textInput":return e=t.data,e===Gi&&Ji?null:e;default:return null}}function ud(e,t){if(Tr)return e==="compositionend"||!tl&&bi(e,t)?(e=Hi(),Us=Jo=Gn=null,Tr=!1,e):null;switch(e){case"paste":return null;case"keypress":if(!(t.ctrlKey||t.altKey||t.metaKey)||t.ctrlKey&&t.altKey){if(t.char&&1=t)return{node:n,offset:t-e};e=r}e:{for(;n;){if(n.nextSibling){n=n.nextSibling;break e}n=n.parentNode}n=void 0}n=sa(n)}}function la(e,t){return e&&t?e===t?!0:e&&e.nodeType===3?!1:t&&t.nodeType===3?la(e,t.parentNode):"contains"in e?e.contains(t):e.compareDocumentPosition?!!(e.compareDocumentPosition(t)&16):!1:!1}function ia(){for(var e=window,t=cn();t instanceof e.HTMLIFrameElement;){try{var n=typeof t.contentWindow.location.href=="string"}catch{n=!1}if(n)e=t.contentWindow;else break;t=cn(e.document)}return t}function sl(e){var t=e&&e.nodeName&&e.nodeName.toLowerCase();return t&&(t==="input"&&(e.type==="text"||e.type==="search"||e.type==="tel"||e.type==="url"||e.type==="password")||t==="textarea"||e.contentEditable==="true")}function yd(e){var t=ia(),n=e.focusedElem,r=e.selectionRange;if(t!==n&&n&&n.ownerDocument&&la(n.ownerDocument.documentElement,n)){if(r!==null&&sl(n)){if(t=r.start,e=r.end,e===void 0&&(e=t),"selectionStart"in n)n.selectionStart=t,n.selectionEnd=Math.min(e,n.value.length);else if(e=(t=n.ownerDocument||document)&&t.defaultView||window,e.getSelection){e=e.getSelection();var s=n.textContent.length,l=Math.min(r.start,s);r=r.end===void 0?l:Math.min(r.end,s),!e.extend&&l>r&&(s=r,r=l,l=s),s=oa(n,l);var a=oa(n,r);s&&a&&(e.rangeCount!==1||e.anchorNode!==s.node||e.anchorOffset!==s.offset||e.focusNode!==a.node||e.focusOffset!==a.offset)&&(t=t.createRange(),t.setStart(s.node,s.offset),e.removeAllRanges(),l>r?(e.addRange(t),e.extend(a.node,a.offset)):(t.setEnd(a.node,a.offset),e.addRange(t)))}}for(t=[],e=n;e=e.parentNode;)e.nodeType===1&&t.push({element:e,left:e.scrollLeft,top:e.scrollTop});for(typeof n.focus=="function"&&n.focus(),n=0;n=document.documentMode,Mr=null,ol=null,cs=null,ll=!1;function aa(e,t,n){var r=n.window===n?n.document:n.nodeType===9?n:n.ownerDocument;ll||Mr==null||Mr!==cn(r)||(r=Mr,"selectionStart"in r&&sl(r)?r={start:r.selectionStart,end:r.selectionEnd}:(r=(r.ownerDocument&&r.ownerDocument.defaultView||window).getSelection(),r={anchorNode:r.anchorNode,anchorOffset:r.anchorOffset,focusNode:r.focusNode,focusOffset:r.focusOffset}),cs&&us(cs,r)||(cs=r,r=Gs(ol,"onSelect"),0zr||(e.current=yl[zr],yl[zr]=null,zr--)}function Pe(e,t){zr++,yl[zr]=e.current,e.current=t}var Xn={},lt=Yn(Xn),xt=Yn(!1),hr=Xn;function Dr(e,t){var n=e.type.contextTypes;if(!n)return Xn;var r=e.stateNode;if(r&&r.__reactInternalMemoizedUnmaskedChildContext===t)return r.__reactInternalMemoizedMaskedChildContext;var s={},l;for(l in n)s[l]=t[l];return r&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=s),s}function _t(e){return e=e.childContextTypes,e!=null}function Xs(){Me(xt),Me(lt)}function ja(e,t,n){if(lt.current!==Xn)throw Error(p(168));Pe(lt,t),Pe(xt,n)}function ka(e,t,n){var r=e.stateNode;if(t=t.childContextTypes,typeof r.getChildContext!="function")return n;r=r.getChildContext();for(var s in r)if(!(s in t))throw Error(p(108,re(e)||"Unknown",s));return L({},n,r)}function Zs(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Xn,hr=lt.current,Pe(lt,e),Pe(xt,xt.current),!0}function Ca(e,t,n){var r=e.stateNode;if(!r)throw Error(p(169));n?(e=ka(e,t,hr),r.__reactInternalMemoizedMergedChildContext=e,Me(xt),Me(lt),Pe(lt,e)):Me(xt),Pe(xt,n)}var Mn=null,eo=!1,xl=!1;function Na(e){Mn===null?Mn=[e]:Mn.push(e)}function Td(e){eo=!0,Na(e)}function Zn(){if(!xl&&Mn!==null){xl=!0;var e=0,t=Ce;try{var n=Mn;for(Ce=1;e>=a,s-=a,In=1<<32-pe(t)+s|n<te?(Xe=b,b=null):Xe=b.sibling;var we=k(v,b,y[te],M);if(we===null){b===null&&(b=Xe);break}e&&b&&we.alternate===null&&t(v,b),m=l(we,m,te),J===null?W=we:J.sibling=we,J=we,b=Xe}if(te===y.length)return n(v,b),Ae&&vr(v,te),W;if(b===null){for(;tete?(Xe=b,b=null):Xe=b.sibling;var ar=k(v,b,we.value,M);if(ar===null){b===null&&(b=Xe);break}e&&b&&ar.alternate===null&&t(v,b),m=l(ar,m,te),J===null?W=ar:J.sibling=ar,J=ar,b=Xe}if(we.done)return n(v,b),Ae&&vr(v,te),W;if(b===null){for(;!we.done;te++,we=y.next())we=N(v,we.value,M),we!==null&&(m=l(we,m,te),J===null?W=we:J.sibling=we,J=we);return Ae&&vr(v,te),W}for(b=r(v,b);!we.done;te++,we=y.next())we=$(b,v,te,we.value,M),we!==null&&(e&&we.alternate!==null&&b.delete(we.key===null?te:we.key),m=l(we,m,te),J===null?W=we:J.sibling=we,J=we);return e&&b.forEach(function(df){return t(v,df)}),Ae&&vr(v,te),W}function He(v,m,y,M){if(typeof y=="object"&&y!==null&&y.type===Ue&&y.key===null&&(y=y.props.children),typeof y=="object"&&y!==null){switch(y.$$typeof){case Oe:e:{for(var W=y.key,J=m;J!==null;){if(J.key===W){if(W=y.type,W===Ue){if(J.tag===7){n(v,J.sibling),m=s(J,y.props.children),m.return=v,v=m;break e}}else if(J.elementType===W||typeof W=="object"&&W!==null&&W.$$typeof===xe&&Ia(W)===J.type){n(v,J.sibling),m=s(J,y.props),m.ref=gs(v,J,y),m.return=v,v=m;break e}n(v,J);break}else t(v,J);J=J.sibling}y.type===Ue?(m=Cr(y.props.children,v.mode,M,y.key),m.return=v,v=m):(M=Ro(y.type,y.key,y.props,null,v.mode,M),M.ref=gs(v,m,y),M.return=v,v=M)}return a(v);case $e:e:{for(J=y.key;m!==null;){if(m.key===J)if(m.tag===4&&m.stateNode.containerInfo===y.containerInfo&&m.stateNode.implementation===y.implementation){n(v,m.sibling),m=s(m,y.children||[]),m.return=v,v=m;break e}else{n(v,m);break}else t(v,m);m=m.sibling}m=gi(y,v.mode,M),m.return=v,v=m}return a(v);case xe:return J=y._init,He(v,m,J(y._payload),M)}if(Et(y))return V(v,m,y,M);if(R(y))return Q(v,m,y,M);so(v,y)}return typeof y=="string"&&y!==""||typeof y=="number"?(y=""+y,m!==null&&m.tag===6?(n(v,m.sibling),m=s(m,y),m.return=v,v=m):(n(v,m),m=hi(y,v.mode,M),m.return=v,v=m),a(v)):n(v,m)}return He}var Br=La(!0),Aa=La(!1),oo=Yn(null),lo=null,Hr=null,Cl=null;function Nl(){Cl=Hr=lo=null}function El(e){var t=oo.current;Me(oo),e._currentValue=t}function Pl(e,t,n){for(;e!==null;){var r=e.alternate;if((e.childLanes&t)!==t?(e.childLanes|=t,r!==null&&(r.childLanes|=t)):r!==null&&(r.childLanes&t)!==t&&(r.childLanes|=t),e===n)break;e=e.return}}function Vr(e,t){lo=e,Cl=Hr=null,e=e.dependencies,e!==null&&e.firstContext!==null&&((e.lanes&t)!==0&&(St=!0),e.firstContext=null)}function Wt(e){var t=e._currentValue;if(Cl!==e)if(e={context:e,memoizedValue:t,next:null},Hr===null){if(lo===null)throw Error(p(308));Hr=e,lo.dependencies={lanes:0,firstContext:e}}else Hr=Hr.next=e;return t}var yr=null;function Rl(e){yr===null?yr=[e]:yr.push(e)}function Oa(e,t,n,r){var s=t.interleaved;return s===null?(n.next=n,Rl(t)):(n.next=s.next,s.next=n),t.interleaved=n,An(e,r)}function An(e,t){e.lanes|=t;var n=e.alternate;for(n!==null&&(n.lanes|=t),n=e,e=e.return;e!==null;)e.childLanes|=t,n=e.alternate,n!==null&&(n.childLanes|=t),n=e,e=e.return;return n.tag===3?n.stateNode:null}var er=!1;function Tl(e){e.updateQueue={baseState:e.memoizedState,firstBaseUpdate:null,lastBaseUpdate:null,shared:{pending:null,interleaved:null,lanes:0},effects:null}}function za(e,t){e=e.updateQueue,t.updateQueue===e&&(t.updateQueue={baseState:e.baseState,firstBaseUpdate:e.firstBaseUpdate,lastBaseUpdate:e.lastBaseUpdate,shared:e.shared,effects:e.effects})}function On(e,t){return{eventTime:e,lane:t,tag:0,payload:null,callback:null,next:null}}function tr(e,t,n){var r=e.updateQueue;if(r===null)return null;if(r=r.shared,(_e&2)!==0){var s=r.pending;return s===null?t.next=t:(t.next=s.next,s.next=t),r.pending=t,An(e,n)}return s=r.interleaved,s===null?(t.next=t,Rl(r)):(t.next=s.next,s.next=t),r.interleaved=t,An(e,n)}function io(e,t,n){if(t=t.updateQueue,t!==null&&(t=t.shared,(n&4194240)!==0)){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Qo(e,n)}}function Da(e,t){var n=e.updateQueue,r=e.alternate;if(r!==null&&(r=r.updateQueue,n===r)){var s=null,l=null;if(n=n.firstBaseUpdate,n!==null){do{var a={eventTime:n.eventTime,lane:n.lane,tag:n.tag,payload:n.payload,callback:n.callback,next:null};l===null?s=l=a:l=l.next=a,n=n.next}while(n!==null);l===null?s=l=t:l=l.next=t}else s=l=t;n={baseState:r.baseState,firstBaseUpdate:s,lastBaseUpdate:l,shared:r.shared,effects:r.effects},e.updateQueue=n;return}e=n.lastBaseUpdate,e===null?n.firstBaseUpdate=t:e.next=t,n.lastBaseUpdate=t}function ao(e,t,n,r){var s=e.updateQueue;er=!1;var l=s.firstBaseUpdate,a=s.lastBaseUpdate,c=s.shared.pending;if(c!==null){s.shared.pending=null;var d=c,_=d.next;d.next=null,a===null?l=_:a.next=_,a=d;var C=e.alternate;C!==null&&(C=C.updateQueue,c=C.lastBaseUpdate,c!==a&&(c===null?C.firstBaseUpdate=_:c.next=_,C.lastBaseUpdate=d))}if(l!==null){var N=s.baseState;a=0,C=_=d=null,c=l;do{var k=c.lane,$=c.eventTime;if((r&k)===k){C!==null&&(C=C.next={eventTime:$,lane:0,tag:c.tag,payload:c.payload,callback:c.callback,next:null});e:{var V=e,Q=c;switch(k=t,$=n,Q.tag){case 1:if(V=Q.payload,typeof V=="function"){N=V.call($,N,k);break e}N=V;break e;case 3:V.flags=V.flags&-65537|128;case 0:if(V=Q.payload,k=typeof V=="function"?V.call($,N,k):V,k==null)break e;N=L({},N,k);break e;case 2:er=!0}}c.callback!==null&&c.lane!==0&&(e.flags|=64,k=s.effects,k===null?s.effects=[c]:k.push(c))}else $={eventTime:$,lane:k,tag:c.tag,payload:c.payload,callback:c.callback,next:null},C===null?(_=C=$,d=N):C=C.next=$,a|=k;if(c=c.next,c===null){if(c=s.shared.pending,c===null)break;k=c,c=k.next,k.next=null,s.lastBaseUpdate=k,s.shared.pending=null}}while(!0);if(C===null&&(d=N),s.baseState=d,s.firstBaseUpdate=_,s.lastBaseUpdate=C,t=s.shared.interleaved,t!==null){s=t;do a|=s.lane,s=s.next;while(s!==t)}else l===null&&(s.shared.lanes=0);Sr|=a,e.lanes=a,e.memoizedState=N}}function $a(e,t,n){if(e=t.effects,t.effects=null,e!==null)for(t=0;tn?n:4,e(!0);var r=Ol.transition;Ol.transition={};try{e(!1),t()}finally{Ce=n,Ol.transition=r}}function ru(){return Kt().memoizedState}function Ad(e,t,n){var r=or(e);if(n={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null},su(e))ou(t,n);else if(n=Oa(e,t,n,r),n!==null){var s=ht();un(n,e,r,s),lu(n,t,r)}}function Od(e,t,n){var r=or(e),s={lane:r,action:n,hasEagerState:!1,eagerState:null,next:null};if(su(e))ou(t,s);else{var l=e.alternate;if(e.lanes===0&&(l===null||l.lanes===0)&&(l=t.lastRenderedReducer,l!==null))try{var a=t.lastRenderedState,c=l(a,n);if(s.hasEagerState=!0,s.eagerState=c,rn(c,a)){var d=t.interleaved;d===null?(s.next=s,Rl(t)):(s.next=d.next,d.next=s),t.interleaved=s;return}}catch{}n=Oa(e,t,s,r),n!==null&&(s=ht(),un(n,e,r,s),lu(n,t,r))}}function su(e){var t=e.alternate;return e===De||t!==null&&t===De}function ou(e,t){_s=fo=!0;var n=e.pending;n===null?t.next=t:(t.next=n.next,n.next=t),e.pending=t}function lu(e,t,n){if((n&4194240)!==0){var r=t.lanes;r&=e.pendingLanes,n|=r,t.lanes=n,Qo(e,n)}}var ho={readContext:Wt,useCallback:it,useContext:it,useEffect:it,useImperativeHandle:it,useInsertionEffect:it,useLayoutEffect:it,useMemo:it,useReducer:it,useRef:it,useState:it,useDebugValue:it,useDeferredValue:it,useTransition:it,useMutableSource:it,useSyncExternalStore:it,useId:it,unstable_isNewReconciler:!1},zd={readContext:Wt,useCallback:function(e,t){return xn().memoizedState=[e,t===void 0?null:t],e},useContext:Wt,useEffect:Ja,useImperativeHandle:function(e,t,n){return n=n!=null?n.concat([e]):null,po(4194308,4,Xa.bind(null,t,e),n)},useLayoutEffect:function(e,t){return po(4194308,4,e,t)},useInsertionEffect:function(e,t){return po(4,2,e,t)},useMemo:function(e,t){var n=xn();return t=t===void 0?null:t,e=e(),n.memoizedState=[e,t],e},useReducer:function(e,t,n){var r=xn();return t=n!==void 0?n(t):t,r.memoizedState=r.baseState=t,e={pending:null,interleaved:null,lanes:0,dispatch:null,lastRenderedReducer:e,lastRenderedState:t},r.queue=e,e=e.dispatch=Ad.bind(null,De,e),[r.memoizedState,e]},useRef:function(e){var t=xn();return e={current:e},t.memoizedState=e},useState:qa,useDebugValue:Hl,useDeferredValue:function(e){return xn().memoizedState=e},useTransition:function(){var e=qa(!1),t=e[0];return e=Ld.bind(null,e[1]),xn().memoizedState=e,[t,e]},useMutableSource:function(){},useSyncExternalStore:function(e,t,n){var r=De,s=xn();if(Ae){if(n===void 0)throw Error(p(407));n=n()}else{if(n=t(),Ye===null)throw Error(p(349));(_r&30)!==0||Ha(r,t,n)}s.memoizedState=n;var l={value:n,getSnapshot:t};return s.queue=l,Ja(Qa.bind(null,r,l,e),[e]),r.flags|=2048,js(9,Va.bind(null,r,l,n,t),void 0,null),n},useId:function(){var e=xn(),t=Ye.identifierPrefix;if(Ae){var n=Ln,r=In;n=(r&~(1<<32-pe(r)-1)).toString(32)+n,t=":"+t+"R"+n,n=Ss++,0<\/script>",e=e.removeChild(e.firstChild)):typeof r.is=="string"?e=a.createElement(n,{is:r.is}):(e=a.createElement(n),n==="select"&&(a=e,r.multiple?a.multiple=!0:r.size&&(a.size=r.size))):e=a.createElementNS(e,n),e[vn]=t,e[ms]=r,Nu(e,t,!1,!1),t.stateNode=e;e:{switch(a=mn(n,r),n){case"dialog":Te("cancel",e),Te("close",e),s=r;break;case"iframe":case"object":case"embed":Te("load",e),s=r;break;case"video":case"audio":for(s=0;sGr&&(t.flags|=128,r=!0,ks(l,!1),t.lanes=4194304)}else{if(!r)if(e=uo(a),e!==null){if(t.flags|=128,r=!0,n=e.updateQueue,n!==null&&(t.updateQueue=n,t.flags|=4),ks(l,!0),l.tail===null&&l.tailMode==="hidden"&&!a.alternate&&!Ae)return at(t),null}else 2*f()-l.renderingStartTime>Gr&&n!==1073741824&&(t.flags|=128,r=!0,ks(l,!1),t.lanes=4194304);l.isBackwards?(a.sibling=t.child,t.child=a):(n=l.last,n!==null?n.sibling=a:t.child=a,l.last=a)}return l.tail!==null?(t=l.tail,l.rendering=t,l.tail=t.sibling,l.renderingStartTime=f(),t.sibling=null,n=ze.current,Pe(ze,r?n&1|2:n&1),t):(at(t),null);case 22:case 23:return fi(),r=t.memoizedState!==null,e!==null&&e.memoizedState!==null!==r&&(t.flags|=8192),r&&(t.mode&1)!==0?(Ot&1073741824)!==0&&(at(t),t.subtreeFlags&6&&(t.flags|=8192)):at(t),null;case 24:return null;case 25:return null}throw Error(p(156,t.tag))}function Qd(e,t){switch(Sl(t),t.tag){case 1:return _t(t.type)&&Xs(),e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 3:return Qr(),Me(xt),Me(lt),Al(),e=t.flags,(e&65536)!==0&&(e&128)===0?(t.flags=e&-65537|128,t):null;case 5:return Il(t),null;case 13:if(Me(ze),e=t.memoizedState,e!==null&&e.dehydrated!==null){if(t.alternate===null)throw Error(p(340));Ur()}return e=t.flags,e&65536?(t.flags=e&-65537|128,t):null;case 19:return Me(ze),null;case 4:return Qr(),null;case 10:return El(t.type._context),null;case 22:case 23:return fi(),null;case 24:return null;default:return null}}var xo=!1,ut=!1,Wd=typeof WeakSet=="function"?WeakSet:Set,U=null;function Kr(e,t){var n=e.ref;if(n!==null)if(typeof n=="function")try{n(null)}catch(r){Fe(e,t,r)}else n.current=null}function ei(e,t,n){try{n()}catch(r){Fe(e,t,r)}}var Ru=!1;function Kd(e,t){if(fl=$s,e=ia(),sl(e)){if("selectionStart"in e)var n={start:e.selectionStart,end:e.selectionEnd};else e:{n=(n=e.ownerDocument)&&n.defaultView||window;var r=n.getSelection&&n.getSelection();if(r&&r.rangeCount!==0){n=r.anchorNode;var s=r.anchorOffset,l=r.focusNode;r=r.focusOffset;try{n.nodeType,l.nodeType}catch{n=null;break e}var a=0,c=-1,d=-1,_=0,C=0,N=e,k=null;t:for(;;){for(var $;N!==n||s!==0&&N.nodeType!==3||(c=a+s),N!==l||r!==0&&N.nodeType!==3||(d=a+r),N.nodeType===3&&(a+=N.nodeValue.length),($=N.firstChild)!==null;)k=N,N=$;for(;;){if(N===e)break t;if(k===n&&++_===s&&(c=a),k===l&&++C===r&&(d=a),($=N.nextSibling)!==null)break;N=k,k=N.parentNode}N=$}n=c===-1||d===-1?null:{start:c,end:d}}else n=null}n=n||{start:0,end:0}}else n=null;for(pl={focusedElem:e,selectionRange:n},$s=!1,U=t;U!==null;)if(t=U,e=t.child,(t.subtreeFlags&1028)!==0&&e!==null)e.return=t,U=e;else for(;U!==null;){t=U;try{var V=t.alternate;if((t.flags&1024)!==0)switch(t.tag){case 0:case 11:case 15:break;case 1:if(V!==null){var Q=V.memoizedProps,He=V.memoizedState,v=t.stateNode,m=v.getSnapshotBeforeUpdate(t.elementType===t.type?Q:on(t.type,Q),He);v.__reactInternalSnapshotBeforeUpdate=m}break;case 3:var y=t.stateNode.containerInfo;y.nodeType===1?y.textContent="":y.nodeType===9&&y.documentElement&&y.removeChild(y.documentElement);break;case 5:case 6:case 4:case 17:break;default:throw Error(p(163))}}catch(M){Fe(t,t.return,M)}if(e=t.sibling,e!==null){e.return=t.return,U=e;break}U=t.return}return V=Ru,Ru=!1,V}function Cs(e,t,n){var r=t.updateQueue;if(r=r!==null?r.lastEffect:null,r!==null){var s=r=r.next;do{if((s.tag&e)===e){var l=s.destroy;s.destroy=void 0,l!==void 0&&ei(t,n,l)}s=s.next}while(s!==r)}}function _o(e,t){if(t=t.updateQueue,t=t!==null?t.lastEffect:null,t!==null){var n=t=t.next;do{if((n.tag&e)===e){var r=n.create;n.destroy=r()}n=n.next}while(n!==t)}}function ti(e){var t=e.ref;if(t!==null){var n=e.stateNode;e.tag,e=n,typeof t=="function"?t(e):t.current=e}}function Tu(e){var t=e.alternate;t!==null&&(e.alternate=null,Tu(t)),e.child=null,e.deletions=null,e.sibling=null,e.tag===5&&(t=e.stateNode,t!==null&&(delete t[vn],delete t[ms],delete t[vl],delete t[Pd],delete t[Rd])),e.stateNode=null,e.return=null,e.dependencies=null,e.memoizedProps=null,e.memoizedState=null,e.pendingProps=null,e.stateNode=null,e.updateQueue=null}function Mu(e){return e.tag===5||e.tag===3||e.tag===4}function Iu(e){e:for(;;){for(;e.sibling===null;){if(e.return===null||Mu(e.return))return null;e=e.return}for(e.sibling.return=e.return,e=e.sibling;e.tag!==5&&e.tag!==6&&e.tag!==18;){if(e.flags&2||e.child===null||e.tag===4)continue e;e.child.return=e,e=e.child}if(!(e.flags&2))return e.stateNode}}function ni(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.nodeType===8?n.parentNode.insertBefore(e,t):n.insertBefore(e,t):(n.nodeType===8?(t=n.parentNode,t.insertBefore(e,n)):(t=n,t.appendChild(e)),n=n._reactRootContainer,n!=null||t.onclick!==null||(t.onclick=bs));else if(r!==4&&(e=e.child,e!==null))for(ni(e,t,n),e=e.sibling;e!==null;)ni(e,t,n),e=e.sibling}function ri(e,t,n){var r=e.tag;if(r===5||r===6)e=e.stateNode,t?n.insertBefore(e,t):n.appendChild(e);else if(r!==4&&(e=e.child,e!==null))for(ri(e,t,n),e=e.sibling;e!==null;)ri(e,t,n),e=e.sibling}var tt=null,ln=!1;function nr(e,t,n){for(n=n.child;n!==null;)Lu(e,t,n),n=n.sibling}function Lu(e,t,n){if(z&&typeof z.onCommitFiberUnmount=="function")try{z.onCommitFiberUnmount(x,n)}catch{}switch(n.tag){case 5:ut||Kr(n,t);case 6:var r=tt,s=ln;tt=null,nr(e,t,n),tt=r,ln=s,tt!==null&&(ln?(e=tt,n=n.stateNode,e.nodeType===8?e.parentNode.removeChild(n):e.removeChild(n)):tt.removeChild(n.stateNode));break;case 18:tt!==null&&(ln?(e=tt,n=n.stateNode,e.nodeType===8?gl(e.parentNode,n):e.nodeType===1&&gl(e,n),rs(e)):gl(tt,n.stateNode));break;case 4:r=tt,s=ln,tt=n.stateNode.containerInfo,ln=!0,nr(e,t,n),tt=r,ln=s;break;case 0:case 11:case 14:case 15:if(!ut&&(r=n.updateQueue,r!==null&&(r=r.lastEffect,r!==null))){s=r=r.next;do{var l=s,a=l.destroy;l=l.tag,a!==void 0&&((l&2)!==0||(l&4)!==0)&&ei(n,t,a),s=s.next}while(s!==r)}nr(e,t,n);break;case 1:if(!ut&&(Kr(n,t),r=n.stateNode,typeof r.componentWillUnmount=="function"))try{r.props=n.memoizedProps,r.state=n.memoizedState,r.componentWillUnmount()}catch(c){Fe(n,t,c)}nr(e,t,n);break;case 21:nr(e,t,n);break;case 22:n.mode&1?(ut=(r=ut)||n.memoizedState!==null,nr(e,t,n),ut=r):nr(e,t,n);break;default:nr(e,t,n)}}function Au(e){var t=e.updateQueue;if(t!==null){e.updateQueue=null;var n=e.stateNode;n===null&&(n=e.stateNode=new Wd),t.forEach(function(r){var s=tf.bind(null,e,r);n.has(r)||(n.add(r),r.then(s,s))})}}function an(e,t){var n=t.deletions;if(n!==null)for(var r=0;rs&&(s=a),r&=~l}if(r=s,r=f()-r,r=(120>r?120:480>r?480:1080>r?1080:1920>r?1920:3e3>r?3e3:4320>r?4320:1960*Gd(r/1960))-r,10e?16:e,sr===null)var r=!1;else{if(e=sr,sr=null,Co=0,(_e&6)!==0)throw Error(p(331));var s=_e;for(_e|=4,U=e.current;U!==null;){var l=U,a=l.child;if((U.flags&16)!==0){var c=l.deletions;if(c!==null){for(var d=0;df()-li?jr(e,0):oi|=n),jt(e,t)}function qu(e,t){t===0&&((e.mode&1)===0?t=1:(t=As,As<<=1,(As&130023424)===0&&(As=4194304)));var n=ht();e=An(e,t),e!==null&&(Xr(e,t,n),jt(e,n))}function ef(e){var t=e.memoizedState,n=0;t!==null&&(n=t.retryLane),qu(e,n)}function tf(e,t){var n=0;switch(e.tag){case 13:var r=e.stateNode,s=e.memoizedState;s!==null&&(n=s.retryLane);break;case 19:r=e.stateNode;break;default:throw Error(p(314))}r!==null&&r.delete(t),qu(e,n)}var Gu;Gu=function(e,t,n){if(e!==null)if(e.memoizedProps!==t.pendingProps||xt.current)St=!0;else{if((e.lanes&n)===0&&(t.flags&128)===0)return St=!1,Hd(e,t,n);St=(e.flags&131072)!==0}else St=!1,Ae&&(t.flags&1048576)!==0&&Ea(t,no,t.index);switch(t.lanes=0,t.tag){case 2:var r=t.type;yo(e,t),e=t.pendingProps;var s=Dr(t,lt.current);Vr(t,n),s=Dl(null,t,r,e,s,n);var l=$l();return t.flags|=1,typeof s=="object"&&s!==null&&typeof s.render=="function"&&s.$$typeof===void 0?(t.tag=1,t.memoizedState=null,t.updateQueue=null,_t(r)?(l=!0,Zs(t)):l=!1,t.memoizedState=s.state!==null&&s.state!==void 0?s.state:null,Tl(t),s.updater=go,t.stateNode=s,s._reactInternals=t,Ql(t,r,e,n),t=Gl(null,t,r,!0,l,n)):(t.tag=0,Ae&&l&&_l(t),mt(null,t,s,n),t=t.child),t;case 16:r=t.elementType;e:{switch(yo(e,t),e=t.pendingProps,s=r._init,r=s(r._payload),t.type=r,s=t.tag=rf(r),e=on(r,e),s){case 0:t=ql(null,t,r,e,n);break e;case 1:t=_u(null,t,r,e,n);break e;case 11:t=hu(null,t,r,e,n);break e;case 14:t=gu(null,t,r,on(r.type,e),n);break e}throw Error(p(306,r,""))}return t;case 0:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:on(r,s),ql(e,t,r,s,n);case 1:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:on(r,s),_u(e,t,r,s,n);case 3:e:{if(Su(t),e===null)throw Error(p(387));r=t.pendingProps,l=t.memoizedState,s=l.element,za(e,t),ao(t,r,null,n);var a=t.memoizedState;if(r=a.element,l.isDehydrated)if(l={element:r,isDehydrated:!1,cache:a.cache,pendingSuspenseBoundaries:a.pendingSuspenseBoundaries,transitions:a.transitions},t.updateQueue.baseState=l,t.memoizedState=l,t.flags&256){s=Wr(Error(p(423)),t),t=wu(e,t,r,n,s);break e}else if(r!==s){s=Wr(Error(p(424)),t),t=wu(e,t,r,n,s);break e}else for(At=bn(t.stateNode.containerInfo.firstChild),Lt=t,Ae=!0,sn=null,n=Aa(t,null,r,n),t.child=n;n;)n.flags=n.flags&-3|4096,n=n.sibling;else{if(Ur(),r===s){t=zn(e,t,n);break e}mt(e,t,r,n)}t=t.child}return t;case 5:return Fa(t),e===null&&jl(t),r=t.type,s=t.pendingProps,l=e!==null?e.memoizedProps:null,a=s.children,ml(r,s)?a=null:l!==null&&ml(r,l)&&(t.flags|=32),xu(e,t),mt(e,t,a,n),t.child;case 6:return e===null&&jl(t),null;case 13:return ju(e,t,n);case 4:return Ml(t,t.stateNode.containerInfo),r=t.pendingProps,e===null?t.child=Br(t,null,r,n):mt(e,t,r,n),t.child;case 11:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:on(r,s),hu(e,t,r,s,n);case 7:return mt(e,t,t.pendingProps,n),t.child;case 8:return mt(e,t,t.pendingProps.children,n),t.child;case 12:return mt(e,t,t.pendingProps.children,n),t.child;case 10:e:{if(r=t.type._context,s=t.pendingProps,l=t.memoizedProps,a=s.value,Pe(oo,r._currentValue),r._currentValue=a,l!==null)if(rn(l.value,a)){if(l.children===s.children&&!xt.current){t=zn(e,t,n);break e}}else for(l=t.child,l!==null&&(l.return=t);l!==null;){var c=l.dependencies;if(c!==null){a=l.child;for(var d=c.firstContext;d!==null;){if(d.context===r){if(l.tag===1){d=On(-1,n&-n),d.tag=2;var _=l.updateQueue;if(_!==null){_=_.shared;var C=_.pending;C===null?d.next=d:(d.next=C.next,C.next=d),_.pending=d}}l.lanes|=n,d=l.alternate,d!==null&&(d.lanes|=n),Pl(l.return,n,t),c.lanes|=n;break}d=d.next}}else if(l.tag===10)a=l.type===t.type?null:l.child;else if(l.tag===18){if(a=l.return,a===null)throw Error(p(341));a.lanes|=n,c=a.alternate,c!==null&&(c.lanes|=n),Pl(a,n,t),a=l.sibling}else a=l.child;if(a!==null)a.return=l;else for(a=l;a!==null;){if(a===t){a=null;break}if(l=a.sibling,l!==null){l.return=a.return,a=l;break}a=a.return}l=a}mt(e,t,s.children,n),t=t.child}return t;case 9:return s=t.type,r=t.pendingProps.children,Vr(t,n),s=Wt(s),r=r(s),t.flags|=1,mt(e,t,r,n),t.child;case 14:return r=t.type,s=on(r,t.pendingProps),s=on(r.type,s),gu(e,t,r,s,n);case 15:return vu(e,t,t.type,t.pendingProps,n);case 17:return r=t.type,s=t.pendingProps,s=t.elementType===r?s:on(r,s),yo(e,t),t.tag=1,_t(r)?(e=!0,Zs(t)):e=!1,Vr(t,n),au(t,r,s),Ql(t,r,s,n),Gl(null,t,r,!0,e,n);case 19:return Cu(e,t,n);case 22:return yu(e,t,n)}throw Error(p(156,t.tag))};function Ju(e,t){return Hn(e,t)}function nf(e,t,n,r){this.tag=e,this.key=n,this.sibling=this.child=this.return=this.stateNode=this.type=this.elementType=null,this.index=0,this.ref=null,this.pendingProps=t,this.dependencies=this.memoizedState=this.updateQueue=this.memoizedProps=null,this.mode=r,this.subtreeFlags=this.flags=0,this.deletions=null,this.childLanes=this.lanes=0,this.alternate=null}function Gt(e,t,n,r){return new nf(e,t,n,r)}function mi(e){return e=e.prototype,!(!e||!e.isReactComponent)}function rf(e){if(typeof e=="function")return mi(e)?1:0;if(e!=null){if(e=e.$$typeof,e===rt)return 11;if(e===Re)return 14}return 2}function ir(e,t){var n=e.alternate;return n===null?(n=Gt(e.tag,t,e.key,e.mode),n.elementType=e.elementType,n.type=e.type,n.stateNode=e.stateNode,n.alternate=e,e.alternate=n):(n.pendingProps=t,n.type=e.type,n.flags=0,n.subtreeFlags=0,n.deletions=null),n.flags=e.flags&14680064,n.childLanes=e.childLanes,n.lanes=e.lanes,n.child=e.child,n.memoizedProps=e.memoizedProps,n.memoizedState=e.memoizedState,n.updateQueue=e.updateQueue,t=e.dependencies,n.dependencies=t===null?null:{lanes:t.lanes,firstContext:t.firstContext},n.sibling=e.sibling,n.index=e.index,n.ref=e.ref,n}function Ro(e,t,n,r,s,l){var a=2;if(r=e,typeof e=="function")mi(e)&&(a=1);else if(typeof e=="string")a=5;else e:switch(e){case Ue:return Cr(n.children,s,l,t);case Ge:a=8,s|=8;break;case Ze:return e=Gt(12,n,t,s|2),e.elementType=Ze,e.lanes=l,e;case ie:return e=Gt(13,n,t,s),e.elementType=ie,e.lanes=l,e;case Be:return e=Gt(19,n,t,s),e.elementType=Be,e.lanes=l,e;case me:return To(n,s,l,t);default:if(typeof e=="object"&&e!==null)switch(e.$$typeof){case ft:a=10;break e;case Ct:a=9;break e;case rt:a=11;break e;case Re:a=14;break e;case xe:a=16,r=null;break e}throw Error(p(130,e==null?e:typeof e,""))}return t=Gt(a,n,t,s),t.elementType=e,t.type=r,t.lanes=l,t}function Cr(e,t,n,r){return e=Gt(7,e,r,t),e.lanes=n,e}function To(e,t,n,r){return e=Gt(22,e,r,t),e.elementType=me,e.lanes=n,e.stateNode={isHidden:!1},e}function hi(e,t,n){return e=Gt(6,e,null,t),e.lanes=n,e}function gi(e,t,n){return t=Gt(4,e.children!==null?e.children:[],e.key,t),t.lanes=n,t.stateNode={containerInfo:e.containerInfo,pendingChildren:null,implementation:e.implementation},t}function sf(e,t,n,r,s){this.tag=t,this.containerInfo=e,this.finishedWork=this.pingCache=this.current=this.pendingChildren=null,this.timeoutHandle=-1,this.callbackNode=this.pendingContext=this.context=null,this.callbackPriority=0,this.eventTimes=Vo(0),this.expirationTimes=Vo(-1),this.entangledLanes=this.finishedLanes=this.mutableReadLanes=this.expiredLanes=this.pingedLanes=this.suspendedLanes=this.pendingLanes=0,this.entanglements=Vo(0),this.identifierPrefix=r,this.onRecoverableError=s,this.mutableSourceEagerHydrationData=null}function vi(e,t,n,r,s,l,a,c,d){return e=new sf(e,t,n,c,d),t===1?(t=1,l===!0&&(t|=8)):t=0,l=Gt(3,null,null,t),e.current=l,l.stateNode=e,l.memoizedState={element:r,isDehydrated:n,cache:null,transitions:null,pendingSuspenseBoundaries:null},Tl(l),e}function of(e,t,n){var r=3"u"||typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE!="function"))try{__REACT_DEVTOOLS_GLOBAL_HOOK__.checkDCE(i)}catch(h){console.error(h)}}return i(),ji.exports=yf(),ji.exports}var uc;function _f(){if(uc)return Do;uc=1;var i=xf();return Do.createRoot=i.createRoot,Do.hydrateRoot=i.hydrateRoot,Do}var Sf=_f();const wf=Cc(Sf),jf="/api";async function Ne(i,h){const p=await fetch(`${jf}${i}`,{...h,headers:{"Content-Type":"application/json",...h?.headers??{}}}),O=await p.json();if(!p.ok){const E=O.error?.message??"Ошибка запроса";throw new Error(E)}return O}const Ie={async listModels(i){return Ne("/llm/models",{method:"POST",body:JSON.stringify({llmProvider:i.llmProvider,apiKey:i.apiKey,model:i.model,baseUrl:i.baseUrl})})},async testConnection(i){return Ne("/llm/test-connection",{method:"POST",body:JSON.stringify({llmProvider:i.llmProvider,apiKey:i.apiKey,model:i.model,baseUrl:i.baseUrl})})},async normalize(i){return Ne("/normalize",{method:"POST",body:JSON.stringify({llmProvider:i.connection.llmProvider,apiKey:i.connection.apiKey,model:i.connection.model,baseUrl:i.connection.baseUrl,temperature:i.connection.temperature,maxOutputTokens:i.connection.maxOutputTokens,promptVersion:i.promptVersion,systemPrompt:i.prompts.systemPrompt,developerPrompt:i.prompts.developerPrompt,domainPrompt:i.prompts.domainPrompt,fewShotExamples:i.prompts.fewShotExamples,userQuestion:i.query.userQuestion,context:{period_hint:i.query.periodHint??"",business_context:i.query.businessContext??"",expected_route:i.query.expectedRoute??""},saveAsTestCase:!!i.saveAsTestCase,useMock:!!i.useMock})})},async loadHistory(){return Ne("/history")},async loadTrace(i){return Ne(`/history/${i}`)},async loadPresets(){return Ne("/presets")},async savePreset(i){return Ne("/presets/save",{method:"POST",body:JSON.stringify(i)})},async runEval(i){return Ne("/eval/run",{method:"POST",body:JSON.stringify({normalizeConfig:{llmProvider:i.connection.llmProvider,apiKey:i.connection.apiKey,model:i.connection.model,baseUrl:i.connection.baseUrl,temperature:i.connection.temperature,maxOutputTokens:i.connection.maxOutputTokens,promptVersion:i.promptVersion,systemPrompt:i.prompts.systemPrompt,developerPrompt:i.prompts.developerPrompt,domainPrompt:i.prompts.domainPrompt,fewShotExamples:i.prompts.fewShotExamples},caseIds:i.caseIds,useMock:!!i.useMock,mode:i.mode??"standard",caseSetFile:i.caseSetFile,rawQuestions:i.rawQuestions,eval_target:i.evalTarget,compare_with_report_file:i.compareWithReportFile})})},async startEvalRunAsync(i){return Ne("/eval/run-async/start",{method:"POST",body:JSON.stringify({normalizeConfig:{llmProvider:i.connection.llmProvider,apiKey:i.connection.apiKey,model:i.connection.model,baseUrl:i.connection.baseUrl,temperature:i.connection.temperature,maxOutputTokens:i.connection.maxOutputTokens,promptVersion:i.promptVersion,systemPrompt:i.prompts.systemPrompt,developerPrompt:i.prompts.developerPrompt,domainPrompt:i.prompts.domainPrompt,fewShotExamples:i.prompts.fewShotExamples},caseIds:i.caseIds,useMock:!!i.useMock,mode:i.mode??"standard",caseSetFile:i.caseSetFile,rawQuestions:i.rawQuestions,eval_target:i.evalTarget,compare_with_report_file:i.compareWithReportFile,questions:i.questions})})},async loadEvalRunAsyncStatus(i){return Ne(`/eval/run-async/${encodeURIComponent(i)}`)},async startRun(){return Ne("/accounting-agent/v1/runs/start",{method:"POST",body:JSON.stringify({initiator:"ndc_operator",source:"gui"})})},async finishRun(i){return Ne("/accounting-agent/v1/runs/finish",{method:"POST",body:JSON.stringify({runId:i,status:"DONE",source:"gui",reason:"Остановлено оператором из GUI"})})},async listRuns(){return Ne("/accounting-agent/v1/runs")},async listResults(){return Ne("/accounting-agent/v1/results")},async runTrace(i){return Ne(`/accounting-agent/v1/trace/run/${i}`)},async sendAssistantMessage(i){return Ne("/assistant/message",{method:"POST",body:JSON.stringify({session_id:i.sessionId??"",mode:"assistant",message:i.userMessage,user_message:i.userMessage,llmProvider:i.connection.llmProvider,apiKey:i.connection.apiKey,model:i.connection.model,baseUrl:i.connection.baseUrl,temperature:i.connection.temperature,maxOutputTokens:i.connection.maxOutputTokens,promptVersion:i.promptVersion??"address_query_runtime_v1",systemPrompt:i.prompts.systemPrompt,developerPrompt:i.prompts.developerPrompt,domainPrompt:i.prompts.domainPrompt,fewShotExamples:i.prompts.fewShotExamples,context:{period_hint:i.context?.periodHint??"",business_context:i.context?.businessContext??""},useMock:!!i.useMock})})},async loadAssistantSession(i){return Ne(`/assistant/session/${i}`)},async loadAutoRunsHistory(i){const h=new URLSearchParams;i?.from&&h.set("from",i.from),i?.to&&h.set("to",i.to),i?.target&&h.set("target",i.target),i?.mode&&h.set("mode",i.mode),i?.use_mock&&h.set("use_mock",i.use_mock),i?.prompt_contains&&h.set("prompt_contains",i.prompt_contains),typeof i?.limit=="number"&&h.set("limit",String(i.limit)),typeof i?.scan_limit=="number"&&h.set("scan_limit",String(i.scan_limit));const p=h.toString();return Ne(`/autoruns/history${p?`?${p}`:""}`)},async loadAutoRunDetail(i){return Ne(`/autoruns/history/${encodeURIComponent(i)}`)},async loadAutoRunCaseDialog(i,h){return Ne(`/autoruns/history/${encodeURIComponent(i)}/case/${encodeURIComponent(h)}/dialog`)},async loadAutoRunAnnotations(i){const h=new URLSearchParams;i?.run_id&&h.set("run_id",i.run_id),i?.case_id&&h.set("case_id",i.case_id),typeof i?.min_rating=="number"&&h.set("min_rating",String(i.min_rating)),i?.manual_case_decision&&h.set("manual_case_decision",i.manual_case_decision),typeof i?.limit=="number"&&h.set("limit",String(i.limit));const p=h.toString();return Ne(`/autoruns/annotations${p?`?${p}`:""}`)},async saveAutoRunAnnotation(i){return Ne("/autoruns/annotations",{method:"POST",body:JSON.stringify(i)})},async updateAutoRunAnnotation(i){return Ne(`/autoruns/annotations/${encodeURIComponent(i.annotation_id)}`,{method:"PATCH",body:JSON.stringify({resolved:i.resolved,resolved_by:i.resolved_by})})},async loadAutoRunPostAnalysis(i){const h=new URLSearchParams;i?.run_id&&h.set("run_id",i.run_id),typeof i?.limit_per_queue=="number"&&h.set("limit_per_queue",String(i.limit_per_queue)),typeof i?.annotation_limit=="number"&&h.set("annotation_limit",String(i.annotation_limit)),typeof i?.scan_limit=="number"&&h.set("scan_limit",String(i.scan_limit)),i?.from&&h.set("from",i.from),i?.to&&h.set("to",i.to),i?.target&&h.set("target",i.target),i?.mode&&h.set("mode",i.mode),i?.use_mock&&h.set("use_mock",i.use_mock),i?.prompt_contains&&h.set("prompt_contains",i.prompt_contains);const p=h.toString();return Ne(`/autoruns/post-analysis${p?`?${p}`:""}`)},async loadAutoRunAutogenHistory(i){const h=new URLSearchParams;i?.mode&&h.set("mode",i.mode),typeof i?.limit=="number"&&h.set("limit",String(i.limit));const p=h.toString();return Ne(`/autoruns/autogen/history${p?`?${p}`:""}`)},async loadAutoRunAutogenPersonalityCatalog(){return Ne("/autoruns/autogen/personality-catalog")},async generateAutoRunQuestions(i){return Ne("/autoruns/autogen/generate",{method:"POST",body:JSON.stringify(i)})}};function dt({value:i}){return o.jsx("pre",{className:"json-view",children:JSON.stringify(i??{},null,2)})}function Sn({title:i,subtitle:h,actions:p,className:O,hideHeader:E,children:D}){return o.jsxs("section",{className:O?`panel-frame ${O}`:"panel-frame",children:[E?null:o.jsxs("header",{className:"panel-header",children:[o.jsxs("div",{children:[o.jsx("h2",{children:i}),h?o.jsx("p",{children:h}):null]}),p?o.jsx("div",{className:"panel-actions",children:p}):null]}),o.jsx("div",{className:"panel-body",children:D})]})}function kf(i){const h=new Date(i);return Number.isNaN(h.getTime())?i:h.toLocaleString("ru-RU")}function Cf({sessionId:i,conversation:h,statusText:p,errorMessage:O,useMock:E,appLogs:D}){const K=h.filter(I=>I.role==="assistant").length,Z=h.filter(I=>I.role==="user").length,B=h.length>0?h[h.length-1]:null;return o.jsxs(Sn,{title:"SAM",subtitle:"System Assistant Monitor: срез по текущей сессии и логам.",children:[o.jsxs("div",{className:"metrics-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"session_id"}),o.jsx("strong",{children:i||"новая сессия"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"mock_mode"}),o.jsx("strong",{children:E?"on":"off"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"сообщений пользователя"}),o.jsx("strong",{children:Z})]}),o.jsxs("div",{children:[o.jsx("span",{children:"ответов ассистента"}),o.jsx("strong",{children:K})]}),o.jsxs("div",{children:[o.jsx("span",{children:"статус"}),o.jsx("strong",{children:p||"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"ошибка"}),o.jsx("strong",{children:O||"нет"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"последнее сообщение"}),o.jsx("strong",{children:B?.created_at?kf(B.created_at):"нет данных"})]})]}),o.jsx("h3",{style:{marginTop:12},children:"Последние системные логи"}),o.jsx(dt,{value:D.slice(0,120)})]})}const Ni={fromLocal:"",toLocal:"",target:"all",mode:"all",useMock:"any",promptContains:"",limit:120},$o="needs_dialog_policy_fix",ct="__all__",Bo="__live__:",cc="ndc_autoruns_ui_config_v1",dc="ndc-autoruns-save",Ti=[{id:"general",label:"Общий контур",domain:"",defaultPrompt:"Генерируй реалистичные живые вопросы бухгалтера по 1С. Добавляй разговорные формулировки и опечатки, но сохраняй бизнес-смысл."}];function Nf(i=Ti){return i.reduce((h,p)=>(h[p.id]=p.defaultPrompt,h),{})}const fc={mode:"codex_creative",count:24,personalityId:"general",personalityPrompts:Nf(),persistToEvalCases:!0,generatedBy:"manual_reviewer"};function Ef(i){const h=i.getFullYear(),p=String(i.getMonth()+1).padStart(2,"0"),O=String(i.getDate()).padStart(2,"0"),E=String(i.getHours()).padStart(2,"0"),D=String(i.getMinutes()).padStart(2,"0");return`${h}-${p}-${O}T${E}:${D}`}function pc(){const i=new Date;return i.setDate(i.getDate()-14),Ef(i)}function Fo(i){if(!i.trim())return;const h=Date.parse(i);if(Number.isFinite(h))return new Date(h).toISOString()}function ur(i){if(!i)return"нет данных";const h=Date.parse(i);return Number.isFinite(h)?new Date(h).toLocaleString("ru-RU"):i}function Pf(i,h){return h<=0?0:Math.max(0,Math.min(100,Number((i/h*100).toFixed(1))))}function Ms(i){return typeof i!="number"?"нет данных":`${i.toFixed(1)}%`}function Rf(i){return i==="assistant_stage1"?"assistant/s1":i==="assistant_stage2"?"assistant/s2":i==="assistant_p0"?"assistant/p0":i}function mc(i){return i==="up"?"Рост":i==="down"?"Регресс":"Без изменений"}function Tf(i,h){return i.find(p=>p.case_id===h)??null}function hc(i){const h=Math.max(1,Math.min(5,Math.round(i)));return`${"●".repeat(h)}${"○".repeat(5-h)}`}function gc(i){return i.length===0?o.jsx("p",{className:"muted",children:"Покрытие доменов пока не сформировано."}):o.jsx("div",{className:"autoruns-coverage-list",children:i.map(h=>{const p=Pf(h.closed_cases,h.total_cases);return o.jsxs("div",{className:"autoruns-coverage-item",children:[o.jsxs("div",{className:"autoruns-coverage-head",children:[o.jsx("strong",{children:h.domain}),o.jsxs("span",{children:[h.closed_cases,"/",h.total_cases," (",p,"%)"]})]}),o.jsx("div",{className:"autoruns-coverage-bar",children:o.jsx("div",{style:{width:`${p}%`}})})]},h.domain)})})}function Uo(i){return`${Bo}${i}`}function br(i){return i.startsWith(Bo)}function vc(i){return i.startsWith(Bo)?i.slice(Bo.length):""}function Nc(i){const h=i.report_summary?.run_timestamp??i.created_at,p=Math.max(0,i.total_cases-i.completed_cases);return{run_id:Uo(i.job_id),eval_target:i.eval_target,run_timestamp:h,mode:"single-pass-strict",llm_provider:null,model:null,use_mock:null,prompt_version:null,schema_version:null,suite_id:i.case_set_file,cases_total:i.total_cases,requests_total:null,report_path:`async_job:${i.job_id}`,score_index:i.report_summary?.score_index??null,blocking_failures:0,quality_failures:0,closed_cases:i.completed_cases,open_cases:p,domain_coverage:[{domain:"runtime",total_cases:i.total_cases,closed_cases:i.completed_cases}]}}function Is(i,h){const p=Nc(i),O=i.cases.map(q=>({case_id:q.case_id,domain:null,query_class:null,status:q.status==="completed"?"closed":q.status==="failed"?"open":"unknown",score_index:null,trace_id:null,reply_type:null,session_id:`${i.run_id}-${q.case_id}`,dialog_available:q.messages.length>0,commented_count:0,latest_annotation_at:null,avg_rating:null,checks:null,metric_subscores:null})),D=h!==ct&&O.some(q=>q.case_id===h)?h:O.length>0?ct:"",K={ok:!0,run:p,coverage:{closed_cases:i.completed_cases,open_cases:Math.max(0,i.total_cases-i.completed_cases),domain_coverage:[{domain:"runtime",total_cases:i.total_cases,closed_cases:i.completed_cases}]},cases:O,annotations_summary:{total:0},report:i.report_summary?{run_id:i.report_summary.run_id,run_timestamp:i.report_summary.run_timestamp,score_index:i.report_summary.score_index,cases_total:i.report_summary.cases_total}:{}},Z=[];let B=0;if(D===ct)for(const q of i.cases)for(let T=0;TT.case_id===D)??null;for(let T=0;T<(q?.messages.length??0);T+=1){const H=q?.messages[T];H&&Z.push({...H,message_index:T,case_id:D,case_message_index:T,commented:!1,annotation:null})}}const I={ok:!0,run_id:p.run_id,case_id:D,source:"assistant_session",session_id:D===ct?`${i.run_id}::__all__`:`${i.run_id}-${D}`,messages:Z,decomposition:[],assistant_mode:{status:i.status,completed_cases:i.completed_cases,total_cases:i.total_cases},annotations:[]};return{detail:K,dialog:I,caseId:D}}function Mf({commented:i}){const h=i?"comment-icon-svg commented":"comment-icon-svg";return o.jsxs("svg",{className:h,viewBox:"0 0 24 24","aria-hidden":"true",focusable:"false",children:[o.jsx("path",{d:"M5 6.5h14v9H11.5l-4.5 3v-3H5z"}),o.jsx("circle",{className:"comment-icon-dot",cx:"9",cy:"11",r:"1.05"}),o.jsx("circle",{className:"comment-icon-dot",cx:"12",cy:"11",r:"1.05"}),o.jsx("circle",{className:"comment-icon-dot",cx:"15",cy:"11",r:"1.05"})]})}function yc({resolved:i}){return o.jsxs("svg",{className:i?"resolve-icon-svg resolved":"resolve-icon-svg",viewBox:"0 0 16 16","aria-hidden":"true",focusable:"false",children:[o.jsx("circle",{cx:"8",cy:"8",r:"6.2"}),i?o.jsx("path",{d:"M5.1 8.2 7.2 10.3 11 6.5"}):null]})}function If({connection:i,prompts:h,assistantPromptVersion:p,decompositionPromptVersion:O,showAssistantMode:E,showDecompositionMode:D,showProgressMode:K,showCommentsMode:Z,onLog:B}){const[I,q]=S.useState({...Ni,fromLocal:pc()}),[T,H]=S.useState(null),[fe,ye]=S.useState(null),[ne,X]=S.useState(null),[G,se]=S.useState([]),[Se,Le]=S.useState("all"),[Oe,$e]=S.useState(!1),[Ue,Ge]=S.useState(null),[Ze,ft]=S.useState([]),[Ct,rt]=S.useState(""),[ie,Be]=S.useState(""),[Re,xe]=S.useState(""),[me,A]=S.useState(Ti),[R,L]=S.useState(fc),[g,j]=S.useState([]),[ee,oe]=S.useState(""),[le,ae]=S.useState([]),[re,he]=S.useState(null),[ce,ke]=S.useState(null),[zt,wn]=S.useState(!1),[cn,Nt]=S.useState(!1),[Jt,jn]=S.useState(!1),[bt,kn]=S.useState(!1),[Cn,Et]=S.useState(!1),[Dt,Yt]=S.useState(!1),[Fn,dn]=S.useState(!1),[Xt,fn]=S.useState(!1),[Nn,$t]=S.useState(""),[Un,Ee]=S.useState(""),[pn,Pt]=S.useState(String(Ni.limit)),[cr,gt]=S.useState(String(fc.count)),[de,We]=S.useState({open:!1,caseId:"",caseMessageIndex:-1,messageIndex:-1,rating:3,comment:"",manualCaseDecision:$o,annotationAuthor:"manual_reviewer",saving:!1,error:""}),mn=S.useRef(!1),Rt=S.useRef(null),Zt=S.useMemo(()=>me.find(u=>u.id===R.personalityId)??me[0]??Ti[0],[R.personalityId,me]),st=S.useMemo(()=>g.find(u=>u.generation_id===ee)??g[0]??null,[g,ee]),Ft=T?.items.find(u=>u.run_id===ie)??fe?.run??null,Je=fe?Tf(fe.cases,Re):null,Ve=S.useMemo(()=>Oe?G.filter(u=>!u.resolved):G,[G,Oe]),je=Ve.find(u=>u.annotation_id===Ct)??null,En=ne?.messages.find(u=>u.message_index===de.messageIndex)??null,dr=S.useMemo(()=>{if(!ne||de.messageIndex<0)return null;for(let u=de.messageIndex-1;u>=0;u-=1){const f=ne.messages[u];if(f?.role==="user")return f}return null},[de.messageIndex,ne]),Pn=S.useMemo(()=>{if(Ve.length===0)return null;const u=Ve.reduce((f,w)=>f+w.rating,0)/Ve.length;return Number(u.toFixed(2))},[Ve]),hn=S.useMemo(()=>{const u=[...T?.items??[]];return re&&u.unshift(Nc(re)),ie&&!u.some(f=>f.run_id===ie)&&fe?.run&&u.unshift(fe.run),u},[re,T?.items,fe?.run,ie]),ge=S.useCallback(u=>{B?.(`[autoruns] ${u}`)},[B]),en=S.useCallback(u=>{const f=u.trim();if(!f){Pt(String(I.limit));return}if(!/^\d+$/.test(f)){Pt(String(I.limit));return}const w=Number.parseInt(f,10);if(!Number.isFinite(w)){Pt(String(I.limit));return}const F=Math.max(1,Math.min(500,w));F!==I.limit&&q(ve=>({...ve,limit:F})),Pt(String(F))},[I.limit]),Ut=S.useCallback(u=>{const f=u.trim();if(!f){gt(String(R.count));return}if(!/^\d+$/.test(f)){gt(String(R.count));return}const w=Number.parseInt(f,10);if(!Number.isFinite(w)){gt(String(R.count));return}const F=Math.max(1,Math.min(200,w));F!==R.count&&L(ve=>({...ve,count:F})),gt(String(F))},[R.count]),vt=S.useCallback(async()=>{fn(!0);try{const u=await Ie.loadAutoRunAnnotations({limit:800,manual_case_decision:Se});se(u.items),Ge(u.manual_case_decision_schema??null),ft(u.available_manual_case_decisions??[]),rt(f=>u.items.length===0?"":u.items.some(w=>w.annotation_id===f)?f:u.items[0].annotation_id)}catch(u){ge(`Annotations load error: ${u instanceof Error?u.message:String(u)}`)}finally{fn(!1)}},[Se,ge]),Tt=S.useCallback(async()=>{kn(!0);try{const u=await Ie.loadAutoRunAutogenHistory({limit:180});j(u.items)}catch(u){ge(`Autogen history load error: ${u instanceof Error?u.message:String(u)}`)}finally{kn(!1)}},[ge]),Mt=S.useCallback(async()=>{try{const f=(await Ie.loadAutoRunAutogenPersonalityCatalog()).items.map(w=>({id:String(w.id??"").trim(),label:String(w.label??"").trim(),domain:typeof w.domain=="string"?w.domain.trim():"",defaultPrompt:String(w.default_prompt??"").trim()})).filter(w=>w.id.length>0&&w.label.length>0);if(f.length===0)return;A(f.map(w=>({id:w.id,label:w.label,domain:w.domain||"",defaultPrompt:w.defaultPrompt||"Генерируй реалистичные вопросы бухгалтера по выбранному профилю. Не выдумывай непокрытые возможности."})))}catch(u){ge(`Autogen personality catalog load error: ${u instanceof Error?u.message:String(u)}`)}},[ge]),et=S.useCallback(async()=>{jn(!0);try{const u=await Ie.loadAutoRunPostAnalysis({run_id:ie&&!br(ie)?ie:void 0,limit_per_queue:30,annotation_limit:1500,from:Fo(I.fromLocal),to:Fo(I.toLocal),target:I.target,mode:I.mode,use_mock:I.useMock,prompt_contains:I.promptContains.trim()||void 0});ke(u)}catch(u){ge(`Post-analysis load error: ${u instanceof Error?u.message:String(u)}`),ke(null)}finally{jn(!1)}},[I.fromLocal,I.mode,I.promptContains,I.target,I.toLocal,I.useMock,ge,ie]),Y=S.useCallback(async()=>{wn(!0),Ee("");try{const u=R.personalityPrompts[R.personalityId]??"",f=[h.systemPrompt,h.developerPrompt,h.domainPrompt,h.schemaNotes,h.fewShotExamples].join(` -`).slice(0,900),w=await Ie.generateAutoRunQuestions({mode:R.mode,count:R.count,domain:Zt.domain||void 0,persist_to_eval_cases:R.persistToEvalCases,generated_by:R.generatedBy.trim()||void 0,llm:{llm_provider:i.llmProvider,api_key:i.apiKey,model:i.model,base_url:i.baseUrl,temperature:i.temperature,max_output_tokens:i.maxOutputTokens},context:{llm_provider:i.llmProvider,model:i.model,assistant_prompt_version:p,decomposition_prompt_version:O,prompt_fingerprint:f,autogen_personality_id:Zt.id,autogen_personality_prompt:u.trim()||void 0}});ge(`Generated ${w.generation.count} questions (${w.generation.mode}) id=${w.generation.generation_id}`+(w.generation.saved_case_set_file?` saved=${w.generation.saved_case_set_file}`:"")),oe(w.generation.generation_id),ae([...w.generation.questions??[]]),await Tt()}catch(u){const f=u instanceof Error?u.message:String(u);Ee(`Автогенерация: ${f}`),ge(`Autogen generate error: ${f}`)}finally{wn(!1)}},[p,R.count,R.generatedBy,R.mode,R.personalityId,R.personalityPrompts,R.persistToEvalCases,i.apiKey,i.baseUrl,i.llmProvider,i.maxOutputTokens,i.model,i.temperature,O,Tt,ge,h.developerPrompt,h.domainPrompt,h.fewShotExamples,h.schemaNotes,h.systemPrompt,Zt.domain,Zt.id]),tn=S.useCallback(async(u,f)=>{if(br(u)){const w=vc(u);if(re&&re.job_id===w){const F=Is(re,f);Be(u),xe(F.caseId),X(F.dialog);return}X(null);return}dn(!0);try{const w=await Ie.loadAutoRunCaseDialog(u,f);X(w)}catch(w){const F=w instanceof Error?w.message:String(w);Ee(`Диалог кейса: ${F}`),X(null),ge(`Dialog load error for ${u}/${f}: ${F}`)}finally{dn(!1)}},[re,ge]),pt=S.useCallback(async(u,f)=>{if(br(u)){const w=vc(u);if(re&&re.job_id===w){const F=Is(re,f??ct);Be(u),xe(F.caseId),ye(F.detail),X(F.dialog);return}Be(u),xe(""),ye(null),X(null);return}Yt(!0);try{const w=await Ie.loadAutoRunDetail(u);ye(w);const F=(f&&(f===ct||w.cases.some(ve=>ve.case_id===f))?f:"")||(w.cases.length>0?ct:"")||"";Be(u),xe(F),F?await tn(u,F):X(null)}catch(w){const F=w instanceof Error?w.message:String(w);Ee(`Детализация прогона: ${F}`),ye(null),X(null),ge(`Run detail load error for ${u}: ${F}`)}finally{Yt(!1)}},[re,tn,ge]),gn=S.useCallback(async u=>{Et(!0),Ee("");try{const f=await Ie.loadAutoRunsHistory({from:Fo(I.fromLocal),to:Fo(I.toLocal),target:I.target,mode:I.mode,use_mock:I.useMock,prompt_contains:I.promptContains.trim()||void 0,limit:I.limit});if(H(f),f.items.length===0){Be(""),xe(""),ye(null),X(null);return}const w=u?.keepSelection??!0,F=u?.preferredRunId??"",ve=u?.preferredCaseId??"",Bt=w&&F&&f.items.some(Ht=>Ht.run_id===F)?F:f.items[0].run_id;await pt(Bt,w?ve:void 0),et()}catch(f){const w=f instanceof Error?f.message:String(f);Ee(`История прогонов: ${w}`),ge(`History load error: ${w}`)}finally{Et(!1)}},[I.fromLocal,I.limit,I.mode,I.promptContains,I.target,I.toLocal,I.useMock,et,pt,ge]),ot=S.useCallback(()=>{Rt.current!==null&&(window.clearTimeout(Rt.current),Rt.current=null)},[]),yt=S.useCallback(async u=>{try{const f=await Ie.loadEvalRunAsyncStatus(u);he(f.job);const w=Uo(u);if(ie===w){const F=Is(f.job,Re||ct);ye(F.detail),X(F.dialog),xe(F.caseId)}if(f.job.status==="completed"){ot(),Nt(!1);const F=f.job.report_summary?.run_id??f.job.run_id;await gn({keepSelection:!0,preferredRunId:F||ie,preferredCaseId:ct}),await Tt(),he(null);return}if(f.job.status==="failed"){ot(),Nt(!1),Ee(`Запуск прогонов: ${f.job.error??"неизвестная ошибка"}`),ge(`Autogen async run failed: ${f.job.error??"unknown error"}`);return}ot(),Rt.current=window.setTimeout(()=>{yt(u)},500)}catch(f){ot(),Nt(!1);const w=f instanceof Error?f.message:String(f);Ee(`Запуск прогонов: ${w}`),ge(`Autogen async status error: ${w}`)}},[Tt,gn,ge,Re,ie,ot]),Nr=S.useCallback(async()=>{ot(),Nt(!0),Ee("");try{const u=st;if(!u)throw new Error("История автогенерации пуста. Сначала сгенерируйте пачку вопросов.");const f=le.map(nn=>nn.trim()).filter(nn=>nn.length>0);if(f.length===0)throw new Error("Нет вопросов для запуска: список пустой после ручного редактирования.");const w=I.useMock==="true",ve=(await Ie.startEvalRunAsync({connection:i,prompts:h,promptVersion:p,mode:"single-pass-strict",caseSetFile:u.saved_case_set_file??void 0,useMock:w,evalTarget:"assistant_stage1",questions:f})).job;he(ve);const Bt=Uo(ve.job_id),Ht=Is(ve,ct);Be(Bt),xe(Ht.caseId),ye(Ht.detail),X(Ht.dialog),ge(`Запущен async-прогон job=${ve.job_id}, run_id=${ve.run_id}, вопросов=${f.length}`+(u.saved_case_set_file?`, base_case_set=${u.saved_case_set_file}`:"")),yt(ve.job_id)}catch(u){const f=u instanceof Error?u.message:String(u);Ee(`Запуск прогонов: ${f}`),ge(`Autogen run error: ${f}`),Nt(!1)}},[p,i,le,I.useMock,ge,yt,h,st,ot]),fr=S.useCallback(u=>{if(u.role!=="assistant")return;const f=u.case_id??Re,w=u.case_message_index??u.message_index;We({open:!0,caseId:f,caseMessageIndex:w,messageIndex:u.message_index,rating:u.annotation?.rating??3,comment:u.annotation?.comment??"",manualCaseDecision:u.annotation?.manual_case_decision??$o,annotationAuthor:u.annotation?.annotation_author??R.generatedBy,saving:!1,error:""})},[R.generatedBy,Re]),Rn=S.useCallback(u=>{We(f=>f.saving&&!u?.force?f:{open:!1,caseId:"",caseMessageIndex:-1,messageIndex:-1,rating:3,comment:"",manualCaseDecision:$o,annotationAuthor:R.generatedBy,saving:!1,error:""})},[R.generatedBy]),Er=S.useCallback(async()=>{const u=ie,f=de.caseId,w=de.caseMessageIndex;if(!(!u||!f||w<0)){if(br(u)){We(F=>({...F,error:"Комментарий можно сохранить после завершения прогона."}));return}if(!de.comment.trim()){We(F=>({...F,error:"Добавьте комментарий."}));return}We(F=>({...F,saving:!0,error:""}));try{await Ie.saveAutoRunAnnotation({run_id:u,case_id:f,message_index:w,rating:de.rating,comment:de.comment.trim(),manual_case_decision:de.manualCaseDecision,annotation_author:de.annotationAuthor.trim()||void 0}),Rn({force:!0}),Promise.all([pt(u,Re),vt(),et()]).catch(F=>{const ve=F instanceof Error?F.message:String(F);Ee(`Обновление после комментария: ${ve}`),ge(`Comment refresh error: ${ve}`)})}catch(F){We(ve=>({...ve,saving:!1,error:F instanceof Error?F.message:String(F)}))}}},[Rn,de.annotationAuthor,de.caseId,de.caseMessageIndex,de.comment,de.manualCaseDecision,de.rating,vt,et,pt,ge,Re,ie]),Bn=S.useCallback(u=>{se(f=>f.map(w=>w.annotation_id===u.annotation_id?{...w,...u}:w)),X(f=>f&&{...f,annotations:f.annotations.map(w=>w.annotation_id===u.annotation_id?u:w),messages:f.messages.map(w=>!w.annotation||w.annotation.annotation_id!==u.annotation_id?w:{...w,commented:!0,annotation:u})})},[]),Hn=S.useCallback(async(u,f)=>{if(u.annotation_id){if(br(u.run_id)){Ee("Статус выполнения можно менять только для завершённых прогонов.");return}$t(u.annotation_id);try{const w=await Ie.updateAutoRunAnnotation({annotation_id:u.annotation_id,resolved:f,resolved_by:R.generatedBy||void 0});Bn(w.annotation),et()}catch(w){const F=w instanceof Error?w.message:String(w);Ee(`Смена статуса кейса: ${F}`),ge(`Annotation resolve toggle error: ${F}`)}finally{$t("")}}},[Bn,R.generatedBy,et,ge]),Vn=S.useCallback(async u=>{rt(u.annotation_id),await pt(u.run_id,u.case_id),T?.items.some(f=>f.run_id===u.run_id)||Ee("Комментарий относится к прогону вне текущего фильтра. Детали загружены напрямую.")},[T?.items,pt]);S.useEffect(()=>{mn.current||(mn.current=!0,gn({keepSelection:!1}),Tt(),Mt(),et())},[Tt,Mt,gn,et]),S.useEffect(()=>{mn.current&&vt()},[Se,vt]),S.useEffect(()=>{rt(u=>Ve.length===0?"":Ve.some(f=>f.annotation_id===u)?u:Ve[0].annotation_id)},[Ve]),S.useEffect(()=>{oe(u=>g.length===0?"":u&&g.some(f=>f.generation_id===u)?u:g[0].generation_id)},[g]),S.useEffect(()=>{if(!st){ae([]);return}ae([...st.questions])},[st?.generation_id]),S.useEffect(()=>{Pt(String(I.limit))},[I.limit]),S.useEffect(()=>{gt(String(R.count))},[R.count]),S.useEffect(()=>{if(!re)return;const u=Uo(re.job_id);if(ie!==u)return;const f=Is(re,Re||ct);ye(f.detail),X(f.dialog),xe(f.caseId)},[re,Re,ie]),S.useEffect(()=>()=>{ot()},[ot]),S.useEffect(()=>{me.length!==0&&L(u=>{let f=!1;const w={...u.personalityPrompts};for(const ve of me)(typeof w[ve.id]!="string"||w[ve.id].trim().length===0)&&(w[ve.id]=ve.defaultPrompt,f=!0);let F=u.personalityId;return me.some(ve=>ve.id===u.personalityId)||(F=me[0].id,f=!0),f?{...u,personalityId:F,personalityPrompts:w}:u})},[me]),S.useEffect(()=>{const u=localStorage.getItem(cc);if(u)try{const f=JSON.parse(u);if(f.filters){const w=f.filters;q(F=>({...F,...w,limit:typeof w.limit=="number"?Math.max(1,Math.min(500,w.limit)):F.limit}))}f.autoGenSettings&&L(w=>{const F={...w.personalityPrompts},ve=f.autoGenSettings?.personalityPrompts??{};for(const[Ht,nn]of Object.entries(ve))typeof nn=="string"&&Ht.trim().length>0&&(F[Ht.trim()]=nn);const Bt=typeof f.autoGenSettings?.personalityId=="string"&&f.autoGenSettings.personalityId.trim().length>0?f.autoGenSettings.personalityId.trim():w.personalityId;return{...w,mode:f.autoGenSettings?.mode==="codex_creative"||f.autoGenSettings?.mode==="qwen_seed"?f.autoGenSettings.mode:w.mode,count:typeof f.autoGenSettings?.count=="number"?Math.max(1,Math.min(200,f.autoGenSettings.count)):w.count,personalityId:Bt,personalityPrompts:F,persistToEvalCases:typeof f.autoGenSettings?.persistToEvalCases=="boolean"?f.autoGenSettings.persistToEvalCases:w.persistToEvalCases,generatedBy:typeof f.autoGenSettings?.generatedBy=="string"?f.autoGenSettings.generatedBy:w.generatedBy}}),(f.annotationDecisionFilter==="all"||typeof f.annotationDecisionFilter=="string"&&f.annotationDecisionFilter.length>0)&&Le(f.annotationDecisionFilter),typeof f.hideResolvedAnnotations=="boolean"&&$e(f.hideResolvedAnnotations)}catch{}},[]);const pr=S.useCallback(()=>{const u={filters:I,autoGenSettings:{mode:R.mode,count:R.count,personalityId:R.personalityId,personalityPrompts:R.personalityPrompts,persistToEvalCases:R.persistToEvalCases,generatedBy:R.generatedBy},annotationDecisionFilter:Se,hideResolvedAnnotations:Oe};localStorage.setItem(cc,JSON.stringify(u))},[Se,R,I,Oe]);return S.useEffect(()=>{const u=()=>{pr(),ge("Сохранены настройки панели автопрогонов.")};return window.addEventListener(dc,u),()=>{window.removeEventListener(dc,u)}},[ge,pr]),o.jsxs(Sn,{className:"autoruns-frame",title:"",hideHeader:!0,children:[o.jsxs("div",{className:"autoruns-columns",children:[o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Настройки"})}),o.jsx("h4",{children:"Настройки выборки"}),o.jsxs("div",{className:"autoruns-form-grid",children:[o.jsxs("label",{children:["Дата с",o.jsx("input",{type:"datetime-local",value:I.fromLocal,onChange:u=>q(f=>({...f,fromLocal:u.target.value}))})]}),o.jsxs("label",{children:["Дата по",o.jsx("input",{type:"datetime-local",value:I.toLocal,onChange:u=>q(f=>({...f,toLocal:u.target.value}))})]}),o.jsxs("label",{children:["Целевой контур",o.jsxs("select",{value:I.target,onChange:u=>q(f=>({...f,target:u.target.value})),children:[o.jsx("option",{value:"all",children:"все"}),(T?.available.targets??[]).map(u=>o.jsx("option",{value:u,children:u},u))]})]}),o.jsxs("label",{children:["Режим",o.jsxs("select",{value:I.mode,onChange:u=>q(f=>({...f,mode:u.target.value})),children:[o.jsx("option",{value:"all",children:"все"}),(T?.available.modes??[]).map(u=>o.jsx("option",{value:u,children:u},u))]})]}),o.jsxs("label",{children:["Использовать mock",o.jsxs("select",{value:I.useMock,onChange:u=>q(f=>({...f,useMock:u.target.value})),children:[o.jsx("option",{value:"any",children:"любой"}),o.jsx("option",{value:"true",children:"да"}),o.jsx("option",{value:"false",children:"нет"})]})]}),o.jsxs("label",{children:["Лимит",o.jsx("input",{type:"number",min:1,max:500,value:pn,onChange:u=>{const f=u.target.value;(f===""||/^\d+$/.test(f))&&Pt(f)},onBlur:u=>en(u.target.value),onKeyDown:u=>{u.key==="Enter"&&en(u.target.value)}})]}),o.jsxs("label",{className:"full-width",children:["Версия промпта содержит",o.jsx("input",{value:I.promptContains,onChange:u=>q(f=>({...f,promptContains:u.target.value})),placeholder:"normalizer_v2_0_2 / address_query_runtime_v1",list:"autoruns-prompt-versions"})]})]}),o.jsx("datalist",{id:"autoruns-prompt-versions",children:(T?.available.prompt_versions??[]).map(u=>o.jsx("option",{value:u},u))}),o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",disabled:Cn,onClick:()=>{gn({keepSelection:!1})},children:Cn?"Обновляю...":"Применить"}),o.jsx("button",{type:"button",className:"tab",onClick:()=>{q({...Ni,fromLocal:pc()}),Ee("")},children:"Сбросить фильтры"})]}),o.jsx("h4",{children:"Контур генерации"}),o.jsxs("div",{className:"autoruns-meta-list",children:[o.jsxs("div",{children:[o.jsx("span",{children:"Провайдер:"}),o.jsx("strong",{children:i.llmProvider})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Модель:"}),o.jsx("strong",{children:i.model||"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Промпт ассистента:"}),o.jsx("strong",{children:p})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Промпт декомпозиции:"}),o.jsx("strong",{children:O})]})]}),o.jsx("h4",{children:"Автогенерация вопросов"}),o.jsx("p",{className:"muted",children:"`qwen_seed` использует текущую LLM-модель из активного контура подключения (та же модель, что и для ответов ассистента)."}),o.jsxs("div",{className:"autoruns-form-grid",children:[o.jsxs("label",{children:["Режим генерации",o.jsxs("select",{value:R.mode,onChange:u=>L(f=>({...f,mode:u.target.value})),children:[o.jsx("option",{value:"codex_creative",children:"codex_creative"}),o.jsx("option",{value:"qwen_seed",children:"qwen_seed"})]})]}),o.jsxs("label",{children:["Кол-во",o.jsx("input",{type:"number",min:1,max:200,value:cr,onChange:u=>{const f=u.target.value;(f===""||/^\d+$/.test(f))&>(f)},onBlur:u=>Ut(u.target.value),onKeyDown:u=>{u.key==="Enter"&&Ut(u.target.value)}})]}),o.jsxs("label",{children:["Личность автогенерации",o.jsx("select",{value:R.personalityId,onChange:u=>L(f=>({...f,personalityId:u.target.value})),children:me.map(u=>o.jsx("option",{value:u.id,children:u.label},u.id))})]}),o.jsxs("label",{children:["Кто генерирует",o.jsx("input",{value:R.generatedBy,onChange:u=>L(f=>({...f,generatedBy:u.target.value})),placeholder:"manual_reviewer"})]}),o.jsxs("label",{className:"full-width",children:["Промпт личности",o.jsx("textarea",{value:R.personalityPrompts[R.personalityId]??"",onChange:u=>L(f=>({...f,personalityPrompts:{...f.personalityPrompts,[f.personalityId]:u.target.value}})),placeholder:"Текст промпта для выбранной личности автогенерации"})]}),o.jsxs("label",{className:"checkbox-row",children:[o.jsx("input",{type:"checkbox",checked:R.persistToEvalCases,onChange:u=>L(f=>({...f,persistToEvalCases:u.target.checked}))}),"Сохранять кейс-сет в `eval_cases`"]})]}),o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",disabled:zt,onClick:()=>{Y()},children:zt?"Генерирую...":"Сгенерировать пачку"}),o.jsx("button",{type:"button",className:"tab",disabled:bt,onClick:()=>{Tt()},children:bt?"Обновляю...":"Обновить историю"}),o.jsx("button",{type:"button",className:"tab",disabled:cn||le.length===0,onClick:()=>{Nr()},children:cn?"Запускаю...":"Запустить прогоны"})]}),o.jsx("div",{className:"autoruns-form-grid",children:o.jsxs("label",{className:"full-width",children:["Кейс-сет для запуска",o.jsxs("select",{value:ee,onChange:u=>oe(u.target.value),disabled:g.length===0,children:[g.length===0?o.jsx("option",{value:"",children:"нет генераций"}):null,g.map(u=>o.jsxs("option",{value:u.generation_id,children:[ur(u.created_at)," | ",u.mode," | ",u.count," | ",u.saved_case_set_file??"без файла"]},u.generation_id))]})]})}),o.jsxs("div",{className:"autoruns-generated-questions",children:[o.jsxs("div",{className:"autoruns-generated-questions-head",children:[o.jsxs("strong",{children:["Вопросы к запуску: ",le.length]}),o.jsx("button",{type:"button",className:"tab",onClick:()=>ae([...st?.questions??[]]),disabled:!st,children:"Восстановить"})]}),le.length===0?o.jsx("p",{className:"muted",children:"Список вопросов пуст. Сгенерируйте пачку или восстановите из выбранной генерации."}):o.jsx("div",{className:"autoruns-generated-questions-list",children:le.map((u,f)=>o.jsxs("div",{className:"autoruns-generated-question-item",children:[o.jsxs("span",{children:[f+1,". ",u]}),o.jsx("button",{type:"button",className:"autoruns-remove-question-btn",onClick:()=>ae(w=>w.filter((F,ve)=>ve!==f)),title:"Удалить вопрос из запуска","aria-label":"Удалить вопрос из запуска",children:"X"})]},`${f}-${u.slice(0,24)}`))})]}),o.jsx("p",{className:"muted",children:"Запуск выполняет `assistant_stage1` eval по выбранному кейс-сету."}),o.jsxs("div",{className:"autoruns-autogen-list",children:[bt?o.jsx("p",{className:"muted",children:"Загружаю историю автогенераций..."}):null,!bt&&g.length===0?o.jsx("p",{className:"muted",children:"История автогенераций пока пустая."}):null,g.slice(0,30).map(u=>o.jsxs("article",{className:ee===u.generation_id?"autoruns-autogen-item selected":"autoruns-autogen-item",onClick:()=>oe(u.generation_id),children:[o.jsxs("header",{children:[o.jsx("strong",{children:ur(u.created_at)}),o.jsx("span",{children:u.mode})]}),o.jsxs("div",{className:"autoruns-run-meta",children:["id=",u.generation_id," | count=",u.count]}),o.jsxs("div",{className:"autoruns-run-meta",children:["домен=",u.domain??"общий",u.generated_by?` | автор=${u.generated_by}`:""]}),u.saved_case_set_file?o.jsxs("div",{className:"autoruns-run-meta",children:["кейс-сет=",u.saved_case_set_file]}):null,(u.questions??[]).length>0?o.jsx("p",{children:u.questions[0]}):null]},u.generation_id))]}),o.jsxs("details",{className:"autoruns-prompt-details",children:[o.jsx("summary",{children:"Копия активного промпта (только чтение)"}),o.jsxs("label",{children:["Системный",o.jsx("textarea",{readOnly:!0,value:h.systemPrompt})]}),o.jsxs("label",{children:["Разработчика",o.jsx("textarea",{readOnly:!0,value:h.developerPrompt})]}),o.jsxs("label",{children:["Доменный",o.jsx("textarea",{readOnly:!0,value:h.domainPrompt})]}),o.jsxs("label",{children:["Заметки по схеме",o.jsx("textarea",{readOnly:!0,value:h.schemaNotes})]}),o.jsxs("label",{children:["Примеры few-shot",o.jsx("textarea",{readOnly:!0,value:h.fewShotExamples})]})]}),Un?o.jsx("p",{className:"error-text",children:Un}):null]}),o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Выдача прогонов"})}),o.jsxs("div",{className:"autoruns-stats-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"Всего"}),o.jsx("strong",{children:(T?.stats.runs_total??0)+(re?1:0)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Средний score"}),o.jsx("strong",{children:Ms(T?.stats.avg_score_index??null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Тренд"}),o.jsx("strong",{children:T?mc(T.stats.trend):"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Блокеры"}),o.jsx("strong",{children:T?.stats.blocking_runs??0})]})]}),o.jsxs("div",{className:"autoruns-run-list",children:[hn.map(u=>o.jsxs("button",{type:"button",className:ie===u.run_id?"autoruns-run-item selected":"autoruns-run-item",onClick:()=>{pt(u.run_id)},children:[o.jsxs("div",{className:"autoruns-run-head",children:[o.jsx("strong",{children:ur(u.run_timestamp)}),o.jsx("span",{children:Rf(u.eval_target)})]}),o.jsx("div",{className:"autoruns-run-meta",children:u.run_id}),o.jsxs("div",{className:"autoruns-run-meta",children:["режим=",u.mode??"нет данных"," | mock=",String(u.use_mock)]}),u.llm_provider||u.model?o.jsxs("div",{className:"autoruns-run-meta",children:["llm=",u.llm_provider??"нет данных"," | модель=",u.model??"нет данных"]}):null,o.jsxs("div",{className:"autoruns-run-meta",children:["промпт=",u.prompt_version??"нет данных"]}),o.jsxs("div",{className:"autoruns-run-foot",children:[o.jsxs("span",{children:["оценка: ",Ms(u.score_index)]}),o.jsxs("span",{children:["закрыто/открыто: ",u.closed_cases,"/",u.open_cases]})]}),o.jsxs("div",{className:"autoruns-run-foot",children:[o.jsxs("span",{children:["блокеры: ",u.blocking_failures]}),o.jsxs("span",{children:["качество: ",u.quality_failures]})]})]},u.run_id)),hn.length===0?o.jsx("p",{className:"muted",children:"За выбранный диапазон прогонов нет."}):null]})]}),o.jsxs("section",{className:"autoruns-col",children:[o.jsxs("div",{className:"autoruns-col-header",children:[o.jsx("h3",{children:"Диалог прогона"}),o.jsxs("div",{className:"autoruns-dialog-toolbar",children:[o.jsxs("label",{children:["Прогон",o.jsx("select",{value:ie,onChange:u=>{const f=u.target.value;pt(f)},children:hn.map(u=>o.jsxs("option",{value:u.run_id,children:[ur(u.run_timestamp)," | ",u.run_id]},u.run_id))})]}),o.jsxs("label",{children:["Кейс",o.jsxs("select",{value:Re,onChange:u=>{const f=u.target.value;xe(f),ie&&f&&tn(ie,f)},children:[(fe?.cases.length??0)>0?o.jsx("option",{value:ct,children:"ВСЕ кейсы подряд"}):null,(fe?.cases??[]).map(u=>o.jsxs("option",{value:u.case_id,children:[u.case_id," | ",u.status]},u.case_id))]})]})]})]}),o.jsxs("div",{className:"autoruns-case-list",children:[(fe?.cases.length??0)>0?o.jsxs("button",{type:"button",className:Re===ct?"autoruns-case-item selected":"autoruns-case-item",onClick:()=>{xe(ct),ie&&tn(ie,ct)},children:[o.jsx("span",{children:"ВСЕ кейсы подряд"}),o.jsx("span",{children:fe?.cases.length})]},ct):null,(fe?.cases??[]).map(u=>o.jsxs("button",{type:"button",className:Re===u.case_id?"autoruns-case-item selected":"autoruns-case-item",onClick:()=>{xe(u.case_id),ie&&tn(ie,u.case_id)},children:[o.jsx("span",{children:u.case_id}),o.jsxs("span",{children:[u.status,u.commented_count>0?` | комм=${u.commented_count}`:""]})]},u.case_id))]}),o.jsxs("div",{className:"autoruns-dialog-view",children:[Fn||Dt?o.jsx("p",{className:"muted",children:"Загружаю диалог..."}):null,!Fn&&!Dt&&(ne?.messages.length??0)===0?o.jsx("p",{className:"muted",children:"Диалог для этого прогона не найден."}):null,(ne?.messages??[]).map((u,f)=>{const w=u.role==="assistant"?"assistant":"user";return o.jsxs("article",{className:`autoruns-msg ${w}`,children:[o.jsxs("header",{children:[o.jsx("strong",{children:w==="assistant"?"Система":"Модель/вопрос"}),o.jsxs("div",{className:"autoruns-msg-head-actions",children:[u.case_id?o.jsx("span",{className:"autoruns-msg-case-tag",children:u.case_id}):null,o.jsx("span",{children:u.created_at?ur(u.created_at):"нет данных"}),w==="assistant"&&!br(ie)?o.jsxs(o.Fragment,{children:[o.jsx("button",{type:"button",className:u.commented?"autoruns-comment-icon commented":"autoruns-comment-icon",onClick:()=>fr(u),title:"\\u041a\\u043e\\u043c\\u043c\\u0435\\u043d\\u0442\\u0438\\u0440\\u043e\\u0432\\u0430\\u0442\\u044c \\u043e\\u0442\\u0432\\u0435\\u0442 \\u0441\\u0438\\u0441\\u0442\\u0435\\u043c\\u044b","aria-label":"\\u041a\\u043e\\u043c\\u043c\\u0435\\u043d\\u0442\\u0438\\u0440\\u043e\\u0432\\u0430\\u0442\\u044c \\u043e\\u0442\\u0432\\u0435\\u0442 \\u0441\\u0438\\u0441\\u0442\\u0435\\u043c\\u044b",children:o.jsx(Mf,{commented:u.commented})}),u.annotation?o.jsx("button",{type:"button",className:u.annotation.resolved?"autoruns-resolve-toggle resolved":"autoruns-resolve-toggle",onClick:()=>{Hn(u.annotation,!u.annotation.resolved)},disabled:Nn===u.annotation.annotation_id,title:u.annotation.resolved?"Отметить кейс как невыполненный":"Отметить кейс как выполненный","aria-label":u.annotation.resolved?"Отметить кейс как невыполненный":"Отметить кейс как выполненный",children:o.jsx(yc,{resolved:u.annotation.resolved})}):null]}):null]})]}),o.jsx("p",{children:u.text}),w==="assistant"&&u.annotation?o.jsxs("div",{className:"autoruns-msg-annotation",children:[o.jsx("strong",{children:hc(u.annotation.rating)}),o.jsx("span",{children:u.annotation.comment}),o.jsxs("span",{className:"muted",children:[u.annotation.manual_case_decision,u.annotation.annotation_author?` | ${u.annotation.annotation_author}`:""]})]}):null,(u.trace_id||u.reply_type)&&o.jsxs("footer",{children:[u.trace_id?o.jsxs("span",{children:["trace=",u.trace_id]}):null,u.reply_type?o.jsxs("span",{children:["reply_type=",u.reply_type]}):null]})]},u.message_id??`${w}-${f}`)})]})]}),E?o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Режим ассистента"})}),o.jsxs("div",{className:"autoruns-meta-list",children:[o.jsxs("div",{children:[o.jsx("span",{children:"источник:"}),o.jsx("strong",{children:ne?.source??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"сессия:"}),o.jsx("strong",{children:ne?.session_id??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"контур прогона:"}),o.jsx("strong",{children:Ft?.eval_target??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"оценка прогона:"}),o.jsx("strong",{children:Ms(Ft?.score_index??null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"комментарии:"}),o.jsx("strong",{children:fe?.annotations_summary?.total??0})]})]}),o.jsx("h4",{children:"Пакет режима ассистента"}),o.jsx(dt,{value:ne?.assistant_mode??{note:"assistant_mode недоступен"}}),o.jsx("h4",{style:{marginTop:12},children:"Проверки кейса"}),o.jsx(dt,{value:Je?.checks??{note:"checks недоступен"}}),o.jsx("h4",{style:{marginTop:12},children:"Сабскор метрик"}),o.jsx(dt,{value:Je?.metric_subscores??{note:"metric_subscores недоступен"}})]}):null,D?o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Режим декомпозиции"})}),o.jsxs("div",{className:"autoruns-meta-list",children:[o.jsxs("div",{children:[o.jsx("span",{children:"кейс:"}),o.jsx("strong",{children:Je?.case_id??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"домен:"}),o.jsx("strong",{children:Je?.domain??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"класс запроса:"}),o.jsx("strong",{children:Je?.query_class??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"trace:"}),o.jsx("strong",{children:Je?.trace_id??"нет данных"})]})]}),o.jsx("h4",{children:"Шаги декомпозиции"}),(ne?.decomposition.length??0)>0?o.jsx("ol",{className:"autoruns-decomposition-list",children:(ne?.decomposition??[]).map((u,f)=>o.jsx("li",{children:u},`${f}-${u.slice(0,24)}`))}):o.jsx("p",{className:"muted",children:"В логах кейса нет явной декомпозиции."})]}):null,K?o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Прогресс / регресс"})}),o.jsxs("div",{className:"autoruns-stats-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"Последний score"}),o.jsx("strong",{children:Ms(T?.stats.latest_score_index??null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Предыдущий"}),o.jsx("strong",{children:Ms(T?.stats.previous_score_index??null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Тренд"}),o.jsx("strong",{children:T?mc(T.stats.trend):"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Пробелы качества"}),o.jsx("strong",{children:T?.stats.quality_gap_runs??0})]})]}),o.jsx("h4",{children:"Покрытие доменов (история)"}),gc(T?.stats.domain_coverage??[]),o.jsx("h4",{style:{marginTop:14},children:"Покрытие доменов (выбранный прогон)"}),gc(fe?.coverage.domain_coverage??[]),o.jsx("h4",{style:{marginTop:14},children:"Очереди фиксов пост-анализа"}),Jt?o.jsx("p",{className:"muted",children:"Собираю пост-анализ..."}):null,Jt?null:o.jsx("div",{className:"autoruns-stats-grid",children:Object.entries(ce?.post_analysis.stats.by_queue??{}).map(([u,f])=>o.jsxs("div",{children:[o.jsx("span",{children:u}),o.jsx("strong",{children:f})]},u))}),o.jsxs("div",{className:"autoruns-autogen-list",children:[(ce?.post_analysis.recommended_regression_candidates??[]).slice(0,12).map(u=>o.jsxs("article",{className:"autoruns-autogen-item",children:[o.jsxs("header",{children:[o.jsx("strong",{children:u.manual_case_decision}),o.jsxs("span",{children:[u.rating,"/5"]})]}),o.jsxs("div",{className:"autoruns-run-meta",children:[u.domain??"неизвестно"," / ",u.query_class??"неизвестно"]}),o.jsx("p",{children:u.comment})]},u.annotation_id)),!Jt&&(ce?.post_analysis.recommended_regression_candidates.length??0)===0?o.jsx("p",{className:"muted",children:"Рекомендованных кандидатов пока нет."}):null]})]}):null,Z?o.jsxs("section",{className:"autoruns-col",children:[o.jsx("div",{className:"autoruns-col-header",children:o.jsx("h3",{children:"Комментарии"})}),o.jsx("h4",{children:"Размеченные ответы"}),o.jsxs("div",{className:"autoruns-comment-filter-row",children:[o.jsxs("label",{children:["Фильтр решений",o.jsxs("select",{value:Se,onChange:u=>Le(u.target.value),children:[o.jsx("option",{value:"all",children:"все"}),(Ze.length>0?Ze:Ue?.enum??[]).map(u=>o.jsx("option",{value:u,children:String(Ue?.labels?.[u]??u)},u))]})]}),o.jsx("button",{type:"button",className:"tab autoruns-resolved-filter-toggle",onClick:()=>$e(u=>!u),children:Oe?"Показать выполненные":"Скрыть выполненные"})]}),o.jsxs("div",{className:"autoruns-stats-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"Комментариев"}),o.jsx("strong",{children:Ve.length})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Средний рейтинг"}),o.jsx("strong",{children:Pn===null?"нет данных":`${Pn.toFixed(2)} / 5`})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Последний"}),o.jsx("strong",{children:Ve.length>0?ur(Ve[0].updated_at):"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"Статус"}),o.jsx("strong",{children:Xt?"обновляю":"готово"})]})]}),o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",disabled:Xt,onClick:()=>{vt()},children:Xt?"Обновляю...":"Обновить список"}),o.jsx("button",{type:"button",className:"tab",disabled:Jt,onClick:()=>{et()},children:Jt?"Идет пост-анализ...":"Обновить пост-анализ"})]}),o.jsxs("div",{className:"autoruns-comments-list",children:[Xt?o.jsx("p",{className:"muted",children:"Загружаю комментарии..."}):null,!Xt&&Ve.length===0?o.jsx("p",{className:"muted",children:G.length===0?"Пока нет откомментированных ответов.":"Нет открытых кейсов по текущему фильтру."}):null,Ve.map(u=>o.jsxs("article",{className:Ct===u.annotation_id?"autoruns-comment-item selected":"autoruns-comment-item",onClick:()=>{Vn(u)},role:"button",tabIndex:0,onKeyDown:f=>{(f.key==="Enter"||f.key===" ")&&(f.preventDefault(),Vn(u))},children:[o.jsxs("div",{className:"autoruns-comment-head",children:[o.jsx("strong",{children:hc(u.rating)}),o.jsxs("div",{className:"autoruns-comment-head-actions",children:[o.jsx("span",{children:ur(u.updated_at)}),o.jsx("button",{type:"button",className:u.resolved?"autoruns-resolve-toggle resolved":"autoruns-resolve-toggle",onClick:f=>{f.preventDefault(),f.stopPropagation(),Hn(u,!u.resolved)},disabled:Nn===u.annotation_id,title:u.resolved?"Отметить кейс как невыполненный":"Отметить кейс как выполненный","aria-label":u.resolved?"Отметить кейс как невыполненный":"Отметить кейс как выполненный",children:o.jsx(yc,{resolved:u.resolved})})]})]}),o.jsx("div",{className:"autoruns-run-meta",children:u.run_id}),o.jsxs("div",{className:"autoruns-run-meta",children:["case=",u.case_id," | msg=",u.message_index]}),o.jsxs("div",{className:"autoruns-run-meta",children:["decision=",u.manual_case_decision,u.annotation_author?` | author=${u.annotation_author}`:""]}),u.resolved_at?o.jsxs("div",{className:"autoruns-run-meta",children:["выполнено",": ",ur(u.resolved_at),u.resolved_by?` | by=${u.resolved_by}`:""]}):null,u.context.question_text?o.jsxs("p",{children:["Q: ",u.context.question_text]}):null,u.context.answer_text?o.jsxs("p",{children:["A: ",u.context.answer_text]}):null,o.jsx("p",{children:u.comment})]},u.annotation_id))]}),je?o.jsxs(o.Fragment,{children:[o.jsx("h4",{children:"Тех-контекст брака"}),o.jsxs("div",{className:"autoruns-meta-list",children:[o.jsxs("div",{children:[o.jsx("span",{children:"trace:"}),o.jsx("strong",{children:je.technical_context.trace_id??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"reply_type:"}),o.jsx("strong",{children:je.technical_context.reply_type??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"domain:"}),o.jsx("strong",{children:je.technical_context.domain??"нет данных"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"query_class:"}),o.jsx("strong",{children:je.technical_context.query_class??"нет данных"})]})]}),o.jsx("h4",{children:"JSON разбор"}),o.jsx(dt,{value:{annotation_id:je.annotation_id,run_id:je.run_id,case_id:je.case_id,message_index:je.message_index,rating:je.rating,comment:je.comment,manual_case_decision:je.manual_case_decision,annotation_author:je.annotation_author,resolved:je.resolved,resolved_at:je.resolved_at,resolved_by:je.resolved_by,context:je.context,technical_context:je.technical_context,case_summary:je.case_summary?{case_id:je.case_summary.case_id,domain:je.case_summary.domain,query_class:je.case_summary.query_class,checks:je.case_summary.checks,metric_subscores:je.case_summary.metric_subscores}:null}})]}):null]}):null]}),de.open?o.jsx("div",{className:"autoruns-comment-modal-backdrop",onClick:u=>{u.target===u.currentTarget&&Rn()},children:o.jsxs("div",{className:"autoruns-comment-modal",children:[o.jsx("h3",{children:"Комментарий к ответу системы"}),o.jsx("p",{className:"muted",children:"Оцените ответ по 5-балльной шкале и добавьте комментарий по браку."}),En?o.jsxs(o.Fragment,{children:[o.jsxs("details",{className:"autoruns-prompt-details",open:!0,children:[o.jsx("summary",{children:"Вопрос пользователя"}),o.jsx("p",{className:"autoruns-comment-quote",children:dr?.text??"Вопрос в диалоге не найден."})]}),o.jsxs("details",{className:"autoruns-prompt-details",open:!0,children:[o.jsx("summary",{children:"Ответ системы"}),o.jsx("p",{className:"autoruns-comment-quote",children:En.text})]})]}):null,o.jsx("div",{className:"autoruns-rating-row",role:"group","aria-label":"Рейтинг ответа",children:[1,2,3,4,5].map(u=>o.jsx("button",{type:"button",className:de.rating>=u?"autoruns-rating-dot active":"autoruns-rating-dot",onClick:()=>We(f=>({...f,rating:u})),disabled:de.saving,"aria-label":`Оценка ${u}`,children:de.rating>=u?"●":"○"},u))}),o.jsxs("div",{className:"autoruns-form-grid",children:[o.jsxs("label",{children:["Решение по кейсу",o.jsx("select",{value:de.manualCaseDecision,onChange:u=>We(f=>({...f,manualCaseDecision:u.target.value})),disabled:de.saving,children:(Ze.length>0?Ze:Ue?.enum??[$o]).map(u=>o.jsx("option",{value:u,children:String(Ue?.labels?.[u]??u)},u))})]}),o.jsxs("label",{children:["Автор комментария",o.jsx("input",{value:de.annotationAuthor,onChange:u=>We(f=>({...f,annotationAuthor:u.target.value})),placeholder:"manual_reviewer",disabled:de.saving})]})]}),o.jsxs("label",{children:["Комментарий",o.jsx("textarea",{value:de.comment,onChange:u=>We(f=>({...f,comment:u.target.value})),placeholder:"Почему ответ бракованный, что именно пошло не так, какие технические детали проверить.",rows:4,disabled:de.saving})]}),de.error?o.jsx("p",{className:"error-text",children:de.error}):null,o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",onClick:()=>{Er()},disabled:de.saving,children:de.saving?"Сохраняю...":"Готово"}),o.jsx("button",{type:"button",className:"tab",onClick:()=>Rn(),disabled:de.saving,children:"Отмена"})]})]})}):null]})}const Lf=/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json|debug_payload|technical_breakdown)\b/i,Af=[/\b(?:debug_payload_json|technical_breakdown_json)\b/i,/\b(?:route_summary|semantic_profile|domain_scope|relation_patterns|account_scope)\b/i,/\b(?:coverage_report|retrieval_status|problem_unit_state|candidate_evidence)\b/i,/\b(?:graph_domain_scope|graph_runtime|selection_reason|why_included)\b/i];function Of(i){try{return JSON.stringify(i,null,2)}catch{return String(i)}}function zf(i){const h=String(i??""),p=h.match(Lf);return(p?h.slice(0,p.index):h).replace(/###\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json)[\s\S]*?(?:```[\s\S]*?```|$)/gi,"").replace(/(?:^|\n)\s*#{0,6}\s*(?:debug_payload_json|technical_breakdown_json|route_summary_json)\b[\s\S]*$/gi,"").split(/\r?\n/g).map(K=>K.trimEnd()).filter(K=>K.trim().length>0).filter(K=>!Af.some(Z=>Z.test(K))).join(` -`).trim()}function Df(i,h,p="default"){const O=p==="technical",E=[];E.push("# Assistant conversation export"),E.push(`session_id: ${i||"n/a"}`),E.push(`export_mode: ${p}`),E.push(`exported_at: ${new Date().toISOString()}`),E.push("");for(let D=0;D{T.current&&(T.current.scrollTop=T.current.scrollHeight)},[h,I]),S.useEffect(()=>()=>{H.current!==null&&window.clearTimeout(H.current)},[]);async function G(se){if(h.length===0)return;const Se=Df(i,h,se),Le=await Uf(Se);X(se==="technical"?"тех":"чат"),ye(Le?"success":"error"),H.current!==null&&window.clearTimeout(H.current),H.current=window.setTimeout(()=>{ye("idle")},2200)}return o.jsxs(Sn,{title:"Режим ассистента",subtitle:"Диалоговый слой поверх normalizer, маршрутизации и factual retrieval.",actions:o.jsxs("div",{className:"assistant-panel-actions",children:[o.jsx("button",{type:"button",className:"assistant-copy-btn",onClick:()=>{G("default")},disabled:h.length===0,title:"Экспорт только user-facing чата",children:"Скопировать чат"}),o.jsx("button",{type:"button",className:"assistant-copy-btn",onClick:()=>{G("technical")},disabled:h.length===0,title:"Технический экспорт с debug payload",children:"Скопировать техчат"}),fe==="success"?o.jsxs("span",{className:"assistant-copy-feedback success",children:["Скопировано (",ne,")"]}):null,fe==="error"?o.jsx("span",{className:"assistant-copy-feedback error",children:"Ошибка копирования"}):null,o.jsx("span",{className:"status-chip",children:i?`session: ${i}`:"новая сессия"})]}),children:[o.jsxs("div",{ref:T,className:"assistant-chat-list",children:[h.length===0?o.jsx("div",{className:"assistant-empty muted",children:"Диалог пуст. Отправьте первый вопрос, чтобы запустить контур ассистента."}):null,h.map(se=>o.jsxs("article",{className:`assistant-msg ${se.role}`,children:[o.jsxs("header",{className:"assistant-msg-head",children:[o.jsx("strong",{children:$f(se.role)}),o.jsx("span",{children:Ff(se.created_at)})]}),o.jsx("div",{className:"assistant-msg-body",children:se.text}),se.role==="assistant"&&se.debug?o.jsxs("details",{className:"assistant-debug",children:[o.jsx("summary",{children:"Показать технический разбор"}),o.jsx(dt,{value:se.debug})]}):null]},se.message_id))]}),o.jsxs("div",{className:"assistant-compose",children:[o.jsxs("label",{className:"full-width",children:["Сообщение",o.jsx("textarea",{value:p,onChange:se=>O(se.target.value),rows:4,placeholder:"Введите вопрос к данным компании..."})]}),o.jsxs("div",{className:"button-row",children:[o.jsxs("label",{className:"checkbox-row",children:[o.jsx("input",{type:"checkbox",checked:E,onChange:se=>D(se.target.checked)}),"Mock-режим"]}),o.jsx("button",{type:"button",onClick:()=>K(),disabled:B||!p.trim(),children:B?"Выполняю...":"Отправить"}),o.jsx("button",{type:"button",onClick:()=>Z(),disabled:B&&h.length===0,children:"Сбросить сессию"})]}),I?o.jsx("p",{className:"diff-summary",children:I}):null,q?o.jsx("p",{className:"error-text",children:q}):null]})]})}function xc({value:i,modelOptions:h,modelsBusy:p,onChange:O,onReloadModels:E,onTestConnection:D,onSaveLocalConfig:K,lastStatus:Z,busy:B}){const I=i.llmProvider==="local",q=h.includes(i.model),[T,H]=S.useState(String(i.temperature)),[fe,ye]=S.useState(String(i.maxOutputTokens));S.useEffect(()=>{H(String(i.temperature))},[i.temperature]),S.useEffect(()=>{ye(String(i.maxOutputTokens))},[i.maxOutputTokens]);const ne=G=>{const se=G.replace(",",".").trim();if(!se){H(String(i.temperature));return}const Se=Number(se);if(!Number.isFinite(Se)){H(String(i.temperature));return}O({...i,temperature:Se}),H(String(Se))},X=G=>{const se=G.trim();if(!se){ye(String(i.maxOutputTokens));return}const Se=Number.parseInt(se,10);if(!Number.isFinite(Se)||Se<=0){ye(String(i.maxOutputTokens));return}O({...i,maxOutputTokens:Se}),ye(String(Se))};return o.jsxs(Sn,{title:"LLM Connection",subtitle:"Switch between OpenAI cloud and local OpenAI-compatible server.",actions:o.jsx("span",{className:"status-chip",children:Z||"Status: not checked"}),children:[o.jsxs("div",{className:"grid-two",children:[o.jsxs("label",{children:["Provider",o.jsxs("select",{value:i.llmProvider,onChange:G=>{const se=G.target.value==="local"?"local":"openai";O({...i,llmProvider:se,baseUrl:se==="local"?"http://127.0.0.1:1234/v1":"https://api.openai.com/v1"})},children:[o.jsx("option",{value:"openai",children:"OpenAI (token)"}),o.jsx("option",{value:"local",children:"Local (LM Studio / OpenAI-compatible)"})]})]}),o.jsxs("label",{children:["Model",o.jsxs("select",{value:q?i.model:"__manual__",onChange:G=>{const se=G.target.value;se!=="__manual__"&&O({...i,model:se})},children:[o.jsx("option",{value:"__manual__",children:"Manual input"}),h.map(G=>o.jsx("option",{value:G,children:G},G))]})]}),o.jsxs("label",{children:["Model ID (manual)",o.jsx("input",{value:i.model,onChange:G=>O({...i,model:G.target.value}),placeholder:"qwen2.5-14b-instruct or lmstudio loaded model id"})]}),I?null:o.jsxs("label",{className:"full-width",children:["OpenAI API Key",o.jsx("input",{type:"password",value:i.apiKey,onChange:G=>O({...i,apiKey:G.target.value}),placeholder:"sk-..."})]}),o.jsxs("label",{className:I?"full-width":void 0,children:[I?"Local server base URL":"Base URL",o.jsx("input",{value:i.baseUrl,onChange:G=>O({...i,baseUrl:G.target.value}),placeholder:I?"http://127.0.0.1:1234/v1":"https://api.openai.com/v1"})]}),o.jsxs("label",{children:["Temperature",o.jsx("input",{type:"number",step:"0.1",value:T,onChange:G=>H(G.target.value),onBlur:G=>ne(G.target.value),onKeyDown:G=>{G.key==="Enter"&&ne(G.target.value)}})]}),o.jsxs("label",{children:["Max output tokens",o.jsx("input",{type:"number",value:fe,onChange:G=>ye(G.target.value),onBlur:G=>X(G.target.value),onKeyDown:G=>{G.key==="Enter"&&X(G.target.value)}})]})]}),o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",onClick:()=>K(),children:"Save local config"}),o.jsx("button",{type:"button",onClick:()=>E(),disabled:B||p,children:p?"Loading models...":"Load model list"}),o.jsx("button",{type:"button",onClick:()=>D(),disabled:B,children:B?"Checking...":"Test connection"})]})]})}function Hf({items:i,onRefresh:h,onOpenTrace:p}){return o.jsx(Sn,{title:"История нормализаций",subtitle:"Короткий вопрос, confidence, route hint и статус валидации.",actions:o.jsx("button",{type:"button",onClick:()=>h(),children:"Обновить"}),children:o.jsxs("div",{className:"history-list",children:[i.length===0?o.jsx("p",{className:"muted",children:"История пока пустая."}):null,i.map(O=>o.jsxs("button",{type:"button",className:"history-item",onClick:()=>p(O.trace_id),children:[o.jsxs("div",{className:"history-row",children:[o.jsx("strong",{children:O.route_hint??"route: n/a"}),o.jsx("span",{children:O.validation_passed?"schema: ok":"schema: fail"})]}),o.jsx("p",{children:O.question_short}),o.jsxs("div",{className:"history-row",children:[o.jsx("span",{children:O.model}),o.jsx("span",{children:new Date(O.timestamp).toLocaleString("ru-RU")})]})]},O.trace_id))]})})}function $n(i){return i==null||i===""?"—":String(i)}function Vf({result:i}){return o.jsx(Sn,{title:"Runtime метрики",subtitle:"trace_id, токены, latency и статус валидации.",children:o.jsxs("div",{className:"metrics-grid",children:[o.jsxs("div",{children:[o.jsx("span",{children:"trace_id"}),o.jsx("strong",{children:$n(i?.trace_id)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"request_started_at"}),o.jsx("strong",{children:$n(i?new Date(Date.now()-i.latency_ms).toISOString():null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"request_finished_at"}),o.jsx("strong",{children:$n(i?new Date().toISOString():null)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"latency_ms"}),o.jsx("strong",{children:$n(i?.latency_ms)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"input_tokens"}),o.jsx("strong",{children:$n(i?.usage?.input_tokens)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"output_tokens"}),o.jsx("strong",{children:$n(i?.usage?.output_tokens)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"total_tokens"}),o.jsx("strong",{children:$n(i?.usage?.total_tokens)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"validation_status"}),o.jsx("strong",{children:i?.validation?.passed?"passed":"failed"})]}),o.jsxs("div",{children:[o.jsx("span",{children:"prompt_version"}),o.jsx("strong",{children:$n(i?.prompt_version)})]}),o.jsxs("div",{children:[o.jsx("span",{children:"schema_version"}),o.jsx("strong",{children:$n(i?.schema_version)})]})]})})}const Qf={normalized:"Normalized JSON",fragments:"Fragment View",scope:"Scope View",flags:"Flags View",route:"Route Simulation",raw:"Raw model output",validation:"Validation",logs:"Logs"};function Wf(i){return i&&typeof i=="object"?i:null}function Kf({tab:i,onTabChange:h,result:p,appLogs:O}){const E=["normalized","fragments","scope","flags","route","raw","validation","logs"],D=Wf(p?.normalized),K=String(D?.schema_version??""),Z=K==="normalized_query_v2"||K==="normalized_query_v2_0_1"||K==="normalized_query_v2_0_2",B=Z?{fragments:D?.fragments??[],discarded_fragments:D?.discarded_fragments??[]}:{note:"Fragment View доступен для normalized_query_v2."},I=Z?{message_in_scope:D?.message_in_scope??null,scope_confidence:D?.scope_confidence??null,contains_multiple_tasks:D?.contains_multiple_tasks??null,global_notes:D?.global_notes??null}:{note:"Scope View доступен для normalized_query_v2."},q=Z?Array.isArray(D?.fragments)?(D?.fragments).map(T=>({fragment_id:T.fragment_id??null,domain_relevance:T.domain_relevance??null,candidate_labels:T.candidate_labels??[],execution_readiness:T.execution_readiness??null,clarification_reason:T.clarification_reason??null,soft_assumption_used:T.soft_assumption_used??[],route_status:T.route_status??null,no_route_reason:T.no_route_reason??null,flags:T.flags??{}})):[]:{note:"Flags View доступен для normalized_query_v2."};return o.jsxs(Sn,{title:"Выходные данные",subtitle:"Structured output и диагностические вкладки.",children:[o.jsx("div",{className:"tab-row",children:E.map(T=>o.jsx("button",{type:"button",className:i===T?"tab active":"tab",onClick:()=>h(T),children:Qf[T]},T))}),i==="normalized"?o.jsx(dt,{value:p?.normalized??{note:"Нет данных."}}):null,i==="fragments"?o.jsx(dt,{value:B}):null,i==="scope"?o.jsx(dt,{value:I}):null,i==="flags"?o.jsx(dt,{value:q}):null,i==="route"?o.jsx(dt,{value:p?.route_hint_summary??{note:"Нет данных."}}):null,i==="raw"?o.jsx(dt,{value:p?.raw_model_output??{note:"Нет данных."}}):null,i==="validation"?o.jsx(dt,{value:p?.validation??{note:"Нет данных."}}):null,i==="logs"?o.jsx(dt,{value:O}):null]})}function _c({value:i,onChange:h,presets:p,selectedPresetId:O,onSelectPreset:E,onLoadPreset:D,onSavePreset:K,onResetDefaults:Z,onDiffPrevious:B,presetName:I,onPresetNameChange:q,diffSummary:T}){return o.jsxs(Sn,{title:"Prompt Manager",subtitle:"Системный, developer и domain уровни управляются отдельно.",children:[o.jsxs("div",{className:"prompt-manager-grid",children:[o.jsxs("label",{children:["Системный prompt",o.jsx("textarea",{value:i.systemPrompt,onChange:H=>h({...i,systemPrompt:H.target.value}),rows:6})]}),o.jsxs("label",{children:["Developer / Instruction prompt",o.jsx("textarea",{value:i.developerPrompt,onChange:H=>h({...i,developerPrompt:H.target.value}),rows:6})]}),o.jsxs("label",{children:["Domain prompt",o.jsx("textarea",{value:i.domainPrompt,onChange:H=>h({...i,domainPrompt:H.target.value}),rows:6})]}),o.jsxs("label",{children:["Schema notes",o.jsx("textarea",{value:i.schemaNotes,onChange:H=>h({...i,schemaNotes:H.target.value}),rows:6})]}),o.jsxs("label",{className:"full-width",children:["Few-shot examples",o.jsx("textarea",{value:i.fewShotExamples,onChange:H=>h({...i,fewShotExamples:H.target.value}),rows:8})]})]}),o.jsxs("div",{className:"button-row",children:[o.jsxs("select",{value:O,onChange:H=>E(H.target.value),children:[o.jsx("option",{value:"",children:"Выберите preset..."}),p.map(H=>o.jsx("option",{value:H.id,children:H.name},H.id))]}),o.jsx("button",{type:"button",onClick:()=>D(),children:"Загрузить preset"}),o.jsx("input",{value:I,onChange:H=>q(H.target.value),placeholder:"Имя для сохранения"}),o.jsx("button",{type:"button",onClick:()=>K(),children:"Сохранить preset"}),o.jsx("button",{type:"button",onClick:()=>B(),children:"Diff с предыдущим"}),o.jsx("button",{type:"button",onClick:()=>Z(),children:"Сбросить к default"})]}),T?o.jsx("p",{className:"diff-summary",children:T}):null]})}function qf({value:i,onChange:h,onApplyBatchFormat:p,onNormalize:O,busy:E,useMock:D,onUseMockChange:K,errorMessage:Z}){return o.jsxs(Sn,{title:"Запрос пользователя",subtitle:"NDC semantic front-end: нормализуем, но не отвечаем за бухгалтерскую суть.",children:[o.jsxs("div",{className:"grid-two",children:[o.jsxs("label",{className:"full-width",children:["Raw user question",o.jsx("textarea",{value:i.userQuestion,onChange:B=>h({...i,userQuestion:B.target.value}),rows:6,placeholder:"Например: По каким покупателям у нас на конец июня висят отгрузки без оплаты..."})]}),o.jsxs("label",{className:"full-width",children:["Batch queries (`;` separator)",o.jsx("textarea",{value:i.batchQuestionsRaw,onChange:B=>h({...i,batchQuestionsRaw:B.target.value}),onBlur:()=>p(),rows:8,placeholder:"Вопрос 1; Вопрос 2; Вопрос 3"})]}),o.jsxs("label",{children:["Optional period context",o.jsx("input",{value:i.periodHint,onChange:B=>h({...i,periodHint:B.target.value})})]}),o.jsxs("label",{children:["Optional business context",o.jsx("input",{value:i.businessContext,onChange:B=>h({...i,businessContext:B.target.value})})]}),o.jsxs("label",{children:["Optional expected route (eval)",o.jsx("input",{value:i.expectedRoute,onChange:B=>h({...i,expectedRoute:B.target.value})})]})]}),o.jsxs("div",{className:"button-row",children:[o.jsxs("label",{className:"checkbox-row",children:[o.jsx("input",{type:"checkbox",checked:D,onChange:B=>K(B.target.checked)}),"Mock-режим (без вызова OpenAI)"]}),o.jsx("button",{type:"button",onClick:()=>p(),disabled:E||!i.batchQuestionsRaw.trim(),children:"Применить `;` в переносы"}),o.jsx("button",{type:"button",onClick:()=>O(!1),disabled:E||!i.userQuestion.trim(),children:E?"Нормализуем...":"Normalize"}),o.jsx("button",{type:"button",onClick:()=>O(!0),disabled:E||!i.userQuestion.trim(),children:E?"Сохраняем...":"Normalize + Save as test case"})]}),Z?o.jsx("p",{className:"error-text",children:Z}):null]})}function Gf({runs:i,selectedRunId:h,onSelectRun:p,onStartRun:O,onFinishRun:E,onRefreshRuns:D,onRunEval:K,onCopyEvalReport:Z,evalBusy:B,traceItems:I,evalReport:q}){return o.jsxs(Sn,{title:"NDC Run Monitor",subtitle:"Важно: кнопка Запустить run создает только run-сущность. Кнопка eval запускает batch-проверку normalizer v2.0.2.",children:[o.jsxs("div",{className:"button-row",children:[o.jsx("button",{type:"button",onClick:()=>O(),children:"Запустить run"}),o.jsx("button",{type:"button",onClick:()=>E(),disabled:!h,children:"Завершить выбранный run"}),o.jsx("button",{type:"button",onClick:()=>D(),children:"Обновить runs"}),o.jsx("button",{type:"button",onClick:()=>K(),disabled:B,children:B?"Идет eval v2.0.2...":"Запустить eval v2.0.2"})]}),o.jsxs("div",{className:"runtime-stack",children:[o.jsxs("div",{className:"runtime-runs",children:[i.map(T=>o.jsxs("button",{type:"button",className:h===T.runId?"history-item selected":"history-item",onClick:()=>p(T.runId),children:[o.jsxs("div",{className:"history-row",children:[o.jsx("strong",{children:T.status}),o.jsx("span",{children:T.runId})]}),o.jsxs("div",{className:"history-row",children:[o.jsx("span",{children:T.sessionId}),o.jsx("span",{children:new Date(T.updatedAt).toLocaleString("ru-RU")})]})]},T.runId)),i.length===0?o.jsx("p",{className:"muted",children:"Нет активных запусков."}):null]}),o.jsxs("div",{className:"runtime-details",children:[o.jsx("h3",{children:"Trace выбранного run"}),o.jsx(dt,{value:I}),o.jsxs("div",{className:"eval-report-wrap",children:[o.jsx("h3",{style:{marginTop:12},children:"Отчет eval"}),o.jsx(dt,{value:q??{note:"Eval пока не запускался"}}),o.jsx("button",{type:"button",className:"copy-cube-button",title:"Скопировать отчет eval",onClick:()=>Z(),children:"⧉"})]})]})]})]})}const Jf={llmProvider:"openai",apiKey:"",model:"gpt-4o-mini",baseUrl:"https://api.openai.com/v1",temperature:0,maxOutputTokens:700},Sc={systemPrompt:"Ты semantic-normalizer для бухгалтерского ассистента NDC. Возвращай только JSON по схеме normalized_query_v2_0_2.",developerPrompt:"Сначала делай decomposition сообщения на task fragments, затем определяй domain scope и route-critical flags. Для каждого fragment заполняй execution_readiness + route_status + no_route_reason. Если fragment routable, не оставляй его в no_route.",domainPrompt:"Контур: данные текущего предприятия в 1С/NDC. In-scope: документы, проводки, взаиморасчеты, остатки, периодное закрытие, аномалии и контрольные проверки. Out-of-scope: общая теория, законы и оффтоп.",schemaNotes:"schema_version: normalized_query_v2_0_2. Строгий JSON без дополнительных полей.",fewShotExamples:"Q: Проверь по поставщикам хвосты и разложи цепочку документов/оплат. => fragment in_scope, flags: multi_entity + chain_explanation. Q: Как вообще по ФСБУ? => out_of_scope/generic_accounting."},bf={userQuestion:"",batchQuestionsRaw:"",periodHint:"",businessContext:"",expectedRoute:""},Ei={colors:{backgroundRgb:"18, 18, 18",mainSurfaceRgb:"25, 25, 25",horizontalSurfaceRgb:"30, 30, 30",focusSurfaceRgb:"35, 35, 35",activeRgb:"167, 59, 255",activeTextRgb:"240, 240, 240",textMainRgb:"240, 240, 240",textMutedRgb:"166, 166, 166",dangerRgb:"126, 126, 126",scrollbarTrackRgb:"20, 20, 20",scrollbarThumbRgb:"30, 30, 30",scrollbarThumbHoverRgb:"30, 50, 30"},layout:{modeColumnWidthPx:440,modeToggleWidthPx:188}},wc="ndc_normalizer_session_config_v1",jc="ndc_autoruns_layout_config_v1",Yf="ndc-autoruns-save",Pi=["Анализ запроса","Получение данных","Подготовка ответа"],Xf="assistant",Ri="normalizer_v2_0_2",kc="address_query_runtime_v1",Zf=["normalized","fragments","scope","flags","route","raw","validation","logs"];function ep(i){return`[${new Date().toLocaleTimeString("ru-RU")}] ${i}`}function tp(i,h){if(!h)return"Previous preset is not selected.";const O=["systemPrompt","developerPrompt","domainPrompt","schemaNotes","fewShotExamples"].filter(E=>i[E]!==h[E]).map(E=>`${E}: ${Math.abs(i[E].length-h[E].length)} chars delta`);return O.length===0?"No changes against previous preset.":`Changed fields: ${O.length}. ${O.join(" | ")}`}function np(){const[i,h]=S.useState(Jf),[p,O]=S.useState(Sc),[E,D]=S.useState(bf),[K,Z]=S.useState(null),[B,I]=S.useState([]),[q,T]=S.useState([]),[H,fe]=S.useState("normalized"),[ye,ne]=S.useState(!1),[X,G]=S.useState(!1),[se,Se]=S.useState([]),[Le,Oe]=S.useState(""),[$e,Ue]=S.useState([]),[Ge,Ze]=S.useState(""),[ft,Ct]=S.useState("NDC custom preset"),[rt,ie]=S.useState(null),[Be,Re]=S.useState(""),[xe,me]=S.useState(!1),[A,R]=S.useState([]),[L,g]=S.useState(""),[j,ee]=S.useState([]),[oe,le]=S.useState(!1),[ae,re]=S.useState(null),[he,ce]=S.useState(""),[ke,zt]=S.useState(Xf),[wn,cn]=S.useState(!0),[Nt,Jt]=S.useState(!0),[jn,bt]=S.useState(!0),[kn,Cn]=S.useState(!0),[Et,Dt]=S.useState(!0),[Yt,Fn]=S.useState(!0),[dn,Xt]=S.useState(!0),[fn,Nn]=S.useState(!0),[$t,Un]=S.useState(!0),[Ee,pn]=S.useState(!0),[Pt,cr]=S.useState(!0),[gt,de]=S.useState(!0),[We,mn]=S.useState(!0),[Rt,Zt]=S.useState(!0),[st,Ft]=S.useState(!0),[Je,Ve]=S.useState(""),[je,En]=S.useState([]),[dr,Pn]=S.useState(""),[hn,ge]=S.useState(!1),[en,Ut]=S.useState(""),[vt,Tt]=S.useState(""),Mt=S.useRef(!1),et=S.useRef(!1);S.useEffect(()=>{const x=document.documentElement,{colors:z}=Ei;x.style.setProperty("--rgb-background",z.backgroundRgb),x.style.setProperty("--rgb-surface-main",z.mainSurfaceRgb),x.style.setProperty("--rgb-surface-horizontal",z.horizontalSurfaceRgb),x.style.setProperty("--rgb-surface-focus",z.focusSurfaceRgb),x.style.setProperty("--rgb-active",z.activeRgb),x.style.setProperty("--rgb-active-text",z.activeTextRgb),x.style.setProperty("--rgb-text-main",z.textMainRgb),x.style.setProperty("--rgb-text-muted",z.textMutedRgb),x.style.setProperty("--rgb-danger",z.dangerRgb),x.style.setProperty("--rgb-scrollbar-track",z.scrollbarTrackRgb),x.style.setProperty("--rgb-scrollbar-thumb",z.scrollbarThumbRgb),x.style.setProperty("--rgb-scrollbar-thumb-hover",z.scrollbarThumbHoverRgb),x.style.setProperty("--mode-column-width",`${Ei.layout.modeColumnWidthPx}px`),x.style.setProperty("--mode-toggle-width",`${Ei.layout.modeToggleWidthPx}px`)},[]);const Y=x=>{T(z=>[ep(x),...z].slice(0,300))};function tn(){let x=0;Ut(Pi[0]);const z=window.setInterval(()=>{x=Math.min(x+1,Pi.length-1),Ut(Pi[x])},650);return()=>window.clearInterval(z)}S.useEffect(()=>{const x=localStorage.getItem(wc);if(x)try{const P=JSON.parse(x);h(pe=>({...pe,llmProvider:P.llmProvider==="local"?"local":"openai",model:P.model??pe.model,baseUrl:P.baseUrl??pe.baseUrl,temperature:P.temperature??pe.temperature,maxOutputTokens:P.maxOutputTokens??pe.maxOutputTokens}))}catch{}const z=localStorage.getItem(jc);if(z)try{const P=JSON.parse(z);(P.uiMode==="assistant"||P.uiMode==="decomposition"||P.uiMode==="autoruns")&&zt(P.uiMode),P.activeTab&&Zf.includes(P.activeTab)&&fe(P.activeTab),typeof P.showAutorunsAssistantMode=="boolean"&&cn(P.showAutorunsAssistantMode),typeof P.showAutorunsDecompositionMode=="boolean"&&Jt(P.showAutorunsDecompositionMode),typeof P.showAutorunsProgressMode=="boolean"&&bt(P.showAutorunsProgressMode),typeof P.showAutorunsCommentsMode=="boolean"&&Cn(P.showAutorunsCommentsMode),typeof P.showAssistantConnectionMode=="boolean"&&Dt(P.showAssistantConnectionMode),typeof P.showAssistantPromptMode=="boolean"&&Fn(P.showAssistantPromptMode),typeof P.showAssistantChatMode=="boolean"&&Xt(P.showAssistantChatMode),typeof P.showAssistantSamMode=="boolean"&&Nn(P.showAssistantSamMode),typeof P.showDecompositionConnectionMode=="boolean"&&Un(P.showDecompositionConnectionMode),typeof P.showDecompositionPromptMode=="boolean"&&pn(P.showDecompositionPromptMode),typeof P.showDecompositionQueryMode=="boolean"&&cr(P.showDecompositionQueryMode),typeof P.showDecompositionOutputMode=="boolean"&&de(P.showDecompositionOutputMode),typeof P.showDecompositionMetricsMode=="boolean"&&mn(P.showDecompositionMetricsMode),typeof P.showDecompositionHistoryMode=="boolean"&&Zt(P.showDecompositionHistoryMode),typeof P.showDecompositionRuntimeMode=="boolean"&&Ft(P.showDecompositionRuntimeMode),P.prompts&&(O(pe=>({...pe,...P.prompts})),et.current=!0)}catch{}pt(),gn(),ot()},[]);async function pt(){try{const x=await Ie.loadHistory();I(x.items??[])}catch(x){Y(`History load error: ${x instanceof Error?x.message:String(x)}`)}}async function gn(){try{const z=(await Ie.loadPresets()).presets??[];if(Ue(z),et.current){Mt.current=!0;return}if(Mt.current)return;const P=z.find(pe=>pe.prompt_version===Ri)??z.find(pe=>pe.id==="default-normalizer-v2_0_2");if(!P){Mt.current=!0,Y(`Preset autoload skipped: ${Ri} not found.`);return}Ze(P.id),ie(p),O({systemPrompt:P.systemPrompt,developerPrompt:P.developerPrompt,domainPrompt:P.domainPrompt,schemaNotes:P.schemaNotes??"",fewShotExamples:P.fewShotExamples??""}),Mt.current=!0,Y(`Preset autoloaded: ${P.name} (${P.prompt_version}).`)}catch(x){Y(`Presets load error: ${x instanceof Error?x.message:String(x)}`)}}async function ot(){try{const x=await Ie.listRuns();R(x.items??[])}catch(x){Y(`Runs load error: ${x instanceof Error?x.message:String(x)}`)}}function yt(){localStorage.setItem(wc,JSON.stringify({model:i.model,llmProvider:i.llmProvider,baseUrl:i.baseUrl,temperature:i.temperature,maxOutputTokens:i.maxOutputTokens})),Y("Local config saved (without API key).")}function Nr(){localStorage.setItem(jc,JSON.stringify({uiMode:ke,activeTab:H,showAutorunsAssistantMode:wn,showAutorunsDecompositionMode:Nt,showAutorunsProgressMode:jn,showAutorunsCommentsMode:kn,showAssistantConnectionMode:Et,showAssistantPromptMode:Yt,showAssistantChatMode:dn,showAssistantSamMode:fn,showDecompositionConnectionMode:$t,showDecompositionPromptMode:Ee,showDecompositionQueryMode:Pt,showDecompositionOutputMode:gt,showDecompositionMetricsMode:We,showDecompositionHistoryMode:Rt,showDecompositionRuntimeMode:st,prompts:p})),window.dispatchEvent(new CustomEvent(Yf)),Y("UI layout and prompts saved.")}async function fr(){ne(!0),ce("");try{const x=await Ie.testConnection(i);x.provider==="local"?x.model_found===!0?(Oe(`LOCAL OK - ${x.model}`),Y(`Local model is available: ${x.model} (catalog size=${x.models_count??"n/a"}).`)):x.model_found===!1?(Oe(`LOCAL OK, model not loaded - ${x.model}`),Y(`Local server is reachable, but model '${x.model}' is not in loaded catalog. Use 'Load model list' and select one of loaded models.`)):(Oe(`LOCAL OK (model list unavailable) - ${x.model}`),Y("Local server is reachable, but model catalog could not be verified.")):(Oe(`OPENAI OK - ${x.model}`),Y(`OpenAI connection ok: ${x.model}`))}catch(x){const z=x instanceof Error?x.message:String(x);Oe("Connection error"),ce(`Test connection: ${z}`),Y(`Test connection error: ${z}`)}finally{ne(!1)}}async function Rn(){G(!0);try{const z=(await Ie.listModels(i)).models??[];Se(z),z.length>0&&h(P=>P.model&&z.includes(P.model)?P:{...P,model:z[0]}),Y(`Model catalog loaded (${i.llmProvider}): ${z.length} items.`)}catch(x){const z=x instanceof Error?x.message:String(x);Y(`Load model list error: ${z}`)}finally{G(!1)}}S.useEffect(()=>{Se([])},[i.llmProvider,i.baseUrl]);async function Er(x){ne(!0),ce("");try{const z=await Ie.normalize({connection:i,prompts:p,promptVersion:"normalizer_v2_0_2",query:{userQuestion:E.userQuestion,periodHint:E.periodHint,businessContext:E.businessContext,expectedRoute:E.expectedRoute},saveAsTestCase:x,useMock:xe});Z(z),fe("normalized"),Y(`Normalize done: trace=${z.trace_id}, validation=${z.validation.passed?"passed":"failed"}`),pt()}catch(z){const P=z instanceof Error?z.message:String(z);ce(`Normalize: ${P}`),Y(`Normalize error: ${P}`)}finally{ne(!1)}}function Bn(){const x=$e.find(z=>z.id===Ge);if(!x){Y("Preset is not selected.");return}ie(p),O({systemPrompt:x.systemPrompt,developerPrompt:x.developerPrompt,domainPrompt:x.domainPrompt,schemaNotes:x.schemaNotes??"",fewShotExamples:x.fewShotExamples??""}),Y(`Preset loaded: ${x.name}`)}async function Hn(){try{await Ie.savePreset({name:ft||"NDC preset",prompt_version:"normalizer_v2_0_2",systemPrompt:p.systemPrompt,developerPrompt:p.developerPrompt,domainPrompt:p.domainPrompt,schemaNotes:p.schemaNotes,fewShotExamples:p.fewShotExamples}),Y("Preset saved."),await gn()}catch(x){Y(`Preset save error: ${x instanceof Error?x.message:String(x)}`)}}function Vn(){O(Sc),Y("Prompt panel reset to defaults.")}function pr(){const x=tp(p,rt);Re(x),Y(x)}function u(){const x=E.batchQuestionsRaw.split(";").map(z=>z.trim()).filter(Boolean).join(` - -`);x&&(D(z=>({...z,batchQuestionsRaw:x})),Y("Batch field formatted: `;` converted to blank-line separators."))}async function f(x){try{const P=(await Ie.loadTrace(x)).trace,pe=P.parsed_normalized_json??null;Z({trace_id:String(P.trace_id??x),ok:!!P.validation_result?.passed,normalized:pe,route_hint_summary:P.route_hint_summary??(pe?{route_hint:pe.route_hint??null,confidence:pe.confidence?.route_hint??null}:null),raw_model_output:P.raw_model_response??{},validation:P.validation_result??{passed:!1,errors:["validation not found"]},usage:P.usage??{input_tokens:0,output_tokens:0,total_tokens:0},latency_ms:Number(P.latency_ms??0),prompt_version:String(P.prompt_version??"unknown"),schema_version:String(P.schema_version??"unknown")}),fe("raw"),ce(""),Y(`Trace opened: ${x}`)}catch(z){const P=z instanceof Error?z.message:String(z);ce(`Trace: ${P}`),Y(`Trace open error ${x}: ${P}`)}}async function w(){try{const x=await Ie.startRun();g(x.run.runId),Y(`Run started: ${x.run.runId}`),Y("Tip: start run does not execute normalize by itself. Use 'Run eval v2.0.2' button."),await ot()}catch(x){Y(`Run start error: ${x instanceof Error?x.message:String(x)}`)}}async function F(){if(L)try{await Ie.finishRun(L),Y(`Run finished: ${L}`),await ot()}catch(x){Y(`Run finish error: ${x instanceof Error?x.message:String(x)}`)}}async function ve(){le(!0),ce("");try{Y("Starting eval in v2 contour.");const x=E.batchQuestionsRaw.trim()||E.userQuestion.trim();if(!x)throw new Error("Fill batch field or Raw user question first.");const z=await Ie.runEval({connection:i,prompts:p,promptVersion:"normalizer_v2_0_2",mode:"single-pass-strict",rawQuestions:x,useMock:xe});re(z.report),Y("Eval v2.0.2 run finished.");const P=z.report;if(P.run_id&&Y(`Eval run id: ${P.run_id}`),P.metrics){const pe=P.metrics;Y(`Eval metrics v2.0.2: schema=${pe.schema_validation_pass_rate??"n/a"}%, route_accuracy=${pe.route_resolution_accuracy??"n/a"}%, no_route_precision=${pe.no_route_precision??"n/a"}%, state_consistency=${pe.execution_state_consistency_rate??"n/a"}%`)}await pt()}catch(x){const z=x instanceof Error?x.message:String(x);z.includes("Legacy eval runner supports normalized_query_v1 only")?(re({status:"plan_only",prompt_version:"normalizer_v2",reason:"backend eval runner is still legacy-v1 only",plan_file:"reports/v2_pilot_eval_plan.md",next_steps:["run cheap mock sanity for schema/fragment/scope","run small real batch (10-15 messages, temperature=0)","run challenge-30 replay with v2 metrics"]}),Y("Backend is legacy-only for eval right now. Showing v2 pilot plan.")):(ce(`Eval: ${z}`),Y(`Eval run error: ${z}`))}finally{le(!1)}}async function Bt(){try{const x=JSON.stringify(ae??{},null,2);await navigator.clipboard.writeText(x),Y("Eval report copied to clipboard.")}catch(x){Y(`Eval report copy error: ${x instanceof Error?x.message:String(x)}`)}}function Ht(){Ve(""),En([]),Pn(""),Ut(""),Tt(""),Y("Assistant session reset.")}async function nn(){const x=dr.trim();if(!x)return;ge(!0),Tt(""),Pn(""),En(P=>[...P,{message_id:`local-${Date.now()}`,session_id:Je||"pending",role:"user",text:x,reply_type:null,created_at:new Date().toISOString(),trace_id:null,debug:null}]);const z=tn();try{const P=await Ie.sendAssistantMessage({connection:i,prompts:p,userMessage:x,sessionId:Je||void 0,promptVersion:kc,useMock:xe});Ve(P.session_id),En(P.conversation),Ut("Ответ готов"),Y(`Assistant reply received: trace=${P.debug.trace_id}`)}catch(P){const pe=P instanceof Error?P.message:String(P);Tt(pe),Ut("Ошибка ассистента"),Y(`Assistant error: ${pe}`)}finally{z(),ge(!1)}}return S.useEffect(()=>{if(!L){ee([]);return}Ie.runTrace(L).then(x=>ee(x.items)).catch(x=>Y(`Run trace error: ${x instanceof Error?x.message:String(x)}`))},[L]),o.jsxs("main",{className:`app-root ${ke==="assistant"||ke==="decomposition"||ke==="autoruns"?"app-root-autoruns":""}`,children:[o.jsxs("header",{className:"app-topbar",children:[o.jsxs("div",{className:"mode-switch-row",children:[o.jsx("button",{type:"button",className:ke==="assistant"?"tab active":"tab",onClick:()=>zt("assistant"),children:"Ассистент"}),o.jsx("button",{type:"button",className:ke==="decomposition"?"tab active":"tab",onClick:()=>zt("decomposition"),children:"Декомпозиция"}),o.jsx("button",{type:"button",className:ke==="autoruns"?"tab active":"tab",onClick:()=>zt("autoruns"),children:"История автопрогонов"}),o.jsx("button",{type:"button",className:"tab",onClick:Nr,children:"Сохранить"})]}),ke==="assistant"?o.jsxs("div",{className:"mode-switch-row mode-switch-row-right",children:[o.jsx("button",{type:"button",className:Et?"tab active":"tab",onClick:()=>Dt(x=>!x),children:"LLM Connector"}),o.jsx("button",{type:"button",className:Yt?"tab active":"tab",onClick:()=>Fn(x=>!x),children:"Prompt Manager"}),o.jsx("button",{type:"button",className:dn?"tab active":"tab",onClick:()=>Xt(x=>!x),children:"Режим ассистента"}),o.jsx("button",{type:"button",className:fn?"tab active":"tab",onClick:()=>Nn(x=>!x),children:"SAM"})]}):ke==="decomposition"?o.jsxs("div",{className:"mode-switch-row mode-switch-row-right",children:[o.jsx("button",{type:"button",className:$t?"tab active":"tab",onClick:()=>Un(x=>!x),children:"LLM"}),o.jsx("button",{type:"button",className:Ee?"tab active":"tab",onClick:()=>pn(x=>!x),children:"Prompt"}),o.jsx("button",{type:"button",className:Pt?"tab active":"tab",onClick:()=>cr(x=>!x),children:"Запрос"}),o.jsx("button",{type:"button",className:gt?"tab active":"tab",onClick:()=>de(x=>!x),children:"Выход"}),o.jsx("button",{type:"button",className:We?"tab active":"tab",onClick:()=>mn(x=>!x),children:"Метрики"}),o.jsx("button",{type:"button",className:Rt?"tab active":"tab",onClick:()=>Zt(x=>!x),children:"История"}),o.jsx("button",{type:"button",className:st?"tab active":"tab",onClick:()=>Ft(x=>!x),children:"NDC Run Monitor"})]}):ke==="autoruns"?o.jsxs("div",{className:"mode-switch-row mode-switch-row-right",children:[o.jsx("button",{type:"button",className:wn?"tab active":"tab",onClick:()=>cn(x=>!x),children:"Режим ассистента"}),o.jsx("button",{type:"button",className:Nt?"tab active":"tab",onClick:()=>Jt(x=>!x),children:"Режим декомпозиции"}),o.jsx("button",{type:"button",className:jn?"tab active":"tab",onClick:()=>bt(x=>!x),children:"Прогресс/регресс"}),o.jsx("button",{type:"button",className:kn?"tab active":"tab",onClick:()=>Cn(x=>!x),children:"Комментарии"})]}):null]}),ke==="assistant"?o.jsx("div",{className:"layout-grid layout-grid-mode-columns",children:o.jsxs("div",{className:"mode-columns",children:[Et?o.jsx("div",{className:"mode-col",children:o.jsx(xc,{value:i,modelOptions:se,modelsBusy:X,onChange:h,onReloadModels:Rn,onSaveLocalConfig:yt,onTestConnection:fr,lastStatus:Le,busy:ye||hn})}):null,Yt?o.jsx("div",{className:"mode-col mode-col-wide",children:o.jsx(_c,{value:p,onChange:O,presets:$e,selectedPresetId:Ge,onSelectPreset:Ze,onLoadPreset:Bn,onSavePreset:Hn,onResetDefaults:Vn,onDiffPrevious:pr,presetName:ft,onPresetNameChange:Ct,diffSummary:Be})}):null,dn?o.jsx("div",{className:"mode-col mode-col-xwide",children:o.jsx(Bf,{sessionId:Je,conversation:je,inputValue:dr,onInputChange:Pn,useMock:xe,onUseMockChange:me,onSend:nn,onClear:Ht,busy:hn,statusText:en,errorMessage:vt})}):null,fn?o.jsx("div",{className:"mode-col",children:o.jsx(Cf,{sessionId:Je,conversation:je,statusText:en,errorMessage:vt,useMock:xe,appLogs:q})}):null,!Et&&!Yt&&!dn&&!fn?o.jsx("div",{className:"mode-columns-empty",children:"Все панели режима ассистента скрыты. Включите нужные блоки справа в шапке."}):null]})}):ke==="decomposition"?o.jsx("div",{className:"layout-grid layout-grid-mode-columns",children:o.jsxs("div",{className:"mode-columns",children:[$t?o.jsx("div",{className:"mode-col",children:o.jsx(xc,{value:i,modelOptions:se,modelsBusy:X,onChange:h,onReloadModels:Rn,onSaveLocalConfig:yt,onTestConnection:fr,lastStatus:Le,busy:ye})}):null,Ee?o.jsx("div",{className:"mode-col mode-col-wide",children:o.jsx(_c,{value:p,onChange:O,presets:$e,selectedPresetId:Ge,onSelectPreset:Ze,onLoadPreset:Bn,onSavePreset:Hn,onResetDefaults:Vn,onDiffPrevious:pr,presetName:ft,onPresetNameChange:Ct,diffSummary:Be})}):null,Pt?o.jsx("div",{className:"mode-col",children:o.jsx(qf,{value:E,onChange:D,onApplyBatchFormat:u,onNormalize:Er,busy:ye,useMock:xe,onUseMockChange:me,errorMessage:he})}):null,gt?o.jsx("div",{className:"mode-col mode-col-xwide",children:o.jsx(Kf,{tab:H,onTabChange:fe,result:K,appLogs:q})}):null,We?o.jsx("div",{className:"mode-col",children:o.jsx(Vf,{result:K})}):null,Rt?o.jsx("div",{className:"mode-col",children:o.jsx(Hf,{items:B,onRefresh:pt,onOpenTrace:f})}):null,st?o.jsx("div",{className:"mode-col mode-col-xwide",children:o.jsx(Gf,{runs:A,selectedRunId:L,onSelectRun:g,onStartRun:w,onFinishRun:F,onRefreshRuns:ot,onRunEval:ve,onCopyEvalReport:Bt,evalBusy:oe,traceItems:j,evalReport:ae})}):null,!$t&&!Ee&&!Pt&&!gt&&!We&&!Rt&&!st?o.jsx("div",{className:"mode-columns-empty",children:"Все панели режима декомпозиции скрыты. Включите нужные блоки справа в шапке."}):null]})}):o.jsx("div",{className:"layout-grid layout-grid-autoruns",children:o.jsx(If,{connection:i,prompts:p,assistantPromptVersion:kc,decompositionPromptVersion:Ri,showAssistantMode:wn,showDecompositionMode:Nt,showProgressMode:jn,showCommentsMode:kn,onLog:Y})})]})}wf.createRoot(document.getElementById("root")).render(o.jsx(hf.StrictMode,{children:o.jsx(np,{})})); diff --git a/llm_normalizer/frontend/dist/assets/index-qw2gR5-7.css b/llm_normalizer/frontend/dist/assets/index-qw2gR5-7.css deleted file mode 100644 index 6514c42..0000000 --- a/llm_normalizer/frontend/dist/assets/index-qw2gR5-7.css +++ /dev/null @@ -1 +0,0 @@ -@import"https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;700;800&family=Space+Grotesk:wght@500;700&display=swap";:root{--rgb-background: 16, 16, 19;--rgb-surface-main: 26, 26, 31;--rgb-surface-horizontal: 32, 32, 38;--rgb-surface-focus: 40, 40, 47;--rgb-active: 228, 142, 92;--rgb-active-text: 18, 18, 18;--rgb-text-main: 240, 240, 240;--rgb-text-muted: 166, 166, 170;--rgb-danger: 255, 126, 126;--rgb-scrollbar-track: 31, 31, 36;--rgb-scrollbar-thumb: 74, 74, 82;--rgb-scrollbar-thumb-hover: 90, 90, 100;--mode-column-width: 440px;--mode-toggle-width: 188px;--bg-main: rgb(var(--rgb-background));--bg-soft: rgb(var(--rgb-surface-main));--bg-panel: rgb(var(--rgb-surface-main));--bg-panel-accent: rgb(var(--rgb-surface-horizontal));--surface-horizontal: rgb(var(--rgb-surface-horizontal));--surface-focus: rgb(var(--rgb-surface-focus));--line: transparent;--line-strong: rgba(var(--rgb-active), .48);--text-main: rgb(var(--rgb-text-main));--text-muted: rgb(var(--rgb-text-muted));--lime-main: rgb(var(--rgb-text-main));--lime-press: rgb(var(--rgb-text-main));--danger: rgb(var(--rgb-danger));--radius-lg: 20px;--radius-md: 14px;--shadow: none;--autoruns-col-width: var(--mode-column-width)}*{box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(var(--rgb-scrollbar-thumb)) rgb(var(--rgb-scrollbar-track))}*::-webkit-scrollbar{width:10px;height:10px}*::-webkit-scrollbar-track{background:rgb(var(--rgb-scrollbar-track))}*::-webkit-scrollbar-thumb{background:rgb(var(--rgb-scrollbar-thumb));border-radius:999px;border:2px solid rgb(var(--rgb-scrollbar-track))}*::-webkit-scrollbar-thumb:hover{background:rgb(var(--rgb-scrollbar-thumb-hover))}html,body,#root{margin:0;min-height:100dvh;font-family:Manrope,Segoe UI,sans-serif;background:var(--bg-main);color:var(--text-main)}.app-root{max-width:1720px;margin:0 auto;padding:12px 16px 16px}.app-root.app-root-autoruns{max-width:none;width:100%;min-height:100dvh;max-height:100dvh;display:flex;flex-direction:column;overflow:hidden}.app-topbar{display:flex;align-items:center;justify-content:space-between;gap:12px;margin:0 0 12px;padding:0;min-height:38px}.layout-grid{display:grid;grid-template-columns:repeat(12,1fr);gap:16px}.layout-grid.layout-grid-autoruns,.layout-grid.layout-grid-mode-columns{min-height:0;flex:1 1 auto;grid-template-columns:minmax(0,1fr)}.mode-switch-row{display:flex;gap:8px;margin:0;padding:0}.mode-switch-row.mode-switch-row-right{margin-left:auto;justify-content:flex-end;max-width:72%;overflow-x:auto;overflow-y:hidden;padding-bottom:2px}.mode-switch-row .tab{white-space:nowrap}.mode-switch-row.mode-switch-row-right .tab{width:var(--mode-toggle-width);min-width:var(--mode-toggle-width);text-align:center}.mode-columns{display:flex;gap:12px;width:100%;min-height:0;flex:1 1 auto;overflow-x:auto;overflow-y:hidden;padding-bottom:4px}.mode-col{flex:0 0 var(--mode-column-width);width:var(--mode-column-width);min-height:0;height:100%;display:flex}.mode-col.mode-col-wide,.mode-col.mode-col-xwide{flex-basis:var(--mode-column-width);width:var(--mode-column-width)}.mode-col .panel-frame{width:100%;height:100%}.mode-col .panel-body{min-height:0;overflow:auto}.mode-columns-empty{min-width:360px;border-radius:14px;background:rgb(var(--rgb-surface-main));color:var(--text-muted);padding:14px}.panel-frame{grid-column:span 12;border:none;border-radius:var(--radius-lg);background:var(--bg-panel);overflow:hidden;box-shadow:none;animation:rise .4s ease-out;display:flex;flex-direction:column;min-height:0}.panel-header{display:flex;align-items:flex-start;justify-content:space-between;gap:14px;padding:14px 18px 10px;border-bottom:none;background:var(--bg-panel-accent)}.panel-header h2{margin:0;font-size:1.02rem;letter-spacing:.02em}.panel-header p{margin:6px 0 0;font-size:.85rem;color:var(--text-muted)}.panel-body{padding:10px 12px 12px;min-height:0}.app-root-autoruns .autoruns-frame{height:100%}.app-root-autoruns .autoruns-frame .panel-body{flex:1 1 auto;overflow:hidden;display:flex;flex-direction:column;gap:10px;padding:10px 12px 12px;background:rgb(var(--rgb-background))}.status-chip{border:none;border-radius:999px;padding:4px 10px;color:var(--lime-main);font-size:.78rem;background:rgb(var(--rgb-surface-focus))}.assistant-panel-actions{display:flex;align-items:center;justify-content:flex-end;flex-wrap:wrap;gap:8px}.assistant-copy-btn{background:transparent;border-color:transparent;color:var(--text-main);box-shadow:none;transform:none}.assistant-copy-btn:hover{background:rgb(var(--rgb-surface-focus));filter:none;box-shadow:none;transform:none}.assistant-copy-feedback{font-size:.76rem;color:var(--text-muted)}.assistant-copy-feedback.success{color:var(--lime-main)}.assistant-copy-feedback.error{color:var(--danger)}input,select,textarea,button{font-family:Manrope,sans-serif}label{display:flex;flex-direction:column;gap:6px;color:var(--text-muted);font-size:.84rem}input,select,textarea{border:none;border-radius:var(--radius-md);background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:10px 12px;outline:none;transition:background-color .18s ease}input:focus,select:focus,textarea:focus{border-color:transparent;box-shadow:none;outline:none;background:rgb(var(--rgb-surface-focus))}textarea{resize:vertical;min-height:86px}button{border:none;border-radius:999px;background:rgb(var(--rgb-surface-horizontal));color:rgb(var(--rgb-text-main));font-weight:700;font-size:.83rem;letter-spacing:.02em;cursor:pointer;padding:9px 14px;transition:background .2s ease,color .2s ease;outline:none;box-shadow:none}button:hover{border-color:transparent;background:rgb(var(--rgb-surface-focus))}button:disabled{opacity:.52;cursor:not-allowed}.button-row{display:flex;flex-wrap:wrap;align-items:center;gap:10px;margin-top:12px}.checkbox-row{flex-direction:row;align-items:center;gap:8px;color:var(--text-main)}.grid-two{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:12px}.prompt-manager-grid{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}.full-width{grid-column:1 / -1}.tab-row{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:12px}.tab{background:rgb(var(--rgb-surface-main));color:var(--text-main);border:none}.tab.active{border-color:transparent;background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.assistant-chat-list{max-height:420px;overflow:auto;display:grid;gap:10px;padding:4px;border:none;border-radius:var(--radius-md);background:rgb(var(--rgb-surface-horizontal))}.assistant-empty{padding:18px;text-align:center}.assistant-msg{border:none;border-radius:12px;background:rgb(var(--rgb-surface-main));padding:10px}.assistant-msg.user{border-color:transparent;background:rgb(var(--rgb-surface-focus))}.assistant-msg.assistant{border-color:transparent}.assistant-msg-head{display:flex;justify-content:space-between;gap:8px;margin-bottom:6px;font-size:.78rem;color:var(--text-muted)}.assistant-msg-body{white-space:pre-wrap;line-height:1.45;font-size:.9rem}.assistant-trace{margin-top:6px;color:var(--text-muted);font-size:.75rem}.assistant-debug{margin-top:8px}.assistant-debug summary{cursor:pointer;color:var(--lime-main);font-size:.8rem}.assistant-compose{margin-top:12px;display:grid;gap:10px}.json-view{margin:0;width:100%;min-height:180px;max-height:420px;overflow:auto;background:rgb(var(--rgb-surface-horizontal));border:none;border-radius:var(--radius-md);padding:12px;color:rgb(var(--rgb-text-main));font-family:JetBrains Mono,Consolas,monospace;font-size:.78rem;line-height:1.45}.metrics-grid{display:grid;grid-template-columns:repeat(5,minmax(0,1fr));gap:10px}.metrics-grid div{background:rgba(var(--rgb-surface-main),.8);border:none;border-radius:12px;padding:10px;display:flex;flex-direction:column;gap:4px}.metrics-grid span{color:var(--text-muted);font-size:.75rem}.metrics-grid strong{font-size:.84rem;color:var(--lime-main)}.history-list{display:grid;gap:8px;max-height:340px;overflow:auto}.history-item{width:100%;text-align:left;border-radius:12px;border:none;background:rgb(var(--rgb-surface-main));color:var(--text-main);padding:10px}.history-item p{margin:8px 0;color:var(--text-muted);font-size:.82rem}.history-item.selected{border-color:var(--line-strong)}.history-row{display:flex;justify-content:space-between;gap:8px;font-size:.76rem;color:var(--text-muted)}.runtime-grid{display:grid;grid-template-columns:1.2fr 1fr;gap:12px}.runtime-stack{display:grid;grid-template-columns:minmax(0,1fr);gap:12px}.runtime-details{display:grid;gap:12px}.runtime-runs{max-height:360px;overflow:auto;display:grid;gap:8px}.eval-report-wrap{position:relative}.copy-cube-button{position:absolute;right:10px;bottom:10px;width:34px;height:34px;border-radius:10px;padding:0;min-width:34px;display:grid;place-items:center;font-size:.92rem;line-height:1}.muted{color:var(--text-muted)}.diff-summary{margin-top:10px;font-size:.82rem;color:var(--lime-main)}.error-text{margin-top:10px;color:var(--danger);font-size:.84rem}.autoruns-columns{display:flex;gap:12px;width:100%;min-height:0;flex:1 1 auto;overflow-x:auto;overflow-y:hidden;padding-bottom:4px}.autoruns-col{flex:0 0 var(--mode-column-width);width:var(--mode-column-width);height:100%;min-height:0;overflow:auto;border:none;border-radius:14px;background:rgb(var(--rgb-surface-main));padding:12px;scrollbar-gutter:stable}.autoruns-col h3{margin:0;font-size:.95rem}.autoruns-col h4{margin:12px 0 8px;font-size:.82rem;color:var(--text-muted)}.autoruns-col-header{position:sticky;top:-12px;z-index:8;margin:-12px -12px 10px;padding:12px 12px 10px;background:rgb(var(--rgb-surface-main))}.autoruns-col-header .tab-row{margin:8px 0 0}.autoruns-col-header .autoruns-dialog-toolbar{margin-top:8px}.autoruns-form-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.autoruns-meta-list{display:grid;gap:8px}.autoruns-meta-list>div{display:flex;justify-content:space-between;gap:8px;border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px 9px;font-size:.79rem}.autoruns-meta-list span{color:var(--text-muted)}.autoruns-prompt-details summary{cursor:pointer;color:var(--text-main);font-size:.8rem;margin-bottom:8px}.autoruns-prompt-details textarea{min-height:68px}.autoruns-stats-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px;margin-bottom:10px}.autoruns-stats-grid>div{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px;display:grid;gap:3px}.autoruns-stats-grid span{color:var(--text-muted);font-size:.74rem}.autoruns-stats-grid strong{color:var(--lime-main);font-size:.84rem}.autoruns-run-list{display:grid;gap:8px;max-height:none;min-height:0;flex:1 1 auto;overflow:auto;padding-right:2px}.autoruns-run-item{width:100%;text-align:left;border-radius:12px;border:none;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:10px;display:grid;gap:5px;transition:background-color .2s ease,box-shadow .2s ease}.autoruns-run-item.selected{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text));box-shadow:none}.autoruns-run-item.selected .autoruns-run-meta{color:rgba(var(--rgb-active-text),.95)}.autoruns-run-head,.autoruns-run-foot{display:flex;justify-content:space-between;gap:8px;font-size:.76rem}.autoruns-run-meta{color:var(--text-muted);font-size:.75rem;word-break:break-word}.autoruns-dialog-toolbar{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:8px}.autoruns-case-list{margin-top:8px;display:grid;gap:6px;max-height:180px;overflow:auto}.autoruns-case-item{width:100%;text-align:left;border-radius:10px;border:none;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:7px 8px;display:flex;justify-content:space-between;gap:6px;font-size:.76rem}.autoruns-case-item.selected{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text));box-shadow:none}.autoruns-dialog-view{margin-top:10px;border:none;border-radius:12px;background:rgb(var(--rgb-surface-horizontal));padding:10px;max-height:none;min-height:0;flex:1 1 auto;overflow:auto;display:grid;gap:8px}.autoruns-msg{border:none;border-radius:12px;background:rgb(var(--rgb-surface-focus));padding:8px 10px;display:grid;gap:6px}.autoruns-msg header,.autoruns-msg footer{display:flex;justify-content:space-between;gap:8px;font-size:.74rem;color:var(--text-muted)}.autoruns-msg-head-actions{display:flex;align-items:center;gap:8px}.autoruns-msg-case-tag{display:inline-flex;align-items:center;border-radius:999px;padding:2px 8px;font-size:.7rem;line-height:1;color:rgb(var(--rgb-active-text));background:rgba(var(--rgb-active),.24)}.autoruns-msg p{margin:0;white-space:pre-wrap;line-height:1.35;font-size:.84rem}.autoruns-comment-icon{border:none;background:transparent;color:rgb(var(--rgb-text-main));border-radius:0;min-width:20px;min-height:20px;width:20px;height:20px;padding:0;line-height:1;box-shadow:none;transform:none;display:inline-flex;align-items:center;justify-content:center}.autoruns-comment-icon:hover{background:transparent;color:rgb(var(--rgb-active));box-shadow:none;transform:none}.autoruns-comment-icon.commented{color:rgb(var(--rgb-active-text));background:transparent;box-shadow:none}.comment-icon-svg{width:20px;height:20px;stroke:currentColor;stroke-width:1.75;stroke-linecap:round;stroke-linejoin:round;fill:none}.comment-icon-svg .comment-icon-dot{fill:currentColor}.comment-icon-svg.commented{fill:rgb(var(--rgb-active));stroke:rgb(var(--rgb-active))}.autoruns-comment-icon.commented .comment-icon-dot{fill:rgb(var(--rgb-active-text))}.autoruns-resolve-toggle{border:none;background:rgb(var(--rgb-surface-focus));color:rgb(var(--rgb-text-main));border-radius:999px;width:22px;height:22px;min-width:22px;min-height:22px;padding:0;display:inline-flex;align-items:center;justify-content:center;box-shadow:none;transform:none}.autoruns-resolve-toggle:hover{background:rgb(var(--rgb-surface-focus));color:rgb(var(--rgb-active));box-shadow:none;transform:none}.autoruns-resolve-toggle:disabled{opacity:.55;cursor:wait}.autoruns-resolve-toggle.resolved{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.resolve-icon-svg{width:14px;height:14px;fill:none;stroke:currentColor;stroke-width:1.8;stroke-linecap:round;stroke-linejoin:round}.resolve-icon-svg.resolved{fill:currentColor}.autoruns-msg-annotation{display:grid;gap:4px;border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:7px 8px;font-size:.78rem}.autoruns-comments-list{display:grid;gap:8px;max-height:none;min-height:0;flex:1 1 auto;overflow:auto;padding-right:2px;margin-top:6px}.autoruns-autogen-list{display:grid;gap:8px;max-height:none;min-height:0;overflow:auto;padding-right:2px}.autoruns-autogen-item{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px;display:grid;gap:5px;cursor:pointer}.autoruns-autogen-item.selected{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.autoruns-autogen-item.selected .autoruns-run-meta,.autoruns-autogen-item.selected p{color:rgba(var(--rgb-active-text),.95)}.autoruns-autogen-item header{display:flex;justify-content:space-between;gap:8px;font-size:.76rem}.autoruns-autogen-item p{margin:0;color:var(--text-muted);white-space:pre-wrap;font-size:.8rem}.autoruns-generated-questions{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px;display:grid;gap:8px}.autoruns-generated-questions-head{display:flex;align-items:center;justify-content:space-between;gap:8px}.autoruns-generated-questions-list{display:grid;gap:6px;max-height:220px;overflow:auto;padding-right:2px}.autoruns-generated-question-item{display:flex;align-items:flex-start;justify-content:space-between;gap:8px;border:none;border-radius:9px;background:rgb(var(--rgb-surface-focus));padding:6px 8px;font-size:.78rem}.autoruns-generated-question-item span{white-space:pre-wrap}.autoruns-remove-question-btn{flex:0 0 auto;border:none;border-radius:7px;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);min-width:24px;height:24px;font-size:.75rem;line-height:1}.autoruns-comment-item{width:100%;text-align:left;border-radius:12px;border:none;background:rgb(var(--rgb-surface-horizontal));color:var(--text-main);padding:9px;display:grid;gap:6px;cursor:pointer}.autoruns-comment-item p{margin:0;white-space:pre-wrap;color:var(--text-muted);font-size:.79rem}.autoruns-comment-item.selected{background:rgb(var(--rgb-active));color:rgb(var(--rgb-active-text))}.autoruns-comment-item.selected p,.autoruns-comment-item.selected .autoruns-run-meta,.autoruns-comment-item.selected .muted{color:rgba(var(--rgb-active-text),.94)}.autoruns-comment-item.selected .autoruns-resolve-toggle{background:rgba(var(--rgb-active-text),.18);color:rgb(var(--rgb-active-text))}.autoruns-comment-head{display:flex;justify-content:space-between;align-items:center;gap:8px;font-size:.75rem}.autoruns-comment-head-actions{display:inline-flex;align-items:center;gap:8px}.autoruns-comment-filter-row{display:grid;grid-template-columns:minmax(0,1fr) auto;align-items:end;gap:10px;margin-bottom:8px}.autoruns-resolved-filter-toggle{min-height:38px;white-space:nowrap}.autoruns-msg.assistant{margin-right:12%}.autoruns-msg.user{margin-left:12%;border-color:transparent;background:rgb(var(--rgb-surface-focus))}.autoruns-decomposition-list{margin:0;padding-left:18px;display:grid;gap:7px;font-size:.8rem}.autoruns-comment-modal-backdrop{position:fixed;inset:0;background:rgba(var(--rgb-background),.74);display:grid;place-items:center;z-index:1800;padding:12px}.autoruns-comment-modal{width:min(660px,100%);border:none;border-radius:16px;background:rgb(var(--rgb-surface-horizontal));box-shadow:var(--shadow);padding:14px;display:grid;gap:10px}.autoruns-comment-modal h3{margin:0;font-size:.95rem}.autoruns-comment-quote{margin:0;border:none;border-radius:10px;background:rgb(var(--rgb-surface-focus));padding:8px;white-space:pre-wrap;max-height:150px;overflow:auto;font-size:.82rem}.autoruns-rating-row{display:flex;gap:8px}.autoruns-rating-dot{width:34px;height:34px;border-radius:999px;padding:0;border:none;background:rgb(var(--rgb-surface-focus));color:var(--text-muted);font-size:.95rem;box-shadow:none;transform:none}.autoruns-rating-dot:hover{border-color:var(--line-strong);box-shadow:none;transform:none}.autoruns-rating-dot.active{border-color:var(--line-strong);color:rgb(var(--rgb-active-text));background:rgb(var(--rgb-active))}.autoruns-coverage-list{display:grid;gap:8px}.autoruns-coverage-item{border:none;border-radius:10px;background:rgb(var(--rgb-surface-horizontal));padding:8px}.autoruns-coverage-head{display:flex;justify-content:space-between;gap:8px;font-size:.76rem;margin-bottom:5px}.autoruns-coverage-head span{color:var(--text-muted)}.autoruns-coverage-bar{height:7px;border-radius:999px;background:rgb(var(--rgb-surface-focus));overflow:hidden}.autoruns-coverage-bar>div{height:100%;border-radius:999px;background:rgb(var(--rgb-active))}@media(max-width:1200px){:root{--mode-column-width: 400px}.metrics-grid{grid-template-columns:repeat(3,minmax(0,1fr))}}@media(max-width:920px){:root{--mode-column-width: 360px}.grid-two,.runtime-grid,.runtime-stack{grid-template-columns:1fr}.metrics-grid{grid-template-columns:repeat(2,minmax(0,1fr))}.autoruns-form-grid,.autoruns-dialog-toolbar,.autoruns-stats-grid,.autoruns-comment-filter-row{grid-template-columns:1fr}}@media(max-width:640px){:root{--mode-column-width: 320px}.app-root{padding:18px 12px 24px}.metrics-grid{grid-template-columns:1fr}}@keyframes rise{0%{opacity:0;transform:translateY(8px)}to{opacity:1;transform:translateY(0)}} diff --git a/llm_normalizer/frontend/dist/index.html b/llm_normalizer/frontend/dist/index.html index aebbd33..23a795d 100644 --- a/llm_normalizer/frontend/dist/index.html +++ b/llm_normalizer/frontend/dist/index.html @@ -4,8 +4,8 @@ NDC AI Normalizer Playground - - + +
diff --git a/llm_normalizer/frontend/src/api/client.ts b/llm_normalizer/frontend/src/api/client.ts index 4223466..c35b5d1 100644 --- a/llm_normalizer/frontend/src/api/client.ts +++ b/llm_normalizer/frontend/src/api/client.ts @@ -160,6 +160,7 @@ export const apiClient = { rawQuestions?: string; evalTarget?: "normalizer" | "assistant_stage1" | "assistant_stage2" | "assistant_p0"; compareWithReportFile?: string; + analysisDate?: string; }): Promise<{ ok: boolean; report: unknown }> { return request("/eval/run", { method: "POST", @@ -183,7 +184,8 @@ export const apiClient = { caseSetFile: input.caseSetFile, rawQuestions: input.rawQuestions, eval_target: input.evalTarget, - compare_with_report_file: input.compareWithReportFile + compare_with_report_file: input.compareWithReportFile, + analysis_date: input.analysisDate }) }); }, @@ -200,6 +202,7 @@ export const apiClient = { evalTarget?: "normalizer" | "assistant_stage1" | "assistant_stage2" | "assistant_p0"; compareWithReportFile?: string; questions?: string[]; + analysisDate?: string; }): Promise { return request("/eval/run-async/start", { method: "POST", @@ -224,7 +227,8 @@ export const apiClient = { rawQuestions: input.rawQuestions, eval_target: input.evalTarget, compare_with_report_file: input.compareWithReportFile, - questions: input.questions + questions: input.questions, + analysis_date: input.analysisDate }) }); }, diff --git a/llm_normalizer/frontend/src/components/AssistantPanel.tsx b/llm_normalizer/frontend/src/components/AssistantPanel.tsx index 18f105c..8c06072 100644 --- a/llm_normalizer/frontend/src/components/AssistantPanel.tsx +++ b/llm_normalizer/frontend/src/components/AssistantPanel.tsx @@ -75,15 +75,16 @@ export function AssistantPanel({ errorMessage }: AssistantPanelProps) { const listRef = useRef(null); + const stickToBottomRef = useRef(true); const copyResetTimerRef = useRef(null); const [copyState, setCopyState] = useState<"idle" | "success" | "error">("idle"); const [copyModeLabel, setCopyModeLabel] = useState<"чат" | "тех">("чат"); useEffect(() => { - if (listRef.current) { + if (listRef.current && stickToBottomRef.current) { listRef.current.scrollTop = listRef.current.scrollHeight; } - }, [conversation, statusText]); + }, [conversation]); useEffect(() => { return () => { @@ -111,85 +112,93 @@ export function AssistantPanel({ }, 2200); } - return ( - - - - {copyState === "success" ? Скопировано ({copyModeLabel}) : null} - {copyState === "error" ? Ошибка копирования : null} - {sessionId ? `session: ${sessionId}` : "новая сессия"} - - } - > -
- {conversation.length === 0 ? ( -
Диалог пуст. Отправьте первый вопрос, чтобы запустить контур ассистента.
- ) : null} - {conversation.map((item) => ( -
-
- {roleLabel(item.role)} - {shortTime(item.created_at)} -
-
{item.text}
- {item.role === "assistant" && item.debug ? ( -
- Показать технический разбор - -
- ) : null} -
- ))} -
+ function handleChatScroll(): void { + if (!listRef.current) return; + const node = listRef.current; + const distanceToBottom = node.scrollHeight - node.scrollTop - node.clientHeight; + stickToBottomRef.current = distanceToBottom < 16; + } -
-