Files
atm-curs-dl/gen/eticheta_libera.py
Claude Agent 8f6372989d 17_STRATEGII/10_dhrt: cele sase diagrame, plus doua unelte reparate
Diagramele capitolului 10 sunt gata (un singur generator pentru toate, aceeasi
serie, deci o cifra schimbata cade peste tot). Textul capitolului nu e scris -
agentul a atins pragul de context acolo.

Doua bug-uri in unelte, gasite de el:
- substituie.py avea glob-ul [0A][1-8]_*.md, deci sarea tacut peste capitolele
  cu doua cifre (09, 10). Capitolul parea doar sa n-aiba marcaje.
- eticheta_libera presupunea text centrat cand eticheta n-are text-anchor. In
  SVG, fara anchor inseamna aliniat la stanga, deci etichetele de Level cu
  label_at=left erau modelate cu jumatate de latime prea mult la stanga si
  dadeau alarme false. L-a costat trei iteratii.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
2026-10-02 05:08:23 +00:00

107 lines
4.6 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""O etichetă de diagramă nu are voie să stea peste desen.
`audit_diagrams.py` prinde doar text peste text. Astea sunt celelalte trei
feluri de suprapunere, găsite vizual pe 02.10.2026 și ușor de regresat la
următoarea mutare de etichetă:
- `<line>` — niveluri, laturi de wedge, bracket-uri, cozi de săgeată;
- `<polyline>` — mediile mobile (punctul orb al primei versiuni);
- `<rect>` — corpurile candelelor și barele de volum.
Verificarea e opt-in, pe etichete numite: pe un grafic de candele aproape
orice text atinge ceva, iar o regulă globală ar bloca tot repo-ul.
eticheta_libera(svg, "wedge_desc", {"baza = 4.00"}, rects=True)
"""
import re
CW = 6.2 # lățimea unui caracter bold de 11-12px
WICK = "#1a1a1a" # fitilele: desenul le traversează oricum, prin construcție
def _bbox(m, min_px):
"""Caseta etichetei, lărgită cu marginea cerută."""
tx, ty, attrs, s = float(m.group(1)), float(m.group(2)), m.group(3), m.group(4)
w = CW * len(s)
# fara text-anchor, SVG aliniaza la stanga (start), nu centrat. Presupunerea
# ca e centrat dadea alarme false pe etichetele de Level cu label_at="left",
# care se deseneaza fara anchor.
x0 = tx - w if 'anchor="end"' in attrs else tx if 'anchor="middle"' not in attrs else tx - w / 2
return s, (x0 - min_px, x0 + w + min_px, ty - 9 - min_px, ty + 3 + min_px)
def _segmente(svg):
"""Toate segmentele desenate, ca perechi de capete: linii + polilinii."""
for m in re.finditer(r'<line x1="(-?[\d.]+)" y1="(-?[\d.]+)" x2="(-?[\d.]+)"'
r' y2="(-?[\d.]+)"[^/]*?stroke="(#[0-9a-f]{3,6})"', svg):
if m.group(5) != WICK:
yield ("linia", *map(float, m.groups()[:4]))
for m in re.finditer(r'<polyline points="([^"]+)"', svg):
pts = [tuple(map(float, p.split(","))) for p in m.group(1).split()]
for (ax, ay), (bx, by) in zip(pts, pts[1:]):
yield ("curba", ax, ay, bx, by)
def _atinge(box, ax, ay, bx, by):
x0, x1, top, bot = box
if max(ax, bx) < x0 or min(ax, bx) > x1:
return False
if ax == bx:
ys = (ay, by)
else: # y-ul segmentului pe porțiunea comună
ys = tuple(ay + (by - ay) * (min(max(x, min(ax, bx)), max(ax, bx)) - ax) / (bx - ax)
for x in (x0, x1))
return min(ys) <= bot and max(ys) >= top
def eticheta_libera(svg, name, etichete, min_px=5.0, rects=False, segmente=True):
"""Ridică AssertionError dacă vreuna din `etichete` stă peste desen.
`segmente=False` pentru etichetele de nivel: alea stau lipite de linia lor
prin construcție (y − 6), deci doar candelele sunt o suprapunere reală.
"""
segs = list(_segmente(svg)) if segmente else []
boxes = []
if rects:
for m in re.finditer(r'<rect x="(-?[\d.]+)" y="(-?[\d.]+)" width="([\d.]+)"'
r' height="([\d.]+)"([^/]*)/>', svg):
x, y, w, h = map(float, m.groups()[:4])
if "fill-opacity" in m.group(5): # zonele colorate stau în fundal
continue
boxes.append((x, y, x + w, y + h))
for m in re.finditer(r'<text x="(-?[\d.]+)" y="(-?[\d.]+)"([^>]*)>([^<]*)</text>', svg):
if m.group(4) not in etichete:
continue
s, box = _bbox(m, min_px)
for kind, ax, ay, bx, by in segs:
if _atinge(box, ax, ay, bx, by):
raise AssertionError(f"{name}: eticheta '{s}' sta pe {kind} "
f"({ax:.0f},{ay:.0f})-({bx:.0f},{by:.0f})")
for rx0, ry0, rx1, ry1 in boxes:
if rx0 < box[1] and rx1 > box[0] and ry0 < box[3] and ry1 > box[2]:
raise AssertionError(f"{name}: eticheta '{s}' sta pe dreptunghiul "
f"({rx0:.0f},{ry0:.0f})-({rx1:.0f},{ry1:.0f})")
def demo():
svg = ('<svg><line x1="0" y1="50" x2="100" y2="50" stroke="#c00"/>'
'<polyline points="0,200 100,120"/>'
'<rect x="0" y="300" width="20" height="40" fill="#188a3e"/>'
'<text x="10" y="55">pe linie</text>'
'<text x="90" y="128">pe curba</text>'
'<text x="10" y="310">pe candela</text>'
'<text x="10" y="400">libera</text></svg>')
for et in ("pe linie", "pe curba", "pe candela"):
try:
eticheta_libera(svg, "demo", {et}, rects=True)
except AssertionError:
pass
else:
raise AssertionError(f"'{et}' trebuia prinsa")
eticheta_libera(svg, "demo", {"libera"}, rects=True)
print("eticheta_libera: ok")
if __name__ == "__main__":
demo()