chore: establish project baseline
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
"""SGV UI oracle: snapshot/diff of ONE C000025 row (READ-ONLY; never writes to Firebird).
|
||||
|
||||
python scripts/sgv_product_snapshot.py before <CODIGO>
|
||||
-> reads the whole row and saves docs/runs/snapshots/<CODIGO>_before.json
|
||||
(operator changes exactly ONE field in the SGV product screen and saves)
|
||||
python scripts/sgv_product_snapshot.py diff <CODIGO> [--ui-label "CFOP saída"]
|
||||
-> re-reads, prints FIELD/BEFORE/AFTER for every changed column, and (with --ui-label) records the
|
||||
result in docs/runs/sgv_ui_diff_ledger.json. A label is CONFIRMED only when exactly ONE
|
||||
non-technical column changed.
|
||||
"""
|
||||
import argparse
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
from datetime import date, datetime
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[1]
|
||||
sys.path.insert(0, str(ROOT / "agent" / "src"))
|
||||
SNAP_DIR = ROOT / "docs" / "runs" / "snapshots"
|
||||
LEDGER = ROOT / "docs" / "runs" / "sgv_ui_diff_ledger.json"
|
||||
# Columns the ERP rewrites by itself on every save; ignored ONLY because they are clearly automatic.
|
||||
TECHNICAL = {"ULTIMA_ALTERACAO", "DATA_ALTERACAO", "DATA_ULTIMA_ALTERACAO", "USUARIO_ALTERACAO"}
|
||||
|
||||
|
||||
def norm(v):
|
||||
if isinstance(v, (bytes, bytearray)):
|
||||
import hashlib
|
||||
return "blob:" + hashlib.sha256(bytes(v)).hexdigest()[:16]
|
||||
if isinstance(v, (datetime, date)):
|
||||
return v.isoformat()
|
||||
if isinstance(v, Decimal):
|
||||
return str(v)
|
||||
return v.strip() if isinstance(v, str) else v
|
||||
|
||||
|
||||
def read_row(env, code):
|
||||
from gti_fiscal_agent.firebird_reader import _connect # read-only isolation
|
||||
conn = _connect(env)
|
||||
try:
|
||||
cur = conn.cursor()
|
||||
cur.execute("SELECT * FROM C000025 WHERE CODIGO = ?", (code,))
|
||||
row = cur.fetchone()
|
||||
if row is None:
|
||||
raise SystemExit(f"product {code} not found")
|
||||
names = [c[0].strip() for c in cur.description]
|
||||
blob = {}
|
||||
return {n: norm(v) for n, v in zip(names, row)}
|
||||
finally:
|
||||
conn.close()
|
||||
|
||||
|
||||
def compute_diff(before, after):
|
||||
out = []
|
||||
for f in sorted(set(before) | set(after)):
|
||||
if before.get(f) != after.get(f):
|
||||
out.append({"FIELD": f, "BEFORE": before.get(f), "AFTER": after.get(f), "technical": f in TECHNICAL})
|
||||
return out
|
||||
|
||||
|
||||
def load_env():
|
||||
for line in (ROOT / ".env").read_text(encoding="utf-8").splitlines():
|
||||
if "=" in line and not line.startswith("#"):
|
||||
k, _, v = line.partition("="); os.environ.setdefault(k.strip(), v.strip())
|
||||
|
||||
|
||||
def record(label, code, changes):
|
||||
real = [c for c in changes if not c["technical"]]
|
||||
ledger = json.loads(LEDGER.read_text(encoding="utf-8")) if LEDGER.exists() else []
|
||||
entry = {"ui_label": label, "product": code, "recorded": datetime.now().isoformat(timespec="seconds"), "changed_columns": [c["FIELD"] for c in real],
|
||||
"before": {c["FIELD"]: c["BEFORE"] for c in real}, "after": {c["FIELD"]: c["AFTER"] for c in real},
|
||||
"confirmed": len(real) == 1, "note": "" if len(real) == 1 else ("no column changed" if not real else "more than one column changed; repeat changing a single field")}
|
||||
ledger.append(entry)
|
||||
LEDGER.write_text(json.dumps(ledger, ensure_ascii=False, indent=1), encoding="utf-8")
|
||||
return entry
|
||||
|
||||
|
||||
def main(argv=None):
|
||||
ap = argparse.ArgumentParser()
|
||||
ap.add_argument("action", choices=["before", "diff"]); ap.add_argument("codigo"); ap.add_argument("--ui-label")
|
||||
a = ap.parse_args(argv)
|
||||
load_env()
|
||||
SNAP_DIR.mkdir(parents=True, exist_ok=True)
|
||||
path = SNAP_DIR / f"{a.codigo}_before.json"
|
||||
if a.action == "before":
|
||||
path.write_text(json.dumps(read_row(os.environ, a.codigo), ensure_ascii=False, indent=1, default=str), encoding="utf-8")
|
||||
print(f"snapshot saved: {path}"); return
|
||||
if not path.exists():
|
||||
raise SystemExit("run 'before' first")
|
||||
changes = compute_diff(json.loads(path.read_text(encoding="utf-8")), read_row(os.environ, a.codigo))
|
||||
print(f"{'FIELD':<28}{'BEFORE':<28}AFTER")
|
||||
for c in changes:
|
||||
print(f"{c['FIELD']:<28}{str(c['BEFORE']):<28}{c['AFTER']}{' (technical, ignored)' if c['technical'] else ''}")
|
||||
if a.ui_label:
|
||||
print(json.dumps(record(a.ui_label, a.codigo, changes), ensure_ascii=False))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user