Initial import NDC_1C
This commit is contained in:
@@ -0,0 +1,335 @@
|
||||
import type {
|
||||
NoRouteReason,
|
||||
NormalizedPayload,
|
||||
NormalizedQueryV1,
|
||||
NormalizedQueryV2,
|
||||
NormalizedQueryV2_0_1,
|
||||
NormalizedQueryV2_0_2,
|
||||
RouteDecisionV2,
|
||||
RouteHintSummary,
|
||||
RouteHintSummaryV1,
|
||||
RouteHintSummaryV2,
|
||||
RouteStatus,
|
||||
SoftAssumption
|
||||
} from "../types/normalizer";
|
||||
|
||||
function toRouteHintSummaryV1(normalized: NormalizedQueryV1): RouteHintSummaryV1 {
|
||||
return {
|
||||
mode: "legacy_v1",
|
||||
intent_class: normalized.intent_class,
|
||||
route_hint: normalized.route_hint,
|
||||
confidence: normalized.confidence.route_hint,
|
||||
decision_flags: {
|
||||
needs_cross_entity_join: normalized.requires.needs_cross_entity_join,
|
||||
needs_causal_chain: normalized.requires.needs_causal_chain,
|
||||
needs_exact_object_trace: normalized.requires.needs_exact_object_trace,
|
||||
needs_ranking: normalized.requires.needs_ranking,
|
||||
needs_anomaly_summary: normalized.requires.needs_anomaly_summary,
|
||||
needs_runtime_truth: normalized.requires.needs_runtime_truth,
|
||||
needs_period_cut: normalized.requires.needs_period_cut,
|
||||
needs_evidence: normalized.requires.needs_evidence
|
||||
},
|
||||
period_scope: normalized.period_scope,
|
||||
entities: {
|
||||
domain_entities: normalized.domain_entities,
|
||||
accounts_mentioned: normalized.accounts_mentioned,
|
||||
documents_mentioned: normalized.documents_mentioned,
|
||||
registers_mentioned: normalized.registers_mentioned
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
type V2Family = NormalizedQueryV2 | NormalizedQueryV2_0_1 | NormalizedQueryV2_0_2;
|
||||
type V2FamilyFragment = V2Family["fragments"][number];
|
||||
|
||||
function reasonForNoRoute(noRouteReason: NoRouteReason | null | undefined): string {
|
||||
if (noRouteReason === "out_of_scope") {
|
||||
return "Fragment is out-of-scope for company-specific accounting contour.";
|
||||
}
|
||||
if (noRouteReason === "missing_mapping") {
|
||||
return "Fragment is in-scope but route mapping is currently missing.";
|
||||
}
|
||||
if (noRouteReason === "unsupported_fragment_type") {
|
||||
return "Fragment type is not supported by the current deterministic route map.";
|
||||
}
|
||||
return "Fragment requires clarification or is too underspecified for safe routing.";
|
||||
}
|
||||
|
||||
function explicitRouteStatus(fragment: V2FamilyFragment): RouteStatus | null {
|
||||
return "route_status" in fragment ? fragment.route_status : null;
|
||||
}
|
||||
|
||||
function explicitNoRouteReason(fragment: V2FamilyFragment): NoRouteReason | null {
|
||||
return "no_route_reason" in fragment ? fragment.no_route_reason : null;
|
||||
}
|
||||
|
||||
function executionReadiness(fragment: V2FamilyFragment): RouteDecisionV2["execution_readiness"] {
|
||||
return "execution_readiness" in fragment ? fragment.execution_readiness : null;
|
||||
}
|
||||
|
||||
function clarificationReason(fragment: V2FamilyFragment): string | null {
|
||||
return "clarification_reason" in fragment ? fragment.clarification_reason : null;
|
||||
}
|
||||
|
||||
function softAssumptions(fragment: V2FamilyFragment): SoftAssumption[] {
|
||||
return "soft_assumption_used" in fragment ? fragment.soft_assumption_used : [];
|
||||
}
|
||||
|
||||
function buildNoRouteDecision(fragment: V2FamilyFragment, noRouteReason: NoRouteReason | null): RouteDecisionV2 {
|
||||
return {
|
||||
fragment_id: fragment.fragment_id,
|
||||
domain_relevance: fragment.domain_relevance,
|
||||
business_scope: fragment.business_scope,
|
||||
candidate_labels: fragment.candidate_labels,
|
||||
decision_flags: fragment.flags,
|
||||
execution_readiness: executionReadiness(fragment),
|
||||
clarification_reason: clarificationReason(fragment),
|
||||
soft_assumption_used: softAssumptions(fragment),
|
||||
route_status: "no_route",
|
||||
no_route_reason: noRouteReason ?? "insufficient_specificity",
|
||||
route: "no_route",
|
||||
reason: reasonForNoRoute(noRouteReason)
|
||||
};
|
||||
}
|
||||
|
||||
function decideRouteForFragment(fragment: V2FamilyFragment): RouteDecisionV2 {
|
||||
const status = explicitRouteStatus(fragment);
|
||||
const noRouteReason = explicitNoRouteReason(fragment);
|
||||
const readiness = executionReadiness(fragment);
|
||||
const clarification = clarificationReason(fragment);
|
||||
const soft = softAssumptions(fragment);
|
||||
|
||||
if (status === "no_route") {
|
||||
return buildNoRouteDecision(fragment, noRouteReason);
|
||||
}
|
||||
|
||||
if (readiness === "needs_clarification" || readiness === "no_route") {
|
||||
return buildNoRouteDecision(fragment, noRouteReason ?? "insufficient_specificity");
|
||||
}
|
||||
|
||||
if (fragment.domain_relevance !== "in_scope") {
|
||||
return buildNoRouteDecision(fragment, "out_of_scope");
|
||||
}
|
||||
|
||||
if (fragment.flags.asks_for_exact_object_trace) {
|
||||
return {
|
||||
fragment_id: fragment.fragment_id,
|
||||
domain_relevance: fragment.domain_relevance,
|
||||
business_scope: fragment.business_scope,
|
||||
candidate_labels: fragment.candidate_labels,
|
||||
decision_flags: fragment.flags,
|
||||
execution_readiness: readiness,
|
||||
clarification_reason: clarification,
|
||||
soft_assumption_used: soft,
|
||||
route_status: "routed",
|
||||
no_route_reason: null,
|
||||
route: "live_mcp_drilldown",
|
||||
reason: "Exact object trace requested."
|
||||
};
|
||||
}
|
||||
|
||||
if (fragment.flags.asks_for_ranking_or_top || fragment.flags.asks_for_period_summary) {
|
||||
return {
|
||||
fragment_id: fragment.fragment_id,
|
||||
domain_relevance: fragment.domain_relevance,
|
||||
business_scope: fragment.business_scope,
|
||||
candidate_labels: fragment.candidate_labels,
|
||||
decision_flags: fragment.flags,
|
||||
execution_readiness: readiness,
|
||||
clarification_reason: clarification,
|
||||
soft_assumption_used: soft,
|
||||
route_status: "routed",
|
||||
no_route_reason: null,
|
||||
route: "batch_refresh_then_store",
|
||||
reason: "Ranking/summary semantics require batch analytical route."
|
||||
};
|
||||
}
|
||||
|
||||
if (fragment.flags.has_multi_entity_scope && fragment.flags.asks_for_chain_explanation) {
|
||||
return {
|
||||
fragment_id: fragment.fragment_id,
|
||||
domain_relevance: fragment.domain_relevance,
|
||||
business_scope: fragment.business_scope,
|
||||
candidate_labels: fragment.candidate_labels,
|
||||
decision_flags: fragment.flags,
|
||||
execution_readiness: readiness,
|
||||
clarification_reason: clarification,
|
||||
soft_assumption_used: soft,
|
||||
route_status: "routed",
|
||||
no_route_reason: null,
|
||||
route: "hybrid_store_plus_live",
|
||||
reason: "Multi-entity causal chain requested."
|
||||
};
|
||||
}
|
||||
|
||||
if (fragment.flags.asks_for_rule_check && !fragment.flags.asks_for_chain_explanation) {
|
||||
return {
|
||||
fragment_id: fragment.fragment_id,
|
||||
domain_relevance: fragment.domain_relevance,
|
||||
business_scope: fragment.business_scope,
|
||||
candidate_labels: fragment.candidate_labels,
|
||||
decision_flags: fragment.flags,
|
||||
execution_readiness: readiness,
|
||||
clarification_reason: clarification,
|
||||
soft_assumption_used: soft,
|
||||
route_status: "routed",
|
||||
no_route_reason: null,
|
||||
route: "store_feature_risk",
|
||||
reason: "Rule-control check without causal decomposition."
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
fragment.flags.asks_for_anomaly_scan &&
|
||||
!fragment.flags.asks_for_ranking_or_top &&
|
||||
!(fragment.flags.has_multi_entity_scope && fragment.flags.asks_for_chain_explanation)
|
||||
) {
|
||||
return {
|
||||
fragment_id: fragment.fragment_id,
|
||||
domain_relevance: fragment.domain_relevance,
|
||||
business_scope: fragment.business_scope,
|
||||
candidate_labels: fragment.candidate_labels,
|
||||
decision_flags: fragment.flags,
|
||||
execution_readiness: readiness,
|
||||
clarification_reason: clarification,
|
||||
soft_assumption_used: soft,
|
||||
route_status: "routed",
|
||||
no_route_reason: null,
|
||||
route: "store_feature_risk",
|
||||
reason: "Anomaly scan without heavy ranking or causal chain."
|
||||
};
|
||||
}
|
||||
|
||||
if (status === "routed") {
|
||||
return {
|
||||
fragment_id: fragment.fragment_id,
|
||||
domain_relevance: fragment.domain_relevance,
|
||||
business_scope: fragment.business_scope,
|
||||
candidate_labels: fragment.candidate_labels,
|
||||
decision_flags: fragment.flags,
|
||||
execution_readiness: readiness,
|
||||
clarification_reason: clarification,
|
||||
soft_assumption_used: soft,
|
||||
route_status: "routed",
|
||||
no_route_reason: null,
|
||||
route: "store_canonical",
|
||||
reason: "Routed fragment without deep analytical or causal signals."
|
||||
};
|
||||
}
|
||||
|
||||
return buildNoRouteDecision(fragment, "missing_mapping");
|
||||
}
|
||||
|
||||
function fallbackMessageFor(type: RouteHintSummaryV2["fallback"]["type"]): string | null {
|
||||
if (type === "out_of_scope") {
|
||||
return "Я работаю только с данными и бухгалтерским контуром текущей компании. Запрос вне доступной предметной области.";
|
||||
}
|
||||
if (type === "clarification") {
|
||||
return "Могу проверить это в контуре компании, но нужно уточнить период, документ, счет или участок учета.";
|
||||
}
|
||||
if (type === "partial") {
|
||||
return "Обработаю только часть запроса, которая относится к данным компании. Остальное выходит за пределы доступного контура.";
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function simulateDeterministicRouting(normalized: V2Family): RouteHintSummaryV2 {
|
||||
const decisions = normalized.fragments.map((fragment) => decideRouteForFragment(fragment));
|
||||
const inScopeCount = decisions.filter((item) => item.domain_relevance === "in_scope").length;
|
||||
const outOfScopeCount = decisions.filter((item) => item.domain_relevance === "out_of_scope").length;
|
||||
const routedInScopeCount = decisions.filter((item) => item.domain_relevance === "in_scope" && item.route !== "no_route").length;
|
||||
const clarificationInScopeCount = decisions.filter(
|
||||
(item) => item.domain_relevance === "in_scope" && item.execution_readiness === "needs_clarification"
|
||||
).length;
|
||||
const noRouteInScopeCount = decisions.filter((item) => item.domain_relevance === "in_scope" && item.route === "no_route").length;
|
||||
|
||||
let fallbackType: RouteHintSummaryV2["fallback"]["type"] = "none";
|
||||
if (!normalized.message_in_scope || inScopeCount === 0) {
|
||||
fallbackType = "out_of_scope";
|
||||
} else if (routedInScopeCount === 0 && clarificationInScopeCount > 0) {
|
||||
fallbackType = "clarification";
|
||||
} else if (routedInScopeCount === 0 && noRouteInScopeCount > 0) {
|
||||
fallbackType = "clarification";
|
||||
} else if ((inScopeCount > 0 && outOfScopeCount > 0) || (routedInScopeCount > 0 && noRouteInScopeCount > 0)) {
|
||||
fallbackType = "partial";
|
||||
}
|
||||
|
||||
return {
|
||||
mode: "deterministic_v2",
|
||||
message_in_scope: normalized.message_in_scope,
|
||||
scope_confidence: normalized.scope_confidence,
|
||||
planner: {
|
||||
total_fragments: normalized.fragments.length,
|
||||
in_scope_fragments: inScopeCount,
|
||||
out_of_scope_fragments: outOfScopeCount,
|
||||
discarded_fragments: normalized.discarded_fragments.length,
|
||||
contains_multiple_tasks: normalized.contains_multiple_tasks
|
||||
},
|
||||
decisions,
|
||||
fallback: {
|
||||
type: fallbackType,
|
||||
message: fallbackMessageFor(fallbackType)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function toRouteHintSummary(normalized: NormalizedPayload): RouteHintSummary {
|
||||
if (
|
||||
normalized.schema_version === "normalized_query_v2" ||
|
||||
normalized.schema_version === "normalized_query_v2_0_1" ||
|
||||
normalized.schema_version === "normalized_query_v2_0_2"
|
||||
) {
|
||||
return simulateDeterministicRouting(normalized);
|
||||
}
|
||||
return toRouteHintSummaryV1(normalized);
|
||||
}
|
||||
|
||||
export function toRouterInput(normalized: NormalizedPayload): Record<string, unknown> {
|
||||
if (
|
||||
normalized.schema_version === "normalized_query_v2" ||
|
||||
normalized.schema_version === "normalized_query_v2_0_1" ||
|
||||
normalized.schema_version === "normalized_query_v2_0_2"
|
||||
) {
|
||||
return {
|
||||
mode: "deterministic_v2",
|
||||
message_in_scope: normalized.message_in_scope,
|
||||
scope_confidence: normalized.scope_confidence,
|
||||
contains_multiple_tasks: normalized.contains_multiple_tasks,
|
||||
fragments: normalized.fragments.map((fragment) => ({
|
||||
fragment_id: fragment.fragment_id,
|
||||
domain_relevance: fragment.domain_relevance,
|
||||
business_scope: fragment.business_scope,
|
||||
execution_readiness: "execution_readiness" in fragment ? fragment.execution_readiness : null,
|
||||
clarification_reason: "clarification_reason" in fragment ? fragment.clarification_reason : null,
|
||||
soft_assumption_used: "soft_assumption_used" in fragment ? fragment.soft_assumption_used : [],
|
||||
route_status: "route_status" in fragment ? fragment.route_status : null,
|
||||
no_route_reason: "no_route_reason" in fragment ? fragment.no_route_reason : null,
|
||||
flags: fragment.flags,
|
||||
candidate_labels: fragment.candidate_labels,
|
||||
confidence: fragment.confidence
|
||||
}))
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
mode: "legacy_v1",
|
||||
intent_class: normalized.intent_class,
|
||||
decision_flags: {
|
||||
needs_cross_entity_join: normalized.requires.needs_cross_entity_join,
|
||||
needs_causal_chain: normalized.requires.needs_causal_chain,
|
||||
needs_exact_object_trace: normalized.requires.needs_exact_object_trace,
|
||||
needs_ranking: normalized.requires.needs_ranking,
|
||||
needs_anomaly_summary: normalized.requires.needs_anomaly_summary,
|
||||
needs_runtime_truth: normalized.requires.needs_runtime_truth
|
||||
},
|
||||
route_hint: normalized.route_hint,
|
||||
confidence: normalized.confidence.overall,
|
||||
entities: {
|
||||
domain_entities: normalized.domain_entities,
|
||||
accounts_mentioned: normalized.accounts_mentioned,
|
||||
documents_mentioned: normalized.documents_mentioned,
|
||||
registers_mentioned: normalized.registers_mentioned
|
||||
},
|
||||
period_scope: normalized.period_scope
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user