Improve audit pipeline and outreach review

This commit is contained in:
2026-06-08 22:16:32 +02:00
parent ff18fc202e
commit 1695110e0a
34 changed files with 2792 additions and 238 deletions

View File

@@ -10,6 +10,7 @@ import { Badge } from "@/components/ui/badge";
import { Globe } from "lucide-react";
type UsedSkill = {
id?: string;
name: string;
purpose?: string;
category?: string;
@@ -17,6 +18,12 @@ type UsedSkill = {
version?: string;
};
type SkillSummary = {
name: string;
purpose: string;
summary: string;
};
type LeadContext = {
_id: Id<"leads">;
companyName?: string;
@@ -35,9 +42,35 @@ type SkillAwareAudit = {
createdAt?: number;
updatedAt?: number;
usedSkills?: UsedSkill[];
skillSummaries?: SkillSummary[];
internalSummary?: string | null;
};
type AuditFindingEvidenceRef = {
id: string;
type:
| "crawl_page"
| "technical_check"
| "screenshot"
| "pagespeed"
| "jina_excerpt"
| "generation_stage";
label: string;
sourceUrl?: string;
};
type AuditFinding = {
_id: string;
skillId: string;
claim: string;
recommendation: string;
customerBenefit: string;
severity: 1 | 2 | 3;
confidence: number;
evidenceRefs: AuditFindingEvidenceRef[];
reviewStatus: "pending" | "accepted" | "rejected";
};
type CheckedPageScreenshot = {
id: Id<"_storage">;
url: string;
@@ -69,6 +102,7 @@ type CheckedPageEvidence = {
type AuditDetailResult = {
audit: SkillAwareAudit;
lead: LeadContext | null;
findings: AuditFinding[];
sourceSummaries: {
checkedPages: CheckedPageEvidence[];
};
@@ -121,6 +155,19 @@ function metaSignalText(page: CheckedPageEvidence) {
return "Unbekannt";
}
function evidenceTypeLabel(type: AuditFindingEvidenceRef["type"]) {
const labels: Record<AuditFindingEvidenceRef["type"], string> = {
crawl_page: "Crawl",
technical_check: "Technik",
screenshot: "Screenshot",
pagespeed: "PageSpeed",
jina_excerpt: "Reader",
generation_stage: "KI-Stufe",
};
return labels[type] ?? type;
}
function leadSummary(lead: LeadContext | null | undefined) {
if (!lead) {
return "Kein Lead-Kontext gespeichert";
@@ -156,7 +203,20 @@ export function AuditDetail({ id }: { id: string | Id<"audits"> }) {
const audit = result?.audit;
const lead = result?.lead;
const usedSkills = useMemo(() => audit?.usedSkills ?? [], [audit]);
const usedSkills = useMemo(() => {
const summaries = audit?.skillSummaries ?? [];
return (audit?.usedSkills ?? []).map((skill) => {
const summary = summaries.find(
(candidate) => candidate.name === skill.name || candidate.name === skill.id,
);
return {
...skill,
purpose: summary?.purpose ?? skill.purpose,
summary: summary?.summary,
};
});
}, [audit]);
const findings = useMemo(() => result?.findings ?? [], [result]);
const checkedPageEvidence = useMemo(
() => result?.sourceSummaries.checkedPages ?? [],
[result],
@@ -220,6 +280,56 @@ export function AuditDetail({ id }: { id: string | Id<"audits"> }) {
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Geprüfte Befunde</CardTitle>
<CardDescription>
Verifizierte Aussagen mit konkreten Belegen aus Crawl, Screenshots und Messungen.
</CardDescription>
</CardHeader>
<CardContent>
{findings.length === 0 ? (
<p className="text-sm text-muted-foreground">
Noch keine verifizierten Befunde gespeichert.
</p>
) : (
<ul className="grid gap-3">
{findings.map((finding) => (
<li className="rounded-md border p-3 text-sm" key={finding._id}>
<div className="flex flex-wrap items-start justify-between gap-2">
<div className="min-w-0">
<p className="font-medium">{finding.claim}</p>
<p className="mt-2 text-muted-foreground">
{finding.customerBenefit}
</p>
</div>
<div className="flex flex-wrap gap-1">
<Badge variant={finding.severity === 3 ? "secondary" : "outline"}>
Priorität {finding.severity}
</Badge>
<Badge variant="outline">
{Math.round(finding.confidence * 100)}% sicher
</Badge>
</div>
</div>
<p className="mt-3">
<span className="font-medium">Empfehlung: </span>
{finding.recommendation}
</p>
<div className="mt-3 flex flex-wrap gap-2">
{finding.evidenceRefs.map((ref) => (
<Badge variant="outline" key={ref.id}>
Quelle: {evidenceTypeLabel(ref.type)} · {ref.label}
</Badge>
))}
</div>
</li>
))}
</ul>
)}
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle>Geprüfte Seiten</CardTitle>
@@ -322,6 +432,9 @@ export function AuditDetail({ id }: { id: string | Id<"audits"> }) {
<p className="text-sm text-muted-foreground">
{skill.purpose ?? "Keine Zweckbeschreibung"}
</p>
{"summary" in skill && skill.summary ? (
<p className="text-sm text-muted-foreground">{skill.summary}</p>
) : null}
<p className="mt-1 inline-flex flex-wrap items-center gap-1">
{skill.category ? <Badge variant="outline">{skill.category}</Badge> : null}
{skill.version ? <Badge variant="outline">{skill.version}</Badge> : null}