YARA-1177

This commit is contained in:
2026-07-31 19:45:33 +03:30
parent e742d43201
commit 77cf420c33
9 changed files with 1100 additions and 303 deletions

View File

@@ -1,5 +1,6 @@
import {
ClaimVehicleTypeV2,
FANAVARAN_CAR_PARTS_CATALOG,
OUTER_PARTS_BY_CAR_TYPE,
OuterPartCatalogItem,
OuterPartSideV2,
@@ -93,12 +94,14 @@ export function sameCatalogPartId(
return na != null && nb != null && na === nb;
}
/** API part id: numeric catalog id only (`null` for internal / non-catalog lines). */
/** API part id: numeric catalog id only (`null` for free-text / non-catalog lines). */
export function catalogPartIdFromSelectedPart(
sp: DamageSelectedPartV2,
): number | null {
if (isInternalDamageSide(sp.side)) return null;
// Parts from the Fanavaran catalog always carry a numeric id regardless of side.
if (sp.id != null && Number.isFinite(sp.id)) return sp.id;
// Legacy parts without an id and with an internal/unknown side are free-text only.
if (isInternalDamageSide(sp.side)) return null;
return null;
}
@@ -153,12 +156,13 @@ export function disambiguateOuterPartLabelFa(
return `${t} (${sideFa})`;
}
const CATALOG_ITEM_BY_ID = new Map<number, OuterPartCatalogItem>();
for (const list of Object.values(OUTER_PARTS_BY_CAR_TYPE)) {
for (const it of list) {
CATALOG_ITEM_BY_ID.set(it.id, it);
}
}
/**
* Static id → item map for legacy DB part resolution.
* Built from the Fanavaran snapshot; unique because all car types share the same list.
*/
const CATALOG_ITEM_BY_ID = new Map<number, OuterPartCatalogItem>(
FANAVARAN_CAR_PARTS_CATALOG.map((it) => [it.id, it]),
);
function findCatalogItemByKey(key: string): OuterPartCatalogItem | undefined {
for (const list of Object.values(OUTER_PARTS_BY_CAR_TYPE)) {
@@ -681,7 +685,7 @@ export function normalizeCarPartDamageForExpertReply(
? o.side.trim().toLowerCase()
: "";
if (name && sideIn) {
if (name) {
const id = toNum(o.id);
const ck =
typeof o.catalogKey === "string" && o.catalogKey.trim()
@@ -689,36 +693,28 @@ export function normalizeCarPartDamageForExpertReply(
: undefined;
const catalog = carType ? OUTER_PARTS_BY_CAR_TYPE[carType] : undefined;
let catItem: OuterPartCatalogItem | undefined;
if (catalog) {
// Try id-based lookup first (works for Fanavaran parts which have no side)
if (id != null) {
catItem = catalog?.find((c) => c.id === id) ?? CATALOG_ITEM_BY_ID.get(id) ?? undefined;
}
if (!catItem && catalog) {
const byKey = new Map(catalog.map((c) => [c.key, c]));
if (ck) catItem = byKey.get(ck);
if (!catItem && id != null) catItem = catalog.find((c) => c.id === id);
if (!catItem) {
if (!catItem && sideIn) {
catItem = byKey.get(catalogLikeKeyFromPart({ side: sideIn, name }));
}
}
if (!catItem && id != null) {
catItem = CATALOG_ITEM_BY_ID.get(id!) ?? undefined;
}
const list = catalog?.length
? catalog
: catItem
? outerCatalogListForItem(catItem)
: [];
if (catItem && list.length) {
if (!catItem && ck) catItem = findCatalogItemByKey(ck);
if (catItem) {
return selectedPartToStoredRecord(
catalogItemToSelectedPart(catItem, list),
catalogItemToSelectedPart(catItem, outerCatalogListForItem(catItem)),
);
}
const label_fa =
typeof o.label_fa === "string" && o.label_fa.trim()
? o.label_fa.trim()
: name;
const out: Record<string, unknown> = {
name,
side: sideIn,
label_fa,
};
const out: Record<string, unknown> = { name, side: sideIn, label_fa };
if (id != null) out.id = id;
if (ck) out.catalogKey = ck;
return out;
@@ -731,9 +727,8 @@ export function normalizeCarPartDamageForExpertReply(
carType,
);
if (catItem) {
const list = outerCatalogListForItem(catItem);
return selectedPartToStoredRecord(
catalogItemToSelectedPart(catItem, list),
catalogItemToSelectedPart(catItem, outerCatalogListForItem(catItem)),
);
}
const sideFa = SIDE_LABEL_FA[legacySide] || legacySide;
@@ -744,7 +739,7 @@ export function normalizeCarPartDamageForExpertReply(
};
}
throw new Error("carPartDamage must include side and (name or part)");
throw new Error("carPartDamage must include a name or part field");
}
/** Best-effort migration for API output; never throws. */