#!/usr/bin/env python3 """summarize.py — read every /record.json of the sweep and print one review line per app: the measured facts and a FIRST verdict hint. The hint is a filter for the reader, not the verdict: README.md carries the verdict after each app's evidence was read.""" import glob, json, os, re, sys EV = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) EN_IN_HU = re.compile(r"(?i)\b(the|and|your|you|please|click|with|this|password|settings|open|first|login)\b") def hint(r): probs, broken = [], [] inst = r.get("install") or {} if not inst.get("ok"): broken.append("install failed") elif not inst.get("front_door"): broken.append("front door never answered") if r.get("exception"): broken.append("cycle raised: " + r["exception"][:80]) if r.get("fixture") and r.get("seed") is False: probs.append("seed failed") if r.get("seed") and r.get("seed_read_now") is False: probs.append("item not read back at once") back = r.get("seed_read_after_reinstall") if r.get("seed") and back is False and not r.get("seed_read_after_unit_restore"): probs.append("item NOT back after keep-data reinstall + unit restore") if r.get("seed_read_after_restore") is False: probs.append("item not back after the unit restore") if (r.get("kept") or {}).get("offered") and (r.get("kept") or {}).get("use_ok") is False: probs.append("use-my-kept-data failed") left = r.get("after_final_remove") or {} if any(left.get(k) for k in ("containers", "volumes", "appyaml", "appdata", "kept")): probs.append("leftovers after remove: " + json.dumps({k: v for k, v in left.items() if v and k != "subdomain"})) if left.get("subdomain") and not left["subdomain"].startswith("404"): probs.append("subdomain after remove: " + left["subdomain"][:40]) for c, v in (r.get("containers") or {}).items(): anon = (r.get("mem_anon_peak_mib") or {}).get(c) lim, peak = v.get("limit_mib"), anon if anon is not None else (r.get("mem_peak_mib") or {}).get(c) if lim and peak and peak > 0.9 * lim: probs.append(f"memory {c} {peak}/{lim} MiB ({'anon' if anon is not None else 'docker stats, may include cache'})") if v.get("restarts"): probs.append(f"restarts {c} {v['restarts']}") if v.get("health") == "unhealthy": probs.append(f"unhealthy {c}") errs = [l for l in (r.get("errors") or {}).get("sample", []) if not re.search(r"(?i)wrong (username|password)|invalid password|failed login|login fail|unauthori[sz]ed|status=40[13]|bad credentials", l)] n = (r.get("errors") or {}).get("count", 0) - (len((r.get("errors") or {}).get("sample", [])) - len(errs)) if n > 0: # the negative controls' own wrong-password logins are expected and not counted probs.append(f"error lines {n}") if r.get("http_port_80") and not r["http_port_80"].startswith(("301", "308")): probs.append("http :80 → " + r["http_port_80"][:30]) for tab, fname in (("install_page", "install"), ("guide", "guide")): t = r.get(tab) or {} try: # English OUTSIDE quotes: a quoted „Settings” is the app's own label, glossed in Hungarian — correct txt = open(os.path.join(EV, r["app"], f"page-{fname}-hu.txt")).read() bare = re.sub(r"„[^”]*”|“[^”]*”|\"[^\"]*\"|\([^)]*\)", " ", txt) words = sorted(set(m.lower() for m in EN_IN_HU.findall(bare))) except OSError: words = t.get("english_words_in_hu") or [] if words: probs.append(f"{tab} EN words in HU {words[:5]}") if t.get("accented_in_en"): probs.append(f"{tab} HU letters in EN {t['accented_in_en']}") if t.get("same_text"): probs.append(f"{tab} same text HU/EN") for s in (r.get("shots_install") or []) + (r.get("shots_after") or []): if "hscroll=True" in s: probs.append("sideways scroll in a screenshot") if "STEP FAILED" in s: probs.append("a screenshot step failed") v = "broken" if broken else ("problem?" if probs else "works") return v, broken + probs def main(): rows = [] for f in sorted(glob.glob(os.path.join(EV, "*", "record.json"))): r = json.load(open(f)) v, why = hint(r) inst = r.get("install") or {} peak = sum((r.get("mem_peak_mib") or {}).values()) lim = sum((c.get("limit_mib") or 0) for c in (r.get("containers") or {}).values()) rows.append((r["app"], v, round(peak), lim, inst.get("seconds_to_front_door"), r.get("fixture") or "-", r.get("stranger", "")[:40], "; ".join(why))) for row in rows: print("\t".join(str(x) for x in row)) print(f"# {len(rows)} records; " + ", ".join(f"{k} {sum(1 for r in rows if r[1] == k)}" for k in ("works", "problem?", "broken")), file=sys.stderr) if __name__ == "__main__": main()