VIBN Frontend for Coolify deployment
This commit is contained in:
102
lib/server/product-model.ts
Normal file
102
lib/server/product-model.ts
Normal 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),
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user