feat(ocr): substat-roll validation + rarity inference for GOOD export

Adds the accuracy check yas / Genshin Optimizer use: a substat value is only
legitimate if it equals round(sum of 1..6 rolls) from that stat's roll table.
Values that fit no combination at either rarity are guaranteed OCR misreads.

- src/lib/substatRolls.ts: 5-star roll tables (+ 4-star %/crit tables to tell
  rarities apart), pure isPlausibleSubstat/implausibleSubstats, and inferRarity
  (level > 16 or roll-table fit; conservative, defaults to 5). Validates against
  the union of rarities so valid 4-star pieces are not false-flagged.
- Wired in: shouldFlagArtifactForReview routes implausible substats to review;
  the parser adds an explanatory note; goodInterop export replaces the hardcoded
  rarity:5 with inferRarity (fixes wrong 4-star exports to GO/IK).
- 10 new unit tests; 131 total green; eval still 100%.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
AzuTear
2026-07-06 16:21:28 +02:00
parent b8309af377
commit 7930e369a7
5 changed files with 221 additions and 1 deletions
+3
View File
@@ -19,6 +19,7 @@ import {
textReplacements,
} from "./genshinData.js";
import { fuzzyFindKnown, simplifyForMatch } from "./fuzzyMatch.js";
import { implausibleSubstats } from "./substatRolls.js";
type MainStatValueReference = { stat: string; base: number; max: number };
@@ -110,6 +111,8 @@ export function parseArtifactCandidate(capture: CaptureResult | null): ParsedArt
if (!mainStatField.value) notes.push("Main stat not confidently parsed.");
if (!mainValueField.value) notes.push("Main stat value not confidently parsed.");
if (substats.length < 3) notes.push("Substats look incomplete; crop or OCR needs tuning.");
const implausible = implausibleSubstats(substats);
if (implausible.length) notes.push(`Substat value has no valid roll combination (likely OCR misread): ${implausible.join(", ")}.`);
if (!setField.value) notes.push("Set name not confidently parsed.");
for (const [label, parsedField] of Object.entries({