Этап 4 / Волна 11: объектный трейс по бизнес-якорям, доменное заземление, cleanup утечки дебага
This commit is contained in:
+397
@@ -0,0 +1,397 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { composeAssistantAnswer } from "../src/services/answerComposer";
|
||||
import type { AnswerGroundingCheck, RequirementCoverageReport, UnifiedRetrievalResult } from "../src/types/assistant";
|
||||
import type { ProblemUnit } from "../src/types/stage2ProblemUnits";
|
||||
|
||||
function buildRouteSummary() {
|
||||
return {
|
||||
mode: "deterministic_v2" as const,
|
||||
message_in_scope: true,
|
||||
scope_confidence: "high" as const,
|
||||
planner: {
|
||||
total_fragments: 1,
|
||||
in_scope_fragments: 1,
|
||||
out_of_scope_fragments: 0,
|
||||
discarded_fragments: 0,
|
||||
contains_multiple_tasks: false
|
||||
},
|
||||
decisions: [],
|
||||
fallback: {
|
||||
type: "none" as const,
|
||||
message: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function buildCoverage(input?: Partial<RequirementCoverageReport>): RequirementCoverageReport {
|
||||
return {
|
||||
requirements_total: 1,
|
||||
requirements_covered: 1,
|
||||
requirements_uncovered: [],
|
||||
requirements_partially_covered: [],
|
||||
clarification_needed_for: [],
|
||||
out_of_scope_requirements: [],
|
||||
...input
|
||||
};
|
||||
}
|
||||
|
||||
function buildGrounding(input?: Partial<AnswerGroundingCheck>): AnswerGroundingCheck {
|
||||
return {
|
||||
status: "grounded",
|
||||
route_subject_match: true,
|
||||
missing_requirements: [],
|
||||
reasons: [],
|
||||
why_included_summary: ["wave12-test"],
|
||||
selection_reason_summary: ["wave12-test"],
|
||||
...input
|
||||
};
|
||||
}
|
||||
|
||||
function buildProblemUnit(input: {
|
||||
id: string;
|
||||
type: ProblemUnit["problem_unit_type"];
|
||||
account: string;
|
||||
defect: string;
|
||||
lifecycleDomain?: ProblemUnit["lifecycle_domain"];
|
||||
}): ProblemUnit {
|
||||
return {
|
||||
schema_version: "problem_unit_v0_1",
|
||||
problem_unit_id: input.id,
|
||||
problem_unit_type: input.type,
|
||||
title: "Wave12 problem unit",
|
||||
mechanism_summary: `Mechanism candidate: ${input.defect}.`,
|
||||
business_defect_class: input.defect,
|
||||
severity: {
|
||||
score: 0.72,
|
||||
grade: "high"
|
||||
},
|
||||
confidence: {
|
||||
score: 0.58,
|
||||
grade: "medium"
|
||||
},
|
||||
affected_entities: ["Document:DOC-1"],
|
||||
affected_documents: ["Document:DOC-1"],
|
||||
affected_postings: ["Posting:POST-1"],
|
||||
affected_accounts: [input.account],
|
||||
affected_counterparties: ["Counterparty:CP-1"],
|
||||
affected_contracts: ["Contract:CTR-1"],
|
||||
failed_expected_edge: input.defect,
|
||||
period_impact: {
|
||||
is_period_sensitive: true,
|
||||
impact_class: "close_risk"
|
||||
},
|
||||
evidence_pack: ["cand-1"],
|
||||
entity_backlinks: [{ entity: "Document", id: "DOC-1" }],
|
||||
snapshot_limitations: [],
|
||||
...(input.lifecycleDomain
|
||||
? {
|
||||
lifecycle_domain: input.lifecycleDomain
|
||||
}
|
||||
: {})
|
||||
};
|
||||
}
|
||||
|
||||
function buildRetrieval(input: {
|
||||
requirementId: string;
|
||||
status: UnifiedRetrievalResult["status"];
|
||||
units?: ProblemUnit[];
|
||||
accountScope?: string[];
|
||||
domainScope?: string[];
|
||||
relationPatterns?: string[];
|
||||
limitations?: string[];
|
||||
confidence?: UnifiedRetrievalResult["confidence"];
|
||||
withEvidence?: boolean;
|
||||
}): UnifiedRetrievalResult {
|
||||
const units = input.units ?? [];
|
||||
const withEvidence = input.withEvidence ?? input.status !== "empty";
|
||||
return {
|
||||
fragment_id: `F-${input.requirementId}`,
|
||||
requirement_ids: [input.requirementId],
|
||||
route: "hybrid_store_plus_live",
|
||||
status: input.status,
|
||||
result_type: "chain",
|
||||
items:
|
||||
input.status === "empty"
|
||||
? []
|
||||
: [
|
||||
{
|
||||
source_entity: "Document",
|
||||
source_id: "DOC-1",
|
||||
account_context: input.accountScope ?? ["60"],
|
||||
graph_domain_scope: input.domainScope ?? ["bank_settlement"],
|
||||
relation_pattern_hits: input.relationPatterns ?? ["payment_to_settlement"]
|
||||
}
|
||||
],
|
||||
summary: {
|
||||
broad_query_detected: false,
|
||||
broad_result_flag: false,
|
||||
minimum_evidence_failed: false,
|
||||
degraded_to: null,
|
||||
narrowing_strength: "strong",
|
||||
semantic_profile: {
|
||||
account_scope: input.accountScope ?? ["60", "62"],
|
||||
domain_scope: input.domainScope ?? ["bank_settlement", "customer_settlement"],
|
||||
relation_patterns: input.relationPatterns ?? ["payment_to_settlement"]
|
||||
}
|
||||
},
|
||||
evidence:
|
||||
input.status === "empty" || !withEvidence
|
||||
? []
|
||||
: [
|
||||
{
|
||||
evidence_id: `ev-${input.requirementId}`,
|
||||
claim_ref: `requirement:${input.requirementId}`,
|
||||
source_type: "retrieval_item",
|
||||
source_ref: {
|
||||
schema_version: "evidence_source_ref_v1",
|
||||
namespace: "snapshot_2020",
|
||||
entity: "document",
|
||||
id: "DOC-1",
|
||||
period: "2020-07",
|
||||
canonical_ref: "evidence_source_ref_v1|snapshot_2020|document|doc-1|2020-07"
|
||||
},
|
||||
pointer: {
|
||||
fragment_id: `F-${input.requirementId}`,
|
||||
route: "hybrid_store_plus_live",
|
||||
source: {
|
||||
namespace: "snapshot_2020",
|
||||
entity: "document",
|
||||
id: "DOC-1",
|
||||
period: "2020-07"
|
||||
},
|
||||
locator: {
|
||||
field_path: "risk_score",
|
||||
item_index: 0
|
||||
}
|
||||
},
|
||||
evidence_kind: "mechanism_link",
|
||||
mechanism_note: (input.relationPatterns ?? ["payment_to_settlement"])[0],
|
||||
confidence: input.status === "ok" ? "medium" : "low",
|
||||
limitation: null,
|
||||
payload: {
|
||||
risk_score: 4
|
||||
}
|
||||
}
|
||||
],
|
||||
candidate_evidence: [],
|
||||
problem_units: units,
|
||||
problem_unit_summary:
|
||||
units.length > 0
|
||||
? {
|
||||
schema_version: "problem_unit_summary_v0_1",
|
||||
units_total: units.length,
|
||||
duplicate_collapses: 0,
|
||||
unit_types: units.map((unit) => unit.problem_unit_type),
|
||||
type_distribution: {
|
||||
[units[0]?.problem_unit_type ?? "broken_chain_segment"]: units.length
|
||||
},
|
||||
severity_distribution: {
|
||||
low: 0,
|
||||
medium: 0,
|
||||
high: units.length
|
||||
},
|
||||
confidence_distribution: {
|
||||
low: 0,
|
||||
medium: units.length,
|
||||
high: 0
|
||||
},
|
||||
primary_unit_type: units[0]?.problem_unit_type ?? null
|
||||
}
|
||||
: null,
|
||||
why_included: ["wave12-test"],
|
||||
selection_reason: ["wave12-test"],
|
||||
risk_factors: ["wave12"],
|
||||
business_interpretation: ["wave12"],
|
||||
confidence: input.confidence ?? (input.status === "ok" ? "medium" : "low"),
|
||||
limitations: input.limitations ?? [],
|
||||
errors: []
|
||||
};
|
||||
}
|
||||
|
||||
function composeCase(input: {
|
||||
userMessage: string;
|
||||
focusDomainHint: string | null;
|
||||
retrievalResults: UnifiedRetrievalResult[];
|
||||
coverage?: Partial<RequirementCoverageReport>;
|
||||
grounding?: Partial<AnswerGroundingCheck>;
|
||||
}) {
|
||||
return composeAssistantAnswer({
|
||||
userMessage: input.userMessage,
|
||||
routeSummary: buildRouteSummary(),
|
||||
retrievalResults: input.retrievalResults,
|
||||
requirements: [
|
||||
{
|
||||
requirement_id: "R1",
|
||||
source_fragment_id: "F-R1",
|
||||
requirement_text: "Wave 12 requirement",
|
||||
subject_tokens: [],
|
||||
status: "covered",
|
||||
route: "hybrid_store_plus_live"
|
||||
}
|
||||
],
|
||||
coverageReport: buildCoverage(input.coverage),
|
||||
groundingCheck: buildGrounding(input.grounding),
|
||||
focusDomainHint: input.focusDomainHint,
|
||||
enableAnswerPolicyV11: true,
|
||||
enableProblemCentricAnswerV1: true,
|
||||
enableLifecycleAnswerV1: true
|
||||
});
|
||||
}
|
||||
|
||||
describe("wave12 vat/month-close consistency + confidence reconciliation", () => {
|
||||
it("vat_query_with_strong_signal_must_override_stale_settlement_focus_hint", () => {
|
||||
const vatUnit = buildProblemUnit({
|
||||
id: "pu-vat-1",
|
||||
type: "cross_branch_inconsistency_cluster",
|
||||
account: "68",
|
||||
defect: "invoice_to_vat",
|
||||
lifecycleDomain: "vat_flow"
|
||||
});
|
||||
const output = composeCase({
|
||||
userMessage: "VAT chain July: invoice exists, but purchase book is empty for accounts 19/68. Why?",
|
||||
focusDomainHint: "settlements_60_62",
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
requirementId: "R1",
|
||||
status: "ok",
|
||||
units: [vatUnit],
|
||||
accountScope: ["19", "68"],
|
||||
domainScope: ["vat_flow"],
|
||||
relationPatterns: ["invoice_to_vat"]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
expect(output.assistant_reply).toMatch(/НДС|vat|книг|счет[-\s]?фактур/i);
|
||||
expect(output.assistant_reply).not.toMatch(/60\/62|закрыти[ея]\s+расч[её]т/i);
|
||||
});
|
||||
|
||||
it("month_close_query_with_strong_signal_must_override_stale_vat_focus_hint", () => {
|
||||
const closeUnit = buildProblemUnit({
|
||||
id: "pu-close-1",
|
||||
type: "period_risk_cluster",
|
||||
account: "25",
|
||||
defect: "close_operation_runs_missing",
|
||||
lifecycleDomain: "period_close"
|
||||
});
|
||||
const output = composeCase({
|
||||
userMessage: "Month close July: costs on accounts 20/44 were allocated partially. Where is the break?",
|
||||
focusDomainHint: "vat_document_register_book",
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
requirementId: "R1",
|
||||
status: "ok",
|
||||
units: [closeUnit],
|
||||
accountScope: ["25", "26"],
|
||||
domainScope: ["period_close"],
|
||||
relationPatterns: ["close_operation_runs"]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
expect(output.assistant_reply).toMatch(/закрыти|месяц|затрат|распредел|month close|20\/44/i);
|
||||
expect(output.assistant_reply).not.toMatch(/НДС|счет[-\s]?фактур|книг/i);
|
||||
});
|
||||
|
||||
it("vat_domain_with_foreign_primary_evidence_must_degrade_to_clarification", () => {
|
||||
const output = composeCase({
|
||||
userMessage: "Проверь НДС-цепочку по июлю: документ -> регистр -> книга.",
|
||||
focusDomainHint: "vat_document_register_book",
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
requirementId: "R1",
|
||||
status: "ok",
|
||||
units: [],
|
||||
accountScope: ["20"],
|
||||
domainScope: ["period_close", "fixed_asset"],
|
||||
relationPatterns: ["allocation_rules_resolved"]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
expect(output.reply_type).toBe("clarification_required");
|
||||
expect(output.assistant_reply).toContain("Ограничения:");
|
||||
expect(output.assistant_reply).not.toContain("Опора достаточна для первичного вывода.");
|
||||
});
|
||||
|
||||
it("month_close_domain_with_vat_primary_evidence_must_degrade_to_clarification", () => {
|
||||
const output = composeCase({
|
||||
userMessage: "Проверь закрытие месяца и контур затрат 20-44 за июль.",
|
||||
focusDomainHint: "month_close_costs_20_44",
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
requirementId: "R1",
|
||||
status: "ok",
|
||||
units: [],
|
||||
accountScope: ["19", "68"],
|
||||
domainScope: ["vat_flow"],
|
||||
relationPatterns: ["invoice_to_vat"]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
expect(output.reply_type).toBe("clarification_required");
|
||||
expect(output.assistant_reply).toContain("Ограничения:");
|
||||
expect(output.assistant_reply).not.toContain("Опора достаточна для первичного вывода.");
|
||||
});
|
||||
|
||||
it("confidence_limitation_must_not_contradict_each_other", () => {
|
||||
const vatUnit = buildProblemUnit({
|
||||
id: "pu-vat-2",
|
||||
type: "lifecycle_anomaly_node",
|
||||
account: "68",
|
||||
defect: "invoice_to_vat",
|
||||
lifecycleDomain: "vat_flow"
|
||||
});
|
||||
const output = composeCase({
|
||||
userMessage: "Почему по НДС есть сигнал, но механизм выглядит неполным?",
|
||||
focusDomainHint: "vat_document_register_book",
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
requirementId: "R1",
|
||||
status: "ok",
|
||||
units: [vatUnit],
|
||||
accountScope: ["19", "68"],
|
||||
domainScope: ["vat_flow"],
|
||||
relationPatterns: ["invoice_to_vat"],
|
||||
limitations: ["Source mapping is weak for part of the evidence."]
|
||||
})
|
||||
],
|
||||
grounding: {
|
||||
status: "partial",
|
||||
reasons: ["Mechanism is unresolved for part of the evidence."]
|
||||
}
|
||||
});
|
||||
|
||||
expect(output.answer_structure_v11?.mechanism_block?.status).toBe("limited");
|
||||
expect(output.assistant_reply).toContain("Ограничения:");
|
||||
expect(output.assistant_reply).not.toContain("Опора достаточна для первичного вывода.");
|
||||
});
|
||||
|
||||
it("settlement_regression_must_remain_pass", () => {
|
||||
const settlementUnit = buildProblemUnit({
|
||||
id: "pu-settlement-1",
|
||||
type: "broken_chain_segment",
|
||||
account: "62",
|
||||
defect: "failed_edge:payment_to_settlement",
|
||||
lifecycleDomain: "customer_settlement"
|
||||
});
|
||||
const output = composeCase({
|
||||
userMessage: "Оплата есть, но 62.01/62.02 не сходятся. Почему долг остался?",
|
||||
focusDomainHint: "settlements_60_62",
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
requirementId: "R1",
|
||||
status: "ok",
|
||||
units: [settlementUnit],
|
||||
accountScope: ["62.01", "62.02"],
|
||||
domainScope: ["customer_settlement"],
|
||||
relationPatterns: ["payment_to_settlement"]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
expect(output.assistant_reply).toMatch(/62\.01|62\.02|расч[её]т|зач[её]т/i);
|
||||
expect(output.assistant_reply).not.toMatch(/НДС|книг|закрыти[ея]\s+месяц/i);
|
||||
});
|
||||
});
|
||||
+405
@@ -0,0 +1,405 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { composeAssistantAnswer } from "../src/services/answerComposer";
|
||||
import type { AnswerGroundingCheck, RequirementCoverageReport, UnifiedRetrievalResult } from "../src/types/assistant";
|
||||
import type { ProblemUnit } from "../src/types/stage2ProblemUnits";
|
||||
import type { QuestionTypeClass } from "../src/services/questionTypeResolver";
|
||||
|
||||
function buildRouteSummary() {
|
||||
return {
|
||||
mode: "deterministic_v2" as const,
|
||||
message_in_scope: true,
|
||||
scope_confidence: "high" as const,
|
||||
planner: {
|
||||
total_fragments: 1,
|
||||
in_scope_fragments: 1,
|
||||
out_of_scope_fragments: 0,
|
||||
discarded_fragments: 0,
|
||||
contains_multiple_tasks: false
|
||||
},
|
||||
decisions: [],
|
||||
fallback: {
|
||||
type: "none" as const,
|
||||
message: null
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function buildCoverage(input?: Partial<RequirementCoverageReport>): RequirementCoverageReport {
|
||||
return {
|
||||
requirements_total: 1,
|
||||
requirements_covered: 1,
|
||||
requirements_uncovered: [],
|
||||
requirements_partially_covered: [],
|
||||
clarification_needed_for: [],
|
||||
out_of_scope_requirements: [],
|
||||
...input
|
||||
};
|
||||
}
|
||||
|
||||
function buildGrounding(input?: Partial<AnswerGroundingCheck>): AnswerGroundingCheck {
|
||||
return {
|
||||
status: "grounded",
|
||||
route_subject_match: true,
|
||||
missing_requirements: [],
|
||||
reasons: [],
|
||||
why_included_summary: ["wave13-test"],
|
||||
selection_reason_summary: ["wave13-test"],
|
||||
...input
|
||||
};
|
||||
}
|
||||
|
||||
function buildProblemUnit(input: {
|
||||
id: string;
|
||||
type: ProblemUnit["problem_unit_type"];
|
||||
account: string;
|
||||
defect: string;
|
||||
lifecycleDomain: ProblemUnit["lifecycle_domain"];
|
||||
}): ProblemUnit {
|
||||
return {
|
||||
schema_version: "problem_unit_v0_1",
|
||||
problem_unit_id: input.id,
|
||||
problem_unit_type: input.type,
|
||||
title: "Wave13 problem unit",
|
||||
mechanism_summary: `Mechanism candidate: ${input.defect}.`,
|
||||
business_defect_class: input.defect,
|
||||
severity: {
|
||||
score: 0.72,
|
||||
grade: "high"
|
||||
},
|
||||
confidence: {
|
||||
score: 0.63,
|
||||
grade: "medium"
|
||||
},
|
||||
lifecycle_domain: input.lifecycleDomain,
|
||||
affected_entities: ["Document:DOC-1"],
|
||||
affected_documents: ["Document:DOC-1"],
|
||||
affected_postings: ["Posting:POST-1"],
|
||||
affected_accounts: [input.account],
|
||||
affected_counterparties: ["Counterparty:CP-1"],
|
||||
affected_contracts: ["Contract:CTR-1"],
|
||||
failed_expected_edge: input.defect,
|
||||
period_impact: {
|
||||
is_period_sensitive: true,
|
||||
impact_class: "close_risk"
|
||||
},
|
||||
evidence_pack: ["cand-1"],
|
||||
entity_backlinks: [{ entity: "Document", id: "DOC-1" }],
|
||||
snapshot_limitations: []
|
||||
};
|
||||
}
|
||||
|
||||
function buildRetrieval(input: {
|
||||
requirementId: string;
|
||||
status: UnifiedRetrievalResult["status"];
|
||||
units?: ProblemUnit[];
|
||||
accountScope?: string[];
|
||||
domainScope?: string[];
|
||||
relationPatterns?: string[];
|
||||
withEvidence?: boolean;
|
||||
notes?: string[];
|
||||
}): UnifiedRetrievalResult {
|
||||
const units = input.units ?? [];
|
||||
const withEvidence = input.withEvidence ?? input.status !== "empty";
|
||||
const accountScope = input.accountScope ?? ["60", "62"];
|
||||
const domainScope = input.domainScope ?? ["bank_settlement"];
|
||||
const relationPatterns = input.relationPatterns ?? ["payment_to_settlement"];
|
||||
return {
|
||||
fragment_id: `F-${input.requirementId}`,
|
||||
requirement_ids: [input.requirementId],
|
||||
route: "hybrid_store_plus_live",
|
||||
status: input.status,
|
||||
result_type: "chain",
|
||||
items:
|
||||
input.status === "empty"
|
||||
? []
|
||||
: [
|
||||
{
|
||||
source_entity: "Document",
|
||||
source_id: "DOC-1",
|
||||
display_name: "Счет № 4 от 07.07.20",
|
||||
account_context: accountScope,
|
||||
graph_domain_scope: domainScope,
|
||||
relation_pattern_hits: relationPatterns,
|
||||
period: "2020-07",
|
||||
amount: "276 873,60"
|
||||
}
|
||||
],
|
||||
summary: {
|
||||
broad_query_detected: false,
|
||||
broad_result_flag: false,
|
||||
minimum_evidence_failed: false,
|
||||
degraded_to: null,
|
||||
narrowing_strength: "strong",
|
||||
semantic_profile: {
|
||||
account_scope: accountScope,
|
||||
domain_scope: domainScope,
|
||||
relation_patterns: relationPatterns,
|
||||
period_scope: {
|
||||
from: "2020-07-01",
|
||||
to: "2020-07-31",
|
||||
granularity: "month"
|
||||
}
|
||||
}
|
||||
},
|
||||
evidence:
|
||||
input.status === "empty" || !withEvidence
|
||||
? []
|
||||
: [
|
||||
{
|
||||
evidence_id: `ev-${input.requirementId}`,
|
||||
claim_ref: `requirement:${input.requirementId}`,
|
||||
source_type: "retrieval_item",
|
||||
source_ref: {
|
||||
schema_version: "evidence_source_ref_v1",
|
||||
namespace: "snapshot_2020_07",
|
||||
entity: "document",
|
||||
id: "DOC-1",
|
||||
period: "2020-07",
|
||||
canonical_ref: "evidence_source_ref_v1|snapshot_2020_07|document|doc-1|2020-07"
|
||||
},
|
||||
pointer: {
|
||||
fragment_id: `F-${input.requirementId}`,
|
||||
route: "hybrid_store_plus_live",
|
||||
source: {
|
||||
namespace: "snapshot_2020_07",
|
||||
entity: "document",
|
||||
id: "DOC-1",
|
||||
period: "2020-07"
|
||||
},
|
||||
locator: {
|
||||
field_path: "risk_score",
|
||||
item_index: 0
|
||||
}
|
||||
},
|
||||
evidence_kind: "mechanism_link",
|
||||
mechanism_note: relationPatterns[0],
|
||||
confidence: input.status === "ok" ? "medium" : "low",
|
||||
limitation: null,
|
||||
payload: {
|
||||
notes: input.notes ?? [],
|
||||
contract: "договор № 01/19-ПТ",
|
||||
amount: "276 873,60",
|
||||
date: "07.07.20"
|
||||
}
|
||||
}
|
||||
],
|
||||
candidate_evidence: [],
|
||||
problem_units: units,
|
||||
problem_unit_summary:
|
||||
units.length > 0
|
||||
? {
|
||||
schema_version: "problem_unit_summary_v0_1",
|
||||
units_total: units.length,
|
||||
duplicate_collapses: 0,
|
||||
unit_types: units.map((unit) => unit.problem_unit_type),
|
||||
type_distribution: {
|
||||
[units[0]?.problem_unit_type ?? "broken_chain_segment"]: units.length
|
||||
},
|
||||
severity_distribution: {
|
||||
low: 0,
|
||||
medium: 0,
|
||||
high: units.length
|
||||
},
|
||||
confidence_distribution: {
|
||||
low: 0,
|
||||
medium: units.length,
|
||||
high: 0
|
||||
},
|
||||
primary_unit_type: units[0]?.problem_unit_type ?? null
|
||||
}
|
||||
: null,
|
||||
why_included: ["wave13-test"],
|
||||
selection_reason: ["wave13-test"],
|
||||
risk_factors: ["wave13"],
|
||||
business_interpretation: ["wave13"],
|
||||
confidence: input.status === "ok" ? "medium" : "low",
|
||||
limitations: [],
|
||||
errors: []
|
||||
};
|
||||
}
|
||||
|
||||
function composeCase(input: {
|
||||
userMessage: string;
|
||||
questionType: QuestionTypeClass;
|
||||
focusDomainHint: string | null;
|
||||
retrievalResults: UnifiedRetrievalResult[];
|
||||
coverage?: Partial<RequirementCoverageReport>;
|
||||
grounding?: Partial<AnswerGroundingCheck>;
|
||||
}) {
|
||||
return composeAssistantAnswer({
|
||||
userMessage: input.userMessage,
|
||||
routeSummary: buildRouteSummary(),
|
||||
retrievalResults: input.retrievalResults,
|
||||
requirements: [
|
||||
{
|
||||
requirement_id: "R1",
|
||||
source_fragment_id: "F-R1",
|
||||
requirement_text: "Wave13 requirement",
|
||||
subject_tokens: [],
|
||||
status: "covered",
|
||||
route: "hybrid_store_plus_live"
|
||||
}
|
||||
],
|
||||
coverageReport: buildCoverage(input.coverage),
|
||||
groundingCheck: buildGrounding(input.grounding),
|
||||
focusDomainHint: input.focusDomainHint,
|
||||
questionTypeHint: input.questionType,
|
||||
companyAnchors: {
|
||||
contract_numbers: ["договор № 01/19-ПТ"],
|
||||
document_numbers: ["документ № 4"],
|
||||
dates: ["07.07.20", "13.07.20"],
|
||||
amounts: ["276 873,60"],
|
||||
accounts: ["62.02"],
|
||||
periods: ["июль 2020"],
|
||||
document_types: ["payment", "invoice"],
|
||||
all: [
|
||||
"договор № 01/19-ПТ",
|
||||
"документ № 4",
|
||||
"07.07.20",
|
||||
"13.07.20",
|
||||
"276 873,60",
|
||||
"account:62.02",
|
||||
"period:июль 2020"
|
||||
]
|
||||
},
|
||||
enableAnswerPolicyV11: true,
|
||||
enableProblemCentricAnswerV1: true,
|
||||
enableLifecycleAnswerV1: true
|
||||
});
|
||||
}
|
||||
|
||||
describe("wave13 domain fit + question-type fit + company-anchor grounding", () => {
|
||||
it("settlement_query_must_keep_settlement_domain_even_if_retrieval_contains_vat_noise", () => {
|
||||
const settlementUnit = buildProblemUnit({
|
||||
id: "pu-settlement-1",
|
||||
type: "broken_chain_segment",
|
||||
account: "62.02",
|
||||
defect: "failed_edge:payment_to_settlement",
|
||||
lifecycleDomain: "customer_settlement"
|
||||
});
|
||||
const output = composeCase({
|
||||
userMessage:
|
||||
"Почему по договору № 01/19-ПТ от 09.01.2019 оплата 276 873,60 есть, а 62.01/62.02 все равно не сходятся?",
|
||||
questionType: "why_breaks",
|
||||
focusDomainHint: "vat_document_register_book",
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
requirementId: "R1",
|
||||
status: "ok",
|
||||
units: [settlementUnit],
|
||||
accountScope: ["62.01", "62.02", "19"],
|
||||
domainScope: ["customer_settlement", "vat_flow"],
|
||||
relationPatterns: ["payment_to_settlement", "invoice_to_vat"]
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
expect(output.assistant_reply).toMatch(/расчет|62\.01|62\.02|зачет|зачёт/i);
|
||||
expect(output.assistant_reply).not.toMatch(/переход от документа к регистру и книге|цепочке ндс/i);
|
||||
});
|
||||
|
||||
it("question_type_where_break_is_must_produce_localization_style_line", () => {
|
||||
const settlementUnit = buildProblemUnit({
|
||||
id: "pu-settlement-2",
|
||||
type: "broken_chain_segment",
|
||||
account: "62.02",
|
||||
defect: "failed_edge:payment_to_settlement",
|
||||
lifecycleDomain: "customer_settlement"
|
||||
});
|
||||
const output = composeCase({
|
||||
userMessage:
|
||||
"Где именно разрыв по договору № 01/19-ПТ: в договоре, объекте расчетов или в связке документов?",
|
||||
questionType: "where_break_is",
|
||||
focusDomainHint: "settlements_60_62",
|
||||
retrievalResults: [buildRetrieval({ requirementId: "R1", status: "ok", units: [settlementUnit] })]
|
||||
});
|
||||
|
||||
expect(output.assistant_reply).toMatch(/локализ|узел разрыва|где именно/i);
|
||||
});
|
||||
|
||||
it("question_type_prove_or_guess_must_explicitly_separate_proven_vs_hypothesis", () => {
|
||||
const vatUnit = buildProblemUnit({
|
||||
id: "pu-vat-1",
|
||||
type: "cross_branch_inconsistency_cluster",
|
||||
account: "68",
|
||||
defect: "invoice_to_vat",
|
||||
lifecycleDomain: "vat_flow"
|
||||
});
|
||||
const output = composeCase({
|
||||
userMessage:
|
||||
"По НДС это доказано по данным или это только гипотеза? На чем основано утверждение?",
|
||||
questionType: "prove_or_guess",
|
||||
focusDomainHint: "vat_document_register_book",
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
requirementId: "R1",
|
||||
status: "ok",
|
||||
units: [vatUnit],
|
||||
accountScope: ["19", "68"],
|
||||
domainScope: ["vat_flow"],
|
||||
relationPatterns: ["invoice_to_vat"]
|
||||
})
|
||||
],
|
||||
grounding: {
|
||||
status: "partial",
|
||||
reasons: ["Mechanism is unresolved for part of the evidence."]
|
||||
}
|
||||
});
|
||||
|
||||
expect(output.assistant_reply).toMatch(/доказан|гипотез|ограничени/i);
|
||||
});
|
||||
|
||||
it("anchor_usage_lines_must_be_present_when_company_anchors_are_used", () => {
|
||||
const output = composeCase({
|
||||
userMessage:
|
||||
"Оплата по счету № 4 от 07.07.20 на 276 873,60 пришла 13 июля. Что проверить первым по 62.02?",
|
||||
questionType: "what_to_check_first",
|
||||
focusDomainHint: "settlements_60_62",
|
||||
retrievalResults: [buildRetrieval({ requirementId: "R1", status: "ok" })]
|
||||
});
|
||||
|
||||
expect(output.assistant_reply).toMatch(/якоря вопроса/i);
|
||||
expect(output.assistant_reply).toMatch(/договор|07\.07\.20|276 873,60|62\.02/i);
|
||||
});
|
||||
|
||||
it("anchor_usage_must_be_honest_when_part_of_anchors_not_confirmed", () => {
|
||||
const output = composeCase({
|
||||
userMessage:
|
||||
"Почему по договору № 01/19-ПТ не сходится 62.02 в июле 2020, если была оплата 276 873,60?",
|
||||
questionType: "what_is_it_grounded_on",
|
||||
focusDomainHint: "settlements_60_62",
|
||||
retrievalResults: [
|
||||
buildRetrieval({
|
||||
requirementId: "R1",
|
||||
status: "ok",
|
||||
accountScope: ["60"],
|
||||
domainScope: ["bank_settlement"],
|
||||
relationPatterns: ["payment_recorded"],
|
||||
withEvidence: true
|
||||
})
|
||||
]
|
||||
});
|
||||
|
||||
expect(output.assistant_reply).toMatch(/без прямого подтверждения|ограничени/i);
|
||||
});
|
||||
|
||||
it("answers_for_different_question_types_must_not_collapse_to_same_generic_pattern", () => {
|
||||
const baseRetrieval = [buildRetrieval({ requirementId: "R1", status: "ok" })];
|
||||
const whereOutput = composeCase({
|
||||
userMessage: "Где именно разрыв по 62.01/62.02?",
|
||||
questionType: "where_break_is",
|
||||
focusDomainHint: "settlements_60_62",
|
||||
retrievalResults: baseRetrieval
|
||||
});
|
||||
const checkFirstOutput = composeCase({
|
||||
userMessage: "Что проверить первым по 62.01/62.02?",
|
||||
questionType: "what_to_check_first",
|
||||
focusDomainHint: "settlements_60_62",
|
||||
retrievalResults: baseRetrieval
|
||||
});
|
||||
|
||||
expect(whereOutput.assistant_reply).not.toEqual(checkFirstOutput.assistant_reply);
|
||||
expect(whereOutput.assistant_reply).toMatch(/локализ|разрыв/i);
|
||||
expect(checkFirstOutput.assistant_reply).toMatch(/первый маршрут проверки|начните с первого пункта/i);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user