Files
clienthub/src/lib/proposal/schema.ts
T
simone fdcc938252 feat(21-22): agente AI generazione preventivo + pagina pubblica deck
- Migration 0010: tabella proposals (id, slug, lead_id, client_id,
  offer_macro_id, content jsonb, state, selected_tier, accepted_at)
  applicata a prod via SSH tunnel
- @anthropic-ai/sdk@0.105.0 installato; ANTHROPIC_API_KEY in .env.local
- src/lib/proposal/: schema Zod ProposalContent, agente Claude Opus 4.8,
  assemble (AI + offerta DB + config consulente), queries, profile.ts
- Admin: /admin/preventivi lista + /genera (pre-fill ?lead_id=X) + /[id] review
- Sidebar: voce Preventivi + CTA globale lime "Genera preventivo"
- LeadDetail: pulsante "Genera preventivo" → /admin/preventivi/genera?lead_id=X
- Pagina pubblica /preventivo/[slug]: deck 20+ slide light-mode iamcavalli,
  navigazione frecce + dot + keyboard, accept/reject con guard immutabilità
- STATE.md aggiornato (80%), 21-PLAN.md scritto nel formato GSD

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-20 14:37:05 +02:00

113 lines
3.9 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { z } from "zod";
// ── Schema delle sezioni generate dall'AI ──────────────────────────────────
// Le sezioni da OFFER DB (pricing tier) e CONFIG (bio/testimonianze) NON
// sono prodotte dall'AI e non fanno parte di questo schema.
export const ProblemNodeSchema = z.object({
id: z.string(), // "01" | "02" | ... | "05"
title: z.string(), // es. "La voce non è una. Sono tre."
subtitle: z.string(), // es. "LA VOCE"
body: z.string(), // paragrafo descrittivo
risk: z.string(), // testo del box "IL RISCHIO"
quote: z.string(), // citazione verbatim dal transcript
quoteAttribution: z.string(), // "NOME COGNOME · DISCOVERY YYYY-MM-DD"
});
export const SolutionNodeSchema = z.object({
id: z.string(), // specchia il problem node
title: z.string(), // es. "Tre voci coordinate, un solo brand."
subtitle: z.string(), // es. "SOLUZIONE 01 · LA VOCE"
transformation: z.string(), // testo del box "LA TRASFORMAZIONE"
throughWhat: z.array(z.string()), // bullet "ATTRAVERSO COSA" (3 voci)
});
export const DiagramNodeSchema = z.object({
id: z.string(),
label: z.string(),
});
export const ScopeSectionSchema = z.object({
scopeTitle: z.string(),
scopeBody: z.string(),
objectives: z.array(z.string()), // 35 bullet con "checkmark" visivo
});
export const DeliverablesSectionSchema = z.object({
deliverables: z.array(z.string()), // elenco output finali (tier-agnostic, macro)
outOfScope: z.array(z.string()), // ciò che NON è incluso
});
export const TimelineSectionSchema = z.object({
totalWeeks: z.number(),
phases: z.array(z.object({
label: z.string(), // es. "Audit Strategico"
startWeek: z.number(),
endWeek: z.number(),
color: z.enum(["cyan", "purple", "green"]), // palette fissa light-mode
})),
disclaimer: z.string(),
});
export const StagesRecapSchema = z.object({
stages: z.array(z.object({
number: z.string(), // "01" | "02" | "03"
tierLetter: z.string(), // "A" | "B" | "C"
label: z.string(), // es. "OPZIONE A AUDIT"
headline: z.string(), // es. "Si capisce."
body: z.string(),
deliverable: z.string(), // es. "Audit decision document"
})),
});
export const ComparisonRowSchema = z.object({
component: z.string(), // es. "Brand Architecture System"
description: z.string(), // sottotitolo
inA: z.boolean(),
inB: z.boolean(),
inC: z.boolean(),
});
// Root schema — contenuto generato dall'AI
export const ProposalContentSchema = z.object({
// Slide "dove andiamo" (02)
vision: z.object({
headline: z.string(), // es. "Tre voci. Un solo brand."
body: z.string(), // paragrafo breve
}),
// Cap.02 — 5 nodi problema estratti dai transcript
problems: z.array(ProblemNodeSchema).min(3).max(5),
// Sintesi problema (5 nodi → 1 radice)
problemSynthesis: z.object({
rootCause: z.string(), // la domanda strategica non ancora risolta
nodeLabels: z.array(z.string()).length(5), // label per il diagramma
}),
// Cap.03 — soluzioni mirror
solutions: z.array(SolutionNodeSchema).min(3).max(5),
// Sintesi soluzione (1 direzione → 5 elevazioni)
solutionSynthesis: z.object({
direction: z.string(),
elevations: z.array(DiagramNodeSchema).length(5),
}),
// Cap.04 — scope, deliverable, timeline
scope: ScopeSectionSchema,
deliverables: DeliverablesSectionSchema,
timeline: TimelineSectionSchema,
// Slide di recap (3 tappe progressive)
stagesRecap: StagesRecapSchema,
// Tabella comparativa componenti × A/B/C
comparisonMatrix: z.array(ComparisonRowSchema).min(4).max(10),
});
export type ProposalContent = z.infer<typeof ProposalContentSchema>;
export type ProblemNode = z.infer<typeof ProblemNodeSchema>;
export type SolutionNode = z.infer<typeof SolutionNodeSchema>;
export type ComparisonRow = z.infer<typeof ComparisonRowSchema>;