АДРЕСНЫЙ РЕЖИМ - Batch-2 (Phase A/B): стабилизация LLM-маршрутизации жизненного цикла контрагентов и договоров + актуализация документации

This commit is contained in:
dctouch 2026-04-08 12:42:02 +03:00
parent eec5ef4fc0
commit 1f9bb886ee
28 changed files with 2281 additions and 128 deletions

4
.gitignore vendored
View File

@ -21,3 +21,7 @@ llm_normalizer/reports/normalizer_v*_micro_eval.md
llm_normalizer/reports/minimal_package_*
llm_normalizer/docs/runs/*/
!llm_normalizer/docs/runs/README.md
# graphify artifacts
graphify-out/
.graphify_*

29
.graphifyignore Normal file
View File

@ -0,0 +1,29 @@
# Core VCS and caches
.git/
.vs/
.pytest_cache/
__pycache__/
.venv/
graphify-out/
# Runtime outputs and local datasets
logs/
tmp/
data/
llm_normalizer/data/traces/
llm_normalizer/data/assistant_sessions/
llm_normalizer/reports/
docs/ARCH/9_audit_artifacts/snapshot_samples/
# Frontend/build artifacts
**/node_modules/
**/dist/
**/.next/
**/.turbo/
# Large binary artifacts
*.zip
*.7z
*.db
*.sqlite
*.sqlite3

8
AGENTS.md Normal file
View File

@ -0,0 +1,8 @@
## graphify
This project has a graphify knowledge graph at graphify-out/.
Rules:
- Before answering architecture or codebase questions, read graphify-out/GRAPH_REPORT.md for god nodes and community structure
- If graphify-out/wiki/index.md exists, navigate it instead of reading raw files
- After modifying code files in this session, run `python -c "from graphify.watch import _rebuild_code; from pathlib import Path; _rebuild_code(Path('.'))"` to keep the graph current

View File

@ -1,94 +0,0 @@
# ADDRESS Test Questions (обновлено 2026-04-02)
Важно:
1. Цепочки `FOLLOW-UP` прогоняй в одной сессии подряд.
2. Блоки `SINGLE` можно гонять по отдельности.
3. Для проверок смотри не только ответ, но и `technical_debug_payload_json`.
## SINGLE: Документы по контрагенту
1. Покажи документы по СВК за 2020 год
2. свк доки за 20й
3. что по свк за 2020 выведи все доки плиз
4. Какие документы по контрагенту СВК за весь период
5. какие есть доки по свк с 2000 по 2025 год
6. Покажи документы по контрагенту СВК
## SINGLE: Банковские операции
7. Покажи банковские операции по СВК за 2020
8. Какие платежи по СВК в 2020
9. Покажи банковские операции по контрагенту СВК за весь период
## SINGLE: Договор
10. Покажи документы по договору 19/15 за 2020
11. Покажи документы по договору 19/15
12. Покажи банковские операции по договору 19/15
13. Есть ли долг по договору 19/15 на 2020-07-31
14. Покажи операции по договору 19/15 за весь период
## SINGLE: Сальдо / остаток
15. Покажи сальдо по счету 60.01 на дату 2020-07-31
16. Какой остаток по счету 60 на 2020.05
17. Какой остаток по счету 60 на 2020 май
18. Какие документы формируют остаток по счету 60.01 на 2020-07-31
19. Какой остаток по счету 62 на сегодня
## FOLLOW-UP CHAIN A (одна сессия)
20. Покажи документы по СВК за 2020
21. а теперь только за май 2020
22. а за все время?
23. а по счету 60.01 на ту же дату
## FOLLOW-UP CHAIN B (одна сессия)
24. Покажи банковские операции по договору 19/15
25. а за 2020 год
26. а теперь только за май 2020
27. а по счету 51 на ту же дату
## FOLLOW-UP CHAIN C (одна сессия)
28. Какой остаток по счету 60 на 2020.05
29. а теперь по 60.01
30. а на ту же дату по 62
## NOISY / TYPO
31. бля епт покажи доки по свк за 20й
32. свк 20 год - покажи доки плс
33. покажи банк опер по дог 19/15 пж
34. остаток 60 на 2020 май покеж
35. по свк за весь период че есть
## NEGATIVE / LIMIT CASE
36. Покажи технические константы конфигурации
37. кто президент США
## SINGLE: Дебиторка / Кредиторка
38. Кто нам должен на 2020-12-31
39. Кому мы должны на 2020-12-31
40. Покажи дебиторскую задолженность за 2020
41. Покажи кредиторскую задолженность за 2020
## SINGLE: Незакрытые договоры / открытые позиции
42. Покажи незакрытые договоры на 2020-12-31
43. Какие открытые договоры есть на 2020-12-31
44. Покажи хвосты по контрагенту СВК на 2020-12-31
45. Есть ли незакрытые позиции по договору 19/15 на 2020-12-31
46. Покажи открытые позиции по договору 19/15
## FOLLOW-UP CHAIN D (одна сессия)
47. Покажи документы по контрагенту СВК за 2020
48. а только за июль 2020
49. а теперь банковские операции
50. а за весь период
51. а сальдо по счету 51 на ту же дату
## FOLLOW-UP CHAIN E (одна сессия)
52. Покажи незакрытые позиции по контрагенту СВК
53. а на 2020-12-31
54. а теперь по договору 19/15
55. а только банковские операции
56. а за май 2020
## EN / Transliteration Stress
57. show documents by counterparty svk for 2020
58. show bank operations by contract 19/15
59. kakoi ostatok po schetu 60.01 na 2020-07-31
60. kto nam dolzhen na 2020-12-31

669
docs/ADDRESS/1915.txt Normal file
View File

