export type Locale = 'en' | 'pt'; export type Audience = 'consumer' | 'business' | 'mixed'; export type Flavor = 'b2b' | 'b2c' | 'mixed'; export interface RadarProfile { coreOffer: string; goals: string[]; topics: string[]; avoid: string[]; location: string; directUrls: string[]; freshnessDays: number; locale: Locale; /** Who the client's content serves — mismatched content flavor is penalized. */ audience: Audience; } export interface RadarDoc { id: string; provider: string; title: string; url: string; source: string; author: string; publishedAt: string; excerpt: string; text: string; image: string; error?: boolean; raw?: Record; } export interface IntentProfile { positiveTerms: string[]; offerTerms: string[]; topicTerms: string[]; audienceTerms: string[]; locationTerms: string[]; locationPhrases: string[]; goalTerms: string[]; negativeTerms: string[]; /** Multi-word profile entries (offer/topics/goals) kept intact — matched * contiguously and credited with their own phrase-level IDF. */ phrases: string[]; /** Tokens that appear only inside multi-word profile entries ("stress" in * "stress testing tools") — weak evidence on their own, discounted and * penalized when they are the only domain signal. */ weakTokens: string[]; profileText: string; queries: string[]; freshnessDays: number; locale: Locale; audience: Audience; /** Accent/case-preserving forms of the location tokens — matching happens * on accent-stripped text, but anything surfaced to the agent (location * hits, the `why` line, idea headlines/angles) must read as "São Paulo", * not "sao paulo". */ locationDisplay: Record; /** Precompiled word-boundary regexes for every profile term — the same * term is matched against hundreds of harvested docs, so compiling once * per profile instead of once per (term, doc) saves tens of thousands of * RegExp constructions per run. */ regexes: Map; } /** Deterministic flavor triage of a harvested item. Never penalized when ambiguous. */ export declare function flavorOf(doc: RadarDoc): Flavor; export declare function buildIntentProfile(name: string, segment: string, radar: RadarProfile): IntentProfile; export type AgeBand = 'hot' | 'week' | 'recent' | 'evergreen'; /** Age bands for digest classification — the curation contract: * hot ≤ 3 days, week ≤ 7 days, recent ≤ 30 days, evergreen older. */ export declare function ageBandOf(days: number | null): AgeBand; export declare function ageLabel(days: number | null): string; export type ContentType = 'news' | 'commerce' | 'press-release'; /** * Cheap, deterministic content-type triage for harvested items. * Press-release wires and product listings are not news and must not * reach the ranked digest: a wire title ("…Market to Reach USD 9B…"), * an e-commerce listing ("…Gummies, 21 Servings…"), or a PT-BR job/class * listing ("…abre 300 vagas…", "…Cursos EAD gratuitos…") is dropped. */ export declare function contentTypeOf(doc: RadarDoc): ContentType; /** * Corpus-relative relevance: every matched profile term is credited by its * inverse document frequency over the harvested corpus, so genericity is a * property of the data, not a hand-maintained list. "fresh" appearing in * dozens of unrelated headlines earns near-zero credit; a rare term like * "bouquet" earns full credit. The harvest itself defines what is generic. */ export interface CorpusStats { docCount: number; docFreq: Map; phraseFreq: Map; } export declare function buildCorpusStats(docs: RadarDoc[], profile: IntentProfile): CorpusStats; /** * Blend this run's corpus with the client's persisted (decayed) history — * the combined stats are what scoring's IDF weights over. Either side may * be absent: a tiny run leans entirely on history, a history-less client * leans entirely on the run, and neither means flat weights. */ export declare function blendCorpus(run: CorpusStats | undefined, history: CorpusStats | undefined): CorpusStats | undefined; /** This run's corpus stats, or undefined below the minimum corpus size — * what callers fold into the client's persisted history. */ export declare function runCorpusOf(documents: RadarDoc[], profile: IntentProfile): CorpusStats | undefined; export declare function scoreDocument(doc: RadarDoc, profile: IntentProfile, corpus?: CorpusStats, asOfMs?: number): RadarDoc & { category: string; format: string; angle: string; score: number; rawScore: number; relevanceScore: number; relevance: Record; why: string; ageDays: number | null; ageBand: AgeBand; contentType: ContentType; }; export declare function rankDocuments(documents: RadarDoc[], profile: IntentProfile, history?: CorpusStats, asOfMs?: number): ReturnType[]; export declare function clusterDocuments(items: ReturnType[]): (ReturnType & { relatedCount: number; relatedUrls: string[]; })[]; export interface RadarIdea { id: string; sourceId: string; headline: string; format: string; hook: string; brief: { client: string; audience: string; voice: string; contentFormat: string; source: { title: string; url: string; source: string; publishedAt: string; }; angle: string; reason: string; guardrails: string[]; suggestedCTA: string; }; prompt: string; } export declare function buildIdeas(client: { name: string; segment: string; radar: RadarProfile; voiceText: string; }, items: ReturnType[]): RadarIdea[]; //# sourceMappingURL=radar.d.ts.map