feat(market-intel): implement role-based campaign engine and gritty reporting

- Implementierung der rollenbasierten Campaign-Engine mit operativem Fokus (Grit).
- Integration von Social Proof (Referenzkunden) in die E-Mail-Generierung.
- Erweiterung des Deep Tech Audits um gezielte Wettbewerber-Recherche (Technographic Search).
- Fix des Lösch-Bugs in der Target-Liste und Optimierung des Frontend-States.
- Erweiterung des Markdown-Exports um transparente Proof-Links und Evidenz.
- Aktualisierung der Dokumentation in readme.md und market_intel_backend_plan.md.
This commit is contained in:
2025-12-22 15:54:06 +00:00
parent 6fcd29a11a
commit 461d9d3bbc
9 changed files with 545 additions and 76 deletions

View File

@@ -9,17 +9,26 @@ interface StepOutreachProps {
language: Language;
referenceUrl: string;
onBack: () => void;
knowledgeBase?: string; // New prop for pre-loaded context
}
export const StepOutreach: React.FC<StepOutreachProps> = ({ company, language, referenceUrl, onBack }) => {
const [fileContent, setFileContent] = useState<string>('');
const [fileName, setFileName] = useState<string>('');
export const StepOutreach: React.FC<StepOutreachProps> = ({ company, language, referenceUrl, onBack, knowledgeBase }) => {
const [fileContent, setFileContent] = useState<string>(knowledgeBase || '');
const [fileName, setFileName] = useState<string>(knowledgeBase ? 'Knowledge Base from Strategy Step' : '');
const [isProcessing, setIsProcessing] = useState(false);
const [isTranslating, setIsTranslating] = useState(false);
const [emails, setEmails] = useState<EmailDraft[]>([]);
const [activeTab, setActiveTab] = useState(0);
const [copied, setCopied] = useState(false);
// If knowledgeBase prop changes, update state (useful if it loads late)
React.useEffect(() => {
if (knowledgeBase && !fileContent) {
setFileContent(knowledgeBase);
setFileName('Knowledge Base from Strategy Step');
}
}, [knowledgeBase]);
const handleFileUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (file) {

View File

@@ -46,8 +46,26 @@ export const StepReport: React.FC<StepReportProps> = ({ results, strategy, onRes
const headers = ["Company", "Prio", "Rev/Emp", "Status", ...signalHeaders, "Recommendation"];
const rows = sortedResults.map(r => {
const signalValues = strategy.signals.map(s => r.dynamicAnalysis[s.id]?.value || '-');
return `| ${r.companyName} | ${r.tier} | ${r.revenue} / ${r.employees} | ${r.status} | ${signalValues.join(" | ")} | ${r.recommendation} |`;
const signalValues = strategy.signals.map(s => {
const data = r.dynamicAnalysis[s.id];
if (!data) return '-';
let content = data.value || '-';
// Sanitize content pipes
content = content.replace(/\|/g, '\\|');
if (data.proof) {
// Sanitize proof pipes and newlines
const safeProof = data.proof.replace(/\|/g, '\\|').replace(/(\r\n|\n|\r)/gm, ' ');
content += `<br><sub>*Proof: ${safeProof}*</sub>`;
}
return content;
});
// Helper to sanitize other fields
const safe = (str: string) => (str || '').replace(/\|/g, '\\|').replace(/(\r\n|\n|\r)/gm, ' ');
return `| ${safe(r.companyName)} | ${r.tier} | ${safe(r.revenue)} / ${safe(r.employees)} | ${r.status} | ${signalValues.join(" | ")} | ${safe(r.recommendation)} |`;
});
const content = `

View File

@@ -30,14 +30,18 @@ export const StepReview: React.FC<StepReviewProps> = ({ competitors, categorized
};
const renderCompetitorList = (comps: Competitor[], category: string) => {
if (!comps || comps.length === 0) {
// Filter out competitors that have been removed from the main list
const activeIds = new Set(competitors.map(c => c.id));
const activeComps = comps.filter(c => activeIds.has(c.id));
if (!activeComps || activeComps.length === 0) {
return (
<li className="p-4 text-center text-slate-500 italic bg-white rounded-md border border-slate-100 mb-2 last:mb-0">
Keine {category} Konkurrenten gefunden.
</li>
);
}
return comps.map((comp) => (
return activeComps.map((comp) => (
<li key={comp.id} className="flex items-start justify-between p-4 hover:bg-slate-50 transition-colors group bg-white rounded-md border border-slate-100 mb-2 last:mb-0">
<div className="flex-1">
<div className="flex items-center gap-2">