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())
|