fix(vinpin): poll Dialogys header before a definitive not_found (fixA follow-up)
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled

FIX A made a CLEAN Dialogys not_found definitive on its own while Rpartstore is
down (no retry). But runDialogysSearch inferred not_found from a SINGLE OCR
sample taken after a blind fixed wait — a slow render or a transient OCR glitch
on a DECODABLE Renault read empty at t=afterDialogysSubmit and was mislabelled
not_found, and under FIX A that miss is now terminal (decodeRenaultLocked
returns null, no cheap retry).

Replace the blind wait + single sample with a poll (pollForState) over the
header region for a decodable render, capped at afterDialogysSubmit — mirrors
runRpartstore's poll-then-decide shape. Multi-samples across the same window and
returns early on a hit, so a slow render/OCR glitch no longer produces a false
not_found. Cap unchanged, so a genuine miss consumes no more time than before:
FIX A's no-budget-burn / no-seat-poison guarantee and the definitive-not_found
semantics both hold, and the full-frame fallback is preserved. Working Renault
decodes only get faster (early return). tsc clean; vinpin (119) + extractModelYear
(12) green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-15 15:48:44 +03:00
parent c03e7f4079
commit cc543ace21

View File

@@ -2307,9 +2307,25 @@ export class VinpinDriverService implements OnModuleDestroy {
await page.keyboard.type(vin, { delay: VINPIN_TYPE_DELAY_MS });
await page.waitForTimeout(400);
await page.mouse.click(VINPIN_COORDS.dialogysSearch.x, VINPIN_COORDS.dialogysSearch.y);
await page.waitForTimeout(VINPIN_WAITS.afterDialogysSubmit);
// Poll the header region for a decodable vehicle render instead of OCR'ing a
// SINGLE moment after a blind fixed wait. A slow render or a transient OCR glitch
// on a decodable Renault would otherwise read empty at t=afterDialogysSubmit and
// be mislabelled not_found. With Rpartstore down that Dialogys not_found is now
// definitive on its own (FIX A) with NO retry, so the single-sample fragility must
// not stand — the poll multi-samples across the same window and returns as soon as
// the header parses. Cap = afterDialogysSubmit (budget-neutral vs the old fixed
// wait; genuine misses still consume no more than before, so FIX A's no-budget-burn
// guarantee holds). Mirrors runRpartstore's poll-then-decide shape.
const { matched, text: polledHeader } = await this.pollForState(
page,
(t) => isUsableRenaultParse(parseRenaultHeader(t)),
VINPIN_WAITS.afterDialogysSubmit,
VINPIN_RENAULT_HEADER_REGION,
);
const header = await ocrRegion(page, VINPIN_RENAULT_HEADER_REGION);
const header = matched
? polledHeader
: await ocrRegion(page, VINPIN_RENAULT_HEADER_REGION);
const parsed = parseRenaultHeader(header);
if (isUsableRenaultParse(parsed)) {
this.logger.log(
@@ -2325,9 +2341,12 @@ export class VinpinDriverService implements OnModuleDestroy {
);
return { status: "found", parsed: parsed2, rawText: full, via: "dialogys" };
}
// Dialogys stays on the form when it can't decode → treat as not_found. Log the
// OCR'd header so this "clean not_found" class is diagnosable from prod logs
// (distinguishes a real miss from an OCR/render fault reading empty text).
// Dialogys stays on the form when it can't decode → treat as not_found. The poll
// above already gave a slow render its full window and re-sampled the header
// repeatedly, so an empty read here is a genuine miss rather than a single-moment
// sampling artifact — making this "clean not_found" safe to treat as definitive
// when Rpartstore is down. Log the OCR'd header so the class stays diagnosable
// from prod logs (distinguishes a real miss from an OCR/render fault).
this.logger.log(
`dialogys ${vin}: not_found (no model parsed) [header: "${this.truncateForLog(header)}"]`,
);