import type { EngineAnswer, EngineScore, ProductEntity, Reputation } from '../types.js'; import { type ProductMention } from './detect.js'; /** One row of a product-level share of voice: what the shelf actually holds. */ export interface ShelfShareRow { /** The product name, canonicalized to the merchant's own name when it is theirs. */ name: string; /** True when this is one of the merchant's own products. */ isYours: boolean; /** Answers that recommended it (once per answer, however often it appears). */ mentions: number; sharePct: number; } /** One product's full result: visibility, reputation, and the shelf around it. */ export interface SkuReport { /** The merchant's name for the product. */ product: string; aliases?: string[]; descriptor?: string; /** * 0-100 product visibility over the category prompts, or `null` when that * layer was never asked (no descriptor and no category). Null is not zero: an * unmeasured product has no score, while zero means engines never named it. */ visibility: number | null; /** Per-engine breakdown of the visibility layer. */ engines: EngineScore[]; /** * Category prompts this product ASKED FOR, whatever came back. * * Distinct from {@link SkuReport.answers}, which counts what arrived. Zero * here means the layer was never written (no descriptor and no store * category) and the merchant can fix it; zero answers against a non-zero * count means the run could not complete those asks (a cost cap, a failed * engine), which is the RUN's problem, not the product list's. Collapsing * the two told a cap-truncated merchant to go add a descriptor. */ categoryPrompts: number; /** Category answers analyzed for this product. */ answers: number; /** Category answers that named it. */ mentions: number; /** Mean shelf rank where it appeared with one, else null. */ avgPosition: number | null; /** Sentiment from the product-named layer. Absent when it was not asked. */ reputation?: Reputation; /** Product-level share of voice across the category answers. */ shelf: ShelfShareRow[]; /** Per-answer detail for the category layer (the evidence renderers show). */ rows: ProductMention[]; /** Per-answer detail for the named layer. */ reputationRows: ProductMention[]; } /** One analyzed answer: the raw answer plus this product's reading of it. */ export interface AnalyzedAnswer { answer: EngineAnswer; result: ProductMention; } export interface ScoreProductInput { product: ProductEntity; /** Category prompts requested for this product (not how many were answered). */ categoryPrompts: number; /** Category-prompt answers, analyzed for this product. */ visibility: AnalyzedAnswer[]; /** Product-named answers, analyzed for this product. */ reputation: AnalyzedAnswer[]; /** Every product the merchant listed, so the shelf can mark what is theirs. */ owners: ProductEntity[]; } /** * Aggregate the recommendation lists into a product-level share of voice. * * Counted ONCE per answer per product: an answer that mentions a rival three * times in its blurb is one recommendation, not three. Entries that are one of * the merchant's own products are folded onto that product's own name, so a * shelf never lists "Aria II" and "Aria 2" as two different machines. */ export declare function shelfShareOfVoice(rows: ProductMention[], owners: ProductEntity[]): ShelfShareRow[]; /** Aggregate one product's analyzed answers into its {@link SkuReport}. */ export declare function scoreProduct(input: ScoreProductInput): SkuReport;