feat: JLR hotspot support & image dimension handling

## PL24 JLR Improvements
- Extend IMAGE_ID_PATTERN to support JLR alphanumeric IDs (e.g., "ll0047c")
- Add extractJlrImageData() method for parsing JLR BOM response
- Extract schema image width/height from JLR images[] array
- Parse hotspots with key and areas from JLR response structure
- Return schemaWidth and schemaHeight in PL24PartsResponse

## Assets
- Add 4 new JLR schema images (eafxpa1a, lf0032a, lf0038a, ls0017)
- Update UI screenshots

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Semih
2026-01-29 19:44:30 +01:00
parent 895416f1aa
commit 4347a499c0
8 changed files with 72 additions and 7 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 147 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 127 KiB

View File

@@ -26,7 +26,9 @@ import { PL24AuthService } from './pl24-auth.service';
import { PL24LegacyScraperService } from './pl24-legacy-scraper.service';
// Security: Strict validation patterns for image handling
const IMAGE_ID_PATTERN = /^\d{9,12}$/;
// Standard PL24: 9-12 digit numeric IDs
// JLR: alphanumeric IDs like "ll0047c" (2-20 chars, letters/numbers only)
const IMAGE_ID_PATTERN = /^(\d{9,12}|[a-zA-Z0-9]{2,20})$/;
const ALLOWED_EXTENSIONS = ['.png', '.jpg', '.jpeg', '.gif', '.webp', '.svg'];
import {
PL24_ENDPOINTS,
@@ -629,16 +631,18 @@ export class PL24Service {
const parts = this.parsePartsResponse(bomData);
const schemaImageUrl = this.extractIllustrationUrl(bomData);
// Extract hotspots from BOM response
const hotspots = this.extractHotspotsFromBom(bomData);
// Extract hotspots and dimensions from BOM response
const { hotspots, schemaWidth, schemaHeight } = this.extractJlrImageData(bomData);
this.logger.log(`JLR: Fetched ${parts.length} parts from BOM, schema: ${schemaImageUrl ? 'yes' : 'no'}`);
this.logger.log(`JLR: Fetched ${parts.length} parts from BOM, schema: ${schemaImageUrl ? 'yes' : 'no'}, hotspots: ${hotspots.length}, dims: ${schemaWidth}x${schemaHeight}`);
return {
success: true,
groupId: String(firstIllus.btnr),
groupName,
schemaImageUrl: schemaImageUrl || undefined,
schemaWidth,
schemaHeight,
parts,
hotspots,
};
@@ -712,6 +716,58 @@ export class PL24Service {
return [];
}
/**
* Extract image data (hotspots, dimensions) from JLR BOM response
* JLR stores image info in data.images[] with width, height, and hotspots
*/
private extractJlrImageData(response: unknown): {
hotspots: PL24Hotspot[];
schemaWidth?: number;
schemaHeight?: number;
} {
const responseData = response as Record<string, unknown>;
const data = responseData.data as Record<string, unknown> || responseData;
const images = data.images as Array<{
id?: string;
uri?: string;
width?: number;
height?: number;
hotspots?: PL24Hotspot[];
}> || [];
// Get the default image or first image
const defaultImage = images.find(img => img.id === '_DFLT_') || images[0];
if (!defaultImage) {
return { hotspots: [] };
}
// Extract hotspots
const hotspots: PL24Hotspot[] = [];
if (defaultImage.hotspots && Array.isArray(defaultImage.hotspots)) {
for (const hs of defaultImage.hotspots) {
if (hs.key && hs.areas) {
hotspots.push({
key: hs.key,
areas: hs.areas,
});
}
}
}
// Extract dimensions
const schemaWidth = defaultImage.width || undefined;
const schemaHeight = defaultImage.height || undefined;
this.logger.debug(`JLR image data: ${images.length} images, dimensions=${schemaWidth}x${schemaHeight}, hotspots=${hotspots.length}`);
return {
hotspots,
schemaWidth,
schemaHeight,
};
}
/**
* Fetch sub-groups for a main group
*/
@@ -1474,12 +1530,21 @@ export class PL24Service {
/**
* Extract PL24 image ID from URL for deduplication
* URL format: https://www.partslink24.com/imageserver/ext/api/images/194500200?...
* Standard URL: https://www.partslink24.com/imageserver/ext/api/images/194500200?...
* JLR URL: https://www.partslink24.com/imageserver/ext/api/images/tiffimages/jlr/ll00/ll0047c.png?...
*/
extractImageIdFromUrl(imageUrl: string): string | null {
if (!imageUrl) return null;
const match = imageUrl.match(/\/images\/(\d+)\?/);
return match ? match[1] : null;
// Standard PL24 image URL pattern: /images/194500200?
const standardMatch = imageUrl.match(/\/images\/(\d+)\?/);
if (standardMatch) return standardMatch[1];
// JLR tiffimages URL pattern: /tiffimages/jlr/.../ll0047c.png?
const jlrMatch = imageUrl.match(/\/tiffimages\/jlr\/[^/]+\/([a-zA-Z0-9]+)\.\w+\?/);
if (jlrMatch) return jlrMatch[1];
return null;
}
/**