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
107 lines
4.6 KiB
Python
107 lines
4.6 KiB
Python
"""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()
|