VIBN Frontend for Coolify deployment

This commit is contained in:
2026-02-15 19:25:52 -08:00
commit 40bf8428cd
398 changed files with 76513 additions and 0 deletions

102
lib/server/product-model.ts Normal file
View File

@@ -0,0 +1,102 @@
import { listChatExtractions } from '@/lib/server/chat-extraction';
import { clamp, nowIso, persistPhaseArtifacts, uniqueStrings, toStage } from '@/lib/server/projects';
import type { CanonicalProductModel } from '@/lib/types/product-model';
import type { ChatExtractionRecord } from '@/lib/types/chat-extraction';
const average = (numbers: number[]) =>
numbers.length ? numbers.reduce((sum, value) => sum + value, 0) / numbers.length : 0;
export async function buildCanonicalProductModel(projectId: string): Promise<CanonicalProductModel> {
const extractions = await listChatExtractions(projectId);
if (!extractions.length) {
throw new Error('No chat extractions found for project');
}
const completionAvg = average(
extractions.map(
(record) =>
(record.data as any)?.summary_scores?.overall_completion ?? record.overallCompletion ?? 0,
),
);
const confidenceAvg = average(
extractions.map(
(record) =>
(record.data as any)?.summary_scores?.overall_confidence ?? record.overallConfidence ?? 0,
),
);
const canonical = mapExtractionToCanonical(
projectId,
pickHighestConfidence(extractions as any),
completionAvg,
confidenceAvg,
);
await persistPhaseArtifacts(projectId, (phaseData, phaseScores, phaseHistory) => {
phaseData.canonicalProductModel = canonical;
phaseScores.vision = {
overallCompletion: canonical.overallCompletion,
overallConfidence: canonical.overallConfidence,
updatedAt: nowIso(),
};
phaseHistory.push({ phase: 'vision', status: 'completed', timestamp: nowIso() });
return { phaseData, phaseScores, phaseHistory, nextPhase: 'vision_ready' };
});
return canonical;
}
function pickHighestConfidence(records: ChatExtractionRecord[]) {
return records.reduce((best, record) =>
record.overallConfidence > best.overallConfidence ? record : best,
);
}
function mapExtractionToCanonical(
projectId: string,
record: ChatExtractionRecord,
completionAvg: number,
confidenceAvg: number,
): CanonicalProductModel {
const data = record.data;
const coreFeatures = data.solution_and_features.core_features.map(
(feature) => feature.name || feature.description,
);
const niceToHaveFeatures = data.solution_and_features.nice_to_have_features.map(
(feature) => feature.name || feature.description,
);
return {
projectId,
workingTitle: data.project_summary.working_title ?? null,
oneLiner: data.project_summary.one_liner ?? null,
problem: data.product_vision.problem_statement.description ?? null,
targetUser: data.target_users.primary_segment.description ?? null,
desiredOutcome: data.product_vision.target_outcome.description ?? null,
coreSolution: data.solution_and_features.core_solution.description ?? null,
coreFeatures: uniqueStrings(coreFeatures),
niceToHaveFeatures: uniqueStrings(niceToHaveFeatures),
marketCategory: data.market_and_competition.market_category.description ?? null,
competitors: uniqueStrings(
data.market_and_competition.competitors.map((competitor) => competitor.name),
),
techStack: uniqueStrings(
data.tech_and_constraints.stack_mentions.map((item) => item.description),
),
constraints: uniqueStrings(
data.tech_and_constraints.constraints.map((constraint) => constraint.description),
),
currentStage: toStage(data.project_summary.stage),
shortTermGoals: uniqueStrings(
data.goals_and_success.short_term_goals.map((goal) => goal.description),
),
longTermGoals: uniqueStrings(
data.goals_and_success.long_term_goals.map((goal) => goal.description),
),
overallCompletion: clamp(completionAvg),
overallConfidence: clamp(confidenceAvg),
};
}