diff --git a/docs/ARCH/11 - architecture_turnaround/21 - current_status_canon_2026-05-01.md b/docs/ARCH/11 - architecture_turnaround/21 - current_status_canon_2026-05-01.md index c3cbcce..04983ed 100644 --- a/docs/ARCH/11 - architecture_turnaround/21 - current_status_canon_2026-05-01.md +++ b/docs/ARCH/11 - architecture_turnaround/21 - current_status_canon_2026-05-01.md @@ -84,10 +84,11 @@ Fresh validation cut: - Completed autonomy slice inside that loop: `Accounting Profit-Margin Reviewed Route`: `accounting_profit_margin` is now promoted from `needs_route_enablement` into a reviewed 90/91/99 accounting-result route with accepted live replay. - Completed autonomy slice inside that loop: `Debt Due-Date Aging Reviewed Route`: `debt_due_date_aging_quality` is now promoted from proxy-only route-candidate gap into a reviewed payment-term/open-balance route with accepted live replay. - Completed autonomy slice inside that loop: `Vendor/Procurement Quality Reviewed Route`: `vendor_risk_procurement_quality` now promotes to reviewed procurement-concentration evidence when confirmed outgoing payment, bank-like recipient segregation, non-financial recipient, counterparty-role, and contract-usage signals are reachable; phase95 live replay is accepted. -- Current live canary: `phase95_vendor_procurement_quality_reviewed_route_live2` accepted `7/7`. -- Current accepted autorun: `AGENT | Phase 95 vendor/procurement quality reviewed route` (`gen-ag05121357-9ea5d6`). +- Completed autonomy slice inside that loop: `Inventory Reserve/Liquidation Quality Reviewed Route`: `inventory_reserve_liquidation_quality` now promotes to reviewed inventory quality-event evidence from posted write-off, receipt-adjustment, stocktaking, and revaluation documents; phase96 live replay is accepted. +- Current live canary: `phase96_inventory_reserve_liquidation_quality_rerun` accepted `2/2`. +- Current accepted autorun: `AGENT | Phase 96 inventory reserve/liquidation quality-events` (`gen-ag05122057-c9786e`). - Implementation breadth: `~99% (Open-World Bounded Autonomy Breadth through Slice 25)`. -- Next active slice: select the remaining phase92 proof family `inventory_reserve_liquidation_quality`. +- Next active slice: start the broader open-world schema/primitive discovery module and use phase91-phase96 as regression canaries. - Active module progress: `~99% (Agentic Semantic Development Loop, accepted dogfood loop + autorun hygiene; manual GUI confirmation still required)`. ## Reporting Rule @@ -99,7 +100,7 @@ Use these labels when reporting progress: - `Прогресс модуля: 99% (Open-World Bounded Autonomy Breadth, active slice: Semantic Control Gate)` while discussing current module closure after the EHMO-derived critical subset accepted live again with W5/W7 hardening. - `Прогресс модуля: 99% (Agentic Semantic Development Loop, accepted dogfood loop + autorun hygiene; manual GUI confirmation still required)` when discussing the current development-loop operating layer. - `Прогресс модуля: 100% (Open-World Route Candidate Promotion, declared phase90 slice accepted)` when discussing the route-candidate handoff slice itself. -- `Прогресс модуля: 92% (Route-Candidate-Driven Enablement Loop, active slice: third reviewed proof-family route accepted)` when discussing the current candidate-driven enablement loop. +- `Прогресс модуля: 100% (Route-Candidate-Driven Enablement Loop, final reviewed proof-family route accepted; use as regression gate)` when discussing the current candidate-driven enablement loop. - `Open-World Business Overview implementation breadth: ~99%, Semantic Control Gate critical subset accepted, fat GUI pack still pending` when discussing only the already wired Slice 25 breadth. - `Прогресс модуля: X% (Open-World Bounded Autonomy Breadth, active slice: )` for later breadth work after the Semantic Control Gate is accepted. @@ -135,7 +136,7 @@ Remaining work belongs to the next breadth module: - confirm the latest autorun Cyrillic hygiene cut in the GUI after backend refresh and inspect frontend/API payloads if old replacement characters remain visible; - continue dogfooding the `Agentic Semantic Development Loop` on real stage packs, especially generated-question quality, semantic business audit, repair handoff, and rerun acceptance; - finish closure of the `Open-World Semantic Control Gate` opened by `assistant-stage1-EHMOy3lNFt`; the EHMO-derived critical subset is accepted live after W5/W7 hardening, but the fat GUI pack and residual answer-shape roughness still need final review; -- extend `business_overview` beyond money-flow/activity, customer and supplier concentration, document/account-section activity mix, counterparty role split, contract usage, yearly operating-flow dynamics, explicit profit/margin wording boundaries, explicit debt due-date wording boundaries, explicit inventory reserve/liquidation wording boundaries, explicit supplier/procurement-quality wording boundaries, explicit-period VAT/tax, as-of-date debt position, open-settlement concentration, contract-date debt age, debt staleness-risk proxy, as-of-date inventory position, trading-margin proxy, sales-to-stock inventory proxy, warehouse staleness-risk proxy, the missing-proof ledger, the reviewed accounting profit/margin route, the reviewed debt due-date aging route, and the reviewed vendor/procurement concentration route into confirmed reserve/write-off/liquidation inventory evidence families; +- extend open-world coverage beyond the reviewed `business_overview` families already wired for money-flow/activity, customer and supplier concentration, document/account-section activity mix, counterparty role split, contract usage, yearly operating-flow dynamics, explicit profit/margin, debt due-date aging, inventory reserve/liquidation quality events, supplier/procurement quality, explicit-period VAT/tax, as-of-date debt and inventory position, open-settlement concentration, contract-date debt age, staleness proxies, trading-margin proxy, sales-to-stock inventory proxy, the missing-proof ledger, and the phase93-phase96 reviewed routes; - broader dynamic schema traversal for unfamiliar 1C asks; - more primitive descriptors where live evidence proves a real gap; - more replay-backed domain packs that start from user business meaning, not from route convenience; @@ -156,17 +157,19 @@ For current planning, read: 1. `README.md` 2. this document -3. `29 - debt_due_date_aging_reviewed_route_2026-05-10.md` -4. `28 - accounting_profit_margin_reviewed_route_2026-05-10.md` -5. `27 - proof_family_enablement_candidates_2026-05-10.md` -6. `26 - route_candidate_driven_enablement_loop_2026-05-10.md` -7. `25 - open_world_route_candidate_promotion_2026-05-10.md` -8. `24 - agentic_semantic_development_loop_and_autorun_hygiene_2026-05-10.md` -9. `23 - current_execution_spine_and_semantic_control_gate_2026-05-05.md` -10. `22 - open_world_bounded_autonomy_breadth_2026-05-01.md` -11. `20 - planner_autonomy_consolidation_2026-05-01.md` -12. `19 - inventory_stock_open_world_breadth_proof_2026-05-01.md` -13. `17 - post_f_semantic_integrity_hardening_2026-04-23.md` -14. `16 - data_need_graph_and_open_world_mcp_plan_2026-04-22.md` +3. `31 - inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md` +4. `30 - vendor_procurement_quality_reviewed_route_2026-05-12.md` +5. `29 - debt_due_date_aging_reviewed_route_2026-05-10.md` +6. `28 - accounting_profit_margin_reviewed_route_2026-05-10.md` +7. `27 - proof_family_enablement_candidates_2026-05-10.md` +8. `26 - route_candidate_driven_enablement_loop_2026-05-10.md` +9. `25 - open_world_route_candidate_promotion_2026-05-10.md` +10. `24 - agentic_semantic_development_loop_and_autorun_hygiene_2026-05-10.md` +11. `23 - current_execution_spine_and_semantic_control_gate_2026-05-05.md` +12. `22 - open_world_bounded_autonomy_breadth_2026-05-01.md` +13. `20 - planner_autonomy_consolidation_2026-05-01.md` +14. `19 - inventory_stock_open_world_breadth_proof_2026-05-01.md` +15. `17 - post_f_semantic_integrity_hardening_2026-04-23.md` +16. `16 - data_need_graph_and_open_world_mcp_plan_2026-04-22.md` Documents `01` through `15` remain valuable, but mostly as the historical architecture trail. diff --git a/docs/ARCH/11 - architecture_turnaround/24 - agentic_semantic_development_loop_and_autorun_hygiene_2026-05-10.md b/docs/ARCH/11 - architecture_turnaround/24 - agentic_semantic_development_loop_and_autorun_hygiene_2026-05-10.md index 44d89ad..97f21bf 100644 --- a/docs/ARCH/11 - architecture_turnaround/24 - agentic_semantic_development_loop_and_autorun_hygiene_2026-05-10.md +++ b/docs/ARCH/11 - architecture_turnaround/24 - agentic_semantic_development_loop_and_autorun_hygiene_2026-05-10.md @@ -97,7 +97,7 @@ Status: - implementation state: operational dogfood loop exists and has an accepted first loop artifact; - semantic status: accepted loop artifact is useful, but manual GUI confirmation remains required; - hygiene status: saved autorun/runtime Cyrillic repair is covered by code/tests and the GUI-side check is reported clean; -- current autonomy status: `Open-World Route Candidate Promotion` is live-accepted at `5/5`; `Route-Candidate-Driven Enablement Loop` has accepted phase91/phase92 canaries; `accounting_profit_margin` is promoted into a reviewed 90/91/99 route by phase93; `debt_due_date_aging_quality` is promoted into a reviewed payment-term/open-balance route by phase94; both reviewed proof-family routes are saved as accepted AGENT autoruns; +- current autonomy status: `Open-World Route Candidate Promotion` is live-accepted at `5/5`; `Route-Candidate-Driven Enablement Loop` has accepted phase91/phase92 canaries; `accounting_profit_margin` is promoted into a reviewed 90/91/99 route by phase93; `debt_due_date_aging_quality` is promoted into a reviewed payment-term/open-balance route by phase94; `vendor_risk_procurement_quality` is promoted into reviewed procurement-concentration evidence by phase95; `inventory_reserve_liquidation_quality` is promoted into reviewed inventory quality-event evidence by phase96; all four reviewed proof-family routes are saved as accepted AGENT autoruns; - risk: medium, because the loop is now infrastructure for future acceptance decisions, not just a local route fix. Recommended reporting line: @@ -113,16 +113,16 @@ Still open: - the first accepted dogfood loop proves the mechanism, not all future stage packs; - generated question quality still needs pressure from real GUI runs and user feedback; - broad arbitrary 1C autonomy is still bounded by reviewed routes, truth gates, and replay evidence; -- route-candidate-driven enablement still needs more proof-family promotions after the accepted `accounting_profit_margin` and `debt_due_date_aging_quality` routes; +- route-candidate-driven enablement is now closed after the accepted phase93-phase96 proof-family routes, and should be treated as a regression gate rather than as an open promotion backlog; - manual GUI confirmation remains required before declaring a fat AGENT pack fully accepted. ## Next Work Next operational pass: -1. Use phase94 as the current candidate-driven enablement canary. +1. Use phase91-phase96 as current candidate-driven enablement canaries. 2. Continue dogfooding the stage-loop on real Open-World/agentic packs. -3. Pick the next proof family, likely vendor/procurement quality or inventory reserve/liquidation, and require the same live replay/save-after-acceptance discipline. +3. Move the active autonomy work to broader schema/primitive discovery and keep the same live replay/save-after-acceptance discipline. 4. Keep Post-F, phase83, inventory, business-overview, and mojibake autorun cases as regression canaries. ## Canonical Reading Order Update @@ -131,13 +131,15 @@ For current planning, read: 1. `README.md` 2. `21 - current_status_canon_2026-05-01.md` -3. `29 - debt_due_date_aging_reviewed_route_2026-05-10.md` -4. `28 - accounting_profit_margin_reviewed_route_2026-05-10.md` -5. `27 - proof_family_enablement_candidates_2026-05-10.md` -6. `26 - route_candidate_driven_enablement_loop_2026-05-10.md` -7. `25 - open_world_route_candidate_promotion_2026-05-10.md` -8. this document -9. `23 - current_execution_spine_and_semantic_control_gate_2026-05-05.md` -10. `22 - open_world_bounded_autonomy_breadth_2026-05-01.md` -11. `20 - planner_autonomy_consolidation_2026-05-01.md` -12. `17 - post_f_semantic_integrity_hardening_2026-04-23.md` +3. `31 - inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md` +4. `30 - vendor_procurement_quality_reviewed_route_2026-05-12.md` +5. `29 - debt_due_date_aging_reviewed_route_2026-05-10.md` +6. `28 - accounting_profit_margin_reviewed_route_2026-05-10.md` +7. `27 - proof_family_enablement_candidates_2026-05-10.md` +8. `26 - route_candidate_driven_enablement_loop_2026-05-10.md` +9. `25 - open_world_route_candidate_promotion_2026-05-10.md` +10. this document +11. `23 - current_execution_spine_and_semantic_control_gate_2026-05-05.md` +12. `22 - open_world_bounded_autonomy_breadth_2026-05-01.md` +13. `20 - planner_autonomy_consolidation_2026-05-01.md` +14. `17 - post_f_semantic_integrity_hardening_2026-04-23.md` diff --git a/docs/ARCH/11 - architecture_turnaround/26 - route_candidate_driven_enablement_loop_2026-05-10.md b/docs/ARCH/11 - architecture_turnaround/26 - route_candidate_driven_enablement_loop_2026-05-10.md index 946402c..930503d 100644 --- a/docs/ARCH/11 - architecture_turnaround/26 - route_candidate_driven_enablement_loop_2026-05-10.md +++ b/docs/ARCH/11 - architecture_turnaround/26 - route_candidate_driven_enablement_loop_2026-05-10.md @@ -52,6 +52,8 @@ Live semantic replay: - accepted user-runnable autorun for that route replay: `AGENT | Phase 94 debt due-date aging reviewed route` (`gen-ag05101319-c04f79`). - third proof-family route replay: `artifacts/domain_runs/phase95_vendor_procurement_quality_reviewed_route_live2`, `7/7` passed, `0` warnings, `0` failures. - accepted user-runnable autorun for that route replay: `AGENT | Phase 95 vendor/procurement quality reviewed route` (`gen-ag05121357-9ea5d6`). +- final proof-family route replay: `artifacts/domain_runs/phase96_inventory_reserve_liquidation_quality_rerun`, `2/2` passed, `0` warnings, `0` failures. +- accepted user-runnable autorun for that route replay: `AGENT | Phase 96 inventory reserve/liquidation quality-events` (`gen-ag05122057-c9786e`). The replay proves the user-facing route-candidate canary remains healthy while the development tooling starts treating route candidates as repair-loop input: @@ -64,25 +66,25 @@ The replay proves the user-facing route-candidate canary remains healthy while t - exact accounting profit/margin wording now has the first reviewed route implementation and can move to `ready_for_reviewed_execution` through confirmed 90/91/99 accounting rows. - exact due-date debt aging wording now has the second reviewed route implementation and can move to `ready_for_reviewed_execution` through confirmed open-balance/payment-term evidence, while absent payment terms produce an honest checked-negative boundary answer. - exact vendor-risk/procurement wording now has a live-accepted reviewed procurement-concentration route that can move to `ready_for_reviewed_execution` when confirmed outgoing payment, bank-like recipient segregation, non-financial recipient, counterparty-role, and contract-usage evidence are reachable. It still does not prove supplier reliability, delivery quality, payment purpose, contract-term compliance, or full expense structure. +- exact reserve/write-off/liquidation wording now has a live-accepted reviewed inventory quality-events route that can move to `ready_for_reviewed_execution` when posted write-off, receipt-adjustment, stocktaking, or revaluation documents are reachable. It still does not prove market liquidation value, management reserve policy, or full warehouse health. ## Status Current module wording: -`Route-Candidate-Driven Enablement Loop, active slice: third reviewed proof-family route accepted` +`Route-Candidate-Driven Enablement Loop, active slice: final reviewed proof-family route accepted` -Progress: `92%`. +Progress: `100%`. -The first cut proved the handoff mechanics and live canary. The second cut proved real proof-family candidates and a saved accepted AGENT pack. The third cut proved the intended promotion loop on `accounting_profit_margin`. The fourth cut proved the same loop on `debt_due_date_aging_quality`, including short boundary follow-up continuity and saved accepted autorun hygiene. The fifth cut proves `vendor_risk_procurement_quality` as reviewed procurement-concentration evidence with accepted phase95 replay. The module is still not complete because the inventory reserve/liquidation proof family remains open. +The first cut proved the handoff mechanics and live canary. The second cut proved real proof-family candidates and a saved accepted AGENT pack. The third cut proved the intended promotion loop on `accounting_profit_margin`. The fourth cut proved the same loop on `debt_due_date_aging_quality`, including short boundary follow-up continuity and saved accepted autorun hygiene. The fifth cut proved `vendor_risk_procurement_quality` as reviewed procurement-concentration evidence with accepted phase95 replay. The final cut proved `inventory_reserve_liquidation_quality` as reviewed inventory quality-event evidence with accepted phase96 replay. The declared route-candidate-driven enablement loop is now closed; future arbitrary 1C breadth work should treat these routes as regression gates, not as open blockers. ## Next Work Next slices: -1. Pick the final phase92 proof family: `inventory_reserve_liquidation_quality`. -2. Implement or explicitly bound that final family only if reliable 1C evidence is reachable. -3. Rerun phase95 as a canary plus the focused inventory route-specific pack. -4. Save the accepted pack into autoruns only after live replay and semantic review pass. +1. Treat this module as closed and keep phase91/92/93/94/95/96 as regression canaries. +2. Start the next broader open-world autonomy slice: schema/primitive discovery beyond the selected phase92 proof families. +3. Keep save-after-acceptance discipline for any new AGENT packs. See also: @@ -90,3 +92,4 @@ See also: - [28 - accounting_profit_margin_reviewed_route_2026-05-10.md](./28%20-%20accounting_profit_margin_reviewed_route_2026-05-10.md) - [29 - debt_due_date_aging_reviewed_route_2026-05-10.md](./29%20-%20debt_due_date_aging_reviewed_route_2026-05-10.md) - [30 - vendor_procurement_quality_reviewed_route_2026-05-12.md](./30%20-%20vendor_procurement_quality_reviewed_route_2026-05-12.md) +- [31 - inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md](./31%20-%20inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md) diff --git a/docs/ARCH/11 - architecture_turnaround/27 - proof_family_enablement_candidates_2026-05-10.md b/docs/ARCH/11 - architecture_turnaround/27 - proof_family_enablement_candidates_2026-05-10.md index 2c8714a..fbb3b83 100644 --- a/docs/ARCH/11 - architecture_turnaround/27 - proof_family_enablement_candidates_2026-05-10.md +++ b/docs/ARCH/11 - architecture_turnaround/27 - proof_family_enablement_candidates_2026-05-10.md @@ -101,23 +101,23 @@ This means `vendor_risk_procurement_quality` is no longer only a missing-proof c Current module wording: -`Route-Candidate-Driven Enablement Loop, active slice: third reviewed proof-family route accepted` +`Route-Candidate-Driven Enablement Loop, final reviewed proof-family route accepted; use as regression gate` -Progress: `92%`. +Progress: `100%`. -This cut proved the missing-proof candidate surface and accepted user-runnable AGENT canary. Phase93 then implemented the first exact reviewed route for the accounting profit/margin family. Phase94 implemented the second reviewed route for due-date debt aging and verified short boundary follow-up continuity. Phase95 now promotes vendor/procurement quality through reviewed procurement-concentration evidence and accepted live replay. The remaining closure work is inventory reserve/liquidation enablement or an explicit bounded non-implementation decision. +This cut proved the missing-proof candidate surface and accepted user-runnable AGENT canary. Phase93 then implemented the first exact reviewed route for the accounting profit/margin family. Phase94 implemented the second reviewed route for due-date debt aging and verified short boundary follow-up continuity. Phase95 promoted vendor/procurement quality through reviewed procurement-concentration evidence. Phase96 promoted inventory reserve/liquidation quality through reviewed inventory quality-event documents. The declared route-candidate-driven enablement loop is now closed and should be used as a regression gate for broader autonomy work. ## Next Work Next slices: -1. Wire or explicitly bound the final remaining family: `inventory_reserve_liquidation_quality`. -2. Keep proxy-only inventory reserve/liquidation wording bounded until reviewed evidence exists. -3. Rerun phase95 as a canary plus the focused inventory route-specific pack. -4. Save the next AGENT autorun only after live replay and semantic review pass. +1. Use phase91-phase96 as regression canaries. +2. Start the next broader open-world schema/primitive discovery module. +3. Keep saving AGENT autoruns only after live replay and semantic review pass. See also: - [28 - accounting_profit_margin_reviewed_route_2026-05-10.md](./28%20-%20accounting_profit_margin_reviewed_route_2026-05-10.md) - [29 - debt_due_date_aging_reviewed_route_2026-05-10.md](./29%20-%20debt_due_date_aging_reviewed_route_2026-05-10.md) - [30 - vendor_procurement_quality_reviewed_route_2026-05-12.md](./30%20-%20vendor_procurement_quality_reviewed_route_2026-05-12.md) +- [31 - inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md](./31%20-%20inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md) diff --git a/docs/ARCH/11 - architecture_turnaround/28 - accounting_profit_margin_reviewed_route_2026-05-10.md b/docs/ARCH/11 - architecture_turnaround/28 - accounting_profit_margin_reviewed_route_2026-05-10.md index c1968ea..b0e2c46 100644 --- a/docs/ARCH/11 - architecture_turnaround/28 - accounting_profit_margin_reviewed_route_2026-05-10.md +++ b/docs/ARCH/11 - architecture_turnaround/28 - accounting_profit_margin_reviewed_route_2026-05-10.md @@ -81,16 +81,16 @@ This keeps the phase93 accounting route as a canary while proving the candidate- Current module wording: -`Route-Candidate-Driven Enablement Loop, active slice: third reviewed proof-family route accepted` +`Route-Candidate-Driven Enablement Loop, final reviewed proof-family route accepted; use as regression gate` -Progress: `92%`. +Progress: `100%`. -This was the first proof that the loop can turn a route candidate into an executable reviewed route. Phase94 repeated the pattern for due-date debt aging, and phase95 accepted vendor/procurement quality through reviewed procurement-concentration evidence. The module is not yet complete because inventory reserve/liquidation still needs the same treatment or an explicit bounded non-implementation decision. +This was the first proof that the loop can turn a route candidate into an executable reviewed route. Phase94 repeated the pattern for due-date debt aging, phase95 accepted vendor/procurement quality through reviewed procurement-concentration evidence, and phase96 accepted inventory reserve/liquidation quality through reviewed inventory quality-event documents. The declared loop is now closed and this route remains a regression canary. ## Next Work Next slices: -1. Select the remaining proof family: `inventory_reserve_liquidation_quality`. -2. Wire only the smallest reliable reviewed route, not a broad heuristic. -3. Keep proxy-only reserve/liquidation wording bounded until the route is accepted. +1. Use this route as a regression canary during broader autonomy work. +2. Continue the next module through open-world schema/primitive discovery rather than more phase92 proof-family closure. +3. Keep proof-specific wording bounded unless a reviewed route has live evidence. diff --git a/docs/ARCH/11 - architecture_turnaround/29 - debt_due_date_aging_reviewed_route_2026-05-10.md b/docs/ARCH/11 - architecture_turnaround/29 - debt_due_date_aging_reviewed_route_2026-05-10.md index 5aba3fe..4fe01f9 100644 --- a/docs/ARCH/11 - architecture_turnaround/29 - debt_due_date_aging_reviewed_route_2026-05-10.md +++ b/docs/ARCH/11 - architecture_turnaround/29 - debt_due_date_aging_reviewed_route_2026-05-10.md @@ -77,17 +77,16 @@ The accepted replay proves: Current module wording: -`Route-Candidate-Driven Enablement Loop, active slice: third reviewed proof-family route accepted` +`Route-Candidate-Driven Enablement Loop, final reviewed proof-family route accepted; use as regression gate` -Progress: `92%`. +Progress: `100%`. -This is the second live-accepted proof that the loop can turn a route candidate into an executable reviewed route. Phase95 now accepts `vendor_risk_procurement_quality` through reviewed procurement-concentration evidence. The module is not yet complete because inventory reserve/liquidation still needs either reviewed exact route enablement or an explicit bounded non-implementation decision. +This is the second live-accepted proof that the loop can turn a route candidate into an executable reviewed route. Phase95 accepted `vendor_risk_procurement_quality` through reviewed procurement-concentration evidence, and phase96 accepted `inventory_reserve_liquidation_quality` through reviewed inventory quality-event documents. The declared loop is now closed and this route remains a regression canary. ## Next Work Next slices: -1. Identify whether reliable 1C evidence exists for `inventory_reserve_liquidation_quality`. -2. Wire the smallest reviewed route only if it can prove the inventory business claim without overreach. -3. Keep proxy-only evidence bounded if exact proof is not reachable. -4. Rerun phase95 as a canary plus the focused inventory route-specific pack. +1. Use phase94, phase95, and phase96 as canaries for due-date, vendor, and inventory-quality continuity. +2. Move the active plan to broader open-world schema/primitive discovery. +3. Keep proxy-only evidence bounded when a reviewed route is not available. diff --git a/docs/ARCH/11 - architecture_turnaround/30 - vendor_procurement_quality_reviewed_route_2026-05-12.md b/docs/ARCH/11 - architecture_turnaround/30 - vendor_procurement_quality_reviewed_route_2026-05-12.md index 0efd844..e029e32 100644 --- a/docs/ARCH/11 - architecture_turnaround/30 - vendor_procurement_quality_reviewed_route_2026-05-12.md +++ b/docs/ARCH/11 - architecture_turnaround/30 - vendor_procurement_quality_reviewed_route_2026-05-12.md @@ -62,29 +62,29 @@ Semantic/live replay: Current module wording: -`Route-Candidate-Driven Enablement Loop, active slice: third reviewed proof-family route accepted` +`Route-Candidate-Driven Enablement Loop, superseded by phase96 final reviewed proof-family acceptance` -Progress: `92%`. +Progress: `100%` in the parent loop after phase96. -The loop has now promoted three phase92 proof families from candidate gaps into reviewed execution or locally reviewed execution: +This phase remains the vendor/procurement-quality proof. After this note, phase96 promoted the final remaining inventory reserve/liquidation proof family, so the parent Route-Candidate-Driven Enablement Loop is now closed. + +The loop has now promoted four phase92 proof families from candidate gaps into reviewed execution or locally reviewed execution: - `accounting_profit_margin` accepted live by phase93; - `debt_due_date_aging_quality` accepted live by phase94; - `vendor_risk_procurement_quality` accepted live through procurement-concentration evidence by phase95. - -The module is still not complete because the remaining `inventory_reserve_liquidation_quality` family still needs either reviewed-route enablement or an explicit bounded non-implementation decision. +- `inventory_reserve_liquidation_quality` accepted live through inventory quality-event evidence by phase96. ## Next Work Next slices: -1. Select the final remaining phase92 family: `inventory_reserve_liquidation_quality`. -2. Determine whether reachable 1C evidence can prove reserve/write-off/liquidation quality without overreach. -3. If yes, wire the smallest reviewed route and run a focused phase96 semantic replay. -4. If no, record an explicit bounded non-implementation decision and keep the proxy answer honest. +1. Use phase95 as a vendor/procurement regression canary during broader autonomy work. +2. See phase96 for the final inventory reserve/liquidation route and module closure. See also: - [26 - route_candidate_driven_enablement_loop_2026-05-10.md](./26%20-%20route_candidate_driven_enablement_loop_2026-05-10.md) - [27 - proof_family_enablement_candidates_2026-05-10.md](./27%20-%20proof_family_enablement_candidates_2026-05-10.md) - [29 - debt_due_date_aging_reviewed_route_2026-05-10.md](./29%20-%20debt_due_date_aging_reviewed_route_2026-05-10.md) +- [31 - inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md](./31%20-%20inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md) diff --git a/docs/ARCH/11 - architecture_turnaround/31 - inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md b/docs/ARCH/11 - architecture_turnaround/31 - inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md new file mode 100644 index 0000000..c3c82f9 --- /dev/null +++ b/docs/ARCH/11 - architecture_turnaround/31 - inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md @@ -0,0 +1,100 @@ +# 31 - Inventory Reserve/Liquidation Quality Reviewed Route (2026-05-12) + +This note records the final phase92 proof-family promotion inside the `Route-Candidate-Driven Enablement Loop`. + +## Why This Slice Exists + +Phase92 exposed `inventory_reserve_liquidation_quality` as a real route-candidate gap: + +- users can naturally ask whether warehouse stock has reserves, write-offs, obsolete goods, or liquidation value; +- earlier business overview could show stock position, sales-to-stock proxy, and staleness-risk proxy; +- those proxies were useful but still not reviewed evidence for write-offs, stocktaking, revaluation, reserve policy, or market liquidation value. + +The safe implementation target was therefore not a fake "full inventory health" route. It was the narrowest reviewed 1C evidence route that can answer what is actually reachable. + +## Implementation Cut + +Implemented locally: + +- added `inventory_quality_events_for_organization` as a reviewed address recipe intent; +- added `inventory_quality_events_profile` over posted 1C documents: + - `Документ.СписаниеТоваров`; + - `Документ.ОприходованиеТоваров`; + - `Документ.ИнвентаризацияТоваровНаСкладе`; + - `Документ.ПереоценкаТоваровВРознице`; +- `business_overview` now runs this probe only for reserve/liquidation/write-off/obsolete-stock boundary wording; +- `derived_business_overview.inventory_quality_events` records: + - checked period; + - matched event rows; + - write-off count and amount; + - receipt-adjustment count and amount; + - stocktaking count; + - revaluation count; + - first/latest event date when events exist; + - evidence status: `reviewed_no_quality_events_found`, `reviewed_inventory_control_events_only`, or `reviewed_writeoff_or_adjustment_events_found`; +- `inventory_reserve_liquidation_quality` is removed from `missing_proof_families` only when this reviewed route executed; +- `inventory_reserve_boundary` route candidates can now promote to `ready_for_reviewed_execution`; +- user-facing direct answers include the organization scope, period, checked document families, and explicit no-overclaim boundary. + +## Boundaries + +Still not claimed: + +- market liquidation value; +- management reserve amount or reserve policy; +- confirmed obsolete stock classification; +- complete warehouse health; +- FIFO liquidation or sell-through quality; +- accounting correctness of the source documents. + +The route can honestly say "checked documents found / not found". It cannot turn absence of events into a legal or market conclusion that no obsolete stock exists. + +## Validation + +Local validation: + +- `npm.cmd test -- assistantMcpDiscoveryAnswerAdapter.test.ts assistantMcpDiscoveryRuntimeBridge.test.ts assistantMcpDiscoveryResponseCandidate.test.ts` passed `84/84` with `1` skipped; +- `npm.cmd test -- assistantMcpDiscoveryPilotExecutor.test.ts` passed `34/34`; +- `npm.cmd run build` passed; +- direct live MCP query for `address_inventory_quality_events_for_organization_v1` against `ООО Альтернатива Плюс / 2020` returned `fetched_rows=0`, `matched_rows=0`, `error=null`, proving the new union query is syntactically valid and yields a checked negative in the current base. + +Semantic/live replay: + +- spec: `docs/orchestration/address_truth_harness_phase96_inventory_reserve_liquidation_quality.json`; +- first live run: `artifacts/domain_runs/phase96_inventory_reserve_liquidation_quality_live`, partial/fail because the answer did not repeat the explicit organization in the direct line; +- rerun after organization anchoring and compact-candidate cleanup: `artifacts/domain_runs/phase96_inventory_reserve_liquidation_quality_rerun`; +- final status: `accepted`, `2/2` passed, `0` warnings, `0` failures; +- accepted autorun: `AGENT | Phase 96 inventory reserve/liquidation quality-events` (`gen-ag05122057-c9786e`). + +## Status + +Current module wording: + +`Route-Candidate-Driven Enablement Loop, active slice: final reviewed proof-family route accepted` + +Progress: `100%`. + +The loop has now promoted all phase92 proof-family candidates that were selected for this module: + +- `accounting_profit_margin` accepted live by phase93; +- `debt_due_date_aging_quality` accepted live by phase94; +- `vendor_risk_procurement_quality` accepted live through procurement-concentration evidence by phase95; +- `inventory_reserve_liquidation_quality` accepted live through reviewed inventory quality-event documents by phase96. + +This closes the declared Route-Candidate-Driven Enablement Loop slice. It does not close arbitrary 1C autonomy: the next work should move to the next broader module, using these proof-family routes as regression gates. + +## Next Work + +Next slices: + +1. Reclassify Route-Candidate-Driven Enablement Loop as closed and use it as a regression gate. +2. Start the next open-world autonomy slice: broader schema/primitive discovery beyond the phase92 proof families. +3. Preserve phase93/94/95/96 accepted autoruns as canaries before expanding new unknown 1C domains. + +See also: + +- [26 - route_candidate_driven_enablement_loop_2026-05-10.md](./26%20-%20route_candidate_driven_enablement_loop_2026-05-10.md) +- [27 - proof_family_enablement_candidates_2026-05-10.md](./27%20-%20proof_family_enablement_candidates_2026-05-10.md) +- [28 - accounting_profit_margin_reviewed_route_2026-05-10.md](./28%20-%20accounting_profit_margin_reviewed_route_2026-05-10.md) +- [29 - debt_due_date_aging_reviewed_route_2026-05-10.md](./29%20-%20debt_due_date_aging_reviewed_route_2026-05-10.md) +- [30 - vendor_procurement_quality_reviewed_route_2026-05-12.md](./30%20-%20vendor_procurement_quality_reviewed_route_2026-05-12.md) diff --git a/docs/ARCH/11 - architecture_turnaround/README.md b/docs/ARCH/11 - architecture_turnaround/README.md index 8c70408..61a74e0 100644 --- a/docs/ARCH/11 - architecture_turnaround/README.md +++ b/docs/ARCH/11 - architecture_turnaround/README.md @@ -48,8 +48,9 @@ This package answers the next question: 28. [28 - accounting_profit_margin_reviewed_route_2026-05-10.md](./28%20-%20accounting_profit_margin_reviewed_route_2026-05-10.md) 29. [29 - debt_due_date_aging_reviewed_route_2026-05-10.md](./29%20-%20debt_due_date_aging_reviewed_route_2026-05-10.md) 30. [30 - vendor_procurement_quality_reviewed_route_2026-05-12.md](./30%20-%20vendor_procurement_quality_reviewed_route_2026-05-12.md) +31. [31 - inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md](./31%20-%20inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md) -## Current Status Snapshot (2026-05-10) +## Current Status Snapshot (2026-05-12) This package is no longer planning-only. @@ -103,16 +104,19 @@ Status canon for planning: - The accepted user-runnable autorun for that slice is `AGENT | Phase 94 debt due-date aging reviewed route` (`gen-ag05101319-c04f79`). - The third proof-family route is now implemented and accepted: `vendor_risk_procurement_quality` moves from missing proof-family gap into reviewed procurement-concentration evidence when outgoing payment, bank-like recipient, non-financial recipient, counterparty-role, and contract-usage signals are reachable; `phase95_vendor_procurement_quality_reviewed_route_live2` passed `7/7`. - The accepted user-runnable autorun for that slice is `AGENT | Phase 95 vendor/procurement quality reviewed route` (`gen-ag05121357-9ea5d6`). +- The fourth/final proof-family route is now implemented and accepted: `inventory_reserve_liquidation_quality` moves from missing proof-family gap into reviewed inventory quality-event evidence over posted write-off, receipt-adjustment, stocktaking, and revaluation documents; `phase96_inventory_reserve_liquidation_quality_rerun` passed `2/2`. +- The accepted user-runnable autorun for that slice is `AGENT | Phase 96 inventory reserve/liquidation quality-events` (`gen-ag05122057-c9786e`). - The phase94 replay spec was repaired to real UTF-8 Russian before autorun persistence, so the saved user-runnable pack does not repeat the earlier GUI mojibake/card-text regression. - The short source of truth for status wording is [21 - current_status_canon_2026-05-01.md](./21%20-%20current_status_canon_2026-05-01.md). - The current execution spine after EHMO is [23 - current_execution_spine_and_semantic_control_gate_2026-05-05.md](./23%20-%20current_execution_spine_and_semantic_control_gate_2026-05-05.md). - The current stage-loop/hygiene overlay after the AGENT dogfood cut is [24 - agentic_semantic_development_loop_and_autorun_hygiene_2026-05-10.md](./24%20-%20agentic_semantic_development_loop_and_autorun_hygiene_2026-05-10.md). - The current route-candidate autonomy slice is [25 - open_world_route_candidate_promotion_2026-05-10.md](./25%20-%20open_world_route_candidate_promotion_2026-05-10.md). -- The current route-candidate enablement-loop slice is [26 - route_candidate_driven_enablement_loop_2026-05-10.md](./26%20-%20route_candidate_driven_enablement_loop_2026-05-10.md). -- The current proof-family enablement-candidate slice is [27 - proof_family_enablement_candidates_2026-05-10.md](./27%20-%20proof_family_enablement_candidates_2026-05-10.md). -- The current first reviewed proof-family route slice is [28 - accounting_profit_margin_reviewed_route_2026-05-10.md](./28%20-%20accounting_profit_margin_reviewed_route_2026-05-10.md). -- The current second reviewed proof-family route slice is [29 - debt_due_date_aging_reviewed_route_2026-05-10.md](./29%20-%20debt_due_date_aging_reviewed_route_2026-05-10.md). -- The current third reviewed proof-family route slice is [30 - vendor_procurement_quality_reviewed_route_2026-05-12.md](./30%20-%20vendor_procurement_quality_reviewed_route_2026-05-12.md). +- The closed route-candidate enablement-loop slice is [26 - route_candidate_driven_enablement_loop_2026-05-10.md](./26%20-%20route_candidate_driven_enablement_loop_2026-05-10.md), now used as a regression gate. +- The closed proof-family enablement-candidate slice is [27 - proof_family_enablement_candidates_2026-05-10.md](./27%20-%20proof_family_enablement_candidates_2026-05-10.md). +- The first reviewed proof-family route slice is [28 - accounting_profit_margin_reviewed_route_2026-05-10.md](./28%20-%20accounting_profit_margin_reviewed_route_2026-05-10.md). +- The second reviewed proof-family route slice is [29 - debt_due_date_aging_reviewed_route_2026-05-10.md](./29%20-%20debt_due_date_aging_reviewed_route_2026-05-10.md). +- The third reviewed proof-family route slice is [30 - vendor_procurement_quality_reviewed_route_2026-05-12.md](./30%20-%20vendor_procurement_quality_reviewed_route_2026-05-12.md). +- The fourth/final reviewed proof-family route slice is [31 - inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md](./31%20-%20inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md). It now documents a turnaround that is already operational in code, already materially past the acute regression breakpoint, and already moved through bounded MCP autonomy, Post-F hardening, inventory breadth proof, and the declared Planner Autonomy slice: @@ -176,13 +180,13 @@ Current honest status: - pre-multidomain readiness: `~90%` - bounded-autonomy foundation readiness: `~89%` - open-world bounded-autonomy readiness: `~87%` -- active Open-World Bounded Autonomy Breadth implementation breadth: `~99%`, with business-overview evidence fusion, the reviewed `business_overview` catalog/data-need/planner route-fabric slice, the fresh multi-probe runtime bridge, the explicit-period VAT/tax fact-family bridge, the explicit-period debt-position bridge, the explicit-date inventory-position bridge, the open-settlement quality bridge accepted by live semantic replay, selected-item profitability bridged by local semantic/runtime regression tests, contract-date debt age bridged locally, debt staleness-risk proxy bridged locally, debt due-date boundary arbitration bridged locally, inventory reserve/liquidation boundary arbitration bridged locally, supplier/procurement-quality boundary arbitration bridged locally, supplier concentration proxy bridged locally, document/account-section activity profile bridged locally, counterparty population/roles and contract usage profiles bridged locally, yearly operating-flow proxy bridged locally, earnings/best-year wording arbitration bridged locally, profit/margin wording boundary arbitration bridged locally, analyst synthesis added to business-overview answer drafting, company-period trading margin proxy bridged locally, inventory sales-to-stock proxy bridged locally, inventory staleness-risk proxy bridged locally, gap-specific answer shaping bridged locally, missing proof families recorded as runtime evidence ledger, exact accounting profit/margin promoted into a reviewed 90/91/99 route by phase93, debt due-date aging promoted into a reviewed payment-term/open-balance route by phase94, and vendor/procurement quality promoted into reviewed procurement-concentration evidence by phase95; confirmed reserve/write-off/liquidation inventory evidence is still pending +- active Open-World Bounded Autonomy Breadth implementation breadth: `~99%`, with business-overview evidence fusion, the reviewed `business_overview` catalog/data-need/planner route-fabric slice, the fresh multi-probe runtime bridge, the explicit-period VAT/tax fact-family bridge, the explicit-period debt-position bridge, the explicit-date inventory-position bridge, the open-settlement quality bridge accepted by live semantic replay, selected-item profitability bridged by local semantic/runtime regression tests, contract-date debt age bridged locally, debt staleness-risk proxy bridged locally, debt due-date boundary arbitration bridged locally, inventory reserve/liquidation boundary arbitration bridged locally, supplier/procurement-quality boundary arbitration bridged locally, supplier concentration proxy bridged locally, document/account-section activity profile bridged locally, counterparty population/roles and contract usage profiles bridged locally, yearly operating-flow proxy bridged locally, earnings/best-year wording arbitration bridged locally, profit/margin wording boundary arbitration bridged locally, analyst synthesis added to business-overview answer drafting, company-period trading margin proxy bridged locally, inventory sales-to-stock proxy bridged locally, inventory staleness-risk proxy bridged locally, gap-specific answer shaping bridged locally, missing proof families recorded as runtime evidence ledger, exact accounting profit/margin promoted into a reviewed 90/91/99 route by phase93, debt due-date aging promoted into a reviewed payment-term/open-balance route by phase94, vendor/procurement quality promoted into reviewed procurement-concentration evidence by phase95, and inventory reserve/write-off/liquidation quality promoted into reviewed inventory quality-event evidence by phase96 - active Open-World Bounded Autonomy Breadth accepted-module progress: `~99%`, because the EHMO-derived `Open-World Semantic Control Gate` critical subset accepts live at `21/21` after W5/W7 hardening; full closure is still held back for the fat manual GUI pack and remaining answer-shape residual review - Post-F semantic integrity module progress: `~99%` operationally closed, with remaining risk now treated as next-slice discovery rather than an open blocker inside the closed slice - active inventory-stock breadth slice progress: `100%` for the declared scenario pack, not for arbitrary inventory questions - Planner Autonomy Consolidation progress: `100%` for the declared module, with catalog-fabric, value-flow arbitration, lifecycle bounded inference, broad-evaluation bridge, inventory catalog templates, inventory runtime-boundary honesty, exact inventory recipe bridging, unambiguous metadata-surface lane inference, catalog chain-template scoring, structured chain-match contract exposure, runtime/debug propagation, subject-aware bidirectional comparison arbitration, structured catalog-alignment verdicts, representative alignment regression guard, catalog-alignment reason-code telemetry, explicit `alignment_status` propagation, truth-harness/acceptance-matrix surfacing, soft divergence warning, `catalog_alignment_ok` acceptance invariant, step-level expected catalog-alignment assertions, phase66 and phase32 spec alignment expectations, AGENT source-catalog surfacing, generated phase83 mixed planner-brain replay spec, checked-source user-facing error sanitation, surface-grounded catalog promotion, and guarded live phase83 acceptance validated. Broader unfamiliar 1C asks are now next-module breadth work rather than an open blocker inside this declared slice - Open-World Route Candidate Promotion progress: `100%` for the declared phase90 slice, with structured `route_candidate` runtime contract, artifact propagation, live semantic replay accepted at `5/5`, and accepted AGENT autorun persistence; broader autonomous route enablement remains the next active slice -- Route-Candidate-Driven Enablement Loop progress: `92%`, with deterministic repair-target grouping, Lead Codex handoff surfacing, local tooling tests, live phase91 canary acceptance, phase92 proof-family candidates accepted/saved as a user-runnable AGENT autorun, `accounting_profit_margin` promoted into reviewed 90/91/99 execution by phase93 live replay, `debt_due_date_aging_quality` promoted into reviewed payment-term/open-balance execution by phase94 live replay, and `vendor_risk_procurement_quality` promoted into reviewed procurement-concentration evidence by phase95 live replay; the remaining inventory reserve/liquidation proof family is still pending +- Route-Candidate-Driven Enablement Loop progress: `100%`, with deterministic repair-target grouping, Lead Codex handoff surfacing, local tooling tests, live phase91 canary acceptance, phase92 proof-family candidates accepted/saved as a user-runnable AGENT autorun, `accounting_profit_margin` promoted into reviewed 90/91/99 execution by phase93 live replay, `debt_due_date_aging_quality` promoted into reviewed payment-term/open-balance execution by phase94 live replay, `vendor_risk_procurement_quality` promoted into reviewed procurement-concentration evidence by phase95 live replay, and `inventory_reserve_liquidation_quality` promoted into reviewed inventory quality-event evidence by phase96 live replay; the declared route-candidate-driven enablement loop is now closed and should be used as a regression gate for the next broader autonomy slice - graph snapshot after latest rebuild: see `graphify-out/GRAPH_REPORT.md` - current regression-gate breakpoint: - the validated hot paths are no longer structurally broken; @@ -272,6 +276,7 @@ Latest live proof now includes: - accounting profit/margin reviewed route accepted locally/live: targeted runtime/answer/turn-input/candidate/intent tests passed `194/194` with `8` skipped; targeted VAT tax-period regression passed; `address_truth_harness_phase93_accounting_profit_margin_reviewed_route_live3_20260510` accepted `6/6`, proving 90/91/99 accounting result, short profit/loss follow-up continuity, VAT continuity, value-flow canary, and inventory reserve boundary canary together; the accepted autorun is `AGENT | Phase 93 accounting profit-margin reviewed route` (`gen-ag05101213-596d99`). - debt due-date aging reviewed route accepted locally/live: transition policy passed `38/38`, turn-input adapter passed `103/103` with `7` skipped, executor/answer/candidate/runtime bridge passed `113/113` with `1` skipped, build passed; `phase94_debt_due_date_aging_reviewed_route_live4` accepted `7/7`, proving payment-term/open-balance checked-negative overdue answers, short due-date boundary follow-up continuity, profit/margin/VAT/value-flow canaries, and reserve/vendor boundary safety together; the accepted autorun is `AGENT | Phase 94 debt due-date aging reviewed route` (`gen-ag05101319-c04f79`). - vendor/procurement quality reviewed route accepted locally/live: executor/runtime bridge/answer/candidate tests passed `118/118` with `1` skipped, build passed; `phase95_vendor_procurement_quality_reviewed_route_live2` accepted `7/7`; `vendor_risk_procurement_quality` now derives reviewed procurement-concentration evidence from confirmed outgoing payment rows, separates bank-like outgoing leaders from ordinary supplier dependency, removes the proof family from `missing_proof_families` when this reviewed evidence exists, and can promote `vendor_risk_procurement_boundary` route candidates to `ready_for_reviewed_execution`; the accepted autorun is `AGENT | Phase 95 vendor/procurement quality reviewed route` (`gen-ag05121357-9ea5d6`). +- inventory reserve/liquidation quality reviewed route accepted locally/live: answer/runtime/candidate tests passed `84/84` with `1` skipped, pilot-executor tests passed `34/34`, build passed; direct MCP query for `address_inventory_quality_events_for_organization_v1` returned `fetched_rows=0`, `matched_rows=0`, `error=null`; `phase96_inventory_reserve_liquidation_quality_rerun` accepted `2/2`; `inventory_reserve_liquidation_quality` now derives reviewed evidence from posted write-off, receipt-adjustment, stocktaking, and revaluation documents, removes the proof family from `missing_proof_families` when this reviewed route executes, anchors the organization in the direct answer, and can promote `inventory_reserve_boundary` route candidates to `ready_for_reviewed_execution`; the accepted autorun is `AGENT | Phase 96 inventory reserve/liquidation quality-events` (`gen-ag05122057-c9786e`). Current architectural reading: @@ -351,6 +356,7 @@ Read in this order: 29. `28 - accounting_profit_margin_reviewed_route_2026-05-10.md` 30. `29 - debt_due_date_aging_reviewed_route_2026-05-10.md` 31. `30 - vendor_procurement_quality_reviewed_route_2026-05-12.md` +32. `31 - inventory_reserve_liquidation_quality_reviewed_route_2026-05-12.md` ## Planning Rules diff --git a/docs/orchestration/address_truth_harness_phase96_inventory_reserve_liquidation_quality.json b/docs/orchestration/address_truth_harness_phase96_inventory_reserve_liquidation_quality.json new file mode 100644 index 0000000..26d8309 --- /dev/null +++ b/docs/orchestration/address_truth_harness_phase96_inventory_reserve_liquidation_quality.json @@ -0,0 +1,71 @@ +{ + "schema_version": "domain_truth_harness_spec_v1", + "scenario_id": "address_truth_harness_phase96_inventory_reserve_liquidation_quality", + "domain": "address_phase96_inventory_reserve_liquidation_quality", + "title": "Phase 96 inventory reserve/liquidation quality-events replay", + "description": "Targeted replay for the reviewed inventory quality-events route: direct reserve/write-off/liquidation questions should trigger checked 1C document evidence for write-offs, receipt adjustments, stocktaking, and retail revaluation, while still refusing to invent market liquidation value or management reserve policy.", + "bindings": {}, + "steps": [ + { + "step_id": "step_01_direct_inventory_reserve_liquidation_boundary", + "title": "Direct reserve/liquidation boundary uses reviewed inventory quality events", + "question": "По ООО Альтернатива Плюс за 2020 год проверь склад: были ли списания товаров, оприходования/корректировки, инвентаризации, переоценки, резервы под неликвиды или ликвидационная стоимость? Скажи коротко и честно, что подтверждено 1С, а что нельзя утверждать.", + "expected_catalog_alignment_status": "selected_matches_top", + "expected_catalog_chain_top_match": "business_overview", + "expected_catalog_selected_matches_top": true, + "allowed_reply_types": [ + "partial_coverage", + "factual_with_explanation" + ], + "required_answer_patterns_all": [ + "(?i)альтернатива|организац|компани", + "(?i)2020|2020 год", + "(?i)склад|товар", + "(?i)списан|оприход|инвентаризац|переоцен", + "(?i)провер|подтвержд|1с", + "(?i)не рыночн|не ликвидационн|не управленческ|не резерв" + ], + "forbidden_answer_patterns": [ + "(?i)business_overview_route_template_v1|inventory_quality_events_profile", + "(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_", + "(?i)ликвидационная стоимость составляет|резерв составляет|подтвержденный резерв под неликвид" + ], + "criticality": "critical", + "semantic_tags": [ + "inventory_reserve_liquidation_quality", + "reviewed_inventory_quality_events", + "direct_answer_first", + "no_market_liquidation_overclaim" + ] + }, + { + "step_id": "step_02_followup_inventory_liquidity_boundary", + "title": "Follow-up does not turn checked quality events into full liquidity claim", + "question": "А по этим же данным можно сказать, что склад ликвидный и неликвидов нет?", + "expected_catalog_alignment_status": "selected_matches_top", + "expected_catalog_chain_top_match": "business_overview", + "expected_catalog_selected_matches_top": true, + "allowed_reply_types": [ + "partial_coverage", + "factual_with_explanation" + ], + "required_answer_patterns_all": [ + "(?i)склад|товар|остат", + "(?i)ликвид|неликвид", + "(?i)не подтвержд|нельзя|не значит|отдельн", + "(?i)списан|инвентаризац|переоцен|quality|событ" + ], + "forbidden_answer_patterns": [ + "(?i)business_overview_route_template_v1|inventory_quality_events_profile", + "(?i)query_movements|query_documents|primitive|planner_|runtime_|pilot_", + "(?i)склад ликвидный$|неликвидов нет$|ликвидность подтверждена" + ], + "criticality": "critical", + "semantic_tags": [ + "inventory_liquidity_boundary", + "followup_context_carryover", + "no_inventory_health_overclaim" + ] + } + ] +} diff --git a/llm_normalizer/backend/dist/services/addressRecipeCatalog.js b/llm_normalizer/backend/dist/services/addressRecipeCatalog.js index 92183e9..cfc5d77 100644 --- a/llm_normalizer/backend/dist/services/addressRecipeCatalog.js +++ b/llm_normalizer/backend/dist/services/addressRecipeCatalog.js @@ -283,6 +283,61 @@ const INVENTORY_ON_HAND_AS_OF_QUERY_TEMPLATE = ` УПОРЯДОЧИТЬ ПО Количество __ORDER_DIRECTION__ `; +const INVENTORY_QUALITY_EVENTS_QUERY_TEMPLATE = ` +ВЫБРАТЬ ПЕРВЫЕ __LIMIT__ + Списание.Дата КАК Период, + ПРЕДСТАВЛЕНИЕ(Списание.Ссылка) КАК Регистратор, + "Списание товаров" КАК ТипСобытия, + ПРЕДСТАВЛЕНИЕ(Списание.Организация) КАК Организация, + ПРЕДСТАВЛЕНИЕ(Списание.Склад) КАК Склад, + Списание.СуммаДокумента КАК Сумма, + Списание.Основание КАК Основание, + Списание.Комментарий КАК Комментарий +ИЗ + Документ.СписаниеТоваров КАК Списание +__WHERE_WRITE_OFF__ +ОБЪЕДИНИТЬ ВСЕ +ВЫБРАТЬ ПЕРВЫЕ __LIMIT__ + Оприходование.Дата КАК Период, + ПРЕДСТАВЛЕНИЕ(Оприходование.Ссылка) КАК Регистратор, + "Оприходование товаров" КАК ТипСобытия, + ПРЕДСТАВЛЕНИЕ(Оприходование.Организация) КАК Организация, + ПРЕДСТАВЛЕНИЕ(Оприходование.Склад) КАК Склад, + Оприходование.СуммаДокумента КАК Сумма, + Оприходование.Основание КАК Основание, + Оприходование.Комментарий КАК Комментарий +ИЗ + Документ.ОприходованиеТоваров КАК Оприходование +__WHERE_RECEIPT__ +ОБЪЕДИНИТЬ ВСЕ +ВЫБРАТЬ ПЕРВЫЕ __LIMIT__ + Инвентаризация.Дата КАК Период, + ПРЕДСТАВЛЕНИЕ(Инвентаризация.Ссылка) КАК Регистратор, + "Инвентаризация товаров на складе" КАК ТипСобытия, + ПРЕДСТАВЛЕНИЕ(Инвентаризация.Организация) КАК Организация, + ПРЕДСТАВЛЕНИЕ(Инвентаризация.Склад) КАК Склад, + 0 КАК Сумма, + Инвентаризация.ПричинаПроведенияИнвентаризации КАК Основание, + Инвентаризация.Комментарий КАК Комментарий +ИЗ + Документ.ИнвентаризацияТоваровНаСкладе КАК Инвентаризация +__WHERE_INVENTORY_COUNT__ +ОБЪЕДИНИТЬ ВСЕ +ВЫБРАТЬ ПЕРВЫЕ __LIMIT__ + Переоценка.Дата КАК Период, + ПРЕДСТАВЛЕНИЕ(Переоценка.Ссылка) КАК Регистратор, + "Переоценка товаров в рознице" КАК ТипСобытия, + ПРЕДСТАВЛЕНИЕ(Переоценка.Организация) КАК Организация, + ПРЕДСТАВЛЕНИЕ(Переоценка.Склад) КАК Склад, + 0 КАК Сумма, + "" КАК Основание, + Переоценка.Комментарий КАК Комментарий +ИЗ + Документ.ПереоценкаТоваровВРознице КАК Переоценка +__WHERE_REVALUATION__ +УПОРЯДОЧИТЬ ПО + Период __ORDER_DIRECTION__ +`; const BANK_DOCS_QUERY_TEMPLATE = ` ВЫБРАТЬ ПЕРВЫЕ __LIMIT__ БанкСписание.Дата КАК Период, @@ -933,6 +988,16 @@ const BASE_RECIPES = [ account_scope_mode: "strict", query_template: "inventory_aging_by_purchase_date_profile" }, + { + recipe_id: "address_inventory_quality_events_for_organization_v1", + intent: "inventory_quality_events_for_organization", + purpose: "Check posted inventory quality event documents: write-offs, stocktaking, receipt adjustments, and retail revaluation", + required_filters: [], + optional_filters: ["as_of_date", "period_from", "period_to", "organization", "warehouse", "limit", "sort"], + default_limit: 400, + account_scope_mode: "preferred", + query_template: "inventory_quality_events_profile" + }, { recipe_id: "address_open_contracts_confirmed_as_of_date_v1", intent: "open_contracts_confirmed_as_of_date", @@ -1326,6 +1391,48 @@ function buildInventoryMovementQuery(filters, resolvedLimit, side) { .replace("__WHERE_CLAUSE__", buildWhereClause(filters, "Движения.Период", [inventoryCondition, itemCondition].filter((item) => Boolean(item)))) .replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort)); } +function buildWarehouseReferenceCondition(filters, fieldPaths) { + const warehouse = typeof filters.warehouse === "string" ? filters.warehouse.trim() : ""; + if (!warehouse) { + return null; + } + const tokens = Array.from(new Set(warehouse + .split(/[^A-Za-zА-Яа-яЁё0-9]+/u) + .map((token) => token.trim()) + .filter((token) => token.length >= 3) + .filter((token) => !["склад", "warehouse"].includes(token.toLowerCase())))); + const effectiveTokens = tokens.length > 0 ? tokens : [warehouse]; + const clauses = fieldPaths + .map((fieldPath) => String(fieldPath ?? "").trim()) + .filter((fieldPath) => fieldPath.length > 0) + .map((fieldPath) => { + const tokenConditions = effectiveTokens.map((token) => { + const escapedToken = toQueryStringLiteral(token); + return `${fieldPath}.Наименование ПОДОБНО "%${escapedToken}%"`; + }); + return tokenConditions.length === 1 ? tokenConditions[0] : `(${tokenConditions.join(" И ")})`; + }); + if (clauses.length === 0) { + return null; + } + return clauses.length === 1 ? clauses[0] : `(${clauses.join(" ИЛИ ")})`; +} +function buildInventoryQualityDocumentWhereClause(filters, dateFieldPath, organizationFieldPath, warehouseFieldPath) { + return buildWhereClause(filters, dateFieldPath, [ + `${dateFieldPath.replace(/\.Дата$/u, ".Проведен")} = ИСТИНА`, + buildOrganizationReferenceCondition(filters, [organizationFieldPath]), + buildWarehouseReferenceCondition(filters, [warehouseFieldPath]) + ].filter((item) => Boolean(item))); +} +function buildInventoryQualityEventsQuery(filters, resolvedLimit) { + return INVENTORY_QUALITY_EVENTS_QUERY_TEMPLATE + .replaceAll("__LIMIT__", String(resolvedLimit)) + .replace("__WHERE_WRITE_OFF__", buildInventoryQualityDocumentWhereClause(filters, "Списание.Дата", "Списание.Организация", "Списание.Склад")) + .replace("__WHERE_RECEIPT__", buildInventoryQualityDocumentWhereClause(filters, "Оприходование.Дата", "Оприходование.Организация", "Оприходование.Склад")) + .replace("__WHERE_INVENTORY_COUNT__", buildInventoryQualityDocumentWhereClause(filters, "Инвентаризация.Дата", "Инвентаризация.Организация", "Инвентаризация.Склад")) + .replace("__WHERE_REVALUATION__", buildInventoryQualityDocumentWhereClause(filters, "Переоценка.Дата", "Переоценка.Организация", "Переоценка.Склад")) + .replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort)); +} function buildInventoryItemReferenceCondition(filters, fieldPaths) { const item = typeof filters.item === "string" ? filters.item.trim() : ""; if (!item) { @@ -1599,6 +1706,7 @@ function maxLimitForIntent(intent) { intent === "inventory_profitability_for_item" || intent === "inventory_purchase_to_sale_chain" || intent === "inventory_aging_by_purchase_date" || + intent === "inventory_quality_events_for_organization" || intent === "open_contracts_confirmed_as_of_date" || intent === "list_contracts_by_counterparty" || intent === "list_documents_by_counterparty" || @@ -1790,27 +1898,11 @@ function buildAddressRecipePlan(recipe, filters) { ? buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit) : recipe.query_template === "inventory_aging_by_purchase_date_profile" ? buildInventoryMovementQuery(filters, resolvedLimit, "dt") - : recipe.query_template === "contracts_by_counterparty_profile" - ? CONTRACTS_BY_COUNTERPARTY_QUERY_TEMPLATE.replaceAll("__LIMIT__", String(resolvedLimit)) - : recipe.query_template === "open_contracts_confirmed_as_of_balance_profile" - ? (() => { - const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0 - ? toDateTimeExpr(filters.as_of_date, true) - : null) ?? - (typeof filters.period_to === "string" && filters.period_to.trim().length > 0 - ? toDateTimeExpr(filters.period_to, true) - : null) ?? - (typeof filters.period_from === "string" && filters.period_from.trim().length > 0 - ? toDateTimeExpr(filters.period_from, true) - : null) ?? - "ТЕКУЩАЯДАТА()"; - return OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE - .replaceAll("__LIMIT__", String(resolvedLimit)) - .replaceAll("__AS_OF_EXPR__", asOfExpr) - .replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "62", "76"])) - .replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort)); - })() - : recipe.query_template === "payables_confirmed_as_of_balance_profile" + : recipe.query_template === "inventory_quality_events_profile" + ? buildInventoryQualityEventsQuery(filters, resolvedLimit) + : recipe.query_template === "contracts_by_counterparty_profile" + ? CONTRACTS_BY_COUNTERPARTY_QUERY_TEMPLATE.replaceAll("__LIMIT__", String(resolvedLimit)) + : recipe.query_template === "open_contracts_confirmed_as_of_balance_profile" ? (() => { const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0 ? toDateTimeExpr(filters.as_of_date, true) @@ -1825,10 +1917,10 @@ function buildAddressRecipePlan(recipe, filters) { return OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE .replaceAll("__LIMIT__", String(resolvedLimit)) .replaceAll("__AS_OF_EXPR__", asOfExpr) - .replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"])) + .replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "62", "76"])) .replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort)); })() - : recipe.query_template === "receivables_confirmed_as_of_balance_profile" + : recipe.query_template === "payables_confirmed_as_of_balance_profile" ? (() => { const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0 ? toDateTimeExpr(filters.as_of_date, true) @@ -1843,20 +1935,38 @@ function buildAddressRecipePlan(recipe, filters) { return OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE .replaceAll("__LIMIT__", String(resolvedLimit)) .replaceAll("__AS_OF_EXPR__", asOfExpr) - .replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"])) + .replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["60", "76"])) .replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort)); })() - : MOVEMENTS_QUERY_TEMPLATE - .replace("__LIMIT__", String(resolvedLimit)) - .replace("__WHERE_CLAUSE__", (() => { - const extraConditions = []; - const accountCondition = buildMovementAccountCondition(filters); - if (accountCondition) { - extraConditions.push(accountCondition); - } - return buildWhereClause(filters, "Движения.Период", extraConditions); - })()) - .replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort)); + : recipe.query_template === "receivables_confirmed_as_of_balance_profile" + ? (() => { + const asOfExpr = (typeof filters.as_of_date === "string" && filters.as_of_date.trim().length > 0 + ? toDateTimeExpr(filters.as_of_date, true) + : null) ?? + (typeof filters.period_to === "string" && filters.period_to.trim().length > 0 + ? toDateTimeExpr(filters.period_to, true) + : null) ?? + (typeof filters.period_from === "string" && filters.period_from.trim().length > 0 + ? toDateTimeExpr(filters.period_from, true) + : null) ?? + "ТЕКУЩАЯДАТА()"; + return OPEN_CONTRACTS_CONFIRMED_AS_OF_QUERY_TEMPLATE + .replaceAll("__LIMIT__", String(resolvedLimit)) + .replaceAll("__AS_OF_EXPR__", asOfExpr) + .replaceAll("__OPEN_CONTRACT_ACCOUNTS_MATCH__", buildAccountPrefixPredicate("Остатки.Счет", ["62", "76"])) + .replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort)); + })() + : MOVEMENTS_QUERY_TEMPLATE + .replace("__LIMIT__", String(resolvedLimit)) + .replace("__WHERE_CLAUSE__", (() => { + const extraConditions = []; + const accountCondition = buildMovementAccountCondition(filters); + if (accountCondition) { + extraConditions.push(accountCondition); + } + return buildWhereClause(filters, "Движения.Период", extraConditions); + })()) + .replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort)); return { recipe, query, diff --git a/llm_normalizer/backend/dist/services/assistantMcpDiscoveryAnswerAdapter.js b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryAnswerAdapter.js index d9823d9..92d8fa5 100644 --- a/llm_normalizer/backend/dist/services/assistantMcpDiscoveryAnswerAdapter.js +++ b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryAnswerAdapter.js @@ -393,6 +393,9 @@ function isVendorRiskBoundaryTurn(pilot) { return action === "vendor_risk_procurement_boundary" || unsupported === "vendor_risk_procurement_boundary"; } function businessOverviewInventoryUnknownLabel(overview) { + if (overview.inventory_quality_events) { + return "рыночная ликвидационная стоимость и управленческий резерв склада"; + } if (overview.inventory_staleness_risk_proxy) { return "резервы/списания/ликвидационная стоимость склада"; } @@ -621,6 +624,24 @@ function businessOverviewVendorProcurementQualityText(overview) { } return `Procurement-concentration route за ${period} отработал по исходящим платежам на ${total}, но надежной небанковской концентрации поставщика по найденным строкам не хватает.${contractText} Полный vendor-risk аудит не подтвержден.`; } +function businessOverviewInventoryQualityEventsText(overview) { + const quality = overview.inventory_quality_events; + if (!quality) { + return null; + } + const period = quality.period_scope ?? "проверенное окно"; + const organization = overview.organization_scope ? ` по организации ${overview.organization_scope}` : ""; + const eventWindow = quality.first_event_date && quality.latest_event_date + ? ` Окно найденных событий: ${quality.first_event_date} - ${quality.latest_event_date}.` + : ""; + if (quality.evidence_status === "reviewed_no_quality_events_found") { + return `Коротко: проверил складские документы списания, оприходования, инвентаризации и переоценки${organization} за ${period}; подтвержденных событий списания/корректировки/инвентаризации/переоценки не найдено. Это сильный отрицательный сигнал по доступным документам 1С, но не рыночная ликвидационная стоимость и не управленческий резерв под неликвиды.`; + } + if (quality.evidence_status === "reviewed_inventory_control_events_only") { + return `Коротко: проверил складские quality-события${organization} за ${period}; списаний и оприходований/корректировок с суммой не найдено, но есть инвентаризации ${quality.inventory_count_rows} и переоценки ${quality.revaluation_rows}.${eventWindow} Это контрольные складские документы, а не подтвержденный резерв или рыночная ликвидационная оценка.`; + } + return `Коротко: проверил складские quality-события${organization} за ${period}; списаний ${quality.writeoff_rows} на ${quality.writeoff_amount_human_ru}, оприходований/корректировок ${quality.receipt_adjustment_rows} на ${quality.receipt_adjustment_amount_human_ru}, инвентаризаций ${quality.inventory_count_rows}, переоценок ${quality.revaluation_rows}.${eventWindow} Это подтвержденные документы 1С по складским событиям, но не самостоятельная рыночная ликвидационная стоимость и не расчет управленческого резерва.`; +} function headlineFor(mode, pilot) { const askedMonthlyBreakdown = pilot.derived_bidirectional_value_flow?.aggregation_axis === "month" || pilot.derived_value_flow?.aggregation_axis === "month"; @@ -653,6 +674,10 @@ function headlineFor(mode, pilot) { return "Нельзя точно определить, какая дебиторка просрочена, по текущему срезу 1С; есть только debt-quality proxy, но нет проверенного due-date маршрута по договорам, срокам оплаты и погашению расчетов."; } if (isInventoryReserveBoundaryTurn(pilot)) { + const inventoryQualityEventsText = businessOverviewInventoryQualityEventsText(overview); + if (inventoryQualityEventsText) { + return inventoryQualityEventsText; + } const inventoryBasis = overview.inventory_staleness_risk_proxy ? "есть только складской staleness-risk proxy по найденным строкам" : overview.inventory_position || overview.inventory_turnover_proxy @@ -724,6 +749,9 @@ function headlineFor(mode, pilot) { if (overview.inventory_staleness_risk_proxy) { families.push("staleness risk proxy склада"); } + if (overview.inventory_quality_events) { + families.push("складские quality-события"); + } const unknownFamilies = overview.accounting_financial_result ? ["аудированная/юридически подтвержденная прибыль"] : [overview.trading_margin_proxy ? "чистая прибыль/точная маржа" : "прибыль/маржа"]; @@ -946,6 +974,9 @@ function buildMustNotClaim(pilot) { claims.push("Do not present an inventory snapshot or purchase-date aging signal as turnover, obsolescence, liquidation value, or full inventory health."); claims.push("Do not present business overview inventory turnover proxy as full inventory liquidity, FIFO turnover, obsolescence analysis, or liquidation value."); claims.push("Do not present business overview inventory staleness risk proxy as confirmed obsolete stock, reserve, write-off, or liquidation value."); + if (pilot.derived_business_overview?.inventory_quality_events) { + claims.push("Do not present reviewed inventory quality events as confirmed obsolete stock, reserve policy, market liquidation value, management reserve, or full inventory health."); + } if (pilot.derived_business_overview?.top_customers?.some(isFinancialInstitutionBucket) || pilot.derived_business_overview?.top_suppliers?.some(isFinancialInstitutionBucket)) { claims.push("Do not present bank-like counterparties as ordinary customers, suppliers, revenue, procurement dependency, or business quality evidence without payment-purpose/contract proof."); @@ -1436,6 +1467,10 @@ function derivedBusinessOverviewConfirmedLines(pilot) { const proxy = overview.inventory_staleness_risk_proxy; lines.push(`Staleness risk proxy склада на ${proxy.as_of_date}: самая ранняя дата закупочного сигнала ${proxy.oldest_purchase_date}, возраст ${proxy.max_purchase_age_days} дн., sales-to-stock ${proxy.sales_to_stock_amount_ratio}x, оценка ${inventoryStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная неликвидность, не резерв и не ликвидационная стоимость.`); } + const inventoryQualityEventsText = businessOverviewInventoryQualityEventsText(overview); + if (inventoryQualityEventsText) { + lines.push(inventoryQualityEventsText.replace(/^Коротко:\s*/u, "")); + } return lines; } function businessOverviewCashSynthesisLine(overview) { @@ -1603,6 +1638,15 @@ function businessOverviewRiskSynthesisLine(overview) { if (overview.inventory_staleness_risk_proxy) { signals.push(`staleness risk proxy склада: ${inventoryStalenessRiskBandRu(overview.inventory_staleness_risk_proxy.risk_band)}, возраст ${overview.inventory_staleness_risk_proxy.max_purchase_age_days} дн.`); } + if (overview.inventory_quality_events) { + const quality = overview.inventory_quality_events; + if (quality.evidence_status === "reviewed_no_quality_events_found") { + signals.push("складские quality-события: документы списания, оприходования, инвентаризации и переоценки проверены, подтвержденных событий не найдено"); + } + else { + signals.push(`складские quality-события: списаний ${quality.writeoff_rows} на ${quality.writeoff_amount_human_ru}, оприходований/корректировок ${quality.receipt_adjustment_rows} на ${quality.receipt_adjustment_amount_human_ru}, инвентаризаций ${quality.inventory_count_rows}, переоценок ${quality.revaluation_rows}`); + } + } return signals.length > 0 ? `Риски и контуры внимания по подтвержденным данным: ${signals.join("; ")}.` : null; @@ -1616,7 +1660,8 @@ function businessOverviewExecutiveVerdictLine(overview) { overview.debt_staleness_risk_proxy || overview.inventory_position || overview.inventory_turnover_proxy || - overview.inventory_staleness_risk_proxy); + overview.inventory_staleness_risk_proxy || + overview.inventory_quality_events); const hasOperationalProfileSignal = Boolean(overview.document_activity_profile || overview.counterparty_profile || overview.contract_usage_profile); const hasExtraSignals = hasTaxDebtInventorySignals || hasOperationalProfileSignal; if (!hasCash && !hasExtraSignals) { @@ -1750,6 +1795,10 @@ function buildAssistantMcpDiscoveryAnswerDraft(pilot) { if (pilot.derived_business_overview?.inventory_staleness_risk_proxy) { pushReason(reasonCodes, "answer_contains_business_overview_inventory_staleness_risk_proxy"); } + if (pilot.derived_business_overview?.inventory_quality_events) { + pushReason(reasonCodes, "answer_contains_business_overview_inventory_quality_events"); + pushReason(reasonCodes, `answer_contains_business_overview_inventory_quality_events_${pilot.derived_business_overview.inventory_quality_events.evidence_status}`); + } if (pilot.derived_business_overview?.missing_proof_families?.length) { pushReason(reasonCodes, "answer_contains_business_overview_missing_proof_ledger"); } diff --git a/llm_normalizer/backend/dist/services/assistantMcpDiscoveryPilotExecutor.js b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryPilotExecutor.js index 1a45803..160f85f 100644 --- a/llm_normalizer/backend/dist/services/assistantMcpDiscoveryPilotExecutor.js +++ b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryPilotExecutor.js @@ -211,6 +211,16 @@ function shouldRunDebtDueDateAgingProbe(planner) { .join(" "); return /(?:debt_due_date_boundary|due[-_ ]?date|overdue|aging|просроч|срок\s+оплат|дебиторк|кредиторск)/iu.test(combined); } +function shouldRunInventoryQualityEventsProbe(planner) { + const actionFamily = toNonEmptyString(planner.data_need_graph?.action_family); + const turnActionFamily = toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.asked_action_family); + const unsupportedFamily = toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.unsupported_but_understood_family); + const proofExpectation = toNonEmptyString(planner.data_need_graph?.proof_expectation); + const combined = [actionFamily, turnActionFamily, unsupportedFamily, proofExpectation] + .filter((item) => Boolean(item)) + .join(" "); + return /(?:inventory_reserve|reserve_liquidation|liquidation|write[-_ ]?off|obsolete|obsolescence|inventory_reserve_liquidation_quality|резерв|списан|ликвидац|неликвид|обесцен)/iu.test(combined); +} function buildBusinessOverviewInventoryFilters(planner) { const meaning = planner.discovery_plan.turn_meaning_ref; const organization = toNonEmptyString(meaning?.explicit_organization_scope); @@ -3021,6 +3031,69 @@ function deriveBusinessOverviewInventoryStalenessRiskProxy(input) { inference_basis: "purchase_date_age_and_sales_to_stock_proxy_confirmed_1c_rows" }; } +function rowInventoryQualityEventType(row) { + return rowTextValue(row, ["ТипСобытия", "EventType", "event_type", "Регистратор", "Registrator", "registrator"]) ?? ""; +} +function deriveBusinessOverviewInventoryQualityEvents(input) { + const result = input.inventoryQualityEventsResult; + if (!result || result.error) { + return null; + } + let writeoffRows = 0; + let writeoffAmount = 0; + let receiptAdjustmentRows = 0; + let receiptAdjustmentAmount = 0; + let inventoryCountRows = 0; + let revaluationRows = 0; + const eventDates = []; + for (const row of result.rows) { + const eventType = rowInventoryQualityEventType(row); + const amount = rowAmountValue(row) ?? 0; + const date = rowDateValue(row); + if (date) { + eventDates.push(date); + } + if (/списан|write[-_ ]?off/iu.test(eventType)) { + writeoffRows += 1; + writeoffAmount += amount; + continue; + } + if (/оприход|receipt|positive/i.test(eventType)) { + receiptAdjustmentRows += 1; + receiptAdjustmentAmount += amount; + continue; + } + if (/инвентаризац|stocktaking|inventory count/i.test(eventType)) { + inventoryCountRows += 1; + continue; + } + if (/переоцен|revaluation/i.test(eventType)) { + revaluationRows += 1; + } + } + const sortedDates = eventDates.sort((left, right) => left.localeCompare(right)); + const evidenceStatus = writeoffRows > 0 || receiptAdjustmentRows > 0 + ? "reviewed_writeoff_or_adjustment_events_found" + : inventoryCountRows > 0 || revaluationRows > 0 + ? "reviewed_inventory_control_events_only" + : "reviewed_no_quality_events_found"; + return { + period_scope: input.periodScope, + rows_matched: result.matched_rows, + writeoff_rows: writeoffRows, + writeoff_amount: writeoffAmount, + writeoff_amount_human_ru: formatAmountHumanRu(writeoffAmount), + receipt_adjustment_rows: receiptAdjustmentRows, + receipt_adjustment_amount: receiptAdjustmentAmount, + receipt_adjustment_amount_human_ru: formatAmountHumanRu(receiptAdjustmentAmount), + inventory_count_rows: inventoryCountRows, + revaluation_rows: revaluationRows, + first_event_date: sortedDates[0] ?? null, + latest_event_date: sortedDates[sortedDates.length - 1] ?? null, + evidence_status: evidenceStatus, + inference_basis: "inventory_quality_documents_confirmed_1c_rows" + }; +} function deriveBusinessOverviewVendorProcurementQuality(input) { if (!input.rankedOutgoing || input.rankedOutgoing.ranked_values.length <= 0 || @@ -3109,10 +3182,10 @@ function buildBusinessOverviewMissingProofFamilies(input) { must_not_claim: "confirmed_overdue_debt_credit_risk_or_due_date_aging" }); } - if (missing.has("inventory_position") || + if ((missing.has("inventory_position") || missing.has("inventory_turnover_quality") || missing.has("inventory_liquidity_quality") || - missing.has("inventory_reserve_liquidation_quality")) { + missing.has("inventory_reserve_liquidation_quality")) && !input.inventoryQualityEvents) { pushUnique({ family: "inventory_reserve_liquidation_quality", current_status: input.inventoryStalenessRiskProxy @@ -3195,6 +3268,10 @@ function deriveBusinessOverview(input) { inventoryPosition, inventoryTurnoverProxy }); + const inventoryQualityEvents = deriveBusinessOverviewInventoryQualityEvents({ + inventoryQualityEventsResult: input.inventoryQualityEventsResult, + periodScope: input.periodScope + }); const vendorProcurementQuality = deriveBusinessOverviewVendorProcurementQuality({ rankedOutgoing, outgoing, @@ -3219,6 +3296,7 @@ function deriveBusinessOverview(input) { Boolean(inventoryPosition), Boolean(inventoryTurnoverProxy), Boolean(inventoryStalenessRiskProxy), + Boolean(inventoryQualityEvents), Boolean(vendorProcurementQuality) ].filter(Boolean).length; if (checkedSignalCount <= 0) { @@ -3232,11 +3310,13 @@ function deriveBusinessOverview(input) { debtDueDateAging ? null : debtOpenSettlementQuality ? "debt_due_date_aging_quality" : "debt_open_settlement_quality", taxPosition ? null : "tax_position", inventoryPosition - ? inventoryStalenessRiskProxy - ? "inventory_reserve_liquidation_quality" - : inventoryTurnoverProxy - ? "inventory_liquidity_quality" - : "inventory_turnover_quality" + ? inventoryQualityEvents + ? null + : inventoryStalenessRiskProxy + ? "inventory_reserve_liquidation_quality" + : inventoryTurnoverProxy + ? "inventory_liquidity_quality" + : "inventory_turnover_quality" : "inventory_position", inventoryPosition?.aging_signal ? null : "inventory_aging_quality" ].filter((item) => Boolean(item)); @@ -3250,6 +3330,7 @@ function deriveBusinessOverview(input) { inventoryPosition, inventoryTurnoverProxy, inventoryStalenessRiskProxy, + inventoryQualityEvents, vendorProcurementQuality, hasSupplierConcentrationSignal: (rankedOutgoing?.ranked_values.length ?? 0) > 0 }); @@ -3275,6 +3356,7 @@ function deriveBusinessOverview(input) { inventory_position: inventoryPosition, inventory_turnover_proxy: inventoryTurnoverProxy, inventory_staleness_risk_proxy: inventoryStalenessRiskProxy, + inventory_quality_events: inventoryQualityEvents, document_activity_profile: documentActivityProfile, counterparty_profile: counterpartyProfile, contract_usage_profile: contractUsageProfile, @@ -3283,7 +3365,7 @@ function deriveBusinessOverview(input) { checked_signal_count: checkedSignalCount, missing_signal_families: missingSignalFamilies, missing_proof_families: missingProofFamilies, - inference_basis: hasBusinessOverviewProfileSignal || inventoryPosition || accountingFinancialResult + inference_basis: hasBusinessOverviewProfileSignal || inventoryPosition || inventoryQualityEvents || accountingFinancialResult ? "business_overview_from_confirmed_1c_multi_family_rows" : debtOpenSettlementQuality || debtDueDateAging ? "business_overview_from_confirmed_1c_multi_family_rows" @@ -3343,6 +3425,9 @@ function summarizeBusinessOverviewRows(input) { if (input.inventoryAgingResult && !input.inventoryAgingResult.error) { parts.push(`${input.inventoryAgingResult.fetched_rows} inventory aging rows fetched, ${input.inventoryAgingResult.matched_rows} matched`); } + if (input.inventoryQualityEventsResult && !input.inventoryQualityEventsResult.error) { + parts.push(`${input.inventoryQualityEventsResult.fetched_rows} inventory quality-event rows fetched, ${input.inventoryQualityEventsResult.matched_rows} matched`); + } return parts.length > 0 ? parts.join("; ") : null; } function buildBusinessOverviewConfirmedFacts(derived) { @@ -3511,6 +3596,18 @@ function buildBusinessOverviewConfirmedFacts(derived) { const proxy = derived.inventory_staleness_risk_proxy; facts.push(`Staleness risk proxy склада на ${proxy.as_of_date}: самая ранняя дата закупочного сигнала ${proxy.oldest_purchase_date}, возраст ${proxy.max_purchase_age_days} дн., sales-to-stock ${proxy.sales_to_stock_amount_ratio}x, оценка ${inventoryStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная неликвидность, не резерв и не ликвидационная стоимость.`); } + if (derived.inventory_quality_events) { + const quality = derived.inventory_quality_events; + const eventWindow = quality.first_event_date && quality.latest_event_date + ? ` Окно найденных событий: ${quality.first_event_date} — ${quality.latest_event_date}.` + : ""; + if (quality.evidence_status === "reviewed_no_quality_events_found") { + facts.push(`Reviewed inventory quality route проверил складские документы списания, оприходования, инвентаризации и переоценки${period}: подтвержденных событий не найдено. Это проверенный отрицательный результат по доступным документам, но не рыночная ликвидационная оценка и не управленческий резерв.`); + } + else { + facts.push(`Reviewed inventory quality route проверил складские документы${period}: списаний ${quality.writeoff_rows} на ${quality.writeoff_amount_human_ru}, оприходований/корректировок ${quality.receipt_adjustment_rows} на ${quality.receipt_adjustment_amount_human_ru}, инвентаризаций ${quality.inventory_count_rows}, переоценок ${quality.revaluation_rows}.${eventWindow} Это подтвержденные документы 1С, но не самостоятельная рыночная ликвидационная стоимость.`); + } + } return facts; } function buildBusinessOverviewInferredFacts(derived) { @@ -4218,6 +4315,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) { let contractUsageProfileResult = null; let inventoryOnHandResult = null; let inventoryAgingResult = null; + let inventoryQualityEventsResult = null; const valueFilters = buildValueFlowFilters(planner); const lifecycleFilters = buildLifecycleFilters(planner); const profileFilters = buildBusinessOverviewProfileFilters(planner); @@ -4227,6 +4325,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) { const debtFilters = buildBusinessOverviewDebtFilters(planner); const debtDueDateAgingProbeEnabled = shouldRunDebtDueDateAgingProbe(planner); const inventoryFilters = buildBusinessOverviewInventoryFilters(planner); + const inventoryQualityEventsProbeEnabled = shouldRunInventoryQualityEventsProbe(planner); const debtAsOfDate = toNonEmptyString(debtFilters?.as_of_date); const inventoryAsOfDate = toNonEmptyString(inventoryFilters?.as_of_date); const incomingSelection = (0, addressRecipeCatalog_1.selectAddressRecipe)("customer_revenue_and_payments", valueFilters); @@ -4262,6 +4361,9 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) { const inventoryAgingSelection = inventoryFilters ? (0, addressRecipeCatalog_1.selectAddressRecipe)("inventory_aging_by_purchase_date", inventoryFilters) : null; + const inventoryQualityEventsSelection = inventoryQualityEventsProbeEnabled + ? (0, addressRecipeCatalog_1.selectAddressRecipe)("inventory_quality_events_for_organization", inventoryFilters ?? buildBusinessOverviewProfileFilters(planner)) + : null; if (!incomingSelection.selected_recipe || !outgoingSelection.selected_recipe || !lifecycleSelection.selected_recipe) { pushReason(reasonCodes, "pilot_business_overview_recipe_not_available"); const missing = [ @@ -4389,6 +4491,16 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) { pushReason(reasonCodes, "pilot_business_overview_inventory_recipe_not_available"); pushUnique(queryLimitations, "Business overview inventory-position probe requires an executable inventory on-hand as-of-date recipe"); } + if (inventoryQualityEventsSelection?.selected_recipe) { + pushReason(reasonCodes, "pilot_business_overview_inventory_quality_events_recipe_selected"); + } + else if (!inventoryQualityEventsProbeEnabled) { + pushReason(reasonCodes, "pilot_business_overview_inventory_quality_events_probe_skipped_without_boundary_need"); + } + else { + pushReason(reasonCodes, "pilot_business_overview_inventory_quality_events_recipe_not_available"); + pushUnique(queryLimitations, "Business overview inventory quality probe requires an executable inventory quality-events recipe"); + } for (const step of dryRun.execution_steps) { if (step.primitive_id === "query_movements") { const incomingExecution = await executeCoverageAwareValueFlowQuery({ @@ -4622,6 +4734,16 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) { }); probeResults.push(queryResultToProbeResult(step.primitive_id, contractUsageProfileResult)); } + if (inventoryQualityEventsSelection?.selected_recipe) { + const inventoryQualityEventsFilters = inventoryFilters ?? buildBusinessOverviewProfileFilters(planner); + const inventoryQualityEventsPlan = (0, addressRecipeCatalog_1.buildAddressRecipePlan)(inventoryQualityEventsSelection.selected_recipe, inventoryQualityEventsFilters); + inventoryQualityEventsResult = await runtimeDeps.executeAddressMcpQuery({ + query: inventoryQualityEventsPlan.query, + limit: inventoryQualityEventsPlan.limit, + account_scope: inventoryQualityEventsPlan.account_scope + }); + probeResults.push(queryResultToProbeResult(step.primitive_id, inventoryQualityEventsResult)); + } if (lifecycleResult.error) { pushUnique(queryLimitations, lifecycleResult.error); pushReason(reasonCodes, "pilot_business_overview_query_documents_mcp_error"); @@ -4657,6 +4779,13 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) { else if (contractUsageProfileResult) { pushReason(reasonCodes, "pilot_business_overview_contract_usage_profile_query_mcp_executed"); } + if (inventoryQualityEventsResult?.error) { + pushUnique(queryLimitations, inventoryQualityEventsResult.error); + pushReason(reasonCodes, "pilot_business_overview_inventory_quality_events_query_mcp_error"); + } + else if (inventoryQualityEventsResult) { + pushReason(reasonCodes, "pilot_business_overview_inventory_quality_events_query_mcp_executed"); + } continue; } skippedPrimitives.push(step.primitive_id); @@ -4679,6 +4808,7 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) { debtAsOfDate, inventoryOnHandResult, inventoryAgingResult, + inventoryQualityEventsResult, inventoryAsOfDate, organizationScope, periodScope: dateScope @@ -4744,6 +4874,10 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) { if (derivedBusinessOverview.inventory_staleness_risk_proxy) { pushReason(reasonCodes, "pilot_derived_business_overview_inventory_staleness_risk_proxy_from_confirmed_rows"); } + if (derivedBusinessOverview.inventory_quality_events) { + pushReason(reasonCodes, "pilot_derived_business_overview_inventory_quality_events_from_reviewed_rows"); + pushReason(reasonCodes, `pilot_derived_business_overview_inventory_quality_events_${derivedBusinessOverview.inventory_quality_events.evidence_status}`); + } if (derivedBusinessOverview.missing_proof_families.length > 0) { pushReason(reasonCodes, "pilot_business_overview_missing_proof_families_recorded"); } @@ -4763,7 +4897,8 @@ async function executeAssistantMcpDiscoveryPilot(planner, deps = DEFAULT_DEPS) { counterpartyProfileResult, contractUsageProfileResult, inventoryOnHandResult, - inventoryAgingResult + inventoryAgingResult, + inventoryQualityEventsResult }); const evidence = (0, assistantMcpDiscoveryPolicy_1.resolveAssistantMcpDiscoveryEvidence)({ plan: planner.discovery_plan, diff --git a/llm_normalizer/backend/dist/services/assistantMcpDiscoveryResponseCandidate.js b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryResponseCandidate.js index c8ae7af..0e319a1 100644 --- a/llm_normalizer/backend/dist/services/assistantMcpDiscoveryResponseCandidate.js +++ b/llm_normalizer/backend/dist/services/assistantMcpDiscoveryResponseCandidate.js @@ -830,10 +830,18 @@ function buildCompactBusinessOverviewReply(entryPoint, draft) { } if (inventoryReserveBoundary) { const headline = toNonEmptyString(draft.headline); + const inventoryQualityEvents = toRecordObject(overview.inventory_quality_events); const cleanHeadline = headline?.replace(/^Коротко:\s*/iu, "").trim(); lines.push(cleanHeadline ? `Коротко: ${localizeLine(cleanHeadline)}` : "Коротко: точно подтвердить резерв под неликвиды по текущим данным нельзя."); + if (inventoryQualityEvents) { + if (limitLine) { + lines.push(limitLine); + } + const reply = lines.join("\n").trim(); + return reply.length > 0 && !hasInternalMechanics(reply) ? reply : null; + } const boundaryLines = userFacingLines([ ...toStringList(draft.unknown_lines), ...toStringList(draft.limitation_lines) diff --git a/llm_normalizer/backend/src/services/addressRecipeCatalog.ts b/llm_normalizer/backend/src/services/addressRecipeCatalog.ts index 150225b..050430f 100644 --- a/llm_normalizer/backend/src/services/addressRecipeCatalog.ts +++ b/llm_normalizer/backend/src/services/addressRecipeCatalog.ts @@ -296,6 +296,62 @@ const INVENTORY_ON_HAND_AS_OF_QUERY_TEMPLATE = ` Количество __ORDER_DIRECTION__ `; +const INVENTORY_QUALITY_EVENTS_QUERY_TEMPLATE = ` +ВЫБРАТЬ ПЕРВЫЕ __LIMIT__ + Списание.Дата КАК Период, + ПРЕДСТАВЛЕНИЕ(Списание.Ссылка) КАК Регистратор, + "Списание товаров" КАК ТипСобытия, + ПРЕДСТАВЛЕНИЕ(Списание.Организация) КАК Организация, + ПРЕДСТАВЛЕНИЕ(Списание.Склад) КАК Склад, + Списание.СуммаДокумента КАК Сумма, + Списание.Основание КАК Основание, + Списание.Комментарий КАК Комментарий +ИЗ + Документ.СписаниеТоваров КАК Списание +__WHERE_WRITE_OFF__ +ОБЪЕДИНИТЬ ВСЕ +ВЫБРАТЬ ПЕРВЫЕ __LIMIT__ + Оприходование.Дата КАК Период, + ПРЕДСТАВЛЕНИЕ(Оприходование.Ссылка) КАК Регистратор, + "Оприходование товаров" КАК ТипСобытия, + ПРЕДСТАВЛЕНИЕ(Оприходование.Организация) КАК Организация, + ПРЕДСТАВЛЕНИЕ(Оприходование.Склад) КАК Склад, + Оприходование.СуммаДокумента КАК Сумма, + Оприходование.Основание КАК Основание, + Оприходование.Комментарий КАК Комментарий +ИЗ + Документ.ОприходованиеТоваров КАК Оприходование +__WHERE_RECEIPT__ +ОБЪЕДИНИТЬ ВСЕ +ВЫБРАТЬ ПЕРВЫЕ __LIMIT__ + Инвентаризация.Дата КАК Период, + ПРЕДСТАВЛЕНИЕ(Инвентаризация.Ссылка) КАК Регистратор, + "Инвентаризация товаров на складе" КАК ТипСобытия, + ПРЕДСТАВЛЕНИЕ(Инвентаризация.Организация) КАК Организация, + ПРЕДСТАВЛЕНИЕ(Инвентаризация.Склад) КАК Склад, + 0 КАК Сумма, + Инвентаризация.ПричинаПроведенияИнвентаризации КАК Основание, + Инвентаризация.Комментарий КАК Комментарий +ИЗ + Документ.ИнвентаризацияТоваровНаСкладе КАК Инвентаризация +__WHERE_INVENTORY_COUNT__ +ОБЪЕДИНИТЬ ВСЕ +ВЫБРАТЬ ПЕРВЫЕ __LIMIT__ + Переоценка.Дата КАК Период, + ПРЕДСТАВЛЕНИЕ(Переоценка.Ссылка) КАК Регистратор, + "Переоценка товаров в рознице" КАК ТипСобытия, + ПРЕДСТАВЛЕНИЕ(Переоценка.Организация) КАК Организация, + ПРЕДСТАВЛЕНИЕ(Переоценка.Склад) КАК Склад, + 0 КАК Сумма, + "" КАК Основание, + Переоценка.Комментарий КАК Комментарий +ИЗ + Документ.ПереоценкаТоваровВРознице КАК Переоценка +__WHERE_REVALUATION__ +УПОРЯДОЧИТЬ ПО + Период __ORDER_DIRECTION__ +`; + const BANK_DOCS_QUERY_TEMPLATE = ` ВЫБРАТЬ ПЕРВЫЕ __LIMIT__ БанкСписание.Дата КАК Период, @@ -958,6 +1014,16 @@ const BASE_RECIPES: AddressRecipeDefinition[] = [ account_scope_mode: "strict", query_template: "inventory_aging_by_purchase_date_profile" }, + { + recipe_id: "address_inventory_quality_events_for_organization_v1", + intent: "inventory_quality_events_for_organization", + purpose: "Check posted inventory quality event documents: write-offs, stocktaking, receipt adjustments, and retail revaluation", + required_filters: [], + optional_filters: ["as_of_date", "period_from", "period_to", "organization", "warehouse", "limit", "sort"], + default_limit: 400, + account_scope_mode: "preferred", + query_template: "inventory_quality_events_profile" + }, { recipe_id: "address_open_contracts_confirmed_as_of_date_v1", intent: "open_contracts_confirmed_as_of_date", @@ -1432,6 +1498,77 @@ function buildInventoryMovementQuery( .replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort)); } +function buildWarehouseReferenceCondition(filters: AddressFilterSet, fieldPaths: string[]): string | null { + const warehouse = typeof filters.warehouse === "string" ? filters.warehouse.trim() : ""; + if (!warehouse) { + return null; + } + const tokens = Array.from( + new Set( + warehouse + .split(/[^A-Za-zА-Яа-яЁё0-9]+/u) + .map((token) => token.trim()) + .filter((token) => token.length >= 3) + .filter((token) => !["склад", "warehouse"].includes(token.toLowerCase())) + ) + ); + const effectiveTokens = tokens.length > 0 ? tokens : [warehouse]; + const clauses = fieldPaths + .map((fieldPath) => String(fieldPath ?? "").trim()) + .filter((fieldPath) => fieldPath.length > 0) + .map((fieldPath) => { + const tokenConditions = effectiveTokens.map((token) => { + const escapedToken = toQueryStringLiteral(token); + return `${fieldPath}.Наименование ПОДОБНО "%${escapedToken}%"`; + }); + return tokenConditions.length === 1 ? tokenConditions[0] : `(${tokenConditions.join(" И ")})`; + }); + if (clauses.length === 0) { + return null; + } + return clauses.length === 1 ? clauses[0] : `(${clauses.join(" ИЛИ ")})`; +} + +function buildInventoryQualityDocumentWhereClause( + filters: AddressFilterSet, + dateFieldPath: string, + organizationFieldPath: string, + warehouseFieldPath: string +): string { + return buildWhereClause(filters, dateFieldPath, [ + `${dateFieldPath.replace(/\.Дата$/u, ".Проведен")} = ИСТИНА`, + buildOrganizationReferenceCondition(filters, [organizationFieldPath]), + buildWarehouseReferenceCondition(filters, [warehouseFieldPath]) + ].filter((item): item is string => Boolean(item))); +} + +function buildInventoryQualityEventsQuery(filters: AddressFilterSet, resolvedLimit: number): string { + return INVENTORY_QUALITY_EVENTS_QUERY_TEMPLATE + .replaceAll("__LIMIT__", String(resolvedLimit)) + .replace( + "__WHERE_WRITE_OFF__", + buildInventoryQualityDocumentWhereClause(filters, "Списание.Дата", "Списание.Организация", "Списание.Склад") + ) + .replace( + "__WHERE_RECEIPT__", + buildInventoryQualityDocumentWhereClause(filters, "Оприходование.Дата", "Оприходование.Организация", "Оприходование.Склад") + ) + .replace( + "__WHERE_INVENTORY_COUNT__", + buildInventoryQualityDocumentWhereClause( + filters, + "Инвентаризация.Дата", + "Инвентаризация.Организация", + "Инвентаризация.Склад" + ) + ) + .replace( + "__WHERE_REVALUATION__", + buildInventoryQualityDocumentWhereClause(filters, "Переоценка.Дата", "Переоценка.Организация", "Переоценка.Склад") + ) + .replaceAll("__ORDER_DIRECTION__", resolveOrderDirection(filters.sort)); +} + function buildInventoryItemReferenceCondition(filters: AddressFilterSet, fieldPaths: string[]): string | null { const item = typeof filters.item === "string" ? filters.item.trim() : ""; if (!item) { @@ -1783,6 +1920,7 @@ function maxLimitForIntent(intent: AddressIntent): number { intent === "inventory_profitability_for_item" || intent === "inventory_purchase_to_sale_chain" || intent === "inventory_aging_by_purchase_date" || + intent === "inventory_quality_events_for_organization" || intent === "open_contracts_confirmed_as_of_date" || intent === "list_contracts_by_counterparty" || intent === "list_documents_by_counterparty" || @@ -2037,6 +2175,8 @@ export function buildAddressRecipePlan( ? buildInventoryPurchaseToSaleDocumentQuery(filters, resolvedLimit) : recipe.query_template === "inventory_aging_by_purchase_date_profile" ? buildInventoryMovementQuery(filters, resolvedLimit, "dt") + : recipe.query_template === "inventory_quality_events_profile" + ? buildInventoryQualityEventsQuery(filters, resolvedLimit) : recipe.query_template === "contracts_by_counterparty_profile" ? CONTRACTS_BY_COUNTERPARTY_QUERY_TEMPLATE.replaceAll("__LIMIT__", String(resolvedLimit)) : recipe.query_template === "open_contracts_confirmed_as_of_balance_profile" diff --git a/llm_normalizer/backend/src/services/assistantMcpDiscoveryAnswerAdapter.ts b/llm_normalizer/backend/src/services/assistantMcpDiscoveryAnswerAdapter.ts index 73538e8..aa58759 100644 --- a/llm_normalizer/backend/src/services/assistantMcpDiscoveryAnswerAdapter.ts +++ b/llm_normalizer/backend/src/services/assistantMcpDiscoveryAnswerAdapter.ts @@ -510,6 +510,9 @@ function isVendorRiskBoundaryTurn(pilot: AssistantMcpDiscoveryPilotExecutionCont } function businessOverviewInventoryUnknownLabel(overview: BusinessOverview): string { + if (overview.inventory_quality_events) { + return "рыночная ликвидационная стоимость и управленческий резерв склада"; + } if (overview.inventory_staleness_risk_proxy) { return "резервы/списания/ликвидационная стоимость склада"; } @@ -764,6 +767,26 @@ function businessOverviewVendorProcurementQualityText(overview: BusinessOverview return `Procurement-concentration route за ${period} отработал по исходящим платежам на ${total}, но надежной небанковской концентрации поставщика по найденным строкам не хватает.${contractText} Полный vendor-risk аудит не подтвержден.`; } +function businessOverviewInventoryQualityEventsText(overview: BusinessOverview): string | null { + const quality = overview.inventory_quality_events; + if (!quality) { + return null; + } + const period = quality.period_scope ?? "проверенное окно"; + const organization = overview.organization_scope ? ` по организации ${overview.organization_scope}` : ""; + const eventWindow = + quality.first_event_date && quality.latest_event_date + ? ` Окно найденных событий: ${quality.first_event_date} - ${quality.latest_event_date}.` + : ""; + if (quality.evidence_status === "reviewed_no_quality_events_found") { + return `Коротко: проверил складские документы списания, оприходования, инвентаризации и переоценки${organization} за ${period}; подтвержденных событий списания/корректировки/инвентаризации/переоценки не найдено. Это сильный отрицательный сигнал по доступным документам 1С, но не рыночная ликвидационная стоимость и не управленческий резерв под неликвиды.`; + } + if (quality.evidence_status === "reviewed_inventory_control_events_only") { + return `Коротко: проверил складские quality-события${organization} за ${period}; списаний и оприходований/корректировок с суммой не найдено, но есть инвентаризации ${quality.inventory_count_rows} и переоценки ${quality.revaluation_rows}.${eventWindow} Это контрольные складские документы, а не подтвержденный резерв или рыночная ликвидационная оценка.`; + } + return `Коротко: проверил складские quality-события${organization} за ${period}; списаний ${quality.writeoff_rows} на ${quality.writeoff_amount_human_ru}, оприходований/корректировок ${quality.receipt_adjustment_rows} на ${quality.receipt_adjustment_amount_human_ru}, инвентаризаций ${quality.inventory_count_rows}, переоценок ${quality.revaluation_rows}.${eventWindow} Это подтвержденные документы 1С по складским событиям, но не самостоятельная рыночная ликвидационная стоимость и не расчет управленческого резерва.`; +} + function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpDiscoveryPilotExecutionContract): string { const askedMonthlyBreakdown = pilot.derived_bidirectional_value_flow?.aggregation_axis === "month" || @@ -797,6 +820,10 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD return "Нельзя точно определить, какая дебиторка просрочена, по текущему срезу 1С; есть только debt-quality proxy, но нет проверенного due-date маршрута по договорам, срокам оплаты и погашению расчетов."; } if (isInventoryReserveBoundaryTurn(pilot)) { + const inventoryQualityEventsText = businessOverviewInventoryQualityEventsText(overview); + if (inventoryQualityEventsText) { + return inventoryQualityEventsText; + } const inventoryBasis = overview.inventory_staleness_risk_proxy ? "есть только складской staleness-risk proxy по найденным строкам" : overview.inventory_position || overview.inventory_turnover_proxy @@ -870,6 +897,9 @@ function headlineFor(mode: AssistantMcpDiscoveryAnswerMode, pilot: AssistantMcpD if (overview.inventory_staleness_risk_proxy) { families.push("staleness risk proxy склада"); } + if (overview.inventory_quality_events) { + families.push("складские quality-события"); + } const unknownFamilies = overview.accounting_financial_result ? ["аудированная/юридически подтвержденная прибыль"] : [overview.trading_margin_proxy ? "чистая прибыль/точная маржа" : "прибыль/маржа"]; @@ -1101,6 +1131,9 @@ function buildMustNotClaim(pilot: AssistantMcpDiscoveryPilotExecutionContract): claims.push("Do not present an inventory snapshot or purchase-date aging signal as turnover, obsolescence, liquidation value, or full inventory health."); claims.push("Do not present business overview inventory turnover proxy as full inventory liquidity, FIFO turnover, obsolescence analysis, or liquidation value."); claims.push("Do not present business overview inventory staleness risk proxy as confirmed obsolete stock, reserve, write-off, or liquidation value."); + if (pilot.derived_business_overview?.inventory_quality_events) { + claims.push("Do not present reviewed inventory quality events as confirmed obsolete stock, reserve policy, market liquidation value, management reserve, or full inventory health."); + } if ( pilot.derived_business_overview?.top_customers?.some(isFinancialInstitutionBucket) || pilot.derived_business_overview?.top_suppliers?.some(isFinancialInstitutionBucket) @@ -1676,6 +1709,10 @@ function derivedBusinessOverviewConfirmedLines(pilot: AssistantMcpDiscoveryPilot `Staleness risk proxy склада на ${proxy.as_of_date}: самая ранняя дата закупочного сигнала ${proxy.oldest_purchase_date}, возраст ${proxy.max_purchase_age_days} дн., sales-to-stock ${proxy.sales_to_stock_amount_ratio}x, оценка ${inventoryStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная неликвидность, не резерв и не ликвидационная стоимость.` ); } + const inventoryQualityEventsText = businessOverviewInventoryQualityEventsText(overview); + if (inventoryQualityEventsText) { + lines.push(inventoryQualityEventsText.replace(/^Коротко:\s*/u, "")); + } return lines; } @@ -1858,6 +1895,16 @@ function businessOverviewRiskSynthesisLine(overview: BusinessOverview): string | `staleness risk proxy склада: ${inventoryStalenessRiskBandRu(overview.inventory_staleness_risk_proxy.risk_band)}, возраст ${overview.inventory_staleness_risk_proxy.max_purchase_age_days} дн.` ); } + if (overview.inventory_quality_events) { + const quality = overview.inventory_quality_events; + if (quality.evidence_status === "reviewed_no_quality_events_found") { + signals.push("складские quality-события: документы списания, оприходования, инвентаризации и переоценки проверены, подтвержденных событий не найдено"); + } else { + signals.push( + `складские quality-события: списаний ${quality.writeoff_rows} на ${quality.writeoff_amount_human_ru}, оприходований/корректировок ${quality.receipt_adjustment_rows} на ${quality.receipt_adjustment_amount_human_ru}, инвентаризаций ${quality.inventory_count_rows}, переоценок ${quality.revaluation_rows}` + ); + } + } return signals.length > 0 ? `Риски и контуры внимания по подтвержденным данным: ${signals.join("; ")}.` : null; @@ -1873,7 +1920,8 @@ function businessOverviewExecutiveVerdictLine(overview: BusinessOverview): strin overview.debt_staleness_risk_proxy || overview.inventory_position || overview.inventory_turnover_proxy || - overview.inventory_staleness_risk_proxy + overview.inventory_staleness_risk_proxy || + overview.inventory_quality_events ); const hasOperationalProfileSignal = Boolean( overview.document_activity_profile || overview.counterparty_profile || overview.contract_usage_profile @@ -2020,6 +2068,10 @@ export function buildAssistantMcpDiscoveryAnswerDraft( if (pilot.derived_business_overview?.inventory_staleness_risk_proxy) { pushReason(reasonCodes, "answer_contains_business_overview_inventory_staleness_risk_proxy"); } + if (pilot.derived_business_overview?.inventory_quality_events) { + pushReason(reasonCodes, "answer_contains_business_overview_inventory_quality_events"); + pushReason(reasonCodes, `answer_contains_business_overview_inventory_quality_events_${pilot.derived_business_overview.inventory_quality_events.evidence_status}`); + } if (pilot.derived_business_overview?.missing_proof_families?.length) { pushReason(reasonCodes, "answer_contains_business_overview_missing_proof_ledger"); } diff --git a/llm_normalizer/backend/src/services/assistantMcpDiscoveryPilotExecutor.ts b/llm_normalizer/backend/src/services/assistantMcpDiscoveryPilotExecutor.ts index 4cb886d..fbb6e06 100644 --- a/llm_normalizer/backend/src/services/assistantMcpDiscoveryPilotExecutor.ts +++ b/llm_normalizer/backend/src/services/assistantMcpDiscoveryPilotExecutor.ts @@ -265,6 +265,7 @@ export interface AssistantMcpDiscoveryDerivedBusinessOverview { inventory_position: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryPosition | null; inventory_turnover_proxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryTurnoverProxy | null; inventory_staleness_risk_proxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryStalenessRiskProxy | null; + inventory_quality_events: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryQualityEvents | null; document_activity_profile: AssistantMcpDiscoveryDerivedBusinessOverviewDocumentActivityProfile | null; counterparty_profile: AssistantMcpDiscoveryDerivedBusinessOverviewCounterpartyProfile | null; contract_usage_profile: AssistantMcpDiscoveryDerivedBusinessOverviewContractUsageProfile | null; @@ -521,6 +522,26 @@ export interface AssistantMcpDiscoveryDerivedBusinessOverviewInventoryStalenessR inference_basis: "purchase_date_age_and_sales_to_stock_proxy_confirmed_1c_rows"; } +export interface AssistantMcpDiscoveryDerivedBusinessOverviewInventoryQualityEvents { + period_scope: string | null; + rows_matched: number; + writeoff_rows: number; + writeoff_amount: number; + writeoff_amount_human_ru: string; + receipt_adjustment_rows: number; + receipt_adjustment_amount: number; + receipt_adjustment_amount_human_ru: string; + inventory_count_rows: number; + revaluation_rows: number; + first_event_date: string | null; + latest_event_date: string | null; + evidence_status: + | "reviewed_no_quality_events_found" + | "reviewed_writeoff_or_adjustment_events_found" + | "reviewed_inventory_control_events_only"; + inference_basis: "inventory_quality_documents_confirmed_1c_rows"; +} + export interface AssistantMcpDiscoveryDerivedMetadataSurface { metadata_scope: string | null; requested_meta_types: string[]; @@ -829,6 +850,17 @@ function shouldRunDebtDueDateAgingProbe(planner: AssistantMcpDiscoveryPlannerCon return /(?:debt_due_date_boundary|due[-_ ]?date|overdue|aging|просроч|срок\s+оплат|дебиторк|кредиторск)/iu.test(combined); } +function shouldRunInventoryQualityEventsProbe(planner: AssistantMcpDiscoveryPlannerContract): boolean { + const actionFamily = toNonEmptyString(planner.data_need_graph?.action_family); + const turnActionFamily = toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.asked_action_family); + const unsupportedFamily = toNonEmptyString(planner.discovery_plan.turn_meaning_ref?.unsupported_but_understood_family); + const proofExpectation = toNonEmptyString(planner.data_need_graph?.proof_expectation); + const combined = [actionFamily, turnActionFamily, unsupportedFamily, proofExpectation] + .filter((item): item is string => Boolean(item)) + .join(" "); + return /(?:inventory_reserve|reserve_liquidation|liquidation|write[-_ ]?off|obsolete|obsolescence|inventory_reserve_liquidation_quality|резерв|списан|ликвидац|неликвид|обесцен)/iu.test(combined); +} + function buildBusinessOverviewInventoryFilters(planner: AssistantMcpDiscoveryPlannerContract): AddressFilterSet | null { const meaning = planner.discovery_plan.turn_meaning_ref; const organization = toNonEmptyString(meaning?.explicit_organization_scope); @@ -4148,6 +4180,79 @@ function deriveBusinessOverviewInventoryStalenessRiskProxy(input: { }; } +function rowInventoryQualityEventType(row: Record): string { + return rowTextValue(row, ["ТипСобытия", "EventType", "event_type", "Регистратор", "Registrator", "registrator"]) ?? ""; +} + +function deriveBusinessOverviewInventoryQualityEvents(input: { + inventoryQualityEventsResult: AddressMcpQueryExecutorResult | null; + periodScope: string | null; +}): AssistantMcpDiscoveryDerivedBusinessOverviewInventoryQualityEvents | null { + const result = input.inventoryQualityEventsResult; + if (!result || result.error) { + return null; + } + + let writeoffRows = 0; + let writeoffAmount = 0; + let receiptAdjustmentRows = 0; + let receiptAdjustmentAmount = 0; + let inventoryCountRows = 0; + let revaluationRows = 0; + const eventDates: string[] = []; + + for (const row of result.rows) { + const eventType = rowInventoryQualityEventType(row); + const amount = rowAmountValue(row) ?? 0; + const date = rowDateValue(row); + if (date) { + eventDates.push(date); + } + if (/списан|write[-_ ]?off/iu.test(eventType)) { + writeoffRows += 1; + writeoffAmount += amount; + continue; + } + if (/оприход|receipt|positive/i.test(eventType)) { + receiptAdjustmentRows += 1; + receiptAdjustmentAmount += amount; + continue; + } + if (/инвентаризац|stocktaking|inventory count/i.test(eventType)) { + inventoryCountRows += 1; + continue; + } + if (/переоцен|revaluation/i.test(eventType)) { + revaluationRows += 1; + } + } + + const sortedDates = eventDates.sort((left, right) => left.localeCompare(right)); + const evidenceStatus: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryQualityEvents["evidence_status"] = + writeoffRows > 0 || receiptAdjustmentRows > 0 + ? "reviewed_writeoff_or_adjustment_events_found" + : inventoryCountRows > 0 || revaluationRows > 0 + ? "reviewed_inventory_control_events_only" + : "reviewed_no_quality_events_found"; + + return { + period_scope: input.periodScope, + rows_matched: result.matched_rows, + writeoff_rows: writeoffRows, + writeoff_amount: writeoffAmount, + writeoff_amount_human_ru: formatAmountHumanRu(writeoffAmount), + receipt_adjustment_rows: receiptAdjustmentRows, + receipt_adjustment_amount: receiptAdjustmentAmount, + receipt_adjustment_amount_human_ru: formatAmountHumanRu(receiptAdjustmentAmount), + inventory_count_rows: inventoryCountRows, + revaluation_rows: revaluationRows, + first_event_date: sortedDates[0] ?? null, + latest_event_date: sortedDates[sortedDates.length - 1] ?? null, + evidence_status: evidenceStatus, + inference_basis: "inventory_quality_documents_confirmed_1c_rows" + }; +} + function deriveBusinessOverviewVendorProcurementQuality(input: { rankedOutgoing: AssistantMcpDiscoveryDerivedRankedValueFlow | null; outgoing: AssistantMcpDiscoveryValueFlowSideSummary; @@ -4226,6 +4331,7 @@ function buildBusinessOverviewMissingProofFamilies(input: { inventoryPosition: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryPosition | null; inventoryTurnoverProxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryTurnoverProxy | null; inventoryStalenessRiskProxy: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryStalenessRiskProxy | null; + inventoryQualityEvents: AssistantMcpDiscoveryDerivedBusinessOverviewInventoryQualityEvents | null; vendorProcurementQuality: AssistantMcpDiscoveryDerivedBusinessOverviewVendorProcurementQuality | null; hasSupplierConcentrationSignal: boolean; }): AssistantMcpDiscoveryBusinessOverviewMissingProofFamily[] { @@ -4267,12 +4373,12 @@ function buildBusinessOverviewMissingProofFamilies(input: { }); } - if ( + if (( missing.has("inventory_position") || missing.has("inventory_turnover_quality") || missing.has("inventory_liquidity_quality") || missing.has("inventory_reserve_liquidation_quality") - ) { + ) && !input.inventoryQualityEvents) { pushUnique({ family: "inventory_reserve_liquidation_quality", current_status: input.inventoryStalenessRiskProxy @@ -4322,6 +4428,7 @@ function deriveBusinessOverview(input: { debtAsOfDate: string | null; inventoryOnHandResult: AddressMcpQueryExecutorResult | null; inventoryAgingResult: AddressMcpQueryExecutorResult | null; + inventoryQualityEventsResult: AddressMcpQueryExecutorResult | null; inventoryAsOfDate: string | null; organizationScope: string | null; periodScope: string | null; @@ -4390,6 +4497,10 @@ function deriveBusinessOverview(input: { inventoryPosition, inventoryTurnoverProxy }); + const inventoryQualityEvents = deriveBusinessOverviewInventoryQualityEvents({ + inventoryQualityEventsResult: input.inventoryQualityEventsResult, + periodScope: input.periodScope + }); const vendorProcurementQuality = deriveBusinessOverviewVendorProcurementQuality({ rankedOutgoing, outgoing, @@ -4414,6 +4525,7 @@ function deriveBusinessOverview(input: { Boolean(inventoryPosition), Boolean(inventoryTurnoverProxy), Boolean(inventoryStalenessRiskProxy), + Boolean(inventoryQualityEvents), Boolean(vendorProcurementQuality) ].filter(Boolean).length; if (checkedSignalCount <= 0) { @@ -4430,7 +4542,9 @@ function deriveBusinessOverview(input: { debtDueDateAging ? null : debtOpenSettlementQuality ? "debt_due_date_aging_quality" : "debt_open_settlement_quality", taxPosition ? null : "tax_position", inventoryPosition - ? inventoryStalenessRiskProxy + ? inventoryQualityEvents + ? null + : inventoryStalenessRiskProxy ? "inventory_reserve_liquidation_quality" : inventoryTurnoverProxy ? "inventory_liquidity_quality" @@ -4448,6 +4562,7 @@ function deriveBusinessOverview(input: { inventoryPosition, inventoryTurnoverProxy, inventoryStalenessRiskProxy, + inventoryQualityEvents, vendorProcurementQuality, hasSupplierConcentrationSignal: (rankedOutgoing?.ranked_values.length ?? 0) > 0 }); @@ -4473,6 +4588,7 @@ function deriveBusinessOverview(input: { inventory_position: inventoryPosition, inventory_turnover_proxy: inventoryTurnoverProxy, inventory_staleness_risk_proxy: inventoryStalenessRiskProxy, + inventory_quality_events: inventoryQualityEvents, document_activity_profile: documentActivityProfile, counterparty_profile: counterpartyProfile, contract_usage_profile: contractUsageProfile, @@ -4483,7 +4599,7 @@ function deriveBusinessOverview(input: { missing_signal_families: missingSignalFamilies, missing_proof_families: missingProofFamilies, inference_basis: - hasBusinessOverviewProfileSignal || inventoryPosition || accountingFinancialResult + hasBusinessOverviewProfileSignal || inventoryPosition || inventoryQualityEvents || accountingFinancialResult ? "business_overview_from_confirmed_1c_multi_family_rows" : debtOpenSettlementQuality || debtDueDateAging ? "business_overview_from_confirmed_1c_multi_family_rows" @@ -4513,6 +4629,7 @@ function summarizeBusinessOverviewRows(input: { contractUsageProfileResult: AddressMcpQueryExecutorResult | null; inventoryOnHandResult: AddressMcpQueryExecutorResult | null; inventoryAgingResult: AddressMcpQueryExecutorResult | null; + inventoryQualityEventsResult: AddressMcpQueryExecutorResult | null; }): string | null { const parts: string[] = []; if (input.incomingResult && !input.incomingResult.error) { @@ -4560,6 +4677,9 @@ function summarizeBusinessOverviewRows(input: { if (input.inventoryAgingResult && !input.inventoryAgingResult.error) { parts.push(`${input.inventoryAgingResult.fetched_rows} inventory aging rows fetched, ${input.inventoryAgingResult.matched_rows} matched`); } + if (input.inventoryQualityEventsResult && !input.inventoryQualityEventsResult.error) { + parts.push(`${input.inventoryQualityEventsResult.fetched_rows} inventory quality-event rows fetched, ${input.inventoryQualityEventsResult.matched_rows} matched`); + } return parts.length > 0 ? parts.join("; ") : null; } @@ -4780,6 +4900,22 @@ function buildBusinessOverviewConfirmedFacts(derived: AssistantMcpDiscoveryDeriv `Staleness risk proxy склада на ${proxy.as_of_date}: самая ранняя дата закупочного сигнала ${proxy.oldest_purchase_date}, возраст ${proxy.max_purchase_age_days} дн., sales-to-stock ${proxy.sales_to_stock_amount_ratio}x, оценка ${inventoryStalenessRiskBandRu(proxy.risk_band)}. Это не подтвержденная неликвидность, не резерв и не ликвидационная стоимость.` ); } + if (derived.inventory_quality_events) { + const quality = derived.inventory_quality_events; + const eventWindow = + quality.first_event_date && quality.latest_event_date + ? ` Окно найденных событий: ${quality.first_event_date} — ${quality.latest_event_date}.` + : ""; + if (quality.evidence_status === "reviewed_no_quality_events_found") { + facts.push( + `Reviewed inventory quality route проверил складские документы списания, оприходования, инвентаризации и переоценки${period}: подтвержденных событий не найдено. Это проверенный отрицательный результат по доступным документам, но не рыночная ликвидационная оценка и не управленческий резерв.` + ); + } else { + facts.push( + `Reviewed inventory quality route проверил складские документы${period}: списаний ${quality.writeoff_rows} на ${quality.writeoff_amount_human_ru}, оприходований/корректировок ${quality.receipt_adjustment_rows} на ${quality.receipt_adjustment_amount_human_ru}, инвентаризаций ${quality.inventory_count_rows}, переоценок ${quality.revaluation_rows}.${eventWindow} Это подтвержденные документы 1С, но не самостоятельная рыночная ликвидационная стоимость.` + ); + } + } return facts; } @@ -5616,6 +5752,7 @@ export async function executeAssistantMcpDiscoveryPilot( let contractUsageProfileResult: AddressMcpQueryExecutorResult | null = null; let inventoryOnHandResult: AddressMcpQueryExecutorResult | null = null; let inventoryAgingResult: AddressMcpQueryExecutorResult | null = null; + let inventoryQualityEventsResult: AddressMcpQueryExecutorResult | null = null; const valueFilters = buildValueFlowFilters(planner); const lifecycleFilters = buildLifecycleFilters(planner); const profileFilters = buildBusinessOverviewProfileFilters(planner); @@ -5625,6 +5762,7 @@ export async function executeAssistantMcpDiscoveryPilot( const debtFilters = buildBusinessOverviewDebtFilters(planner); const debtDueDateAgingProbeEnabled = shouldRunDebtDueDateAgingProbe(planner); const inventoryFilters = buildBusinessOverviewInventoryFilters(planner); + const inventoryQualityEventsProbeEnabled = shouldRunInventoryQualityEventsProbe(planner); const debtAsOfDate = toNonEmptyString(debtFilters?.as_of_date); const inventoryAsOfDate = toNonEmptyString(inventoryFilters?.as_of_date); const incomingSelection = selectAddressRecipe("customer_revenue_and_payments", valueFilters); @@ -5660,6 +5798,9 @@ export async function executeAssistantMcpDiscoveryPilot( const inventoryAgingSelection = inventoryFilters ? selectAddressRecipe("inventory_aging_by_purchase_date", inventoryFilters) : null; + const inventoryQualityEventsSelection = inventoryQualityEventsProbeEnabled + ? selectAddressRecipe("inventory_quality_events_for_organization", inventoryFilters ?? buildBusinessOverviewProfileFilters(planner)) + : null; if (!incomingSelection.selected_recipe || !outgoingSelection.selected_recipe || !lifecycleSelection.selected_recipe) { pushReason(reasonCodes, "pilot_business_overview_recipe_not_available"); @@ -5771,6 +5912,14 @@ export async function executeAssistantMcpDiscoveryPilot( pushReason(reasonCodes, "pilot_business_overview_inventory_recipe_not_available"); pushUnique(queryLimitations, "Business overview inventory-position probe requires an executable inventory on-hand as-of-date recipe"); } + if (inventoryQualityEventsSelection?.selected_recipe) { + pushReason(reasonCodes, "pilot_business_overview_inventory_quality_events_recipe_selected"); + } else if (!inventoryQualityEventsProbeEnabled) { + pushReason(reasonCodes, "pilot_business_overview_inventory_quality_events_probe_skipped_without_boundary_need"); + } else { + pushReason(reasonCodes, "pilot_business_overview_inventory_quality_events_recipe_not_available"); + pushUnique(queryLimitations, "Business overview inventory quality probe requires an executable inventory quality-events recipe"); + } for (const step of dryRun.execution_steps) { if (step.primitive_id === "query_movements") { const incomingExecution = await executeCoverageAwareValueFlowQuery({ @@ -6007,6 +6156,19 @@ export async function executeAssistantMcpDiscoveryPilot( }); probeResults.push(queryResultToProbeResult(step.primitive_id, contractUsageProfileResult)); } + if (inventoryQualityEventsSelection?.selected_recipe) { + const inventoryQualityEventsFilters = inventoryFilters ?? buildBusinessOverviewProfileFilters(planner); + const inventoryQualityEventsPlan = buildAddressRecipePlan( + inventoryQualityEventsSelection.selected_recipe, + inventoryQualityEventsFilters + ); + inventoryQualityEventsResult = await runtimeDeps.executeAddressMcpQuery({ + query: inventoryQualityEventsPlan.query, + limit: inventoryQualityEventsPlan.limit, + account_scope: inventoryQualityEventsPlan.account_scope + }); + probeResults.push(queryResultToProbeResult(step.primitive_id, inventoryQualityEventsResult)); + } if (lifecycleResult.error) { pushUnique(queryLimitations, lifecycleResult.error); pushReason(reasonCodes, "pilot_business_overview_query_documents_mcp_error"); @@ -6037,6 +6199,12 @@ export async function executeAssistantMcpDiscoveryPilot( } else if (contractUsageProfileResult) { pushReason(reasonCodes, "pilot_business_overview_contract_usage_profile_query_mcp_executed"); } + if (inventoryQualityEventsResult?.error) { + pushUnique(queryLimitations, inventoryQualityEventsResult.error); + pushReason(reasonCodes, "pilot_business_overview_inventory_quality_events_query_mcp_error"); + } else if (inventoryQualityEventsResult) { + pushReason(reasonCodes, "pilot_business_overview_inventory_quality_events_query_mcp_executed"); + } continue; } @@ -6061,6 +6229,7 @@ export async function executeAssistantMcpDiscoveryPilot( debtAsOfDate, inventoryOnHandResult, inventoryAgingResult, + inventoryQualityEventsResult, inventoryAsOfDate, organizationScope, periodScope: dateScope @@ -6126,6 +6295,10 @@ export async function executeAssistantMcpDiscoveryPilot( if (derivedBusinessOverview.inventory_staleness_risk_proxy) { pushReason(reasonCodes, "pilot_derived_business_overview_inventory_staleness_risk_proxy_from_confirmed_rows"); } + if (derivedBusinessOverview.inventory_quality_events) { + pushReason(reasonCodes, "pilot_derived_business_overview_inventory_quality_events_from_reviewed_rows"); + pushReason(reasonCodes, `pilot_derived_business_overview_inventory_quality_events_${derivedBusinessOverview.inventory_quality_events.evidence_status}`); + } if (derivedBusinessOverview.missing_proof_families.length > 0) { pushReason(reasonCodes, "pilot_business_overview_missing_proof_families_recorded"); } @@ -6145,7 +6318,8 @@ export async function executeAssistantMcpDiscoveryPilot( counterpartyProfileResult, contractUsageProfileResult, inventoryOnHandResult, - inventoryAgingResult + inventoryAgingResult, + inventoryQualityEventsResult }); const evidence = resolveAssistantMcpDiscoveryEvidence({ plan: planner.discovery_plan, diff --git a/llm_normalizer/backend/src/services/assistantMcpDiscoveryResponseCandidate.ts b/llm_normalizer/backend/src/services/assistantMcpDiscoveryResponseCandidate.ts index a98ea19..8f07ede 100644 --- a/llm_normalizer/backend/src/services/assistantMcpDiscoveryResponseCandidate.ts +++ b/llm_normalizer/backend/src/services/assistantMcpDiscoveryResponseCandidate.ts @@ -997,12 +997,20 @@ function buildCompactBusinessOverviewReply( if (inventoryReserveBoundary) { const headline = toNonEmptyString(draft.headline); + const inventoryQualityEvents = toRecordObject(overview.inventory_quality_events); const cleanHeadline = headline?.replace(/^Коротко:\s*/iu, "").trim(); lines.push( cleanHeadline ? `Коротко: ${localizeLine(cleanHeadline)}` : "Коротко: точно подтвердить резерв под неликвиды по текущим данным нельзя." ); + if (inventoryQualityEvents) { + if (limitLine) { + lines.push(limitLine); + } + const reply = lines.join("\n").trim(); + return reply.length > 0 && !hasInternalMechanics(reply) ? reply : null; + } const boundaryLines = userFacingLines([ ...toStringList(draft.unknown_lines), ...toStringList(draft.limitation_lines) diff --git a/llm_normalizer/backend/src/types/addressQuery.ts b/llm_normalizer/backend/src/types/addressQuery.ts index c2e3524..cb3997e 100644 --- a/llm_normalizer/backend/src/types/addressQuery.ts +++ b/llm_normalizer/backend/src/types/addressQuery.ts @@ -36,6 +36,7 @@ export type AddressIntent = | "inventory_profitability_for_item" | "inventory_purchase_to_sale_chain" | "inventory_aging_by_purchase_date" + | "inventory_quality_events_for_organization" | "account_balance_snapshot" | "open_items_by_counterparty_or_contract" | "list_documents_by_counterparty" @@ -204,7 +205,8 @@ export interface AddressRecipeDefinition { | "inventory_trading_margin_proxy_profile" | "inventory_profitability_profile" | "inventory_purchase_to_sale_chain_profile" - | "inventory_aging_by_purchase_date_profile"; + | "inventory_aging_by_purchase_date_profile" + | "inventory_quality_events_profile"; required_filters: Array; optional_filters: Array; default_limit: number; diff --git a/llm_normalizer/backend/tests/assistantMcpDiscoveryAnswerAdapter.test.ts b/llm_normalizer/backend/tests/assistantMcpDiscoveryAnswerAdapter.test.ts index 9fc839a..0f95982 100644 --- a/llm_normalizer/backend/tests/assistantMcpDiscoveryAnswerAdapter.test.ts +++ b/llm_normalizer/backend/tests/assistantMcpDiscoveryAnswerAdapter.test.ts @@ -710,10 +710,14 @@ describe("assistant MCP discovery answer adapter", () => { const draft = buildAssistantMcpDiscoveryAnswerDraft(pilot); expect(draft.headline).toContain( - "\u0442\u043e\u0447\u043d\u043e \u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0434\u0438\u0442\u044c \u0440\u0435\u0437\u0435\u0440\u0432" + "\u043f\u0440\u043e\u0432\u0435\u0440\u0438\u043b \u0441\u043a\u043b\u0430\u0434\u0441\u043a\u0438\u0435 \u0434\u043e\u043a\u0443\u043c\u0435\u043d\u0442\u044b" + ); + expect(draft.headline).toContain( + "\u043f\u043e\u0434\u0442\u0432\u0435\u0440\u0436\u0434\u0435\u043d\u043d\u044b\u0445 \u0441\u043e\u0431\u044b\u0442\u0438\u0439" + ); + expect(draft.headline).toContain( + "\u043d\u0435 \u0440\u044b\u043d\u043e\u0447\u043d\u0430\u044f \u043b\u0438\u043a\u0432\u0438\u0434\u0430\u0446\u0438\u043e\u043d\u043d\u0430\u044f \u0441\u0442\u043e\u0438\u043c\u043e\u0441\u0442\u044c" ); - expect(draft.headline).toContain("\u043d\u0435\u043b\u044c\u0437\u044f"); - expect(draft.headline).toContain("staleness-risk proxy"); expect(draft.headline).not.toContain("бизнес-обзор"); expect(draft.must_not_claim).toContain("Do not present business overview inventory staleness risk proxy as confirmed obsolete stock, reserve, write-off, or liquidation value."); }); diff --git a/llm_normalizer/backend/tests/assistantMcpDiscoveryRuntimeBridge.test.ts b/llm_normalizer/backend/tests/assistantMcpDiscoveryRuntimeBridge.test.ts index a88942f..f131bf3 100644 --- a/llm_normalizer/backend/tests/assistantMcpDiscoveryRuntimeBridge.test.ts +++ b/llm_normalizer/backend/tests/assistantMcpDiscoveryRuntimeBridge.test.ts @@ -475,7 +475,7 @@ describe("assistant MCP discovery runtime bridge", () => { expect(userFacing).not.toContain("MCP discovery pilot"); }); - it("marks exact business-overview proof gaps as route enablement instead of reviewed execution", async () => { + it("promotes inventory reserve boundary after reviewed quality-event route executes", async () => { const deps = buildSequentialDeps([ { rows: [{ Period: "2020-01-15T00:00:00", Amount: 120000, Counterparty: "Client A" }] }, { rows: [{ Period: "2020-01-20T00:00:00", Amount: 50000, Counterparty: "Supplier A" }] }, @@ -548,20 +548,21 @@ describe("assistant MCP discovery runtime bridge", () => { expect(result.bridge_status).toBe("answer_draft_ready"); expect(result.business_fact_answer_allowed).toBe(true); expect(result.route_candidate).toMatchObject({ - candidate_status: "needs_route_enablement", + candidate_status: "ready_for_reviewed_execution", selected_chain_id: "business_overview", business_fact_family: "business_overview", action_family: "inventory_reserve_boundary", - executable_now: false + executable_now: true, + enablement_reason: null }); - expect(result.route_candidate.enablement_reason).toContain("inventory_reserve_liquidation_quality"); - expect(result.route_candidate.enablement_reason).toContain( - "reviewed_inventory_quality_route_with_reserves_writeoffs_obsolescence_and_liquidation_value" + expect(result.pilot.derived_business_overview?.inventory_quality_events?.evidence_status).toBe( + "reviewed_no_quality_events_found" ); - expect(result.route_candidate.forbidden_overclaim_flags).toContain( - "confirmed_obsolete_stock_reserve_writeoff_or_liquidation_value" + expect(result.pilot.derived_business_overview?.missing_proof_families.map((item) => item.family)).not.toContain( + "inventory_reserve_liquidation_quality" ); - expect(result.reason_codes).toContain("runtime_bridge_route_candidate_needs_route_enablement"); + expect(result.answer_draft.reason_codes).toContain("answer_contains_business_overview_inventory_quality_events"); + expect(result.reason_codes).toContain("runtime_bridge_route_candidate_ready_for_reviewed_execution"); }); it("promotes profit-margin boundary when accounting 90/91/99 proof is available", async () => { diff --git a/llm_normalizer/data/autorun_generators/history.json b/llm_normalizer/data/autorun_generators/history.json index e81957a..862266c 100644 --- a/llm_normalizer/data/autorun_generators/history.json +++ b/llm_normalizer/data/autorun_generators/history.json @@ -1,4 +1,47 @@ [ + { + "generation_id": "gen-ag05122057-c9786e", + "created_at": "2026-05-12T20:57:28+00:00", + "mode": "saved_user_sessions", + "title": "AGENT | Phase 96 inventory reserve/liquidation quality-events", + "count": 2, + "domain": "address_phase96_inventory_reserve_liquidation_quality", + "questions": [ + "По ООО Альтернатива Плюс за 2020 год проверь склад: были ли списания товаров, оприходования/корректировки, инвентаризации, переоценки, резервы под неликвиды или ликвидационная стоимость? Скажи коротко и честно, что подтверждено 1С, а что нельзя утверждать.", + "А по этим же данным можно сказать, что склад ликвидный и неликвидов нет?" + ], + "generated_by": "Codex", + "saved_case_set_file": "assistant_autogen_saved_user_sessions_20260512205728_gen-ag05122057-c9786e.json", + "context": { + "llm_provider": null, + "model": null, + "assistant_prompt_version": null, + "decomposition_prompt_version": null, + "prompt_fingerprint": null, + "autogen_personality_id": null, + "autogen_personality_prompt": null, + "source_session_id": null, + "saved_session_file": "assistant_saved_session_20260512205728_gen-ag05122057-c9786e.json", + "saved_case_set_kind": "agent_semantic_scenario", + "agent_run": true, + "agent_focus": "inventory_reserve_liquidation_quality", + "architecture_phase": "Route-Candidate-Driven Enablement Loop", + "source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase96_inventory_reserve_liquidation_quality.json", + "scenario_id": "address_truth_harness_phase96_inventory_reserve_liquidation_quality", + "semantic_tags": [ + "direct_answer_first", + "followup_context_carryover", + "inventory_liquidity_boundary", + "inventory_reserve_liquidation_quality", + "no_inventory_health_overclaim", + "no_market_liquidation_overclaim", + "reviewed_inventory_quality_events" + ], + "validation_status": "accepted_live_replay", + "validated_run_dir": "artifacts\\domain_runs\\phase96_inventory_reserve_liquidation_quality_rerun", + "saved_after_validated_replay": true + } + }, { "generation_id": "gen-ag05121628-50ea6c", "created_at": "2026-05-12T16:28:41+00:00", diff --git a/llm_normalizer/data/autorun_generators/saved_sessions/assistant_saved_session_20260512205728_gen-ag05122057-c9786e.json b/llm_normalizer/data/autorun_generators/saved_sessions/assistant_saved_session_20260512205728_gen-ag05122057-c9786e.json new file mode 100644 index 0000000..7ecfdf4 --- /dev/null +++ b/llm_normalizer/data/autorun_generators/saved_sessions/assistant_saved_session_20260512205728_gen-ag05122057-c9786e.json @@ -0,0 +1,109 @@ +{ + "saved_at": "2026-05-12T20:57:28+00:00", + "generation_id": "gen-ag05122057-c9786e", + "mode": "saved_user_sessions", + "title": "AGENT | Phase 96 inventory reserve/liquidation quality-events", + "agent_run": true, + "questions": [ + "По ООО Альтернатива Плюс за 2020 год проверь склад: были ли списания товаров, оприходования/корректировки, инвентаризации, переоценки, резервы под неликвиды или ликвидационная стоимость? Скажи коротко и честно, что подтверждено 1С, а что нельзя утверждать.", + "А по этим же данным можно сказать, что склад ликвидный и неликвидов нет?" + ], + "metadata": { + "assistant_prompt_version": null, + "decomposition_prompt_version": null, + "prompt_fingerprint": null, + "agent_focus": "inventory_reserve_liquidation_quality", + "architecture_phase": "Route-Candidate-Driven Enablement Loop", + "source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase96_inventory_reserve_liquidation_quality.json", + "scenario_id": "address_truth_harness_phase96_inventory_reserve_liquidation_quality", + "semantic_tags": [ + "direct_answer_first", + "followup_context_carryover", + "inventory_liquidity_boundary", + "inventory_reserve_liquidation_quality", + "no_inventory_health_overclaim", + "no_market_liquidation_overclaim", + "reviewed_inventory_quality_events" + ], + "validation_status": "accepted_live_replay", + "validated_run_dir": "artifacts\\domain_runs\\phase96_inventory_reserve_liquidation_quality_rerun", + "saved_after_validated_replay": true, + "save_gate": { + "schema_version": "agent_semantic_save_gate_v1", + "validation_status": "accepted_live_replay", + "validated_run_dir": "artifacts\\domain_runs\\phase96_inventory_reserve_liquidation_quality_rerun", + "final_status": "accepted", + "review_overall_status": "pass", + "business_overall_status": "pass", + "steps_total": 2, + "steps_passed": 2, + "steps_failed": 0, + "steps_with_business_failures": 0, + "steps_with_business_warnings": 0, + "acceptance_gate_passed": true, + "saved_after_validated_replay": true + } + }, + "source_session_id": null, + "session": { + "session_id": null, + "mode": "agent_semantic_run", + "items": [ + { + "message_id": "agent-user-001", + "role": "user", + "text": "По ООО Альтернатива Плюс за 2020 год проверь склад: были ли списания товаров, оприходования/корректировки, инвентаризации, переоценки, резервы под неликвиды или ликвидационная стоимость? Скажи коротко и честно, что подтверждено 1С, а что нельзя утверждать.", + "created_at": "2026-05-12T20:57:28+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + }, + { + "message_id": "agent-user-002", + "role": "user", + "text": "А по этим же данным можно сказать, что склад ликвидный и неликвидов нет?", + "created_at": "2026-05-12T20:57:28+00:00", + "reply_type": null, + "trace_id": null, + "debug": null + } + ], + "agent_run": true, + "metadata": { + "assistant_prompt_version": null, + "decomposition_prompt_version": null, + "prompt_fingerprint": null, + "agent_focus": "inventory_reserve_liquidation_quality", + "architecture_phase": "Route-Candidate-Driven Enablement Loop", + "source_spec_file": "X:\\1C\\NDC_1C\\docs\\orchestration\\address_truth_harness_phase96_inventory_reserve_liquidation_quality.json", + "scenario_id": "address_truth_harness_phase96_inventory_reserve_liquidation_quality", + "semantic_tags": [ + "direct_answer_first", + "followup_context_carryover", + "inventory_liquidity_boundary", + "inventory_reserve_liquidation_quality", + "no_inventory_health_overclaim", + "no_market_liquidation_overclaim", + "reviewed_inventory_quality_events" + ], + "validation_status": "accepted_live_replay", + "validated_run_dir": "artifacts\\domain_runs\\phase96_inventory_reserve_liquidation_quality_rerun", + "saved_after_validated_replay": true, + "save_gate": { + "schema_version": "agent_semantic_save_gate_v1", + "validation_status": "accepted_live_replay", + "validated_run_dir": "artifacts\\domain_runs\\phase96_inventory_reserve_liquidation_quality_rerun", + "final_status": "accepted", + "review_overall_status": "pass", + "business_overall_status": "pass", + "steps_total": 2, + "steps_passed": 2, + "steps_failed": 0, + "steps_with_business_failures": 0, + "steps_with_business_warnings": 0, + "acceptance_gate_passed": true, + "saved_after_validated_replay": true + } + } + } +} diff --git a/llm_normalizer/data/eval_cases/assistant_autogen_saved_user_sessions_20260512205728_gen-ag05122057-c9786e.json b/llm_normalizer/data/eval_cases/assistant_autogen_saved_user_sessions_20260512205728_gen-ag05122057-c9786e.json new file mode 100644 index 0000000..2d9b5d9 --- /dev/null +++ b/llm_normalizer/data/eval_cases/assistant_autogen_saved_user_sessions_20260512205728_gen-ag05122057-c9786e.json @@ -0,0 +1,31 @@ +{ + "suite_id": "assistant_saved_session_gen-ag05122057-c9786e", + "suite_version": "0.1.0", + "schema_version": "assistant_saved_session_suite_v0_1", + "generated_at": "2026-05-12T20:57:28+00:00", + "generation_id": "gen-ag05122057-c9786e", + "mode": "saved_user_sessions", + "title": "AGENT | Phase 96 inventory reserve/liquidation quality-events", + "domain": "address_phase96_inventory_reserve_liquidation_quality", + "scenario_count": 1, + "case_ids": [ + "SAVED-001" + ], + "cases": [ + { + "case_id": "SAVED-001", + "scenario_tag": "agent_saved_user_sessions", + "title": "AGENT | Phase 96 inventory reserve/liquidation quality-events", + "question_type": "followup", + "broadness_level": "medium", + "turns": [ + { + "user_message": "По ООО Альтернатива Плюс за 2020 год проверь склад: были ли списания товаров, оприходования/корректировки, инвентаризации, переоценки, резервы под неликвиды или ликвидационная стоимость? Скажи коротко и честно, что подтверждено 1С, а что нельзя утверждать." + }, + { + "user_message": "А по этим же данным можно сказать, что склад ликвидный и неликвидов нет?" + } + ] + } + ] +}