feat(5.15+5.14): CLOSE — fix-uri code-review + embeddings functional

5.15 (propagare design + dashboard editare) si 5.14 (mapare LLM distilata)
inchise dupa /code-review high. 8 buguri reparate TDD:

- HIGH modal nu se deschidea pe randul slim (base.html: trimitere-slim)
- HIGH /repune trunchia prestatii (declaratie incompleta la RAR) -> iterare
  peste existing, codes pozitional
- HIGH embeddings incarca model ~230MB degeaba pe corpus gol -> poarta has_corpus()
- HIGH picker chips gol pe re-render eroare -> conn/account_id pe toate ramurile
- MED obs re-derivat dupa stergere explicita -> _merge_override pastreaza obs=''
- MED mapare salvata fara denumire poluă GOLD -> _record_gold_validation guard
- MED typo nome_prestatie -> nume_prestatie in select /repune
- MED bucketare timp +3h gresita iarna -> SQLite localtime + TZ=Europe/Bucharest

Embeddings WIRE-uit functional (PRD #15, decizie user): ensure_embeddings_corpus
construieste corpus din nomenclator, gated pe AUTOPASS_EMBEDDINGS_ENABLED (default
off). Marime model corectata ~50MB->~230MB (estimare PRD gresita).

Cleanup: hoist load_* din bucla bulk-fix; import re la top.
Regresie: 1256 passed, 1 deselected (live), 0 failed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Claude Agent
2026-06-28 20:48:34 +00:00
parent 9e42e7ed6f
commit 3fc53534e2
53 changed files with 9684 additions and 384 deletions

View File

@@ -16,13 +16,16 @@
<script>
// Anti-FOUC: citeste preferinta tema din localStorage inainte de primul
// paint; seteaza data-theme pe <html> sincron, fara blink.
// Cunoaste toate cele 4 teme: light/dark/petrol/auto. Valoare legacy/necunoscuta -> auto.
// 'auto' se rezolva la 'light' sau 'dark' dupa prefers-color-scheme (fara blink).
// Cunoaste TOATE cele 7+1 teme: light/dark/petrol/grafit/cobalt/cupru/hartie + auto.
// Valori legacy (light/dark/petrol) raman valide — fara migrare fortata.
// Valoare lipsa/necunoscuta -> auto (fallback sigur, fara blink).
// 'auto' se rezolva la 'light' sau 'dark' dupa prefers-color-scheme (fara blink):
// auto + dark OS -> 'dark' | auto + light OS -> 'light' (comportament existent pastrat).
(function() {
var VALID = {light:1, dark:1, petrol:1, auto:1};
var VALID = {light:1, dark:1, petrol:1, grafit:1, cobalt:1, cupru:1, hartie:1, auto:1};
try {
var t = localStorage.getItem('theme');
if (!t || !VALID[t]) t = 'auto'; // fallback: valoare lipsa sau legacy -> auto
if (!t || !VALID[t]) t = 'auto'; // fallback: valoare lipsa sau necunoscuta -> auto
if (t === 'auto') {
t = window.matchMedia('(prefers-color-scheme: light)').matches ? 'light' : 'dark';
}
@@ -100,16 +103,32 @@
src: url("/static/fonts/IBMPlexMono-Regular-latin.woff2") format("woff2");
unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+20AC, U+2122, U+FEFF, U+FFFD;
}
/* Paleta dark (default) — accent azur ROMFAST */
:root { --bg:#0f1218; --card:#181c24; --ink:#e6e9ef; --muted:#8b93a7; --line:#262b36;
/* Paleta dark (default) — accent azur ROMFAST.
--card2: fundal input/contor (= --bg, nivelul cel mai adanc).
--line2: separator subtire (intre --bg si --line). */
:root { --bg:#0f1218; --card:#181c24; --card2:#0f1218; --ink:#e6e9ef; --muted:#8b93a7; --line:#262b36; --line2:#1f2530;
--ok:#2FBF8F; --warn:#E0A93B; --err:#E05D5D; --accent:#2E74D6; }
/* Paleta light — accent azur inchis pentru contrast AA pe alb (#1F66C9: 5.51:1 pe alb) */
[data-theme="light"] { --bg:#f5f7fa; --card:#ffffff; --ink:#1a1d24; --muted:#5c6473; --line:#e2e5ea;
[data-theme="light"] { --bg:#f5f7fa; --card:#ffffff; --card2:#f5f7fa; --ink:#1a1d24; --muted:#5c6473; --line:#e2e5ea; --line2:#eaedf2;
--ok:#15803d; --warn:#b45309; --err:#dc2626; --accent:#1F66C9; }
/* Paleta Petrol — tema intunecata alternativa, accent teal #0E7C7B.
Wordmark-ul FAST #2E74D6 coexista armonios: ambele sunt reci/saturate, contrast AA pe --card #161e20. */
[data-theme="petrol"] { --bg:#0e1416; --card:#161e20; --ink:#e6e9ef; --muted:#8b93a7; --line:#232c2e;
[data-theme="petrol"] { --bg:#0e1416; --card:#161e20; --card2:#0e1416; --ink:#e6e9ef; --muted:#8b93a7; --line:#232c2e; --line2:#1c2426;
--ok:#2FBF8F; --warn:#E0A93B; --err:#E05D5D; --accent:#0E7C7B; }
/* Paleta Grafit — similara cu dark, accent azur deschis (#6ea2ec = landing --infot).
Distinta de dark la cererea userului (D2 PRD 5.15). */
[data-theme="grafit"] { --bg:#0f1218; --card:#181c24; --card2:#0f1218; --ink:#e6e9ef; --muted:#8b93a7; --line:#262b36; --line2:#1f2530;
--ok:#2FBF8F; --warn:#E0A93B; --err:#E05D5D; --accent:#6ea2ec; }
/* Paleta Cobalt — fundal bleumarin adanc, accent albastru viu (#8aa0ff = landing --infot). */
[data-theme="cobalt"] { --bg:#080d1c; --card:#111a33; --card2:#0b1226; --ink:#e9ecfb; --muted:#8a93b8; --line:#1d2747; --line2:#161f3a;
--ok:#2fd0a6; --warn:#E0A93B; --err:#f06a7a; --accent:#8aa0ff; }
/* Paleta Cupru — fundal cald ciocolata, accent chihlimbar (#dfa45c = landing --infot). */
[data-theme="cupru"] { --bg:#15110b; --card:#211a12; --card2:#15110b; --ink:#efe6d6; --muted:#a89a85; --line:#36291c; --line2:#281e14;
--ok:#67b98c; --warn:#c97d2e; --err:#e2685a; --accent:#dfa45c; }
/* Paleta Hartie — fundal crem cald, accent albastru clasic (#1F5FBF = landing --infot = --accent).
Similara cu light, distinta la cererea userului (D2 PRD 5.15). */
[data-theme="hartie"] { --bg:#f3efe6; --card:#fffdf7; --card2:#f3efe6; --ink:#1e1a13; --muted:#6a6052; --line:#e2dccc; --line2:#ece6d9;
--ok:#1c7d5d; --warn:#b45309; --err:#bd463c; --accent:#1F5FBF; }
* { box-sizing:border-box; }
/* CONVENTIE BREAKPOINT: un singur prag mobil la 768px.
CSS custom properties NU functioneaza in `@media`, deci pragul nu poate fi o
@@ -675,6 +694,62 @@
.sticky-bar { padding:10px 12px; gap:10px; }
.sticky-bar button { width:100%; min-height:44px; }
}
/* === SENTINEL-COMPONENTE-SLIM: inceput componente slim US-002 (PRD 5.15).
Testele ancoreaza pe acest marker. Nu muta/sterge. === */
/* .contor-card — card cifra contor: fundal --card2, bordura --line, radius 8px, padding 10-12px.
Variante de culoare a cifrei prin clasele .s-* existente (verde/accent/rosu). */
.contor-card { background:var(--card2); border:1px solid var(--line); border-radius:8px; padding:10px 12px; }
.contor-cifra { font-size:22px; font-weight:700; line-height:1; }
.contor-label { font-size:11px; color:var(--muted); margin-top:5px; }
.contor-sub { font-family:"IBM Plex Mono",ui-monospace,monospace; font-size:10px; color:var(--muted); margin-top:3px; }
/* .lista-trimiteri-slim + .trimitere-slim — lista compacta cu separator --line2.
Randul e clickabil (rol button), tinta min-height:44px pe mobil. */
.lista-trimiteri-slim { list-style:none; margin:0; padding:0; }
.trimitere-slim { display:flex; align-items:center; justify-content:space-between; gap:12px;
padding:11px 14px; border-bottom:1px solid var(--line2); min-height:44px; cursor:pointer; }
.trimitere-slim:last-child { border-bottom:none; }
.trimitere-slim:hover { background:color-mix(in srgb, var(--accent) 6%, transparent); }
.trimitere-slim:focus, .trimitere-slim:focus-visible { outline:2px solid var(--accent); outline-offset:-2px; }
.slim-vin { font-family:"IBM Plex Mono",ui-monospace,monospace; font-size:13px; font-weight:500; color:var(--ink); }
.slim-meta { font-size:11px; color:var(--muted); margin-top:3px; }
/* .camp-slim — varianta compacta camp formular: label 11px muted deasupra, input ~30px, fundal --card2.
Mono pentru campuri VIN/odometru/nr: adauga clasa .camp-mono pe input. */
.camp-slim { margin-bottom:8px; }
.camp-slim label { font-size:11px; color:var(--muted); display:block; margin-bottom:4px; }
.camp-slim input, .camp-slim textarea, .camp-slim select { background:var(--card2); height:30px; width:100%;
padding:0 10px; border:1px solid var(--line); border-radius:6px; font:inherit; color:var(--ink); }
.camp-slim textarea { height:auto; min-height:48px; padding:8px 10px; resize:vertical; }
.camp-slim .camp-mono { font-family:"IBM Plex Mono",ui-monospace,monospace; font-size:12px; }
/* .chips + .chip — prestatii multi-select cu buton de stergere accesibil (.chip-del).
Fundal accent 18%, font IBM Plex Mono 11px. */
.chips { min-height:30px; display:flex; align-items:center; gap:6px; flex-wrap:wrap;
padding:4px 8px; border:1px solid var(--line); border-radius:6px; background:var(--card2); }
.chip { display:inline-flex; align-items:center; gap:5px; padding:3px 8px; border-radius:5px;
background:color-mix(in srgb, var(--accent) 18%, transparent); color:var(--accent);
font-family:"IBM Plex Mono",ui-monospace,monospace; font-size:11px; font-weight:600; }
.chip .chip-del { background:transparent; border:none; color:inherit; opacity:.7; cursor:pointer;
padding:0; font-size:13px; line-height:1; display:inline-flex;
align-items:center; justify-content:center; min-width:16px; min-height:16px; }
.chip .chip-del:hover, .chip .chip-del:focus-visible { opacity:1; }
.chip .chip-del:focus-visible { outline:2px solid var(--accent); outline-offset:1px; }
/* Varianta chip warn (ex. R-ODO necesita odometruInitial) */
.chip-warn { background:color-mix(in srgb, var(--warn) 22%, transparent); color:var(--warn); }
/* .add-code — buton dashed pentru adaugare cod in chipbox */
.add-code { display:inline-flex; align-items:center; height:22px; padding:0 7px; background:transparent;
border:1px dashed color-mix(in srgb, var(--accent) 55%, var(--line));
border-radius:5px; color:var(--accent); font:500 10px inherit; cursor:pointer; }
.add-code:hover, .add-code:focus-visible { border-style:solid; }
/* .op-row — rand operatie cu picker op<->cod (E4): operatie + chip cod + picker */
.op-row { display:flex; align-items:center; justify-content:space-between; gap:10px;
padding:8px 10px; border:1px solid var(--line); border-radius:6px;
background:var(--card2); margin-bottom:8px; }
.op-row-name { font-size:12px; font-weight:500; color:var(--ink); }
.op-row-warn { border-color:color-mix(in srgb, var(--warn) 45%, var(--line)); }
/* Mobil: tinta touch pentru trimitere-slim (deja garantata prin min-height:44px in regula de baza) */
@media (max-width:767px) {
.trimitere-slim { padding:12px 14px; }
}
/* === SENTINEL-COMPONENTE-SLIM: sfarsit componente slim US-002 === */
</style>
</head>
<body>
@@ -749,18 +824,36 @@
</div>
</div>
<script>
// Comutator tema ciclic: click cicleaza Light->Dark->Petrol->Auto.
// Separare init (sincronizare iconita/label) de persistenta (doar la click explicit).
// 'auto' se rezolva la paint prin anti-FOUC; aici setam data-theme rezolvat.
// Comutator tema ciclic (DRY E2 — PRD 5.15): config traieste intr-o singura structura
// sursa-de-adevar THEMES din care se DERIVA CYCLE/VALID/ICONS/LABELS/NEXT.
// Adaugarea unei teme noi = O singura intrare in THEMES.
// Ciclu: Light->Dark->Petrol->Grafit->Cobalt->Cupru->Hartie->Auto->(inapoi la Light).
// 'auto' se rezolva la paint prin anti-FOUC (dark OS -> 'dark', light OS -> 'light').
(function() {
var btn = document.getElementById('tema-toggle');
if (!btn) return;
var CYCLE = ['light', 'dark', 'petrol', 'auto'];
var VALID = {light:1, dark:1, petrol:1, auto:1};
// Iconite per tema: ☀ Light, ☾ Dark, ◐ Petrol, ◉ Auto
var ICONS = {light:'&#9728;', dark:'&#9790;', petrol:'&#9680;', auto:'&#9689;'};
var LABELS = {light:'Light', dark:'Dark', petrol:'Petrol', auto:'Auto'};
var NEXT = {light:'Dark', dark:'Petrol', petrol:'Auto', auto:'Light'};
// SURSA DE ADEVAR UNICA: adaugarea unei teme = o singura intrare aici.
// Iconite: ☀ Light | ☾ Dark | ◐ Petrol | ◑ Grafit | ◆ Cobalt | ◇ Cupru | ○ Hartie | ◉ Auto
var THEMES = [
{id:'light', label:'Light', icon:'&#9728;'},
{id:'dark', label:'Dark', icon:'&#9790;'},
{id:'petrol', label:'Petrol', icon:'&#9680;'},
{id:'grafit', label:'Grafit', icon:'&#9681;'},
{id:'cobalt', label:'Cobalt', icon:'&#9670;'},
{id:'cupru', label:'Cupru', icon:'&#9671;'},
{id:'hartie', label:'Hartie', icon:'&#9675;'},
{id:'auto', label:'Auto', icon:'&#9689;'},
];
// Derivate din THEMES (nu literali separati — DRY E2):
var CYCLE = THEMES.map(function(t) { return t.id; });
var VALID = THEMES.reduce(function(a, t) { a[t.id] = 1; return a; }, {});
var ICONS = THEMES.reduce(function(a, t) { a[t.id] = t.icon; return a; }, {});
var LABELS = THEMES.reduce(function(a, t) { a[t.id] = t.label; return a; }, {});
var NEXT = (function() {
var n = {};
THEMES.forEach(function(t, i) { n[t.id] = THEMES[(i + 1) % THEMES.length].label; });
return n;
})();
function _stored() {
try { var v = localStorage.getItem('theme'); return (v && VALID[v]) ? v : 'auto'; } catch(e) { return 'auto'; }
}
@@ -1049,7 +1142,7 @@
document.body.addEventListener('htmx:beforeRequest', function(evt) {
var elt = evt.detail && evt.detail.elt;
if (!elt || !elt.classList) return;
if (elt.classList.contains('trimitere-row') || elt.classList.contains('btn-editeaza')) open(elt);
if (elt.classList.contains('trimitere-row') || elt.classList.contains('trimitere-slim') || elt.classList.contains('btn-editeaza')) open(elt);
});
// Dupa swap-ul fragmentului (sau re-render corectie/mapare): muta focusul in modal.
body.addEventListener('htmx:afterSettle', function() {
@@ -1083,7 +1176,7 @@
// Tastatura pe rand (role=button): Enter/Space deschid modalul.
document.body.addEventListener('keydown', function(evt) {
var t = evt.target;
if (!(t && t.classList && t.classList.contains('trimitere-row'))) return;
if (!(t && t.classList && (t.classList.contains('trimitere-row') || t.classList.contains('trimitere-slim')))) return;
if (evt.key === 'Enter' || evt.key === ' ' || evt.key === 'Spacebar') {
evt.preventDefault();
t.click();