@ -0,0 +1,669 @@
# Assistant conversation export
session_id: asst-qsZmniA1bx
export_mode: technical
exported_at: 2026-04-08T08:42:43.575Z
## 1. user
message_id: msg-f9gF7eEEGy
created_at: 2026-04-08T08:40:56.493Z
reply_type: n/a
Покажи документы по договору 19/15 за 2020
## 2. assistant
message_id: msg-Je6z6-84tL
created_at: 2026-04-08T08:41:03.503Z
reply_type: factual
trace_id: address-bMqqNTkC2y
Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк.
Собран список документов по договору (live address lane).
Строк отобрано: 8.
1. 2020-08-31T15:00:06Z | Поступление товаров и услуг 00000000024 от 31.08.2020 15:00:06 | 19.04 / 76.05 | 233.33
2. 2020-07-15T13:11:19Z | Реализация товаров и услуг 00000000008 от 15.07.2020 13:11:19 | 62.01 / 90.01.1 | 43804.8
3. 2020-07-15T13:11:19Z | Реализация товаров и услуг 00000000008 от 15.07.2020 13:11:19 | 62.01 / 90.01.1 | 9776
4. 2020-07-15T13:11:19Z | Реализация товаров и услуг 00000000008 от 15.07.2020 13:11:19 | 62.01 / 90.01.1 | 73968
5. 2020-07-15T13:11:19Z | Реализация товаров и услуг 00000000008 от 15.07.2020 13:11:19 | 62.01 / 90.01.1 | 94924.8
6. 2020-07-15T13:11:19Z | Реализация товаров и услуг 00000000008 от 15.07.2020 13:11:19 | 62.01 / 90.01.1 | 54400
7. 2020-07-15T13:11:19Z | Реализация товаров и услуг 00000000008 от 15.07.2020 13:11:19 | 62.02 / 62.01 | 276873.6
8. 2020-06-30T12:00:00Z | Поступление товаров и услуг 00000000015 от 30.06.2020 12:00:00 | 19.04 / 76.05 | 233.33
### technical_debug_payload_json
```json
{
"trace_id": "address-bMqqNTkC2y",
"prompt_version": "address_query_runtime_v1",
"schema_version": "address_query_runtime_v1",
"fallback_type": "none",
"route_summary": null,
"fragments": [],
"requirements_extracted": [],
"coverage_report": {
"requirements_total": 0,
"requirements_covered": 0,
"requirements_uncovered": [],
"requirements_partially_covered": [],
"clarification_needed_for": [],
"out_of_scope_requirements": []
},
"routes": [],
"retrieval_status": [],
"retrieval_results": [],
"answer_grounding_check": {
"status": "grounded",
"route_subject_match": true,
"missing_requirements": [],
"reasons": [
"address_action_detected",
"address_entity_detected",
"document_list_signal_detected",
"documents_by_contract_signal_detected",
"query_limit_auto_expanded_for_anchor_recovery"
],
"why_included_summary": [],
"selection_reason_summary": []
},
"dropped_intent_segments": [],
"detected_mode": "address_query",
"detected_mode_confidence": "high",
"query_shape": "DOCUMENT_LIST",
"query_shape_confidence": "medium",
"detected_intent": "list_documents_by_contract",
"detected_intent_confidence": "medium",
"extracted_filters": {
"sort": "period_desc",
"limit": 20,
"contract": "19/15",
"period_from": "2020-01-01",
"period_to": "2020-12-31"
},
"missing_required_filters": [],
"selected_recipe": "address_documents_by_contract_v1",
"mcp_call_status_legacy": "matched_non_empty",
"account_scope_mode": "preferred",
"account_scope_fallback_applied": false,
"anchor_type": "contract",
"anchor_value_raw": "19/15",
"anchor_value_resolved": "19/15",
"resolver_confidence": "medium",
"ambiguity_count": 0,
"match_failure_stage": "none",
"match_failure_reason": null,
"mcp_call_status": "matched_non_empty",
"rows_fetched": 1000,
"raw_rows_received": 1000,
"rows_after_account_scope": 438,
"rows_after_recipe_filter": 11,
"rows_materialized": 438,
"rows_matched": 8,
"raw_row_keys_sample": [
"Период",
"Регистратор",
"СчетДт",
"СчетКт",
"Сумма",
"Period",
"Registrator",
"AccountDt",
"AccountKt",
"Amount"
],
"materialization_drop_reason": "none",
"account_token_raw": null,
"account_token_normalized": null,
"account_scope_fields_checked": [
"account_dt",
"account_kt",
"registrator",
"analytics"
],
"account_scope_match_strategy": "account_code_regex_plus_alias_map_v1",
"account_scope_drop_reason": "not_applicable",
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
"limited_reason_category": null,
"response_type": "FACTUAL_LIST",
"execution_lane": "address_query",
"llm_decomposition_applied": true,
"llm_decomposition_attempted": true,
"llm_provider_used": "local",
"llm_decomposition_trace_id": "RsRWAyD29sjgY7",
"llm_decomposition_effective_message": "Показать документы по договору 19/15 за период 2020 года.",
"llm_decomposition_reason": "normalized_fragment_applied",
"llm_canonical_candidate_detected": true,
"llm_predecompose_contract": {
"schema_version": "address_llm_predecompose_contract_v1",
"source_message": "Покажи документы по договору 19/15 за 2020",
"canonical_message": "Показать документы по договору 19/15 за период 2020 года.",
"mode": "address_query",
"mode_confidence": "high",
"query_shape": "DOCUMENT_LIST",
"query_shape_confidence": "medium",
"intent": "list_documents_by_contract",
"intent_confidence": "medium",
"entities": {
"account": null,
"counterparty": null,
"contract": "19/15",
"document_type": null,
"document_ref": null,
"organization": null
},
"period": {
"scope": "year",
"period_from": "2020-01-01",
"period_to": "2020-12-31",
"as_of_date": null,
"has_explicit_period": true
},
"aggregation_profile": "list_lookup"
},
"fallback_rule_hit": null,
"sanitized_user_message": "покажи документы по договору 19/15 за 2020",
"tool_gate_decision": "run_address_lane",
"tool_gate_reason": "address_mode_classifier_detected",
"answer_structure_v11": null,
"investigation_state_snapshot": null,
"normalized": null,
"normalizer_output": {
"trace_id": "RsRWAyD29sjgY7",
"prompt_version": "normalizer_v2_0_2",
"applied": true,
"effective_message": "Показать документы по договору 19/15 за период 2020 года."
}
}
```
## 3. user
message_id: msg-1w0963YnGN
created_at: 2026-04-08T08:41:09.598Z
reply_type: n/a
а теперь банковские операции
## 4. assistant
message_id: msg-76jKqn5b1O
created_at: 2026-04-08T08:41:17.668Z
reply_type: factual
trace_id: address-mB-Zxj7T3o
Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк.
Собран список банковских операций по договору (live address lane).
Строк отобрано: 3.
1. 2020-10-15T13:34:19Z | Списание с расчетного счета 00000000245 от 15.10.2020 13:34:19 | 66.01 / 51 | 134206.53
2. 2020-07-15T13:11:23Z | Списание с расчетного счета 00000000191 от 15.07.2020 13:11:23 | 66.02 / 51 | 54125.93
3. 2020-07-15T13:11:22Z | Списание с расчетного счета 00000000190 от 15.07.2020 13:11:22 | 66.01 / 51 | 122035.07
### technical_debug_payload_json
```json
{
"trace_id": "address-mB-Zxj7T3o",
"prompt_version": "address_query_runtime_v1",
"schema_version": "address_query_runtime_v1",
"fallback_type": "none",
"route_summary": null,
"fragments": [],
"requirements_extracted": [],
"coverage_report": {
"requirements_total": 0,
"requirements_covered": 0,
"requirements_uncovered": [],
"requirements_partially_covered": [],
"clarification_needed_for": [],
"out_of_scope_requirements": []
},
"routes": [],
"retrieval_status": [],
"retrieval_results": [],
"answer_grounding_check": {
"status": "grounded",
"route_subject_match": true,
"missing_requirements": [],
"reasons": [
"address_entity_detected",
"shape_not_detected",
"bank_ops_by_counterparty_signal_detected",
"intent_adjusted_to_contract_followup_context",
"contract_from_followup_context",
"period_from_followup_context",
"address_followup_context_applied",
"query_limit_auto_expanded_for_anchor_recovery"
],
"why_included_summary": [],
"selection_reason_summary": []
},
"dropped_intent_segments": [],
"detected_mode": "address_query",
"detected_mode_confidence": "medium",
"query_shape": "UNKNOWN",
"query_shape_confidence": "low",
"detected_intent": "bank_operations_by_contract",
"detected_intent_confidence": "low",
"extracted_filters": {
"sort": "period_desc",
"limit": 20,
"contract": "19/15",
"period_from": "2020-01-01",
"period_to": "2020-12-31"
},
"missing_required_filters": [],
"selected_recipe": "address_bank_operations_by_contract_v1",
"mcp_call_status_legacy": "matched_non_empty",
"account_scope_mode": "preferred",
"account_scope_fallback_applied": false,
"anchor_type": "contract",
"anchor_value_raw": "19/15",
"anchor_value_resolved": "19/15",
"resolver_confidence": "medium",
"ambiguity_count": 0,
"match_failure_stage": "none",
"match_failure_reason": null,
"mcp_call_status": "matched_non_empty",
"rows_fetched": 1000,
"raw_rows_received": 1000,
"rows_after_account_scope": 234,
"rows_after_recipe_filter": 3,
"rows_materialized": 234,
"rows_matched": 3,
"raw_row_keys_sample": [
"Период",
"Регистратор",
"СчетДт",
"СчетКт",
"Сумма",
"Period",
"Registrator",
"AccountDt",
"AccountKt",
"Amount"
],
"materialization_drop_reason": "none",
"account_token_raw": null,
"account_token_normalized": null,
"account_scope_fields_checked": [
"account_dt",
"account_kt",
"registrator",
"analytics"
],
"account_scope_match_strategy": "account_code_regex_plus_alias_map_v1",
"account_scope_drop_reason": "not_applicable",
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
"limited_reason_category": null,
"response_type": "FACTUAL_LIST",
"execution_lane": "address_query",
"llm_decomposition_applied": false,
"llm_decomposition_attempted": true,
"llm_provider_used": "local",
"llm_decomposition_trace_id": "fQPoAyba2nzNW_",
"llm_decomposition_effective_message": "а теперь банковские операции",
"llm_decomposition_reason": "normalized_fragment_same",
"llm_canonical_candidate_detected": true,
"llm_predecompose_contract": {
"schema_version": "address_llm_predecompose_contract_v1",
"source_message": "а теперь банковские операции",
"canonical_message": "а теперь банковские операции",
"mode": "address_query",
"mode_confidence": "medium",
"query_shape": "UNKNOWN",
"query_shape_confidence": "low",
"intent": "bank_operations_by_counterparty",
"intent_confidence": "medium",
"entities": {
"account": null,
"counterparty": null,
"contract": null,
"document_type": null,
"document_ref": null,
"organization": null
},
"period": {
"scope": "unspecified",
"period_from": null,
"period_to": null,
"as_of_date": null,
"has_explicit_period": false
},
"aggregation_profile": "list_lookup"
},
"fallback_rule_hit": null,
"sanitized_user_message": "а теперь банковские операции",
"tool_gate_decision": "run_address_lane",
"tool_gate_reason": "address_mode_classifier_detected",
"answer_structure_v11": null,
"investigation_state_snapshot": null,
"normalized": null,
"normalizer_output": {
"trace_id": "fQPoAyba2nzNW_",
"prompt_version": "normalizer_v2_0_2",
"applied": false,
"effective_message": "а теперь банковские операции"
}
}
```
## 5. user
message_id: msg-FfUbRQG205
created_at: 2026-04-08T08:41:40.344Z
reply_type: n/a
есть ли открытые позиции по договору 19/15 на 2020-12-31
## 6. assistant
message_id: msg-kML8gXvOyB
created_at: 2026-04-08T08:41:48.227Z
reply_type: factual
trace_id: address-oAwR0ldclP
Период сохранен. Глубина live-выборки автоматически расширена до 1000 строк.
Собраны открытые позиции по указанному фильтру (контрагент/договор).
Строк отобрано: 8.
1. 2020-08-31T15:00:06Z | Поступление товаров и услуг 00000000024 от 31.08.2020 15:00:06 | 19.04 / 76.05 | 233.33
2. 2020-07-15T13:11:19Z | Реализация товаров и услуг 00000000008 от 15.07.2020 13:11:19 | 62.01 / 90.01.1 | 43804.8
3. 2020-07-15T13:11:19Z | Реализация товаров и услуг 00000000008 от 15.07.2020 13:11:19 | 62.01 / 90.01.1 | 9776
4. 2020-07-15T13:11:19Z | Реализация товаров и услуг 00000000008 от 15.07.2020 13:11:19 | 62.01 / 90.01.1 | 73968
5. 2020-07-15T13:11:19Z | Реализация товаров и услуг 00000000008 от 15.07.2020 13:11:19 | 62.01 / 90.01.1 | 94924.8
6. 2020-07-15T13:11:19Z | Реализация товаров и услуг 00000000008 от 15.07.2020 13:11:19 | 62.01 / 90.01.1 | 54400
### technical_debug_payload_json
```json
{
"trace_id": "address-oAwR0ldclP",
"prompt_version": "address_query_runtime_v1",
"schema_version": "address_query_runtime_v1",
"fallback_type": "none",
"route_summary": null,
"fragments": [],
"requirements_extracted": [],
"coverage_report": {
"requirements_total": 0,
"requirements_covered": 0,
"requirements_uncovered": [],
"requirements_partially_covered": [],
"clarification_needed_for": [],
"out_of_scope_requirements": []
},
"routes": [],
"retrieval_status": [],
"retrieval_results": [],
"answer_grounding_check": {
"status": "grounded",
"route_subject_match": true,
"missing_requirements": [],
"reasons": [
"address_entity_detected",
"verify_signal_detected",
"open_items_signal_detected",
"query_limit_auto_expanded_for_anchor_recovery"
],
"why_included_summary": [],
"selection_reason_summary": []
},
"dropped_intent_segments": [],
"detected_mode": "address_query",
"detected_mode_confidence": "medium",
"query_shape": "VERIFY_FACTUAL",
"query_shape_confidence": "medium",
"detected_intent": "open_items_by_counterparty_or_contract",
"detected_intent_confidence": "medium",
"extracted_filters": {
"sort": "period_desc",
"limit": 20,
"contract": "19/15",
"as_of_date": "2020-12-31"
},
"missing_required_filters": [],
"selected_recipe": "address_open_items_by_party_or_contract_v1",
"mcp_call_status_legacy": "matched_non_empty",
"account_scope_mode": "preferred",
"account_scope_fallback_applied": false,
"anchor_type": "contract",
"anchor_value_raw": "19/15",
"anchor_value_resolved": "19/15",
"resolver_confidence": "medium",
"ambiguity_count": 0,
"match_failure_stage": "none",
"match_failure_reason": null,
"mcp_call_status": "matched_non_empty",
"rows_fetched": 1000,
"raw_rows_received": 1000,
"rows_after_account_scope": 285,
"rows_after_recipe_filter": 8,
"rows_materialized": 285,
"rows_matched": 8,
"raw_row_keys_sample": [
"Период",
"Регистратор",
"СчетДт",
"СчетКт",
"Сумма",
"Period",
"Registrator",
"AccountDt",
"AccountKt",
"Amount"
],
"materialization_drop_reason": "none",
"account_token_raw": null,
"account_token_normalized": null,
"account_scope_fields_checked": [
"account_dt",
"account_kt",
"registrator",
"analytics"
],
"account_scope_match_strategy": "account_code_regex_plus_alias_map_v1",
"account_scope_drop_reason": "not_applicable",
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
"limited_reason_category": null,
"response_type": "FACTUAL_LIST",
"execution_lane": "address_query",
"llm_decomposition_applied": true,
"llm_decomposition_attempted": true,
"llm_provider_used": "local",
"llm_decomposition_trace_id": "v905zWHzp1kuHh",
"llm_decomposition_effective_message": "Проверить наличие открытых позиций по договору 19/15 на конец периода 2020-12-31.",
"llm_decomposition_reason": "normalized_fragment_applied",
"llm_canonical_candidate_detected": true,
"llm_predecompose_contract": {
"schema_version": "address_llm_predecompose_contract_v1",
"source_message": "есть ли открытые позиции по договору 19/15 на 2020-12-31",
"canonical_message": "Проверить наличие открытых позиций по договору 19/15 на конец периода 2020-12-31.",
"mode": "address_query",
"mode_confidence": "medium",
"query_shape": "VERIFY_FACTUAL",
"query_shape_confidence": "medium",
"intent": "open_items_by_counterparty_or_contract",
"intent_confidence": "medium",
"entities": {
"account": null,
"counterparty": null,
"contract": "19/15",
"document_type": null,
"document_ref": null,
"organization": null
},
"period": {
"scope": "as_of",
"period_from": null,
"period_to": null,
"as_of_date": "2020-12-31",
"has_explicit_period": true
},
"aggregation_profile": "open_items"
},
"fallback_rule_hit": null,
"sanitized_user_message": "есть ли открытые позиции по договору 19/15 на 2020-12-31",
"tool_gate_decision": "run_address_lane",
"tool_gate_reason": "address_mode_classifier_detected",
"answer_structure_v11": null,
"investigation_state_snapshot": null,
"normalized": null,
"normalizer_output": {
"trace_id": "v905zWHzp1kuHh",
"prompt_version": "normalizer_v2_0_2",
"applied": true,
"effective_message": "Проверить наличие открытых позиций по договору 19/15 на конец периода 2020-12-31."
}
}
```
## 7. user
message_id: msg-VtC7fvCUIt
created_at: 2026-04-08T08:42:06.001Z
reply_type: n/a
покажи документы по этому же договору
## 8. assistant
message_id: msg-VswRgnV4fb
created_at: 2026-04-08T08:42:11.635Z
reply_type: factual
trace_id: address-zoj3HXDSnN
Собран список документов по договору (live address lane).
Строк отобрано: 1.
1. 2021-12-31T12:00:00Z | Поступление товаров и услуг 00000000015 от 31.12.2021 12:00:00 | 19.04 / 76.05 | 233.33
### technical_debug_payload_json
```json
{
"trace_id": "address-zoj3HXDSnN",
"prompt_version": "address_query_runtime_v1",
"schema_version": "address_query_runtime_v1",
"fallback_type": "none",
"route_summary": null,
"fragments": [],
"requirements_extracted": [],
"coverage_report": {
"requirements_total": 0,
"requirements_covered": 0,
"requirements_uncovered": [],
"requirements_partially_covered": [],
"clarification_needed_for": [],
"out_of_scope_requirements": []
},
"routes": [],
"retrieval_status": [],
"retrieval_results": [],
"answer_grounding_check": {
"status": "grounded",
"route_subject_match": true,
"missing_requirements": [],
"reasons": [
"address_action_detected",
"address_entity_detected",
"document_list_signal_detected",
"documents_by_contract_signal_detected",
"contract_from_followup_context",
"address_followup_context_applied"
],
"why_included_summary": [],
"selection_reason_summary": []
},
"dropped_intent_segments": [],
"detected_mode": "address_query",
"detected_mode_confidence": "high",
"query_shape": "DOCUMENT_LIST",
"query_shape_confidence": "medium",
"detected_intent": "list_documents_by_contract",
"detected_intent_confidence": "medium",
"extracted_filters": {
"sort": "period_desc",
"limit": 20,
"contract": "19/15"
},
"missing_required_filters": [],
"selected_recipe": "address_documents_by_contract_v1",
"mcp_call_status_legacy": "matched_non_empty",
"account_scope_mode": "preferred",
"account_scope_fallback_applied": false,
"anchor_type": "contract",
"anchor_value_raw": "19/15",
"anchor_value_resolved": "19/15",
"resolver_confidence": "medium",
"ambiguity_count": 0,
"match_failure_stage": "none",
"match_failure_reason": null,
"mcp_call_status": "matched_non_empty",
"rows_fetched": 1000,
"raw_rows_received": 1000,
"rows_after_account_scope": 416,
"rows_after_recipe_filter": 2,
"rows_materialized": 416,
"rows_matched": 1,
"raw_row_keys_sample": [
"Период",
"Регистратор",
"СчетДт",
"СчетКт",
"Сумма",
"Period",
"Registrator",
"AccountDt",
"AccountKt",
"Amount"
],
"materialization_drop_reason": "none",
"account_token_raw": null,
"account_token_normalized": null,
"account_scope_fields_checked": [
"account_dt",
"account_kt",
"registrator",
"analytics"
],
"account_scope_match_strategy": "account_code_regex_plus_alias_map_v1",
"account_scope_drop_reason": "not_applicable",
"runtime_readiness": "LIVE_QUERYABLE_WITH_LIMITS",
"limited_reason_category": null,
"response_type": "FACTUAL_LIST",
"execution_lane": "address_query",
"llm_decomposition_applied": true,
"llm_decomposition_attempted": true,
"llm_provider_used": "local",
"llm_decomposition_trace_id": "2owWUuckOkI3Tu",
"llm_decomposition_effective_message": "Показать документы по указанному договору.",
"llm_decomposition_reason": "normalized_fragment_applied",
"llm_canonical_candidate_detected": true,
"llm_predecompose_contract": {
"schema_version": "address_llm_predecompose_contract_v1",
"source_message": "покажи документы по этому же договору",
"canonical_message": "Показать документы по указанному договору.",
"mode": "address_query",
"mode_confidence": "high",
"query_shape": "DOCUMENT_LIST",
"query_shape_confidence": "medium",
"intent": "list_documents_by_contract",
"intent_confidence": "medium",
"entities": {
"account": null,
"counterparty": null,
"contract": null,
"document_type": null,
"document_ref": null,
"organization": null
},
"period": {
"scope": "unspecified",
"period_from": null,
"period_to": null,
"as_of_date": null,
"has_explicit_period": false
},
"aggregation_profile": "list_lookup"
},
"fallback_rule_hit": null,
"sanitized_user_message": "покажи документы по этому же договору",
"tool_gate_decision": "run_address_lane",
"tool_gate_reason": "address_mode_classifier_detected",
"answer_structure_v11": null,
"investigation_state_snapshot": null,
"normalized": null,
"normalizer_output": {
"trace_id": "2owWUuckOkI3Tu",
"prompt_version": "normalizer_v2_0_2",
"applied": true,
"effective_message": "Показать документы по указанному договору."
}
}
```

View File

