audit_diagrams.py - verificare mecanica pe diagrame: text taiat de viewBox, texte suprapuse, linii goale in <svg>, si raportul pret-vs-pixeli pe diagramele cu trei niveluri. Gasit si reparat in propriile sumarizari: - Black Swan (25.08): stop-loss-ul de 24.00 desenat prea aproape de 27.00 — raport 0.21R in preturi, 0.31R desenat. Acum la scara, deci golul de sub stop arata cat e in realitate - eticheta care descria panoul COT era pusa in panoul de pret - 5 captions taiate de marginea dreapta Plus moltbot: supervizor de repornire dupa ce workerul a murit fara urma in loguri (probabil OOM nelogat), cpu_threads 5 -> 4. Documentat in PROGRESS.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MtSTyTmt6AbCL9j5ajEDm1
86 lines
3.6 KiB
Python
86 lines
3.6 KiB
Python
"""
|
|
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/<modul>/<fisier>.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'<text\b([^>]*)>(.*?)</text>', 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'<svg .*?</svg>', 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'<line x1="[\d.]+" y1="([\d.]+)"[^>]*?/>\s*<text[^>]*>([^<]*?(\d+\.\d\d)[^<]*)</text>', 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())
|