feat(p): link only OEM codes with a cross-reference match
Every OEM code was a link → unmatched codes (the majority — fasteners, clips, body parts TecDoc doesn't carry) opened an empty "no equivalents" page. Now the schema page batch-checks its codes against the snapshot (POST /p/matched, one indexed query) and links ONLY the matched ones; unmatched codes render as plain text. Copy stays for all. No more dead-end empty pages from the parts panel. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -64,6 +64,39 @@ export class PSourceDbService implements OnModuleInit, OnModuleDestroy {
|
||||
return code.toUpperCase().replace(/[^A-Z0-9]/g, "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch membership test for the schema page: given the OEM codes in a parts
|
||||
* list, return the subset that has at least one OE cross-reference in the
|
||||
* snapshot. The panel links only these (and renders the rest as plain text),
|
||||
* so a click never lands on an empty "no equivalents" detail page. One indexed
|
||||
* query (`code_norm` btree). Fail-open to [] (→ no links) when disabled / on
|
||||
* error, echoing back the caller's original spelling for the matched codes.
|
||||
*/
|
||||
async matchedCodes(rawCodes: string[]): Promise<string[]> {
|
||||
if (!this.enabled || !this.sql || !rawCodes?.length) return [];
|
||||
const rawByNorm = new Map<string, string>();
|
||||
for (const raw of rawCodes) {
|
||||
const n = PSourceDbService.norm(raw ?? "");
|
||||
if (n.length >= PSourceDbService.MIN_NORM_LEN && !rawByNorm.has(n)) rawByNorm.set(n, raw);
|
||||
}
|
||||
if (rawByNorm.size === 0) return [];
|
||||
try {
|
||||
const norms = [...rawByNorm.keys()];
|
||||
const rows = await this.sql<Array<{ code_norm: string }>>`
|
||||
SELECT DISTINCT code_norm FROM article_oe_numbers
|
||||
WHERE code_norm IN ${this.sql(norms)}
|
||||
`;
|
||||
return rows
|
||||
.map((r) => rawByNorm.get(r.code_norm))
|
||||
.filter((c): c is string => c !== undefined);
|
||||
} catch (err) {
|
||||
this.logger.warn(
|
||||
`[p] matchedCodes failed (${rawByNorm.size} codes): ${(err as Error).message}`,
|
||||
);
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
async lookupByOem(rawCode: string): Promise<POemResult | null> {
|
||||
const query = (rawCode ?? "").trim();
|
||||
const queryNorm = PSourceDbService.norm(query);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { Controller, Get, Query } from "@nestjs/common";
|
||||
import { Body, Controller, Get, Post, Query } from "@nestjs/common";
|
||||
import { PSourceDbService } from "./p-source-db.service";
|
||||
|
||||
@Controller("p")
|
||||
@@ -14,4 +14,15 @@ export class PController {
|
||||
async oem(@Query("code") code: string) {
|
||||
return this.p.lookupByOem(code ?? "");
|
||||
}
|
||||
|
||||
/**
|
||||
* Batch: which of these OEM codes have a P cross-reference. The schema page
|
||||
* sends the codes it's about to render and links only the returned ones, so
|
||||
* unmatched codes stay plain text and never open an empty detail page.
|
||||
* `POST /p/matched { codes: [...] }` → { matched: [...] }.
|
||||
*/
|
||||
@Post("matched")
|
||||
async matched(@Body("codes") codes: string[]) {
|
||||
return { matched: await this.p.matchedCodes(Array.isArray(codes) ? codes : []) };
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user