@ -1,6 +1,6 @@
# ADDRESS Query Docs
Дата: 2026-04-03
Дата: 2026-04-08
Статус синхронизации: актуализировано по текущему коду в `llm_normalizer/backend/src/services/*`.
## Актуальный статус (2026-04-08)
@ -23,6 +23,15 @@
- `followup_25`: `25/25`
- comparator: `PASS`
- run: `docs/ADDRESS/runs/2026-04-08_Address_Nightly_Regression_2026-04-08_10-51-20/nightly_summary.json`
- Batch-2 lifecycle официально переведен в `Phase A active`:
- domain card: `domain_general_batch2_lifecycle_card_v1.md`
- acceptance set: `docs/ADDRESS/question_sets/domain_general_batch2_lifecycle_acceptance_2026-04-08_phaseA.json`
- probe report: `wave1_batch2_phaseA_probe_report_2026-04-08.md`
- backlog: `step4_wave1_batch2_phaseA_backlog_v1.md`
- Batch-2 Phase-B progress:
- resolver hardening по `Q12/Q13/Q26/Q27/Q31/Q32` выполнен
- targeted test gate: `addressQueryRuntimeM23.test.ts = 210/210`, `build=PASS`
- live hotpass: `wave1_batch2_phaseB_resolver_hotpass_2026-04-08.md` (`route match 6/6`)
- Финальный stress-pack: `102/102`
`docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_12-57-27/run_summary.json`
- Финальный follow-up pack: `25/25`
@ -37,14 +46,14 @@
Поддерживаемые intents в runtime:
- `period_coverage_profile` (Wave-1 B1, pre-gate)
- `period_coverage_profile` (Wave-1 B1, gate-closed)
- `document_type_and_account_section_profile` (Wave-1 B1, pre-gate)
- `counterparty_population_and_roles` (Wave-1 B1, pre-gate)
- `counterparty_activity_lifecycle` (Wave-1/B2, pre-gate)
- `contract_usage_overview` (Wave-1 B1, pre-gate)
- `customer_revenue_and_payments` (Wave-1 B3 value, pre-gate)
- `supplier_payouts_profile` (Wave-1 B3 value, pre-gate)
- `contract_usage_and_value` (Wave-1 B3 value, pre-gate)
- `counterparty_activity_lifecycle` (Wave-1/B2, Phase-A active)
- `contract_usage_overview` (Wave-1 B1, gate-closed)
- `customer_revenue_and_payments` (Wave-1 B3 value, gate-closed)
- `supplier_payouts_profile` (Wave-1 B3 value, gate-closed)
- `contract_usage_and_value` (Wave-1 B3 value, gate-closed)
- `list_open_contracts`
- `list_payables_counterparties`
- `list_receivables_counterparties`
@ -84,6 +93,11 @@
- `wave1_batch1_readiness_report_2026-04-02.md` - авто-отчет готовности к старту Batch-1.
- `stage3_value_universalization_closeout_2026-04-03.md` - закрытие этапа 3 по value-вопросам общего домена (профит, тестовый gate, остаточные риски).
- `wave1_batch1_batch3_live_gate_report_2026-04-03.md` - фактический отчет по live-gate Batch-1/Batch-3 и статусу глобального regression/comparator (PASS в R6).
- `project_status_rails_graph_2026-04-08.md` - визуальный граф текущего статуса рельсов (закрытые гейты, operational-контур, точка входа в Batch-2).
- `domain_general_batch2_lifecycle_card_v1.md` - domain card для Batch-2 lifecycle (`Q8..Q13 + Q26 + Q27 + Q31 + Q32`).
- `wave1_batch2_phaseA_probe_report_2026-04-08.md` - baseline probe Batch-2 с фактическими gap-ами перед Phase B.
- `step4_wave1_batch2_phaseA_backlog_v1.md` - рабочий backlog Batch-2 (Phase A/B/C).
- `wave1_batch2_phaseB_resolver_hotpass_2026-04-08.md` - фиксация закрытия resolver drift на критичных Batch-2 формулировках (`6/6`).
- `domain_general_batch1_foundation_card_v1.md` - domain card первой волны (Phase A).
- `step4_wave1_batch1_phaseA_backlog_v1.md` - рабочий backlog по подготовке кода и gate-этапам Batch-1.
- `domain_card_template_v1.md` - шаблон описания домена для repeatable delivery.

View File

@ -2,7 +2,7 @@
Дата: `2026-04-02`
Домен: `general_batch1_foundation`
Статус: `active` (Phase A prepared, Phase B runtime intents implemented, Phase C pending)
Статус: `closed` (Phase A/B/C закрыты, domain gate PASS)
Владелец: `Address Runtime Team`
## 1. Scope
@ -152,3 +152,4 @@ Global gate:
- `2026-04-02` — реализованы `counterparty_population_and_roles` и `contract_usage_overview`; targeted live-pack `Q6/Q7/Q28` прошел `strict factual 9/9` (`2026-04-02_Address_Batch1_NextPack_Q6_Q7_Q28`).
- 2026-04-02 - hotfix slang count routing: скока/скок поставщиков|заказчиков стабильно маршрутизируются в counterparty_population_and_roles; targeted live-pack 2026-04-02_Address_SupplierCount_Targeted_AfterFix прошел strict factual 4/4.
- 2026-04-02 - hotfix follow-up slang variant: скок клиентов now maps to counterparty_population_and_roles; targeted live-pack 2026-04-02_Address_SupplierClient_Followup_AfterFix passed strict factual 3/3.
- 2026-04-08 - статус карточки обновлен на `closed` после live-gate (`strict 28/28`) и global comparator PASS; следующий шаг вынесен в Batch-2 lifecycle.

View File

@ -0,0 +1,138 @@
# Domain Card — general_batch2_lifecycle V1
Дата: `2026-04-08`
Домен: `general_batch2_lifecycle`
Статус: `active` (Phase A стартован)
Владелец: `Address Runtime Team`
## 1. Scope
Кратко: домен закрывает lifecycle-вопросы общего домена (`Q8..Q13 + Q26 + Q27 + Q31 + Q32`) и готовит единый рельсовый контракт перед Phase B.
In-scope intents:
1. `counterparty_activity_lifecycle`
2. `contract_usage_overview` (для stale contract слоя)
3. `contract_usage_and_value` (для multi-contract структуры)
Out-of-scope:
1. value-ranking блок (`Q14..Q25 + Q29 + Q30`) — закрыт отдельным Batch-3.
2. debt/aging/risk блок (`Q33+`) — отдельные Batch-4/Batch-5.
3. `COMPOUND_FACTUAL_QUERY` multi-step execution вне явного lifecycle контракта.
## 2. Intent Contract
### 2.1 `counterparty_activity_lifecycle`
- `query_shape`: `FACTUAL_LIST`
- `required_filters`: `[]`
- `optional_filters`: `[period_from, period_to, as_of_date, organization, limit, sort]`
- `resolver_signals`: `активны`, `новые`, `исчезли`, `только один раз`, `дольше всех`, `регулярные`, `эпизодические`, `давно не использовались`
- `ambiguity_rules`: если есть сигналы lifecycle + role split, приоритет у lifecycle (не у `counterparty_population_and_roles`)
- `fallback_policy`: при отсутствии явного периода используется all-time окно; factual без якорей разрешен
### 2.2 `contract_usage_overview`
- `query_shape`: `FACTUAL_SUMMARY`
- `required_filters`: `[]`
- `optional_filters`: `[period_from, period_to, as_of_date, organization, limit]`
- `resolver_signals`: `договоры не использовались`, `stale contracts`, `мертвые договоры`
- `ambiguity_rules`: если вопрос про общий статус использования договоров без топ-ранжирования, приоритет у `contract_usage_overview`
- `fallback_policy`: если stale-метрика не подтверждается, допустим `LIMITED_WITH_REASON` без ложного factual
### 2.3 `contract_usage_and_value`
- `query_shape`: `FACTUAL_LIST`
- `required_filters`: `[]`
- `optional_filters`: `[period_from, period_to, as_of_date, organization, limit, sort]`
- `resolver_signals`: `несколько договоров у контрагента`, `мультидоговорные`, `какие договоры активны`
- `ambiguity_rules`: multi-contract структура имеет приоритет над anchor-required lookup intent-ами
- `fallback_policy`: при невозможности построить структуру выдаем `LIMITED_WITH_REASON`, без перехода в broad document list
## 3. Recipe Mapping
Связка `intent -> recipe_id` должна совпадать с runtime catalog (`addressRecipeCatalog.ts`).
| intent | recipe_id | mcp_method | expected_statuses |
| --- | --- | --- | --- |
| `counterparty_activity_lifecycle` | `address_counterparty_activity_lifecycle_v1` | `POST /api/execute_query` | `matched_non_empty`, `no_raw_rows`, `materialized_but_not_matched` |
| `contract_usage_overview` | `address_contract_usage_overview_v1` | `POST /api/execute_query` | `matched_non_empty`, `no_raw_rows`, `materialized_but_not_matched` |
| `contract_usage_and_value` | `address_contract_usage_and_value_v1` | `POST /api/execute_query` | `matched_non_empty`, `no_raw_rows`, `materialized_but_not_matched` |
## 4. Anchor and Resolver Rules
- `anchor_type`: `period`, `organization`, optional `counterparty` for follow-up context
- `anchor_resolution_order`: explicit period -> follow-up period -> all-time default
- `min_confidence`: `medium`
- `unresolved_behavior`: при unresolved lifecycle сегменте отдавать `LIMITED_WITH_REASON` (`unsupported`/`empty_match`) без псевдо-factual
## 5. Limited Reasons (taxonomy)
Разрешенные категории для домена:
1. `empty_match`
2. `unsupported`
3. `execution_error`
4. `missing_anchor` (только для реального anchor-required маршрута)
Запрещено:
- подменять lifecycle intent на lookup intent с обязательным якорем без явного сигнала пользователя;
- выдавать broad fallback list в вопросах про lifecycle сегментацию.
## 6. Test Coverage
Unit:
1. resolver positives/negatives для `Q8..Q13`, `Q26`, `Q27`, `Q31`, `Q32`
2. приоритет lifecycle над population-role split
3. follow-up period switch (`2020 -> 2021`) для lifecycle цепочек
4. contract lifecycle phrase routing без обязательного якоря
Integration:
1. recipe selection на lifecycle/contract-lifecycle фразах
2. execution status mapping (`matched_non_empty`, `unsupported`, `empty_match`)
3. debug payload completeness
Live acceptance:
1. canonical вопросы
2. noisy/slang вопросы
3. follow-up chains
## 7. Gate Criteria
Domain gate:
- `strict_pass(route)=100%`
- `false_factual_rate=0`
- `execution_error_rate=0`
Global gate:
- baseline stress не ниже эталона `102/102`
- baseline follow-up не ниже эталона `25/25`
## 8. Rollout Plan
1. `phase_a` — contract freeze + acceptance set + probe baseline.
2. `phase_b` — resolver/compose hardening по lifecycle gap-ам.
3. `phase_c` — domain live gate + global comparator PASS.
## 9. Artifacts
Обязательные артефакты:
1. `domain_general_batch2_lifecycle_card_v1.md`
2. `question_sets/domain_general_batch2_lifecycle_acceptance_2026-04-08_phaseA.json`
3. `wave1_batch2_phaseA_probe_report_2026-04-08.md`
4. `step4_wave1_batch2_phaseA_backlog_v1.md`
5. `runs/<run_id>/run_summary.json`
6. `runs/<run_id>/full_live_results.json`
## 10. Change Log
- `2026-04-08` — создана карточка домена Batch-2 lifecycle и зафиксирован contract scope.
- `2026-04-08` — добавлен Phase-A acceptance набор `Q8..Q13 + Q26 + Q27 + Q31 + Q32`.

View File

@ -1,7 +1,7 @@
# Global Execution Checklist V1
Дата старта: 2026-04-02
Последнее обновление: 2026-04-02
Последнее обновление: 2026-04-08
Цель: последовательное закрытие Address Query V1 без архитектурного дрейфа и без company-specific хардкода.
## Правило выполнения
@ -89,12 +89,27 @@
- `domain_general_batch1_foundation_card_v1.md`
- `domain_general_batch1_acceptance_2026-04-02_phaseA.json`
- `step4_wave1_batch1_phaseA_backlog_v1.md`
- [ ] Phase A закрыт (intent naming freeze + negative cases).
- [ ] Phase B закрыт (resolver/types/recipes/compose для Batch-1).
- [ ] Phase C закрыт (domain gate + global non-regression gate).
- [x] Phase A закрыт (intent naming freeze + acceptance set + domain card).
- [x] Phase B закрыт (resolver/types/recipes/compose для Batch-1).
- [x] Phase C закрыт (domain gate + global non-regression gate).
- [x] Phase B.1 начат: реализован первый Batch-1 intent `period_coverage_profile` (resolver + types + recipe + compose), unit/build green.
- [x] Phase B.1 progress: реализован второй Batch-1 intent `document_type_and_account_section_profile` (resolver + classifier + recipe + compose), unit/build green (`107 passed`).
#### Step-4 Wave-1 (general domain, Batch-2 lifecycle: Q8..Q13 + Q26 + Q27 + Q31 + Q32)
- [x] Phase A стартован:
- `domain_general_batch2_lifecycle_card_v1.md`
- `domain_general_batch2_lifecycle_acceptance_2026-04-08_phaseA.json`
- `wave1_batch2_phaseA_probe_report_2026-04-08.md`
- `step4_wave1_batch2_phaseA_backlog_v1.md`
- [ ] Phase A закрыт (negative-пары + final contract freeze).
- [ ] Phase B закрыт (resolver/decompose/compose hardening для lifecycle gap-ов).
- [x] Phase B progress:
- resolver hardening по `Q12/Q13/Q26/Q27/Q31/Q32`
- test gate: `addressQueryRuntimeM23.test.ts = 210/210`, `build=PASS`
- live hotpass: `wave1_batch2_phaseB_resolver_hotpass_2026-04-08.md` (`route match 6/6`)
- [ ] Phase C закрыт (Batch-2 domain gate + global comparator PASS).
## Документация (code-sync)
- [x] Базовые docs синхронизированы с текущим runtime-кодом (`README`, `address_scenario_matrix`, `query_recipes`, `runtime_readiness_matrix`, `address_runtime_contracts`, `runtime_integration_plan`).

View File

@ -0,0 +1,51 @@
# ADDRESS Project Status Rails Graph (2026-04-08)
Дата среза: 2026-04-08
Контур: `question_mode=address_query`
## Граф статуса
```mermaid
flowchart LR
A["Step-0 Pre-Prod Rails<br/>CLOSED"]
B["Wave-1 Runtime Prep (B1+B3)<br/>intents + tests"]
C["Domain Live Gates<br/>B1: 28/28 PASS<br/>B3: 33/33 PASS"]
D["Global Nightly Comparator (R6)<br/>102/102 + 25/25 PASS"]
E["Auto-nightly Incident<br/>2026-04-08 09:52<br/>MCP fetch failed"]
F["Targeted Fix<br/>S019 predecompose anchor drift"]
G["Manual Control Nightly Recheck<br/>2026-04-08 10:51<br/>102/102 + 25/25 PASS"]
H["CURRENT STATE<br/>BATCH2_PHASE_B_ACTIVE"]
I["NEXT STEP<br/>Batch-2 Phase B closeout<br/>Follow-up carryover + compose hardening"]
K["Batch-2 Gate<br/>Phase C -> Global Regression"]
J["Operational Rail<br/>Scheduler: Disabled<br/>Manual nightly only"]
A --> B --> C --> D --> H --> I --> K
D --> E --> F --> G --> H
E --> J
H --> J
```
## Контрольные артефакты
- Global comparator PASS (reference):
`docs/ADDRESS/runs/2026-04-03_Address_Nightly_Regression_Post_AnchorHardening_R6/nightly_summary.json`
- Incident run (auto-nightly):
`docs/ADDRESS/runs/2026-04-08_Address_Nightly_Regression_2026-04-08_09-52-39/nightly_summary.json`
- Control recheck PASS (manual):
`docs/ADDRESS/runs/2026-04-08_Address_Nightly_Regression_2026-04-08_10-51-20/nightly_summary.json`
- Master checker (entry to Batch-2):
`docs/ADDRESS/address_query/step4_wave1_batch1_master_checker_v1.md`
- Batch-2 Phase-A artifacts:
- `docs/ADDRESS/address_query/domain_general_batch2_lifecycle_card_v1.md`
- `docs/ADDRESS/question_sets/domain_general_batch2_lifecycle_acceptance_2026-04-08_phaseA.json`
- `docs/ADDRESS/address_query/wave1_batch2_phaseA_probe_report_2026-04-08.md`
- `docs/ADDRESS/address_query/step4_wave1_batch2_phaseA_backlog_v1.md`
- Batch-2 Phase-B hotpass (resolver drift fix):
- `docs/ADDRESS/address_query/wave1_batch2_phaseB_resolver_hotpass_2026-04-08.md`
## Коротко по статусу
- Функциональные гейты закрыты.
- Точечный flake `S019` закрыт.
- Batch-2 переведен в `Phase B active`; resolver drift по `Q12/Q13/Q26/Q27/Q31/Q32` закрыт (`hotpass 6/6`).
- Следующий практический шаг: Phase-B closeout (follow-up carryover + compose), затем Batch-2 domain gate.

