ОРРКЕСТРАЦИЯ -Уточнить safety-policy оркестратора: останавливать loop на hard blockers и рискованных решениях, а не только по порогу 80%
This commit is contained in:
parent
2b48229312
commit
9048632d3e
|
|
@ -1,5 +1,5 @@
|
|||
name = "orchestrator"
|
||||
description = "Coordinates a repo-native domain-case loop for NDC_1C: baseline capture, analyst verdict, minimal domain patch, rerun, and 80-point acceptance gate."
|
||||
description = "Coordinates a repo-native domain-case or scenario loop for NDC_1C: baseline or scenario capture, analyst verdict, minimal domain patch, rerun, and 80-point acceptance gate."
|
||||
model = "gpt-5.4"
|
||||
model_reasoning_effort = "high"
|
||||
sandbox_mode = "workspace-write"
|
||||
|
|
@ -13,14 +13,18 @@ Primary repo facts:
|
|||
- The helper runner is python scripts/domain_case_loop.py.
|
||||
|
||||
Your job:
|
||||
1. Accept one concrete domain case from the user.
|
||||
2. Create or reuse an artifact folder under artifacts/domain_runs/<case_id>/.
|
||||
1. Accept one concrete domain case or one linked multi-step domain scenario from the user.
|
||||
2. Create or reuse an artifact folder under artifacts/domain_runs/<case_id>/ or artifacts/domain_runs/<scenario_id>/.
|
||||
3. Capture baseline via one of:
|
||||
- python scripts/domain_case_loop.py run-case ...
|
||||
- python scripts/domain_case_loop.py import-export ...
|
||||
4. Ask domain_analyst for a strict verdict in Russian using baseline_turn.json first, then baseline_output.md / baseline_debug.json.
|
||||
- python scripts/domain_case_loop.py run-scenario --manifest ...
|
||||
- python scripts/domain_case_loop.py run-pack --manifest ...
|
||||
4. Ask domain_analyst for a strict verdict in Russian using machine-readable artifacts first:
|
||||
- case mode: baseline_turn.json, then baseline_output.md / baseline_debug.json
|
||||
- scenario mode: scenario_state.json and per-step turn.json, then scenario_summary.md / per-step debug.json
|
||||
5. Feed the verdict to domain_coder for the smallest defensible domain-only patch.
|
||||
6. Capture rerun artifacts.
|
||||
6. Capture rerun artifacts or scenario rerun artifacts.
|
||||
7. Ask domain_analyst for before/after comparison and a quality score.
|
||||
8. End with one status: accepted | partial | blocked | needs_exact_capability.
|
||||
|
||||
|
|
@ -31,6 +35,9 @@ Hard rules:
|
|||
- Keep the loop artifact-driven.
|
||||
- Reuse the existing backend/session/export flow; do not invent a parallel runtime.
|
||||
- When the repo structure differs from a template, adapt the skill/scripts/paths, not the product architecture.
|
||||
- In autonomous loop mode, do not stop only because the analyst says `needs_exact_capability` or `partial` if there is still autonomous implementation work to do.
|
||||
- Stop early when the analyst sets `requires_user_decision = true` because the next step would otherwise require guessing a missing required observation, accepting a risky architecture fork, choosing a business-critical tradeoff, or pushing through a hacky / brittle / disproportionally complex fix.
|
||||
- Treat true runtime or 1C availability failures as `blocked`, not as a normal low-score iteration.
|
||||
|
||||
Acceptance gate:
|
||||
- accepted requires analyst quality_score >= 80
|
||||
|
|
@ -42,6 +49,9 @@ Required artifacts per cycle:
|
|||
- baseline_output.md
|
||||
- baseline_debug.json
|
||||
- baseline_turn.json
|
||||
- scenario_manifest.json
|
||||
- scenario_state.json
|
||||
- scenario_summary.md
|
||||
- analyst_verdict.md
|
||||
- coder_plan.md
|
||||
- patch_summary.md
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
---
|
||||
name: domain-case-loop
|
||||
description: Use this skill when a user wants to iteratively refine one NDC_1C domain case through a multi-agent loop: automated baseline capture, JSON analysis, minimal domain patch, rerun, and before/after verdict.
|
||||
description: "Use this skill when a user wants to iteratively refine one NDC_1C domain case or one linked multi-step domain scenario through a multi-agent loop: automated capture, JSON analysis, minimal domain patch, rerun, and before/after verdict."
|
||||
---
|
||||
|
||||
# Domain case loop
|
||||
|
||||
This skill packages the standard workflow for iterating on one concrete domain case in NDC_1C.
|
||||
This skill packages the standard workflow for iterating on one concrete domain case or one linked multi-step domain scenario in NDC_1C.
|
||||
|
||||
## Use this skill when
|
||||
|
||||
|
|
@ -14,6 +14,7 @@ This skill packages the standard workflow for iterating on one concrete domain c
|
|||
- the route is wrong even if the wording looks better;
|
||||
- there is a gap between exact compute intent and actual fallback output;
|
||||
- there are follow-up / continuation bugs that corrupt business context.
|
||||
- the user has a cascade of linked questions that should reuse one assistant session and semantic state.
|
||||
|
||||
## Do not use this skill when
|
||||
|
||||
|
|
@ -28,12 +29,55 @@ Read `references/repo_runtime_map.md` before the first real cycle.
|
|||
|
||||
Use these repo-native capture paths:
|
||||
- automated capture: `python scripts/domain_case_loop.py run-case ...`
|
||||
- linked multi-step capture: `python scripts/domain_case_loop.py run-scenario --manifest path/to/manifest.json`
|
||||
- full domain question pool capture: `python scripts/domain_case_loop.py run-pack --manifest path/to/pack.json`
|
||||
- autonomous full-pack loop: `python scripts/domain_case_loop.py run-pack-loop --manifest path/to/pack.json`
|
||||
- import existing technical export: `python scripts/domain_case_loop.py import-export ...`
|
||||
- `run-case` defaults to the repo's live local profile: `local / qwen2.5-14b-instruct-1m / http://127.0.0.1:1234/v1`
|
||||
- override with `--llm-provider`, `--llm-model`, `--llm-base-url`, `--llm-api-key` when needed
|
||||
- `run-pack-loop` defaults to `gpt-5.4` for analyst and `gpt-5.4-mini` for coder; tune with `--analyst-codex-model`, `--coder-codex-model`, `--analyst-reasoning-effort`, `--coder-reasoning-effort`
|
||||
|
||||
## Workflow
|
||||
|
||||
### Scenario mode
|
||||
|
||||
Use scenario mode when the user brings a linked chain such as:
|
||||
- "what is on stock now"
|
||||
- "who supplied this item"
|
||||
- "which documents bought it"
|
||||
- "was it later sold"
|
||||
|
||||
In scenario mode:
|
||||
- create `scenario_manifest.json` first;
|
||||
- keep one shared `session_id`;
|
||||
- capture each step under `artifacts/domain_runs/<scenario_id>/steps/<step_id>/`;
|
||||
- preserve semantic carryover via explicit `scenario_state.json`, not vague model memory.
|
||||
|
||||
Use `references/scenario_manifest_template.json`.
|
||||
|
||||
### Pack mode
|
||||
|
||||
Use pack mode when the user brings a whole domain pool and wants grouped orchestration rather than one isolated chain.
|
||||
|
||||
In pack mode:
|
||||
- group the question pool into several coherent scenarios;
|
||||
- capture each scenario under `artifacts/domain_runs/<pack_id>/scenarios/<scenario_id>/`;
|
||||
- write aggregate `pack_state.json` and `pack_summary.md`;
|
||||
- treat unresolved scenarios as enablement backlog, not as a reason to drop the domain.
|
||||
|
||||
### Autonomous pack-loop mode
|
||||
|
||||
Use autonomous pack-loop mode when the user wants the system to continue with analyst/coder iterations until the analyst gate is reached or the loop hits a real blocker.
|
||||
|
||||
In autonomous pack-loop mode:
|
||||
- run `python scripts/domain_case_loop.py run-pack-loop --manifest ...`;
|
||||
- keep each iteration under `artifacts/domain_runs/<loop_id>/iterations/<iteration_id>/`;
|
||||
- read `analyst_verdict.json` before any coder patch;
|
||||
- let coder patch only the highest-value domain targets from the current analyst verdict;
|
||||
- stop only on `accepted`, `blocked`, explicit `requires_user_decision = true`, or `max_iterations`;
|
||||
- do not stop just because the analyst returns `needs_exact_capability` or `partial` if autonomous domain enablement work still remains.
|
||||
- treat `quality score >= 80` as the target gate, not as permission to keep pushing through hard blockers, missing essential observations, or unsafe fixes.
|
||||
|
||||
### Step 1 - Normalize the case
|
||||
|
||||
Create `artifacts/domain_runs/<case_id>/case_brief.md` with:
|
||||
|
|
@ -114,6 +158,14 @@ Write `final_status.md` with one of:
|
|||
|
||||
`needs_exact_capability` is the default status when the business/domain request is valid for the project, but the current contour is missing the route, intent, capability, or domain bootstrap needed to answer it.
|
||||
|
||||
`needs_exact_capability` does not automatically stop autonomous pack-loop mode. Treat it as "continue domain enablement work" unless the analyst explicitly marks `requires_user_decision = true`, the runtime is truly blocked, or the loop hits `max_iterations`.
|
||||
|
||||
Autonomous pack-loop mode should stop early and ask the user when at least one of these is true:
|
||||
- a required observation anchor is missing and cannot be recovered safely from artifacts, 1C, or the current scenario state;
|
||||
- the next patch would introduce a hack, brittle workaround, hidden heuristic masking, or another low-trust shortcut;
|
||||
- the next patch would cause risky architecture drift, disproportionate complexity, or a contour expansion with unclear blast radius;
|
||||
- a business-critical ambiguity or scope tradeoff cannot be resolved from repo context and artifacts alone.
|
||||
|
||||
Accepted requires:
|
||||
- quality score >= 80
|
||||
- no unresolved P0 defects
|
||||
|
|
@ -125,6 +177,7 @@ Accepted requires:
|
|||
- If exact data should exist in 1C/MCP, prefer exact route work over prompt cosmetics.
|
||||
- If exact data does not exist yet in the reachable contour, return a technical insufficiency with a crisp blocker.
|
||||
- If the user case belongs to a project-relevant domain but is outside the current contour, do not treat that as a terminal rejection. Treat it as domain enablement work and record the missing route/intent/capability explicitly.
|
||||
- Raise `requires_user_decision = true` when the loop would otherwise have to guess a missing anchor, choose between materially different risky implementations, or push through a hacky/suspicious fix path.
|
||||
- Never fabricate 1C data.
|
||||
- Keep domain fixes minimal and localized.
|
||||
- Preserve successful baseline scenarios.
|
||||
|
|
|
|||
|
|
@ -21,3 +21,53 @@ artifacts/domain_runs/<case_id>/
|
|||
- rerun_report_case.json
|
||||
- before_after_diff.md
|
||||
- final_status.md
|
||||
|
||||
For each linked domain scenario use:
|
||||
|
||||
artifacts/domain_runs/<scenario_id>/
|
||||
- scenario_brief.md
|
||||
- scenario_manifest.json
|
||||
- manifest_source.txt
|
||||
- scenario_state.json
|
||||
- scenario_output.md
|
||||
- scenario_summary.md
|
||||
- final_status.md
|
||||
- session_id.txt
|
||||
- steps/<step_id>/output.md
|
||||
- steps/<step_id>/debug.json
|
||||
- steps/<step_id>/turn.json
|
||||
- steps/<step_id>/session.json
|
||||
- steps/<step_id>/assistant_response.json
|
||||
- steps/<step_id>/step_state.json
|
||||
- steps/<step_id>/resolved_question.txt
|
||||
|
||||
For each full domain question pack use:
|
||||
|
||||
artifacts/domain_runs/<pack_id>/
|
||||
- pack_manifest.json
|
||||
- manifest_source.txt
|
||||
- pack_state.json
|
||||
- pack_summary.md
|
||||
- final_status.md
|
||||
- scenarios/<scenario_id>/...
|
||||
|
||||
For each autonomous pack loop use:
|
||||
|
||||
artifacts/domain_runs/<loop_id>/
|
||||
- manifest_source.txt
|
||||
- loop_state.json
|
||||
- loop_summary.md
|
||||
- final_status.md
|
||||
- iterations/<iteration_id>/analyst_prompt.md
|
||||
- iterations/<iteration_id>/analyst_verdict.json
|
||||
- iterations/<iteration_id>/analyst_exec.stdout.log
|
||||
- iterations/<iteration_id>/analyst_exec.stderr.log
|
||||
- iterations/<iteration_id>/coder_prompt.md
|
||||
- iterations/<iteration_id>/coder_result.json
|
||||
- iterations/<iteration_id>/coder_plan.md
|
||||
- iterations/<iteration_id>/patch_summary.md
|
||||
- iterations/<iteration_id>/coder_exec.stdout.log
|
||||
- iterations/<iteration_id>/coder_exec.stderr.log
|
||||
- iterations/<iteration_id>/pack_run.stdout.log
|
||||
- iterations/<iteration_id>/pack_run.stderr.log
|
||||
- iterations/<iteration_id>/pack_output/pack_run/...
|
||||
|
|
|
|||
|
|
@ -13,10 +13,19 @@
|
|||
|
||||
1. Prefer automated capture with:
|
||||
- `python scripts/domain_case_loop.py run-case ...`
|
||||
2. If baseline already exists as copied markdown export, import it with:
|
||||
2. For linked multi-step scenarios, capture with:
|
||||
- `python scripts/domain_case_loop.py run-scenario --manifest ...`
|
||||
3. For full domain pools grouped into several scenarios, capture with:
|
||||
- `python scripts/domain_case_loop.py run-pack --manifest ...`
|
||||
4. For autonomous analyst/coder improvement over a full pack, run:
|
||||
- `python scripts/domain_case_loop.py run-pack-loop --manifest ...`
|
||||
5. If baseline already exists as copied markdown export, import it with:
|
||||
- `python scripts/domain_case_loop.py import-export ...`
|
||||
3. Use `baseline_turn.json` / `rerun_turn.json` as canonical analyst input.
|
||||
4. Use `baseline_output.md` / `rerun_output.md` as human-readable paired artifacts.
|
||||
6. Use `baseline_turn.json` / `rerun_turn.json` as canonical analyst input for case mode.
|
||||
7. Use `scenario_state.json` plus per-step `turn.json` as canonical analyst input for scenario mode.
|
||||
8. Use `pack_state.json` plus per-scenario `scenario_state.json` as canonical analyst input for pack mode.
|
||||
9. Use `loop_state.json` plus per-iteration `analyst_verdict.json` / `coder_result.json` as canonical analyst input for autonomous pack-loop mode.
|
||||
10. Use `baseline_output.md` / `rerun_output.md` or per-step `output.md` as human-readable paired artifacts.
|
||||
|
||||
## Default run assumptions
|
||||
|
||||
|
|
@ -24,6 +33,9 @@
|
|||
- eval target: `assistant_stage1`
|
||||
- single-case async run uses generated case id `AUTO-001`
|
||||
- artifact root: `artifacts/domain_runs/<case_id>/`
|
||||
- scenario capture uses `POST /api/assistant/message` and `GET /api/assistant/session/:session_id`
|
||||
- live runners perform backend preflight via `GET /api/health`
|
||||
- `run-pack-loop` defaults to `gpt-5.4` for analyst and `gpt-5.4-mini` for coder
|
||||
|
||||
## Important constraints
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
{
|
||||
"schema_version": "domain_scenario_manifest_v1",
|
||||
"scenario_id": "inventory_supplier_trace_demo",
|
||||
"domain": "inventory_stock",
|
||||
"title": "Inventory stock -> supplier provenance chain",
|
||||
"description": "Shared-session scenario for warehouse stock, supplier provenance, and downstream document tracing.",
|
||||
"analysis_context": {
|
||||
"as_of_date": "2026-04-13",
|
||||
"source": "scenario_manifest"
|
||||
},
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_inventory",
|
||||
"title": "Current stock snapshot",
|
||||
"question": "Какие товары сейчас лежат на складе",
|
||||
"expected_capability": "confirmed_inventory_on_hand_as_of_date",
|
||||
"expected_result_mode": "confirmed_balance"
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_supplier",
|
||||
"title": "Supplier provenance",
|
||||
"question": "У какого поставщика купили {{step_01_inventory.entries[0].item}}",
|
||||
"depends_on": ["step_01_inventory"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_documents",
|
||||
"title": "Purchase documents",
|
||||
"question": "По каким документам был куплен {{step_01_inventory.entries[0].item}}",
|
||||
"depends_on": ["step_01_inventory", "step_02_supplier"]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -16,8 +16,11 @@ Rules:
|
|||
## codex_domain_loop
|
||||
- Project-scoped Codex orchestration lives under `.codex/`.
|
||||
- Use `.codex/skills/domain-case-loop` for repeatable domain hardening loops on one concrete case.
|
||||
- The same skill/launcher also supports multi-step domain scenarios with shared assistant session state under `artifacts/domain_runs/<scenario_id>/steps/`.
|
||||
- For full domain question pools, use pack mode and aggregate artifacts under `artifacts/domain_runs/<pack_id>/scenarios/`.
|
||||
- Preserve current architecture: domain loop may automate capture, review, rerun, and artifact storage, but must not rewrite runtime foundations.
|
||||
- Prefer machine-readable case artifacts in `artifacts/domain_runs/<case_id>/`, especially `baseline_turn.json` / `rerun_turn.json`, over ad hoc prose-only summaries.
|
||||
- For cascading user questions in one domain, prefer scenario artifacts (`scenario_manifest.json`, `scenario_state.json`, per-step `turn.json`) over separate unlinked case folders.
|
||||
- If a case falls outside the current routed contour because the route/intent/capability is not wired yet, treat it as domain enablement work for this project, not as automatic out-of-scope rejection.
|
||||
- For new unmarked domains, `needs_exact_capability` means "bootstrap or extend the contour" rather than "close the case as unsupported".
|
||||
- A case can be marked `accepted` only when analyst verdict is at least `80/100`, no unresolved `P0` remains, and the rerun does not mask heuristic output as confirmed.
|
||||
|
|
|
|||
|
|
@ -0,0 +1 @@
|
|||
{"timestamp":"2026-04-14T04:26:37.615Z","level":"info","service":"llm_normalizer_backend","message":"Backend started on http://localhost:8787"}
|
||||
|
|
@ -0,0 +1,95 @@
|
|||
# Domain D - Inventory Stock, Warehouse, Supplier Provenance
|
||||
|
||||
## Meaning
|
||||
|
||||
This domain covers one of the key business goals of the project:
|
||||
- determine which items are currently on stock;
|
||||
- understand which items form the balance on account `41.01`;
|
||||
- trace the current stock back to supplier-side purchase provenance;
|
||||
- continue the chain into purchase documents and later sale documents.
|
||||
|
||||
It should be treated as project scope, even when the current contour is not yet wired for every question.
|
||||
|
||||
## Question families
|
||||
|
||||
### Stock snapshot
|
||||
- Какие товары сейчас лежат на складе
|
||||
- Из каких товаров состоит остаток по 41 счету
|
||||
- Какие товары числятся на 41 счете на дату ...
|
||||
- Какие конкретно номенклатуры формируют остаток по складу на дату ...
|
||||
|
||||
### Purchase provenance
|
||||
- От какого поставщика куплен товар ...
|
||||
- У какого поставщика были куплены товары, которые сейчас лежат на складе
|
||||
- По какому поставщику проходит текущий товарный остаток
|
||||
- Когда был куплен товар ...
|
||||
- По каким документам был куплен товар ...
|
||||
- Какие товары от поставщика ... сейчас еще лежат на складе
|
||||
- Какие товары по состоянию на дату ... были куплены у поставщика ...
|
||||
|
||||
### Aging and unresolved residue
|
||||
- Какие остатки по товарам относятся к старым закупкам
|
||||
- Какие товары сейчас висят в остатке без понятной привязки к поставщику
|
||||
- Есть ли остатки товара, которые закупались очень давно
|
||||
|
||||
### Downstream sales trace
|
||||
- Кому был продан товар ...
|
||||
- Через какие документы прошел путь товара: закупка -> склад -> продажа
|
||||
- Какие товары были куплены у поставщика ... и позже проданы покупателю ...
|
||||
|
||||
## Observed anchors
|
||||
|
||||
The domain pack now carries explicit observed anchors from real stock snapshots so that linked scenarios stay attached to business objects already seen in the system:
|
||||
|
||||
- warehouse: `Основной склад`
|
||||
- organization: `ООО \Альтернатива Плюс\`
|
||||
- current stock anchor: `Диван трехместный`
|
||||
- historical stock anchor on `2020-03-31`: `Шкаф картотечный 1000*400*2100`
|
||||
|
||||
For supplier / buyer endpoints we only have candidate observed counterparties at the moment:
|
||||
|
||||
- supplier candidate: `Гамма-мебель, ООО`
|
||||
- buyer candidate: `Департамент капитального ремонта города Москвы`
|
||||
|
||||
These candidate counterparties are used to keep the scenario realistic, but they must not be treated as confirmed provenance until an exact trace capability exists.
|
||||
|
||||
## Domain rules
|
||||
|
||||
- `stock snapshot` belongs to exact compute and should prefer confirmed balance routes.
|
||||
- `supplier provenance` must not silently collapse multiple historical suppliers into one confirmed answer.
|
||||
- If provenance is not uniquely provable, the system should surface that as unresolved or candidate provenance, not as a fabricated exact answer.
|
||||
- Missing route, intent, capability, or domain bootstrap is enablement work, not out-of-scope rejection.
|
||||
|
||||
## Capability backlog
|
||||
|
||||
### Ready or near-ready
|
||||
- `inventory_on_hand_as_of_date`
|
||||
- `inventory_on_hand_on_account_41_as_of_date`
|
||||
|
||||
### Needs enablement
|
||||
- `inventory_purchase_provenance_for_item`
|
||||
- `inventory_purchase_documents_for_item`
|
||||
- `inventory_supplier_stock_overlap_as_of_date`
|
||||
- `inventory_sale_trace_for_item`
|
||||
- `inventory_purchase_to_sale_chain`
|
||||
|
||||
## Scenario orchestration guidance
|
||||
|
||||
For linked user chains in this domain, prefer `run-scenario` over isolated one-off runs.
|
||||
|
||||
Recommended scenario state fields:
|
||||
- `as_of_date`
|
||||
- `organization_scope`
|
||||
- `warehouse`
|
||||
- `item`
|
||||
- `supplier`
|
||||
- `contract`
|
||||
- `purchase_documents`
|
||||
- `sale_documents`
|
||||
- `active_result_set_id`
|
||||
|
||||
This domain is the reference use case for shared-session scenario capture.
|
||||
|
||||
The full question pool is fixed in:
|
||||
- [domain_inventory_stock_supplier_trace_pack.json](/x:/1C/NDC_1C/docs/orchestration/domain_inventory_stock_supplier_trace_pack.json:1)
|
||||
- [domain_inventory_stock_supplier_trace_pool.md](/x:/1C/NDC_1C/docs/orchestration/domain_inventory_stock_supplier_trace_pool.md:1)
|
||||
|
|
@ -0,0 +1,195 @@
|
|||
{
|
||||
"schema_version": "domain_scenario_pack_v1",
|
||||
"pack_id": "inventory_stock_supplier_trace_pool",
|
||||
"domain": "inventory_stock",
|
||||
"title": "Inventory stock and supplier provenance question pool",
|
||||
"description": "Full orchestration pack for the warehouse stock / supplier provenance domain question pool.",
|
||||
"analysis_context": {
|
||||
"as_of_date": "2026-04-13",
|
||||
"source": "scenario_pack"
|
||||
},
|
||||
"bindings": {
|
||||
"target_date": "2020-03-31",
|
||||
"observed_warehouse": "Основной склад",
|
||||
"observed_organization": "ООО \\Альтернатива Плюс\\",
|
||||
"focus_item_current": "Диван трехместный",
|
||||
"focus_item_historical": "Шкаф картотечный 1000*400*2100",
|
||||
"observed_supplier_candidate": "Гамма-мебель, ООО",
|
||||
"observed_customer_candidate": "Департамент капитального ремонта города Москвы"
|
||||
},
|
||||
"scenarios": [
|
||||
{
|
||||
"scenario_id": "inventory_snapshot_core",
|
||||
"title": "Stock snapshot core",
|
||||
"description": "Questions about current stock, account 41.01, and date-based snapshots.",
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_stock_now",
|
||||
"title": "Current stock",
|
||||
"question": "Какие товары сейчас лежат на складе",
|
||||
"expected_capability": "confirmed_inventory_on_hand_as_of_date",
|
||||
"expected_result_mode": "confirmed_balance"
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_account_41_now",
|
||||
"title": "Account 41 composition",
|
||||
"question": "Из каких товаров состоит остаток по 41 счету",
|
||||
"expected_capability": "confirmed_inventory_on_hand_as_of_date",
|
||||
"expected_result_mode": "confirmed_balance"
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_account_41_on_date",
|
||||
"title": "Account 41 on chosen date",
|
||||
"question": "Какие товары числятся на 41 счете на дату {{bindings.target_date}}",
|
||||
"analysis_context": {
|
||||
"as_of_date": "2020-03-31",
|
||||
"source": "pack_binding_target_date"
|
||||
},
|
||||
"expected_capability": "confirmed_inventory_on_hand_as_of_date",
|
||||
"expected_result_mode": "confirmed_balance"
|
||||
},
|
||||
{
|
||||
"step_id": "step_04_stock_nomenclature_on_date",
|
||||
"title": "Stock nomenclature on chosen date",
|
||||
"question": "Какие конкретно номенклатуры формируют остаток по складу на дату {{bindings.target_date}}",
|
||||
"analysis_context": {
|
||||
"as_of_date": "2020-03-31",
|
||||
"source": "pack_binding_target_date"
|
||||
},
|
||||
"expected_capability": "confirmed_inventory_on_hand_as_of_date",
|
||||
"expected_result_mode": "confirmed_balance"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenario_id": "inventory_purchase_provenance",
|
||||
"title": "Purchase provenance and supplier linkage",
|
||||
"description": "Questions about suppliers, purchase dates, purchase documents, and supplier-scoped stock.",
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_stock_now",
|
||||
"title": "Current stock",
|
||||
"question": "Какие товары сейчас лежат на складе",
|
||||
"expected_capability": "confirmed_inventory_on_hand_as_of_date",
|
||||
"expected_result_mode": "confirmed_balance"
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_item_supplier",
|
||||
"title": "Supplier for chosen item",
|
||||
"question": "От какого поставщика куплен товар {{bindings.focus_item_current}} из текущего остатка на складе {{bindings.observed_warehouse}}",
|
||||
"depends_on": ["step_01_stock_now"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_suppliers_for_current_stock",
|
||||
"title": "Suppliers behind current stock",
|
||||
"question": "У какого поставщика были куплены товары, которые сейчас лежат на складе {{bindings.observed_warehouse}} организации {{bindings.observed_organization}}",
|
||||
"depends_on": ["step_01_stock_now"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_04_supplier_current_residue",
|
||||
"title": "Supplier attribution of current residue",
|
||||
"question": "По какому поставщику проходит текущий товарный остаток на складе {{bindings.observed_warehouse}}",
|
||||
"depends_on": ["step_01_stock_now"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_05_item_purchase_date",
|
||||
"title": "Purchase date for chosen item",
|
||||
"question": "Когда был куплен товар {{bindings.focus_item_current}} из текущего остатка на складе {{bindings.observed_warehouse}}",
|
||||
"depends_on": ["step_01_stock_now", "step_02_item_supplier"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_06_item_purchase_documents",
|
||||
"title": "Purchase documents for chosen item",
|
||||
"question": "По каким документам был куплен товар {{bindings.focus_item_current}} для остатка на складе {{bindings.observed_warehouse}}",
|
||||
"depends_on": ["step_01_stock_now", "step_02_item_supplier"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_07_supplier_items_now",
|
||||
"title": "Current stock by supplier",
|
||||
"question": "Какие товары от поставщика {{bindings.observed_supplier_candidate}} сейчас еще лежат на складе {{bindings.observed_warehouse}}",
|
||||
"depends_on": ["step_01_stock_now"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_08_supplier_items_on_date",
|
||||
"title": "Supplier items on chosen date",
|
||||
"question": "Какие товары по состоянию на дату {{bindings.target_date}} на складе {{bindings.observed_warehouse}} были куплены у поставщика {{bindings.observed_supplier_candidate}}",
|
||||
"analysis_context": {
|
||||
"as_of_date": "2020-03-31",
|
||||
"source": "pack_binding_target_date"
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenario_id": "inventory_aging_and_unresolved",
|
||||
"title": "Aging and unresolved stock residue",
|
||||
"description": "Questions about old purchases and stock with missing supplier linkage.",
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_stock_now",
|
||||
"title": "Current stock",
|
||||
"question": "Какие товары сейчас лежат на складе",
|
||||
"expected_capability": "confirmed_inventory_on_hand_as_of_date",
|
||||
"expected_result_mode": "confirmed_balance"
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_old_purchase_residue",
|
||||
"title": "Old purchase residue",
|
||||
"question": "Относится ли товар {{bindings.focus_item_historical}} в остатке на дату {{bindings.target_date}} к старым закупкам",
|
||||
"depends_on": ["step_01_stock_now"],
|
||||
"analysis_context": {
|
||||
"as_of_date": "2020-03-31",
|
||||
"source": "pack_binding_target_date"
|
||||
}
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_unresolved_supplier_link",
|
||||
"title": "Unresolved supplier linkage",
|
||||
"question": "Есть ли на складе {{bindings.observed_warehouse}} товары с остатком без понятной привязки к поставщику",
|
||||
"depends_on": ["step_01_stock_now"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_04_very_old_stock",
|
||||
"title": "Very old stock",
|
||||
"question": "Есть ли среди текущих остатков на складе {{bindings.observed_warehouse}} позиции, закупленные задолго до {{bindings.target_date}}",
|
||||
"depends_on": ["step_01_stock_now", "step_02_old_purchase_residue"]
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"scenario_id": "inventory_sale_trace",
|
||||
"title": "Sale trace and purchase-to-sale chain",
|
||||
"description": "Questions about buyers, sale documents, and full purchase -> stock -> sale chains.",
|
||||
"steps": [
|
||||
{
|
||||
"step_id": "step_01_stock_on_date",
|
||||
"title": "Historical stock anchor",
|
||||
"question": "Какие товары числятся на 41 счете на дату {{bindings.target_date}} на складе {{bindings.observed_warehouse}}",
|
||||
"analysis_context": {
|
||||
"as_of_date": "2020-03-31",
|
||||
"source": "pack_binding_target_date"
|
||||
},
|
||||
"expected_capability": "confirmed_inventory_on_hand_as_of_date",
|
||||
"expected_result_mode": "confirmed_balance"
|
||||
},
|
||||
{
|
||||
"step_id": "step_02_item_buyer",
|
||||
"title": "Buyer for chosen item",
|
||||
"question": "Кому был продан товар {{bindings.focus_item_historical}}",
|
||||
"depends_on": ["step_01_stock_on_date"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_03_item_purchase_stock_sale_chain",
|
||||
"title": "Full document chain for chosen item",
|
||||
"question": "Через какие документы прошел путь товара {{bindings.focus_item_historical}}: закупка -> склад -> продажа",
|
||||
"depends_on": ["step_01_stock_on_date", "step_02_item_buyer"]
|
||||
},
|
||||
{
|
||||
"step_id": "step_04_supplier_to_buyer_chain",
|
||||
"title": "Supplier to buyer overlap",
|
||||
"question": "Есть ли документально подтвержденная цепочка: поставщик {{bindings.observed_supplier_candidate}} -> товар {{bindings.focus_item_historical}} -> покупатель {{bindings.observed_customer_candidate}}"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
# Inventory Question Pool - Orchestration Map
|
||||
|
||||
## Purpose
|
||||
|
||||
This file fixes the full user question pool for the warehouse stock / supplier provenance domain in orchestration format.
|
||||
|
||||
The runnable pack is:
|
||||
- [domain_inventory_stock_supplier_trace_pack.json](/x:/1C/NDC_1C/docs/orchestration/domain_inventory_stock_supplier_trace_pack.json:1)
|
||||
|
||||
## Scenario map
|
||||
|
||||
## Observed anchors
|
||||
|
||||
These anchors are taken from the live stock snapshots already captured by the orchestration run and are used to keep provenance / trace scenarios grounded in real observed objects:
|
||||
|
||||
- warehouse: `Основной склад`
|
||||
- organization: `ООО \Альтернатива Плюс\`
|
||||
- current focus item: `Диван трехместный`
|
||||
- historical focus item on `2020-03-31`: `Шкаф картотечный 1000*400*2100`
|
||||
- supplier candidate from observed counterparty artifacts: `Гамма-мебель, ООО`
|
||||
- customer candidate from observed counterparty artifacts: `Департамент капитального ремонта города Москвы`
|
||||
|
||||
The supplier/customer anchors above are still `candidate` observations, not confirmed purchase-to-sale truth.
|
||||
|
||||
### Scenario 1 - `inventory_snapshot_core`
|
||||
- Какие товары сейчас лежат на складе
|
||||
- Из каких товаров состоит остаток по 41 счету
|
||||
- Какие товары числятся на 41 счете на дату ...
|
||||
- Какие конкретно номенклатуры формируют остаток по складу на дату ...
|
||||
|
||||
### Scenario 2 - `inventory_purchase_provenance`
|
||||
- От какого поставщика куплен товар ...
|
||||
- У какого поставщика были куплены товары, которые сейчас лежат на складе
|
||||
- По какому поставщику проходит текущий товарный остаток
|
||||
- Когда был куплен товар ...
|
||||
- По каким документам был куплен товар ...
|
||||
- Какие товары от поставщика ... сейчас еще лежат на складе
|
||||
- Какие товары по состоянию на дату ... были куплены у поставщика ...
|
||||
|
||||
### Scenario 3 - `inventory_aging_and_unresolved`
|
||||
- Какие остатки по товарам относятся к старым закупкам
|
||||
- Какие товары сейчас висят в остатке без понятной привязки к поставщику
|
||||
- Есть ли остатки товара, которые закупались очень давно
|
||||
|
||||
### Scenario 4 - `inventory_sale_trace`
|
||||
- Кому был продан товар ...
|
||||
- Через какие документы прошел путь товара: закупка -> склад -> продажа
|
||||
- Какие товары были куплены у поставщика ... и позже проданы покупателю ...
|
||||
|
||||
This scenario is now anchored to the observed historical item `Шкаф картотечный 1000*400*2100` rather than to an arbitrary item from the current stock list.
|
||||
|
||||
## Current readiness
|
||||
|
||||
### Exact now
|
||||
- stock snapshot questions on current date or chosen date
|
||||
- account `41.01` stock composition and nomenclature snapshot
|
||||
|
||||
### Partial now
|
||||
- some document-oriented questions can route into existing `documents_drilldown`, but this is not yet a true item purchase provenance chain
|
||||
|
||||
### Needs exact capability
|
||||
- supplier attribution for current residue
|
||||
- supplier-scoped current stock overlap
|
||||
- purchase provenance date / supplier trace for one item
|
||||
- old purchase aging over current residue
|
||||
- unresolved supplier linkage over current stock
|
||||
- downstream sale trace and purchase -> stock -> sale chain
|
||||
|
||||
## Operational rule
|
||||
|
||||
This pool is not treated as out-of-scope.
|
||||
|
||||
Every unresolved question in this pack is domain enablement work inside the project contour.
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
# Domain Scenario Loop - Repo Adapter
|
||||
|
||||
## Purpose
|
||||
|
||||
This repository now supports two outer-loop capture modes:
|
||||
- `run-case` for one concrete domain question;
|
||||
- `run-scenario` for a linked multi-step domain chain that should reuse one assistant session.
|
||||
- `run-pack` for a whole domain question pool grouped into several scenarios.
|
||||
- `run-pack-loop` for an autonomous analyst/coder loop over a whole domain pack.
|
||||
|
||||
`run-scenario` is the preferred capture mode for domains where the user's next question depends on the previous result set.
|
||||
`run-pack` is the preferred capture mode when the user brings a full domain pool that should be kept in one aggregate backlog.
|
||||
|
||||
## Runtime contract
|
||||
|
||||
The scenario runner does not introduce a new product runtime.
|
||||
|
||||
It reuses:
|
||||
- `POST /api/assistant/message`
|
||||
- `GET /api/assistant/session/:session_id`
|
||||
- current backend LLM/profile configuration
|
||||
- current address/deep routing inside the product
|
||||
|
||||
## Artifact contract
|
||||
|
||||
Scenario artifacts live under:
|
||||
|
||||
`artifacts/domain_runs/<scenario_id>/`
|
||||
|
||||
Top-level artifacts:
|
||||
- `scenario_brief.md`
|
||||
- `scenario_manifest.json`
|
||||
- `scenario_state.json`
|
||||
- `scenario_summary.md`
|
||||
- `scenario_output.md`
|
||||
- `final_status.md`
|
||||
|
||||
Per-step artifacts:
|
||||
- `steps/<step_id>/output.md`
|
||||
- `steps/<step_id>/debug.json`
|
||||
- `steps/<step_id>/turn.json`
|
||||
- `steps/<step_id>/session.json`
|
||||
- `steps/<step_id>/assistant_response.json`
|
||||
- `steps/<step_id>/step_state.json`
|
||||
|
||||
Pack artifacts live under:
|
||||
|
||||
`artifacts/domain_runs/<pack_id>/`
|
||||
|
||||
- `pack_manifest.json`
|
||||
- `pack_state.json`
|
||||
- `pack_summary.md`
|
||||
- `final_status.md`
|
||||
- `scenarios/<scenario_id>/...`
|
||||
|
||||
## Placeholder contract
|
||||
|
||||
Scenario questions can reference earlier step outputs with placeholders such as:
|
||||
|
||||
- `{{step_01_inventory.entries[0].item}}`
|
||||
- `{{semantic_memory.active_result_set_id}}`
|
||||
|
||||
This keeps carryover explicit and machine-readable.
|
||||
|
||||
## Status contract
|
||||
|
||||
Scenario capture uses four operational statuses:
|
||||
- `accepted`
|
||||
- `partial`
|
||||
- `blocked`
|
||||
- `needs_exact_capability`
|
||||
|
||||
`partial` means the scenario executed, but one or more steps still need route hardening, evidence hardening, or presentation hardening.
|
||||
`needs_exact_capability` means the scenario is valid for the project, but the current contour still lacks the exact route or capability needed to answer it.
|
||||
|
||||
In autonomous pack-loop mode, `partial` and `needs_exact_capability` are non-terminal by default. The loop should continue domain enablement work until one of these happens:
|
||||
- analyst quality reaches the configured acceptance gate, normally `>= 80`;
|
||||
- the analyst marks `requires_user_decision = true` because the next step would otherwise require guessing a missing required observation, making an architecture-risky change, accepting a hacky/brittle workaround, or choosing a business-critical tradeoff without enough evidence;
|
||||
- the runtime is truly blocked;
|
||||
- the loop reaches `max_iterations`.
|
||||
|
|
@ -0,0 +1,92 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Domain Loop Analyst Verdict",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"summary",
|
||||
"quality_score",
|
||||
"loop_decision",
|
||||
"requires_user_decision",
|
||||
"user_decision_type",
|
||||
"user_decision_prompt",
|
||||
"unresolved_p0_count",
|
||||
"regression_detected",
|
||||
"priority_targets",
|
||||
"acceptance_criteria",
|
||||
"notes"
|
||||
],
|
||||
"properties": {
|
||||
"summary": {
|
||||
"type": "string"
|
||||
},
|
||||
"quality_score": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"maximum": 100
|
||||
},
|
||||
"loop_decision": {
|
||||
"type": "string",
|
||||
"enum": ["accepted", "continue", "partial", "blocked", "needs_exact_capability"]
|
||||
},
|
||||
"requires_user_decision": {
|
||||
"type": "boolean",
|
||||
"description": "Set true only when the autonomous loop must stop and ask the user because the next step is an architecture fork, important business question, scope tradeoff, or another non-autonomous decision."
|
||||
},
|
||||
"user_decision_type": {
|
||||
"type": "string",
|
||||
"enum": ["none", "architecture_fork", "important_business_question", "scope_tradeoff", "data_truth_gap", "missing_required_observation", "risky_workaround", "risky_complexity", "other"],
|
||||
"description": "Explain why the loop needs user input. Use none when requires_user_decision is false."
|
||||
},
|
||||
"user_decision_prompt": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Short user-facing question to unblock the loop when requires_user_decision is true, otherwise null."
|
||||
},
|
||||
"unresolved_p0_count": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"regression_detected": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"priority_targets": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["scenario_id", "step_id", "severity", "problem_type", "fix_goal"],
|
||||
"properties": {
|
||||
"scenario_id": {
|
||||
"type": "string"
|
||||
},
|
||||
"step_id": {
|
||||
"type": ["string", "null"]
|
||||
},
|
||||
"severity": {
|
||||
"type": "string",
|
||||
"enum": ["P0", "P1", "P2"]
|
||||
},
|
||||
"problem_type": {
|
||||
"type": "string",
|
||||
"enum": ["route_gap", "capability_gap", "evidence_gap", "presentation_gap", "regression", "other"]
|
||||
},
|
||||
"fix_goal": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"acceptance_criteria": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Domain Loop Coder Result",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"status",
|
||||
"summary",
|
||||
"changed_files",
|
||||
"notes",
|
||||
"patch_summary_path"
|
||||
],
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["patched", "no_changes", "blocked"]
|
||||
},
|
||||
"summary": {
|
||||
"type": "string"
|
||||
},
|
||||
"changed_files": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"notes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"patch_summary_path": {
|
||||
"type": ["string", "null"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,6 +8,12 @@ const COMPUTE_EXACT_INTENTS = new Set([
|
|||
"account_balance_snapshot",
|
||||
"documents_forming_balance",
|
||||
"inventory_on_hand_as_of_date",
|
||||
"inventory_purchase_provenance_for_item",
|
||||
"inventory_purchase_documents_for_item",
|
||||
"inventory_supplier_stock_overlap_as_of_date",
|
||||
"inventory_sale_trace_for_item",
|
||||
"inventory_purchase_to_sale_chain",
|
||||
"inventory_aging_by_purchase_date",
|
||||
"open_contracts_confirmed_as_of_date",
|
||||
"payables_confirmed_as_of_date",
|
||||
"receivables_confirmed_as_of_date",
|
||||
|
|
@ -55,6 +61,14 @@ function defaultCapabilityId(intent) {
|
|||
if (intent === "inventory_on_hand_as_of_date") {
|
||||
return "confirmed_inventory_on_hand_as_of_date";
|
||||
}
|
||||
if (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date") {
|
||||
return `inventory_${intent}`;
|
||||
}
|
||||
if (intent === "list_payables_counterparties") {
|
||||
return "payables_candidates_list";
|
||||
}
|
||||
|
|
@ -122,6 +136,17 @@ function resolveCapabilityEnabled(intent) {
|
|||
: "inventory_on_hand_route_disabled_by_flag"
|
||||
};
|
||||
}
|
||||
if (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date") {
|
||||
return {
|
||||
enabled: false,
|
||||
reason: "inventory_provenance_route_not_implemented"
|
||||
};
|
||||
}
|
||||
if (intent === "list_payables_counterparties") {
|
||||
return {
|
||||
enabled: config_1.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
|
||||
|
|
@ -181,5 +206,13 @@ function resolveShadowRouteIntent(intent, requestedResultMode) {
|
|||
if (intent === "list_payables_counterparties" && requestedResultMode === "confirmed_balance") {
|
||||
return "payables_confirmed_as_of_date";
|
||||
}
|
||||
if (intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date") {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|||
exports.extractAddressFilters = extractAddressFilters;
|
||||
const iconv_lite_1 = __importDefault(require("iconv-lite"));
|
||||
const ACCOUNT_PATTERN = /(?:сч[её]т|счет|account)[^0-9]{0,12}(\d{2}(?:[.,]\d{1,2})?)/i;
|
||||
const ACCOUNT_REVERSE_PATTERN = /(?:^|[\s,.;:!?()\-])(\d{2}(?:[.,]\d{1,2})?)(?=\s*(?:сч[её]т|счет|account|acct))/iu;
|
||||
const LIMIT_PATTERN = /(?:\btop\b|\blimit\b|первые|топ)[\s\-–—_:№#]*?(\d{1,3})/iu;
|
||||
const COUNTERPARTY_PATTERN = /(?:по\s+контрагенту|контрагент(?:у|а)?|по\s+контре|контра|по\s+компан(?:ии|ию|ия)|компан(?:ия|ии|ию)|по\s+организац(?:ии|ию|ия)|организац(?:ия|ии|ию)|по\s+поставщик(?:у|а)?|поставщик(?:у|а)?|по\s+клиент(?:у|а)?|клиент(?:у|а)?|по\s+покупател(?:ю|я)|покупател(?:ю|я)|по\s+партнер(?:у|а)?|партнер(?:у|а)?|by\s+counterparty|counterparty|by\s+company|company|by\s+supplier|supplier|by\s+vendor|vendor|by\s+customer|customer|by\s+client|client|by\s+partner|partner)\s+([^\r\n,.;:]+)/iu;
|
||||
const CONTRACT_PATTERN = /(?:по\s+(?:договору|контракту)|(?:договор|контракт)(?:у|а)?\s*(?:№|#|n)?|by\s+contract|contract(?:\s*(?:no|number|#|n))?)\s+([^\r\n,.;:]+)/i;
|
||||
|
|
@ -876,7 +877,7 @@ function extractAddressFilters(userMessage, intent) {
|
|||
const warnings = [];
|
||||
const explicitAsOfDate = extractAsOfDate(text);
|
||||
const explicitAsOfDateWithCue = extractAsOfDateWithCue(text);
|
||||
const accountMatch = text.match(ACCOUNT_PATTERN);
|
||||
const accountMatch = text.match(ACCOUNT_REVERSE_PATTERN) ?? text.match(ACCOUNT_PATTERN);
|
||||
if (accountMatch) {
|
||||
filters.account = String(accountMatch[1]).replace(",", ".");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1307,11 +1307,56 @@ function hasInventoryOnHandSignal(text) {
|
|||
if (!hasStockLexeme) {
|
||||
return false;
|
||||
}
|
||||
if (hasInventoryProvenanceSignalV2(text) ||
|
||||
hasInventoryPurchaseDocumentsSignalV2(text) ||
|
||||
hasInventorySaleTraceSignalV2(text)) {
|
||||
return false;
|
||||
}
|
||||
const hasGoodsLexeme = /(?:товар(?:ы|ов|ом|а|ные)?|номенклатур|материал(?:ы|ов|а|ам)?|item(?:s)?|sku|product(?:s)?)/iu.test(text);
|
||||
const hasBalanceLexeme = /(?:леж(?:ит|ат)|есть|числ(?:ит(?:ся|сь)|ятся)|остат(?:ок|ки)|срез|на\s+дат|по\s+состоянию|на\s+конец|today|now|current|as\s+of)/iu.test(text);
|
||||
const hasRequestCue = /(?:покажи|показать|выведи|дай|какие|что|какой|сколько|show|list|which|what)/iu.test(text);
|
||||
return (hasGoodsLexeme || hasBalanceLexeme) && (hasRequestCue || hasBalanceLexeme);
|
||||
}
|
||||
function hasInventoryProvenanceSignal(text) {
|
||||
return /(?:поставщик|закупк|происхожд|откуда|когда был куплен|активная закупк|purchase provenance|purchase date|supplier provenance|stock overlap)/iu.test(text);
|
||||
}
|
||||
function hasInventoryPurchaseDocumentsSignal(text) {
|
||||
return /(?:по каким документам|документы закупки|purchase documents|documents of purchase|through which documents|chain of documents)/iu.test(text);
|
||||
}
|
||||
function hasInventorySaleTraceSignal(text) {
|
||||
return /(?:продаж|покупател|buyer|sale trace|purchase[\s-]?to[\s-]?sale|purchase -> warehouse -> sale|закупка.*продаж)/iu.test(text);
|
||||
}
|
||||
function hasInventoryProvenanceSignalV2(text) {
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product|остат(?:ок|ки)|склад)/iu.test(text);
|
||||
const hasSupplierCue = /(?:от\s+какого\s+поставщика|у\s+какого\s+поставщика|от\s+кого\s+куплен|поставщик|supplier|vendor)/iu.test(text);
|
||||
const hasPurchaseCue = /(?:куплен(?:ы|а)?|закупк|происхождени|откуда|когда\s+был\s+куплен|когда\s+куплен|дата\s+закупк|purchase\s+provenance|purchase\s+date)/iu.test(text);
|
||||
return hasItemCue && hasSupplierCue && hasPurchaseCue;
|
||||
}
|
||||
function hasInventoryPurchaseDocumentsSignalV2(text) {
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
|
||||
const hasPurchaseDocCue = /(?:по\s+каким\s+документам\s+был\s+куплен|по\s+каким\s+документам\s+куплен|какими\s+документами\s+был\s+куплен|документ(?:ам|ы)\s+закупк|purchase\s+documents|documents\s+of\s+purchase|through\s+which\s+documents)/iu.test(text);
|
||||
return hasItemCue && hasPurchaseDocCue;
|
||||
}
|
||||
function hasInventorySaleTraceSignalV2(text) {
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
|
||||
const hasTraceCue = /(?:кому\s+был\s+продан|кто\s+купил|buyer|sale\s+trace|trace\s+of\s+sale|через\s+какие\s+документы\s+прош[её]л\s+путь\s+товара|закупк.*склад.*продаж|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*warehouse\s*->\s*sale|purchase\s*->\s*stock\s*->\s*sale)/iu.test(text);
|
||||
return hasItemCue && hasTraceCue;
|
||||
}
|
||||
function hasInventorySupplierStockOverlapSignal(text) {
|
||||
const hasSupplierCue = /(?:поставщик|supplier|vendor|от\s+поставщика|у\s+поставщика)/iu.test(text);
|
||||
const hasStockCue = /(?:товар|номенклатур|склад|остат(?:ок|ки)|лежат|на\s+дату|по\s+состоянию\s+на\s+дату|current\s+stock|stock\s+overlap|что\s+сейчас\s+лежит)/iu.test(text);
|
||||
return hasSupplierCue && hasStockCue;
|
||||
}
|
||||
function hasInventoryAgingSignal(text) {
|
||||
return /(?:стар(?:ые|ым|ых)\s+закупк|закупал(?:ись|ся)\s+очень\s+давно|очень\s+давно|давно\s+куплен|когда\s+куплен|возраст\s+остатк|aged?\s+stock|old\s+purchase|aging\s+by\s+purchase\s+date|very\s+old\s+stock)/iu.test(text);
|
||||
}
|
||||
function hasInventoryPurchaseToSaleChainSignal(text) {
|
||||
const hasSupplierCue = /(?:поставщик|supplier|vendor|от\s+кого\s+куплен)/iu.test(text);
|
||||
const hasBuyerCue = /(?:покупател|buyer|customer|client|кому\s+был\s+продан)/iu.test(text);
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
|
||||
const hasPurchaseSaleCue = /(?:куплен(?:ы)?|закупк|позже\s+продан(?:ы)?|продан(?:ы)?|purchase|sale|цепочк[аи]\s+движен)/iu.test(text);
|
||||
return (hasSupplierCue && hasBuyerCue && hasItemCue && hasPurchaseSaleCue) || /(?:purchase[\s-]?to[\s-]?sale\s+chain|закупка\s*->\s*склад\s*->\s*продажа)/iu.test(text);
|
||||
}
|
||||
function resolveAddressIntent(userMessage) {
|
||||
const text = String(userMessage ?? "").trim().toLowerCase();
|
||||
if (hasVatLiabilityConfirmedTaxPeriodSignal(text)) {
|
||||
|
|
@ -1414,6 +1459,48 @@ function resolveAddressIntent(userMessage) {
|
|||
reasons: ["documents_by_account_drilldown_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasInventoryProvenanceSignalV2(text)) {
|
||||
return {
|
||||
intent: "inventory_purchase_provenance_for_item",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_provenance_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasInventoryPurchaseDocumentsSignalV2(text)) {
|
||||
return {
|
||||
intent: "inventory_purchase_documents_for_item",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_purchase_documents_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasInventoryPurchaseToSaleChainSignal(text)) {
|
||||
return {
|
||||
intent: "inventory_purchase_to_sale_chain",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_purchase_to_sale_chain_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasInventorySupplierStockOverlapSignal(text)) {
|
||||
return {
|
||||
intent: "inventory_supplier_stock_overlap_as_of_date",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_supplier_stock_overlap_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasInventoryAgingSignal(text)) {
|
||||
return {
|
||||
intent: "inventory_aging_by_purchase_date",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_aging_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasInventorySaleTraceSignalV2(text)) {
|
||||
return {
|
||||
intent: "inventory_sale_trace_for_item",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_sale_trace_signal_detected"]
|
||||
};
|
||||
}
|
||||
if (hasInventoryOnHandSignal(text)) {
|
||||
return {
|
||||
intent: "inventory_on_hand_as_of_date",
|
||||
|
|
@ -1430,6 +1517,10 @@ function resolveAddressIntent(userMessage) {
|
|||
}
|
||||
if (hasAny(text, OPEN_ITEMS_HINTS) &&
|
||||
!hasCounterpartyDebtLongevitySignal(text) &&
|
||||
!hasInventoryAgingSignal(text) &&
|
||||
!hasInventoryProvenanceSignalV2(text) &&
|
||||
!hasInventoryPurchaseDocumentsSignalV2(text) &&
|
||||
!hasInventorySaleTraceSignalV2(text) &&
|
||||
/(?:контраг|договор|контракт|counterparty|contract|покупател|клиент|заказчик|customer|client|buyer|supplier|поставщик)/iu.test(text)) {
|
||||
return {
|
||||
intent: "open_items_by_counterparty_or_contract",
|
||||
|
|
|
|||
|
|
@ -27,6 +27,12 @@ const COMPUTE_EXACT_INTENTS = new Set<AddressIntent>([
|
|||
"account_balance_snapshot",
|
||||
"documents_forming_balance",
|
||||
"inventory_on_hand_as_of_date",
|
||||
"inventory_purchase_provenance_for_item",
|
||||
"inventory_purchase_documents_for_item",
|
||||
"inventory_supplier_stock_overlap_as_of_date",
|
||||
"inventory_sale_trace_for_item",
|
||||
"inventory_purchase_to_sale_chain",
|
||||
"inventory_aging_by_purchase_date",
|
||||
"open_contracts_confirmed_as_of_date",
|
||||
"payables_confirmed_as_of_date",
|
||||
"receivables_confirmed_as_of_date",
|
||||
|
|
@ -78,6 +84,16 @@ function defaultCapabilityId(intent: AddressIntent): string {
|
|||
if (intent === "inventory_on_hand_as_of_date") {
|
||||
return "confirmed_inventory_on_hand_as_of_date";
|
||||
}
|
||||
if (
|
||||
intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date"
|
||||
) {
|
||||
return `inventory_${intent}` as const;
|
||||
}
|
||||
if (intent === "list_payables_counterparties") {
|
||||
return "payables_candidates_list";
|
||||
}
|
||||
|
|
@ -146,6 +162,19 @@ function resolveCapabilityEnabled(intent: AddressIntent): { enabled: boolean; re
|
|||
: "inventory_on_hand_route_disabled_by_flag"
|
||||
};
|
||||
}
|
||||
if (
|
||||
intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date"
|
||||
) {
|
||||
return {
|
||||
enabled: false,
|
||||
reason: "inventory_provenance_route_not_implemented"
|
||||
};
|
||||
}
|
||||
if (intent === "list_payables_counterparties") {
|
||||
return {
|
||||
enabled: FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
|
||||
|
|
@ -211,5 +240,15 @@ export function resolveShadowRouteIntent(
|
|||
if (intent === "list_payables_counterparties" && requestedResultMode === "confirmed_balance") {
|
||||
return "payables_confirmed_as_of_date";
|
||||
}
|
||||
if (
|
||||
intent === "inventory_purchase_provenance_for_item" ||
|
||||
intent === "inventory_purchase_documents_for_item" ||
|
||||
intent === "inventory_supplier_stock_overlap_as_of_date" ||
|
||||
intent === "inventory_sale_trace_for_item" ||
|
||||
intent === "inventory_purchase_to_sale_chain" ||
|
||||
intent === "inventory_aging_by_purchase_date"
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@
|
|||
import iconv from "iconv-lite";
|
||||
|
||||
const ACCOUNT_PATTERN = /(?:сч[её]т|счет|account)[^0-9]{0,12}(\d{2}(?:[.,]\d{1,2})?)/i;
|
||||
const ACCOUNT_REVERSE_PATTERN =
|
||||
/(?:^|[\s,.;:!?()\-])(\d{2}(?:[.,]\d{1,2})?)(?=\s*(?:сч[её]т|счет|account|acct))/iu;
|
||||
const LIMIT_PATTERN = /(?:\btop\b|\blimit\b|первые|топ)[\s\-–—_:№#]*?(\d{1,3})/iu;
|
||||
const COUNTERPARTY_PATTERN =
|
||||
/(?:по\s+контрагенту|контрагент(?:у|а)?|по\s+контре|контра|по\s+компан(?:ии|ию|ия)|компан(?:ия|ии|ию)|по\s+организац(?:ии|ию|ия)|организац(?:ия|ии|ию)|по\s+поставщик(?:у|а)?|поставщик(?:у|а)?|по\s+клиент(?:у|а)?|клиент(?:у|а)?|по\s+покупател(?:ю|я)|покупател(?:ю|я)|по\s+партнер(?:у|а)?|партнер(?:у|а)?|by\s+counterparty|counterparty|by\s+company|company|by\s+supplier|supplier|by\s+vendor|vendor|by\s+customer|customer|by\s+client|client|by\s+partner|partner)\s+([^\r\n,.;:]+)/iu;
|
||||
|
|
@ -995,7 +997,7 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
|
|||
const explicitAsOfDate = extractAsOfDate(text);
|
||||
const explicitAsOfDateWithCue = extractAsOfDateWithCue(text);
|
||||
|
||||
const accountMatch = text.match(ACCOUNT_PATTERN);
|
||||
const accountMatch = text.match(ACCOUNT_REVERSE_PATTERN) ?? text.match(ACCOUNT_PATTERN);
|
||||
if (accountMatch) {
|
||||
filters.account = String(accountMatch[1]).replace(",", ".");
|
||||
}
|
||||
|
|
@ -1221,4 +1223,3 @@ export function extractAddressFilters(userMessage: string, intent: AddressIntent
|
|||
};
|
||||
}
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -1548,6 +1548,13 @@ function hasInventoryOnHandSignal(text: string): boolean {
|
|||
if (!hasStockLexeme) {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
hasInventoryProvenanceSignalV2(text) ||
|
||||
hasInventoryPurchaseDocumentsSignalV2(text) ||
|
||||
hasInventorySaleTraceSignalV2(text)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
const hasGoodsLexeme =
|
||||
/(?:товар(?:ы|ов|ом|а|ные)?|номенклатур|материал(?:ы|ов|а|ам)?|item(?:s)?|sku|product(?:s)?)/iu.test(text);
|
||||
const hasBalanceLexeme =
|
||||
|
|
@ -1558,6 +1565,69 @@ function hasInventoryOnHandSignal(text: string): boolean {
|
|||
return (hasGoodsLexeme || hasBalanceLexeme) && (hasRequestCue || hasBalanceLexeme);
|
||||
}
|
||||
|
||||
function hasInventoryProvenanceSignal(text: string): boolean {
|
||||
return /(?:поставщик|закупк|происхожд|откуда|когда был куплен|активная закупк|purchase provenance|purchase date|supplier provenance|stock overlap)/iu.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function hasInventoryPurchaseDocumentsSignal(text: string): boolean {
|
||||
return /(?:по каким документам|документы закупки|purchase documents|documents of purchase|through which documents|chain of documents)/iu.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function hasInventorySaleTraceSignal(text: string): boolean {
|
||||
return /(?:продаж|покупател|buyer|sale trace|purchase[\s-]?to[\s-]?sale|purchase -> warehouse -> sale|закупка.*продаж)/iu.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function hasInventoryProvenanceSignalV2(text: string): boolean {
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product|остат(?:ок|ки)|склад)/iu.test(text);
|
||||
const hasSupplierCue = /(?:от\s+какого\s+поставщика|у\s+какого\s+поставщика|от\s+кого\s+куплен|поставщик|supplier|vendor)/iu.test(text);
|
||||
const hasPurchaseCue = /(?:куплен(?:ы|а)?|закупк|происхождени|откуда|когда\s+был\s+куплен|когда\s+куплен|дата\s+закупк|purchase\s+provenance|purchase\s+date)/iu.test(text);
|
||||
return hasItemCue && hasSupplierCue && hasPurchaseCue;
|
||||
}
|
||||
|
||||
function hasInventoryPurchaseDocumentsSignalV2(text: string): boolean {
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
|
||||
const hasPurchaseDocCue = /(?:по\s+каким\s+документам\s+был\s+куплен|по\s+каким\s+документам\s+куплен|какими\s+документами\s+был\s+куплен|документ(?:ам|ы)\s+закупк|purchase\s+documents|documents\s+of\s+purchase|through\s+which\s+documents)/iu.test(
|
||||
text
|
||||
);
|
||||
return hasItemCue && hasPurchaseDocCue;
|
||||
}
|
||||
|
||||
function hasInventorySaleTraceSignalV2(text: string): boolean {
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
|
||||
const hasTraceCue = /(?:кому\s+был\s+продан|кто\s+купил|buyer|sale\s+trace|trace\s+of\s+sale|через\s+какие\s+документы\s+прош[её]л\s+путь\s+товара|закупк.*склад.*продаж|purchase[\s-]?to[\s-]?sale|purchase\s*->\s*warehouse\s*->\s*sale|purchase\s*->\s*stock\s*->\s*sale)/iu.test(
|
||||
text
|
||||
);
|
||||
return hasItemCue && hasTraceCue;
|
||||
}
|
||||
|
||||
function hasInventorySupplierStockOverlapSignal(text: string): boolean {
|
||||
const hasSupplierCue = /(?:поставщик|supplier|vendor|от\s+поставщика|у\s+поставщика)/iu.test(text);
|
||||
const hasStockCue = /(?:товар|номенклатур|склад|остат(?:ок|ки)|лежат|на\s+дату|по\s+состоянию\s+на\s+дату|current\s+stock|stock\s+overlap|что\s+сейчас\s+лежит)/iu.test(
|
||||
text
|
||||
);
|
||||
return hasSupplierCue && hasStockCue;
|
||||
}
|
||||
|
||||
function hasInventoryAgingSignal(text: string): boolean {
|
||||
return /(?:стар(?:ые|ым|ых)\s+закупк|закупал(?:ись|ся)\s+очень\s+давно|очень\s+давно|давно\s+куплен|когда\s+куплен|возраст\s+остатк|aged?\s+stock|old\s+purchase|aging\s+by\s+purchase\s+date|very\s+old\s+stock)/iu.test(
|
||||
text
|
||||
);
|
||||
}
|
||||
|
||||
function hasInventoryPurchaseToSaleChainSignal(text: string): boolean {
|
||||
const hasSupplierCue = /(?:поставщик|supplier|vendor|от\s+кого\s+куплен)/iu.test(text);
|
||||
const hasBuyerCue = /(?:покупател|buyer|customer|client|кому\s+был\s+продан)/iu.test(text);
|
||||
const hasItemCue = /(?:товар|номенклатур|sku|item|product)/iu.test(text);
|
||||
const hasPurchaseSaleCue = /(?:куплен(?:ы)?|закупк|позже\s+продан(?:ы)?|продан(?:ы)?|purchase|sale|цепочк[аи]\s+движен)/iu.test(text);
|
||||
return (hasSupplierCue && hasBuyerCue && hasItemCue && hasPurchaseSaleCue) || /(?:purchase[\s-]?to[\s-]?sale\s+chain|закупка\s*->\s*склад\s*->\s*продажа)/iu.test(text);
|
||||
}
|
||||
|
||||
export function resolveAddressIntent(userMessage: string): AddressIntentResolution {
|
||||
const text = String(userMessage ?? "").trim().toLowerCase();
|
||||
|
||||
|
|
@ -1675,6 +1745,54 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
|
|||
};
|
||||
}
|
||||
|
||||
if (hasInventoryProvenanceSignalV2(text)) {
|
||||
return {
|
||||
intent: "inventory_purchase_provenance_for_item",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_provenance_signal_detected"]
|
||||
};
|
||||
}
|
||||
|
||||
if (hasInventoryPurchaseDocumentsSignalV2(text)) {
|
||||
return {
|
||||
intent: "inventory_purchase_documents_for_item",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_purchase_documents_signal_detected"]
|
||||
};
|
||||
}
|
||||
|
||||
if (hasInventoryPurchaseToSaleChainSignal(text)) {
|
||||
return {
|
||||
intent: "inventory_purchase_to_sale_chain",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_purchase_to_sale_chain_signal_detected"]
|
||||
};
|
||||
}
|
||||
|
||||
if (hasInventorySupplierStockOverlapSignal(text)) {
|
||||
return {
|
||||
intent: "inventory_supplier_stock_overlap_as_of_date",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_supplier_stock_overlap_signal_detected"]
|
||||
};
|
||||
}
|
||||
|
||||
if (hasInventoryAgingSignal(text)) {
|
||||
return {
|
||||
intent: "inventory_aging_by_purchase_date",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_aging_signal_detected"]
|
||||
};
|
||||
}
|
||||
|
||||
if (hasInventorySaleTraceSignalV2(text)) {
|
||||
return {
|
||||
intent: "inventory_sale_trace_for_item",
|
||||
confidence: "medium",
|
||||
reasons: ["inventory_sale_trace_signal_detected"]
|
||||
};
|
||||
}
|
||||
|
||||
if (hasInventoryOnHandSignal(text)) {
|
||||
return {
|
||||
intent: "inventory_on_hand_as_of_date",
|
||||
|
|
@ -1694,6 +1812,10 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
|
|||
if (
|
||||
hasAny(text, OPEN_ITEMS_HINTS) &&
|
||||
!hasCounterpartyDebtLongevitySignal(text) &&
|
||||
!hasInventoryAgingSignal(text) &&
|
||||
!hasInventoryProvenanceSignalV2(text) &&
|
||||
!hasInventoryPurchaseDocumentsSignalV2(text) &&
|
||||
!hasInventorySaleTraceSignalV2(text) &&
|
||||
/(?:контраг|договор|контракт|counterparty|contract|покупател|клиент|заказчик|customer|client|buyer|supplier|поставщик)/iu.test(
|
||||
text
|
||||
)
|
||||
|
|
|
|||
|
|
@ -20,6 +20,12 @@ export type AddressIntent =
|
|||
| "receivables_confirmed_as_of_date"
|
||||
| "list_receivables_counterparties"
|
||||
| "inventory_on_hand_as_of_date"
|
||||
| "inventory_purchase_provenance_for_item"
|
||||
| "inventory_purchase_documents_for_item"
|
||||
| "inventory_supplier_stock_overlap_as_of_date"
|
||||
| "inventory_sale_trace_for_item"
|
||||
| "inventory_purchase_to_sale_chain"
|
||||
| "inventory_aging_by_purchase_date"
|
||||
| "account_balance_snapshot"
|
||||
| "open_items_by_counterparty_or_contract"
|
||||
| "list_documents_by_counterparty"
|
||||
|
|
@ -140,7 +146,11 @@ export interface AddressRecipeDefinition {
|
|||
| "open_contracts_confirmed_as_of_balance_profile"
|
||||
| "payables_confirmed_as_of_balance_profile"
|
||||
| "receivables_confirmed_as_of_balance_profile"
|
||||
| "inventory_on_hand_as_of_balance_profile";
|
||||
| "inventory_on_hand_as_of_balance_profile"
|
||||
| "inventory_purchase_provenance_profile"
|
||||
| "inventory_purchase_documents_profile"
|
||||
| "inventory_supplier_stock_overlap_profile"
|
||||
| "inventory_sale_trace_profile";
|
||||
required_filters: Array<keyof AddressFilterSet>;
|
||||
optional_filters: Array<keyof AddressFilterSet>;
|
||||
default_limit: number;
|
||||
|
|
|
|||
|
|
@ -42,6 +42,30 @@ describe("address capability policy", () => {
|
|||
expect(isCapabilityRouteBlocked(decision)).toBe(false);
|
||||
});
|
||||
|
||||
it("marks inventory provenance intents as blocked exact-capability gaps", () => {
|
||||
const decision = resolveAddressCapabilityRouteDecision("inventory_purchase_provenance_for_item");
|
||||
expect(decision.capability_id).toBe("inventory_inventory_purchase_provenance_for_item");
|
||||
expect(decision.capability_layer).toBe("compute");
|
||||
expect(decision.capability_route_mode).toBe("exact");
|
||||
expect(decision.capability_route_enabled).toBe(false);
|
||||
expect(decision.capability_route_reason).toBe("inventory_provenance_route_not_implemented");
|
||||
expect(isCapabilityRouteBlocked(decision)).toBe(true);
|
||||
});
|
||||
|
||||
it("marks purchase-to-sale trace and aging intents as blocked exact-capability gaps", () => {
|
||||
const chainDecision = resolveAddressCapabilityRouteDecision("inventory_purchase_to_sale_chain");
|
||||
expect(chainDecision.capability_id).toBe("inventory_inventory_purchase_to_sale_chain");
|
||||
expect(chainDecision.capability_route_mode).toBe("exact");
|
||||
expect(chainDecision.capability_route_enabled).toBe(false);
|
||||
expect(isCapabilityRouteBlocked(chainDecision)).toBe(true);
|
||||
|
||||
const agingDecision = resolveAddressCapabilityRouteDecision("inventory_aging_by_purchase_date");
|
||||
expect(agingDecision.capability_id).toBe("inventory_inventory_aging_by_purchase_date");
|
||||
expect(agingDecision.capability_route_mode).toBe("exact");
|
||||
expect(agingDecision.capability_route_enabled).toBe(false);
|
||||
expect(isCapabilityRouteBlocked(agingDecision)).toBe(true);
|
||||
});
|
||||
|
||||
it("maps document drilldown intent to navigation capability", () => {
|
||||
const decision = resolveAddressCapabilityRouteDecision("list_documents_by_contract");
|
||||
expect(decision.capability_id).toBe("documents_drilldown");
|
||||
|
|
|
|||
|
|
@ -4394,5 +4394,51 @@ describe("address recipe catalog counterparty filtering", () => {
|
|||
expect(reply.semantics?.result_mode).toBe("confirmed_balance");
|
||||
expect(reply.semantics?.balance_confirmed).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
it("routes inventory provenance questions to a dedicated intent", () => {
|
||||
const result = resolveAddressIntent("От какого поставщика куплен товар Шкаф картоотечный?");
|
||||
expect(result.intent).toBe("inventory_purchase_provenance_for_item");
|
||||
});
|
||||
|
||||
it("keeps inventory supplier overlap questions out of on-hand routing", () => {
|
||||
const result = resolveAddressIntent("Какие товары от поставщика Альфа сейчас лежат на складе?");
|
||||
expect(result.intent).toBe("inventory_supplier_stock_overlap_as_of_date");
|
||||
});
|
||||
|
||||
it("routes inventory purchase document questions to a dedicated intent", () => {
|
||||
const result = resolveAddressIntent("По каким документам был куплен товар Шкаф картоотечный?");
|
||||
expect(result.intent).toBe("inventory_purchase_documents_for_item");
|
||||
});
|
||||
|
||||
it("routes inventory sale chain questions to a dedicated intent", () => {
|
||||
const result = resolveAddressIntent(
|
||||
"Через какие документы прошел путь товара Шкаф картоотечный: закупка -> склад -> продажа?"
|
||||
);
|
||||
expect(result.intent).toBe("inventory_sale_trace_for_item");
|
||||
});
|
||||
|
||||
it("keeps inventory provenance wording out of inventory-on-hand routing", () => {
|
||||
const result = resolveAddressIntent("От кого куплен товар Шкаф картоотечный и когда был куплен?");
|
||||
expect(result.intent).toBe("inventory_purchase_provenance_for_item");
|
||||
});
|
||||
|
||||
it("keeps aging wording out of open-items and bank routing", () => {
|
||||
const result = resolveAddressIntent("Какие товары были куплены очень давно и до сих пор лежат на складе?");
|
||||
expect(result.intent).toBe("inventory_aging_by_purchase_date");
|
||||
});
|
||||
|
||||
it("routes purchase-document trace wording to dedicated inventory intent", () => {
|
||||
const result = resolveAddressIntent("Какими документами был куплен товар Шкаф картоотечный?");
|
||||
expect(result.intent).toBe("inventory_purchase_documents_for_item");
|
||||
});
|
||||
it("keeps very old stock wording in dedicated aging intent", () => {
|
||||
const result = resolveAddressIntent("Есть ли остатки товара, которые закупались очень давно?");
|
||||
expect(result.intent).toBe("inventory_aging_by_purchase_date");
|
||||
});
|
||||
|
||||
it("keeps unresolved stock provenance wording out of open-items routing", () => {
|
||||
const result = resolveAddressIntent("Какие товары сейчас висят в остатке без понятной привязки к поставщику?");
|
||||
expect(result.intent).toBe("inventory_purchase_provenance_for_item");
|
||||
});
|
||||
|
||||
});
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue