feat: Rename "Betrieb & Hosting" to "Sorglos-Paket" and refine PDF pricing module layout and text.
Some checks failed
Build & Deploy Mintel Blog / build-and-deploy (push) Failing after 2m28s
Some checks failed
Build & Deploy Mintel Blog / build-and-deploy (push) Failing after 2m28s
This commit is contained in:
@@ -4,7 +4,7 @@ Preise
|
||||
|
||||
Basis
|
||||
|
||||
6.000 € einmalig
|
||||
4.000 € einmalig
|
||||
|
||||
Die Grundlage für jede Website:
|
||||
• Projekt-Setup & Infrastruktur
|
||||
@@ -23,7 +23,7 @@ Enthält keine Seiten, Inhalte oder Funktionen.
|
||||
|
||||
Seite
|
||||
|
||||
800 € / Seite
|
||||
600 € / Seite
|
||||
|
||||
Individuell gestaltete Seite –
|
||||
mit Layout, Struktur, Textaufteilung, responsivem Design.
|
||||
@@ -32,7 +32,7 @@ mit Layout, Struktur, Textaufteilung, responsivem Design.
|
||||
|
||||
Feature (System)
|
||||
|
||||
2.000 € / Feature
|
||||
1.500 € / Feature
|
||||
|
||||
Ein in sich geschlossenes System mit Datenstruktur, Darstellung und Pflegefähigkeit.
|
||||
|
||||
@@ -50,7 +50,7 @@ Ein Feature erzeugt ein Datenmodell, Übersichten & Detailseiten.
|
||||
|
||||
Funktion (Logik)
|
||||
|
||||
1.000 € / Funktion
|
||||
800 € / Funktion
|
||||
|
||||
Funktionen liefern Logik und Interaktion, z. B.:
|
||||
• Kontaktformular
|
||||
@@ -73,7 +73,7 @@ Jede Funktion ist ein klar umrissener Logikbaustein.
|
||||
|
||||
Visuelle Inszenierung
|
||||
|
||||
2.000 € / Abschnitt
|
||||
1.500 € / Abschnitt
|
||||
|
||||
Erweiterte Gestaltung:
|
||||
• Hero-Story
|
||||
@@ -100,7 +100,7 @@ Dargestellte, interaktive UI-Erlebnisse:
|
||||
|
||||
Neuer Datensatz
|
||||
|
||||
400 € / Stück
|
||||
200 € / Stück
|
||||
|
||||
Beispiele:
|
||||
• Produkt
|
||||
@@ -126,7 +126,7 @@ Datensatz anpassen
|
||||
|
||||
Hosting & Betrieb
|
||||
|
||||
12 Monate = 1440 €
|
||||
12 Monate = 3.000 €
|
||||
|
||||
Sichert:
|
||||
• Webhosting & Verfügbarkeit
|
||||
@@ -195,7 +195,7 @@ Für:
|
||||
|
||||
API-Schnittstelle / Daten-Sync
|
||||
|
||||
1.000 € / Zielsystem
|
||||
800 € / Zielsystem
|
||||
|
||||
Synchronisation zu externem System (Push):
|
||||
• Produkt-Sync
|
||||
|
||||
@@ -7,7 +7,8 @@ import { execSync } from 'node:child_process';
|
||||
import axios from 'axios';
|
||||
import { FileCacheAdapter } from '../src/utils/cache/file-adapter.js';
|
||||
|
||||
import { initialState } from '../src/logic/pricing/constants.js';
|
||||
import { initialState, PRICING } from '../src/logic/pricing/constants.js';
|
||||
import { calculateTotals } from '../src/logic/pricing/calculator.js';
|
||||
|
||||
async function main() {
|
||||
const OPENROUTER_KEY = process.env.OPENROUTER_KEY;
|
||||
@@ -19,6 +20,7 @@ async function main() {
|
||||
let briefing = '';
|
||||
let targetUrl: string | null = null;
|
||||
let comments: string | null = null;
|
||||
let budget: string | null = null;
|
||||
let cacheKey: string | null = null;
|
||||
|
||||
let jsonStatePath: string | null = null;
|
||||
@@ -32,6 +34,8 @@ async function main() {
|
||||
targetUrl = args[++i];
|
||||
} else if (arg === '--comments' || arg === '--notes') {
|
||||
comments = args[++i];
|
||||
} else if (arg === '--budget') {
|
||||
budget = args[++i];
|
||||
} else if (arg === '--cache-key') {
|
||||
cacheKey = args[++i];
|
||||
} else if (arg === '--json') {
|
||||
@@ -77,7 +81,7 @@ async function main() {
|
||||
}
|
||||
|
||||
const cache = new FileCacheAdapter({ prefix: 'ai_est_' });
|
||||
const finalCacheKey = cacheKey || `${briefing}_${targetUrl}_${comments}`;
|
||||
const finalCacheKey = cacheKey || `${briefing}_${targetUrl}_${comments}_${budget}`;
|
||||
|
||||
// 1. Crawl if URL provided
|
||||
let crawlContext = '';
|
||||
@@ -103,6 +107,11 @@ async function main() {
|
||||
distilledCrawl = await distillCrawlContext(crawlContext, OPENROUTER_KEY);
|
||||
await cache.set(`distilled_${targetUrl}`, distilledCrawl, 86400);
|
||||
}
|
||||
} else if (targetUrl) {
|
||||
distilledCrawl = `WARNING: The crawl of ${targetUrl} failed (ENOTFOUND or timeout).
|
||||
The AI must NOT hallucinate details about the current website.
|
||||
Focus ONLY on the BRIEFING provided. If details are missing, mark them as 'unknown'.`;
|
||||
console.warn('⚠️ Crawl failed. AI will be notified to avoid hallucinations.');
|
||||
}
|
||||
|
||||
// 3. AI Prompting
|
||||
@@ -124,7 +133,7 @@ async function main() {
|
||||
console.log('📦 Using cached AI response.');
|
||||
formState = cachedAi;
|
||||
} else {
|
||||
const result = await getAiEstimation(briefing, distilledCrawl, comments, OPENROUTER_KEY, principles, techStandards, tone);
|
||||
const result = await getAiEstimation(briefing, distilledCrawl, comments, budget, OPENROUTER_KEY, principles, techStandards, tone);
|
||||
formState = result.state;
|
||||
usage = result.usage;
|
||||
await cache.set(finalCacheKey, formState);
|
||||
@@ -251,14 +260,24 @@ async function performCrawl(url: string): Promise<string> {
|
||||
const cleanJson = (str: string) => {
|
||||
// Remove markdown code blocks if present
|
||||
let cleaned = str.replace(/```json\n?|```/g, '').trim();
|
||||
|
||||
// Remove potential control characters that break JSON.parse
|
||||
cleaned = cleaned.replace(/[\u0000-\u001F\u007F-\u009F]/g, " ");
|
||||
// Remove trailing commas before closing braces/brackets
|
||||
// We keep \n \r \t for now as they might be escaped or need handling
|
||||
cleaned = cleaned.replace(/[\u0000-\u0009\u000B\u000C\u000E-\u001F\u007F-\u009F]/g, " ");
|
||||
|
||||
// Specific fix for Gemini: raw newlines inside strings
|
||||
// This is tricky. We'll try to escape newlines that are NOT followed by a quote and colon (property start)
|
||||
// or a closing brace/bracket. This is heuristic.
|
||||
// A better way is to replace all raw newlines that are preceded by a non-backslash with \n
|
||||
// but only if they are inside double quotes.
|
||||
|
||||
// Simplest robust approach: Remove trailing commas and hope response_format does its job.
|
||||
cleaned = cleaned.replace(/,\s*([\]}])/g, '$1');
|
||||
|
||||
return cleaned;
|
||||
};
|
||||
|
||||
const getAiEstimation = async (briefing: string, distilledCrawl: string, comments: string | null, apiKey: string, principles: string, techStandards: string, tone: string) => {
|
||||
const getAiEstimation = async (briefing: string, distilledCrawl: string, comments: string | null, budget: string | null, apiKey: string, principles: string, techStandards: string, tone: string) => {
|
||||
let usage = { prompt: 0, completion: 0, cost: 0 };
|
||||
const addUsage = (data: any) => {
|
||||
if (data?.usage) {
|
||||
@@ -281,90 +300,150 @@ const getAiEstimation = async (briefing: string, distilledCrawl: string, comment
|
||||
You are a precision sensor. Analyze the BRIEFING and extract ONLY the raw facts.
|
||||
Tone: Literal, non-interpretive.
|
||||
Output language: GERMAN (Strict).
|
||||
Output format: ROOT LEVEL JSON (No wrapper keys like '0' or 'data').
|
||||
|
||||
### MISSION:
|
||||
Focus 100% on the BRIEFING text provided by the user. Use the DISTILLED_CRAWL only as background context for terms or company details. If there is a conflict, the BRIEFING is the absolute source of truth.
|
||||
|
||||
### OBJECTIVES:
|
||||
- Extract **companyName**: The full legal and brand name (e.g., "E-TIB GmbH"). Use signatures and crawl data.
|
||||
- Extract **personName**: The name of the primary human contact (e.g., "Danny Joseph"). **CRITICAL**: Check email signatures and "Mit freundlichen Grüßen" blocks. DO NOT use "Sie", "Firma" or generic terms if a name exists.
|
||||
- Extract **existingWebsite**: The primary URL mentioned in the briefing or signature (e.g., "www.e-tib.com").
|
||||
- Extract **websiteTopic**: A short descriptor of the CORE BUSINESS (e.g., "Kabeltiefbau"). MAX 3 WORDS.
|
||||
- **isRelaunch**: Set to TRUE if the briefing mentions an existing website, a URL, or if the company is an established entity (e.g. "Gruppe", "GmbH seit 20XX"). Assume a presence exists that needs a modern "Zentrale Webpräsenz".
|
||||
- **CRITICAL LOGIC**: If a URL is mentioned, isRelaunch MUST be TRUE.
|
||||
- For all textual values (deadline, websiteTopic, targetAudience etc.): USE GERMAN.
|
||||
- **multilang**: ONLY if the briefing mentions multiple target languages (e.g., DE/EN).
|
||||
- **maps**: If "Google Maps" or location maps are mentioned or implicit (Contact page).
|
||||
- **CRITICAL**: Do NOT include "social" in apiSystems unless the user explicitly wants to SYNC/POST content to social media. "Existing social media links" are NOT apiSystems.
|
||||
- **CRITICAL**: "Video Player", "Cookie Banner", "Animations" are NOT features. They are visual/base. Do NOT map them to features.
|
||||
### PRICING REFERENCE (FOR CALCULATION):
|
||||
- Base Project (Infrastructure + 12 Months Hosting): 5.440 € (MANDATORY START)
|
||||
- Additional Pages: 600 € / stk
|
||||
- System-Modules (Features): 1.500 € / stk
|
||||
- Logic-Functions: 800 € / stk
|
||||
- API Integrations: 800 € / stk
|
||||
- CMS Setup: 1.500 € (optional)
|
||||
- Visual Staging/Interactions: 1.500 € - 2.000 €
|
||||
|
||||
### CATEGORY MAPPING (IDs ONLY):
|
||||
- **selectedPages**: [Home, About, Services, Contact, Landing, Legal]
|
||||
- **features**: [blog_news, products, jobs, refs, events]
|
||||
- **functions**: [search, filter, pdf, forms, members, calendar, multilang, chat]
|
||||
- **apiSystems**: [crm_erp, payment, marketing, ecommerce, maps, social, analytics]
|
||||
- **assets**: [existing_website, logo, styleguide, content_concept, media, icons, illustrations, fonts]
|
||||
${budget ? `### BUDGET LOGIC (ULTRA-STRICT):
|
||||
1. **Mental Calculation**: Start with 7.000 €. Add items based on the reference above.
|
||||
2. **Hard Ceiling**: If total > ${budget}, you MUST discard lower priority items.
|
||||
3. **Priority**: High-End Design and Core Pages > Features.
|
||||
4. **Restriction**: For ${budget}, do NOT exceed 2 features and 4 extra pages.
|
||||
5. THE TOTAL COST CALCULATED BY THESE RULES MUST BE <= ${budget}.
|
||||
6. Do NOT mention the budget in any string fields.` : ''}
|
||||
|
||||
### OUTPUT FORMAT (Strict JSON):
|
||||
- ** features **: Items from the FEATURE_REFERENCE.
|
||||
- ** ABSOLUTE CONSERVATIVE RULE **: Only use features if the briefing implies *dynamic complexity* (CMS, filtering, search, database).
|
||||
- Simple keywords like 'Karriere', 'Referenzen', 'Messen' or lists of items MUST be treated as simple pages. Add them to 'otherPages' instead.
|
||||
- If in doubt, categorizing as a PAGE is the mandatory default.
|
||||
- ** otherPages **: Any specific pages mentioned (e.g. 'Historie', 'Team', 'Partner') that are not in the standard list. Use this for static lists of jobs or references too.
|
||||
- ** companyName **: The full legal and brand name (e.g., "E-TIB GmbH"). Use signatures and crawl data.
|
||||
- ** personName **: The name of the primary human contact (e.g., "Danny Joseph"). ** CRITICAL **: Check email signatures and "Mit freundlichen Grüßen" blocks.
|
||||
- ** email **: The email address of the contact person if found in the briefing / signature.
|
||||
- ** existingWebsite **: The primary URL mentioned in the briefing or signature (e.g., "www.e-tib.com").
|
||||
- ** websiteTopic **: A short descriptor of the CORE BUSINESS (e.g., "Kabeltiefbau"). MAX 3 WORDS.
|
||||
- ** isRelaunch **: Set to TRUE if the briefing mentions an existing website, a URL, or if the company is an established entity.
|
||||
- ** CRITICAL LOGIC **: If a URL is mentioned, isRelaunch MUST be TRUE.
|
||||
- For all textual values: USE GERMAN.
|
||||
- ** multilang **: ONLY if the briefing mentions multiple target languages.
|
||||
- ** maps **: If "Google Maps" or location maps are mentioned or implicit.
|
||||
- ** CRITICAL **: Do NOT include "social" in apiSystems unless the user explicitly wants to SYNC / POST content.
|
||||
|
||||
### CATEGORY MAPPING(IDs ONLY):
|
||||
- ** selectedPages **: [Home, About, Services, Contact, Landing, Legal]
|
||||
- ** features **: [blog_news, products, jobs, refs, events]
|
||||
- ** functions **: [search, filter, pdf, forms, members, calendar, multilang, chat]
|
||||
- ** apiSystems **: [crm_erp, payment, marketing, ecommerce, maps, social, analytics]
|
||||
- ** assets **: [existing_website, logo, styleguide, content_concept, media, icons, illustrations, fonts]
|
||||
|
||||
### OUTPUT FORMAT(Strict JSON - ROOT LEVEL):
|
||||
{
|
||||
"companyName": string,
|
||||
"companyAddress": string,
|
||||
"personName": string,
|
||||
"existingWebsite": string,
|
||||
"websiteTopic": string,
|
||||
"isRelaunch": boolean,
|
||||
"selectedPages": string[],
|
||||
"features": string[],
|
||||
"functions": string[],
|
||||
"apiSystems": string[],
|
||||
"assets": string[],
|
||||
"deadline": string (GERMAN),
|
||||
"targetAudience": "B2B" | "B2C" | "Internal" | string (GERMAN),
|
||||
"expectedAdjustments": "low" | "medium" | "high" | string (GERMAN),
|
||||
"employeeCount": "ca. 10+" | "ca. 50+" | "ca. 100+" | "ca. 250+" | "ca. 500+" | "ca. 1000+"
|
||||
"companyName": string,
|
||||
"companyAddress": string,
|
||||
"personName": string,
|
||||
"email": string,
|
||||
"existingWebsite": string,
|
||||
"websiteTopic": string,
|
||||
"isRelaunch": boolean,
|
||||
"selectedPages": string[],
|
||||
"features": string[],
|
||||
"functions": string[],
|
||||
"apiSystems": string[],
|
||||
"assets": string[],
|
||||
"deadline": string(GERMAN),
|
||||
"targetAudience": "B2B" | "B2C" | "Internal" | string(GERMAN),
|
||||
"expectedAdjustments": "low" | "medium" | "high" | string(GERMAN),
|
||||
"employeeCount": "ca. 10+" | "ca. 50+" | "ca. 100+" | "ca. 250+" | "ca. 500+" | "ca. 1000+"
|
||||
}
|
||||
`;
|
||||
const pass1UserPrompt = `BRIEFING (TRUTH SOURCE):\n${briefing}\n\nCOMMENTS:\n${comments}\n\nDISTILLED_CRAWL (CONTEXT ONLY):\n${distilledCrawl}`;
|
||||
const pass1UserPrompt = `BRIEFING(TRUTH SOURCE): \n${briefing} \n\nCOMMENTS: \n${comments} \n\nDISTILLED_CRAWL(CONTEXT ONLY): \n${distilledCrawl} `;
|
||||
const p1Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
|
||||
model: 'google/gemini-3-flash-preview',
|
||||
messages: [{ role: 'system', content: pass1SystemPrompt }, { role: 'user', content: pass1UserPrompt }],
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey} `, 'Content-Type': 'application/json' } });
|
||||
if (!p1Resp.data.choices?.[0]?.message?.content) {
|
||||
console.error('❌ Pass 1 failed. Response:', JSON.stringify(p1Resp.data, null, 2));
|
||||
throw new Error('Pass 1: No content in response');
|
||||
}
|
||||
const facts = JSON.parse(cleanJson(p1Resp.data.choices[0].message.content));
|
||||
|
||||
// 1.5. PASS 1.5: The Feature Auditor (Skeptical Review)
|
||||
console.log(' ↳ Pass 1.5: The Feature Auditor (Skeptical Review)...');
|
||||
const pass15SystemPrompt = `
|
||||
You are a "Strict Cost Controller". Your mission is to prevent over-billing.
|
||||
Review the extracted FEATURES and the BRIEFING.
|
||||
|
||||
### RULE OF THUMB:
|
||||
- A "Feature" (1.500 €) is ONLY justified for complex, dynamic systems (logic, database, CMS-driven management, advanced filtering).
|
||||
- Simple lists, information sections, or static descriptions (e.g., "Messen", "Team", "Historie", "Jobs" as mere text) are ALWAYS "Pages" (600 €).
|
||||
- If the briefing doesn't explicitly mention "Management System", "Filterable Database", or "Client Login", it is likely a PAGE.
|
||||
|
||||
### MISSION:
|
||||
Analyze each feature in the list. Decide if it should stay a "Feature" or be downgraded to the "otherPages" array.
|
||||
|
||||
### OUTPUT FORMAT:
|
||||
Return only the corrected 'features' and 'otherPages' arrays.
|
||||
{
|
||||
"features": string[],
|
||||
"otherPages": string[]
|
||||
}
|
||||
`;
|
||||
const p15Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
|
||||
model: 'google/gemini-3-flash-preview',
|
||||
messages: [
|
||||
{ role: 'system', content: pass15SystemPrompt },
|
||||
{ role: 'user', content: `EXTRACTED_FEATURES: ${JSON.stringify(facts.features)} \nBRIEFING: \n${briefing}` }
|
||||
],
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
addUsage(p15Resp.data);
|
||||
const auditResult = JSON.parse(cleanJson(p15Resp.data.choices[0].message.content));
|
||||
|
||||
// Apply Audit: Downgrade features to otherPages
|
||||
facts.features = auditResult.features || [];
|
||||
facts.otherPages = Array.from(new Set([...(facts.otherPages || []), ...(auditResult.otherPages || [])]));
|
||||
|
||||
// 2. PASS 2: Feature Deep-Dive
|
||||
console.log(' ↳ Pass 2: Feature Deep-Dive...');
|
||||
const pass2SystemPrompt = `
|
||||
You are a detail-oriented Solution Architect.
|
||||
For EVERY item selected in Pass 1 (pages, features, functions, apiSystems), write a specific justification and technical scope.
|
||||
You are a detail - oriented Solution Architect.
|
||||
For EVERY item selected in Pass 1(pages, features, functions, apiSystems), write a specific justification and technical scope.
|
||||
|
||||
### RULES:
|
||||
1. **CONCRETE & SPECIFIC**: Do NOT say "Implementation of X". Say "Displaying X with Y filters".
|
||||
2. **NO EFFECTS**: Do not mention "fade-ins", "animations" or "visual styling". Focus on FUNCTION.
|
||||
3. **ABSOLUTE RULE**: EVERYTHING MUST BE GERMAN.
|
||||
4. **TRANSPARENCY**: Explain exactly what the USER gets.
|
||||
5. **API NOTE**: For 'media' or 'video', explicitly state "Upload & Integration" (NO STREAMING).
|
||||
1. ** CONCRETE & SPECIFIC **: Do NOT say "Implementation of X". Say "Displaying X with Y filters".
|
||||
2. ** JUSTIFICATION (CRITICAL) **: For every entry in 'featureDetails', explicitly explain WHY this is a complex system (1.500 €) and not just a static page (600 €). If it's just a list of items, it's a PAGE.
|
||||
3. ** NO EFFECTS **: Do not mention "fade-ins", "animations" or "visual styling". Focus on FUNCTION.
|
||||
4. ** ABSOLUTE RULE **: EVERYTHING MUST BE GERMAN.
|
||||
5. ** TRANSPARENCY **: Explain exactly what the USER gets.
|
||||
6. ** API NOTE **: For 'media' or 'video', explicitly state "Upload & Integration" (NO STREAMING).
|
||||
|
||||
### INPUT (from Pass 1):
|
||||
${JSON.stringify(facts, null, 2)}
|
||||
|
||||
### OUTPUT FORMAT (Strict JSON):
|
||||
### OUTPUT FORMAT(Strict JSON):
|
||||
{
|
||||
"pageDetails": { "Home": string, ... },
|
||||
"featureDetails": { "blog_news": string, ... },
|
||||
"functionDetails": { "search": string, ... },
|
||||
"apiDetails": { "crm_erp": string, ... }
|
||||
"pageDetails": { "Home": string, ... },
|
||||
"featureDetails": { "blog_news": string, ... },
|
||||
"functionDetails": { "search": string, ... },
|
||||
"apiDetails": { "crm_erp": string, ... }
|
||||
}
|
||||
`;
|
||||
const p2Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
|
||||
model: 'google/gemini-3-flash-preview',
|
||||
messages: [{ role: 'system', content: pass2SystemPrompt }, { role: 'user', content: briefing }],
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey} `, 'Content-Type': 'application/json' } });
|
||||
addUsage(p2Resp.data);
|
||||
if (!p2Resp.data.choices?.[0]?.message?.content) {
|
||||
console.error('❌ Pass 2 failed. Response:', JSON.stringify(p2Resp.data, null, 2));
|
||||
@@ -375,39 +454,39 @@ ${JSON.stringify(facts, null, 2)}
|
||||
// 3. PASS 3: Strategic Content (Bespoke Strategy)
|
||||
console.log(' ↳ Pass 3: Strategic Content (Bespoke Strategy)...');
|
||||
const pass3SystemPrompt = `
|
||||
You are a high-end Digital Architect. Your goal is to make the CUSTOMER feel 100% understood.
|
||||
You are a high - end Digital Architect.Your goal is to make the CUSTOMER feel 100 % understood.
|
||||
Analyze the BRIEFING and the EXISTING WEBSITE context.
|
||||
|
||||
### TONE & COMMUNICATION PRINCIPLES (STRICT):
|
||||
### TONE & COMMUNICATION PRINCIPLES(STRICT):
|
||||
${tone}
|
||||
|
||||
### OBJECTIVE:
|
||||
3. **briefingSummary**: Ein sachlicher, tiefgehender Überblick der Unternehmenslage.
|
||||
- **STIL**: Keine Ich-Form. Keine Marketing-Floskeln. Nutze präzise Fachbegriffe. Sei prägnant und effizient (ca. 70% der vorherigen Länge).
|
||||
- **FORM**: EXAKT ZWEI ABSÄTZE. Insgesamt ca. 6 Sätze.
|
||||
- **INHALT**: Welcher technologische Sprung ist notwendig? Was ist der Status Quo? (Bezug zur URL/Briefing).
|
||||
- **ABSOLUTE REGEL**: Keine Halluzinationen über fehlende Präsenzen bei Relaunches.
|
||||
- **DATENSCHUTZ**: KEINERLEI namentliche Nennungen von Personen (z. B. "Danny Joseph") in diesen Texten.
|
||||
4. **designVision**: Ein abstraktes, strategisches Konzept.
|
||||
- **STIL**: Rein konzeptionell. Keine Umsetzungsschritte. Keinerlei "To-dos". Keine Ich-Form. Sei prägnant.
|
||||
- **FORM**: EXAKT ZWEI ABSÄTZE. Insgesamt ca. 4 Sätze.
|
||||
- **DATENSCHUTZ**: KEINERLEI namentliche Nennungen von Personen in diesen Texten.
|
||||
- **FOKUS**: Welche strategische Wirkung soll erzielt werden? (Z. B. "Industrielle Souveränität").
|
||||
3. ** briefingSummary **: Ein sachlicher, tiefgehender Überblick der Unternehmenslage.
|
||||
- ** STIL **: Keine Ich - Form.Keine Marketing - Floskeln.Nutze präzise Fachbegriffe.Sei prägnant und effizient(ca. 70 % der vorherigen Länge).
|
||||
- ** FORM **: EXAKT ZWEI ABSÄTZE.Insgesamt ca. 6 Sätze.
|
||||
- ** INHALT **: Welcher technologische Sprung ist notwendig ? Was ist der Status Quo ? (Bezug zur URL / Briefing).
|
||||
- ** ABSOLUTE REGEL **: Keine Halluzinationen über fehlende Präsenzen bei Relaunches.
|
||||
- ** DATENSCHUTZ **: KEINERLEI namentliche Nennungen von Personen(z.B. "Danny Joseph") in diesen Texten.
|
||||
4. ** designVision **: Ein abstraktes, strategisches Konzept.
|
||||
- ** STIL **: Rein konzeptionell.Keine Umsetzungsschritte.Keinerlei "To-dos".Keine Ich - Form.Sei prägnant.
|
||||
- ** FORM **: EXAKT ZWEI ABSÄTZE.Insgesamt ca. 4 Sätze.
|
||||
- ** DATENSCHUTZ **: KEINERLEI namentliche Nennungen von Personen in diesen Texten.
|
||||
- ** FOKUS **: Welche strategische Wirkung soll erzielt werden ? (Z.B. "Industrielle Souveränität").
|
||||
|
||||
### OUTPUT FORMAT (Strict JSON):
|
||||
### OUTPUT FORMAT(Strict JSON):
|
||||
{
|
||||
"briefingSummary": string,
|
||||
"designVision": string
|
||||
"briefingSummary": string,
|
||||
"designVision": string
|
||||
}
|
||||
`;
|
||||
const p3Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
|
||||
model: 'google/gemini-3-flash-preview',
|
||||
messages: [
|
||||
{ role: 'system', content: pass3SystemPrompt },
|
||||
{ role: 'user', content: `BRIEFING (TRUTH SOURCE):\n${briefing}\n\nEXISTING WEBSITE (CONTEXT):\n${distilledCrawl}\n\nEXTRACTED FACTS:\n${JSON.stringify(facts, null, 2)}` }
|
||||
{ role: 'user', content: `BRIEFING(TRUTH SOURCE): \n${briefing} \n\nEXISTING WEBSITE(CONTEXT): \n${distilledCrawl} \n\nEXTRACTED FACTS: \n${JSON.stringify(facts, null, 2)} ` }
|
||||
],
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey} `, 'Content-Type': 'application/json' } });
|
||||
addUsage(p3Resp.data);
|
||||
if (!p3Resp.data.choices?.[0]?.message?.content) {
|
||||
console.error('❌ Pass 3 failed. Response:', JSON.stringify(p3Resp.data, null, 2));
|
||||
@@ -418,29 +497,30 @@ ${tone}
|
||||
// 4. PASS 4: Information Architecture (Sitemap)
|
||||
console.log(' ↳ Pass 4: Information Architecture...');
|
||||
const pass4SystemPrompt = `
|
||||
You are a Senior UX Architect. Design a hierarchical sitemap following the 'Industrial Logic' principle.
|
||||
You are a Senior UX Architect.Design a hierarchical sitemap following the 'Industrial Logic' principle.
|
||||
EVERYTHING MUST BE IN GERMAN.
|
||||
|
||||
### SITEMAP RULES:
|
||||
1. **HIERARCHY**: Build a logical tree. Group by category (e.g., "Kern-Präsenz", "Lösungen", "Vertrauen", "Rechtliches").
|
||||
2. **INTENT**: Each page MUST have a title and a brief functional conversion intent (desc).
|
||||
3. **COMPREHENSIVENESS**: Ensure all 'selectedPages' and 'features' from Pass 1 are represented.
|
||||
4. **LANGUAGE**: STRICT GERMAN TITLES. Do NOT use "Home", "About", "Services". Use "Startseite", "Über uns", "Leistungen".
|
||||
1. ** HIERARCHY **: Build a logical tree.Group by category(e.g., "Kern-Präsenz", "Lösungen", "Vertrauen", "Rechtliches").
|
||||
2. ** INTENT **: Each page MUST have a title and a brief functional conversion intent(desc).
|
||||
3. ** COMPREHENSIVENESS **: Ensure all 'selectedPages' and 'features' from Pass 1 are represented.
|
||||
4. ** LANGUAGE **: STRICT GERMAN TITLES.Do NOT use "Home", "About", "Services".Use "Startseite", "Über uns", "Leistungen".
|
||||
5. ** NO IMPLEMENTATION NOTES **: Do NOT add implementation details in parentheses to titles (e.g. NO "Startseite (Hero-Video)", NO "About (Timeline)"). Keep titles clean and abstract.
|
||||
|
||||
### DATA CONTEXT:
|
||||
${JSON.stringify({ facts, strategy }, null, 2)}
|
||||
|
||||
### OUTPUT FORMAT (Strict JSON):
|
||||
### OUTPUT FORMAT(Strict JSON):
|
||||
{
|
||||
"websiteTopic": string,
|
||||
"sitemap": [ { "category": string, "pages": [ { "title": string, "desc": string } ] } ]
|
||||
"websiteTopic": string,
|
||||
"sitemap": [{ "category": string, "pages": [{ "title": string, "desc": string }] }]
|
||||
}
|
||||
`;
|
||||
const p4Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
|
||||
model: 'google/gemini-3-flash-preview',
|
||||
messages: [{ role: 'system', content: pass4SystemPrompt }, { role: 'user', content: `BRIEFING (TRUTH SOURCE):\n${briefing}\n\nDISTILLED_CRAWL (CONTEXT):\n${distilledCrawl}` }],
|
||||
messages: [{ role: 'system', content: pass4SystemPrompt }, { role: 'user', content: `BRIEFING(TRUTH SOURCE): \n${briefing} \n\nDISTILLED_CRAWL(CONTEXT): \n${distilledCrawl} ` }],
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey} `, 'Content-Type': 'application/json' } });
|
||||
addUsage(p4Resp.data);
|
||||
if (!p4Resp.data.choices?.[0]?.message?.content) {
|
||||
console.error('❌ Pass 4 failed. Response:', JSON.stringify(p4Resp.data, null, 2));
|
||||
@@ -450,57 +530,75 @@ ${JSON.stringify({ facts, strategy }, null, 2)}
|
||||
|
||||
// 5. PASS 5: Position Synthesis & Pricing Transparency
|
||||
console.log(' ↳ Pass 5: Position Synthesis...');
|
||||
|
||||
// Determine which positions are actually relevant to avoid hallucinations
|
||||
const requiredPositions = [
|
||||
"1. Das technische Fundament",
|
||||
facts.selectedPages.length + facts.otherPages.length > 0 ? "2. Individuelle Seiten" : null,
|
||||
facts.features.length > 0 ? "3. System-Module (Features)" : null,
|
||||
facts.functions.length > 0 ? "4. Logik-Funktionen" : null,
|
||||
facts.apiSystems.length > 0 ? "5. Schnittstellen (API)" : null,
|
||||
facts.cmsSetup ? "6. Inhaltsverwaltung (CMS)" : null,
|
||||
"7. Inszenierung & Interaktion", // Always include for high-end strategy
|
||||
facts.multilang ? "8. Mehrsprachigkeit" : null,
|
||||
"9. Inhaltliche Initial-Pflege",
|
||||
"10. Sorglos-Paket (Betrieb & Pflege)"
|
||||
].filter(Boolean);
|
||||
|
||||
const pass5SystemPrompt = `
|
||||
You are a Senior Solution Architect. Your goal is ABSOLUTE TRANSPARENCY and professionalism.
|
||||
Each position in the quote must be perfectly justified and detailed using an objective, technical tone.
|
||||
|
||||
### POSITION TITLES (STRICT - MUST MATCH EXACTLY):
|
||||
"1. Das technische Fundament", "2. Individuelle Seiten", "3. System-Module (Features)", "4. Logik-Funktionen", "5. Schnittstellen (API)", "6. Inhaltsverwaltung (CMS)", "7. Inszenierung & Interaktion", "8. Mehrsprachigkeit", "9. Inhaltliche Initial-Pflege", "10. Laufender Betrieb & Hosting".
|
||||
### REQUIRED POSITION TITLES (STRICT - ONLY DESCRIBE THESE):
|
||||
${requiredPositions.map(p => `"${p}"`).join(", ")}
|
||||
|
||||
### MAPPING RULES (STRICT):
|
||||
- **1. Das technische Fundament**: Infrastructure, Hosting setup, SEO-Basics, Analytics, Environments.
|
||||
- **2. Individuelle Seiten**: Layout/structure for specific pages (Home, About, etc.).
|
||||
- **3. System-Module (Features)**: Functional systems like Blog, News, Products, Jobs, References.
|
||||
- **4. Logik-Funktionen**: Logic modules like Search, Filter, Forms, PDF-Export.
|
||||
- **5. Schnittstellen (API)**: Data Syncs with CRM, ERP, Payment systems.
|
||||
- **6. Inhaltsverwaltung (CMS)**: Setup and mapping for CMS.
|
||||
- **7. Inszenierung & Interaktion**: Hero-stories, visual effects, configurators.
|
||||
- **8. Mehrsprachigkeit**: Architecture scaling for multiple languages.
|
||||
- **9. Inhaltliche Initial-Pflege**: Manual data entry/cleanup.
|
||||
- **10. Laufender Betrieb & Hosting**: Ongoing maintenance, updates, 24/7 monitoring.
|
||||
- ** 1. Das technische Fundament **: Infrastructure, Hosting setup, SEO-Basics, Analytics, Environments.
|
||||
- ** 2. Individuelle Seiten **: Layout / structure for specific pages. ** RULE **: If quantity is high (e.g. > 10), lead with "Umsetzung von [QTY] individuellen Einzelseiten...".
|
||||
- ** 3. System-Module (Features) **: Functional systems like Blog, News, Products, Jobs, References. ** RULE **: Describe exactly 1 thing if qty is 1. If qty is 0, DO NOT DESCRIBE THIS.
|
||||
- ** 4. Logik-Funktionen **: Logic modules like Search, Filter, Forms, PDF-Export.
|
||||
- ** 5. Schnittstellen (API) **: Data Syncs with CRM, ERP, Payment systems.
|
||||
- ** 6. Inhaltsverwaltung (CMS) **: Setup and mapping for CMS.
|
||||
- ** 7. Inszenierung & Interaktion **: Hero-stories, visual effects, configurators.
|
||||
- ** 8. Mehrsprachigkeit **: Architecture scaling for multiple languages.
|
||||
- ** 9. Inhaltliche Initial-Pflege **: Manual data entry / cleanup.
|
||||
- ** 10. Sorglos-Paket (Betrieb & Pflege) **: ** RULE **: Describe as "1 Jahr Sicherung des technischen Betriebs, Instandhaltung, Sicherheits-Updates und Inhalts-Aktualisierungen gemäß AGB Punkt 7a."
|
||||
|
||||
### RULES FOR positionDescriptions (STRICT):
|
||||
1. **ABSOLUTE RULE: NO FIRST PERSON**: NEVER use "Ich", "Mein", "Wir" or "Unser". Lead with nouns or passive verbs.
|
||||
2. **PROFESSIONAL TONE**: Use "Erstellung von...", "Anbindung der...", "Implementierung technischer...", "Bereitstellung von...".
|
||||
3. **CONCISE & ITEM-BASED**: Use technical, high-density sentences. Name specific industry terms from context.
|
||||
4. **ITEMIZED SYNTHESIS**: Mention EVERY component selected in Pass 1.
|
||||
5. **HARD SPECIFICS**: If the briefing mentions "Glasfaser-Trassen" or "Schwerlast-Logistik", IT MUST BE IN THE DESCRIPTION.
|
||||
6. **INDUSTRIAL AMBITION**: Describe it as a high-end technical solution. Avoid "schöne Website" or marketing fluff.
|
||||
7. **PAGES**: For "2. Individuelle Seiten", list the pages (e.g., "Startseite (Hero-Video), Leistungen, Kontakt").
|
||||
8. **LOGIC**: Describe the ACTUAL logic (e.g., "Volltextsuche mit Auto-Complete", not "eine Suche").
|
||||
9. **KEYS**: Return EXACTLY the keys defined in "POSITION TITLES".
|
||||
10. **NO AGB**: NEVER mention "AGB" or "Geschäftsbedingungen".
|
||||
### RULES FOR positionDescriptions(STRICT):
|
||||
1. ** ABSOLUTE RULE: NO FIRST PERSON **: NEVER use "Ich", "Mein", "Wir" or "Unser". Lead with nouns or passive verbs.
|
||||
2. ** QUANTITY PARITY (ULTRA-STRICT) **: The description MUST list EXACTLY the number of items matching the 'qty' for that position. If qty is 3, describe exactly 3 items. If qty is 1, describe exactly 1 item. Do NOT "stuff" additional features into one description.
|
||||
3. ** LOGIC GUARD (CMS) **: If 'cmsSetup' is false in the DATA CONTEXT, you MUST NOT mention "CMS", "Modul", "Management System" or "Inhaltsverwaltung". Use "Statische Seite" or "Darstellung".
|
||||
4. ** STATIC vs DYNAMIC **: If no complex logic was extracted in Pass 2 for a feature, describe it as a technical layout/page, not as a system.
|
||||
5. ** PROFESSIONAL TONE **: Use "Erstellung von...", "Anbindung der...", "Implementierung technischer...", "Bereitstellung von...".
|
||||
6. ** CONCISE & ITEM-BASED **: Use technical, high-density sentences. Name specific industry terms from context.
|
||||
7. ** ITEMIZED SYNTHESIS **: Mention EVERY component selected in Pass 1.
|
||||
8. ** HARD SPECIFICS **: If the briefing mentions "Glasfaser-Trassen" or "Schwerlast-Logistik", IT MUST BE IN THE DESCRIPTION.
|
||||
9. ** INDUSTRIAL AMBITION **: Describe it as a high-end technical solution. Avoid "schöne Website" or marketing fluff.
|
||||
10. ** PAGES **: For "2. Individuelle Seiten", list the pages. ** ABSOLUTE RULE **: Do NOT add implementation details or technical notes in parentheses (e.g. NO "(Matrix-Struktur)", NO "(Timeline-Modul)"). Use clean titles like "Startseite, Über uns, Leistungen".
|
||||
11. ** LOGIC **: Describe the ACTUAL logic (e.g., "Volltextsuche mit Auto-Complete", not "eine Suche").
|
||||
12. ** KEYS **: Return EXACTLY the keys defined in "POSITION TITLES".
|
||||
13. ** NO AGB **: NEVER mention "AGB" or "Geschäftsbedingungen".
|
||||
|
||||
### EXAMPLES (PASSIVE & TECHNICAL):
|
||||
- **GOOD**: "Konfiguration der CMS-Infrastruktur zur unabhängigen Verwaltung von Produkt-Katalogen und News-Beiträgen."
|
||||
- **GOOD**: "Implementierung einer Volltextsuche inkl. Kategorisierungs-Logik für effizientes Auffinden von Projektreferenzen."
|
||||
- **GOOD**: "Native API-Anbindung an das ERP-System zur Echtzeit-Synchronisation von Bestandsdaten."
|
||||
- **BAD**: "Ich richte dir das CMS ein."
|
||||
- **BAD**: "Ich programmiere eine tolle Suche für deine Seite."
|
||||
### EXAMPLES(PASSIVE & TECHNICAL):
|
||||
- ** GOOD **: "Konfiguration der CMS-Infrastruktur zur unabhängigen Verwaltung von Produkt-Katalogen und News-Beiträgen."
|
||||
- ** GOOD **: "Implementierung einer Volltextsuche inkl. Kategorisierungs-Logik für effizientes Auffinden von Projektreferenzen."
|
||||
- ** GOOD **: "Native API-Anbindung an das ERP-System zur Echtzeit-Synchronisation von Bestandsdaten."
|
||||
- ** BAD **: "Ich richte dir das CMS ein."
|
||||
- ** BAD **: "Ich programmiere eine tolle Suche für deine Seite."
|
||||
|
||||
### DATA CONTEXT:
|
||||
${JSON.stringify({ facts, details, strategy, ia }, null, 2)}
|
||||
|
||||
### OUTPUT FORMAT (Strict JSON):
|
||||
### OUTPUT FORMAT(Strict JSON):
|
||||
{
|
||||
"positionDescriptions": { "1. Das technische Fundament": string, ... }
|
||||
"positionDescriptions": { "1. Das technische Fundament": string, ... }
|
||||
}
|
||||
`;
|
||||
const p5Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
|
||||
model: 'google/gemini-3-flash-preview',
|
||||
messages: [{ role: 'system', content: pass5SystemPrompt }, { role: 'user', content: briefing }],
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey} `, 'Content-Type': 'application/json' } });
|
||||
addUsage(p5Resp.data);
|
||||
if (!p5Resp.data.choices?.[0]?.message?.content) {
|
||||
console.error('❌ Pass 5 failed. Response:', JSON.stringify(p5Resp.data, null, 2));
|
||||
@@ -511,31 +609,32 @@ ${JSON.stringify({ facts, details, strategy, ia }, null, 2)}
|
||||
// 6. PASS 6: The Industrial Critic
|
||||
console.log(' ↳ Pass 6: The Industrial Critic (Quality Gate)...');
|
||||
const pass6SystemPrompt = `
|
||||
You are the "Industrial Critic". Your goal is to catch quality regressions and ensure the document is bespoke, technical, and professional.
|
||||
You are the "Industrial Critic".Your goal is to catch quality regressions and ensure the document is bespoke, technical, and professional.
|
||||
Analyze the CURRENT_STATE against the BRIEFING_TRUTH.
|
||||
|
||||
### CRITICAL ERROR CHECKLIST (FAIL IF FOUND):
|
||||
1. **Hallucination Leakage**: FAIL if names of people (e.g., "Frieder Helmich"), specific software versions, or invented details are used unless they appear EXACTLY in the BRIEFING.
|
||||
- **CRITICAL**: Forbid "Sie", "Ansprechpartner" or "Unternehmen" for personName if a name IS in the briefing. If none is in briefing, use empty string.
|
||||
2. **Logic Conflict**: FAIL if isRelaunch is true but briefingSummary claims no website exists.
|
||||
### CRITICAL ERROR CHECKLIST(FAIL IF FOUND):
|
||||
1. ** Hallucination Leakage **: FAIL if names of people(e.g., "Frieder Helmich"), specific software versions, or invented details are used unless they appear EXACTLY in the BRIEFING.
|
||||
- ** CRITICAL **: Forbid "Sie", "Ansprechpartner" or "Unternehmen" for personName if a name IS in the briefing.If none is in briefing, use empty string.
|
||||
2. ** Logic Conflict **: FAIL if isRelaunch is true but briefingSummary claims no website exists.
|
||||
- FAIL if the description in positionDescriptions mentions more items than extracted in facts.
|
||||
3. **Implementation Fluff**: FAIL if tech-stack details are mentioned (React, etc.). Focus on Concept & Result.
|
||||
4. **Genericism Check (CRITICAL)**: FAIL if any text sounds like it could apply to ANY company. It MUST mention specific industry details (e.g., "Kabeltiefbau", "Infrastruktur-Zentrum") from the Briefing or Crawl.
|
||||
6. **Namen-Verbot (STRICT)**: FAIL if any personal names (e.g. "Danny Joseph", "Joseph", etc.) appear in 'briefingSummary' or 'designVision'. Use abstract terms like "Unternehmensführung" or "Management" if necessary.
|
||||
7. **AGB BAN**: FAIL if "Allgemeine Geschäftsbedingungen" or "AGB" appear anywhere.
|
||||
8. **Length Check**: Briefing (ca. 6 Sätze) und Vision (ca. 4 Sätze). Kürze Texte, die zu ausschweifend sind, auf das Wesentliche.
|
||||
3. ** Implementation Fluff **: FAIL if tech - stack details are mentioned(React, etc.).Focus on Concept & Result.
|
||||
4. ** Genericism Check(CRITICAL) **: FAIL if any text sounds like it could apply to ANY company.It MUST mention specific industry details(e.g., "Kabeltiefbau", "Infrastruktur-Zentrum") from the Briefing or Crawl.
|
||||
6. ** Namen-Verbot (STRICT) **: FAIL if any personal names (e.g. "Danny Joseph", "Joseph", etc.) appear in 'briefingSummary' or 'designVision'. Use abstract terms like "Unternehmensführung" or "Management" if necessary.
|
||||
7. ** LOGIC GUARD (CMS) **: If 'cmsSetup' is false in the DATA CONTEXT, FAIL if any 'positionDescriptions' or 'briefingSummary' mentions "CMS", "Content Management System" or "Inhaltsverwaltung".
|
||||
8. ** AGB BAN **: FAIL if "Allgemeine Geschäftsbedingungen" or "AGB" appear anywhere.
|
||||
9. ** Length Check **: Briefing (ca. 6 Sätze) und Vision (ca. 4 Sätze). Kürze Texte, die zu ausschweifend sind, auf das Wesentliche.
|
||||
|
||||
### MISSION:
|
||||
Return updated fields ONLY. Specifically focus on hardening 'positionDescriptions', 'sitemap', 'briefingSummary', and 'designVision'.
|
||||
Return updated fields ONLY.Specifically focus on hardening 'positionDescriptions', 'sitemap', 'briefingSummary', and 'designVision'.
|
||||
|
||||
### DATA CONTEXT:
|
||||
${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
|
||||
`;
|
||||
const p6Resp = await axios.post('https://openrouter.ai/api/v1/chat/completions', {
|
||||
model: 'google/gemini-3-flash-preview',
|
||||
messages: [{ role: 'system', content: pass6SystemPrompt }, { role: 'user', content: `BRIEFING_TRUTH:\n${briefing}` }],
|
||||
messages: [{ role: 'system', content: pass6SystemPrompt }, { role: 'user', content: `BRIEFING_TRUTH: \n${briefing} ` }],
|
||||
response_format: { type: 'json_object' }
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey}`, 'Content-Type': 'application/json' } });
|
||||
}, { headers: { 'Authorization': `Bearer ${apiKey} `, 'Content-Type': 'application/json' } });
|
||||
addUsage(p6Resp.data);
|
||||
if (!p6Resp.data.choices?.[0]?.message?.content) {
|
||||
console.error('❌ Pass 6 failed. Response:', JSON.stringify(p6Resp.data, null, 2));
|
||||
@@ -548,12 +647,10 @@ ${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
|
||||
let result = { ...state };
|
||||
const unwrap = (obj: any): any => {
|
||||
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) return obj;
|
||||
// Always unwrap "0" if it exists, regardless of other keys (AI often nests)
|
||||
if (obj["0"]) return unwrap(obj["0"]);
|
||||
if (obj.state) return unwrap(obj.state);
|
||||
if (obj.facts) return unwrap(obj.facts);
|
||||
if (obj.strategy) return unwrap(obj.strategy);
|
||||
if (obj.ia) return unwrap(obj.ia);
|
||||
if (obj.positionsData) return unwrap(obj.positionsData);
|
||||
if (obj.state && Object.keys(obj).length === 1) return unwrap(obj.state);
|
||||
if (obj.facts && Object.keys(obj).length === 1) return unwrap(obj.facts);
|
||||
return obj;
|
||||
};
|
||||
|
||||
@@ -576,6 +673,10 @@ ${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
|
||||
|
||||
finalState.statusQuo = facts.isRelaunch ? 'Relaunch' : 'Neuentwicklung';
|
||||
|
||||
// Recipient Mapping
|
||||
if (finalState.personName) finalState.name = finalState.personName;
|
||||
if (finalState.email) finalState.email = finalState.email;
|
||||
|
||||
// Normalization Layer: Map hallucinated German keys back to internal keys
|
||||
const normalizationMap: Record<string, string> = {
|
||||
"Briefing-Zusammenfassung": "briefingSummary",
|
||||
@@ -596,6 +697,27 @@ ${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
|
||||
}
|
||||
});
|
||||
|
||||
// Final Logic Guard: Strictly strip CMS from ALL descriptions and fields if not enabled
|
||||
if (!finalState.cmsSetup) {
|
||||
const stripCMS = (obj: any): any => {
|
||||
if (typeof obj === 'string') {
|
||||
return obj.replace(/CMS|Content-Management-System|Inhaltsverwaltung/gi, 'Plattform-Struktur');
|
||||
}
|
||||
if (Array.isArray(obj)) {
|
||||
return obj.map(stripCMS);
|
||||
}
|
||||
if (obj !== null && typeof obj === 'object') {
|
||||
const newObj: any = {};
|
||||
Object.entries(obj).forEach(([k, v]) => {
|
||||
newObj[k] = stripCMS(v);
|
||||
});
|
||||
return newObj;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
finalState = stripCMS(finalState);
|
||||
}
|
||||
|
||||
// Sitemap Normalization (German keys to internal)
|
||||
if (Array.isArray(finalState.sitemap)) {
|
||||
finalState.sitemap = finalState.sitemap.map((cat: any) => ({
|
||||
@@ -607,14 +729,41 @@ ${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
|
||||
}));
|
||||
}
|
||||
|
||||
// Position Descriptions Normalization
|
||||
// Position Descriptions Normalization (Strict Title Mapping + Index-based Fallback)
|
||||
if (finalState.positionDescriptions) {
|
||||
const normalized: Record<string, string> = {};
|
||||
Object.entries(finalState.positionDescriptions).forEach(([key, value]) => {
|
||||
const normalizedKey = key === 'titel' || key === 'Title' ? 'title' : key;
|
||||
const rawPositions = finalState.positionDescriptions;
|
||||
|
||||
// 1. Initial cleanup
|
||||
Object.entries(rawPositions).forEach(([key, value]) => {
|
||||
const normalizedValue = typeof value === 'object' ? (value as any).beschreibung || (value as any).description || JSON.stringify(value) : value;
|
||||
normalized[normalizedKey] = normalizedValue as string;
|
||||
normalized[key] = normalizedValue as string;
|
||||
});
|
||||
|
||||
// 2. Index-based matching (Map "10. Foo" to "10. Bar")
|
||||
const standardTitles = [
|
||||
"1. Das technische Fundament",
|
||||
"2. Individuelle Seiten",
|
||||
"3. System-Module (Features)",
|
||||
"4. Logik-Funktionen",
|
||||
"5. Schnittstellen (API)",
|
||||
"6. Inhaltsverwaltung (CMS)",
|
||||
"7. Inszenierung & Interaktion",
|
||||
"8. Mehrsprachigkeit",
|
||||
"9. Inhaltliche Initial-Pflege",
|
||||
"10. Sorglos-Paket (Betrieb & Pflege)"
|
||||
];
|
||||
|
||||
standardTitles.forEach(std => {
|
||||
const prefix = std.split('.')[0] + '.'; // e.g., "10."
|
||||
// Find any key in the AI output that starts with this number
|
||||
const matchingKey = Object.keys(normalized).find(k => k.trim().startsWith(prefix));
|
||||
if (matchingKey && matchingKey !== std) {
|
||||
normalized[std] = normalized[matchingKey];
|
||||
// Keep the old key too just in case, but prioritize the standard one
|
||||
}
|
||||
});
|
||||
|
||||
finalState.positionDescriptions = normalized;
|
||||
}
|
||||
|
||||
@@ -639,6 +788,69 @@ ${JSON.stringify({ facts, strategy, ia, positionsData }, null, 2)}
|
||||
}
|
||||
}
|
||||
|
||||
// Final Post-Reflection Budget Sync (Hard Pruning if still over)
|
||||
if (budget) {
|
||||
const targetValue = parseInt(budget.replace(/[^0-9]/g, ''));
|
||||
if (!isNaN(targetValue)) {
|
||||
console.log(`⚖️ Final Budget Audit(${targetValue} € target)...`);
|
||||
let currentTotals = calculateTotals(finalState, PRICING);
|
||||
|
||||
// Step-by-step pruning if too expensive
|
||||
if (currentTotals.totalPrice > targetValue) {
|
||||
console.log(`⚠️ Budget exceeded(${currentTotals.totalPrice} €).Pruning scope to fit ${targetValue} €...`);
|
||||
|
||||
// 1. Remove optional "other" stuff
|
||||
finalState.otherFeatures = [];
|
||||
finalState.otherFunctions = [];
|
||||
finalState.otherTech = [];
|
||||
|
||||
// 2. Remove non-critical functions
|
||||
const funcPriority = ['search', 'filter', 'calendar', 'multilang'];
|
||||
for (const f of funcPriority) {
|
||||
if (currentTotals.totalPrice <= targetValue) break;
|
||||
if (finalState.functions.includes(f)) {
|
||||
finalState.functions = finalState.functions.filter((x: string) => x !== f);
|
||||
currentTotals = calculateTotals(finalState, PRICING);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Remove least critical features if still over
|
||||
const featurePriority = ['events', 'blog_news', 'products'];
|
||||
for (const p of featurePriority) {
|
||||
if (currentTotals.totalPrice <= targetValue) break;
|
||||
if (finalState.features.includes(p)) {
|
||||
finalState.features = finalState.features.filter((f: string) => f !== p);
|
||||
currentTotals = calculateTotals(finalState, PRICING);
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Reduce page count (Selected Pages AND Sitemap)
|
||||
while (currentTotals.totalPrice > targetValue && (finalState.selectedPages.length > 4 || currentTotals.totalPagesCount > 5)) {
|
||||
if (finalState.selectedPages.length > 4) {
|
||||
finalState.selectedPages.pop();
|
||||
}
|
||||
|
||||
// Prune Sitemap to match
|
||||
if (finalState.sitemap && Array.isArray(finalState.sitemap)) {
|
||||
const lastCat = finalState.sitemap[finalState.sitemap.length - 1];
|
||||
if (lastCat && lastCat.pages && lastCat.pages.length > 0) {
|
||||
lastCat.pages.pop();
|
||||
if (lastCat.pages.length === 0) finalState.sitemap.pop();
|
||||
}
|
||||
}
|
||||
currentTotals = calculateTotals(finalState, PRICING);
|
||||
}
|
||||
|
||||
// 5. Final fallback: Remove second feature if still over
|
||||
if (currentTotals.totalPrice > targetValue && finalState.features.length > 1) {
|
||||
finalState.features.pop();
|
||||
currentTotals = calculateTotals(finalState, PRICING);
|
||||
}
|
||||
}
|
||||
console.log(`✅ Final budget audit complete: ${currentTotals.totalPrice} €`);
|
||||
}
|
||||
}
|
||||
|
||||
return { state: finalState, usage };
|
||||
}
|
||||
|
||||
|
||||
@@ -104,7 +104,7 @@ export function PriceCalculation({
|
||||
</div>
|
||||
<div className="pt-4 border-t border-slate-200 space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<span className="text-slate-600 font-medium text-sm">Betrieb & Hosting</span>
|
||||
<span className="text-slate-600 font-medium text-sm">Sorglos-Paket</span>
|
||||
<span className="text-base font-bold text-slate-900">{monthlyPrice.toLocaleString()} € / Monat</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -54,9 +54,9 @@ export const AboutModule = () => (
|
||||
</PDFView>
|
||||
|
||||
<PDFView style={{ marginTop: 32, paddingVertical: 16, borderTopWidth: 1, borderTopColor: COLORS.GRID }}>
|
||||
<PDFText style={[styles.industrialText, { fontWeight: 'bold', color: COLORS.CHARCOAL, marginBottom: 4 }]}>Qualitäts-Zusage</PDFText>
|
||||
<PDFText style={[styles.industrialText, { fontWeight: 'bold', color: COLORS.CHARCOAL, marginBottom: 4 }]}>Infrastruktur & Souveränität</PDFText>
|
||||
<PDFText style={styles.industrialText}>
|
||||
Es werden keine instabilen Prototypen geliefert, sondern produktionsreife Software, die skalierbar bleibt und mit dem Unternehmen wächst. Direkt. Sauber. Ohne Ballast.
|
||||
Es wird keine instabile Prototyp-Software geliefert, sondern produktionsreife Systeme, die technisch skalierbar bleiben. Die Codebasis folgt modernen Standards – bei wachsenden Ansprüchen oder dem Wechsel zu einer Agentur kann der Quellcode jederzeit nahtlos übernommen und weitergeführt werden.
|
||||
</PDFText>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
@@ -65,8 +65,8 @@ export const AboutModule = () => (
|
||||
|
||||
export const CrossSellModule = ({ state }: any) => {
|
||||
const isWebsite = state.projectType === 'website';
|
||||
const title = isWebsite ? "Routine-Automatisierung" : "Websites & Ökosysteme";
|
||||
const subtitle = isWebsite ? "Maßgeschneiderte Software zur Prozessoptimierung" : "Technische Infrastruktur ohne Kompromisse";
|
||||
const title = isWebsite ? "Weitere Potenziale" : "Websites & Ökosysteme";
|
||||
const subtitle = isWebsite ? "Automatisierung und Prozessoptimierung" : "Technische Infrastruktur ohne Kompromisse";
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -77,7 +77,7 @@ export const CrossSellModule = ({ state }: any) => {
|
||||
{isWebsite ? (
|
||||
<>
|
||||
<PDFView style={[styles.industrialCol, { marginRight: '8%' }]}>
|
||||
<PDFText style={styles.industrialTextLead}>Manuelle Alltags-Aufgaben sind teuer und fehleranfällig. In einer separaten Beauftragung können maßgeschneiderte Systeme entwickelt werden, die Routine-Prozesse automatisiert im Hintergrund verarbeiten.</PDFText>
|
||||
<PDFText style={styles.industrialTextLead}>Über die klassische Webpräsenz hinaus werden maßgeschneiderte Lösungen zur Automatisierung von Routine-Prozessen angeboten. Dies ermöglicht eine signifikante Effizienzsteigerung im Tagesgeschäft.</PDFText>
|
||||
<PDFText style={[styles.industrialText, { fontWeight: 'bold' }]}>Keine Abos. Keine komplexen neuen Systeme. Gezielte Zeitersparnis.</PDFText>
|
||||
<PDFView style={{ marginTop: 24, padding: 16, backgroundColor: '#f8fafc', borderLeftWidth: 2, borderLeftColor: COLORS.GRID }}>
|
||||
<PDFText style={[styles.industrialText, { fontWeight: 'bold', color: COLORS.CHARCOAL, marginBottom: 4 }]}>Individuelle Analyse</PDFText>
|
||||
|
||||
@@ -5,11 +5,11 @@ import { View as PDFView, Text as PDFText, StyleSheet, Image as PDFImage } from
|
||||
import { DocumentTitle, Divider, COLORS, FONT_SIZES } from '../SharedUI';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
section: { marginBottom: 24 },
|
||||
pricingGrid: { marginTop: 24 },
|
||||
pricingRow: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: COLORS.DIVIDER, paddingVertical: 10, alignItems: 'flex-start' },
|
||||
pricingTitle: { width: '30%', fontSize: FONT_SIZES.BODY, fontWeight: 'bold', color: COLORS.CHARCOAL },
|
||||
pricingDesc: { width: '55%', fontSize: FONT_SIZES.SUB, color: COLORS.TEXT_DIM, lineHeight: 1.4 },
|
||||
section: { marginBottom: 16 },
|
||||
pricingGrid: { marginTop: 12 },
|
||||
pricingRow: { flexDirection: 'row', borderBottomWidth: 1, borderBottomColor: COLORS.DIVIDER, paddingVertical: 12, alignItems: 'flex-start' },
|
||||
pricingTitle: { width: '30%', fontSize: FONT_SIZES.BODY, fontWeight: 'bold', color: COLORS.CHARCOAL, paddingRight: 15 },
|
||||
pricingDesc: { width: '55%', fontSize: FONT_SIZES.SUB, color: COLORS.TEXT_DIM, lineHeight: 1.5, paddingRight: 10 },
|
||||
pricingTag: { width: '15%', fontSize: FONT_SIZES.BODY, fontWeight: 'bold', textAlign: 'right', color: COLORS.CHARCOAL },
|
||||
configLabel: { fontSize: FONT_SIZES.BLUEPRINT, color: COLORS.TEXT_LIGHT, textTransform: 'uppercase', marginBottom: 8 },
|
||||
});
|
||||
@@ -18,12 +18,11 @@ export const techPageModule = ({ techDetails, headerIcon }: any) => (
|
||||
<>
|
||||
<DocumentTitle title="Technische Umsetzung" />
|
||||
<PDFView style={styles.section}>
|
||||
<PDFText style={{ fontSize: FONT_SIZES.SUB, color: COLORS.TEXT_DIM, lineHeight: 1.6, marginBottom: 16 }}>Entwicklung von Websites als moderne, performante Websysteme nach industriellen Standards.</PDFText>
|
||||
<PDFView style={styles.pricingGrid}>
|
||||
{techDetails?.map((item: any, i: number) => (
|
||||
<PDFView key={i} style={styles.pricingRow}>
|
||||
<PDFText style={[styles.pricingTitle, { width: '35%' }]}>{item.t}</PDFText>
|
||||
<PDFText style={[styles.pricingDesc, { width: '65%' }]}>{item.d}</PDFText>
|
||||
<PDFText style={[styles.pricingTitle, { width: '30%' }]}>{item.t}</PDFText>
|
||||
<PDFText style={[styles.pricingDesc, { width: '70%', paddingRight: 0 }]}>{item.d}</PDFText>
|
||||
</PDFView>
|
||||
))}
|
||||
</PDFView>
|
||||
@@ -34,62 +33,57 @@ export const techPageModule = ({ techDetails, headerIcon }: any) => (
|
||||
export const TransparenzModule = ({ pricing }: any) => (
|
||||
<>
|
||||
<DocumentTitle title="Preis-Transparenz & Modell" />
|
||||
<PDFView style={styles.section}>
|
||||
<PDFText style={{ fontSize: FONT_SIZES.BODY + 1, fontWeight: 'bold', color: COLORS.CHARCOAL, marginBottom: 8 }}>Festpreise statt Stundenabrechnung</PDFText>
|
||||
<PDFText style={{ fontSize: FONT_SIZES.SUB, color: COLORS.TEXT_DIM, lineHeight: 1.6, marginBottom: 12 }}>Maximale Planungssicherheit durch ein modulares Festpreis-System. Die Abrechnung erfolgt nach Ergebnissen statt nach reinem Zeitaufwand. Versteckte Kosten sind durch die modulare Struktur ausgeschlossen.</PDFText>
|
||||
<Divider style={{ marginTop: 12 }} />
|
||||
</PDFView>
|
||||
<PDFView style={styles.section}>
|
||||
<PDFView style={styles.pricingGrid}>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>1. Das technische Fundament</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Einrichtung der technischen Infrastruktur, Hosting-Bereitstellung, SEO-Basics sowie Bereitstellung von Test-, Staging- und produktiven Live-Umgebungen.</PDFText>
|
||||
<PDFText style={styles.pricingTitle}>1. Fundament</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Setup, Infrastruktur, Hosting, SEO-Basics, Staging & Live-Umgebungen.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>{pricing.BASE_WEBSITE?.toLocaleString('de-DE')} €</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>2. Individuelle Seiten</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Entwicklung individueller Layouts und Strukturen pro Seite. Optimierung für alle Endgeräte (Responsive Design) und Browser-Kompatibilität.</PDFText>
|
||||
<PDFText style={styles.pricingTitle}>2. Seiten</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Layout & Umsetzung individueller Seiten. Responsive Design / Cross-Browser.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>{pricing.PAGE?.toLocaleString('de-DE')} € / Stk</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>3. System-Module (Features)</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Implementierung abgeschlossener technischer Systeme (z. B. Blog, News, Produkte). Definition notwendiger Datenfelder und Pflege-Oberflächen.</PDFText>
|
||||
<PDFText style={styles.pricingTitle}>3. Features</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Abgeschlossene Systeme (z. B. Blog, Jobs, Produkte) inkl. Datenstruktur.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>{pricing.FEATURE?.toLocaleString('de-DE')} € / Stk</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>4. Logik-Funktionen</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Programmierung technischer Logik-Einheiten wie Filter, Suchen oder Kontakt-Schnittstellen zur fehlerfreien Datenverarbeitung.</PDFText>
|
||||
<PDFText style={styles.pricingTitle}>4. Funktionen</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Logik-Einheiten wie Filter, Suchen oder Kontakt-Schnittstellen.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>{pricing.FUNCTION?.toLocaleString('de-DE')} € / Stk</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>5. Schnittstellen (API)</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Anbindung externer Drittsysteme (CRM, ERP, Payment-Provider) zur automatisierten Datensynchronisation und Prozessoptimierung.</PDFText>
|
||||
<PDFText style={styles.pricingTitle}>5. Schnittstellen</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Anbindung externer Systeme (CRM, ERP, Payment) zur Synchronisation.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>ab {pricing.API_INTEGRATION?.toLocaleString('de-DE')} € / Stk</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>6. Inhaltsverwaltung (CMS)</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Bereitstellung und Konfiguration eines Content Management Systems (CMS) inkl. technischer Anbindung aller Module zur unabhängigen Datenpflege.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>{pricing.CMS_SETUP?.toLocaleString('de-DE')} €</PDFText>
|
||||
<PDFText style={styles.pricingTitle}>6. CMS Setup</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Konfiguration Headless CMS zur unabhängigen Datenpflege aller Module.</PDFText>
|
||||
<PDFText style={pricing.CMS_SETUP ? styles.pricingTag : [styles.pricingTag, { color: COLORS.TEXT_LIGHT }]}>{pricing.CMS_SETUP?.toLocaleString('de-DE')} €</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>7. Inszenierung & Interaktion</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Umsetzung komplexer Interaktions-Mechanismen, Konfiguratoren oder aufwendiger visueller Storytelling-Elemente zur Steigerung der Conversion.</PDFText>
|
||||
<PDFText style={styles.pricingTitle}>7. Inszenierung</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Interaktions-Mechanismen, Konfiguratoren oder visuelles Storytelling.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>ab {pricing.VISUAL_STAGING?.toLocaleString('de-DE')} €</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>8. Mehrsprachigkeit</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Skalierung der System-Architektur auf zusätzliche Sprachen. Inklusive struktureller Anpassungen und Logik für Sprachumschaltung.</PDFText>
|
||||
<PDFText style={styles.pricingTitle}>8. Sprachen</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Skalierung der System-Architektur auf zusätzliche Sprachversionen.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>+20% / Sprache</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>9. Inhaltliche Initial-Pflege</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Manuelle Übernahme und Aufbereitung von Datensätzen in das Zielsystem zur Sicherstellung einer initialen Inhaltsabdeckung.</PDFText>
|
||||
<PDFText style={styles.pricingTitle}>9. Initial-Pflege</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Manuelle Aufbereitung & Übernahme von Datensätzen in das Zielsystem.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>{pricing.NEW_DATASET?.toLocaleString('de-DE')} € / Stk</PDFText>
|
||||
</PDFView>
|
||||
<PDFView style={styles.pricingRow}>
|
||||
<PDFText style={styles.pricingTitle}>10. Laufender Betrieb & Hosting</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Laufender Betrieb, Hosting, Sicherheits-Updates sowie monatliche Backups und Monitoring zur Sicherstellung der Systemverfügbarkeit.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>{pricing.HOSTING_MONTHLY?.toLocaleString('de-DE')} € / Monat</PDFText>
|
||||
<PDFText style={styles.pricingTitle}>10. Sorglos-Paket</PDFText>
|
||||
<PDFText style={styles.pricingDesc}>Betrieb, Hosting, Updates & Monitoring gemäß AGB Punkt 7a.</PDFText>
|
||||
<PDFText style={styles.pricingTag}>Inklusive 1 Jahr</PDFText>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
</PDFView>
|
||||
@@ -99,11 +93,11 @@ export const TransparenzModule = ({ pricing }: any) => (
|
||||
export const PrinciplesModule = ({ principles }: any) => (
|
||||
<>
|
||||
<DocumentTitle title="Prinzipien & Standards" />
|
||||
<PDFView style={styles.pricingGrid}>
|
||||
<PDFView style={[styles.pricingGrid, { marginTop: 8 }]}>
|
||||
{principles?.map((item: any, i: number) => (
|
||||
<PDFView key={i} style={styles.pricingRow}>
|
||||
<PDFText style={[styles.pricingTitle, { width: '35%' }]}>{item.t}</PDFText>
|
||||
<PDFText style={[styles.pricingDesc, { width: '65%' }]}>{item.d}</PDFText>
|
||||
<PDFText style={[styles.pricingTitle, { width: '30%' }]}>{item.t}</PDFText>
|
||||
<PDFText style={[styles.pricingDesc, { width: '70%', paddingRight: 0 }]}>{item.d}</PDFText>
|
||||
</PDFView>
|
||||
))}
|
||||
</PDFView>
|
||||
|
||||
@@ -183,8 +183,8 @@ export function calculatePositions(state: FormState, pricing: any): Position[] {
|
||||
const monthlyRate = pricing.HOSTING_MONTHLY + (state.storageExpansion * pricing.STORAGE_EXPANSION_MONTHLY);
|
||||
positions.push({
|
||||
pos: pos++,
|
||||
title: '10. Laufender Betrieb & Hosting',
|
||||
desc: `Bereitstellung der Infrastruktur, technische Instandhaltung, Sicherheits-Updates und Backup-Management gemäß AGB Punkt 7a. Inklusive 12 Monate Service.`,
|
||||
title: '10. Sorglos-Paket (Betrieb & Pflege)',
|
||||
desc: `1 Jahr Sicherung des technischen Betriebs, Instandhaltung, Sicherheits-Updates und Inhalts-Aktualisierungen gemäß AGB Punkt 7a.`,
|
||||
qty: 1,
|
||||
price: monthlyRate * 12
|
||||
});
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
import { FormState } from './types';
|
||||
|
||||
export const PRICING = {
|
||||
BASE_WEBSITE: 6000,
|
||||
PAGE: 800,
|
||||
FEATURE: 2000,
|
||||
FUNCTION: 1000,
|
||||
NEW_DATASET: 400,
|
||||
HOSTING_MONTHLY: 120,
|
||||
BASE_WEBSITE: 4000,
|
||||
PAGE: 600,
|
||||
FEATURE: 1500,
|
||||
FUNCTION: 800,
|
||||
NEW_DATASET: 200,
|
||||
HOSTING_MONTHLY: 250,
|
||||
STORAGE_EXPANSION_MONTHLY: 10,
|
||||
CMS_SETUP: 1500,
|
||||
CMS_CONNECTION_PER_FEATURE: 800,
|
||||
API_INTEGRATION: 1000,
|
||||
API_INTEGRATION: 800,
|
||||
APP_HOURLY: 120,
|
||||
VISUAL_STAGING: 2000,
|
||||
COMPLEX_INTERACTION: 1500,
|
||||
|
||||
@@ -1,68 +1,27 @@
|
||||
{
|
||||
"requestsFinished": 0,
|
||||
"requestsFailed": 1,
|
||||
"requestsRetries": 1,
|
||||
"requestsFailedPerMinute": 279,
|
||||
"requestsFinishedPerMinute": 0,
|
||||
"requestMinDurationMillis": null,
|
||||
"requestMaxDurationMillis": 0,
|
||||
"requestTotalFailedDurationMillis": 2,
|
||||
"requestTotalFinishedDurationMillis": 0,
|
||||
"crawlerStartedAt": "2026-02-04T17:39:09.193Z",
|
||||
"crawlerFinishedAt": "2026-02-04T17:39:09.396Z",
|
||||
"statsPersistedAt": "2026-02-04T17:39:09.396Z",
|
||||
"crawlerRuntimeMillis": 215,
|
||||
"crawlerLastStartTimestamp": 1770226749181,
|
||||
"requestsFinished": 7,
|
||||
"requestsFailed": 0,
|
||||
"requestsRetries": 0,
|
||||
"requestsFailedPerMinute": 0,
|
||||
"requestsFinishedPerMinute": 596,
|
||||
"requestMinDurationMillis": 72,
|
||||
"requestMaxDurationMillis": 503,
|
||||
"requestTotalFailedDurationMillis": 0,
|
||||
"requestTotalFinishedDurationMillis": 1167,
|
||||
"crawlerStartedAt": "2026-02-05T00:44:22.537Z",
|
||||
"crawlerFinishedAt": "2026-02-05T00:44:23.230Z",
|
||||
"statsPersistedAt": "2026-02-05T00:44:23.230Z",
|
||||
"crawlerRuntimeMillis": 705,
|
||||
"crawlerLastStartTimestamp": 1770252262525,
|
||||
"requestRetryHistogram": [
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
1
|
||||
7
|
||||
],
|
||||
"statsId": 0,
|
||||
"requestAvgFailedDurationMillis": 2,
|
||||
"requestAvgFinishedDurationMillis": null,
|
||||
"requestTotalDurationMillis": 2,
|
||||
"requestsTotal": 1,
|
||||
"requestAvgFailedDurationMillis": null,
|
||||
"requestAvgFinishedDurationMillis": 167,
|
||||
"requestTotalDurationMillis": 1167,
|
||||
"requestsTotal": 7,
|
||||
"requestsWithStatusCode": {},
|
||||
"errors": {
|
||||
"file:///Users/marcmintel/Projects/mintel.me/node_modules/got/dist/source/core/index.js:198:21": {
|
||||
"ENOTFOUND": {
|
||||
"RequestError": {
|
||||
"getaddrinfo ENOTFOUND etib-et.com": {
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"node:dns:120:26": {
|
||||
"ENOTFOUND": {
|
||||
"Error": {
|
||||
"getaddrinfo ENOTFOUND etib-et.com": {
|
||||
"count": 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"retryErrors": {
|
||||
"file:///Users/marcmintel/Projects/mintel.me/node_modules/got/dist/source/core/index.js:198:21": {
|
||||
"ENOTFOUND": {
|
||||
"RequestError": {
|
||||
"getaddrinfo ENOTFOUND etib-et.com": {
|
||||
"count": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"node:dns:120:26": {
|
||||
"ENOTFOUND": {
|
||||
"Error": {
|
||||
"getaddrinfo ENOTFOUND etib-et.com": {
|
||||
"count": 3
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"errors": {},
|
||||
"retryErrors": {}
|
||||
}
|
||||
@@ -1,9 +1,9 @@
|
||||
{
|
||||
"usableSessionsCount": 4,
|
||||
"usableSessionsCount": 7,
|
||||
"retiredSessionsCount": 0,
|
||||
"sessions": [
|
||||
{
|
||||
"id": "session_qS9LzMPYLU",
|
||||
"id": "session_MKcXTj8jBY",
|
||||
"cookieJar": {
|
||||
"version": "tough-cookie@6.0.0",
|
||||
"storeType": "MemoryCookieStore",
|
||||
@@ -16,14 +16,14 @@
|
||||
"userData": {},
|
||||
"maxErrorScore": 3,
|
||||
"errorScoreDecrement": 0.5,
|
||||
"expiresAt": "2026-02-04T18:29:09.266Z",
|
||||
"createdAt": "2026-02-04T17:39:09.266Z",
|
||||
"expiresAt": "2026-02-05T01:34:22.574Z",
|
||||
"createdAt": "2026-02-05T00:44:22.574Z",
|
||||
"usageCount": 1,
|
||||
"maxUsageCount": 50,
|
||||
"errorScore": 1
|
||||
"errorScore": 0
|
||||
},
|
||||
{
|
||||
"id": "session_rQXoQ3YoSI",
|
||||
"id": "session_CzBvH8k5d6",
|
||||
"cookieJar": {
|
||||
"version": "tough-cookie@6.0.0",
|
||||
"storeType": "MemoryCookieStore",
|
||||
@@ -36,14 +36,14 @@
|
||||
"userData": {},
|
||||
"maxErrorScore": 3,
|
||||
"errorScoreDecrement": 0.5,
|
||||
"expiresAt": "2026-02-04T18:29:09.369Z",
|
||||
"createdAt": "2026-02-04T17:39:09.369Z",
|
||||
"expiresAt": "2026-02-05T01:34:23.083Z",
|
||||
"createdAt": "2026-02-05T00:44:23.083Z",
|
||||
"usageCount": 1,
|
||||
"maxUsageCount": 50,
|
||||
"errorScore": 1
|
||||
"errorScore": 0
|
||||
},
|
||||
{
|
||||
"id": "session_VQKYMg9QOd",
|
||||
"id": "session_6tYd3j1pzA",
|
||||
"cookieJar": {
|
||||
"version": "tough-cookie@6.0.0",
|
||||
"storeType": "MemoryCookieStore",
|
||||
@@ -56,14 +56,14 @@
|
||||
"userData": {},
|
||||
"maxErrorScore": 3,
|
||||
"errorScoreDecrement": 0.5,
|
||||
"expiresAt": "2026-02-04T18:29:09.376Z",
|
||||
"createdAt": "2026-02-04T17:39:09.376Z",
|
||||
"expiresAt": "2026-02-05T01:34:23.085Z",
|
||||
"createdAt": "2026-02-05T00:44:23.085Z",
|
||||
"usageCount": 1,
|
||||
"maxUsageCount": 50,
|
||||
"errorScore": 1
|
||||
"errorScore": 0
|
||||
},
|
||||
{
|
||||
"id": "session_Jf7MUM5INz",
|
||||
"id": "session_MahMPRKWfS",
|
||||
"cookieJar": {
|
||||
"version": "tough-cookie@6.0.0",
|
||||
"storeType": "MemoryCookieStore",
|
||||
@@ -76,11 +76,71 @@
|
||||
"userData": {},
|
||||
"maxErrorScore": 3,
|
||||
"errorScoreDecrement": 0.5,
|
||||
"expiresAt": "2026-02-04T18:29:09.380Z",
|
||||
"createdAt": "2026-02-04T17:39:09.380Z",
|
||||
"expiresAt": "2026-02-05T01:34:23.086Z",
|
||||
"createdAt": "2026-02-05T00:44:23.086Z",
|
||||
"usageCount": 1,
|
||||
"maxUsageCount": 50,
|
||||
"errorScore": 1
|
||||
"errorScore": 0
|
||||
},
|
||||
{
|
||||
"id": "session_POCFcWGlXP",
|
||||
"cookieJar": {
|
||||
"version": "tough-cookie@6.0.0",
|
||||
"storeType": "MemoryCookieStore",
|
||||
"rejectPublicSuffixes": true,
|
||||
"enableLooseMode": false,
|
||||
"allowSpecialUseDomain": true,
|
||||
"prefixSecurity": "silent",
|
||||
"cookies": []
|
||||
},
|
||||
"userData": {},
|
||||
"maxErrorScore": 3,
|
||||
"errorScoreDecrement": 0.5,
|
||||
"expiresAt": "2026-02-05T01:34:23.087Z",
|
||||
"createdAt": "2026-02-05T00:44:23.087Z",
|
||||
"usageCount": 1,
|
||||
"maxUsageCount": 50,
|
||||
"errorScore": 0
|
||||
},
|
||||
{
|
||||
"id": "session_zra0Syci00",
|
||||
"cookieJar": {
|
||||
"version": "tough-cookie@6.0.0",
|
||||
"storeType": "MemoryCookieStore",
|
||||
"rejectPublicSuffixes": true,
|
||||
"enableLooseMode": false,
|
||||
"allowSpecialUseDomain": true,
|
||||
"prefixSecurity": "silent",
|
||||
"cookies": []
|
||||
},
|
||||
"userData": {},
|
||||
"maxErrorScore": 3,
|
||||
"errorScoreDecrement": 0.5,
|
||||
"expiresAt": "2026-02-05T01:34:23.088Z",
|
||||
"createdAt": "2026-02-05T00:44:23.088Z",
|
||||
"usageCount": 1,
|
||||
"maxUsageCount": 50,
|
||||
"errorScore": 0
|
||||
},
|
||||
{
|
||||
"id": "session_JbCC8UIBHk",
|
||||
"cookieJar": {
|
||||
"version": "tough-cookie@6.0.0",
|
||||
"storeType": "MemoryCookieStore",
|
||||
"rejectPublicSuffixes": true,
|
||||
"enableLooseMode": false,
|
||||
"allowSpecialUseDomain": true,
|
||||
"prefixSecurity": "silent",
|
||||
"cookies": []
|
||||
},
|
||||
"userData": {},
|
||||
"maxErrorScore": 3,
|
||||
"errorScoreDecrement": 0.5,
|
||||
"expiresAt": "2026-02-05T01:34:23.092Z",
|
||||
"createdAt": "2026-02-05T00:44:23.092Z",
|
||||
"usageCount": 1,
|
||||
"maxUsageCount": 50,
|
||||
"errorScore": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user