View File

@ -1,6 +1,6 @@
# Runtime Readiness Matrix V1 (Code Sync)
Дата: 2026-04-03
Дата: 2026-04-08
Формат: `scenario -> structural_readiness -> runtime_readiness -> blocker`
@ -30,7 +30,7 @@
| AQ-B1-10 | period_coverage_profile | STRUCTURALLY_VISIBLE | LIVE_QUERYABLE_WITH_LIMITS | functional gates закрыты; остаточный риск в operational стабильности auto-nightly/MCP канала | держать comparator PASS (R6) и переходить к Batch-2 lifecycle |
| AQ-B1-11 | document_type_and_account_section_profile | STRUCTURALLY_VISIBLE | LIVE_QUERYABLE_WITH_LIMITS | functional gates закрыты; остаточный риск в operational стабильности auto-nightly/MCP канала | держать comparator PASS (R6) и переходить к Batch-2 lifecycle |
| AQ-B1-12 | counterparty_population_and_roles | STRUCTURALLY_VISIBLE | LIVE_QUERYABLE_WITH_LIMITS | functional gates закрыты; остаточный риск в operational стабильности auto-nightly/MCP канала | держать comparator PASS (R6) и переходить к Batch-2 lifecycle |
| AQ-B2-13 | counterparty_activity_lifecycle | STRUCTURALLY_VISIBLE | LIVE_QUERYABLE_WITH_LIMITS | lifecycle-сценарии требуют отдельного доменного live-pack (Batch-2) | собрать Batch-2 acceptance и закрыть route gate |
| AQ-B2-13 | counterparty_activity_lifecycle | STRUCTURALLY_VISIBLE | LIVE_QUERYABLE_WITH_LIMITS | resolver drift hotfix закрыт, но Batch-2 domain gate еще не прогнан | закрыть Phase-B (follow-up/compose) и выполнить Batch-2 domain gate |
| AQ-B1-14 | contract_usage_overview | STRUCTURALLY_VISIBLE | LIVE_QUERYABLE_WITH_LIMITS | functional gates закрыты; остаточный риск в operational стабильности auto-nightly/MCP канала | держать comparator PASS (R6) и переходить к Batch-2 lifecycle |
| AQ-B3-15 | customer_revenue_and_payments | STRUCTURALLY_VISIBLE | LIVE_QUERYABLE_WITH_LIMITS | functional gates закрыты; остаточный риск в operational стабильности auto-nightly/MCP канала | держать comparator PASS (R6) и переходить к Batch-2 lifecycle |
| AQ-B3-16 | supplier_payouts_profile | STRUCTURALLY_VISIBLE | LIVE_QUERYABLE_WITH_LIMITS | functional gates закрыты; остаточный риск в operational стабильности auto-nightly/MCP канала | держать comparator PASS (R6) и переходить к Batch-2 lifecycle |
@ -67,6 +67,14 @@
- авто-запуск nightly из Task Scheduler дал `overall_ok=false` из-за инфраструктурного `MCP fetch failed: fetch failed` в части вызовов, не как функциональный откат доменных intent-ов.
- ручной контрольный nightly (`2026-04-08 10:29`) показал точечный flake в `AQ-P0-07` семействе (`S019`: `materialized_but_not_anchor_matched`), при этом `followup_25` остался `25/25`.
- повторный контрольный nightly (`2026-04-08 10:51`) закрыт в `PASS`: `stress_102=102/102`, `followup_25=25/25`, comparator `PASS` (`docs/ADDRESS/runs/2026-04-08_Address_Nightly_Regression_2026-04-08_10-51-20/nightly_summary.json`).
- Batch-2 lifecycle Phase-A probe (2026-04-08):
- report: `docs/ADDRESS/address_query/wave1_batch2_phaseA_probe_report_2026-04-08.md`
- baseline coverage: `semantic_match 4/10`
- основные gap-и: `Q12/Q13/Q27` -> `unsupported`, `Q26` -> drift в `counterparty_population_and_roles`, `Q31/Q32` -> drift в anchor-required lookup ветки.
- Batch-2 lifecycle Phase-B resolver hotpass (2026-04-08):
- report: `docs/ADDRESS/address_query/wave1_batch2_phaseB_resolver_hotpass_2026-04-08.md`
- фактический результат: `route match 6/6` на `Q12/Q13/Q26/Q27/Q31/Q32`
- кодовый gate: `addressQueryRuntimeM23.test.ts = 210/210`, `build=PASS`.
- `COMPOUND_FACTUAL_QUERY` остается detection-only (без multi-intent execution).
- Финальные gate-артефакты стабильности:
- stress `102/102`: `docs/ADDRESS/runs/2026-04-02_Address_Slang_Live_Stress_2026-04-02_12-57-27/run_summary.json`

View File

@ -62,6 +62,15 @@ Batch-1 можно переводить в runtime только после за
- [x] Контрольный nightly recheck (`2026-04-08 10:51`) закрыт в `PASS`
run: `docs/ADDRESS/runs/2026-04-08_Address_Nightly_Regression_2026-04-08_10-51-20/nightly_summary.json`
детали: `stress_102=102/102`, `followup_25=25/25`, comparator PASS.
- [x] Batch-2 lifecycle Phase A стартован:
- `domain_general_batch2_lifecycle_card_v1.md`
- `docs/ADDRESS/question_sets/domain_general_batch2_lifecycle_acceptance_2026-04-08_phaseA.json`
- `wave1_batch2_phaseA_probe_report_2026-04-08.md`
- `step4_wave1_batch2_phaseA_backlog_v1.md`
- [x] Batch-2 Phase-B progress:
- resolver drift по `Q12/Q13/Q26/Q27/Q31/Q32` закрыт
- `addressQueryRuntimeM23.test.ts = 210/210`, `build=PASS`
- live hotpass `wave1_batch2_phaseB_resolver_hotpass_2026-04-08.md` (`route match 6/6`)
- [x] Phase B закрыт.
- [x] Phase C закрыт.
@ -69,4 +78,4 @@ Batch-1 можно переводить в runtime только после за
1. Начинать можно, но строго по фазам выше.
2. Прямое включение Batch-1 intents в production-path без Phase B/C — запрещено.
3. Точка входа в работу: контрольный nightly уже зафиксирован в `PASS`, следующий шаг — старт Batch-2 lifecycle (`Q8..Q13 + Q26 + Q27 + Q31 + Q32`) по тем же gate-правилам.
3. Точка входа в работу: Batch-2 уже в `Phase A active`; следующий шаг — выполнить Batch-2 Phase B и закрыть domain gate/глобальный comparator.

View File

@ -1,9 +1,12 @@
# Step-4 Wave-1 Batch-1 — Phase A/B Backlog V1
Дата: 2026-04-02
Статус: `active`
Статус: `closed` (historical backlog, фактическое закрытие Batch-1/Batch-3 зафиксировано в gate reports)
Scope: `Q1..Q7 + Q28`
> Примечание на 2026-04-08: файл сохранен как исторический backlog старта Wave-1.
> Текущий рабочий backlog для новой волны: `step4_wave1_batch2_phaseA_backlog_v1.md`.
## 1. Phase A (Design/Contract) — старт
- [x] Зафиксирован master checker: `step4_wave1_batch1_master_checker_v1.md`

View File

@ -0,0 +1,83 @@
# Step-4 Wave-1 Batch-2 — Phase A/B Backlog V1
Дата: 2026-04-08
Статус: `active`
Scope: `Q8..Q13 + Q26 + Q27 + Q31 + Q32`
## 1. Phase A (Design/Contract) — статус
- [x] Зафиксирован вход в Batch-2 из master checker: `step4_wave1_batch1_master_checker_v1.md`
- [x] Зафиксирована domain card: `domain_general_batch2_lifecycle_card_v1.md`
- [x] Сформирован acceptance набор: `domain_general_batch2_lifecycle_acceptance_2026-04-08_phaseA.json`
- [x] Выполнен Phase-A probe и зафиксированы gap-ы: `wave1_batch2_phaseA_probe_report_2026-04-08.md`
- [ ] Добавить explicit negative-пары (близкие формулировки для anti-drift на lookup intent-ы)
## 2. Phase B (Runtime Prep) — задачи по коду
### 2.1 Resolver / Decompose
- [x] `llm_normalizer/backend/src/services/addressIntentResolver.ts`
Добавить сигналы lifecycle для `Q12/Q13/Q27`:
- `только один раз`, `single activity`, `one-time`
- `дольше всех`, `longest-running`
- `давно не использовались` (supplier lifecycle)
- [x] `llm_normalizer/backend/src/services/addressIntentResolver.ts`
Зафиксировать priority rule:
`counterparty_activity_lifecycle` > `counterparty_population_and_roles`
для сегментационных формулировок (`регулярные/эпизодические`, `исчезли`, `новые`, `long-term`).
- [x] `llm_normalizer/backend/src/services/addressIntentResolver.ts`
Добавить contract lifecycle routing:
- stale contract phrasing -> `contract_usage_overview`
- multi-contract/active contract phrasing -> `contract_usage_and_value`
- [ ] `llm_normalizer/backend/src/services/address_runtime/decomposeStage.ts`
Усилить follow-up carryover для Batch-2 chain:
`а кто новые`, `а кто потом исчез`, `а теперь за 2021`, `и какие договоры активны`.
### 2.2 Filter / Recipe / Compose
- [ ] `llm_normalizer/backend/src/services/addressFilterExtractor.ts`
Нормализовать lifecycle period cues (`после <год>`, `за <год>`, `впервые в <год>`).
- [ ] `llm_normalizer/backend/src/services/address_runtime/composeStage.ts`
Добавить стабильные summary-профили для:
- new counterparties
- lost counterparties
- one-time counterparties
- long-running counterparties
- regular vs episodic suppliers
- stale suppliers/contracts
- [ ] `llm_normalizer/backend/src/services/address_runtime/composeStage.ts`
Для `contract_usage_and_value` добавить блок multi-contract структуры:
`контрагент -> количество договоров -> активные/неактивные`.
### 2.3 Tests / Gates
- [x] `llm_normalizer/backend/tests/addressQueryRuntimeM23.test.ts`
Добавить regression-кейсы под `Q12/Q13/Q26/Q27/Q31/Q32`.
- [ ] `llm_normalizer/backend/tests/assistantAddressFollowupContext.test.ts`
Добавить lifecycle follow-up chains для переключения года и сегментов.
- [x] Кодовый gate:
- `npm --prefix llm_normalizer/backend run test -- addressQueryRuntimeM23.test.ts --testTimeout 20000`
- `npm --prefix llm_normalizer/backend run build`
- [x] Быстрый live hotpass по проблемным маршрутам:
- report: `wave1_batch2_phaseB_resolver_hotpass_2026-04-08.md`
- result: `route match 6/6`
## 3. Phase C (Gate) — критерии закрытия
- [ ] Domain run-pack по
`docs/ADDRESS/question_sets/domain_general_batch2_lifecycle_acceptance_2026-04-08_phaseA.json`:
- `strict_pass(route)=100%`
- `false_factual_rate=0`
- `execution_error_rate=0`
- [ ] Global non-regression:
- `address_slang_stress_full_2026-04-02.json`
- `address_followup_context_chains_2026-04-02.json`
обе метрики не ниже baseline (`102/102`, `25/25`).
- [ ] Comparator PASS against latest green nightly:
`docs/ADDRESS/runs/2026-04-08_Address_Nightly_Regression_2026-04-08_10-51-20/nightly_summary.json`
## 4. Текущий приоритет (следующий кодовый шаг)
1. Закрыть follow-up carryover для lifecycle/contract lifecycle chain.
2. Дожать compose-представления для `stale`/`one-time`/`longest-running`/`regular-vs-episodic`.
3. Прогнать полный Batch-2 domain gate pack + global comparator.

View File

@ -0,0 +1,62 @@
# Wave-1 Batch-2 Phase-A Probe Report
Дата: 2026-04-08
Контур: `question_mode=address_query`
Цель: зафиксировать текущий фактический coverage Batch-2 перед Phase-B implementation.
## 1. Probe set
Проверены 10 representative-вопросов:
- `Q8..Q13` (customer lifecycle)
- `Q26..Q27` (supplier lifecycle)
- `Q31..Q32` (contract lifecycle/structure)
## 2. Контур прогона
Probe выполнен через временный `tsx` harness к `AddressQueryService` с 10 representative-кейсами.
Временный script удален после фиксации результата в отчете.
## 3. Результат
- `semantic_match = 4/10`
- `route coverage = partial`
- `execution_error = 0` (в probe-срезе)
## 4. Детализация по кейсам
| Q | expected_intent | detected_intent | status | note |
| --- | --- | --- | --- | --- |
| Q8 | `counterparty_activity_lifecycle` | `counterparty_activity_lifecycle` | PASS | factual list |
| Q9 | `counterparty_activity_lifecycle` | `counterparty_activity_lifecycle` | PASS | factual list |
| Q10 | `counterparty_activity_lifecycle` | `counterparty_activity_lifecycle` | PASS | factual list |
| Q11 | `counterparty_activity_lifecycle` | `counterparty_activity_lifecycle` | PASS | factual list |
| Q12 | `counterparty_activity_lifecycle` | `unknown` | FAIL | `unsupported` |
| Q13 | `counterparty_activity_lifecycle` | `unknown` | FAIL | `unsupported` |
| Q26 | `counterparty_activity_lifecycle` | `counterparty_population_and_roles` | FAIL | role-split intent hijack |
| Q27 | `counterparty_activity_lifecycle` | `unknown` | FAIL | `unsupported` |
| Q31 | `contract_usage_overview` | `list_documents_by_contract` | FAIL | `missing_anchor` drift to lookup |
| Q32 | `contract_usage_and_value` | `list_contracts_by_counterparty` | FAIL | `missing_anchor` drift to lookup |
## 5. Выводы для Phase B
1. Lifecycle клиентского слоя (`Q8..Q11`) базово работает.
2. Не закрыты важные lifecycle сегменты:
`one-time`, `longest-running`, `stale suppliers`.
3. Есть конфликт маршрутов между lifecycle intent-ом и population/lookup intent-ами:
- `Q26` уходит в `counterparty_population_and_roles`;
- `Q31/Q32` уходит в anchor-required lookup ветки.
## 6. Приоритеты исправлений
1. Resolver-сигналы для `Q12/Q13/Q27` -> `counterparty_activity_lifecycle`.
2. Resolver-priority rule: lifecycle segmentation > role population split.
3. Contract lifecycle routing:
- `stale contracts` -> `contract_usage_overview`;
- `multi-contract active map` -> `contract_usage_and_value`.
4. Follow-up carryover для lifecycle chain без обязательных anchor-фильтров.
## 7. Решение
`READY_FOR_PHASE_B_IMPLEMENTATION`
Фаза A закрыта как design/probe baseline; следующий шаг — runtime hardening и domain live-gate Batch-2.

