Этап 4 / Волна 10: корректировка settlement-кейса — доменная фиксация синтеза, честное покрытие, удержание фокуса / Этап 4 / Волна 11: бизнес-якоря, доменное заземление и устранение утечки дебага

This commit is contained in:
2026-03-28 02:17:19 +03:00
parent 914843a8ba
commit a06e575be4
367 changed files with 432257 additions and 3627 deletions
+196
View File
@@ -0,0 +1,196 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.evaluateP0AcceptanceGate = evaluateP0AcceptanceGate;
exports.evaluateP0BaselineStabilityGate = evaluateP0BaselineStabilityGate;
const p0_metric_definitions_1 = require("./p0_metric_definitions");
function buildMetricChecks(input) {
const { metrics, thresholds } = input;
return [
{
metric: "problem_first_answer_rate",
value: metrics.problem_first_answer_rate,
threshold: thresholds.problem_first_answer_rate_min,
comparator: ">=",
passed: metrics.problem_first_answer_rate >= thresholds.problem_first_answer_rate_min,
severity: "blocking"
},
{
metric: "entity_leakage_rate",
value: metrics.entity_leakage_rate,
threshold: thresholds.entity_leakage_rate_max,
comparator: "<=",
passed: metrics.entity_leakage_rate <= thresholds.entity_leakage_rate_max,
severity: "blocking"
},
{
metric: "route_correctness_rate",
value: metrics.route_correctness_rate,
threshold: thresholds.route_correctness_rate_min,
comparator: ">=",
passed: metrics.route_correctness_rate >= thresholds.route_correctness_rate_min,
severity: "blocking"
},
{
metric: "domain_purity_rate",
value: metrics.domain_purity_rate,
threshold: thresholds.domain_purity_rate_min,
comparator: ">=",
passed: metrics.domain_purity_rate >= thresholds.domain_purity_rate_min,
severity: "blocking"
},
{
metric: "mechanism_coherence_score",
value: metrics.mechanism_coherence_score,
threshold: thresholds.mechanism_coherence_score_min,
comparator: ">=",
passed: metrics.mechanism_coherence_score >= thresholds.mechanism_coherence_score_min,
severity: "quality"
},
{
metric: "accountant_actionability_score",
value: metrics.accountant_actionability_score,
threshold: thresholds.accountant_actionability_score_min,
comparator: ">=",
passed: metrics.accountant_actionability_score >= thresholds.accountant_actionability_score_min,
severity: "quality"
},
{
metric: "limitation_honesty_rate",
value: metrics.limitation_honesty_rate,
threshold: thresholds.limitation_honesty_rate_min,
comparator: ">=",
passed: metrics.limitation_honesty_rate >= thresholds.limitation_honesty_rate_min,
severity: "quality"
},
{
metric: "top_problem_unit_match_rate",
value: metrics.top_problem_unit_match_rate,
threshold: thresholds.top_problem_unit_match_rate_min,
comparator: ">=",
passed: metrics.top_problem_unit_match_rate >= thresholds.top_problem_unit_match_rate_min,
severity: "quality"
}
];
}
function toFailureLine(check) {
return `${check.metric}: ${check.value.toFixed(2)} ${check.comparator} ${check.threshold.toFixed(2)} (failed)`;
}
function toQualityGapFailureLine(check) {
return `${check.metric}: ${check.value.toFixed(2)} ${check.comparator} ${check.threshold.toFixed(2)} (failed)`;
}
function buildQualityGapChecks(input) {
const { metrics, thresholds } = input;
return [
{
metric: "generic_explanation_rate",
value: metrics.generic_explanation_rate,
threshold: thresholds.generic_explanation_rate_max,
comparator: "<=",
passed: metrics.generic_explanation_rate <= thresholds.generic_explanation_rate_max
},
{
metric: "false_confidence_rate",
value: metrics.false_confidence_rate,
threshold: thresholds.false_confidence_rate_max,
comparator: "<=",
passed: metrics.false_confidence_rate <= thresholds.false_confidence_rate_max
},
{
metric: "mechanism_specificity_score",
value: metrics.mechanism_specificity_score,
threshold: thresholds.mechanism_specificity_score_min,
comparator: ">=",
passed: metrics.mechanism_specificity_score >= thresholds.mechanism_specificity_score_min
},
{
metric: "followup_context_retention_score",
value: metrics.followup_context_retention_score,
threshold: thresholds.followup_context_retention_score_min,
comparator: ">=",
passed: metrics.followup_context_retention_score >= thresholds.followup_context_retention_score_min
}
];
}
function evaluateP0AcceptanceGate(input) {
const thresholds = {
...p0_metric_definitions_1.P0_DEFAULT_ACCEPTANCE_THRESHOLDS,
...(input.thresholds ?? {})
};
const checks = buildMetricChecks({
metrics: input.metrics,
thresholds
});
const blockingFailures = checks.filter((item) => item.severity === "blocking" && !item.passed);
const qualityFailures = checks.filter((item) => item.severity === "quality" && !item.passed);
let verdict = "P0_NOT_ACCEPTED";
if (blockingFailures.length === 0 && qualityFailures.length === 0) {
verdict = "P0_ACCEPTED";
}
else if (blockingFailures.length === 0) {
verdict = "P0_ACCEPTED_WITH_LIMITATIONS";
}
const rationale = [];
if (blockingFailures.length > 0) {
rationale.push("Blocking metrics failed:");
rationale.push(...blockingFailures.map((item) => `- ${toFailureLine(item)}`));
}
if (qualityFailures.length > 0) {
rationale.push("Quality metrics failed:");
rationale.push(...qualityFailures.map((item) => `- ${toFailureLine(item)}`));
}
if (rationale.length === 0) {
rationale.push("All acceptance thresholds are satisfied.");
}
return {
verdict,
passed_all: checks.every((item) => item.passed),
blocking_failures: blockingFailures,
quality_failures: qualityFailures,
metric_checks: checks,
rationale
};
}
function evaluateP0BaselineStabilityGate(input) {
const acceptance = evaluateP0AcceptanceGate({
metrics: input.metrics,
thresholds: input.acceptanceThresholds
});
const qualityGapThresholds = {
...p0_metric_definitions_1.P0_DEFAULT_QUALITY_GAP_THRESHOLDS,
...(input.qualityGapThresholds ?? {})
};
const qualityGapChecks = buildQualityGapChecks({
metrics: input.qualityGapMetrics,
thresholds: qualityGapThresholds
});
const qualityGapFailures = qualityGapChecks.filter((item) => !item.passed);
const baselineIntegrityPassed = acceptance.blocking_failures.length === 0;
const qualityGapsOpen = acceptance.quality_failures.length > 0 || qualityGapFailures.length > 0;
const verdict = baselineIntegrityPassed && !qualityGapsOpen ? "P0_BASELINE_STABLE" : "P0_BASELINE_STABLE_WITH_OPEN_QUALITY_GAPS";
const rationale = [];
if (!baselineIntegrityPassed) {
rationale.push("Blocking baseline regressions detected:");
rationale.push(...acceptance.blocking_failures.map((item) => `- ${toFailureLine(item)}`));
}
if (acceptance.quality_failures.length > 0) {
rationale.push("Legacy quality thresholds still below full acceptance:");
rationale.push(...acceptance.quality_failures.map((item) => `- ${toFailureLine(item)}`));
}
if (qualityGapFailures.length > 0) {
rationale.push("Wave 9 quality-gap thresholds still open:");
rationale.push(...qualityGapFailures.map((item) => `- ${toQualityGapFailureLine(item)}`));
}
if (rationale.length === 0) {
rationale.push("Baseline is stable and Wave 9 quality-gap thresholds are satisfied.");
}
return {
verdict,
baseline_integrity_passed: baselineIntegrityPassed,
quality_gaps_open: qualityGapsOpen,
blocking_regressions: acceptance.blocking_failures,
legacy_quality_failures: acceptance.quality_failures,
quality_gap_failures: qualityGapFailures,
quality_gap_checks: qualityGapChecks,
rationale
};
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,278 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.P0_DEFAULT_QUALITY_GAP_THRESHOLDS = exports.P0_DEFAULT_ACCEPTANCE_THRESHOLDS = exports.P0_DEFAULT_FORBIDDEN_LEAKAGE_TOKENS = exports.P0_QUALITY_GAP_METRIC_DEFINITIONS = exports.P0_METRIC_DEFINITIONS = void 0;
exports.validateP0EvalCorpus = validateP0EvalCorpus;
exports.normalizeExpectedRoutes = normalizeExpectedRoutes;
exports.P0_METRIC_DEFINITIONS = {
problem_first_answer_rate: {
description: "Share of cases where answer is problem-first and avoids entity-dump framing.",
unit: "rate",
direction: "higher_is_better"
},
mechanism_coherence_score: {
description: "Average 0..5 score for mechanism clarity and alignment with expected defect class.",
unit: "score",
direction: "higher_is_better"
},
entity_leakage_rate: {
description: "Share of cases with internal/technical leakage in user-facing answer.",
unit: "rate",
direction: "lower_is_better"
},
accountant_actionability_score: {
description: "Average 0..5 score for first-check concreteness and practical accounting actionability.",
unit: "score",
direction: "higher_is_better"
},
route_correctness_rate: {
description: "Share of cases routed to expected path class for query type.",
unit: "rate",
direction: "higher_is_better"
},
domain_purity_rate: {
description: "Share of cases where top-3 retrieval stays inside expected P0 domain boundary.",
unit: "rate",
direction: "higher_is_better"
},
limitation_honesty_rate: {
description: "Share of cases where mandatory limitations are explicit when evidence is incomplete.",
unit: "rate",
direction: "higher_is_better"
},
top_problem_unit_match_rate: {
description: "Share of cases where top problem unit type matches expected annotation.",
unit: "rate",
direction: "higher_is_better"
}
};
exports.P0_QUALITY_GAP_METRIC_DEFINITIONS = {
generic_explanation_rate: {
description: "Share of cases where direct answer stays generic and mechanism/actionability specificity is insufficient.",
unit: "rate",
direction: "lower_is_better"
},
false_confidence_rate: {
description: "Share of cases with overconfident framing not supported by evidence/limitation state.",
unit: "rate",
direction: "lower_is_better"
},
mechanism_specificity_score: {
description: "Average 0..5 score of mechanism-level specificity in accountant-facing answer.",
unit: "score",
direction: "higher_is_better"
},
followup_context_retention_score: {
description: "Rate of correct context retention in follow-up investigation cases.",
unit: "rate",
direction: "higher_is_better"
}
};
exports.P0_DEFAULT_FORBIDDEN_LEAKAGE_TOKENS = [
"graph_",
"domain_scope",
"relation_patterns",
"account_scope",
"semantic_profile",
"route",
"profile",
"store_canonical",
"hybrid_store_plus_live",
"deterministic_v2",
"candidate_evidence",
"problem_unit",
"raw_entities",
"graph_domain_scope"
];
exports.P0_DEFAULT_ACCEPTANCE_THRESHOLDS = {
problem_first_answer_rate_min: 0.8,
mechanism_coherence_score_min: 3.5,
entity_leakage_rate_max: 0.08,
accountant_actionability_score_min: 3.5,
route_correctness_rate_min: 0.85,
domain_purity_rate_min: 0.9,
limitation_honesty_rate_min: 0.9,
top_problem_unit_match_rate_min: 0.65
};
exports.P0_DEFAULT_QUALITY_GAP_THRESHOLDS = {
generic_explanation_rate_max: 0.2,
false_confidence_rate_max: 0.12,
mechanism_specificity_score_min: 3.0,
followup_context_retention_score_min: 0.75
};
function isProblemUnitType(value) {
return (value === "document_conflict" ||
value === "broken_chain_segment" ||
value === "lifecycle_anomaly_node" ||
value === "unresolved_settlement_cluster" ||
value === "period_risk_cluster" ||
value === "cross_branch_inconsistency_cluster");
}
function isP0DomainId(value) {
return value === "settlements_60_62" || value === "vat_document_register_book" || value === "month_close_costs_20_44";
}
function isP0QueryClass(value) {
return (value === "symptom_first" ||
value === "lifecycle_first" ||
value === "causal_query" ||
value === "mixed_ambiguity" ||
value === "followup_investigation" ||
value === "noisy_input" ||
value === "translit_noisy" ||
value === "multi_intent");
}
function isRouteExpected(value) {
return (value === "hybrid_store_plus_live" ||
value === "store_feature_risk" ||
value === "store_canonical" ||
value === "batch_refresh_then_store" ||
value === "live_mcp_drilldown" ||
value === "no_route");
}
function assertString(value, fieldPath) {
if (typeof value !== "string" || !value.trim()) {
throw new Error(`Invalid corpus field '${fieldPath}': non-empty string is required.`);
}
return value.trim();
}
function assertBoolean(value, fieldPath) {
if (typeof value !== "boolean") {
throw new Error(`Invalid corpus field '${fieldPath}': boolean is required.`);
}
return value;
}
function assertStringArray(value, fieldPath) {
if (!Array.isArray(value)) {
throw new Error(`Invalid corpus field '${fieldPath}': string[] is required.`);
}
const output = value.map((item, index) => assertString(item, `${fieldPath}[${index}]`));
return output;
}
function assertRouteExpected(value, fieldPath) {
if (Array.isArray(value)) {
const routes = value.map((item, index) => {
if (!isRouteExpected(item)) {
throw new Error(`Invalid corpus field '${fieldPath}[${index}]': unsupported route '${String(item)}'.`);
}
return item;
});
if (routes.length === 0) {
throw new Error(`Invalid corpus field '${fieldPath}': route list must not be empty.`);
}
return routes;
}
if (!isRouteExpected(value)) {
throw new Error(`Invalid corpus field '${fieldPath}': unsupported route '${String(value)}'.`);
}
return value;
}
function parseCase(raw, index) {
if (!raw || typeof raw !== "object") {
throw new Error(`Invalid corpus case at index ${index}: object is required.`);
}
const row = raw;
const expected = row.expected_annotations;
if (!expected || typeof expected !== "object") {
throw new Error(`Invalid corpus case '${String(row.case_id ?? index)}': expected_annotations is required.`);
}
const annotations = expected;
const domain = row.domain;
if (!isP0DomainId(domain)) {
throw new Error(`Invalid corpus case '${String(row.case_id ?? index)}': unsupported domain '${String(domain)}'.`);
}
const queryClass = row.query_class;
if (!isP0QueryClass(queryClass)) {
throw new Error(`Invalid corpus case '${String(row.case_id ?? index)}': unsupported query_class '${String(queryClass)}'.`);
}
const expectedProblemType = row.expected_problem_unit_type;
if (!isProblemUnitType(expectedProblemType)) {
throw new Error(`Invalid corpus case '${String(row.case_id ?? index)}': expected_problem_unit_type '${String(expectedProblemType)}' is not supported.`);
}
const annotationProblemType = annotations.problem_unit_type_expected;
if (!isProblemUnitType(annotationProblemType)) {
throw new Error(`Invalid corpus case '${String(row.case_id ?? index)}': expected_annotations.problem_unit_type_expected '${String(annotationProblemType)}' is not supported.`);
}
const annotationDomain = annotations.domain_expected;
if (!isP0DomainId(annotationDomain)) {
throw new Error(`Invalid corpus case '${String(row.case_id ?? index)}': expected_annotations.domain_expected '${String(annotationDomain)}' is not supported.`);
}
const followupSeedQuery = typeof row.followup_seed_query === "string" && row.followup_seed_query.trim() ? row.followup_seed_query.trim() : undefined;
const followupExpectedContextTokens = Array.isArray(row.followup_expected_context_tokens)
? assertStringArray(row.followup_expected_context_tokens, `cases[${index}].followup_expected_context_tokens`)
: undefined;
if (queryClass === "followup_investigation" && !followupSeedQuery) {
throw new Error(`Invalid corpus case '${String(row.case_id ?? index)}': followup_investigation requires followup_seed_query.`);
}
return {
case_id: assertString(row.case_id, `cases[${index}].case_id`),
domain,
user_query_raw: assertString(row.user_query_raw, `cases[${index}].user_query_raw`),
query_class: queryClass,
expected_problem_unit_type: expectedProblemType,
expected_mechanism_summary: assertString(row.expected_mechanism_summary, `cases[${index}].expected_mechanism_summary`),
expected_first_check: assertString(row.expected_first_check, `cases[${index}].expected_first_check`),
must_include_limitations: assertBoolean(row.must_include_limitations, `cases[${index}].must_include_limitations`),
forbidden_leakage_tokens: assertStringArray(row.forbidden_leakage_tokens, `cases[${index}].forbidden_leakage_tokens`),
followup_seed_query: followupSeedQuery,
followup_expected_context_tokens: followupExpectedContextTokens,
notes: assertString(row.notes, `cases[${index}].notes`),
expected_annotations: {
domain_expected: annotationDomain,
route_expected: assertRouteExpected(annotations.route_expected, `cases[${index}].expected_annotations.route_expected`),
problem_unit_type_expected: annotationProblemType,
mechanism_class_expected: assertString(annotations.mechanism_class_expected, `cases[${index}].expected_annotations.mechanism_class_expected`),
evidence_expectation: assertString(annotations.evidence_expectation, `cases[${index}].expected_annotations.evidence_expectation`),
must_show_limitation: assertBoolean(annotations.must_show_limitation, `cases[${index}].expected_annotations.must_show_limitation`),
must_not_leak_internal: assertBoolean(annotations.must_not_leak_internal, `cases[${index}].expected_annotations.must_not_leak_internal`),
first_check_action_expected: assertString(annotations.first_check_action_expected, `cases[${index}].expected_annotations.first_check_action_expected`)
}
};
}
function validateP0EvalCorpus(input) {
if (!input || typeof input !== "object") {
throw new Error("Invalid P0 corpus: root object is required.");
}
const parsed = input;
const casesRaw = parsed.cases;
if (!Array.isArray(casesRaw)) {
throw new Error("Invalid P0 corpus: cases[] is required.");
}
const cases = casesRaw.map((item, index) => parseCase(item, index));
if (cases.length < 30 || cases.length > 150) {
throw new Error(`Invalid P0 corpus: expected 30..150 cases, got ${cases.length}.`);
}
const caseIds = assertStringArray(parsed.case_ids, "case_ids");
const declaredIds = [...caseIds].sort();
const actualIds = cases.map((item) => item.case_id).sort();
const idsMatch = declaredIds.length === actualIds.length && declaredIds.every((value, index) => value === actualIds[index]);
if (!idsMatch) {
throw new Error("Invalid P0 corpus: case_ids[] does not match cases[].");
}
const scenarioCount = Number(parsed.scenario_count ?? 0);
if (!Number.isFinite(scenarioCount) || scenarioCount !== cases.length) {
throw new Error("Invalid P0 corpus: scenario_count must match cases.length.");
}
const perDomain = cases.reduce((acc, item) => {
acc[item.domain] += 1;
return acc;
}, {
settlements_60_62: 0,
vat_document_register_book: 0,
month_close_costs_20_44: 0
});
for (const [domain, count] of Object.entries(perDomain)) {
if (count < 10) {
throw new Error(`Invalid P0 corpus: domain '${domain}' must contain at least 10 cases, got ${count}.`);
}
}
return {
suite_id: assertString(parsed.suite_id, "suite_id"),
suite_version: assertString(parsed.suite_version, "suite_version"),
schema_version: assertString(parsed.schema_version, "schema_version"),
scenario_count: scenarioCount,
case_ids: caseIds,
cases
};
}
function normalizeExpectedRoutes(value) {
return Array.isArray(value) ? value : [value];
}