Ctrl K
ComunidadGuías…

scripts/gen-golden-vectors.py

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
"""Golden vectors: the Python engine's answers, frozen as JSON, so the JavaScript engine in `room/`
can be held to byte-identical verdicts and the same transcript hash.

    PYTHONPATH=. .venv/bin/python scripts/gen-golden-vectors.py          # rewrite the file
    PYTHONPATH=. .venv/bin/python scripts/gen-golden-vectors.py --check  # exit 1 if it is stale
"""
import glob
import json
import os
import sys

from parley.spec import DecisionSpec

ROOT = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")
OUT = os.path.join(ROOT, "tests", "golden", "engine_vectors.json")

EDGE = {
    "title": "edge: every operator, unicode ids, scores at 0 and 1, a deadlock-free pick",
    "options": [
        {"id": "ñandú-ü", "n": 3, "price": 0.1, "ok": True, "kind": "a", "tags": ["x", "y"]},
        {"id": "b", "n": 10, "price": 2.5, "ok": False, "kind": "b", "tags": []},
        {"id": "c", "n": 0, "price": 1e-7, "ok": True, "kind": "c", "tags": ["z"]},
        {"id": "d", "n": 7, "price": 100.25, "ok": True, "kind": "a", "tags": ["x"]},
    ],
    "parties": [
        {"owner": "Ana", "hard": [{"attr": "ok", "op": "==", "value": True},
                                   {"attr": "n", "op": "<=", "value": 8}],
         "utility": [{"attr": "kind", "weight": 2.0, "prefer": "a"},
                     {"attr": "price", "direction": "lower", "lo": 0, "hi": 100}]},
        {"owner": "Bo", "hard": [{"attr": "kind", "op": "!=", "value": "c"},
                                  {"attr": "n", "op": ">=", "value": 3}],
         "utility": [{"attr": "n", "direction": "higher", "lo": 0, "hi": 10}]},
        {"owner": "Cy", "hard": [{"attr": "kind", "op": "in", "value": ["a", "c"]},
                                  {"attr": "price", "op": "<", "value": 101},
                                  {"attr": "n", "op": ">", "value": 2},
                                  {"attr": "id", "op": "not_in", "value": ["b"]}],
         "utility": []},
    ],
}

DEADLOCK = {
    "title": "edge: honest deadlock",
    "options": [{"id": "a", "n": 1}, {"id": "b", "n": 2}],
    "parties": [
        {"owner": "P", "hard": [{"attr": "n", "op": "==", "value": 1}], "utility": []},
        {"owner": "Q", "hard": [{"attr": "n", "op": "==", "value": 2}], "utility": []},
    ],
}

NESTED = {
    "title": "edge: bool is not int inside containers (PR 161 parity)",
    "options": [{"id": "a", "x": [True], "m": {"k": True}}, {"id": "b", "x": [1], "m": {"k": 1}},
                {"id": "c", "x": [True, 2], "m": {"k": 1}}],
    "parties": [
        {"owner": "P", "hard": [{"attr": "x", "op": "==", "value": [1]}], "utility": []},
        {"owner": "Q", "hard": [{"attr": "m", "op": "in", "value": [{"k": 1}]},
                                 {"attr": "x", "op": "!=", "value": [True]},
                                 {"attr": "x", "op": "not_in", "value": [[True, 2]]}], "utility": []},
    ],
}

HEIRS = {
    "title": "three invented heirs, one flat (docs/dogfood-09)",
    "options": [
        {"id": "sell-now", "months_to_cash": 4, "price_pct_of_market": 85, "marta_stays_months": 2,
         "monthly_cost_per_heir": 0, "family_home_kept": False},
        {"id": "sell-in-a-year", "months_to_cash": 16, "price_pct_of_market": 100,
         "marta_stays_months": 12, "monthly_cost_per_heir": 220, "family_home_kept": False},
        {"id": "marta-buys-out-with-loan", "months_to_cash": 8, "price_pct_of_market": 92,
         "marta_stays_months": 999, "monthly_cost_per_heir": 0, "family_home_kept": True},
        {"id": "sell-in-seven-months", "months_to_cash": 7, "price_pct_of_market": 95,
         "marta_stays_months": 6, "monthly_cost_per_heir": 60, "family_home_kept": False},
    ],
    "parties": [
        {"owner": "Marta", "hard": [{"attr": "marta_stays_months", "op": ">=", "value": 6}],
         "utility": [{"attr": "family_home_kept", "prefer": True}]},
        {"owner": "Javier", "hard": [{"attr": "months_to_cash", "op": "<=", "value": 9}],
         "utility": [{"attr": "price_pct_of_market", "direction": "higher", "lo": 80, "hi": 100}]},
        {"owner": "Elena", "hard": [{"attr": "monthly_cost_per_heir", "op": "<=", "value": 100}],
         "utility": [{"attr": "price_pct_of_market", "direction": "higher", "lo": 80, "hi": 100}]},
    ],
}


def recipe_sources() -> list:
    sources = [(os.path.basename(p), json.load(open(p, encoding="utf-8")))
               for p in sorted(glob.glob(os.path.join(ROOT, "examples", "demo", "recipe_*.json")))]
    return sources + [("edge", EDGE), ("deadlock", DEADLOCK), ("nested", NESTED), ("heirs", HEIRS)]


def stored_sources() -> list:
    """The specs frozen inside the vectors file, so --check does not depend on which recipe files
    this checkout happens to carry (the private tree and the public mirror hold different ones)."""
    with open(OUT, encoding="utf-8") as fh:
        return [(c["name"], c["spec"]) for c in json.load(fh)["cases"]]


def build(sources=None) -> dict:
    cases = []
    sources = recipe_sources() if sources is None else sources
    for name, raw in sources:
        spec_in = {"title": raw["title"], "options": raw["options"], "parties": raw["parties"]}
        result = DecisionSpec.from_dict(spec_in).run()
        cases.append({
            "name": name,
            "spec": spec_in,
            "expected": {
                "status": result.status,
                "decision": result.decision,
                "transcript_hash": result.transcript.hash(),
                "entries": [{"option": e["option"], "verdicts": [
                    {k: v[k] for k in ("owner", "acceptable", "score", "reason")} for v in e["verdicts"]]}
                    for e in result.transcript.entries],
            },
        })
    return {"about": "Python engine answers, frozen. Regenerate with scripts/gen-golden-vectors.py.",
            "cases": cases}


def main() -> int:
    if "--check" in sys.argv:
        current = open(OUT, encoding="utf-8").read() if os.path.exists(OUT) else ""
        text = json.dumps(build(stored_sources() if current else None), indent=1, ensure_ascii=False) + "\n"
        if current != text:
            print("tests/golden/engine_vectors.json is stale; rerun scripts/gen-golden-vectors.py")
            return 1
        return 0
    text = json.dumps(build(), indent=1, ensure_ascii=False) + "\n"
    with open(OUT, "w", encoding="utf-8") as fh:
        fh.write(text)
    print(f"wrote {len(build()['cases'])} cases to {OUT}")
    return 0


if __name__ == "__main__":
    raise SystemExit(main())