Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016679RwmHsuTFfxt26wP6rk
92 lines
4.4 KiB
Python
92 lines
4.4 KiB
Python
#!/usr/bin/env python3
|
|
"""Sēdes datnes pārbaude pret TAP lapām (kešs no tools/fetch.py), ar citu nolasīšanas veidu nekā tools/tap.py.
|
|
|
|
python3 tools/verify.py .cache/<sēdes uuid> data/lv-mk-sede-….xml
|
|
|
|
tools/tap.py nolasa punktus pa HTML elementiem. Šī pārbaude ņem visu lapas teksta bloku kā vienu virkni un pārbauda:
|
|
1. darba kārtība — katra jautājuma numurs, TAP identifikators un statuss ir lapas tekstā šajā secībā;
|
|
2. katra lēmuma lapa — sha256 sakrīt ar datnē norādīto;
|
|
3. katra punkta numurs un teksts ir lapas teksta blokā burtiski un tādā pašā secībā kā datnē;
|
|
4. pārklājums — lapas teksta bloks bez datnes punktiem, numuriem un virsraksta ir tukšs (nekas nav izlaists).
|
|
Atskaite — verification/<sēde>.verify.json.
|
|
"""
|
|
import hashlib
|
|
import json
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
from lxml import etree
|
|
from lxml import html as H
|
|
|
|
ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
NS = "urn:pppa:vpk:mk-lemumi:0.1"
|
|
N = {"m": NS}
|
|
|
|
|
|
def flat(s):
|
|
return re.sub(r"\s+", " ", s.replace("\xa0", " ")).strip()
|
|
|
|
|
|
def main(cache, path):
|
|
man = json.load(open(os.path.join(cache, "manifest.json"), encoding="utf-8"))
|
|
r = etree.parse(path).getroot()
|
|
rep = {"file": os.path.relpath(path, ROOT), "meeting": r.get("id"), "agenda": {}, "decisions": {}, "passed": True}
|
|
page = flat(H.parse(os.path.join(cache, "meeting.html")).getroot().text_content())
|
|
pos, miss = 0, []
|
|
for it in r.iterfind("m:Section/m:Item", N):
|
|
for token in (it.get("n"), it.get("tap") or "", it.get("status")):
|
|
i = page.find(token, pos) if token else pos
|
|
if i < 0:
|
|
miss.append(f"{it.get('id')}: {token}")
|
|
else:
|
|
pos = i
|
|
rep["agenda"] = {"items": len(r.findall("m:Section/m:Item", N)), "missing": miss}
|
|
rep["passed"] &= not miss
|
|
by_url = {v["url"]: k for k, v in man["files"].items()}
|
|
for d in r.iter(f"{{{NS}}}Decision"):
|
|
rel = by_url.get(d.findtext("m:Url", namespaces=N))
|
|
res = {"sha256_ok": False, "points": 0, "missing": [], "leftover": ""}
|
|
if rel:
|
|
raw = open(os.path.join(cache, rel), "rb").read()
|
|
res["sha256_ok"] = hashlib.sha256(raw).hexdigest() == d.findtext("m:SHA256", namespaces=N)
|
|
tree = H.fromstring(raw).xpath('//div[contains(@class,"structuralizer-tree--preview")]')[0]
|
|
for x in tree.xpath('.//div[contains(@class,"node-preview__actions")]'):
|
|
x.getparent().remove(x)
|
|
text = flat(tree.text_content())
|
|
rest, pos = text, 0
|
|
for p in d.iter(f"{{{NS}}}Point"):
|
|
res["points"] += 1
|
|
pat = (re.escape(p.get("n")) + r"\s*" if p.get("n") else "") + re.escape(flat(p.findtext("m:Text", namespaces=N)))
|
|
m = re.compile(pat).search(text, pos)
|
|
if not m:
|
|
res["missing"].append(p.get("id"))
|
|
else:
|
|
pos = m.end()
|
|
rest = re.compile(pat).sub(" ", rest, count=1)
|
|
for q in p.iterfind("m:Quote", N):
|
|
rest = rest.replace(flat(q.text), " ", 1)
|
|
extras = ([x.text for x in d.iterfind("m:Heading", N)] + [d.findtext("m:Title", namespaces=N) or ""]
|
|
+ [x.text for x in d.iterfind("m:Annex", N)] + [x.text for x in d.iterfind("m:Signatory", N)])
|
|
for extra in extras:
|
|
rest = rest.replace(flat(extra), " ", 1)
|
|
rest = flat(re.sub(r"\b\d{2}-TA-\d+[A-Za-z]*\b", " ", rest))
|
|
res["leftover"] = rest
|
|
res["passed"] = res["sha256_ok"] and not res["missing"] and not res["leftover"]
|
|
rep["passed"] &= res["passed"]
|
|
rep["decisions"][d.get("id")] = res
|
|
out = os.path.join(ROOT, "verification", r.get("id") + ".verify.json")
|
|
os.makedirs(os.path.dirname(out), exist_ok=True)
|
|
with open(out, "w", encoding="utf-8") as f:
|
|
json.dump(rep, f, ensure_ascii=False, indent=1)
|
|
bad = {k: v for k, v in rep["decisions"].items() if not v["passed"]}
|
|
print(f"{rep['meeting']}: jautājumi {rep['agenda']['items']} (trūkst {len(miss)}), lēmumi {len(rep['decisions'])}, "
|
|
f"neizturēti {len(bad)} -> {out}")
|
|
for k, v in list(bad.items())[:10]:
|
|
print(" ", k, "sha" if not v["sha256_ok"] else "", v["missing"][:3], v["leftover"][:160])
|
|
return 0 if rep["passed"] else 1
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main(sys.argv[1], sys.argv[2]))
|