/** * ClauseGuard — Multi-format Report Export Utility * Generates reports in: JSON, CSV, Markdown, Plain Text, HTML * PDF and DOCX use server-side generation via API routes. */ import type { AnalysisResult, Clause, Entity, Contradiction, Obligation, ComplianceReg, Redline } from "./types"; // ── Severity ordering ── const SEV_ORDER: Record = { CRITICAL: 4, HIGH: 3, MEDIUM: 2, LOW: 1 }; function sevSort(a: string, b: string) { return (SEV_ORDER[b] || 0) - (SEV_ORDER[a] || 0); } function timestamp() { return new Date().toISOString().replace(/[:.]/g, "-").slice(0, 19); } function download(content: string | Blob, filename: string, mime: string) { const blob = content instanceof Blob ? content : new Blob([content], { type: mime }); const url = URL.createObjectURL(blob); const a = document.createElement("a"); a.href = url; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); URL.revokeObjectURL(url); } // ═══════════════════════════════════════════════════════════════ // JSON Export // ═══════════════════════════════════════════════════════════════ export function exportJSON(results: AnalysisResult, formatted = true) { const json = formatted ? JSON.stringify(results, null, 2) : JSON.stringify(results); download(json, `clauseguard-report-${timestamp()}.json`, "application/json"); } // ═══════════════════════════════════════════════════════════════ // CSV Export // ═══════════════════════════════════════════════════════════════ function escapeCSV(val: string): string { if (val.includes(",") || val.includes('"') || val.includes("\n")) { return `"${val.replace(/"/g, '""')}"`; } return val; } export function exportCSV(results: AnalysisResult) { const rows: string[] = []; // Header rows.push("Section,Category,Severity,Confidence,Source,Text,Description"); // Clauses for (const clause of results.results) { for (const cat of clause.categories) { rows.push([ "Clause", escapeCSV(cat.name), cat.severity, cat.confidence != null ? String(Math.round(cat.confidence * 100)) + "%" : "pattern", cat.confidence != null ? "ML" : "Pattern", escapeCSV(clause.text.slice(0, 500)), escapeCSV(cat.description || ""), ].join(",")); } } // Entities for (const ent of results.entities) { rows.push([ "Entity", escapeCSV(ent.type), "", ent.score ? String(Math.round(ent.score * 100)) + "%" : "", ent.source || "", escapeCSV(ent.text), "", ].join(",")); } // Contradictions for (const c of results.contradictions) { rows.push([ "Contradiction", escapeCSV(c.type), c.severity, c.confidence ? String(Math.round(c.confidence * 100)) + "%" : "", c.source || "", escapeCSV(c.explanation), "", ].join(",")); } // Obligations for (const o of results.obligations) { rows.push([ "Obligation", escapeCSV(o.type), o.priority != null && o.priority >= 3 ? "HIGH" : o.priority === 2 ? "MEDIUM" : "LOW", "", "", escapeCSV(o.description), escapeCSV(`${o.party} · ${o.deadline}`), ].join(",")); } download(rows.join("\n"), `clauseguard-report-${timestamp()}.csv`, "text/csv"); } // ═══════════════════════════════════════════════════════════════ // Markdown Export // ═══════════════════════════════════════════════════════════════ export function exportMarkdown(results: AnalysisResult) { const lines: string[] = []; const flagged = results.results.filter(r => r.categories.length > 0); const sevCounts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 }; flagged.forEach(r => r.categories.forEach(c => { if (sevCounts[c.severity as keyof typeof sevCounts] !== undefined) sevCounts[c.severity as keyof typeof sevCounts]++; })); lines.push("# 🛡️ ClauseGuard Analysis Report"); lines.push(""); lines.push(`**Generated:** ${new Date().toLocaleString()}`); lines.push(`**Risk Score:** ${results.risk_score}/100 · **Grade:** ${results.grade}`); lines.push(`**Clauses:** ${results.total_clauses} total · ${results.flagged_count} flagged`); lines.push(`**Model:** ${results.model === "ml" || results.model !== "regex" ? "ML Models" : "Pattern Matching"}`); lines.push(""); // Severity breakdown lines.push("## 📊 Risk Breakdown"); lines.push(""); lines.push("| Severity | Count |"); lines.push("|----------|-------|"); lines.push(`| 🔴 Critical | ${sevCounts.CRITICAL} |`); lines.push(`| 🟠 High | ${sevCounts.HIGH} |`); lines.push(`| 🟡 Medium | ${sevCounts.MEDIUM} |`); lines.push(`| 🟢 Low | ${sevCounts.LOW} |`); lines.push(""); // Flagged clauses if (flagged.length > 0) { lines.push("## ⚠️ Flagged Clauses"); lines.push(""); for (const clause of flagged) { const labels = clause.categories.map(c => `**${c.name}** (${c.severity})`).join(", "); lines.push(`### ${labels}`); lines.push(""); lines.push(`> ${clause.text.slice(0, 500)}${clause.text.length > 500 ? "..." : ""}`); lines.push(""); for (const cat of clause.categories) { if (cat.description) lines.push(`- ${cat.description}`); const src = cat.confidence != null ? `ML ${Math.round(cat.confidence * 100)}%` : "Pattern match"; lines.push(`- *Source: ${src}*`); } lines.push(""); } } // Entities if (results.entities.length > 0) { lines.push("## 🏷️ Extracted Entities"); lines.push(""); const grouped: Record = {}; results.entities.forEach(e => { if (!grouped[e.type]) grouped[e.type] = []; if (!grouped[e.type].includes(e.text)) grouped[e.type].push(e.text); }); for (const [type, items] of Object.entries(grouped)) { lines.push(`**${type.replace(/_/g, " ")}:** ${items.join(", ")}`); } lines.push(""); } // Contradictions if (results.contradictions.length > 0) { lines.push("## 🔍 Contradictions & Issues"); lines.push(""); for (const c of results.contradictions) { lines.push(`- **[${c.severity}] ${c.type}:** ${c.explanation}`); } lines.push(""); } // Obligations if (results.obligations.length > 0) { lines.push("## 📋 Obligations"); lines.push(""); lines.push("| Type | Party | Description | Deadline |"); lines.push("|------|-------|-------------|----------|"); for (const o of results.obligations) { lines.push(`| ${o.type} | ${o.party} | ${o.description.slice(0, 100)} | ${o.deadline} |`); } lines.push(""); } // Compliance if (Object.keys(results.compliance).length > 0) { lines.push("## ⚖️ Compliance"); lines.push(""); for (const [name, reg] of Object.entries(results.compliance)) { lines.push(`### ${name} — ${reg.compliance_rate}% (${reg.overall_status})`); lines.push(`*${reg.description}*`); lines.push(""); for (const check of reg.checks) { const icon = check.status === "PASS" ? "✅" : check.status === "MISSING" ? "❌" : "⚠️"; lines.push(`${icon} ${check.description} (${check.severity})`); } lines.push(""); } } // Redlines if (results.redlines && results.redlines.length > 0) { lines.push("## ✏️ Redlining Suggestions"); lines.push(""); for (const rl of results.redlines) { lines.push(`### ${rl.clause_label} (${rl.risk_level})`); lines.push(""); lines.push(`~~${rl.original_text.slice(0, 200)}~~`); lines.push(""); lines.push(`✅ **Suggested:** ${rl.safe_alternative}`); lines.push(`📚 ${rl.legal_basis} · 🛡️ ${rl.consumer_standard}`); lines.push(""); } } lines.push("---"); lines.push("*⚠️ Not legal advice. Generated by ClauseGuard AI.*"); download(lines.join("\n"), `clauseguard-report-${timestamp()}.md`, "text/markdown"); } // ═══════════════════════════════════════════════════════════════ // Plain Text Export // ═══════════════════════════════════════════════════════════════ export function exportText(results: AnalysisResult) { const lines: string[] = []; const flagged = results.results.filter(r => r.categories.length > 0); lines.push("═══════════════════════════════════════════════════════"); lines.push(" CLAUSEGUARD ANALYSIS REPORT"); lines.push("═══════════════════════════════════════════════════════"); lines.push(""); lines.push(`Date: ${new Date().toLocaleString()}`); lines.push(`Risk Score: ${results.risk_score}/100`); lines.push(`Grade: ${results.grade}`); lines.push(`Clauses: ${results.total_clauses} total, ${results.flagged_count} flagged`); lines.push(`Entities: ${results.entities.length}`); lines.push(`Issues: ${results.contradictions.length}`); lines.push(`Obligations: ${results.obligations.length}`); lines.push(""); lines.push("───────────────────────────────────────────────────────"); lines.push(" FLAGGED CLAUSES"); lines.push("───────────────────────────────────────────────────────"); lines.push(""); for (let i = 0; i < flagged.length; i++) { const clause = flagged[i]; const labels = clause.categories.map(c => `[${c.severity}] ${c.name}`).join(", "); lines.push(`${i + 1}. ${labels}`); lines.push(` ${clause.text.slice(0, 300)}${clause.text.length > 300 ? "..." : ""}`); lines.push(""); } if (results.entities.length > 0) { lines.push("───────────────────────────────────────────────────────"); lines.push(" ENTITIES"); lines.push("───────────────────────────────────────────────────────"); lines.push(""); const grouped: Record = {}; results.entities.forEach(e => { if (!grouped[e.type]) grouped[e.type] = []; if (!grouped[e.type].includes(e.text)) grouped[e.type].push(e.text); }); for (const [type, items] of Object.entries(grouped)) { lines.push(` ${type}: ${items.join(", ")}`); } lines.push(""); } if (results.contradictions.length > 0) { lines.push("───────────────────────────────────────────────────────"); lines.push(" CONTRADICTIONS & ISSUES"); lines.push("───────────────────────────────────────────────────────"); lines.push(""); for (const c of results.contradictions) { lines.push(` [${c.severity}] ${c.type}: ${c.explanation}`); } lines.push(""); } if (results.obligations.length > 0) { lines.push("───────────────────────────────────────────────────────"); lines.push(" OBLIGATIONS"); lines.push("───────────────────────────────────────────────────────"); lines.push(""); for (const o of results.obligations) { lines.push(` [${o.type}] ${o.party}: ${o.description} (${o.deadline})`); } lines.push(""); } if (results.redlines && results.redlines.length > 0) { lines.push("───────────────────────────────────────────────────────"); lines.push(" REDLINING SUGGESTIONS"); lines.push("───────────────────────────────────────────────────────"); lines.push(""); for (const rl of results.redlines) { lines.push(` [${rl.risk_level}] ${rl.clause_label}`); lines.push(` ORIGINAL: ${rl.original_text.slice(0, 200)}`); lines.push(` SUGGESTED: ${rl.safe_alternative}`); lines.push(""); } } lines.push("═══════════════════════════════════════════════════════"); lines.push(" NOT LEGAL ADVICE — Generated by ClauseGuard AI"); lines.push("═══════════════════════════════════════════════════════"); download(lines.join("\n"), `clauseguard-report-${timestamp()}.txt`, "text/plain"); } // ═══════════════════════════════════════════════════════════════ // HTML Export (self-contained styled report) // ═══════════════════════════════════════════════════════════════ export function exportHTML(results: AnalysisResult) { const flagged = results.results.filter(r => r.categories.length > 0); const sevCounts = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 }; flagged.forEach(r => r.categories.forEach(c => { if (sevCounts[c.severity as keyof typeof sevCounts] !== undefined) sevCounts[c.severity as keyof typeof sevCounts]++; })); const sevColor: Record = { CRITICAL: "#dc2626", HIGH: "#ea580c", MEDIUM: "#ca8a04", LOW: "#16a34a" }; const clauseHTML = flagged.map(clause => { const tags = clause.categories.map(c => `${c.name} (${c.severity})` ).join(""); return `
${tags}

