Spaces:
Sleeping
Sleeping
| from __future__ import annotations | |
| import csv | |
| from collections.abc import Mapping | |
| from pathlib import Path | |
| from typing import TYPE_CHECKING, Final, final | |
| from redstack.adapters.candidate_jsonl import JsonlCandidateSourceAdapter | |
| from redstack.adapters.entropy import OfflineEntropy | |
| from redstack.adapters.st_embedder import SentenceTransformerEmbeddingAdapter | |
| from redstack.config.loader import ( | |
| load_eligibility_rules, | |
| load_jd_anchors, | |
| load_lexicon_seed, | |
| ) | |
| from redstack.domain.errors import ArtifactContractError | |
| from redstack.features.registry import FEATURE_REGISTRY | |
| from redstack.pipelines.offline.build_artifact_store import BuildArtifactStore | |
| from redstack.pipelines.offline.context import OfflinePipelineContext | |
| from redstack.pipelines.offline.graph import ( | |
| OFFLINE_EXECUTION_GRAPH, | |
| OfflineExecutionGraph, | |
| StageNode, | |
| ) | |
| from redstack.pipelines.offline.pipeline import OfflinePipeline, OfflinePipelineReport | |
| from redstack.pipelines.offline.registry import ( | |
| OFFLINE_ARTIFACT_REGISTRY, | |
| OfflineArtifactRegistry, | |
| ) | |
| from redstack.pipelines.offline.runner import StageCallable, StageReceipt, StageResult | |
| from redstack.pipelines.offline.stages import OfflineStage | |
| from redstack.pipelines.offline.stages._labeling_seed import GoldLabelSeed, ReviewTag | |
| from redstack.pipelines.offline.stages.archetype_discovery import ( | |
| ArchetypeDiscoveryStage, | |
| ) | |
| from redstack.pipelines.offline.stages.behavioral_calib import ( | |
| BehavioralCalibrationStage, | |
| ) | |
| from redstack.pipelines.offline.stages.census import CensusStage | |
| from redstack.pipelines.offline.stages.embedding_gen import ( | |
| AnchorEmbeddingStage, | |
| CandidateEmbeddingStage, | |
| CareerEmbeddingStage, | |
| EmbeddingManifestStage, | |
| ) | |
| from redstack.pipelines.offline.stages.feature_importance import ( | |
| FeatureImportanceStage, | |
| ) | |
| from redstack.pipelines.offline.stages.feature_snapshot import FeatureSnapshotStage | |
| from redstack.pipelines.offline.stages.honeypot_discovery import ( | |
| HoneypotDiscoveryStage, | |
| ) | |
| from redstack.pipelines.offline.stages.jd_concepts import JdConceptStage | |
| from redstack.pipelines.offline.stages.labeling import LabelingStage | |
| from redstack.pipelines.offline.stages.lexicon_discovery import LexiconDiscoveryStage | |
| from redstack.pipelines.offline.stages.normalization import NormalizationStage | |
| from redstack.pipelines.offline.stages.packaging import PackagingStage | |
| from redstack.pipelines.offline.stages.ranking_calib import RankingCalibrationStage | |
| from redstack.pipelines.offline.stages.reasoning_templates import ( | |
| ReasoningTemplateStage, | |
| ) | |
| from redstack.pipelines.offline.stages.reproducibility import ReproducibilityStage | |
| from redstack.pipelines.offline.stages.risk_calib import RiskCalibrationStage | |
| from redstack.pipelines.offline.stages.validation import ValidationStage | |
| from redstack.pipelines.offline.stages.vocab_expansion import VocabExpansionStage | |
| from redstack.pipelines.offline.stages.weight_search import WeightSearchStage | |
| if TYPE_CHECKING: | |
| from redstack.config.schema import RedstackConfig | |
| __all__: tuple[str, ...] = ( | |
| "run_offline_build", | |
| "run_offline_build_with_locked_heuristics", | |
| ) | |
| #: Default offline encode batch size handed to the sentence-transformers adapter. | |
| _ST_BATCH_SIZE: Final[int] = 32 | |
| class GoldLabelSeedMissingError(ArtifactContractError): | |
| """O8 needs the human-curated review workspace output; none is committed.""" | |
| #: Required ``golden_labels.csv`` header. ``cited_features`` is a | |
| #: ``;``-separated list within its cell (CSV has no native list type). | |
| _REVIEW_CSV_FIELDS: Final[tuple[str, ...]] = ( | |
| "candidate_id", | |
| "tier", | |
| "reasoning", | |
| "reviewer", | |
| "archetype_id", | |
| "is_honeypot_suspect", | |
| "is_borderline", | |
| "cited_features", | |
| ) | |
| _CSV_TRUE: Final[frozenset[str]] = frozenset({"true", "1", "yes"}) | |
| def _parse_review_row(row: Mapping[str, str], *, line_no: int) -> ReviewTag: | |
| """Parse one ``golden_labels.csv`` data row into a ``ReviewTag``. | |
| Raises: | |
| GoldLabelSeedMissingError: the row is malformed (bad int, empty | |
| required field) — surfaced with its 1-based data line for the | |
| reviewer to fix. | |
| """ | |
| try: | |
| archetype_raw = row.get("archetype_id", "").strip() | |
| return ReviewTag( | |
| candidate_id=row["candidate_id"].strip(), | |
| tier=int(row["tier"]), | |
| reasoning=row["reasoning"], | |
| reviewer=row["reviewer"].strip(), | |
| archetype_id=int(archetype_raw) if archetype_raw else None, | |
| is_honeypot_suspect=row.get("is_honeypot_suspect", "").strip().lower() | |
| in _CSV_TRUE, | |
| is_borderline=row.get("is_borderline", "").strip().lower() in _CSV_TRUE, | |
| cited_features=tuple( | |
| f.strip() | |
| for f in row.get("cited_features", "").split(";") | |
| if f.strip() | |
| ), | |
| ) | |
| except (KeyError, ValueError) as exc: | |
| raise GoldLabelSeedMissingError( | |
| f"golden_labels.csv data row {line_no} is malformed: {exc}" | |
| ) from exc | |
| def _load_gold_label_seed(path: Path) -> GoldLabelSeed: | |
| """Load the committed Offline Pipeline Part 7 review tags from ``path``. | |
| ``path`` is a CSV with header :data:`_REVIEW_CSV_FIELDS` (one row per | |
| reviewer tag); ``archetype_id`` empty means ``None``, ``is_honeypot_suspect`` | |
| / ``is_borderline`` are ``true``/``false``-style strings, and | |
| ``cited_features`` is a ``;``-separated list. | |
| Raises: | |
| GoldLabelSeedMissingError: ``path`` does not exist, or a data row is | |
| malformed. A missing file is *not* a code bug — O8 is | |
| human-in-the-loop labeling; the seed is a workspace output | |
| authored by reviewers, never synthesized here. | |
| """ | |
| if not path.is_file(): | |
| raise GoldLabelSeedMissingError( | |
| f"O8 requires committed gold labels at {path} (Offline Pipeline " | |
| "Part 7: the human-in-the-loop labeling workspace's output) — " | |
| "none found. This is human-curated ground truth and cannot be " | |
| "generated by the pipeline; a reviewer must commit it first." | |
| ) | |
| with path.open(encoding="utf-8", newline="") as handle: | |
| reader = csv.DictReader(handle) | |
| tags = tuple( | |
| _parse_review_row(row, line_no=line_no) | |
| for line_no, row in enumerate(reader, start=2) | |
| ) | |
| if not tags: | |
| raise GoldLabelSeedMissingError(f"golden_labels.csv at {path} has no data rows") | |
| return GoldLabelSeed(tags=tags) | |
| class _LazyLabelingStage: | |
| """Defers O8's :class:`GoldLabelSeed` load until the stage actually runs. | |
| Lets ``plan() → run()`` reach every stage that does not depend on O8 | |
| (O0-O7, O13*, O14) and fail only at O8 itself, with a clear message, | |
| instead of refusing to even wire the pipeline when no seed is committed. | |
| """ | |
| stage_id: Final[str] = "O8" | |
| stage_version: Final[str] = LabelingStage.stage_version | |
| def __init__( | |
| self, | |
| golden_labels_path: Path, | |
| registry: OfflineArtifactRegistry = OFFLINE_ARTIFACT_REGISTRY, | |
| ) -> None: | |
| self._golden_labels_path = golden_labels_path | |
| self._registry = registry | |
| def __call__( | |
| self, | |
| ctx: OfflinePipelineContext, | |
| upstream: Mapping[str, StageReceipt], | |
| ) -> StageResult: | |
| seed = _load_gold_label_seed(self._golden_labels_path) | |
| stage = LabelingStage(seed=seed, registry=self._registry) | |
| return stage(ctx, upstream) | |
| def _build_stages( | |
| *, configs_root: Path, golden_labels_path: Path | |
| ) -> dict[str, StageCallable]: | |
| """Wire every O0-O18 stage callable, loading the authoring seeds it needs.""" | |
| lexicon_seed = load_lexicon_seed(configs_root) | |
| jd_anchors = load_jd_anchors(configs_root) | |
| eligibility_rules = load_eligibility_rules(configs_root) | |
| stages: tuple[StageCallable, ...] = ( | |
| CensusStage(), | |
| NormalizationStage(), | |
| ValidationStage(), | |
| HoneypotDiscoveryStage(), | |
| LexiconDiscoveryStage(seed=lexicon_seed), | |
| VocabExpansionStage(), | |
| JdConceptStage(anchors=jd_anchors, eligibility=eligibility_rules), | |
| ArchetypeDiscoveryStage(), | |
| _LazyLabelingStage(golden_labels_path=golden_labels_path), | |
| WeightSearchStage(), | |
| FeatureImportanceStage(), | |
| BehavioralCalibrationStage(), | |
| RiskCalibrationStage(), | |
| CandidateEmbeddingStage(), | |
| AnchorEmbeddingStage(), | |
| CareerEmbeddingStage(), | |
| EmbeddingManifestStage(), | |
| FeatureSnapshotStage(), | |
| RankingCalibrationStage(), | |
| ReasoningTemplateStage(), | |
| PackagingStage(), | |
| ReproducibilityStage(), | |
| ) | |
| return {stage.stage_id: stage for stage in stages} | |
| def _build_context( | |
| config: RedstackConfig, *, code_version: str | |
| ) -> OfflinePipelineContext: | |
| """Bind every adapter and build the immutable offline build context. | |
| Raises: | |
| ValueError: ``config.offline`` is absent (wrong run mode). | |
| """ | |
| offline = config.offline | |
| if offline is None: | |
| msg = "an offline build requires a config with an 'offline' runtime block" | |
| raise ValueError(msg) | |
| candidates_path = Path(config.paths.candidates_path).resolve() | |
| artifacts_root = Path(config.paths.artifacts_root).resolve() | |
| candidate_source = JsonlCandidateSourceAdapter(candidates_path) | |
| embedding_model = SentenceTransformerEmbeddingAdapter( | |
| offline.st_model_id, | |
| revision=offline.st_model_revision, | |
| batch_size=_ST_BATCH_SIZE, | |
| ) | |
| entropy = OfflineEntropy(seed=offline.seed, as_of=offline.as_of.date()) | |
| artifact_store = BuildArtifactStore(artifacts_root, OFFLINE_ARTIFACT_REGISTRY) | |
| return OfflinePipelineContext.build( | |
| config=config, | |
| candidate_source=candidate_source, | |
| embedding_model=embedding_model, | |
| artifact_store=artifact_store, | |
| entropy=entropy, | |
| feature_registry=FEATURE_REGISTRY, | |
| code_version=code_version, | |
| ) | |
| def run_offline_build( | |
| config: RedstackConfig, | |
| *, | |
| configs_root: Path, | |
| code_version: str, | |
| force: tuple[str, ...] | None = None, | |
| ) -> OfflinePipelineReport: | |
| """Bind adapters, build the context, wire O0-O18, and execute the build. | |
| Args: | |
| config: The fully-composed, validated offline ``RedstackConfig``. | |
| configs_root: Path to the ``configs/`` directory (authoring seeds). | |
| code_version: The build's code provenance, recorded into the report. | |
| force: Stage ids to recompute regardless of checkpoint freshness. | |
| Returns: | |
| The terminal :class:`OfflinePipelineReport`. | |
| Raises: | |
| ValueError: ``config.offline`` is absent (wrong run mode). | |
| GoldLabelSeedMissingError: O8 is reached with no committed gold labels. | |
| """ | |
| ctx = _build_context(config, code_version=code_version) | |
| golden_labels_path = Path(config.paths.golden_labels_path).resolve() | |
| stages = _build_stages( | |
| configs_root=configs_root, golden_labels_path=golden_labels_path | |
| ) | |
| pipeline = OfflinePipeline(stages=stages) | |
| return pipeline.execute(ctx, force=force) | |
| # --------------------------------------------------------------------------- # | |
| # Locked-heuristics bypass: no gold labels, fixed expert-authored weights. # | |
| # --------------------------------------------------------------------------- # | |
| class _FixedScoringWeightsStage(OfflineStage): | |
| """O9 substitute — package given component weights, no calibration search.""" | |
| stage_id = "O9" | |
| stage_version = "locked-1.0" | |
| def __init__( | |
| self, | |
| weights: Mapping[str, float], | |
| neutral_prior: float, | |
| registry: OfflineArtifactRegistry = OFFLINE_ARTIFACT_REGISTRY, | |
| ) -> None: | |
| super().__init__(registry) | |
| self._weights = dict(weights) | |
| self._neutral_prior = neutral_prior | |
| def _run( | |
| self, | |
| ctx: OfflinePipelineContext, | |
| upstream: Mapping[str, StageReceipt], | |
| ) -> StageResult: | |
| payload: dict[str, object] = { | |
| "layout_version": ctx.layout_version, | |
| "weights": dict(sorted(self._weights.items())), | |
| "neutral_prior": self._neutral_prior, | |
| "calibrated_by": "locked-heuristics (no gold-label search)", | |
| } | |
| artifact = self.emit_yaml(ctx, "scoring_weights", payload) | |
| metrics: dict[str, object] = { | |
| "component_count": len(self._weights), | |
| "neutral_prior": self._neutral_prior, | |
| } | |
| return StageResult(artifacts=(artifact,), metrics=metrics) | |
| class _UniformFeatureImportanceStage(OfflineStage): | |
| """O10 substitute — flat per-feature importance, no permutation search.""" | |
| stage_id = "O10" | |
| stage_version = "locked-1.0" | |
| def _run( | |
| self, | |
| ctx: OfflinePipelineContext, | |
| upstream: Mapping[str, StageReceipt], | |
| ) -> StageResult: | |
| importances = {str(d.feature_id): 1.0 for d in ctx.feature_registry.definitions} | |
| payload: dict[str, object] = { | |
| "importances": importances, | |
| "calibrated_by": "locked-heuristics (uniform, no permutation search)", | |
| } | |
| artifact = self.emit_json(ctx, "feature_importance", payload) | |
| metrics: dict[str, object] = {"features_scored": len(importances)} | |
| return StageResult(artifacts=(artifact,), metrics=metrics) | |
| def _locked_heuristics_graph() -> OfflineExecutionGraph: | |
| """The full Part 11 DAG with O8 dropped and its dependents' edges pruned. | |
| O9/O16/O17 are the only nodes whose ``depends_on`` names "O8"; every other | |
| edge is untouched, so O11/O12 (already O8-independent) and O15/O16 (already | |
| gold-label-optional in their real implementations) run exactly as designed. | |
| """ | |
| nodes: list[StageNode] = [] | |
| for node in OFFLINE_EXECUTION_GRAPH.nodes: | |
| if node.stage_id == "O8": | |
| continue | |
| if node.stage_id == "O9": | |
| nodes.append(StageNode("O9", (), critical=node.critical)) | |
| continue | |
| if node.stage_id in ("O16", "O17"): | |
| pruned = tuple(dep for dep in node.depends_on if dep != "O8") | |
| nodes.append(StageNode(node.stage_id, pruned, critical=node.critical)) | |
| continue | |
| nodes.append(node) | |
| return OfflineExecutionGraph(nodes=tuple(nodes)) | |
| def run_offline_build_with_locked_heuristics( | |
| config: RedstackConfig, | |
| *, | |
| configs_root: Path, | |
| code_version: str, | |
| component_weights: Mapping[str, float], | |
| neutral_prior: float, | |
| force: tuple[str, ...] | None = None, | |
| ) -> OfflinePipelineReport: | |
| """Run the build on a reduced graph that drops O8 and its O9/O10 dependents. | |
| Substitutes :class:`_FixedScoringWeightsStage` / | |
| :class:`_UniformFeatureImportanceStage` for O9/O10 so O11/O12/O15/O16/O17/O18 | |
| see a complete, real artifact set and O17 produces a genuine | |
| ``MANIFEST.json`` — without any committed gold labels. | |
| Args: | |
| config: The fully-composed, validated offline ``RedstackConfig``. | |
| configs_root: Path to the ``configs/`` directory (authoring seeds). | |
| code_version: The build's code provenance, recorded into the report. | |
| component_weights: One weight per ``domain.enums.ScoreComponent`` value. | |
| neutral_prior: The online ``ScoringPolicy.neutral_prior`` fallback. | |
| force: Stage ids to recompute regardless of checkpoint freshness. | |
| Returns: | |
| The terminal :class:`OfflinePipelineReport`. | |
| Raises: | |
| ValueError: ``config.offline`` is absent (wrong run mode). | |
| """ | |
| ctx = _build_context(config, code_version=code_version) | |
| lexicon_seed = load_lexicon_seed(configs_root) | |
| jd_anchors = load_jd_anchors(configs_root) | |
| eligibility_rules = load_eligibility_rules(configs_root) | |
| stages: tuple[StageCallable, ...] = ( | |
| CensusStage(), | |
| NormalizationStage(), | |
| ValidationStage(), | |
| HoneypotDiscoveryStage(), | |
| LexiconDiscoveryStage(seed=lexicon_seed), | |
| VocabExpansionStage(), | |
| JdConceptStage(anchors=jd_anchors, eligibility=eligibility_rules), | |
| ArchetypeDiscoveryStage(), | |
| _FixedScoringWeightsStage(component_weights, neutral_prior), | |
| _UniformFeatureImportanceStage(), | |
| BehavioralCalibrationStage(), | |
| RiskCalibrationStage(), | |
| CandidateEmbeddingStage(), | |
| AnchorEmbeddingStage(), | |
| CareerEmbeddingStage(), | |
| EmbeddingManifestStage(), | |
| FeatureSnapshotStage(), | |
| RankingCalibrationStage(), | |
| ReasoningTemplateStage(), | |
| PackagingStage(), | |
| ReproducibilityStage(), | |
| ) | |
| pipeline = OfflinePipeline( | |
| stages={stage.stage_id: stage for stage in stages}, | |
| graph=_locked_heuristics_graph(), | |
| ) | |
| return pipeline.execute(ctx, force=force) | |