feat: Intelligence Pipeline v3 — full implementation
Multi-stage evidence-grounded inference architecture replacing the monolithic 9B model extraction pipeline. CPU-first specialist services handle routine extraction while the 9B vLLM model is preserved for semantic adjudication of ambiguous cases. Key components: - Capability-aware inference gateway (OpenAI-compatible + Ollama) - Endpoint registry with DB migrations and REST API - Sentence-aware document segmenter (property tests) - Deterministic financial parsing with offset integrity - Symbol resolution with ambiguity detection - Specialist service (GLiNER2, dynamic batching, K8s deployment) - Company-specific sentiment (FinBERT, calibration) - Retrieval-based novelty and duplicate detection - Confidence calibration pipeline - Deterministic routing engine (property tests) - 9B adjudication layer with VRAM gating - Stock-specific impact model (features, labels, baseline, trained) - Pipeline orchestrator (state machine, queues, leases, feature flags) - Bounded parallelism (async workers, semaphore, load shedding) - Observability (tracing, metrics, alerts) - Compatibility adapter (v3→v2 golden mapping tests) - Shadow/canary promotion framework - Active learning and fine-tuning pipeline Test results: 1,161 tests pass, ruff lint clean. All 282 spec tasks completed.
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
"""Data models for evidence verification results and rejected candidates.
|
||||
|
||||
Provides structured types for offset verification, entity-evidence association,
|
||||
numeric consistency checks, rejected-candidate storage, and verification reports.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from enum import Enum
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
|
||||
class RejectionReason(str, Enum):
|
||||
"""Structured reason codes for candidate rejection during verification."""
|
||||
|
||||
INVALID_OFFSET = "invalid_offset"
|
||||
TEXT_MISMATCH = "text_mismatch"
|
||||
ENTITY_NOT_IN_EVIDENCE = "entity_not_in_evidence"
|
||||
NUMERIC_INCONSISTENCY = "numeric_inconsistency"
|
||||
UNSUPPORTED_CLAIM = "unsupported_claim"
|
||||
SCHEMA_VIOLATION = "schema_violation"
|
||||
CONFIDENCE_BELOW_THRESHOLD = "confidence_below_threshold"
|
||||
|
||||
|
||||
class OffsetVerification(BaseModel):
|
||||
"""Result of verifying a single evidence span's offsets against source text."""
|
||||
|
||||
span_id: str = Field(description="ID of the evidence span being verified.")
|
||||
valid: bool = Field(description="Whether the span text matches source at offsets.")
|
||||
reason: str | None = Field(
|
||||
default=None,
|
||||
description="Explanation when invalid (e.g., 'text mismatch at offset 42').",
|
||||
)
|
||||
|
||||
|
||||
class AssociationVerification(BaseModel):
|
||||
"""Result of verifying that an entity appears in at least one linked evidence span."""
|
||||
|
||||
entity_id: str = Field(description="ID of the entity being verified.")
|
||||
valid: bool = Field(description="Whether entity text was found in any linked span.")
|
||||
reason: str | None = Field(
|
||||
default=None,
|
||||
description="Explanation when invalid (e.g., 'entity text not in any linked span').",
|
||||
)
|
||||
|
||||
|
||||
class NumericVerification(BaseModel):
|
||||
"""Result of verifying a numeric fact against its evidence spans."""
|
||||
|
||||
fact_id: str = Field(description="ID of the numeric fact being verified.")
|
||||
valid: bool = Field(description="Whether the numeric value was found in evidence text.")
|
||||
found_value: str | None = Field(
|
||||
default=None,
|
||||
description="The value found in the evidence text, if any.",
|
||||
)
|
||||
expected_value: str = Field(description="The expected normalized value from the fact.")
|
||||
reason: str | None = Field(
|
||||
default=None,
|
||||
description="Explanation when invalid (e.g., 'value 3.14 not found in evidence').",
|
||||
)
|
||||
|
||||
|
||||
class RejectedCandidate(BaseModel):
|
||||
"""A candidate that was rejected during verification, stored for audit and learning."""
|
||||
|
||||
candidate_type: str = Field(
|
||||
description="Type of candidate: entity, fact, event, relation, sentiment."
|
||||
)
|
||||
candidate_data: dict = Field(
|
||||
description="Serialized candidate data for audit trail."
|
||||
)
|
||||
rejection_reason: RejectionReason = Field(
|
||||
description="Structured reason code for rejection."
|
||||
)
|
||||
stage: str = Field(
|
||||
description="Pipeline stage where rejection occurred (e.g., 'offset_verification')."
|
||||
)
|
||||
timestamp: datetime = Field(
|
||||
default_factory=lambda: datetime.now(tz=timezone.utc),
|
||||
description="When the rejection was recorded.",
|
||||
)
|
||||
|
||||
|
||||
class VerificationReport(BaseModel):
|
||||
"""Summary report from a full verification pass over extraction candidates."""
|
||||
|
||||
total_candidates: int = Field(ge=0, description="Total candidates evaluated.")
|
||||
verified: int = Field(ge=0, description="Candidates that passed verification.")
|
||||
rejected: int = Field(ge=0, description="Candidates that failed verification.")
|
||||
coverage_rate: float = Field(
|
||||
ge=0.0,
|
||||
le=1.0,
|
||||
description="Proportion of candidates with valid evidence support.",
|
||||
)
|
||||
rejection_breakdown: dict[str, int] = Field(
|
||||
default_factory=dict,
|
||||
description="Count of rejections per RejectionReason code.",
|
||||
)
|
||||
Reference in New Issue
Block a user