112 lines
4.2 KiB
Python
112 lines
4.2 KiB
Python
from __future__ import annotations
|
|
|
|
from dataclasses import asdict, dataclass
|
|
from typing import Any
|
|
|
|
from router.query_classifier import RouteDecisionFlags
|
|
|
|
|
|
@dataclass
|
|
class StoreSufficiencyResult:
|
|
canonical_sufficient: bool
|
|
feature_sufficient: bool
|
|
risk_sufficient: bool
|
|
freshness_ok: bool
|
|
aggregate_level_ok: bool
|
|
ranking_ready: bool
|
|
explanation_ready: bool
|
|
reason_codes: list[str]
|
|
|
|
def to_dict(self) -> dict[str, Any]:
|
|
return asdict(self)
|
|
|
|
|
|
def _to_float(value: Any, default: float = 0.0) -> float:
|
|
try:
|
|
return float(value)
|
|
except (TypeError, ValueError):
|
|
return default
|
|
|
|
|
|
def check_store_sufficiency(
|
|
question_shape: RouteDecisionFlags,
|
|
store_metadata: dict[str, Any],
|
|
) -> StoreSufficiencyResult:
|
|
reason_codes: list[str] = []
|
|
|
|
freshness_threshold_hours = _to_float(store_metadata.get("freshness_threshold_hours", 6.0), default=6.0)
|
|
refresh_age = _to_float(store_metadata.get("refresh_age_hours", 0.0), default=0.0)
|
|
feature_age = _to_float(store_metadata.get("feature_age_hours", refresh_age), default=refresh_age)
|
|
risk_age = _to_float(store_metadata.get("risk_age_hours", refresh_age), default=refresh_age)
|
|
|
|
canonical_semantic_coverage = _to_float(store_metadata.get("canonical_semantic_coverage", 0.0), default=0.0)
|
|
canonical_relation_types = int(store_metadata.get("canonical_relation_types", 0))
|
|
canonical_links_total = int(store_metadata.get("canonical_links_total", 0))
|
|
canonical_entities_total = int(store_metadata.get("canonical_entities_total", 0))
|
|
|
|
feature_ready = bool(store_metadata.get("feature_ready", False))
|
|
risk_ready = bool(store_metadata.get("risk_ready", False))
|
|
ranking_ready = bool(store_metadata.get("ranking_ready", False))
|
|
aggregate_ready = bool(store_metadata.get("aggregate_ready", False))
|
|
|
|
freshness_ok = refresh_age <= freshness_threshold_hours
|
|
if question_shape.freshness_sensitive and not freshness_ok:
|
|
reason_codes.append("refresh_stale")
|
|
|
|
canonical_sufficient = (
|
|
canonical_entities_total > 0
|
|
and canonical_links_total > 0
|
|
and canonical_semantic_coverage >= 0.85
|
|
and canonical_relation_types >= 10
|
|
and (freshness_ok or not question_shape.freshness_sensitive)
|
|
)
|
|
if not canonical_sufficient:
|
|
reason_codes.append("canonical_not_sufficient")
|
|
|
|
feature_sufficient = feature_ready and (
|
|
feature_age <= freshness_threshold_hours or not question_shape.freshness_sensitive
|
|
)
|
|
if not feature_sufficient and question_shape.needs_anomaly_summary:
|
|
reason_codes.append("feature_not_sufficient")
|
|
|
|
risk_sufficient = risk_ready and (
|
|
risk_age <= freshness_threshold_hours or not question_shape.freshness_sensitive
|
|
)
|
|
if not risk_sufficient and (question_shape.needs_anomaly_summary or question_shape.needs_ranking):
|
|
reason_codes.append("risk_not_sufficient")
|
|
|
|
if question_shape.needs_ranking:
|
|
if not ranking_ready:
|
|
reason_codes.append("ranking_not_ready")
|
|
aggregate_level_ok = aggregate_ready and (
|
|
(not question_shape.needs_ranking or ranking_ready)
|
|
) and question_shape.precomputed_aggregate_available
|
|
if not aggregate_level_ok and (
|
|
question_shape.needs_full_period_aggregation
|
|
or question_shape.needs_ranking
|
|
or question_shape.needs_anomaly_summary
|
|
):
|
|
reason_codes.append("aggregate_not_sufficient")
|
|
|
|
explanation_ready = (
|
|
canonical_sufficient
|
|
and canonical_semantic_coverage >= 0.90
|
|
and canonical_relation_types >= 20
|
|
and not question_shape.needs_runtime_truth
|
|
and not (question_shape.needs_cross_entity_join and question_shape.needs_causal_chain)
|
|
)
|
|
if not explanation_ready and (question_shape.needs_causal_chain or question_shape.needs_cross_entity_join):
|
|
reason_codes.append("explanation_not_ready")
|
|
|
|
return StoreSufficiencyResult(
|
|
canonical_sufficient=canonical_sufficient,
|
|
feature_sufficient=feature_sufficient,
|
|
risk_sufficient=risk_sufficient,
|
|
freshness_ok=freshness_ok,
|
|
aggregate_level_ok=aggregate_level_ok,
|
|
ranking_ready=ranking_ready,
|
|
explanation_ready=explanation_ready,
|
|
reason_codes=reason_codes,
|
|
)
|
|
|