ОРРКЕСТРАЦИЯ -Уточнить safety-policy оркестратора: останавливать loop на hard blockers и рискованных решениях, а не только по порогу 80%
This commit is contained in:
parent
2b48229312
commit
9048632d3e
|
|
@ -1,5 +1,5 @@
|
||||||
name = "orchestrator"
|
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 = "gpt-5.4"
|
||||||
model_reasoning_effort = "high"
|
model_reasoning_effort = "high"
|
||||||
sandbox_mode = "workspace-write"
|
sandbox_mode = "workspace-write"
|
||||||
|
|
@ -13,14 +13,18 @@ Primary repo facts:
|
||||||
- The helper runner is python scripts/domain_case_loop.py.
|
- The helper runner is python scripts/domain_case_loop.py.
|
||||||
|
|
||||||
Your job:
|
Your job:
|
||||||
1. Accept one concrete domain case from the user.
|
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>/.
|
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:
|
3. Capture baseline via one of:
|
||||||
- python scripts/domain_case_loop.py run-case ...
|
- python scripts/domain_case_loop.py run-case ...
|
||||||
- python scripts/domain_case_loop.py import-export ...
|
- 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.
|
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.
|
7. Ask domain_analyst for before/after comparison and a quality score.
|
||||||
8. End with one status: accepted | partial | blocked | needs_exact_capability.
|
8. End with one status: accepted | partial | blocked | needs_exact_capability.
|
||||||
|
|
||||||
|
|
@ -31,6 +35,9 @@ Hard rules:
|
||||||
- Keep the loop artifact-driven.
|
- Keep the loop artifact-driven.
|
||||||
- Reuse the existing backend/session/export flow; do not invent a parallel runtime.
|
- 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.
|
- 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:
|
Acceptance gate:
|
||||||
- accepted requires analyst quality_score >= 80
|
- accepted requires analyst quality_score >= 80
|
||||||
|
|
@ -42,6 +49,9 @@ Required artifacts per cycle:
|
||||||
- baseline_output.md
|
- baseline_output.md
|
||||||
- baseline_debug.json
|
- baseline_debug.json
|
||||||
- baseline_turn.json
|
- baseline_turn.json
|
||||||
|
- scenario_manifest.json
|
||||||
|
- scenario_state.json
|
||||||
|
- scenario_summary.md
|
||||||
- analyst_verdict.md
|
- analyst_verdict.md
|
||||||
- coder_plan.md
|
- coder_plan.md
|
||||||
- patch_summary.md
|
- patch_summary.md
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
---
|
---
|
||||||
name: domain-case-loop
|
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
|
# 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
|
## 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;
|
- the route is wrong even if the wording looks better;
|
||||||
- there is a gap between exact compute intent and actual fallback output;
|
- there is a gap between exact compute intent and actual fallback output;
|
||||||
- there are follow-up / continuation bugs that corrupt business context.
|
- 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
|
## 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:
|
Use these repo-native capture paths:
|
||||||
- automated capture: `python scripts/domain_case_loop.py run-case ...`
|
- 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 ...`
|
- 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`
|
- `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
|
- 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
|
## 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
|
### Step 1 - Normalize the case
|
||||||
|
|
||||||
Create `artifacts/domain_runs/<case_id>/case_brief.md` with:
|
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` 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:
|
Accepted requires:
|
||||||
- quality score >= 80
|
- quality score >= 80
|
||||||
- no unresolved P0 defects
|
- 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 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 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.
|
- 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.
|
- Never fabricate 1C data.
|
||||||
- Keep domain fixes minimal and localized.
|
- Keep domain fixes minimal and localized.
|
||||||
- Preserve successful baseline scenarios.
|
- Preserve successful baseline scenarios.
|
||||||
|
|
|
||||||
|
|
@ -21,3 +21,53 @@ artifacts/domain_runs/<case_id>/
|
||||||
- rerun_report_case.json
|
- rerun_report_case.json
|
||||||
- before_after_diff.md
|
- before_after_diff.md
|
||||||
- final_status.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:
|
1. Prefer automated capture with:
|
||||||
- `python scripts/domain_case_loop.py run-case ...`
|
- `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 ...`
|
- `python scripts/domain_case_loop.py import-export ...`
|
||||||
3. Use `baseline_turn.json` / `rerun_turn.json` as canonical analyst input.
|
6. Use `baseline_turn.json` / `rerun_turn.json` as canonical analyst input for case mode.
|
||||||
4. Use `baseline_output.md` / `rerun_output.md` as human-readable paired artifacts.
|
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
|
## Default run assumptions
|
||||||
|
|
||||||
|
|
@ -24,6 +33,9 @@
|
||||||
- eval target: `assistant_stage1`
|
- eval target: `assistant_stage1`
|
||||||
- single-case async run uses generated case id `AUTO-001`
|
- single-case async run uses generated case id `AUTO-001`
|
||||||
- artifact root: `artifacts/domain_runs/<case_id>/`
|
- 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
|
## 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
|
## codex_domain_loop
|
||||||
- Project-scoped Codex orchestration lives under `.codex/`.
|
- Project-scoped Codex orchestration lives under `.codex/`.
|
||||||
- Use `.codex/skills/domain-case-loop` for repeatable domain hardening loops on one concrete case.
|
- 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.
|
- 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.
|
- 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.
|
- 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".
|
- 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.
|
- 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",
|
"account_balance_snapshot",
|
||||||
"documents_forming_balance",
|
"documents_forming_balance",
|
||||||
"inventory_on_hand_as_of_date",
|
"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",
|
"open_contracts_confirmed_as_of_date",
|
||||||
"payables_confirmed_as_of_date",
|
"payables_confirmed_as_of_date",
|
||||||
"receivables_confirmed_as_of_date",
|
"receivables_confirmed_as_of_date",
|
||||||
|
|
@ -55,6 +61,14 @@ function defaultCapabilityId(intent) {
|
||||||
if (intent === "inventory_on_hand_as_of_date") {
|
if (intent === "inventory_on_hand_as_of_date") {
|
||||||
return "confirmed_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") {
|
if (intent === "list_payables_counterparties") {
|
||||||
return "payables_candidates_list";
|
return "payables_candidates_list";
|
||||||
}
|
}
|
||||||
|
|
@ -122,6 +136,17 @@ function resolveCapabilityEnabled(intent) {
|
||||||
: "inventory_on_hand_route_disabled_by_flag"
|
: "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") {
|
if (intent === "list_payables_counterparties") {
|
||||||
return {
|
return {
|
||||||
enabled: config_1.FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
|
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") {
|
if (intent === "list_payables_counterparties" && requestedResultMode === "confirmed_balance") {
|
||||||
return "payables_confirmed_as_of_date";
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
||||||
exports.extractAddressFilters = extractAddressFilters;
|
exports.extractAddressFilters = extractAddressFilters;
|
||||||
const iconv_lite_1 = __importDefault(require("iconv-lite"));
|
const iconv_lite_1 = __importDefault(require("iconv-lite"));
|
||||||
const ACCOUNT_PATTERN = /(?:сч[её]т|счет|account)[^0-9]{0,12}(\d{2}(?:[.,]\d{1,2})?)/i;
|
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 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 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;
|
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 warnings = [];
|
||||||
const explicitAsOfDate = extractAsOfDate(text);
|
const explicitAsOfDate = extractAsOfDate(text);
|
||||||
const explicitAsOfDateWithCue = extractAsOfDateWithCue(text);
|
const explicitAsOfDateWithCue = extractAsOfDateWithCue(text);
|
||||||
const accountMatch = text.match(ACCOUNT_PATTERN);
|
const accountMatch = text.match(ACCOUNT_REVERSE_PATTERN) ?? text.match(ACCOUNT_PATTERN);
|
||||||
if (accountMatch) {
|
if (accountMatch) {
|
||||||
filters.account = String(accountMatch[1]).replace(",", ".");
|
filters.account = String(accountMatch[1]).replace(",", ".");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1307,11 +1307,56 @@ function hasInventoryOnHandSignal(text) {
|
||||||
if (!hasStockLexeme) {
|
if (!hasStockLexeme) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (hasInventoryProvenanceSignalV2(text) ||
|
||||||
|
hasInventoryPurchaseDocumentsSignalV2(text) ||
|
||||||
|
hasInventorySaleTraceSignalV2(text)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const hasGoodsLexeme = /(?:товар(?:ы|ов|ом|а|ные)?|номенклатур|материал(?:ы|ов|а|ам)?|item(?:s)?|sku|product(?:s)?)/iu.test(text);
|
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 hasBalanceLexeme = /(?:леж(?:ит|ат)|есть|числ(?:ит(?:ся|сь)|ятся)|остат(?:ок|ки)|срез|на\s+дат|по\s+состоянию|на\s+конец|today|now|current|as\s+of)/iu.test(text);
|
||||||
const hasRequestCue = /(?:покажи|показать|выведи|дай|какие|что|какой|сколько|show|list|which|what)/iu.test(text);
|
const hasRequestCue = /(?:покажи|показать|выведи|дай|какие|что|какой|сколько|show|list|which|what)/iu.test(text);
|
||||||
return (hasGoodsLexeme || hasBalanceLexeme) && (hasRequestCue || hasBalanceLexeme);
|
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) {
|
function resolveAddressIntent(userMessage) {
|
||||||
const text = String(userMessage ?? "").trim().toLowerCase();
|
const text = String(userMessage ?? "").trim().toLowerCase();
|
||||||
if (hasVatLiabilityConfirmedTaxPeriodSignal(text)) {
|
if (hasVatLiabilityConfirmedTaxPeriodSignal(text)) {
|
||||||
|
|
@ -1414,6 +1459,48 @@ function resolveAddressIntent(userMessage) {
|
||||||
reasons: ["documents_by_account_drilldown_signal_detected"]
|
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)) {
|
if (hasInventoryOnHandSignal(text)) {
|
||||||
return {
|
return {
|
||||||
intent: "inventory_on_hand_as_of_date",
|
intent: "inventory_on_hand_as_of_date",
|
||||||
|
|
@ -1430,6 +1517,10 @@ function resolveAddressIntent(userMessage) {
|
||||||
}
|
}
|
||||||
if (hasAny(text, OPEN_ITEMS_HINTS) &&
|
if (hasAny(text, OPEN_ITEMS_HINTS) &&
|
||||||
!hasCounterpartyDebtLongevitySignal(text) &&
|
!hasCounterpartyDebtLongevitySignal(text) &&
|
||||||
|
!hasInventoryAgingSignal(text) &&
|
||||||
|
!hasInventoryProvenanceSignalV2(text) &&
|
||||||
|
!hasInventoryPurchaseDocumentsSignalV2(text) &&
|
||||||
|
!hasInventorySaleTraceSignalV2(text) &&
|
||||||
/(?:контраг|договор|контракт|counterparty|contract|покупател|клиент|заказчик|customer|client|buyer|supplier|поставщик)/iu.test(text)) {
|
/(?:контраг|договор|контракт|counterparty|contract|покупател|клиент|заказчик|customer|client|buyer|supplier|поставщик)/iu.test(text)) {
|
||||||
return {
|
return {
|
||||||
intent: "open_items_by_counterparty_or_contract",
|
intent: "open_items_by_counterparty_or_contract",
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,12 @@ const COMPUTE_EXACT_INTENTS = new Set<AddressIntent>([
|
||||||
"account_balance_snapshot",
|
"account_balance_snapshot",
|
||||||
"documents_forming_balance",
|
"documents_forming_balance",
|
||||||
"inventory_on_hand_as_of_date",
|
"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",
|
"open_contracts_confirmed_as_of_date",
|
||||||
"payables_confirmed_as_of_date",
|
"payables_confirmed_as_of_date",
|
||||||
"receivables_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") {
|
if (intent === "inventory_on_hand_as_of_date") {
|
||||||
return "confirmed_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") {
|
if (intent === "list_payables_counterparties") {
|
||||||
return "payables_candidates_list";
|
return "payables_candidates_list";
|
||||||
}
|
}
|
||||||
|
|
@ -146,6 +162,19 @@ function resolveCapabilityEnabled(intent: AddressIntent): { enabled: boolean; re
|
||||||
: "inventory_on_hand_route_disabled_by_flag"
|
: "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") {
|
if (intent === "list_payables_counterparties") {
|
||||||
return {
|
return {
|
||||||
enabled: FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
|
enabled: FEATURE_ASSISTANT_ROUTE_PAYABLES_HEURISTIC_V1,
|
||||||
|
|
@ -211,5 +240,15 @@ export function resolveShadowRouteIntent(
|
||||||
if (intent === "list_payables_counterparties" && requestedResultMode === "confirmed_balance") {
|
if (intent === "list_payables_counterparties" && requestedResultMode === "confirmed_balance") {
|
||||||
return "payables_confirmed_as_of_date";
|
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;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,8 @@
|
||||||
import iconv from "iconv-lite";
|
import iconv from "iconv-lite";
|
||||||
|
|
||||||
const ACCOUNT_PATTERN = /(?:сч[её]т|счет|account)[^0-9]{0,12}(\d{2}(?:[.,]\d{1,2})?)/i;
|
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 LIMIT_PATTERN = /(?:\btop\b|\blimit\b|первые|топ)[\s\-–—_:№#]*?(\d{1,3})/iu;
|
||||||
const COUNTERPARTY_PATTERN =
|
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;
|
/(?:по\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 explicitAsOfDate = extractAsOfDate(text);
|
||||||
const explicitAsOfDateWithCue = extractAsOfDateWithCue(text);
|
const explicitAsOfDateWithCue = extractAsOfDateWithCue(text);
|
||||||
|
|
||||||
const accountMatch = text.match(ACCOUNT_PATTERN);
|
const accountMatch = text.match(ACCOUNT_REVERSE_PATTERN) ?? text.match(ACCOUNT_PATTERN);
|
||||||
if (accountMatch) {
|
if (accountMatch) {
|
||||||
filters.account = String(accountMatch[1]).replace(",", ".");
|
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) {
|
if (!hasStockLexeme) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if (
|
||||||
|
hasInventoryProvenanceSignalV2(text) ||
|
||||||
|
hasInventoryPurchaseDocumentsSignalV2(text) ||
|
||||||
|
hasInventorySaleTraceSignalV2(text)
|
||||||
|
) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const hasGoodsLexeme =
|
const hasGoodsLexeme =
|
||||||
/(?:товар(?:ы|ов|ом|а|ные)?|номенклатур|материал(?:ы|ов|а|ам)?|item(?:s)?|sku|product(?:s)?)/iu.test(text);
|
/(?:товар(?:ы|ов|ом|а|ные)?|номенклатур|материал(?:ы|ов|а|ам)?|item(?:s)?|sku|product(?:s)?)/iu.test(text);
|
||||||
const hasBalanceLexeme =
|
const hasBalanceLexeme =
|
||||||
|
|
@ -1558,6 +1565,69 @@ function hasInventoryOnHandSignal(text: string): boolean {
|
||||||
return (hasGoodsLexeme || hasBalanceLexeme) && (hasRequestCue || hasBalanceLexeme);
|
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 {
|
export function resolveAddressIntent(userMessage: string): AddressIntentResolution {
|
||||||
const text = String(userMessage ?? "").trim().toLowerCase();
|
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)) {
|
if (hasInventoryOnHandSignal(text)) {
|
||||||
return {
|
return {
|
||||||
intent: "inventory_on_hand_as_of_date",
|
intent: "inventory_on_hand_as_of_date",
|
||||||
|
|
@ -1694,6 +1812,10 @@ export function resolveAddressIntent(userMessage: string): AddressIntentResoluti
|
||||||
if (
|
if (
|
||||||
hasAny(text, OPEN_ITEMS_HINTS) &&
|
hasAny(text, OPEN_ITEMS_HINTS) &&
|
||||||
!hasCounterpartyDebtLongevitySignal(text) &&
|
!hasCounterpartyDebtLongevitySignal(text) &&
|
||||||
|
!hasInventoryAgingSignal(text) &&
|
||||||
|
!hasInventoryProvenanceSignalV2(text) &&
|
||||||
|
!hasInventoryPurchaseDocumentsSignalV2(text) &&
|
||||||
|
!hasInventorySaleTraceSignalV2(text) &&
|
||||||
/(?:контраг|договор|контракт|counterparty|contract|покупател|клиент|заказчик|customer|client|buyer|supplier|поставщик)/iu.test(
|
/(?:контраг|договор|контракт|counterparty|contract|покупател|клиент|заказчик|customer|client|buyer|supplier|поставщик)/iu.test(
|
||||||
text
|
text
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -20,6 +20,12 @@ export type AddressIntent =
|
||||||
| "receivables_confirmed_as_of_date"
|
| "receivables_confirmed_as_of_date"
|
||||||
| "list_receivables_counterparties"
|
| "list_receivables_counterparties"
|
||||||
| "inventory_on_hand_as_of_date"
|
| "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"
|
| "account_balance_snapshot"
|
||||||
| "open_items_by_counterparty_or_contract"
|
| "open_items_by_counterparty_or_contract"
|
||||||
| "list_documents_by_counterparty"
|
| "list_documents_by_counterparty"
|
||||||
|
|
@ -140,7 +146,11 @@ export interface AddressRecipeDefinition {
|
||||||
| "open_contracts_confirmed_as_of_balance_profile"
|
| "open_contracts_confirmed_as_of_balance_profile"
|
||||||
| "payables_confirmed_as_of_balance_profile"
|
| "payables_confirmed_as_of_balance_profile"
|
||||||
| "receivables_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>;
|
required_filters: Array<keyof AddressFilterSet>;
|
||||||
optional_filters: Array<keyof AddressFilterSet>;
|
optional_filters: Array<keyof AddressFilterSet>;
|
||||||
default_limit: number;
|
default_limit: number;
|
||||||
|
|
|
||||||
|
|
@ -42,6 +42,30 @@ describe("address capability policy", () => {
|
||||||
expect(isCapabilityRouteBlocked(decision)).toBe(false);
|
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", () => {
|
it("maps document drilldown intent to navigation capability", () => {
|
||||||
const decision = resolveAddressCapabilityRouteDecision("list_documents_by_contract");
|
const decision = resolveAddressCapabilityRouteDecision("list_documents_by_contract");
|
||||||
expect(decision.capability_id).toBe("documents_drilldown");
|
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?.result_mode).toBe("confirmed_balance");
|
||||||
expect(reply.semantics?.balance_confirmed).toBe(true);
|
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