fix(pl24): Hyundai/Kia illustration açıklamasını kod-yaprağa ekle
Some checks failed
QA Gate (P0/P1) / Test affected app (pull_request) Has been cancelled

Upstream doğrulandı: illustration satırında td[0]=figür kodu ("20-201A .1"),
td[1]=açıklama ("SUB ENGINE ASSY") — parser ilk td'yi (kod) alıp açıklamayı
düşürüyordu. Artık ilk td figür-koduysa (\d\d-\d\d\d[A-Z]?) açıklamalı sonraki
td eklenir → "20-201A .1 SUB ENGINE ASSY", canonical eşleşir (engine→motor).
Diğer markalar etkilenmez (yalnız kod-only ilk td'de tetiklenir). 10/10 test.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-05 10:02:01 +03:00
parent 6bbc32d9c6
commit c70b9b61b9
2 changed files with 33 additions and 24 deletions

View File

@@ -91,6 +91,20 @@ describe("PL24FordLegacyService.parseFordGroupsFromHtml — nav-crumb junk filte
expect(groups.some((g) => g.linkPath.includes("eu.nissan.biz"))).toBe(false);
});
it("appends the Hyundai illustration description to the bare figure code", () => {
// Real Hyundai row shape: first <td> is the figure code (in a link), the
// second <td> is the human description — which the parser used to drop.
const html = [
"<table>",
'<tr class="tc-data-row" url="image-board.action?mainGroup=EN&subGroup=20-201A&illustration=1&lang=tr">' +
'<td class="identifier"><a href="image-board.action?subGroup=20-201A">20-201A .1</a></td>' +
"<td>SUB ENGINE ASSY</td><td></td></tr>",
"</table>",
].join("");
const groups = pg.parseFordGroupsFromHtml(html, "hyundai_parts", "");
expect(groups[0]?.nameTr).toBe("20-201A .1 SUB ENGINE ASSY");
});
it("keeps Volvo vin-group.action?...group1=... (real category) but drops the bare breadcrumb", () => {
const html = [
'<tr class="tc-data-row" url="vin-group.action?group1=2&lang=tr" caption="Motor"><td>Motor</td></tr>',

View File

@@ -3633,24 +3633,6 @@ export class PL24FordLegacyService {
const groups: PL24DecodedCategory[] = [];
const seen = new Set<string>();
// TEMP DEBUG (katalogJob Faz 2): dump the Hyundai/Kia illustration rows
// (subGroup=/image-board), showing every <td> so we can see whether a
// description sits next to the figure code.
if (/hyundai|kia/i.test(serviceName)) {
const rows = html
.split(/<tr\s/)
.filter((s) => s.includes("tc-data-row") && /subGroup=|image-board/.test(s))
.slice(0, 5)
.map((s) => {
const tds = [...s.matchAll(/<td[^>]*>([\s\S]*?)<\/td>/g)].map((m) =>
m[1].replace(/<[^>]+>/g, "·").replace(/&nbsp;/g, " ").replace(/\s+/g, " ").trim(),
);
const cap = s.match(/caption="([^"]*)"/)?.[1] ?? "";
return { caption: cap, tds };
});
this.logger.warn(`[hyundai-debug2] ${serviceName} illus rows: ${JSON.stringify(rows)}`);
}
const config = getServiceConfig(serviceName);
const basePath = config ? `${config.basePath}/${serviceName}` : `/ford/${serviceName}`;
@@ -3681,9 +3663,10 @@ export class PL24FordLegacyService {
seen.add(url);
const captionMatch = segment.match(/\bcaption="([^"]+)"/);
// Try to extract any text from <td> elements, including those with nested tags
let tdName = "";
// Collect all <td> texts (nested tags stripped). The first non-empty one
// is the primary name for most brands.
const tdPattern = /<td[^>]*>([\s\S]*?)<\/td>/g;
const tdTexts: string[] = [];
for (const tdMatch2 of segment.matchAll(tdPattern)) {
const text = tdMatch2[1]
.replace(/<[^>]+>/g, " ")
@@ -3692,10 +3675,22 @@ export class PL24FordLegacyService {
.replace(/\xa0/g, " ")
.replace(/\s+/g, " ")
.trim();
if (text.length >= 3) {
tdName = text;
break;
}
if (text.length >= 2) tdTexts.push(text);
}
let tdName = tdTexts.find((t) => t.length >= 3) ?? "";
// Hyundai/Kia illustration rows put a bare figure code in the first cell
// ("09-091 .1") and the human description in the next ("SUB ENGINE ASSY").
// When the chosen name is code-only, append the first later cell that has
// real words so the leaf is nameable (and canonically mappable via ENGINE).
// Figure code like "09-091", "20-201A .1" (optional letter suffix + " .N"),
// or a purely numeric/dotted code.
const isCodeOnly = (s: string) => {
const t = s.trim();
return /^\d{1,3}-\d{1,4}[a-z]?(\s*\.\s*\d+)?$/i.test(t) || /^[\d.\s-]+$/.test(t);
};
if (tdName && isCodeOnly(tdName)) {
const desc = tdTexts.find((t) => t !== tdName && /[a-zA-ZğüşıöçĞÜŞİÖÇ]{3,}/.test(t));
if (desc) tdName = `${tdName} ${desc}`.replace(/\s+/g, " ").trim();
}
// Caption may contain HTML-encoded content (e.g. &lt;div class="restrictionBlock"...&gt;)