Cai
2026-08-02 3390d59f68c4ebcb87ed45d58ce8554d6e59b896
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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
from __future__ import annotations
 
import argparse
import json
import sys
from pathlib import Path
 
from .v1_bridge import V1Bridge, V1ContractError
from .telemetry import RunClock
from .workflow import _input_summary, run_ticker_pipeline
 
 
class CliInputError(ValueError):
    pass
 
 
class JsonArgumentParser(argparse.ArgumentParser):
    def error(self, message: str) -> None:
        raise CliInputError(message)
 
 
def build_parser() -> argparse.ArgumentParser:
    parser = JsonArgumentParser(description="股票估值 V2 端到端协调层")
    mode = parser.add_mutually_exclusive_group(required=True)
    mode.add_argument("--input", type=Path, help="兼容 V1 标准估值快照")
    mode.add_argument("--ticker", help="股票代码,例如 001270.SZ")
    parser.add_argument("--as-of")
    parser.add_argument("--output-dir", required=True, type=Path)
    parser.add_argument("--cache-dir", type=Path)
    parser.add_argument("--judgment", type=Path)
    parser.add_argument("--fixture-dir", type=Path)
    parser.add_argument("--task-start")
    parser.add_argument("--force", action="store_true")
    return parser
 
 
def _v1_mode(args: argparse.Namespace) -> int:
    try:
        bridge = V1Bridge.load()
        status = bridge.run_input(args.input, args.output_dir, args.force)
    except V1ContractError as exc:
        print(
            json.dumps(
                {"status": "RUNTIME_ERROR", "error_code": "E_V1_CONTRACT", "error": str(exc)},
                ensure_ascii=False,
                indent=2,
            ),
            file=sys.stderr,
        )
        return 3
    except Exception as exc:
        try:
            bridge  # type: ignore[used-before-assignment]
            input_error = bridge.module.InputError  # type: ignore[possibly-undefined]
        except Exception:
            input_error = ()
        if input_error and isinstance(exc, input_error):
            print(
                json.dumps(
                    {"status": "INPUT_ERROR", "errors": exc.errors},
                    ensure_ascii=False,
                    indent=2,
                ),
                file=sys.stderr,
            )
            return 2
        if isinstance(exc, (OSError, json.JSONDecodeError)):
            print(
                json.dumps({"status": "RUNTIME_ERROR", "error": str(exc)}, ensure_ascii=False, indent=2),
                file=sys.stderr,
            )
            return 3
        raise
    print(json.dumps(status, ensure_ascii=False, indent=2))
    return 0
 
 
def main(argv: list[str] | None = None) -> int:
    parser = build_parser()
    try:
        args = parser.parse_args(argv)
    except CliInputError as exc:
        print(json.dumps(_input_summary(RunClock(), exc, False), ensure_ascii=False, sort_keys=True, separators=(",", ":")))
        return 2
    if args.input is not None:
        if any((args.as_of, args.cache_dir, args.judgment, args.fixture_dir, args.task_start)):
            summary = _input_summary(RunClock(), CliInputError("--input 模式不接受 ticker 模式参数"), False)
            print(json.dumps(summary, ensure_ascii=False, sort_keys=True, separators=(",", ":")))
            return 2
        return _v1_mode(args)
    if not args.as_of or args.cache_dir is None:
        summary = _input_summary(RunClock(), CliInputError("--ticker 模式必须提供 --as-of 和 --cache-dir"), False)
        print(json.dumps(summary, ensure_ascii=False, sort_keys=True, separators=(",", ":")))
        return 2
    code, summary = run_ticker_pipeline(
        ticker=args.ticker,
        as_of=args.as_of,
        output_dir=args.output_dir,
        cache_dir=args.cache_dir,
        judgment_path=args.judgment,
        fixture_dir=args.fixture_dir,
        task_start=args.task_start,
        force=args.force,
    )
    print(json.dumps(summary, ensure_ascii=False, sort_keys=True, separators=(",", ":")))
    return code
 
 
if __name__ == "__main__":
    raise SystemExit(main())