fix(vinpin): strict not-found for warm-Fiat full-frame garble check
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled

Review finding: warmDecode's genuine-not-found decision used VINPIN_OCR.notFound
(/…|Catalogue/i) against the FULL frame, where the 'Spare Parts Catalogue' header
always matches → every on-panel garble was flagged a genuine miss and null'd out
instead of falling back to the cold retry. Add notFoundStrict (no Catalogue token)
for the full-frame check so a transient on-panel garble (VIN exists) recovers via
the cold path; keep notFound for the runVinFlow modal-region settle poll.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 13:03:23 +03:00
parent 31aabfc28f
commit 46f0210ba6
2 changed files with 15 additions and 2 deletions

View File

@@ -648,7 +648,12 @@ export class VinpinDriverService implements OnModuleDestroy {
// just produced garbage isn't trusted; the daemon re-warms next cycle.
const frame = await this.ocrFrame(page);
const onFiatPanel = VINPIN_OCR.catalogueReady.test(frame);
const genuineNotFound = VINPIN_OCR.notFound.test(frame);
// STRICT not-found against the FULL frame: `notFound` includes the loose
// `Catalogue` token which the "Spare Parts Catalogue" header always matches,
// so it would flag EVERY on-panel garble as a genuine miss and null it out.
// `notFoundStrict` requires a real "не найден"/"not found" string, letting a
// transient on-panel garble (VIN actually exists) fall through to cold retry.
const genuineNotFound = VINPIN_OCR.notFoundStrict.test(frame);
if (onFiatPanel && genuineNotFound) {
this.logger.log(`warm ${vin}: genuine not-found on the Fiat VIN panel → null`);
return null;

View File

@@ -166,8 +166,16 @@ export const VINPIN_OCR = {
/** The decode modal actually carries a result (a model / prod-date / MVS). */
modalHit:
/MVS|найден|Prod\.?\s*date|TIPO|EGEA|DOBLO|PALIO|PANDA|PUNTO|LINEA|DUCATO|QUBO|FIORINO|ULYSSE/i,
/** Genuine "no vehicle found" outcome (the catalog has no record). */
/** Genuine "no vehicle found" outcome (the catalog has no record). Used against
* the cropped decode-modal region in runVinFlow, where the loose `Catalogue`
* token is a useful settle signal. */
notFound: /не\s*найден|not\s*found|Catalogue/i,
/** STRICT genuine not-found — requires an actual "не найден"/"not found" string,
* WITHOUT the loose `Catalogue` token. Use this against the FULL frame (where
* the "Spare Parts Catalogue" header is always present, so `notFound` would
* false-match), e.g. the warm-Fiat genuine-miss-vs-garble decision — so an
* on-panel transient garble falls through to the cold retry instead of null. */
notFoundStrict: /не\s*найден|not\s*found/i,
// ─── Renault flow OCR sets ───
/** A catalog app window (Rpartstore / Dialogys / Fiat ePER) is open OVER the