import { hashId } from "./artifactStore.js"; import type { ParsedArtifactCandidate } from "./artifactOcrParser.js"; import type { ArtifactValueStatus, ScanResultFieldConfidence, ScanResultIkMatch, ScanResultExtractionStatus, ScanResultCategory, StoredScanResultEntry, } from "../types/storage.js"; export interface ScanResultEntryInput { runId: string; sequence: number; page?: number; row?: number; col?: number; category?: ScanResultCategory; source: string; imagePath: string; parsed: ParsedArtifactCandidate | null; needsReview: boolean; confidence: number; capturedAt?: string; artifactRecordId?: string; persistedArtifact?: boolean; notes?: string[]; error?: string; locked?: boolean; ikMatch?: ScanResultIkMatch; } export function deriveScanResultExtractionStatus(input: { parsed: boolean; needsReview: boolean; error?: string; }): ScanResultExtractionStatus { if (input.error === "Card crop image missing.") return "missing_crop"; if (input.error) return input.parsed ? "review" : "error"; if (!input.parsed) return "parse_error"; if (input.needsReview) return "review"; return "parsed"; } export function deriveArtifactValueStatus(extractionStatus: ScanResultExtractionStatus): ArtifactValueStatus { return extractionStatus === "parsed" ? "deferred" : "review"; } export function createStoredScanResultEntry(input: ScanResultEntryInput): StoredScanResultEntry { const category = input.category ?? "artifact"; const extractionStatus = deriveScanResultExtractionStatus({ parsed: Boolean(input.parsed), needsReview: input.needsReview, error: input.error, }); const id = hashId([ input.runId, input.sequence, category, input.imagePath, input.parsed?.name ?? "", input.parsed?.slot ?? "", input.error ?? "", ].join("::")); return { id, runId: input.runId, sequence: input.sequence, page: input.page, row: input.row, col: input.col, category, source: input.source, imagePath: input.imagePath, capturedAt: input.capturedAt ?? new Date(0).toISOString(), extractionStatus, extractionConfidence: Math.max(0, Math.round(input.confidence || 0)), needsReview: extractionStatus !== "parsed", valueStatus: deriveArtifactValueStatus(extractionStatus), valueScore: null, artifact: input.parsed ? { name: input.parsed.name, slot: input.parsed.slot, level: input.parsed.level, setName: input.parsed.setName, mainStat: input.parsed.mainStat, mainValue: input.parsed.mainValue, substats: [...input.parsed.substats], equipped: input.parsed.equipped, locked: input.locked, } : undefined, ikMatch: input.ikMatch, fieldConfidences: input.parsed ? parsedFieldConfidences(input.parsed) : undefined, artifactRecordId: input.artifactRecordId, persistedArtifact: Boolean(input.persistedArtifact), notes: [...(input.notes ?? input.parsed?.notes ?? [])], error: input.error, }; } function parsedFieldConfidences(parsed: ParsedArtifactCandidate): ScanResultFieldConfidence[] { return [ fieldConfidence("name", "Name", parsed.fields.name), fieldConfidence("slot", "Slot", parsed.fields.slot), fieldConfidence("level", "Level", parsed.fields.level), fieldConfidence("mainStat", "Main stat", parsed.fields.mainStat), fieldConfidence("mainValue", "Main value", parsed.fields.mainValue), fieldConfidence("setName", "Set", parsed.fields.setName), fieldConfidence("equipped", "Equipped", parsed.fields.equipped), fieldConfidence("substats", "Substats", parsed.fields.substats), ].filter((entry): entry is ScanResultFieldConfidence => Boolean(entry)); } function fieldConfidence( key: string, label: string, parsedField: ParsedArtifactCandidate["fields"][keyof ParsedArtifactCandidate["fields"]] | undefined, ): ScanResultFieldConfidence | null { if (!parsedField) return null; return { key, label, value: parsedField.value, confidence: Math.max(0, Math.round(parsedField.confidence || 0)), source: parsedField.source, }; }