rohitsar567's picture
Stack A: NIM brain + Maverick judge + Sarvam voice/Indic (D-019). Data moved to insurance-bot-data dataset; Space is now code-only.
c32bddc verified
Raw
History Blame
11.1 kB
// Typed client for the FastAPI backend.
// Backend URL is configurable via NEXT_PUBLIC_BACKEND_URL so we can point at
// localhost in dev and at Render in production.
// In production (HF Spaces deploy): empty -> same-origin requests, no CORS.
// In dev: set NEXT_PUBLIC_BACKEND_URL=http://localhost:8000 in frontend/.env.local
export const BACKEND_URL =
process.env.NEXT_PUBLIC_BACKEND_URL ?? "";
export type Citation = {
policy_id: string;
policy_name: string;
insurer_slug: string;
page_start: number;
page_end: number;
source_url: string;
score: number;
};
export type ChatResponse = {
reply_text: string;
citations: Citation[];
brain_used: string;
intent: string;
language: string;
latency_ms: number;
session_id: string;
audio_base64?: string | null;
faithfulness_passed?: boolean;
faithfulness_reasons?: string[];
blocked?: boolean;
};
export type ChatMessage = {
role: "user" | "assistant";
content: string;
};
export async function postChat(args: {
user_text: string;
session_id?: string;
chat_history?: ChatMessage[];
profile?: Record<string, unknown>;
policy_filter_ids?: string[];
return_audio?: boolean;
tts_language_code?: string;
}): Promise<ChatResponse> {
const resp = await fetch(`${BACKEND_URL}/api/chat`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
user_text: args.user_text,
session_id: args.session_id,
chat_history: args.chat_history ?? [],
profile: args.profile ?? {},
policy_filter_ids: args.policy_filter_ids,
return_audio: args.return_audio ?? false,
tts_language_code: args.tts_language_code ?? "en-IN",
}),
});
if (!resp.ok) {
const t = await resp.text();
throw new Error(`chat failed: ${resp.status} ${t}`);
}
return resp.json();
}
export async function postTranscribe(
blob: Blob,
language_code?: string,
): Promise<{ text: string; language_code?: string; latency_ms: number }> {
const fd = new FormData();
// Use blob's mime to derive extension; default to wav
const mime = blob.type || "audio/wav";
const ext = mime.includes("webm")
? "webm"
: mime.includes("mp3")
? "mp3"
: mime.includes("ogg")
? "ogg"
: "wav";
fd.append("file", blob, `audio.${ext}`);
if (language_code) fd.append("language_code", language_code);
const resp = await fetch(`${BACKEND_URL}/api/transcribe`, {
method: "POST",
body: fd,
});
if (!resp.ok) {
const t = await resp.text();
throw new Error(`transcribe failed: ${resp.status} ${t}`);
}
return resp.json();
}
export async function getHealth(): Promise<{
status: string;
providers_ok: Record<string, boolean>;
missing_keys: string[];
}> {
const resp = await fetch(`${BACKEND_URL}/api/health`);
if (!resp.ok) throw new Error(`health failed: ${resp.status}`);
return resp.json();
}
export type PolicyEntry = {
name: string;
source_url: string;
};
export type CoverageInsurer = {
slug: string;
name: string;
home_url: string;
policy_count: number;
sample_policies: PolicyEntry[];
};
export type CoverageResponse = {
total_chunks: number;
total_policies: number;
total_insurers: number;
insurers: CoverageInsurer[];
};
export async function getCoverage(): Promise<CoverageResponse> {
const resp = await fetch(`${BACKEND_URL}/api/coverage`);
if (!resp.ok) throw new Error(`coverage failed: ${resp.status}`);
return resp.json();
}
export type UploadResponse = {
policy_id: string;
policy_name: string;
chunks_added: number;
pages_indexed: number;
elapsed_ms: number;
};
export type ScorecardSubScore = {
name: string;
score: number;
summary: string;
signals: string[];
};
export type ScorecardResponse = {
policy_id: string;
policy_name: string;
insurer_slug: string;
overall_score: number;
grade: string;
one_liner: string;
sub_scores: ScorecardSubScore[];
data_completeness_pct: number;
methodology_link: string;
};
export async function getScorecard(policy_id: string): Promise<ScorecardResponse> {
const resp = await fetch(`${BACKEND_URL}/api/policies/${encodeURIComponent(policy_id)}/scorecard`);
if (!resp.ok) {
const t = await resp.text();
throw new Error(`scorecard failed: ${resp.status} ${t}`);
}
return resp.json();
}
export type PreExistingCondition =
| "none"
| "diabetes_or_hypertension"
| "heart_disease"
| "multiple";
export type PremiumEstimateRequest = {
age: number;
sum_insured_inr: number;
city_tier?: "metro" | "tier1" | "tier2";
smoker?: boolean;
family_size?: number;
policy_id?: string | null;
pre_existing_conditions?: PreExistingCondition;
copayment_pct?: number;
};
export type PremiumEstimateResponse = {
policy_id: string;
point_estimate_inr: number;
low_inr: number;
high_inr: number;
methodology: string;
sources: string[];
is_illustrative: boolean;
disclaimer: string;
};
export type ComparePolicyEntry = {
policy_id: string;
policy_name: string;
insurer_slug: string;
fields: Record<string, unknown>;
scorecard?: ScorecardResponse;
};
export type CompareResponse = {
policies: ComparePolicyEntry[];
field_order: string[];
};
export type MarketplacePolicy = {
policy_id: string;
policy_name: string;
insurer_slug: string;
insurer_name: string;
insurer_home_url: string;
source_pdf_url: string;
grade: string;
overall_score: number;
one_liner: string;
data_completeness_pct: number;
min_entry_age?: number | null;
max_entry_age?: number | null;
max_renewal_age?: number | null;
sum_insured_options: number[];
pre_existing_disease_waiting_months?: number | null;
initial_waiting_period_days?: number | null;
maternity_waiting_months?: number | null;
copayment_pct?: number | null;
network_hospital_count?: number | null;
no_claim_bonus_pct?: number | null;
ayush_coverage?: boolean | null;
maternity_coverage?: boolean | null;
cashless_treatment_supported?: boolean | null;
room_rent_capping?: string | null;
};
export type MarketplaceResponse = {
policies: MarketplacePolicy[];
total: number;
insurers_indexed: number;
};
export type InsurerReviews = {
insurer_slug: string;
insurer_name: string;
aggregate_score: { value_0_100?: number; letter_grade?: string; headline?: string };
claim_metrics: {
claim_settlement_ratio_pct?: number;
claim_settlement_ratio_year?: string;
complaints_per_10k_policies?: number;
complaints_year?: string;
source_irdai_url?: string;
};
aggregator_ratings: Record<string, { avg_star?: number; review_count?: number; url?: string }>;
reddit_sentiment: { sentiment_overall?: string; notable_themes?: string[] };
youtube_coverage: { overall_youtube_sentiment?: string; top_creators_who_reviewed?: Array<{ creator?: string; video_url?: string; verdict?: string }> };
in_news?: Array<{ headline?: string; url?: string; publication?: string; date?: string; tone?: string }>;
};
export async function getInsurerReviews(slug: string): Promise<InsurerReviews> {
const resp = await fetch(`${BACKEND_URL}/api/insurers/${slug}/reviews`);
if (!resp.ok) throw new Error(`reviews failed: ${resp.status}`);
return resp.json();
}
export async function getMarketplace(session_id?: string): Promise<MarketplaceResponse> {
// When session_id is passed AND its profile is complete enough, the backend
// re-scores every policy with the user's profile β€” cards reveal personalised
// grades. Without session_id, grades use the generic baseline.
const qs = session_id ? `?session_id=${encodeURIComponent(session_id)}` : "";
const resp = await fetch(`${BACKEND_URL}/api/policies/all${qs}`);
if (!resp.ok) throw new Error(`marketplace failed: ${resp.status}`);
return resp.json();
}
export type UserProfile = {
age?: number | null;
dependents?: string | null;
income_band?: string | null;
existing_cover_inr?: number | null;
primary_goal?: string | null;
location_tier?: string | null;
parents_to_insure?: boolean | null;
parents_age_max?: number | null;
parents_has_ped?: boolean | null;
health_conditions?: string[] | null;
budget_band?: string | null;
};
export type ProfileCompletenessResponse = {
completeness: number;
completeness_pct: number;
fields_collected: string[];
fields_missing: string[];
is_personalized: boolean;
gate_threshold: number;
next_question_hint?: string | null;
profile?: UserProfile;
session_id?: string | null;
};
export async function getProfileCompleteness(session_id?: string): Promise<ProfileCompletenessResponse> {
const qs = session_id ? `?session_id=${encodeURIComponent(session_id)}` : "";
const resp = await fetch(`${BACKEND_URL}/api/profile/completeness${qs}`);
if (!resp.ok) throw new Error(`profile completeness failed: ${resp.status}`);
return resp.json();
}
export async function postProfileUpdate(req: UserProfile & { session_id: string }): Promise<ProfileCompletenessResponse> {
const resp = await fetch(`${BACKEND_URL}/api/profile`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(req),
});
if (!resp.ok) throw new Error(`profile update failed: ${resp.status}`);
return resp.json();
}
export async function getCompare(policy_ids: string[]): Promise<CompareResponse> {
// Build query string manually β€” URL constructor requires an absolute URL,
// but in production BACKEND_URL is "" (same-origin) which makes the path
// relative. Constructing `new URL("/api/...")` throws "Invalid URL".
const params = policy_ids.map((id) => `policy_ids=${encodeURIComponent(id)}`).join("&");
const resp = await fetch(`${BACKEND_URL}/api/policies/compare?${params}`);
if (!resp.ok) throw new Error(`compare failed: ${resp.status}`);
return resp.json();
}
export async function postPremiumEstimate(req: PremiumEstimateRequest): Promise<PremiumEstimateResponse> {
const resp = await fetch(`${BACKEND_URL}/api/premium/estimate`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
...req,
city_tier: req.city_tier ?? "metro",
smoker: req.smoker ?? false,
family_size: req.family_size ?? 1,
}),
});
if (!resp.ok) throw new Error(`premium estimate failed: ${resp.status}`);
return resp.json();
}
export async function uploadPolicy(file: File): Promise<UploadResponse> {
const fd = new FormData();
fd.append("file", file);
const resp = await fetch(`${BACKEND_URL}/api/upload-policy`, {
method: "POST",
body: fd,
});
if (!resp.ok) {
const t = await resp.text();
throw new Error(`upload failed: ${resp.status} ${t}`);
}
return resp.json();
}
// Decode a base64 string to a playable audio Blob URL.
export function audioBlobURLFromBase64(b64: string, mime = "audio/wav"): string {
const bin = atob(b64);
const bytes = new Uint8Array(bin.length);
for (let i = 0; i < bin.length; i++) bytes[i] = bin.charCodeAt(i);
const blob = new Blob([bytes], { type: mime });
return URL.createObjectURL(blob);
}