eticheta_libera: filtru orb pentru cifrele care dispar sub candele
cifre_ascunse() ruleaza pe tot repo-ul, nu pe etichete numite, si raporteaza doar cazul care chiar costa: eticheta cu cifra peste un <rect> plin, fie de aceeasi culoare (distanta RGB sub 60, cu transparenta amestecata peste alb), fie desenat dupa ea si deci acoperind-o indiferent de culoare. Al doilea caz a fost adaugat pentru ca m2d_directie avea text gri sub corp verde - aceeasi paguba, dar filtrul pe culoare singur il rata. Calibrare care conta: CW=6.2 e potrivit pentru bold si prea larg pentru rest, iar coada goala prindea candela de alaturi. CW=5.4 pentru nebold a eliminat primele doua rezultate, verificate pe ecran ca false pozitive. Ancora inversa: pe versiunile dinainte de reparatiile de azi filtrul prinde exact cele doua defecte confirmate vizual. Scanarea: 17 etichete in 13 diagrame, din care 9 cu gaura pe cifra. Noua din cele 17 sunt in summaries/06_MODUL_5, unde nu s-a facut niciodata o trecere de etichete; in gen/strategii, dupa trei treceri, au ramas cinci. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ThN7ixZrowca8aTGrK1MUJ
This commit is contained in:
@@ -168,6 +168,146 @@ def eticheta_libera(svg, name, etichete, min_px=5.0, rects=False, segmente=True)
|
|||||||
f"({rx0:.0f},{ry0:.0f})-({rx1:.0f},{ry1:.0f})")
|
f"({rx0:.0f},{ry0:.0f})-({rx1:.0f},{ry1:.0f})")
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# Filtrul orb: o cifră scrisă peste un corp plin de aceeași culoare
|
||||||
|
#
|
||||||
|
# Trierea vizuală din 02.10.2026 (02, 04, A3, 13 diagrame citite pe ecran) a
|
||||||
|
# dat o singură cauză fizică pentru suprapunerile care chiar schimbă ce
|
||||||
|
# citești: textul stă peste un `<rect>` **de culoarea lui**, iar caracterele
|
||||||
|
# nu se încalecă — dispar („intrare 577.00" se citea „intrare 577. 0").
|
||||||
|
# Celelalte ~91 de atingeri — text peste fitil, peste linia unui nivel, peste
|
||||||
|
# chenar, peste coada unei săgeți — rămân lizibile: fundalul e o linie
|
||||||
|
# subțire, nu o suprafață plină.
|
||||||
|
#
|
||||||
|
# De aceea filtrul ăsta NU e opt-in pe etichete numite ca `eticheta_libera`:
|
||||||
|
# fiind îngust, poate rula orb pe tot repo-ul.
|
||||||
|
#
|
||||||
|
# Ordinea de desenare nu contează: verde pe verde e invizibil și dacă textul
|
||||||
|
# e pictat deasupra. De aceea singurul criteriu e culoarea, nu ordinea.
|
||||||
|
|
||||||
|
DIST = 60.0 # distanță RGB sub care două culori nu se mai disting
|
||||||
|
ACOPERIRE = 4.0 # px de suprapunere pe orizontală sub care e doar atingere
|
||||||
|
CW_N = 5.4 # un caracter de 12px NEbold e mai îngust decât CW
|
||||||
|
# `_bbox` folosește CW=6.2 peste tot, bun pentru etichetele bold și prudent
|
||||||
|
# pentru restul. Aici prudența costă: la o etichetă lungă și nebold, cei ~0.8
|
||||||
|
# px pe caracter se adună într-o coadă goală de 30 px, care prinde candela de
|
||||||
|
# alături și raportează o înghițire care nu există (`buy_doji_labu`).
|
||||||
|
|
||||||
|
|
||||||
|
def _rgb(c, alpha=1.0):
|
||||||
|
"""#rgb/#rrggbb -> (r, g, b), amestecat peste alb dacă e transparent."""
|
||||||
|
c = c.lstrip("#")
|
||||||
|
if len(c) == 3:
|
||||||
|
c = "".join(ch * 2 for ch in c)
|
||||||
|
v = tuple(int(c[i:i + 2], 16) for i in (0, 2, 4))
|
||||||
|
return tuple(255 - (255 - x) * alpha for x in v)
|
||||||
|
|
||||||
|
|
||||||
|
def _departe(a, b):
|
||||||
|
return sum((x - y) ** 2 for x, y in zip(a, b)) ** 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def _panouri(svg):
|
||||||
|
"""Fiecare `<svg>` imbricat e un sistem de coordonate separat.
|
||||||
|
|
||||||
|
Întoarce bucățile care se pot compara între ele: panoul exterior fără
|
||||||
|
imbricate, apoi fiecare imbricat, recursiv.
|
||||||
|
"""
|
||||||
|
out, adanc, start = [], 0, None
|
||||||
|
rest = []
|
||||||
|
i = 0
|
||||||
|
for m in re.finditer(r"<svg\b|</svg>", svg):
|
||||||
|
if m.group(0) == "</svg>":
|
||||||
|
adanc -= 1
|
||||||
|
if adanc == 1 and start is not None:
|
||||||
|
out.extend(_panouri(svg[start:m.end()]))
|
||||||
|
i = m.end()
|
||||||
|
start = None
|
||||||
|
else:
|
||||||
|
adanc += 1
|
||||||
|
if adanc == 2:
|
||||||
|
rest.append(svg[i:m.start()])
|
||||||
|
start = m.start()
|
||||||
|
rest.append(svg[i:])
|
||||||
|
return ["".join(rest)] + out
|
||||||
|
|
||||||
|
|
||||||
|
def cifre_ascunse(svg, nume=""):
|
||||||
|
"""Etichetele cu cifre care stau peste un dreptunghi de culoarea lor.
|
||||||
|
|
||||||
|
Întoarce o listă de dicte (fără să ridice nimic): `eticheta`, `culoare`,
|
||||||
|
`peste` (culoarea efectivă a dreptunghiului) și `px` — cât din lățimea
|
||||||
|
etichetei e înghițit.
|
||||||
|
"""
|
||||||
|
gasite = []
|
||||||
|
for panou in _panouri(svg):
|
||||||
|
panou = _aplatizeaza(panou)
|
||||||
|
rects = []
|
||||||
|
for m in re.finditer(r'<rect ([^>]*?)/?>', panou):
|
||||||
|
a = dict(_ATTR.findall(m.group(1)))
|
||||||
|
fill = re.search(r'fill="(#[0-9a-fA-F]{3,6})"', m.group(1))
|
||||||
|
if not fill or "width" not in a or "height" not in a:
|
||||||
|
continue
|
||||||
|
op = float(a.get("opacity", 1.0)) * float(a.get("fill-opacity", 1.0))
|
||||||
|
x, y = float(a["x"]), float(a["y"])
|
||||||
|
rects.append((x, y, x + float(a["width"]), y + float(a["height"]),
|
||||||
|
_rgb(fill.group(1), op), op, m.start()))
|
||||||
|
for m in re.finditer(r'<text x="(-?[\d.]+)" y="(-?[\d.]+)"([^>]*)>([^<]*)</text>',
|
||||||
|
panou):
|
||||||
|
s = m.group(4)
|
||||||
|
if not re.search(r"\d", s):
|
||||||
|
continue
|
||||||
|
culoare = re.search(r'fill="(#[0-9a-fA-F]{3,6})"', m.group(3))
|
||||||
|
if not culoare:
|
||||||
|
continue
|
||||||
|
c = _rgb(culoare.group(1))
|
||||||
|
_, (x0, x1, top, bot) = _bbox(m, 0.0)
|
||||||
|
if "bold" not in m.group(3): # caseta reală, nu cea prudentă
|
||||||
|
w = (x1 - x0) * CW_N / CW
|
||||||
|
x0, x1 = ((x1 - w, x1) if 'anchor="end"' in m.group(3) else
|
||||||
|
((x0 + x1 - w) / 2, (x0 + x1 + w) / 2)
|
||||||
|
if 'anchor="middle"' in m.group(3) else (x0, x0 + w))
|
||||||
|
for rx0, ry0, rx1, ry1, rc, op, poz in rects:
|
||||||
|
lat = min(x1, rx1) - max(x0, rx0)
|
||||||
|
inalt = min(bot, ry1) - max(top, ry0)
|
||||||
|
if lat < ACOPERIRE or inalt < 4.0:
|
||||||
|
continue
|
||||||
|
if _departe(c, rc) <= DIST:
|
||||||
|
de_ce = "aceeași culoare"
|
||||||
|
elif poz > m.start() and op >= 0.5:
|
||||||
|
# desenat PESTE text: îl acoperă oricare i-ar fi culoarea
|
||||||
|
# (`m2d_directie`: corp verde peste „…mișcarea 99.80" gri)
|
||||||
|
de_ce = "desenat peste"
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
# ce anume dispare: lățimea e aproximată uniform, deci
|
||||||
|
# bucata e orientativă — spune însă dacă gaura cade pe cifră
|
||||||
|
lat_car = (x1 - x0) / max(len(s), 1)
|
||||||
|
i0 = int((max(x0, rx0) - x0) / lat_car)
|
||||||
|
i1 = min(len(s), int((min(x1, rx1) - x0) / lat_car) + 1)
|
||||||
|
gasite.append({"diagrama": nume, "eticheta": s, "de_ce": de_ce,
|
||||||
|
"culoare": culoare.group(1), "px": round(lat),
|
||||||
|
"inghitit": s[i0:i1],
|
||||||
|
"peste": tuple(round(v) for v in rc)})
|
||||||
|
break
|
||||||
|
return gasite
|
||||||
|
|
||||||
|
|
||||||
|
def blocuri_svg(text):
|
||||||
|
"""Blocurile `<svg>…</svg>` de nivel întâi dintr-un .md sau .html."""
|
||||||
|
adanc, start = 0, None
|
||||||
|
for m in re.finditer(r"<svg\b|</svg>", text):
|
||||||
|
if m.group(0) == "</svg>":
|
||||||
|
adanc -= 1
|
||||||
|
if adanc == 0 and start is not None:
|
||||||
|
yield text[start:m.end()]
|
||||||
|
start = None
|
||||||
|
else:
|
||||||
|
if adanc == 0:
|
||||||
|
start = m.start()
|
||||||
|
adanc += 1
|
||||||
|
|
||||||
|
|
||||||
def demo():
|
def demo():
|
||||||
svg = ('<svg><line x1="0" y1="50" x2="100" y2="50" stroke="#c00"/>'
|
svg = ('<svg><line x1="0" y1="50" x2="100" y2="50" stroke="#c00"/>'
|
||||||
'<polyline points="0,200 100,120"/>'
|
'<polyline points="0,200 100,120"/>'
|
||||||
@@ -215,6 +355,47 @@ def demo():
|
|||||||
raise AssertionError("'pe candela scalata' trebuia prinsa")
|
raise AssertionError("'pe candela scalata' trebuia prinsa")
|
||||||
print("eticheta_libera: ok")
|
print("eticheta_libera: ok")
|
||||||
|
|
||||||
|
# --- filtrul orb: cifră peste un corp de aceeași culoare ---------------
|
||||||
|
# cazul real (mp2_exemplu, 02.10.2026): verde pe verde, caracterul dispare
|
||||||
|
verde = ('<svg><rect x="40" y="90" width="9" height="30" fill="#188a3e"/>'
|
||||||
|
'<line x1="0" y1="100" x2="200" y2="100" stroke="#188a3e"/>'
|
||||||
|
'<polyline points="0,100 200,100" stroke="#188a3e"/>'
|
||||||
|
'<rect x="40" y="300" width="9" height="30" fill="#188a3e" opacity="0.55"/>'
|
||||||
|
'<rect x="140" y="90" width="9" height="30" fill="#c00"/>'
|
||||||
|
'<text x="10" y="100" fill="#188a3e">intrare 577.00</text>'
|
||||||
|
'<text x="120" y="100" fill="#188a3e">stop 554.40</text>'
|
||||||
|
'<text x="10" y="310" fill="#188a3e">volum 1.2 mil.</text>'
|
||||||
|
'<text x="10" y="400" fill="#188a3e">liber 100.00</text>'
|
||||||
|
'<text x="10" y="500" fill="#188a3e">fara cifra aici</text></svg>')
|
||||||
|
prinse = {g["eticheta"] for g in cifre_ascunse(verde)}
|
||||||
|
assert prinse == {"intrare 577.00"}, prinse
|
||||||
|
# dovada pe rând: linia și curba de aceeași culoare NU se aprind (textul
|
||||||
|
# rămâne lizibil peste un fir de 1 px), corpul roșu de sub text nici el
|
||||||
|
# (altă culoare = contrast), bara de volum la 0.55 nici ea (e pală), iar
|
||||||
|
# eticheta fără cifră nu intră în filtru deloc.
|
||||||
|
print("cifre_ascunse: ok")
|
||||||
|
|
||||||
|
|
||||||
|
def scaneaza(cai):
|
||||||
|
"""Raport pe fișiere: .svg întregi, sau blocurile <svg> dintr-un .md/.html."""
|
||||||
|
from pathlib import Path
|
||||||
|
tot = []
|
||||||
|
for cale in cai:
|
||||||
|
text = Path(cale).read_text(encoding="utf-8")
|
||||||
|
bucati = [text] if str(cale).endswith(".svg") else list(blocuri_svg(text))
|
||||||
|
for k, b in enumerate(bucati):
|
||||||
|
nume = Path(cale).name + (f" #{k + 1}" if len(bucati) > 1 else "")
|
||||||
|
tot += cifre_ascunse(b, nume)
|
||||||
|
for g in tot:
|
||||||
|
print(f"{g['diagrama']}: '{g['eticheta']}' — dispare ~'{g['inghitit']}' "
|
||||||
|
f"({g['px']} px, {g['de_ce']})")
|
||||||
|
print(f"{len(tot)} etichete cu cifre înghițite de un corp plin")
|
||||||
|
return tot
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
import sys
|
||||||
|
if len(sys.argv) > 1:
|
||||||
|
scaneaza(sys.argv[1:])
|
||||||
|
else:
|
||||||
demo()
|
demo()
|
||||||
|
|||||||
@@ -147,3 +147,84 @@ Doua defecte vechi, vazute dar neatinse (nu erau pe lista lui):
|
|||||||
rasturnarea exemplului long)" e traversat de coada sagetii „piciorul 1".
|
rasturnarea exemplului long)" e traversat de coada sagetii „piciorul 1".
|
||||||
- `mdsrt_aplus`, panoul NU — `cds_n[18].l` e 94.50, dar nivelul se cheama
|
- `mdsrt_aplus`, panoul NU — `cds_n[18].l` e 94.50, dar nivelul se cheama
|
||||||
„primul low 94.60". E close-ul, nu low-ul.
|
„primul low 94.60". E close-ul, nu low-ul.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Filtrul orb: cifră peste un corp plin (scanare 02.10.2026)
|
||||||
|
|
||||||
|
Cerut după trierea vizuală a capitolelor 02 / 04 / A3, care a dat o singură
|
||||||
|
cauză fizică pentru suprapunerile care chiar schimbă ce citești: textul stă
|
||||||
|
peste un `<rect>` **plin**, iar caracterele nu se încalecă — dispar. Peste o
|
||||||
|
linie subțire (nivel, fitil, coadă de săgeată, chenar) textul rămâne lizibil,
|
||||||
|
oricât de urât arată. De aceea tiparele (a)-(d) de mai sus nu merită un lot,
|
||||||
|
dar clasa asta merită o listă.
|
||||||
|
|
||||||
|
`gen/eticheta_libera.py` are acum `cifre_ascunse(svg)`, care nu e opt-in pe
|
||||||
|
etichete numite — fiind îngustă, rulează oarbă:
|
||||||
|
|
||||||
|
.venv/bin/python gen/eticheta_libera.py gen/strategii/*.svg \
|
||||||
|
summaries/06_MODUL_5/*.md
|
||||||
|
|
||||||
|
Ce raportează: o etichetă **care conține o cifră**, peste un dreptunghi plin,
|
||||||
|
în unul din două cazuri — (1) **aceeași culoare** (verde pe verde: caracterul
|
||||||
|
dispare oricare ar fi ordinea de desenare), sau (2) **desenat peste** text
|
||||||
|
(corpul e pictat după etichetă, deci o acoperă indiferent de culoare — cazul
|
||||||
|
`m2d_directie` din lista „mincinoase"). Pragurile: distanță RGB sub 60,
|
||||||
|
suprapunere de cel puțin 4 px pe orizontală și 4 px pe verticală, opacitatea
|
||||||
|
dreptunghiului cel puțin 0.5 (zonele palide nu acoperă nimic). Transparența se
|
||||||
|
amestecă peste alb înainte de comparație, deci barele de volum la 0.55 nu se
|
||||||
|
aprind. Bucata „dispare ~" e aproximativă (lățime uniformă pe caracter), dar
|
||||||
|
spune dacă gaura cade pe cifră sau pe o literă.
|
||||||
|
|
||||||
|
Calibrare: lățimea din `_bbox` (CW = 6.2) e potrivită pentru bold și prea
|
||||||
|
largă pentru restul; la o etichetă lungă nebold coada goală de ~30 px prindea
|
||||||
|
candela de alături (`buy_doji_labu`, `mp2_aplus` — ambele verificate pe ecran,
|
||||||
|
false pozitive). Filtrul folosește CW = 5.4 pentru textul nebold. Pe versiunea
|
||||||
|
de dinaintea reparațiilor de azi, `mp2_exemplu` („intrare 577.00", 0 păstrat de
|
||||||
|
corpul verde) și `m2d_directie` (ambele panouri) sunt prinse — cele două
|
||||||
|
defecte reale confirmate vizual, deci filtrul nu e doar strâmt, e și calibrat.
|
||||||
|
|
||||||
|
### Rezultat: 17 etichete, în 13 diagrame
|
||||||
|
|
||||||
|
**Gaura cade pe cifră (9):**
|
||||||
|
|
||||||
|
```
|
||||||
|
m2d_iesire.svg: 'intrare 99.60' — dispare ~'60' (12 px, desenat peste)
|
||||||
|
mdsrt_exemplu.svg: 'TP1 624.70 = 1:1 (construit)' — dispare ~'1 (' (9 px, desenat peste)
|
||||||
|
mp2_short.svg: '128.97: crossing bear' — dispare ~'8.' (9 px, desenat peste)
|
||||||
|
02_patternuri_complexe_partea_a_treia.md #3: 'SL 29.40' — dispare ~'9.4' (9 px, aceeași culoare)
|
||||||
|
02_patternuri_complexe_partea_a_treia.md #4: 'target 62.00 = 65.00 − 3.00' — dispare ~'62.' (9 px, desenat peste)
|
||||||
|
02_patternuri_complexe_partea_a_treia.md #5: 'target 60.50 = 65.00 − 4.50' — dispare ~'0.5' (9 px, desenat peste)
|
||||||
|
02_patternuri_complexe_partea_a_treia.md #8: 'intrare short 89.00' — dispare ~' 8' (9 px, desenat peste)
|
||||||
|
02_patternuri_complexe_partea_a_treia.md #8: 'target 60.00 = 90.00 − 30.00' — dispare ~' 90' (9 px, desenat peste)
|
||||||
|
04_strategia_7_micis.md #6: 'intri cu 1/3 și rămâi cu 1/3' — dispare ~'1/3' (10 px, aceeași culoare)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Gaura cade pe o literă (8) — eticheta are cifre, dar ce dispare e text:**
|
||||||
|
|
||||||
|
```
|
||||||
|
hammer_mbt10_bh_stop.svg: 'intrare 63.35 (close-ul confirmării)' — dispare ~'irm' (13 px, desenat peste)
|
||||||
|
hammer_mbt10_mbt_close.svg: 'la 55.30, în timpul zilei, semnalul' — dispare ~'sem' (13 px, aceeași culoare)
|
||||||
|
mdsrt_exemplu.svg: 'hammer închide 622.60, înapoi peste pivot' — dispare ~'oi' (9 px, aceeași culoare)
|
||||||
|
mdsrt_exemplu.svg: 'intri aici: close peste high-ul hammer-ului (622.70)' — dispare ~'r-' (9 px, aceeași culoare)
|
||||||
|
02_patternuri_complexe_partea_a_treia.md #5: 'suport (neckline) 65.00' — dispare ~'in' (9 px, desenat peste)
|
||||||
|
02_patternuri_complexe_partea_a_treia.md #7: 'limita de 50% 100.00' — dispare ~'ta ' (9 px, desenat peste)
|
||||||
|
03_patternuri_complexe_partea_a_patra.md #8: 'TP2 42.40 = batul steagului = 6.2R' — dispare ~' = ' (9 px, desenat peste)
|
||||||
|
05_strategia_8_dhrt.md #10: 'vârful consolidării 53.70 = locul crimei' — dispare ~'ons' (12 px, desenat peste)
|
||||||
|
```
|
||||||
|
|
||||||
|
Verificate pe ecran, cu zoom 4x: 6 din 17, luate din ambele categorii
|
||||||
|
(`m2d_iesire` „99.6|", `mdsrt_exemplu` „= 1:_ (construit)", `mp2_short` cu
|
||||||
|
„128" sub corpul roșu, `hammer_mbt10_bh_stop` „confirm_rii",
|
||||||
|
`02_patternuri…` „neckl_ne", `05_strategia_8_dhrt` „c_nsolidării"). Toate
|
||||||
|
șase, reale. Nu am reparat nimic și n-am atins capitolele 06, 09, 10, care
|
||||||
|
sunt în lucru la alt agent.
|
||||||
|
|
||||||
|
Două observații pentru cine ia lista:
|
||||||
|
|
||||||
|
- Jumătate din cazuri înghit o literă, nu o cifră: filtrul cere ca eticheta să
|
||||||
|
**conțină** o cifră, nu ca gaura să cadă pe ea. Dacă lotul trebuie scurtat,
|
||||||
|
primele 9 sunt cele care costă.
|
||||||
|
- Nouă din cele 17 sunt în `summaries/06_MODUL_5/` — adică în
|
||||||
|
diagramele de modul, nu în cele de strategie. Acolo nu s-a făcut niciodată
|
||||||
|
o trecere de etichete.
|
||||||
|
|||||||
Reference in New Issue
Block a user