/** * Page Analysis Response Mapper * * Maps API response schemas to domain PageAnalysis objects. * Handles transformation from @archer/api-interface schemas → dashboard domain. * Includes nested element mapping for complex analysis structures. * * @layer Infrastructure - API Client */ import type { PageAnalysisResponse, PageElementResponse, ElementStatsResponse, } from '@archer/api-interface'; import { PageAnalysis, PageElement, SelectorStrategy, } from '@/domain/entities/PageAnalysis'; import { AnalysisStatus, ElementType, SelectorType, } from '@/domain/entities/DiscoveredPage'; /** * PageAnalysisResponseMapper * * Transforms response DTOs to PageAnalysis domain objects with nested elements. * All mappings are internal - not exposed to application layer. */ export class PageAnalysisResponseMapper { /** * Maps PageAnalysisResponse schema to domain PageAnalysis object * * Includes mapping of nested elements array and new pattern detection fields. * * @param response - PageAnalysisResponse DTO from API * @returns PageAnalysis domain entity */ static toAnalysis(response: PageAnalysisResponse): PageAnalysis { return PageAnalysis.fromPersistence({ id: response.id, tenantId: response.tenantId, discoveredPageId: response.discoveredPageId, url: response.url, version: response.version, status: this.mapAnalysisStatus(response.status), analyzedAt: response.analyzedAt, title: response.title, elements: response.elements.map((el) => this.mapElement(el)), elementCount: response.elementCount, analysisDurationMs: response.analysisDurationMs, reliabilityStatus: (response as any).reliabilityStatus, listingTemplates: (response as any).listingTemplates, searchMechanism: (response as any).searchMechanism, paginationMechanism: (response as any).paginationMechanism, }); } /** * Maps PageElementResponse schema to domain PageElement object * * @param response - PageElementResponse DTO from API * @returns PageElement domain object */ static mapElement(response: PageElementResponse): PageElement { return { id: response.id, type: this.mapElementType(response.type), selectors: response.selectors.map((s) => this.mapSelectorStrategy(s)), semanticDescription: response.semanticDescription, text: response.text, isVisible: response.isVisible, position: response.position ? { x: response.position.x, y: response.position.y, width: response.position.width, height: response.position.height, } : undefined, }; } /** * Maps selector strategy response to domain SelectorStrategy object * * @param response - SelectorStrategyResponse from API * @returns SelectorStrategy domain object */ static mapSelectorStrategy(response: { type: 'ARIA' | 'TEXT' | 'CSS' | 'XPATH' | 'POSITION'; value: string; score: number; }): SelectorStrategy { return { type: this.mapSelectorType(response.type), value: response.value, score: response.score, }; } /** * Maps ElementStatsResponse to statistics object * * @param response - ElementStatsResponse DTO from API * @returns Element statistics object */ static toElementStats(response: ElementStatsResponse): { totalElements: number; byType: Record; visibleElements: number; interactiveElements: number; } { return { totalElements: response.totalElements, byType: response.byType, visibleElements: response.visibleElements, interactiveElements: response.interactiveElements, }; } /** * Maps API element type to domain ElementType enum * * API uses lowercase: button, input, link, select, textarea, checkbox, radio, form, etc. * Domain uses uppercase: BUTTON, INPUT, LINK, SELECT, TEXTAREA, CHECKBOX, RADIO, FORM, etc. * * @param type - API element type string (lowercase) * @returns ElementType enum value (uppercase) */ private static mapElementType( type: | 'button' | 'input' | 'link' | 'select' | 'textarea' | 'checkbox' | 'radio' | 'form' | 'heading' | 'text' | 'div' | 'span' | 'modal' | 'dialog' | 'menu' | 'tab' | 'accordion' | 'slider' | 'switch' | 'date_picker' | 'rich_editor' | 'custom' ): ElementType { // Map lowercase API types to uppercase domain types switch (type) { case 'button': return ElementType.BUTTON; case 'input': return ElementType.INPUT; case 'link': return ElementType.LINK; case 'select': return ElementType.SELECT; case 'textarea': return ElementType.TEXTAREA; case 'checkbox': return ElementType.CHECKBOX; case 'radio': return ElementType.RADIO; case 'form': return ElementType.FORM; // Map extended types to closest domain equivalent or CUSTOM case 'heading': case 'text': case 'div': case 'span': case 'modal': case 'dialog': case 'menu': case 'tab': case 'accordion': case 'slider': case 'switch': case 'date_picker': case 'rich_editor': case 'custom': return ElementType.CUSTOM; default: return ElementType.CUSTOM; } } /** * Maps API selector type to domain SelectorType enum * * @param type - API selector type string * @returns SelectorType enum value */ private static mapSelectorType( type: 'ARIA' | 'TEXT' | 'CSS' | 'XPATH' | 'POSITION' ): SelectorType { switch (type) { case 'ARIA': return SelectorType.ARIA; case 'TEXT': return SelectorType.TEXT; case 'CSS': return SelectorType.CSS; case 'XPATH': return SelectorType.XPATH; case 'POSITION': return SelectorType.POSITION; default: return SelectorType.CSS; } } /** * Maps API analysis status to domain AnalysisStatus enum * * API uses: PENDING, IN_PROGRESS, COMPLETE, OUTDATED, FAILED * Domain uses: NONE, PENDING, IN_PROGRESS, ANALYZED, OUTDATED, FAILED * * @param status - API analysis status string * @returns AnalysisStatus enum value */ private static mapAnalysisStatus( status: 'PENDING' | 'IN_PROGRESS' | 'COMPLETE' | 'OUTDATED' | 'FAILED' ): AnalysisStatus { switch (status) { case 'PENDING': return AnalysisStatus.PENDING; case 'IN_PROGRESS': return AnalysisStatus.IN_PROGRESS; case 'COMPLETE': return AnalysisStatus.ANALYZED; // API's COMPLETE maps to domain's ANALYZED case 'OUTDATED': return AnalysisStatus.OUTDATED; case 'FAILED': return AnalysisStatus.FAILED; default: return AnalysisStatus.NONE; } } }