${clause.text.replace(/

`; }).join("\n"); const entityHTML = (() => { const grouped: Record = {}; results.entities.forEach(e => { if (!grouped[e.type]) grouped[e.type] = []; if (!grouped[e.type].includes(e.text)) grouped[e.type].push(e.text); }); return Object.entries(grouped).map(([type, items]) => `
${type.replace(/_/g, " ")}
${items.map(t => `${t}`).join("")}
` ).join("\n"); })(); const html = ` ClauseGuard Report — ${new Date().toLocaleDateString()}

🛡️ ClauseGuard Analysis Report

${new Date().toLocaleString()} · ${results.model !== "regex" ? "ML Models" : "Pattern Matching"}

RISK SCORE

${results.risk_score}/100

Grade ${results.grade}
${sevCounts.CRITICAL}
Critical
${sevCounts.HIGH}
High
${sevCounts.MEDIUM}
Medium
${sevCounts.LOW}
Low

${results.total_clauses} clauses · ${results.flagged_count} flagged · ${results.entities.length} entities · ${results.obligations.length} obligations

${flagged.length > 0 ? `

⚠️ Flagged Clauses (${flagged.length})

${clauseHTML}` : ""} ${results.entities.length > 0 ? `

🏷️ Entities (${results.entities.length})

${entityHTML}` : ""} ${results.contradictions.length > 0 ? `

🔍 Issues (${results.contradictions.length})

${results.contradictions.map(c => `
${c.type} (${c.severity})

${c.explanation}

`).join("")}` : ""} ${results.obligations.length > 0 ? `

📋 Obligations (${results.obligations.length})

${results.obligations.map(o => ``).join("")}
TypePartyDescriptionDeadline
${o.type}${o.party}${o.description.slice(0, 120)}${o.deadline}
` : ""} ${results.redlines && results.redlines.length > 0 ? `

✏️ Redlining (${results.redlines.length})

${results.redlines.map(rl => `
${rl.clause_label} (${rl.risk_level})
${rl.original_text.slice(0, 200)}
${rl.safe_alternative}

📚 ${rl.legal_basis} · 🛡️ ${rl.consumer_standard}

`).join("")}` : ""}
⚠️ Not legal advice. This report was generated by ClauseGuard AI for informational purposes only. Consult a licensed attorney for legal decisions.
`; download(html, `clauseguard-report-${timestamp()}.html`, "text/html"); } // ═══════════════════════════════════════════════════════════════ // PDF Export (via server-side API route) // ═══════════════════════════════════════════════════════════════ export async function exportPDF(results: AnalysisResult) { try { const res = await fetch("/api/pdf/report", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(results), }); if (!res.ok) throw new Error("PDF generation failed"); const blob = await res.blob(); download(blob, `clauseguard-report-${timestamp()}.pdf`, "application/pdf"); return true; } catch { // Fallback: print HTML version exportHTML(results); return false; } } // ═══════════════════════════════════════════════════════════════ // Export formats manifest (for the UI dropdown) // ═══════════════════════════════════════════════════════════════ export const EXPORT_FORMATS = [ { key: "pdf", label: "PDF Report", icon: "📄", description: "Formatted PDF document", fn: exportPDF }, { key: "html", label: "HTML Report", icon: "🌐", description: "Styled HTML (printable)", fn: exportHTML }, { key: "md", label: "Markdown", icon: "📝", description: "GitHub-flavored markdown", fn: exportMarkdown }, { key: "txt", label: "Plain Text", icon: "📋", description: "Simple text format", fn: exportText }, { key: "csv", label: "CSV Spreadsheet", icon: "📊", description: "For Excel / Google Sheets", fn: exportCSV }, { key: "json", label: "JSON (formatted)", icon: "🔧", description: "Full structured data", fn: (r: AnalysisResult) => exportJSON(r, true) }, { key: "json-raw", label: "JSON (raw)", icon: "⚡", description: "Compact, no whitespace", fn: (r: AnalysisResult) => exportJSON(r, false) }, ] as const;