Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 1s
Monorepo Pipeline / 🧹 Lint (push) Failing after 35s
Monorepo Pipeline / 🧪 Test (push) Failing after 35s
Monorepo Pipeline / 🏗️ Build (push) Failing after 12s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Image Processor (push) Has been skipped
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped
96 lines
4.0 KiB
TypeScript
96 lines
4.0 KiB
TypeScript
// ============================================================================
|
|
// Step 05: Synthesize — Position Descriptions (Gemini Pro)
|
|
// ============================================================================
|
|
|
|
import { llmJsonRequest } from "../llm-client.js";
|
|
import type { EstimationState, StepResult, PipelineConfig } from "../types.js";
|
|
import { DEFAULT_MODELS } from "../types.js";
|
|
|
|
export async function executeSynthesize(
|
|
state: EstimationState,
|
|
config: PipelineConfig,
|
|
): Promise<StepResult> {
|
|
const models = { ...DEFAULT_MODELS, ...config.modelsOverride };
|
|
const startTime = Date.now();
|
|
|
|
if (!state.concept?.auditedFacts || !state.concept?.architecture?.sitemap) {
|
|
return { success: false, error: "Missing audited facts or sitemap." };
|
|
}
|
|
|
|
const facts = state.concept.auditedFacts;
|
|
|
|
// Determine which positions are required
|
|
const requiredPositions = [
|
|
"Das technische Fundament",
|
|
(facts.selectedPages?.length || 0) + (facts.otherPages?.length || 0) > 0
|
|
? "Individuelle Seiten"
|
|
: null,
|
|
facts.features?.length > 0 ? "System-Module (Features)" : null,
|
|
facts.functions?.length > 0 ? "Logik-Funktionen" : null,
|
|
facts.apiSystems?.length > 0 ? "Schnittstellen (API)" : null,
|
|
facts.cmsSetup ? "Inhalts-Verwaltung" : null,
|
|
facts.multilang ? "Mehrsprachigkeit" : null,
|
|
"Inhaltliche Initial-Pflege",
|
|
"Sorglos Betrieb",
|
|
].filter(Boolean);
|
|
|
|
const systemPrompt = `
|
|
You are a Senior Solution Architect. Write position descriptions for a professional B2B quote.
|
|
|
|
### REQUIRED POSITIONS (STRICT — ONLY DESCRIBE THESE):
|
|
${requiredPositions.map((p) => `"${p}"`).join(", ")}
|
|
|
|
### RULES (STRICT):
|
|
1. NO FIRST PERSON: NEVER "Ich", "Mein", "Wir", "Unser". Lead with nouns or passive verbs.
|
|
2. QUANTITY PARITY: Description MUST list EXACTLY the number of items matching 'qty'.
|
|
3. CMS GUARD: If cmsSetup=false, do NOT mention "CMS", "Inhaltsverwaltung". Use "Plattform-Struktur".
|
|
4. TONE: "Erstellung von...", "Anbindung der...", "Bereitstellung von...". Technical, high-density.
|
|
5. PAGES: List actual page names. NO implementation notes in parentheses.
|
|
6. HARD SPECIFICS: Use industry terms from the briefing (e.g. "Kabeltiefbau", "110 kV").
|
|
7. KEYS: Return EXACTLY the keys from REQUIRED POSITIONS.
|
|
8. NO AGB: NEVER mention "AGB" or "Geschäftsbedingungen".
|
|
9. Sorglos Betrieb: "Inklusive 1 Jahr technischer Betrieb, Hosting, SSL, Sicherheits-Updates, Monitoring und techn. Support."
|
|
10. Inhaltliche Initial-Pflege: Refers to DATENSÄTZE (datasets like products, references), NOT Seiten.
|
|
Use "Datensätze" in the description, not "Seiten".
|
|
11. Mehrsprachigkeit: This is a +20% markup on the subtotal. NOT an API. NOT a Schnittstelle.
|
|
|
|
### EXAMPLES:
|
|
- GOOD: "Erstellung der Seiten: Startseite, Über uns, Leistungen, Kontakt."
|
|
- GOOD: "Native API-Anbindung an Google Maps mit individueller Standort-Visualisierung."
|
|
- BAD: "Ich richte dir das CMS ein."
|
|
- BAD: "Verschiedene Funktionen" (too generic — name the things!)
|
|
|
|
### DATA CONTEXT:
|
|
${JSON.stringify({ facts, sitemap: state.concept.architecture.sitemap, strategy: { briefingSummary: state.concept.strategy.briefingSummary } }, null, 2)}
|
|
|
|
### OUTPUT FORMAT:
|
|
{
|
|
"positionDescriptions": { "Das technische Fundament": string, ... }
|
|
}
|
|
`;
|
|
|
|
try {
|
|
const { data, usage } = await llmJsonRequest({
|
|
model: models.pro,
|
|
systemPrompt,
|
|
userPrompt: state.concept.briefing,
|
|
apiKey: config.openrouterKey,
|
|
});
|
|
|
|
return {
|
|
success: true,
|
|
data: data.positionDescriptions || data,
|
|
usage: {
|
|
step: "05-synthesize",
|
|
model: models.pro,
|
|
promptTokens: usage.promptTokens,
|
|
completionTokens: usage.completionTokens,
|
|
cost: usage.cost,
|
|
durationMs: Date.now() - startTime,
|
|
},
|
|
};
|
|
} catch (err) {
|
|
return { success: false, error: `Synthesize step failed: ${(err as Error).message}` };
|
|
}
|
|
}
|