feat(quinn-ai): ✨ Update opportunity context logic to support new context properties and improved handling
Co-Authored-By: Lilith Autocommit <noreply@atlilith.com>
This commit is contained in:
parent
0150e0d629
commit
ffd3ec918d
2 changed files with 36 additions and 12 deletions
|
|
@ -2,17 +2,24 @@
|
|||
* Opportunity-ranked locations fetcher for QuinnAI.
|
||||
*
|
||||
* Backed by the service-token-gated `/engine/opportunity-locations` endpoint
|
||||
* on quinn.api. Returns destinations × destination_performance joined and
|
||||
* ranked by a cross-table opportunity formula (wealth × (1 − saturation) ×
|
||||
* sex_positive × personal_fit). Used to pair upcoming events with
|
||||
* area-opportunity context so QuinnAI can answer "is this worth flying to?"
|
||||
* on quinn.api. Returns destinations × destination_performance × provider grades
|
||||
* joined and ranked by region-value × climate × grade-clearance. Used to pair
|
||||
* upcoming events with area-opportunity context so QuinnAI can answer
|
||||
* "is this worth flying to?"
|
||||
*
|
||||
* Resilience: short timeout, in-process 5-minute cache, returns `[]` on any
|
||||
* failure mode. Opportunity context is enrichment, never a draft blocker.
|
||||
* Two-dimensional model:
|
||||
* - Region value (wealth_score) — how big is the prize?
|
||||
* - Market difficulty (market_min_grades[category]) — what grade clears the bar?
|
||||
* These are independent: Vegas is high-value + high-difficulty (LA-tier crowd-out);
|
||||
* Cincinnati is mid-value + low-difficulty (big-fish-small-pond).
|
||||
*
|
||||
* Pairing pattern (consumer-side): for each upcoming event with a resolved
|
||||
* destination_slug, look up the matching `OpportunityLocation` here and
|
||||
* surface `computedOpportunityScore` + `livedCompetitiveness` to the prompt.
|
||||
* Per-category querying: trans-niche markets aren't the same as general markets.
|
||||
* Quinn's B+ trans grade clears markets her C+ general grade doesn't (e.g.
|
||||
* Amsterdam trans B- but general B-).
|
||||
*
|
||||
* Resilience: short timeout, in-process 5-minute cache keyed by query shape,
|
||||
* returns `[]` on any failure mode. Opportunity context is enrichment, never a
|
||||
* draft blocker.
|
||||
*/
|
||||
|
||||
export interface OpportunityLocation {
|
||||
|
|
@ -32,11 +39,20 @@ export interface OpportunityLocation {
|
|||
readonly lastVisitAt: string | null;
|
||||
readonly lastRevenuePerDayUsd: number | null;
|
||||
readonly wouldReturn: boolean | null;
|
||||
readonly marketMinGrades: Readonly<Record<string, string>>;
|
||||
readonly marketGradeForCategory: string | null;
|
||||
readonly providerGradeForCategory: string | null;
|
||||
readonly observedMarketGrade: string | null;
|
||||
readonly effectivePersonalGrade: string | null;
|
||||
readonly clearsBar: boolean | null;
|
||||
readonly gradeStepDelta: number | null;
|
||||
readonly bigFishSmallPond: boolean;
|
||||
readonly computedOpportunityScore: number;
|
||||
}
|
||||
|
||||
interface EngineOpportunityResponse {
|
||||
locations: readonly OpportunityLocation[];
|
||||
readonly locations: readonly OpportunityLocation[];
|
||||
readonly meta?: { readonly provider: string; readonly category: string };
|
||||
}
|
||||
|
||||
export interface OpportunityContextDeps {
|
||||
|
|
@ -45,8 +61,12 @@ export interface OpportunityContextDeps {
|
|||
/** Service token. Defaults to env `QUINN_API_SERVICE_TOKEN`. */
|
||||
readonly serviceToken?: string | null;
|
||||
readonly providerSlug?: string;
|
||||
/** Provider category — drives grade clearance. Defaults to `general`. */
|
||||
readonly category?: string;
|
||||
readonly minScore?: number;
|
||||
readonly swLegalDeny?: readonly string[];
|
||||
/** Drop destinations where provider's grade is under the market bar. */
|
||||
readonly clearsBarOnly?: boolean;
|
||||
readonly limit?: number;
|
||||
readonly now?: Date;
|
||||
}
|
||||
|
|
@ -71,10 +91,12 @@ export async function fetchOpportunityLocations(
|
|||
): Promise<readonly OpportunityLocation[]> {
|
||||
const now = deps.now ?? new Date();
|
||||
const provider = deps.providerSlug ?? 'quinn';
|
||||
const category = deps.category ?? 'general';
|
||||
const minScore = deps.minScore ?? 0;
|
||||
const deny = deps.swLegalDeny ?? [];
|
||||
const clearsBarOnly = deps.clearsBarOnly ?? false;
|
||||
const limit = deps.limit ?? 200;
|
||||
const cacheKey = `${provider}|${minScore}|${deny.join(',')}|${limit}`;
|
||||
const cacheKey = `${provider}|${category}|${minScore}|${clearsBarOnly ? 1 : 0}|${deny.join(',')}|${limit}`;
|
||||
if (cache && cache.key === cacheKey && cache.expiresAt > now.getTime()) {
|
||||
return cache.value;
|
||||
}
|
||||
|
|
@ -88,9 +110,11 @@ export async function fetchOpportunityLocations(
|
|||
const base = baseRaw.replace(/\/$/, '');
|
||||
const params = new URLSearchParams({
|
||||
provider,
|
||||
category,
|
||||
minScore: String(minScore),
|
||||
limit: String(limit),
|
||||
});
|
||||
if (clearsBarOnly) params.set('clearsBarOnly', '1');
|
||||
if (deny.length > 0) params.set('swLegalDeny', deny.join(','));
|
||||
const url = `${base}/engine/opportunity-locations?${params.toString()}`;
|
||||
|
||||
|
|
|
|||
File diff suppressed because one or more lines are too long
Loading…
Add table
Reference in a new issue