52b6599d97
gates / gates (push) Successful in 6m36s
Read-only. fetch.py (polite, 5 s apart, 34 pages, all 200), parse.py (own text only; four parser defects found by the controls and fixed), build.py + spotcheck.py (stdlib). tags.csv holds post numbers and tags only - no username, author id, salt or post text in git. FINDINGS.md (2,908 posts, 195 people, 82 beginner threads in 7 clusters), FELHOM.md (catalog gap, hardware fit, 15 website questions, objections, ten ranked recommendations), METHOD.md (controls, spot-check: role and hardware below 85 % after two rounds, the table-driving splits 92.5 % and 97.3 %), REPORT.md. R-990: operator rules on the recommendations. R-991: no buyer-readable hardware floor. Claude-Session: https://claude.ai/code/session_012qRErfCoiTkvDK9N5XHbzb
95 lines
4.3 KiB
Python
95 lines
4.3 KiB
Python
#!/usr/bin/env python3
|
|
"""Compare the session's own blind re-tags of a random 5% sample with the helper tags (spec 9.6).
|
|
|
|
Mine: ~/research-cache/.../spot/mine_*.txt, one line per post:
|
|
num|role|beginner(y/n)|question_type|hardware|hardware_status|apps|apps_status|platform|platform_status|pain|budget_huf|resolved
|
|
Theirs: ~/research-cache/.../tags/tags_*.csv
|
|
Agreement per column = exact match of the normalised value (sets for ;-lists; hardware compared by
|
|
class, the bracketed model dropped). Prints overall agreement and agreement where either side is
|
|
non-empty (the strict view: empty-vs-empty agreement inflates the overall rate).
|
|
Usage: python3 spotcheck.py (v1: every column, helper tags/ vs spot/mine_*.txt)
|
|
python3 spotcheck.py --v2 (v2 re-tag: role, hardware, hardware_status;
|
|
tags_v2/v2_*.csv vs spot/mine_v2.txt num|role|hardware|hardware_status)
|
|
"""
|
|
import csv
|
|
import glob
|
|
import os
|
|
import re
|
|
import sys
|
|
|
|
CACHE = os.path.expanduser("~/research-cache/prohardver-hazi-szerver")
|
|
COLS = ["role", "beginner", "question_type", "hardware", "hardware_status", "apps", "apps_status",
|
|
"platform", "platform_status", "pain", "budget_huf", "resolved"]
|
|
SETS = {"question_type", "hardware", "apps", "platform", "pain"}
|
|
|
|
|
|
def norm(col, v):
|
|
v = (v or "").strip()
|
|
if col in SETS:
|
|
parts = [re.sub(r"\s*\(.*?\)", "", x).strip().lower() for x in v.split(";")]
|
|
return tuple(sorted(x for x in parts if x))
|
|
if col == "budget_huf":
|
|
return v.split(".")[0]
|
|
return v.lower()
|
|
|
|
|
|
def main_v2():
|
|
cols = ["role", "hardware", "hardware_status"]
|
|
theirs = {}
|
|
for f in glob.glob(os.path.join(CACHE, "tags_v2", "v2_*.csv")):
|
|
for r in csv.DictReader(open(f, encoding="utf-8")):
|
|
theirs[int(r["num"])] = r
|
|
mine = {}
|
|
for line in open(os.path.join(CACHE, "spot", "mine_v2.txt"), encoding="utf-8"):
|
|
v = line.rstrip("\n").split("|")
|
|
mine[int(v[0])] = dict(zip(cols, v[1:]))
|
|
nums = sorted(n for n in mine if n in theirs)
|
|
print("v2 sample=%d compared=%d" % (len(mine), len(nums)))
|
|
for c in cols:
|
|
agree = sum(norm(c, mine[n][c]) == norm(c, theirs[n].get(c)) for n in nums)
|
|
ne = [n for n in nums if norm(c, mine[n][c]) or norm(c, theirs[n].get(c))]
|
|
agree_ne = sum(norm(c, mine[n][c]) == norm(c, theirs[n].get(c)) for n in ne)
|
|
print("%-16s %7.1f%% %10.1f%% (n=%d)" % (c, 100.0 * agree / len(nums),
|
|
100.0 * agree_ne / len(ne) if ne else 100.0, len(ne)))
|
|
if os.environ.get("SHOW") == c:
|
|
for n in nums:
|
|
if norm(c, mine[n][c]) != norm(c, theirs[n].get(c)):
|
|
print(" #%d mine=%s theirs=%s" % (n, norm(c, mine[n][c]), norm(c, theirs[n].get(c))))
|
|
|
|
|
|
def main():
|
|
if "--v2" in sys.argv:
|
|
return main_v2()
|
|
theirs = {}
|
|
for f in glob.glob(os.path.join(CACHE, "tags", "tags_*.csv")):
|
|
for r in csv.DictReader(open(f, encoding="utf-8")):
|
|
r["beginner"] = "y" if (r.get("beginner_evidence") or "").strip() else "n"
|
|
theirs[int(r["num"])] = r
|
|
mine = {}
|
|
for f in glob.glob(os.path.join(CACHE, "spot", "mine_[0-9].txt")):
|
|
for line in open(f, encoding="utf-8"):
|
|
if not line.strip():
|
|
continue
|
|
v = line.rstrip("\n").split("|")
|
|
mine[int(v[0])] = dict(zip(COLS, v[1:]))
|
|
nums = sorted(n for n in mine if n in theirs)
|
|
print("sample=%d compared=%d (missing in helper tags: %s)" %
|
|
(len(mine), len(nums), sorted(set(mine) - set(theirs))))
|
|
print("%-16s %8s %18s" % ("column", "overall", "non-empty either"))
|
|
for c in COLS:
|
|
agree = sum(norm(c, mine[n][c]) == norm(c, theirs[n].get(c)) for n in nums)
|
|
ne = [n for n in nums if norm(c, mine[n][c]) or norm(c, theirs[n].get(c))]
|
|
agree_ne = sum(norm(c, mine[n][c]) == norm(c, theirs[n].get(c)) for n in ne)
|
|
print("%-16s %7.1f%% %10.1f%% (n=%d)" % (c, 100.0 * agree / len(nums),
|
|
100.0 * agree_ne / len(ne) if ne else 100.0, len(ne)))
|
|
if os.environ.get("SHOW"):
|
|
c = os.environ["SHOW"]
|
|
for n in nums:
|
|
a, b = norm(c, mine[n][c]), norm(c, theirs[n].get(c))
|
|
if a != b:
|
|
print(" #%d mine=%s theirs=%s" % (n, a, b))
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|