View File

@ -0,0 +1,38 @@
# Wave-1 Batch-2 Phase-B Resolver Hotpass
Дата: 2026-04-08
Контур: `question_mode=address_query`
Цель: подтвердить устранение resolver drift по критичным Batch-2 lifecycle формулировкам.
## 1. Проверочный набор
Проверены бывшие проблемные формулировки:
1. `Q12` one-time counterparties
2. `Q13` longest-running counterparties
3. `Q26` regular vs episodic suppliers
4. `Q27` stale suppliers
5. `Q31` stale contracts
6. `Q32` multi-contract counterparties + active contracts
## 2. Факт результата
- `route match = 6/6`
- `reply_type = factual` по всем 6 кейсам
- `mcp_call_status = matched_non_empty` по всем 6 кейсам
## 3. Детализация
| case | detected_intent | selected_recipe | status |
| --- | --- | --- | --- |
| Q12 one-time | `counterparty_activity_lifecycle` | `address_counterparty_activity_lifecycle_v1` | PASS |
| Q13 longest-running | `counterparty_activity_lifecycle` | `address_counterparty_activity_lifecycle_v1` | PASS |
| Q26 regular/episodic | `counterparty_activity_lifecycle` | `address_counterparty_activity_lifecycle_v1` | PASS |
| Q27 stale suppliers | `counterparty_activity_lifecycle` | `address_counterparty_activity_lifecycle_v1` | PASS |
| Q31 stale contracts | `contract_usage_overview` | `address_contract_usage_overview_v1` | PASS |
| Q32 multi-contract active map | `contract_usage_and_value` | `address_contract_usage_and_value_v1` | PASS |
## 4. Примечание
Это не финальный domain gate Batch-2.
Следующий шаг: закрыть оставшиеся Phase-B задачи (follow-up carryover + compose нюансы), затем прогнать полный Batch-2 acceptance pack и global comparator.

View File

@ -0,0 +1,398 @@
# ADDRESS Test Questions (обновлено 2026-04-02)
Важно:
1. Цепочки `FOLLOW-UP` прогоняй в одной сессии подряд.
2. Блоки `SINGLE` можно гонять по отдельности.
3. Для проверок смотри не только ответ, но и `technical_debug_payload_json`.
## SINGLE: Документы по контрагенту
1. Покажи документы по СВК за 2020 год
2. свк доки за 20й
3. что по свк за 2020 выведи все доки плиз
4. Какие документы по контрагенту СВК за весь период
5. какие есть доки по свк с 2000 по 2025 год
6. Покажи документы по контрагенту СВК
## SINGLE: Банковские операции
7. Покажи банковские операции по СВК за 2020
8. Какие платежи по СВК в 2020
9. Покажи банковские операции по контрагенту СВК за весь период
## SINGLE: Договор
10. Покажи документы по договору 19/15 за 2020
11. Покажи документы по договору 19/15
12. Покажи банковские операции по договору 19/15
13. Есть ли долг по договору 19/15 на 2020-07-31
14. Покажи операции по договору 19/15 за весь период
## SINGLE: Сальдо / остаток
15. Покажи сальдо по счету 60.01 на дату 2020-07-31
16. Какой остаток по счету 60 на 2020.05
17. Какой остаток по счету 60 на 2020 май
18. Какие документы формируют остаток по счету 60.01 на 2020-07-31
19. Какой остаток по счету 62 на сегодня
## FOLLOW-UP CHAIN A (одна сессия)
20. Покажи документы по СВК за 2020
21. а теперь только за май 2020
22. а за все время?
23. а по счету 60.01 на ту же дату
## FOLLOW-UP CHAIN B (одна сессия)
24. Покажи банковские операции по договору 19/15
25. а за 2020 год
26. а теперь только за май 2020
27. а по счету 51 на ту же дату
## FOLLOW-UP CHAIN C (одна сессия)
28. Какой остаток по счету 60 на 2020.05
29. а теперь по 60.01
30. а на ту же дату по 62
## NOISY / TYPO
31. бля епт покажи доки по свк за 20й
32. свк 20 год - покажи доки плс
33. покажи банк опер по дог 19/15 пж
34. остаток 60 на 2020 май покеж
35. по свк за весь период че есть
## NEGATIVE / LIMIT CASE
36. Покажи технические константы конфигурации
37. кто президент США
## SINGLE: Дебиторка / Кредиторка
38. Кто нам должен на 2020-12-31
39. Кому мы должны на 2020-12-31
40. Покажи дебиторскую задолженность за 2020
41. Покажи кредиторскую задолженность за 2020
## SINGLE: Незакрытые договоры / открытые позиции
42. Покажи незакрытые договоры на 2020-12-31
43. Какие открытые договоры есть на 2020-12-31
44. Покажи хвосты по контрагенту СВК на 2020-12-31
45. Есть ли незакрытые позиции по договору 19/15 на 2020-12-31
46. Покажи открытые позиции по договору 19/15
## FOLLOW-UP CHAIN D (одна сессия)
47. Покажи документы по контрагенту СВК за 2020
48. а только за июль 2020
49. а теперь банковские операции
50. а за весь период
51. а сальдо по счету 51 на ту же дату
## FOLLOW-UP CHAIN E (одна сессия)
52. Покажи незакрытые позиции по контрагенту СВК
53. а на 2020-12-31
54. а теперь по договору 19/15
55. а только банковские операции
56. а за май 2020
## EN / Transliteration Stress
57. show documents by counterparty svk for 2020
58. show bank operations by contract 19/15
59. kakoi ostatok po schetu 60.01 na 2020-07-31
60. kto nam dolzhen na 2020-12-31
## SUPPORTED FULL POOL (AUTO, 2026-04-08)
Срез: 2026-04-08
Принцип: включены только вопросы из покрытых доменов/наборов, которые прогонялись в рамках текущего runtime-контура.
Источники:
- docs/ADDRESS/question_sets/address_slang_stress_full_2026-04-02.json
- docs/ADDRESS/question_sets/address_followup_context_chains_2026-04-02.json
- docs/ADDRESS/question_sets/domain_contracts_open_items_acceptance_2026-04-02.json
- docs/ADDRESS/question_sets/domain_general_batch1_acceptance_2026-04-02_phaseA.json
- docs/ADDRESS/question_sets/temp_batch3_value_top20_2026-04-02.json
- docs/ADDRESS/question_sets/temp_batch2_q8_q9_2026-04-02.json
- docs/ADDRESS/question_sets/temp_contracts_zhukovka_targeted_2026-04-02.json
- docs/ADDRESS/question_sets/temp_supplier_count_targeted_2026-04-02.json
- docs/ADDRESS/question_sets/temp_supplier_client_followup_targeted_2026-04-02.json
- docs/ADDRESS/question_sets/address_contract_19_15_postfix_2026-04-02.json
- Всего уникальных поддерживаемых формулировок в этом пуле: 212
### Документы по контрагенту (`list_documents_by_counterparty`) — 22
1. свк доки за 2020 покаж пж
2. покеж все доки по свк за 20 год
3. дай список документов по свк за 2020
4. какие у свк есть доки за 2020?
5. че по свк по докам за 2020 год
6. ёпт покажи доки по свк за 2020
7. бля, доки свк 2020 вывали
8. svk docs for 2020 please
9. docs by counterparty svk year 2020
10. по контрагенту свк покаж документы за 2020
11. плс доки по свк 2020
12. свк док за 2020 гони
13. свк доки кинь за 2020
14. по свк все документы за 20й год
15. пж вывали доки контрагент свк 2020
16. svk poka docy za 2020
17. какие доки по компании свк за 2020
18. бля покаж свк документы 2020
19. Покажи документы по СВК за 2020
20. а теперь только за май 2020
21. а теперь документы по нему
22. покажи документы все по жуковке 51
### Банковские операции по контрагенту (`bank_operations_by_counterparty`) — 18
1. по свк банковские операции за 2020 покаж
2. свк выписка банка 2020
3. платежи по свк за 2020 выведи
4. оплаты по свк за 2020
5. свк банк опер за 2020
6. че у свк по банку за 2020
7. свк списания/поступления за 2020
8. bank ops by svk for 2020
9. transactions by counterparty svk 2020
10. бля, по свк банковские платежки 2020
11. свк банк операции за 2020
12. по свк выписку банка за 2020 выведи
13. свк платежки за 2020
14. bank ops svk 2020
15. транзакции по свк за 2020
16. ёпт по свк че по банку 2020
17. Покажи банковские операции по СВК за 2020
18. а теперь только ноябрь 2020
### Договоры по контрагенту (`list_contracts_by_counterparty`) — 2
1. покажи договора все по жуковке 51
2. покажи договора по свк
### Документы по договору (`list_documents_by_contract`) — 16
1. покажи документы по договору 19/15 за 2020
2. доки по дог. 19/15 за 2020
3. какие документы по контракту 19/15
4. docs by contract 19/15 year 2020
5. бля, покажи по договору 19/15 доки
6. договор 19/15 документы
7. плс документы договор №19/15
8. покеж документы по 19/15
9. доки по договору 19/15 за 2020
10. покажи документы контракт 19/15
11. docs by contract 19/15 2020
12. по дог 19/15 какие документы
13. договор 19/15 выведи доки
14. нах покажи документы по 19/15
15. покажи документы по этому же договору
16. а документы по этому же договору за тот же период
### Банковские операции по договору (`bank_operations_by_contract`) — 14
1. банковские операции по договору 19/15
2. платежи по договору 19/15
3. выписка по договору 19/15
4. bank ops by contract 19/15
5. ёпт, покажи банк операции по дог 19/15
6. по контракту 19/15 платежки
7. банк операции по договору 19/15
8. платежки по дог 19/15
9. выписка по контракту 19/15
10. покажи банк опер по дог 19/15 пж
11. епт транзакции договор 19/15
12. а теперь банковские операции
13. а теперь только за 2020
14. Покажи банковские операции по договору 19/15
### Остаток / сальдо по счету (`account_balance_snapshot`) — 31
1. сальдо по счету 60.01 на дату 2020-07-31
2. остаток по счету 60.01 на 2020.05
3. покажи остаток по 60.01 за май 2020
4. счет 60 остаток на май 2020
5. баланс account 60.01 as of 2020-07-31
6. 60.01 saldo на 31.07.2020
7. сколько на счете 60 на конец 2020-05
8. дай остаток 60.02 на 2020-12-31
9. епт, сальдо по счету 62.01 на 2020-12-31
10. плс остаток по счету 51 на 2020-06-30
11. как там по счету 60.01 на 2020.07?
12. сальдо 60 на 2020
13. покажи сальдо по счету 60 за 2020
14. а теперь только май 2020
15. ок, теперь 2020.05
16. теперь на 2020-07-31
17. остаток по 60.01 на 2020-07-31
18. сальдо 60.01 на 31.07.2020
19. что на счете 60 на 2020.05
20. дай остаток 62.01 на 2020 май
21. saldo account 60 as of 2020-12-31
22. плиз остаток по счету 51 на 2020-06-30
23. скока по 60.02 на конец 2020-12
24. бля сальдо 60.01 на 2020-05-31
25. а по счету 60.01 на ту же дату
26. а на ту же дату по 62.01
27. и остаток по 51 на ту же дату
28. Покажи сальдо по счету 60 на 2020-12-31
29. а на ту же дату по 62
30. вернись на 2020-12-31 по 60
31. а по счету 51 на ту же дату
### Документы, формирующие остаток (`documents_forming_balance`) — 16
1. какие документы формируют остаток по счету 60.01 на 2020-07-31
2. из чего состоит остаток по счету 60 на 2020.05
3. раскрой остаток 60.01 по документам на конец июля 2020
4. доки под остатком 60.01 на 2020-07-31
5. documents forming balance for account 60.01 as of 2020-07-31
6. бля, какие доки формируют остаток 60.01 на 2020-05-31
7. по счету 62.01 раскрой остаток документами на 2020-12-31
8. из чего остаток по счету 51 на 2020-06-30
9. из чего остаток 60.01 на 2020-07-31
10. какие документы формируют остаток по 60.01 на 2020-07-31
11. docs forming balance 60.01 as of 2020-07-31
12. раскидай остаток 62.01 по докам на 2020-12-31
13. по счету 51 раскрой остаток документами на 2020-06-30
14. бля из чего состоит остаток по 60.01 на 2020-05-31
15. раскрой остаток по 62.01 документами на ту же дату
16. раскрой 62 документами на ту же дату
### Открытые позиции / долг (`open_items_by_counterparty_or_contract`) — 15
1. есть ли открытые позиции по договору 19/15 на 2020-12-31
2. а теперь открытые позиции по нему
3. а теперь на 2020-12-31
4. Есть ли долг по договору 19/15 на 2020-07-31
5. Есть ли незакрытые позиции по договору 19/15 на 2020-12-31
6. Покажи открытые позиции по договору 19/15
7. по дог 19/15 есть хвосты на 2020-12-31
8. долг по дог 19/15 на 2020-12-31 есть?
9. open items by contract 19/15 as of 2020-12-31
10. есть ли долг по договору 19/15 на 31.12.2020
11. покажи открытые позиции по дог 19/15 на 2020-12-31 пж
12. а теперь есть ли долг по договору 19/15 на 2020-12-31
13. Есть ли незакрытые позиции по договору 19/15 на 2020-11-30
14. Есть ли незакрытые позиции по договору 19/15 на 2022-12-31
15. Есть ли незакрытые позиции по договору 19/15 на 2015-12-31
### Незакрытые договоры (`list_open_contracts`) — 9
1. Покажи незакрытые договоры на 2020-12-31
2. Show open contracts as of 2020-12-31
3. Покажи незакрытые договоры на конец 2020-12-31
4. list open contracts for 2020-12-31
5. покажи незакрытые договоры на 2020-12-31 пж
6. незакрытые договоры на 2020-12-31 гони
7. open contracts 2020-12-31 pls
8. show open contracts for 2020.12.31
9. незакрытые договоры 2020-12-31
### Покрытие периода и активность (`period_coverage_profile`) — 10
1. За какие годы в базе есть данные?
2. Какой год самый активный по количеству документов?
3. Какой месяц самый активный по количеству операций?
4. Покажи профиль данных по базе: диапазон лет, топ год и топ месяц активности.
5. за какие года база ваще живая?
6. какой год тут самый движовый по докам
7. какой месяц прям пик по операциям?
8. покажи по базе years/top year/top month, только без воды
9. а какой год самый активный по документам?
10. а теперь месяц-пик по операциям
### Типы документов и разделы учета (`document_type_and_account_section_profile`) — 5
1. Какие типы документов используются чаще всего в базе?
2. Какие разделы учета наиболее заполнены и какие почти не используются?
3. Сформируй сводку по типам документов и их доле в общем объеме.
4. каких доков у нас больше всего крутится?
5. какие разделы учета жирные, а какие мертвые?
### Контрагенты и роли (`counterparty_population_and_roles`) — 13
1. Сколько всего уникальных контрагентов в базе?
2. Сколько у нас заказчиков, поставщиков и смешанных контрагентов?
3. Сколько контрагентов активны как клиенты и как поставщики?
4. скок уник контрагентов вообще есть
5. разбей контров на клиентов/поставщиков/смешанных
6. а теперь разбей их на заказчиков и поставщиков
7. скока всего контрагентов в базе
8. скока поставщиков
9. скока поставщиков в базе
10. скока заказчиков в базе
11. скок поставщиков в базе
12. скок клиентов
13. скок клиентов в базе
### Активность заказчиков по периодам (`counterparty_activity_lifecycle`) — 5
1. Какие заказчики работали с нами в 2020 году?
2. Какие заказчики работали с нами за все время?
3. какие клиенты у нас были активны в 2020?
4. выведи активных клиентов за все время
5. а за все время?
### Использование договорной базы (`contract_usage_overview`) — 5
1. Сколько всего договоров заведено и сколько из них реально использовались?
2. Дай обзор договорной базы: total договоров и used договоров.
3. договоров сколько всего и сколько реально юзались?
4. чо по договорной базе: total vs used
5. и сразу сколько всего договоров и сколько used
### Заказчики и поступления (value, top-20) (`customer_revenue_and_payments`) — 17
1. Покажи топ-20 заказчиков по сумме поступлений за все время.
2. Покажи топ-20 заказчиков по сумме поступлений за 2020 год.
3. Покажи топ-20 заказчиков по количеству входящих платежных операций за все время.
4. Покажи топ-20 заказчиков по максимальной сумме одной входящей операции за все время.
5. Покажи топ-20 заказчиков по среднему чеку среди активных клиентов (минимум 3 входящие операции).
6. Покажи топ-20 самых крупных разовых сделок по поступлениям (дата, контрагент, документ, сумма).
7. Покажи топ-20 самых маленьких разовых сделок по поступлениям среди активных заказчиков.
8. какие клиенты самые доходные, выдай топ-20
9. топ-20 заказчиков по деньгам за все время
10. за 20й год кто нам больше всего занес, топ-20
11. кто платит чаще всего, дай топ-20
12. покажи топ-20 самых жирных сделок по поступлениям
13. покажи топ-20 самых маленьких сделок по бюджету
14. Теперь только за 2020 год, тоже топ-20.
15. И отдельно покажи топ-20 по частоте входящих платежей.
16. Покажи топ-20 самых крупных разовых сделок по поступлениям.
17. А теперь топ-20 самых маленьких сделок по бюджету среди активных заказчиков.
### Поставщики и выплаты (value, top-20) (`supplier_payouts_profile`) — 10
1. Покажи топ-20 поставщиков по сумме выплат за все время.
2. Покажи топ-20 поставщиков по сумме выплат за 2020 год.
3. Покажи топ-20 поставщиков по количеству исходящих платежных операций за все время.
4. Покажи топ-20 самых крупных разовых выплат поставщикам (дата, контрагент, документ, сумма).
5. кому мы больше всего сгрузили денег, топ-20 поставщиков
6. топ-20 поставщиков по выплатам за все время
7. за 2020 год кому ушло больше всего денег, топ-20
8. поставщики с максимальным числом выплат, топ-20
9. Теперь за 2020 год, тоже топ-20.
10. И дай топ-20 поставщиков по количеству выплат.
### Договоры и суммы (value, top-20) (`contract_usage_and_value`) — 4
1. Покажи топ-20 договоров по сумме оборота за все время.
2. Покажи топ-20 договоров с минимальным бюджетом среди активных договоров.
3. договоры по обороту ранкни и дай топ-20
4. покажи топ-20 договоров с самым мелким бюджетом, но только активные
## FOLLOW-UP CHAINS (из `address_followup_context_chains_2026-04-02.json`)
Важно: вопросы внутри каждой цепочки гонять в одной сессии подряд.
### session: `chain_docs_svk_a`
1. Покажи документы по СВК за 2020
2. а теперь только за май 2020
3. а по счету 60.01 на ту же дату
4. а на ту же дату по 62.01
5. раскрой остаток по 62.01 документами на ту же дату
### session: `chain_bank_svk_b`
1. Покажи банковские операции по СВК за 2020
2. а теперь только ноябрь 2020
3. а теперь документы по нему
4. и остаток по 51 на ту же дату
5. а теперь только май 2020
### session: `chain_balance_c`
1. Покажи сальдо по счету 60 на 2020-12-31
2. а теперь только май 2020
3. а на ту же дату по 62
4. раскрой 62 документами на ту же дату
5. вернись на 2020-12-31 по 60
### session: `chain_contract_d`
1. Покажи документы по договору 19/15 за 2020
2. а теперь банковские операции
3. а теперь только за 2020
4. есть ли открытые позиции по договору 19/15 на 2020-12-31
5. покажи документы по этому же договору
### session: `chain_contract_e`
1. Покажи банковские операции по договору 19/15
2. а теперь открытые позиции по нему
3. а теперь на 2020-12-31
4. а по счету 51 на ту же дату
5. а документы по этому же договору за тот же период

