arm-scan-gateway/api/tests/golden_update.py
2026-10-18 20:18:00 +04:00

101 lines
4.3 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Golden нормализатора: сбор склейки реального ответа и запись эталонов (`make golden-update`).
Набор задания `<name>` — `<name>.json` + `<name>_*.json` из `../fixtures/ocr` (как у эмулятора); эталон —
`../fixtures/ocr/expected_normalize/<name>.json`. Запуск: `cd api && .venv/bin/python -m tests.golden_update`.
"""
import json
import sys
import uuid
from pathlib import Path
from typing import Any
from arm_statements import default_rules
from src.contract import ResultStatus, ServiceInfo, ServiceName
from src.core.worker import _outcome_metrics, build_result
from src.gateway.hotfolder import AnswerFile
from src.gateway.merge import merge_answers
from src.normalizer import NormalizeOutcome, OcrNormalizer
API_DIR = Path(__file__).resolve().parents[1]
REAL_FIXTURES = API_DIR.parent.parent / "fixtures" / "ocr"
GOLDEN_DIR = REAL_FIXTURES / "expected_normalize"
JOB = "3f1c2b7a-0000-4000-8000-000000000001"
JOB_ID = uuid.UUID(JOB)
FILE_ID = uuid.UUID("3f1c2b7a-0000-4000-8000-0000000000f1")
SERVICE = ServiceInfo(name=ServiceName.SCAN_GATEWAY, version="golden")
GOLDEN_VERSION = "0" * 40 # фиксированная версия копии: golden от раскладки копии не зависит
def golden_cases() -> list[str]:
"""Имена `<name>` без суффикса `_*`: `aep_3`, `aep_6`, … (по одному на набор)."""
names = sorted(p.name[: -len(".json")] for p in REAL_FIXTURES.glob("*.json") if p.is_file())
stems = {name for name in names if not any(name.startswith(f"{other}_") for other in names if other != name)}
return sorted(stems)
def real_set(name: str) -> dict[str, Any]:
files = []
for path in sorted(REAL_FIXTURES.glob(f"{name}*.json")):
base = path.name[: -len(".json")]
if base == name:
target = f"{JOB}.json"
elif base.startswith(f"{name}_"):
target = f"{JOB}_{base[len(name) + 1 :]}.json"
else:
continue
files.append(AnswerFile(target, path.read_bytes()))
return dict(merge_answers(JOB, files))
def full_payload(outcome: NormalizeOutcome) -> dict[str, Any]:
"""Полный файл результата (только успех) с фиктивными идентификаторами: им проверяется схема `result`."""
result = build_result(
outcome,
job_id=JOB_ID,
file_id=FILE_ID,
service=SERVICE,
raw_s3_key="raw/golden.json",
metrics=_outcome_metrics(outcome),
)
return dict(result.to_wire())
def outcome_to_json(outcome: NormalizeOutcome) -> dict[str, Any]:
"""Эталон golden v2 (P5-14): `schema_version`, `status`, `metrics`, `documents`; для ошибки — ещё `error`.
`job_id`, `file_id`, `service`, `raw_s3_key` не пишутся: `statements_version` меняется при каждой раскладке."""
metrics = _outcome_metrics(outcome)
if outcome.status is ResultStatus.ERROR:
return {
"schema_version": "2.0",
"status": "error",
"error": outcome.error.model_dump(mode="json", exclude_none=True) if outcome.error else None,
"metrics": metrics.model_dump(mode="json", exclude_none=True) if metrics else None,
"documents": [],
}
wire = full_payload(outcome)
return {key: wire[key] for key in ("schema_version", "status", "metrics", "documents") if key in wire}
def main() -> int:
if not REAL_FIXTURES.is_dir():
print(f"нет каталога {REAL_FIXTURES}", file=sys.stderr)
return 2
rules = default_rules()
GOLDEN_DIR.mkdir(exist_ok=True)
for name in golden_cases():
payload = outcome_to_json(OcrNormalizer(rules, version=GOLDEN_VERSION).normalize(real_set(name)))
(GOLDEN_DIR / f"{name}.json").write_text(
json.dumps(payload, ensure_ascii=False, indent=2, sort_keys=True) + "\n", encoding="utf-8"
)
print(
f"{name}: {payload['status']}, документов {len(payload['documents'])}, "
f"предупреждений {len((payload.get('metrics') or {}).get('warnings', []))}"
)
return 0
if __name__ == "__main__":
sys.exit(main())