# ============================================================ # SPID — extraction interface & record schema # ============================================================ from __future__ import annotations import hashlib from dataclasses import dataclass, field, asdict from typing import Protocol @dataclass class ProjectMention: """A single project signal extracted from one filing section. Mentions are the atomic unit; `resolve.py` merges many mentions (across filings & years) into a single Project with a timeline. """ mention_id: str accession_number: str cik: str section_id: str section_name: str project_name: str project_type: str description: str objective: str = "" amount_usd: float | None = None amount_raw: str | None = None locations: list[str] = field(default_factory=list) technologies: list[str] = field(default_factory=list) partners: list[str] = field(default_factory=list) suppliers: list[str] = field(default_factory=list) benefits: list[str] = field(default_factory=list) risks: list[str] = field(default_factory=list) years: list[int] = field(default_factory=list) status: str = "mentioned" confidence: float = 0.0 backend: str = "rules" def to_row(self) -> dict: d = asdict(self) # store list fields as JSON-friendly via duckdb list[str] return d @staticmethod def make_id(accession: str, section_id: str, ptype: str, anchor: str) -> str: h = hashlib.sha1(f"{accession}|{section_id}|{ptype}|{anchor}".encode()).hexdigest() return h[:20] class Extractor(Protocol): name: str def extract(self, *, text: str, accession_number: str, cik: str, section_id: str, section_name: str) -> list[ProjectMention]: ... def get_extractor(backend: str): """Factory. `backend` in {'rules','llm'}.""" backend = (backend or "rules").lower() if backend == "llm": from spid.extract.llm import LLMExtractor return LLMExtractor() from spid.extract.rules import RuleExtractor return RuleExtractor()