audit: pragul de rand derivat din font-size; si cele doua diagrame chiar stricate

Al doilea defect al uneltei: inaltimea de rand era constanta LINE_H=13,
calibrata pe 12 px, deci la etichetele de 10 px cu pas de 12 px credea ca
randurile se ating. Acum e LINE_H_RATIO x font-size-ul efectiv, mostenit
corect de pe <g>. Factorul e 1.07, nu 1.15: agentul a incercat valorile mari,
a masurat cu metrice reale de glif si a aratat ca scot cinci fantome noi.

Cele doua probleme reale pe care unealta reparata le-a scos la iveala:
atmi_ansamblu avea un caption de 792 px intr-un viewBox de 720, deci se taia
la margine; la selectia_proximitate se calcau eticheta sagetii si titlul.
Ambele reparate si privite la 1:1.

Cele 92 de diagrame: 0 probleme. Selftest-ul trece, si prinde in continuare o
suprapunere fabricata deliberat - unealta tace pentru ca e curat, nu pentru ca
a orbit.

Ramane, raportat si nereparat: CHAR_W = 5.7 e tot calibrat pe 12 px, deci
latimea textelor mici e supraestimata. Aceeasi clasa de bug, n-a produs inca
raportari gresite.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
This commit is contained in:
Claude Agent
2026-09-22 20:45:50 +00:00
parent 5b157116d3
commit 43e6fbcd32
9 changed files with 488 additions and 378 deletions

View File

@@ -13,6 +13,7 @@ Rulează:
python3 gen/strategii/atmi_ansamblu.py
Scrie lângă el `atmi_ansamblu.svg`.
"""
import re
import sys
from pathlib import Path
@@ -84,11 +85,13 @@ marks = [
Mark(LAST, "ieși cu tot", BLUE, "below", dy=22),
]
caption = ("100.00 / 96.00 / 105.60 sunt cifrele exemplului din curs; PPQ 95.80 e calculat "
"în diagrama pasului 1 (cursul nu dă valorile pivoților).")
ch = Chart(candles=cds, levels=levels, marks=marks,
scale=22.0, gap=17, body_w=9, left=44, width=720, vol_h=70, vol_gap=72, top=88,
vol_label="volum",
caption="100.00 / 96.00 / 105.60 sunt cifrele exemplului din curs; PPQ 95.80 e calculat "
"în diagrama pasului 1 (cursul nu dă valorile pivoților).")
caption=caption)
svg = ch.render()
@@ -125,6 +128,46 @@ titlu = ('<text x="24" y="26" fill="#333" style="font-weight:bold">'
i = svg.index(">") + 1
svg = svg[:i] + "\n " + "\n ".join([ppq, no_sl, titlu]) + svg[i:]
# ---------------------------------------------------------------- caption pe două rânduri
# Captionul are 135 de caractere, adică ~794 px de la x=24 într-o pânză lată de
# 720: se rupe pe două rânduri, ca în selectia_ansamblu.py.
def _wrap(text: str, max_chars: int) -> list[str]:
lines, cur = [], ""
for w in text.split():
if cur and len(cur) + 1 + len(w) > max_chars:
lines.append(cur)
cur = w
else:
cur = f"{cur} {w}".strip()
if cur:
lines.append(cur)
return lines
cap_lines = _wrap(caption, 80)
assert len(cap_lines) == 2, cap_lines
m = re.search(r'<text x="24" y="(\d+)" fill="#555">' + re.escape(caption) + r"</text>", svg)
assert m, "caption-ul nu a fost găsit în SVG"
y0 = int(m.group(1))
wrapped = "\n ".join(f'<text x="24" y="{y0 + 17 * i}" fill="#555">{ln}</text>'
for i, ln in enumerate(cap_lines))
svg = svg[:m.start()] + wrapped + svg[m.end():]
vm = re.search(r'viewBox="0 0 (\d+) (\d+)"', svg)
assert vm, "nu găsesc viewBox-ul"
svg = svg.replace(vm.group(0),
f'viewBox="0 0 {vm.group(1)} {int(vm.group(2)) + 17 * (len(cap_lines) - 1)}"', 1)
# fiecare rând de caption CHIAR DESENAT trebuie să încapă în lățimea pânzei.
# CHAR_W = 5.7 e lățimea medie per caracter calibrată de audit_diagrams.py;
# assertul prinde exact defectul vechi, când captionul era un singur text de
# 135 de caractere: 24 + 135 × 5.7 = 794 > 720.
CHAR_W = 5.7
vw = int(vm.group(1))
for m_cap in re.finditer(r'<text x="24" y="[\d.]+" fill="#555">([^<]*)</text>', svg):
ln = m_cap.group(1)
x1 = 24 + len(ln) * CHAR_W
assert x1 <= vw, f"rând de caption tăiat la marginea dreaptă ({x1:.0f} > {vw}): {ln!r}"
assert svg.count("\n\n") == 0, "linie goală în SVG rupe blocul din markdown"
assert ".md:" not in svg, "trimitere fișier:linie în SVG"
OUT.write_text(svg)