""" Verificări mecanice pe diagramele SVG dintr-un fișier de sumarizare. Prinde exact clasele de bug găsite manual în 01_MODUL_1/05_patternuri_candele.md: scară inconsistentă între niveluri, text tăiat de viewBox, text suprapus, linii goale care rup blocul HTML. NU înlocuiește verificarea vizuală — nu vede dacă un text stă peste o linie de preț. Rulează: .venv/bin/python audit_diagrams.py summaries//.md """ import re import sys from pathlib import Path CHAR_W = 5.7 # latime medie per caracter la 12px sans-serif (calibrat pe randare) LINE_H = 13 # înălțimea unei linii de text def texts(svg: str): for m in re.finditer(r']*)>(.*?)', svg, re.S): attrs, inner = m.group(1), re.sub(r'<[^>]+>', '', m.group(2)) x = float(re.search(r'\bx="([-\d.]+)"', attrs).group(1)) if re.search(r'\bx="', attrs) else 0.0 y = float(re.search(r'\by="([-\d.]+)"', attrs).group(1)) if re.search(r'\by="', attrs) else 0.0 anchor = (re.search(r'text-anchor="(\w+)"', attrs) or [None, "start"])[1] w = len(inner) * CHAR_W x0 = x - w if anchor == "end" else (x - w / 2 if anchor == "middle" else x) yield x0, x0 + w, y, inner.strip() def check(path: Path) -> list[str]: md = path.read_text(encoding="utf-8") problems = [] for i, svg in enumerate(re.findall(r'', md, re.S)): vb = re.search(r'viewBox="0 0 ([\d.]+) ([\d.]+)"', svg) if not vb: problems.append(f"svg {i}: fără viewBox") continue vw, vh = float(vb.group(1)), float(vb.group(2)) if re.search(r"\n[ \t]*\n", svg): problems.append(f"svg {i}: linie goală înăuntru — markdown rupe blocul") tx = list(texts(svg)) for x0, x1, y, s in tx: if x1 > vw + 2: problems.append(f'svg {i}: text depășește marginea dreaptă ({x1:.0f} > {vw:.0f}): "{s[:45]}"') if y > vh + 2 or y < 0: problems.append(f'svg {i}: text în afara înălțimii (y={y:.0f}, viewBox {vh:.0f}): "{s[:45]}"') for a in range(len(tx)): for b in range(a + 1, len(tx)): xa0, xa1, ya, sa = tx[a] xb0, xb1, yb, sb = tx[b] if abs(ya - yb) < LINE_H and xa0 < xb1 - 4 and xb0 < xa1 - 4: problems.append(f'svg {i}: texte suprapuse — "{sa[:28]}" / "{sb[:28]}"') # dacă diagrama are exact 3 niveluri de preț orizontale (SL/intrare/TP), # raportul distanțelor în preț trebuie să fie cel din pixeli lv = re.findall(r']*?/>\s*]*>([^<]*?(\d+\.\d\d)[^<]*)', svg) if len(lv) == 3: pts = sorted((float(p), float(y)) for y, _, p in lv) (plo, ylo), (pmid, ymid), (phi, yhi) = pts if pmid - plo > 0 and ylo - ymid > 0: rp = (phi - pmid) / (pmid - plo) rx = (ymid - yhi) / (ylo - ymid) if abs(rp - rx) > 0.05: problems.append(f"svg {i}: SCARĂ GREȘITĂ — raport în prețuri {rp:.2f}R, desenat {rx:.2f}R") return problems def main(): files = [Path(a) for a in sys.argv[1:]] or sorted(Path("summaries").glob("*/*.md")) total = 0 for f in files: probs = check(f) total += len(probs) print(f"\n=== {f} — {len(probs)} probleme" if probs else f"\n=== {f} — curat") for p in probs: print(" ", p) print(f"\nTOTAL: {total} probleme în {len(files)} fișiere") return 0 if __name__ == "__main__": sys.exit(main())