/** * KnowledgeEntry Domain Entity * * Represents a single knowledge point stored in Qdrant. * Covers all types: PRODUCT (synced), content (DOM), schema (DOM), * FAQ/DOCUMENT/CUSTOM (manual). * * Immutable — all fields readonly. Use static factory fromPayload(). * * @layer Domain * * (c) 2026 TWWIM UG. All rights reserved. (www.twwim.com) */ import { KnowledgeEntryType, KnowledgeSource } from '@archer/domain'; export { KnowledgeEntryType, KnowledgeSource }; export interface KnowledgeEntryProps { readonly id: string; readonly type: KnowledgeEntryType | string; readonly source: KnowledgeSource | string; readonly title: string; readonly text: string; readonly url: string; readonly externalId: string; readonly sourceId: string; readonly category: string; readonly categories: readonly string[]; readonly tags: readonly string[]; readonly status: string; readonly priceMin: number | null; readonly priceMax: number | null; readonly currency: string; readonly variantCount: number; readonly imageUrl: string; readonly productType: string; readonly sectionPath: string; readonly chunkCount: number; } export class KnowledgeEntry { readonly id: string; readonly type: KnowledgeEntryType | string; readonly source: KnowledgeSource | string; readonly title: string; readonly text: string; readonly url: string; readonly externalId: string; readonly sourceId: string; readonly category: string; readonly categories: readonly string[]; readonly tags: readonly string[]; readonly status: string; readonly priceMin: number | null; readonly priceMax: number | null; readonly currency: string; readonly variantCount: number; readonly imageUrl: string; readonly productType: string; readonly sectionPath: string; readonly chunkCount: number; private constructor(props: KnowledgeEntryProps) { this.id = props.id; this.type = props.type; this.source = props.source; this.title = props.title; this.text = props.text; this.url = props.url; this.externalId = props.externalId; this.sourceId = props.sourceId; this.category = props.category; this.categories = props.categories; this.tags = props.tags; this.status = props.status; this.priceMin = props.priceMin; this.priceMax = props.priceMax; this.sectionPath = props.sectionPath; this.chunkCount = props.chunkCount; this.currency = props.currency; this.variantCount = props.variantCount; this.imageUrl = props.imageUrl; this.productType = props.productType; } get isProduct(): boolean { return this.type === KnowledgeEntryType.PRODUCT; } get isPage(): boolean { return this.type === 'content' || this.type === KnowledgeEntryType.PAGE; } get isManual(): boolean { return this.source === KnowledgeSource.MANUAL || this.source === KnowledgeSource.UPLOAD; } get isSynced(): boolean { return this.source === KnowledgeSource.WORDPRESS || this.source === KnowledgeSource.SHOPIFY; } get priceFormatted(): string { if (this.priceMin === null) return ''; if (this.priceMax === null || this.priceMin === this.priceMax) { return `${this.priceMin} ${this.currency}`; } return `${this.priceMin}–${this.priceMax} ${this.currency}`; } get preview(): string { return this.text.length > 120 ? this.text.slice(0, 120) + '...' : this.text; } static fromPayload(id: string, payload: Record): KnowledgeEntry { // DOM content uses `text`/`uri`/`section_path`; products use `_text`/`url` const text = (payload._text as string) || (payload.text as string) || ''; const url = (payload.url as string) || (payload.uri as string) || ''; const sectionPath = (payload.section_path as string) || ''; const title = (payload.title as string) || sectionPath || url || id; return new KnowledgeEntry({ id, type: (payload.type as string) || '', source: (payload.source as string) || '', title, text, url, externalId: (payload.external_id as string) || '', sourceId: (payload.source_id as string) || '', category: (payload.category as string) || (payload.categories as string[] | undefined)?.join(', ') || '', categories: (payload.categories as string[]) || [], tags: (payload.tags as string[]) || [], status: (payload.status as string) || '', priceMin: (payload.priceMin as number) ?? null, priceMax: (payload.priceMax as number) ?? null, currency: (payload.currency as string) || '', variantCount: (payload.variantCount as number) || 0, imageUrl: (payload.imageUrl as string) || '', productType: (payload.productType as string) || '', sectionPath, chunkCount: (payload.chunk_count as number) || 1, }); } }