View File

@ -0,0 +1,266 @@
[
{
"id": "B2_C001",
"group": "canonical",
"text": "Какие заказчики работали с нами в 2020 году?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_C002",
"group": "canonical",
"text": "Какие заказчики работали с нами за все время?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_C003",
"group": "canonical",
"text": "Какие заказчики появились у нас впервые в 2020 году?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_C004",
"group": "canonical",
"text": "Какие заказчики были активны в 2020 году, но дальше перестали встречаться?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_C005",
"group": "canonical",
"text": "Какие контрагенты работали с нами только один раз за все время?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_C006",
"group": "canonical",
"text": "Какие контрагенты работают с нами дольше всего по периоду активности?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_C007",
"group": "canonical",
"text": "Раздели поставщиков на регулярных и эпизодических по частоте операций.",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_C008",
"group": "canonical",
"text": "Какие поставщики давно не использовались в операционной активности?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_C009",
"group": "canonical",
"text": "Какие договоры давно не использовались (stale contracts)?",
"expected_intent": "contract_usage_overview",
"expected_mode": "address_query"
},
{
"id": "B2_C010",
"group": "canonical",
"text": "Покажи контрагентов с несколькими договорами и укажи, какие договоры у них активны.",
"expected_intent": "contract_usage_and_value",
"expected_mode": "address_query"
},
{
"id": "B2_C011",
"group": "canonical",
"text": "Какие заказчики работали с нами в 2021 году?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_C012",
"group": "canonical",
"text": "Какие заказчики впервые появились у нас в 2021 году?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_N001",
"group": "noisy_slang",
"text": "какие клиенты были активны в 20м",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_N002",
"group": "noisy_slang",
"text": "выведи всех заков за все время",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_N003",
"group": "noisy_slang",
"text": "кто к нам пришел впервые в 2020",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_N004",
"group": "noisy_slang",
"text": "кто был активен в 2020 и потом отвалился",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_N005",
"group": "noisy_slang",
"text": "кто с нами был ровно один раз и пропал",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_N006",
"group": "noisy_slang",
"text": "самые старые по сотрудничеству кто",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_N007",
"group": "noisy_slang",
"text": "разбей поставщиков на регуляр и разовые",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_N008",
"group": "noisy_slang",
"text": "какие поставщики давно мертвые",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_N009",
"group": "noisy_slang",
"text": "какие договоры уже протухли и не двигаются",
"expected_intent": "contract_usage_overview",
"expected_mode": "address_query"
},
{
"id": "B2_N010",
"group": "noisy_slang",
"text": "у кого несколько договоров и какие из них рабочие",
"expected_intent": "contract_usage_and_value",
"expected_mode": "address_query"
},
{
"id": "B2_N011",
"group": "noisy_slang",
"text": "новые клиенты за 21й год",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_N012",
"group": "noisy_slang",
"text": "клиенты которые исчезли после 21 года",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_F001",
"group": "followup_chain",
"session": "b2_customer_lifecycle_chain",
"text": "Какие заказчики работали с нами в 2020 году?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_F002",
"group": "followup_chain",
"session": "b2_customer_lifecycle_chain",
"text": "А кто из них новые?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_F003",
"group": "followup_chain",
"session": "b2_customer_lifecycle_chain",
"text": "А кто потом исчез?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_F004",
"group": "followup_chain",
"session": "b2_customer_lifecycle_chain",
"text": "И кто был только один раз?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_F005",
"group": "followup_chain",
"session": "b2_supplier_lifecycle_chain",
"text": "Раздели поставщиков на регулярных и эпизодических.",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_F006",
"group": "followup_chain",
"session": "b2_supplier_lifecycle_chain",
"text": "Теперь покажи тех, кто давно не использовался.",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_F007",
"group": "followup_chain",
"session": "b2_contract_lifecycle_chain",
"text": "Какие договоры давно не использовались?",
"expected_intent": "contract_usage_overview",
"expected_mode": "address_query"
},
{
"id": "B2_F008",
"group": "followup_chain",
"session": "b2_contract_lifecycle_chain",
"text": "Теперь покажи контрагентов с несколькими договорами.",
"expected_intent": "contract_usage_and_value",
"expected_mode": "address_query"
},
{
"id": "B2_F009",
"group": "followup_chain",
"session": "b2_contract_lifecycle_chain",
"text": у них какие договоры сейчас активны?",
"expected_intent": "contract_usage_and_value",
"expected_mode": "address_query"
},
{
"id": "B2_F010",
"group": "followup_chain",
"session": "b2_year_switch_chain",
"text": "Какие заказчики работали с нами в 2020 году?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_F011",
"group": "followup_chain",
"session": "b2_year_switch_chain",
"text": "Теперь за 2021.",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
},
{
"id": "B2_F012",
"group": "followup_chain",
"session": "b2_year_switch_chain",
"text": "А кто новые в этом году?",
"expected_intent": "counterparty_activity_lifecycle",
"expected_mode": "address_query"
}
]

View File

@ -0,0 +1,51 @@
# Graphify for Codex in `NDC_1C`
This repository now has project-level setup for `graphify` (knowledge graph workflow for Codex).
## What was added
- `AGENTS.md` with graph-first Codex rules.
- `.graphifyignore` tuned for this repository.
- `.gitignore` rules for graphify temporary/build outputs.
- `scripts/setup_graphify_codex.ps1` bootstrap script.
## One-command setup (Windows)
```powershell
powershell -ExecutionPolicy Bypass -File .\scripts\setup_graphify_codex.ps1
```
Options:
```powershell
# do not reinstall pip package
powershell -ExecutionPolicy Bypass -File .\scripts\setup_graphify_codex.ps1 -SkipPipInstall
# also install graphify git hooks (post-commit/post-checkout)
powershell -ExecutionPolicy Bypass -File .\scripts\setup_graphify_codex.ps1 -InstallGitHooks
```
## Codex usage
In Codex chat (skill trigger uses `$`):
```text
$graphify .
$graphify . --update
$graphify query "what connects routing to runtime?"
```
Main outputs are created in `graphify-out/`:
- `graphify-out/graph.json`
- `graphify-out/GRAPH_REPORT.md`
- `graphify-out/graph.html`
## Important Codex config
For parallel semantic extraction, make sure `~/.codex/config.toml` contains:
```toml
[features]
multi_agent = true
```

View File

