Cai
2026-08-24 156ea25b402479f0abc54c558bbf87f9eaaa0422
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
from __future__ import annotations
 
import hashlib
import importlib
import inspect
import json
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Any
 
 
EXPECTED = {
    "__init__.py": (257, "F39479C1A7B7F40A1A71D5EDE8A287F4E701B4F1473D674F04A2E1BA1B522D29"),
    "core.py": (53165, "6AB34917A8B136F684EF655995428ACB5936050E1DA36B7B2371941A3E5A5F8F"),
    "cli.py": (1811, "260692D62C8983B623483E0AA29C763869E30D40952700D46C636357A7376A1E"),
    "source_registry.json": (4958, "0895D4F0559A1353787059D506DF16572E6818CD847C21042FE1D1106AA854E0"),
}
 
 
class V1ContractError(RuntimeError):
    pass
 
 
def _sha(path: Path) -> str:
    return hashlib.sha256(path.read_bytes()).hexdigest().upper()
 
 
@dataclass(frozen=True)
class V1Bridge:
    module: Any
    package_dir: Path
    registry_path: Path
    fingerprints: dict[str, dict[str, Any]]
 
    @classmethod
    def load(cls) -> "V1Bridge":
        repo = Path(__file__).resolve().parents[3]
        parent = repo / "dev" / "ana-dev"
        package = parent / "stock_valuation_pipeline"
        fingerprints: dict[str, dict[str, Any]] = {}
        for name, (size, digest) in EXPECTED.items():
            path = package / name
            if not path.is_file() or path.stat().st_size != size or _sha(path) != digest:
                raise V1ContractError(f"V1 合同漂移:{path}")
            fingerprints[name] = {"bytes": size, "sha256": digest}
        parent_text = str(parent)
        if parent_text not in sys.path:
            sys.path.insert(0, parent_text)
        module = importlib.import_module("stock_valuation_pipeline")
        module_file = Path(module.__file__).resolve()
        if package.resolve() not in module_file.parents or module.__version__ != "1.0.0":
            raise V1ContractError("V1 模块加载路径或版本不匹配")
        expected_signatures = {
            "run_pipeline": "(snapshot_path: 'Path', output_dir: 'Path', source_registry_path: 'Path', *, force: 'bool' = False) -> 'dict[str, Any]'",
            "compute_valuation": "(snapshot: 'dict[str, Any]', registry: 'dict[str, Any]') -> 'dict[str, Any]'",
        }
        for name, expected in expected_signatures.items():
            actual = str(inspect.signature(getattr(module, name)))
            if actual != expected:
                raise V1ContractError(f"V1 {name} 签名漂移:{actual}")
        return cls(module, package, package / "source_registry.json", fingerprints)
 
    def metadata(self) -> dict[str, Any]:
        return {
            "version": self.module.__version__,
            "module_path": str(Path(self.module.__file__).resolve()),
            "registry_path": str(self.registry_path.resolve()),
            "files": self.fingerprints,
        }
 
    def run_input(self, snapshot_path: Path, output_dir: Path, force: bool) -> dict[str, Any]:
        return self.module.run_pipeline(
            snapshot_path=snapshot_path,
            output_dir=output_dir,
            source_registry_path=self.registry_path,
            force=force,
        )
 
    def compute(self, snapshot_path: Path) -> dict[str, Any]:
        snapshot = self.module.load_snapshot(snapshot_path)
        registry = json.loads(self.registry_path.read_text(encoding="utf-8"))
        return self.module.compute_valuation(snapshot, registry)