@ -236,12 +236,27 @@ const COUNTERPARTY_ACTIVITY_LIFECYCLE_HINTS = [
"какие заказчики активны",
"какие клиенты работали",
"какие клиенты активны",
"какие контрагенты работали",
"какие поставщики работали",
"список заказчиков",
"список клиентов",
"список заказчиков за все время",
"список клиентов за все время",
"список активных заказчиков",
"список активных клиентов",
"новые заказчики",
"новые клиенты",
"новые контрагенты",
"впервые в",
"кто исчез",
"кто ушел",
"кто ушёл",
"только один раз",
"дольше всех",
"долгоживущие контрагенты",
"регулярные поставщики",
"эпизодические поставщики",
"давно не использовались поставщики",
"active customers",
"customer activity list",
"counterparty lifecycle"
@ -255,6 +270,12 @@ const CONTRACT_USAGE_OVERVIEW_HINTS = [
"договоры total vs used",
"обзор договорной базы",
"договорная база total used",
"неиспользуемые договоры",
"давно не использовались договоры",
"мертвые договоры",
"мёртвые договоры",
"stale contracts",
"unused contracts",
"contracts total used",
"contract usage overview"
];
@ -296,6 +317,11 @@ const CONTRACT_USAGE_AND_VALUE_HINTS = [
"договоры с минимальным бюджетом",
"договоры с самым маленьким бюджетом",
"активные договоры по бюджету",
"контрагенты с несколькими договорами",
"несколько договоров у контрагента",
"мультидоговорные контрагенты",
"какие договоры активны",
"рабочие договоры",
"contracts by turnover",
"contracts by budget"
];
@ -506,6 +532,9 @@ function hasDocumentTypeAndAccountSectionProfileSignal(text) {
return false;
}
function hasCounterpartyPopulationAndRolesSignal(text) {
if (hasLifecycleSegmentationSignal(text)) {
return false;
}
if (hasAny(text, COUNTERPARTY_POPULATION_AND_ROLES_HINTS)) {
return true;
}
@ -520,6 +549,9 @@ function hasCounterpartyPopulationAndRolesSignal(text) {
}
return false;
}
function hasLifecycleSegmentationSignal(text) {
return /(?:вперв|нов(?:ые|ых|ые\s+контрагент|ые\s+клиент|ые\s+заказчик)|исчез|ушед|ушл|только\s+один\s+раз|однораз|дольше\s+всех|долгожив|регулярн|эпизодич|давно\s+не\s+использ|неиспольз|потом\s+перестал)/iu.test(text);
}
function hasCounterpartyActivityLifecycleSignal(text) {
if (hasDocumentSignal(text) || hasBankOperationSignal(text)) {
return false;
@ -530,19 +562,22 @@ function hasCounterpartyActivityLifecycleSignal(text) {
if (/(?:сколько|скока|скок)\s+/iu.test(text)) {
return false;
}
const hasCustomerLexeme = /(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?|customer(?:s)?|client(?:s)?)/iu.test(text);
const hasActivityLexeme = /(?:работал(?:и)?|активн(?:ые|ых|а|о)?|сотрудничал(?:и)?|были\s+в\s+работе|active)/iu.test(text);
const hasCounterpartyLexeme = /(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?|контрагент(?:ов|а|ы)?|поставщик(?:ов|а|и)?|customer(?:s)?|client(?:s)?|counterpart(?:y|ies)|supplier(?:s)?|vendor(?:s)?)/iu.test(text);
const hasActivityLexeme = /(?:работал(?:и)?|активн(?:ые|ых|а|о)?|сотрудничал(?:и)?|были\s+в\s+работе|active|использ(?:овал(?:и|ось)?|уются|ован(?:ы|о)?))/iu.test(text);
const hasTimeWindowLexeme = /(?:за\s+вс[её]\s+время|all\s+time|\b(?:19|20)\d{2}\b|(?:^|[^\d])\d{2}\s*(?:г(?:од|ода)?|г)(?:[^\p{L}\p{N}]|$)|в\s+конкретн(?:ом|ый)\s+год|за\s+год|в\s+году)/iu.test(text);
const hasListVerb = /(?:какие|кто|покажи|выведи|список|list|show)/iu.test(text);
const hasRosterQualifier = /(?:у\s+нас|вообще|в\s+баз[еы]|какие\s+есть|кто\s+есть|who\s+are)/iu.test(text);
const hasListWithWindow = hasCustomerLexeme && hasListVerb && hasTimeWindowLexeme;
const hasListWithWindow = hasCounterpartyLexeme && hasListVerb && hasTimeWindowLexeme;
if (hasListWithWindow) {
return true;
}
if (hasCustomerLexeme && hasListVerb && hasRosterQualifier) {
if (hasCounterpartyLexeme && hasListVerb && hasRosterQualifier) {
return true;
}
return hasCustomerLexeme && hasActivityLexeme && (hasTimeWindowLexeme || hasListVerb);
if (hasCounterpartyLexeme && hasLifecycleSegmentationSignal(text)) {
return true;
}
return hasCounterpartyLexeme && hasActivityLexeme && (hasTimeWindowLexeme || hasListVerb);
}
function hasContractUsageOverviewSignal(text) {
if (hasAny(text, CONTRACT_USAGE_OVERVIEW_HINTS)) {
@ -557,6 +592,9 @@ function hasContractUsageOverviewSignal(text) {
if (/(?:total\s+vs\s+used|used\s+vs\s+total).*(?:договор|contract)?/iu.test(text)) {
return true;
}
if (/(?:какие\s+договор(?:ы|а)?).*(?:давно\s+не\s+использ|неиспольз|протух|мертв|мёртв|stale|unused)/iu.test(text)) {
return true;
}
return false;
}
function hasCustomerRevenueAndPaymentsSignal(text) {
@ -636,9 +674,10 @@ function hasContractUsageAndValueSignal(text) {
if (hasContractUsageOverviewSignal(text)) {
return false;
}
const asksStructure = /(?:нескольк(?:ими|их|ие|о)?\s+договор|мультидоговор|контрагент(?:ов|ы)?.*нескольк(?:ими|их|ие|о)\s+договор|какие\s+договор(?:ы|а)?\s+активн|рабоч(?:ие|их)\s+договор)/iu.test(text);
const asksValue = /(?:оборот|бюджет|сумм|стоим|value|turnover|amount|revenue|крупн|мелк|миним|максим)/iu.test(text);
const asksRank = /(?:топ|top|ранк|rank|сам(?:ый|ая|ое|ые))/iu.test(text);
return asksValue || asksRank;
return asksStructure || asksValue || asksRank;
}
function hasContractListByCounterpartySignal(text) {
const hasContractLexeme = /(?:договор(?:а|у|ом|е|ы)?|contracts?|contract)/iu.test(text);

View File

@ -22,7 +22,8 @@ const BANK_DOCS_QUERY_TEMPLATE = `
"" КАК СчетДт,
"" КАК СчетКт,
БанкСписание.СуммаДокумента КАК Сумма,
ПРЕДСТАВЛЕНИЕ(БанкСписание.Контрагент) КАК Контрагент
ПРЕДСТАВЛЕНИЕ(БанкСписание.Контрагент) КАК Контрагент,
ПРЕДСТАВЛЕНИЕ(БанкСписание.ДоговорКонтрагента) КАК Договор
ИЗ
Документ.СписаниеСРасчетногоСчета КАК БанкСписание
__WHERE_OUT__
@ -33,7 +34,8 @@ __WHERE_OUT__
"" КАК СчетДт,
"" КАК СчетКт,
БанкПоступление.СуммаДокумента КАК Сумма,
ПРЕДСТАВЛЕНИЕ(БанкПоступление.Контрагент) КАК Контрагент
ПРЕДСТАВЛЕНИЕ(БанкПоступление.Контрагент) КАК Контрагент,
ПРЕДСТАВЛЕНИЕ(БанкПоступление.ДоговорКонтрагента) КАК Договор
ИЗ
Документ.ПоступлениеНаРасчетныйСчет КАК БанкПоступление
__WHERE_IN__
@ -460,7 +462,8 @@ const BASE_RECIPES = [
optional_filters: ["as_of_date", "counterparty", "contract", "limit"],
default_limit: 96,
account_scope: ["60", "62", "76"],
account_scope_mode: "preferred"
account_scope_mode: "preferred",
query_template: "bank_docs"
},
{
recipe_id: "address_documents_by_counterparty_v1",

View File

@ -2771,6 +2771,41 @@ function isLowQualityPredecomposeCounterpartyAnchor(value) {
});
return meaningful.length === 0;
}
function normalizePredecomposeCounterpartyAnchorTokensForMatch(value) {
return normalizePredecomposeAnchorTokens(value).filter((token) => {
if (token.length < 2) {
return false;
}
if (/^(?:19|20)\d{2}$/.test(token)) {
return false;
}
return !ADDRESS_PREDECOMPOSE_LOW_QUALITY_COUNTERPARTY_TOKENS.has(token);
});
}
function hasCounterpartyAnchorSubstitution(sourceValue, candidateValue) {
const sourceNormalized = String(sourceValue ?? "").trim().toLowerCase().replace(/ё/g, "е");
const candidateNormalized = String(candidateValue ?? "").trim().toLowerCase().replace(/ё/g, "е");
if (!sourceNormalized || !candidateNormalized) {
return false;
}
if (sourceNormalized === candidateNormalized) {
return false;
}
if (sourceNormalized.includes(candidateNormalized) || candidateNormalized.includes(sourceNormalized)) {
return false;
}
const sourceTokens = new Set(normalizePredecomposeCounterpartyAnchorTokensForMatch(sourceNormalized));
const candidateTokens = normalizePredecomposeCounterpartyAnchorTokensForMatch(candidateNormalized);
if (sourceTokens.size === 0 || candidateTokens.length === 0) {
return false;
}
for (const token of candidateTokens) {
if (sourceTokens.has(token)) {
return false;
}
}
return true;
}
function isLowQualityPredecomposeContractAnchor(value) {
const normalized = String(value ?? "").trim().toLowerCase().replace(/ё/g, "е");
if (!normalized) {
@ -3003,6 +3038,27 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
sanitizedUserMessage
}, userMessage);
}
const counterpartyAnchorSubstitutedByCandidate = sameIntentForAnchorSafety &&
sourceAnchorQuality.anchorType === "counterparty" &&
candidateAnchorQuality.anchorType === "counterparty" &&
sourceAnchorQuality.quality >= 2 &&
candidateAnchorQuality.quality >= 2 &&
Boolean(sourceAnchorQuality.anchorValue) &&
Boolean(candidateAnchorQuality.anchorValue) &&
hasCounterpartyAnchorSubstitution(sourceAnchorQuality.anchorValue ?? "", candidateAnchorQuality.anchorValue ?? "");
if (counterpartyAnchorSubstitutedByCandidate) {
return attachAddressPredecomposeContract({
...baseMeta,
attempted: true,
applied: false,
traceId: normalized?.trace_id ?? null,
llmCanonicalCandidateDetected: true,
effectiveMessage: userMessage,
reason: "normalized_fragment_rejected_anchor_substitution",
fallbackRuleHit: null,
sanitizedUserMessage
}, userMessage);
}
if (fallbackCandidate) {
const fallbackAnchorQuality = evaluateAddressAnchorQuality(String(fallbackCandidate.candidate ?? ""));
const fallbackPreferredForAnchorSafety = sameIntentForAnchorSafety &&

View File

@ -247,12 +247,27 @@ const COUNTERPARTY_ACTIVITY_LIFECYCLE_HINTS = [
"какие заказчики активны",
"какие клиенты работали",
"какие клиенты активны",
"какие контрагенты работали",
"какие поставщики работали",
"список заказчиков",
"список клиентов",
"список заказчиков за все время",
"список клиентов за все время",
"список активных заказчиков",
"список активных клиентов",
"новые заказчики",
"новые клиенты",
"новые контрагенты",
"впервые в",
"кто исчез",
"кто ушел",
"кто ушёл",
"только один раз",
"дольше всех",
"долгоживущие контрагенты",
"регулярные поставщики",
"эпизодические поставщики",
"давно не использовались поставщики",
"active customers",
"customer activity list",
"counterparty lifecycle"
@ -267,6 +282,12 @@ const CONTRACT_USAGE_OVERVIEW_HINTS = [
"договоры total vs used",
"обзор договорной базы",
"договорная база total used",
"неиспользуемые договоры",
"давно не использовались договоры",
"мертвые договоры",
"мёртвые договоры",
"stale contracts",
"unused contracts",
"contracts total used",
"contract usage overview"
];
@ -311,6 +332,11 @@ const CONTRACT_USAGE_AND_VALUE_HINTS = [
"договоры с минимальным бюджетом",
"договоры с самым маленьким бюджетом",
"активные договоры по бюджету",
"контрагенты с несколькими договорами",
"несколько договоров у контрагента",
"мультидоговорные контрагенты",
"какие договоры активны",
"рабочие договоры",
"contracts by turnover",
"contracts by budget"
];
@ -552,6 +578,9 @@ function hasDocumentTypeAndAccountSectionProfileSignal(text: string): boolean {
}
function hasCounterpartyPopulationAndRolesSignal(text: string): boolean {
if (hasLifecycleSegmentationSignal(text)) {
return false;
}
if (hasAny(text, COUNTERPARTY_POPULATION_AND_ROLES_HINTS)) {
return true;
}
@ -575,6 +604,12 @@ function hasCounterpartyPopulationAndRolesSignal(text: string): boolean {
return false;
}
function hasLifecycleSegmentationSignal(text: string): boolean {
return /(?:вперв|нов(?:ые|ых|ые\s+контрагент|ые\s+клиент|ые\s+заказчик)|исчез|ушед|ушл|только\s+один\s+раз|однораз|дольше\s+всех|долгожив|регулярн|эпизодич|давно\s+не\s+использ|неиспольз|потом\s+перестал)/iu.test(
text
);
}
function hasCounterpartyActivityLifecycleSignal(text: string): boolean {
if (hasDocumentSignal(text) || hasBankOperationSignal(text)) {
return false;
@ -585,25 +620,31 @@ function hasCounterpartyActivityLifecycleSignal(text: string): boolean {
if (/(?:сколько|скока|скок)\s+/iu.test(text)) {
return false;
}
const hasCustomerLexeme = /(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?|customer(?:s)?|client(?:s)?)/iu.test(
const hasCounterpartyLexeme = /(?:заказчик(?:ов|а|и)?|клиент(?:ов|а|ы)?|покупател(?:ей|я|и)?|контрагент(?:ов|а|ы)?|поставщик(?:ов|а|и)?|customer(?:s)?|client(?:s)?|counterpart(?:y|ies)|supplier(?:s)?|vendor(?:s)?)/iu.test(
text
);
const hasActivityLexeme =
/(?:работал(?:и)?|активн(?:ые|ых|а|о)?|сотрудничал(?:и)?|были\s+в\s+работе|active|использ(?:овал(?:и|ось)?|уются|ован(?:ы|о)?))/iu.test(
text
);
const hasActivityLexeme = /(?:работал(?:и)?|активн(?:ые|ых|а|о)?|сотрудничал(?:и)?|были\s+в\s+работе|active)/iu.test(text);
const hasTimeWindowLexeme =
/(?:за\s+вс[её]\s+время|all\s+time|\b(?:19|20)\d{2}\b|(?:^|[^\d])\d{2}\s*(?:г(?:од|ода)?|г)(?:[^\p{L}\p{N}]|$)|в\s+конкретн(?:ом|ый)\s+год|за\s+год|в\s+году)/iu.test(
text
);
const hasListVerb = /(?:какие|кто|покажи|выведи|список|list|show)/iu.test(text);
const hasRosterQualifier = /(?:у\s+нас|вообще|в\s+баз[еы]|какие\s+есть|кто\s+есть|who\s+are)/iu.test(text);
const hasListWithWindow = hasCustomerLexeme && hasListVerb && hasTimeWindowLexeme;
const hasListWithWindow = hasCounterpartyLexeme && hasListVerb && hasTimeWindowLexeme;
if (hasListWithWindow) {
return true;
}
if (hasCustomerLexeme && hasListVerb && hasRosterQualifier) {
if (hasCounterpartyLexeme && hasListVerb && hasRosterQualifier) {
return true;
}
if (hasCounterpartyLexeme && hasLifecycleSegmentationSignal(text)) {
return true;
}
return hasCustomerLexeme && hasActivityLexeme && (hasTimeWindowLexeme || hasListVerb);
return hasCounterpartyLexeme && hasActivityLexeme && (hasTimeWindowLexeme || hasListVerb);
}
function hasContractUsageOverviewSignal(text: string): boolean {
@ -625,6 +666,9 @@ function hasContractUsageOverviewSignal(text: string): boolean {
if (/(?:total\s+vs\s+used|used\s+vs\s+total).*(?:договор|contract)?/iu.test(text)) {
return true;
}
if (/(?:какие\s+договор(?:ы|а)?).*(?:давно\s+не\s+использ|неиспольз|протух|мертв|мёртв|stale|unused)/iu.test(text)) {
return true;
}
return false;
}
@ -724,10 +768,13 @@ function hasContractUsageAndValueSignal(text: string): boolean {
if (hasContractUsageOverviewSignal(text)) {
return false;
}
const asksStructure = /(?:нескольк(?:ими|их|ие|о)?\s+договор|мультидоговор|контрагент(?:ов|ы)?.*нескольк(?:ими|их|ие|о)\s+договор|какие\s+договор(?:ы|а)?\s+активн|рабоч(?:ие|их)\s+договор)/iu.test(
text
);
const asksValue =
/(?:оборот|бюджет|сумм|стоим|value|turnover|amount|revenue|крупн|мелк|миним|максим)/iu.test(text);
const asksRank = /(?:топ|top|ранк|rank|сам(?:ый|ая|ое|ые))/iu.test(text);
return asksValue || asksRank;
return asksStructure || asksValue || asksRank;
}
function hasContractListByCounterpartySignal(text: string): boolean {

View File

@ -26,7 +26,8 @@ const BANK_DOCS_QUERY_TEMPLATE = `
"" КАК СчетДт,
"" КАК СчетКт,
БанкСписание.СуммаДокумента КАК Сумма,
ПРЕДСТАВЛЕНИЕ(БанкСписание.Контрагент) КАК Контрагент
ПРЕДСТАВЛЕНИЕ(БанкСписание.Контрагент) КАК Контрагент,
ПРЕДСТАВЛЕНИЕ(БанкСписание.ДоговорКонтрагента) КАК Договор
ИЗ
Документ.СписаниеСРасчетногоСчета КАК БанкСписание
__WHERE_OUT__
@ -37,7 +38,8 @@ __WHERE_OUT__
"" КАК СчетДт,
"" КАК СчетКт,
БанкПоступление.СуммаДокумента КАК Сумма,
ПРЕДСТАВЛЕНИЕ(БанкПоступление.Контрагент) КАК Контрагент
ПРЕДСТАВЛЕНИЕ(БанкПоступление.Контрагент) КАК Контрагент,
ПРЕДСТАВЛЕНИЕ(БанкПоступление.ДоговорКонтрагента) КАК Договор
ИЗ
Документ.ПоступлениеНаРасчетныйСчет КАК БанкПоступление
__WHERE_IN__
@ -474,7 +476,8 @@ const BASE_RECIPES: AddressRecipeDefinition[] = [
optional_filters: ["as_of_date", "counterparty", "contract", "limit"],
default_limit: 96,
account_scope: ["60", "62", "76"],
account_scope_mode: "preferred"
account_scope_mode: "preferred",
query_template: "bank_docs"
},
{
recipe_id: "address_documents_by_counterparty_v1",

View File

@ -2732,6 +2732,41 @@ function isLowQualityPredecomposeCounterpartyAnchor(value) {
});
return meaningful.length === 0;
}
function normalizePredecomposeCounterpartyAnchorTokensForMatch(value) {
return normalizePredecomposeAnchorTokens(value).filter((token) => {
if (token.length < 2) {
return false;
}
if (/^(?:19|20)\d{2}$/.test(token)) {
return false;
}
return !ADDRESS_PREDECOMPOSE_LOW_QUALITY_COUNTERPARTY_TOKENS.has(token);
});
}
function hasCounterpartyAnchorSubstitution(sourceValue, candidateValue) {
const sourceNormalized = String(sourceValue ?? "").trim().toLowerCase().replace(/ё/g, "е");
const candidateNormalized = String(candidateValue ?? "").trim().toLowerCase().replace(/ё/g, "е");
if (!sourceNormalized || !candidateNormalized) {
return false;
}
if (sourceNormalized === candidateNormalized) {
return false;
}
if (sourceNormalized.includes(candidateNormalized) || candidateNormalized.includes(sourceNormalized)) {
return false;
}
const sourceTokens = new Set(normalizePredecomposeCounterpartyAnchorTokensForMatch(sourceNormalized));
const candidateTokens = normalizePredecomposeCounterpartyAnchorTokensForMatch(candidateNormalized);
if (sourceTokens.size === 0 || candidateTokens.length === 0) {
return false;
}
for (const token of candidateTokens) {
if (sourceTokens.has(token)) {
return false;
}
}
return true;
}
function isLowQualityPredecomposeContractAnchor(value) {
const normalized = String(value ?? "").trim().toLowerCase().replace(/ё/g, "е");
if (!normalized) {
@ -2964,6 +2999,27 @@ async function runAddressLlmPreDecompose(normalizerService, payload, userMessage
sanitizedUserMessage
}, userMessage);
}
const counterpartyAnchorSubstitutedByCandidate = sameIntentForAnchorSafety &&
sourceAnchorQuality.anchorType === "counterparty" &&
candidateAnchorQuality.anchorType === "counterparty" &&
sourceAnchorQuality.quality >= 2 &&
candidateAnchorQuality.quality >= 2 &&
Boolean(sourceAnchorQuality.anchorValue) &&
Boolean(candidateAnchorQuality.anchorValue) &&
hasCounterpartyAnchorSubstitution(sourceAnchorQuality.anchorValue ?? "", candidateAnchorQuality.anchorValue ?? "");
if (counterpartyAnchorSubstitutedByCandidate) {
return attachAddressPredecomposeContract({
...baseMeta,
attempted: true,
applied: false,
traceId: normalized?.trace_id ?? null,
llmCanonicalCandidateDetected: true,
effectiveMessage: userMessage,
reason: "normalized_fragment_rejected_anchor_substitution",
fallbackRuleHit: null,
sanitizedUserMessage
}, userMessage);
}
if (fallbackCandidate) {
const fallbackAnchorQuality = evaluateAddressAnchorQuality(String(fallbackCandidate.candidate ?? ""));
const fallbackPreferredForAnchorSafety = sameIntentForAnchorSafety &&

View File

@ -1383,11 +1383,36 @@ describe("address intent resolver expansion (M2.3a)", () => {
expect(result.intent).toBe("counterparty_activity_lifecycle");
});
it("resolves counterparty lifecycle intent for one-time counterparties wording", () => {
const result = resolveAddressIntent("Какие контрагенты работали с нами только один раз за все время?");
expect(result.intent).toBe("counterparty_activity_lifecycle");
});
it("resolves counterparty lifecycle intent for longest-running counterparties wording", () => {
const result = resolveAddressIntent("Какие контрагенты работают с нами дольше всех?");
expect(result.intent).toBe("counterparty_activity_lifecycle");
});
it("resolves supplier lifecycle segmentation wording into lifecycle intent", () => {
const result = resolveAddressIntent("Раздели поставщиков на регулярных и эпизодических по активности.");
expect(result.intent).toBe("counterparty_activity_lifecycle");
});
it("resolves stale suppliers wording into lifecycle intent", () => {
const result = resolveAddressIntent("Какие поставщики давно не использовались?");
expect(result.intent).toBe("counterparty_activity_lifecycle");
});
it("resolves contract usage overview intent", () => {
const result = resolveAddressIntent("Сколько всего договоров заведено и сколько из них реально использовались?");
expect(result.intent).toBe("contract_usage_overview");
});
it("resolves stale contracts wording into contract usage overview intent", () => {
const result = resolveAddressIntent("Какие договоры давно не использовались?");
expect(result.intent).toBe("contract_usage_overview");
});
it("resolves customer revenue/payout ranking intent", () => {
const result = resolveAddressIntent("какие клиенты самые доходные, выдай топ-20");
expect(result.intent).toBe("customer_revenue_and_payments");
@ -1418,6 +1443,11 @@ describe("address intent resolver expansion (M2.3a)", () => {
expect(result.intent).toBe("contract_usage_and_value");
});
it("resolves multi-contract counterparties wording into contract usage and value intent", () => {
const result = resolveAddressIntent("Покажи контрагентов с несколькими договорами и какие из договоров активны.");
expect(result.intent).toBe("contract_usage_and_value");
});
it("resolves contracts-by-counterparty intent from list wording", () => {
const result = resolveAddressIntent("покажи договора все по жуковке 51");
expect(result.intent).toBe("list_contracts_by_counterparty");
@ -2017,6 +2047,36 @@ describe("address query limited taxonomy and stage diagnostics", () => {
expect(["FACTUAL_LIST", "LIMITED_WITH_REASON", "FACTUAL_SUMMARY"]).toContain(result?.response_type);
});
it("routes one-time counterparties wording into lifecycle aggregate recipe", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle("Какие контрагенты работали с нами только один раз за все время?");
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("counterparty_activity_lifecycle");
expect(result?.debug.selected_recipe).toBe("address_counterparty_activity_lifecycle_v1");
expect(result?.debug.mcp_call_status).not.toBe("skipped");
expect(["FACTUAL_LIST", "LIMITED_WITH_REASON", "FACTUAL_SUMMARY"]).toContain(result?.response_type);
});
it("routes stale contracts wording into contract usage overview recipe", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle("Какие договоры давно не использовались?");
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("contract_usage_overview");
expect(result?.debug.selected_recipe).toBe("address_contract_usage_overview_v1");
expect(result?.debug.mcp_call_status).not.toBe("skipped");
expect(["FACTUAL_LIST", "LIMITED_WITH_REASON", "FACTUAL_SUMMARY"]).toContain(result?.response_type);
});
it("routes multi-contract counterparties wording into contract usage and value recipe", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle("Покажи контрагентов с несколькими договорами и какие из договоров активны.");
expect(result?.handled).toBe(true);
expect(result?.debug.detected_intent).toBe("contract_usage_and_value");
expect(result?.debug.selected_recipe).toBe("address_contract_usage_and_value_v1");
expect(result?.debug.mcp_call_status).not.toBe("skipped");
expect(["FACTUAL_LIST", "LIMITED_WITH_REASON", "FACTUAL_SUMMARY"]).toContain(result?.response_type);
});
it("returns missing_anchor for open items without concrete counterparty/contract anchor", async () => {
const service = new AddressQueryService();
const result = await service.tryHandle("show open items by contract");
@ -2510,6 +2570,22 @@ describe("address recipe catalog counterparty filtering", () => {
expect(plan.limit).toBe(1000);
});
it("uses bank-doc profile with contract projection for open-items anchor matching", () => {
const selected = selectAddressRecipe("open_items_by_counterparty_or_contract", {
counterparty: "СВК",
as_of_date: "2020-12-31"
});
expect(selected.selected_recipe).toBeTruthy();
const plan = buildAddressRecipePlan(selected.selected_recipe!, {
counterparty: "СВК",
as_of_date: "2020-12-31"
});
expect(plan.query).toContain("Документ.СписаниеСРасчетногоСчета");
expect(plan.query).toContain("Документ.ПоступлениеНаРасчетныйСчет");
expect(plan.query).toContain("ПРЕДСТАВЛЕНИЕ(БанкПоступление.ДоговорКонтрагента) КАК Договор");
});
it("allows extended limit for open-contracts intent", () => {
const selected = selectAddressRecipe("list_open_contracts", {
as_of_date: "2020-12-31",

View File

@ -348,6 +348,100 @@ describe("assistant address llm pre-decompose candidate preference", () => {
expect(response.debug?.llm_decomposition_reason).not.toBe("normalized_fragment_applied");
});
it("rejects llm fragment when counterparty anchor is substituted by unrelated noun", async () => {
const calls: Array<{ message: string }> = [];
const addressQueryService = {
tryHandle: vi.fn(async (message: string) => {
calls.push({ message });
return buildAddressLaneResult(message);
})
} as any;
const normalizerService = {
normalize: vi.fn(async () => ({
trace_id: "norm-predecompose-anchor-substitution",
ok: true,
normalized: {
schema_version: "normalized_query_v2_0_2",
user_message_raw: "свк доки за 20й",
message_in_scope: true,
scope_confidence: "medium",
contains_multiple_tasks: false,
fragments: [
{
fragment_id: "F1",
raw_fragment_text: "свк доки за 20й",
normalized_fragment_text: "сканирование документов за 20-й период",
domain_relevance: "in_scope",
business_scope: "company_specific_accounting",
entity_hints: [],
account_hints: [],
document_hints: ["документы"],
register_hints: [],
time_scope: {
type: "explicit",
value: "2020",
confidence: "high"
},
flags: {
has_multi_entity_scope: false,
asks_for_chain_explanation: false,
asks_for_ranking_or_top: false,
asks_for_period_summary: false,
asks_for_rule_check: false,
asks_for_anomaly_scan: false,
asks_for_exact_object_trace: false,
asks_for_evidence: false,
mentions_period_close_context: false
},
candidate_labels: ["simple_factual"],
confidence: "medium",
execution_readiness: "executable",
clarification_reason: null,
soft_assumption_used: [],
route_status: "routed",
no_route_reason: null
}
],
discarded_fragments: [],
global_notes: {
needs_clarification: false,
clarification_reason: null
}
},
raw_model_output: null,
validation: { passed: true, errors: [] },
usage: { input_tokens: 1, output_tokens: 1, total_tokens: 2 },
latency_ms: 10,
prompt_version: "normalizer_v2_0_2",
schema_version: "v2_0_2",
request_count_for_case: 1
}))
} as any;
const sessions = new AssistantSessionStore();
const service = new AssistantService(
normalizerService,
sessions as any,
{} as any,
{ persistSession: vi.fn() } as any,
addressQueryService
);
const response = await service.handleMessage({
session_id: `asst-predecompose-anchor-substitution-${Date.now()}`,
user_message: "свк доки за 20й",
llmProvider: "local",
useMock: false
} as any);
expect(response.ok).toBe(true);
expect(calls).toHaveLength(1);
expect(calls[0].message.toLowerCase()).toContain("свк");
expect(calls[0].message.toLowerCase()).not.toContain("сканирование");
expect(response.debug?.llm_decomposition_reason).toBe("normalized_fragment_rejected_anchor_substitution");
});
it("rejects follow-up intent injection when llm adds documents to same-date account prompt", async () => {
const calls: Array<{ message: string }> = [];
const addressQueryService = {

View File

@ -0,0 +1,26 @@
param(
[switch]$SkipPipInstall,
[switch]$InstallGitHooks
)
$ErrorActionPreference = "Stop"
$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot "..")).Path
Set-Location $repoRoot
if (-not $SkipPipInstall) {
py -m pip install --user graphifyy
}
py -m graphify install --platform codex
py -m graphify codex install
if ($InstallGitHooks) {
py -m graphify hook install
}
Write-Host ""
Write-Host "Graphify configured for Codex in $repoRoot"
Write-Host "Use these commands inside Codex:"
Write-Host " `$graphify ."
Write-Host " `$graphify . --update"