/home/techb158/balavpn.abdallabala.com/src/services
NameSizeModeActions
audit-service.js6590644editdlrm
auth-service.js47790644editdlrm
dashboard-service.js11090644editdlrm
gate-service.js45120644editdlrm
integration-service.js165950644editdlrm
project-service.js59730644editdlrm
reporting-service.js86440644editdlrm
risk-service.js125050644editdlrm
sso-service.js31670644editdlrm
Edit: /home/techb158/balavpn.abdallabala.com/src/services/reporting-service.js (8644B)
const { prisma } = require("../lib/prisma"); const { getProjectDashboard } = require("./dashboard-service"); function csvEscape(value) { const text = value === null || value === undefined ? "" : String(value); return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text; } function toCsv(headers, rows) { return [headers.join(","), ...rows.map(row => headers.map(header => csvEscape(row[header])).join(","))].join("\n"); } function riskScoreColor(score) { if (score >= 15) return "#dc2626"; if (score >= 10) return "#f59e0b"; if (score >= 5) return "#eab308"; return "#16a34a"; } async function getFullExportCsv(projectId) { const project = await prisma.project.findUnique({ where: { id: projectId }, include: { risks: { include: { mitigations: { orderBy: { sequence: "asc" } } }, orderBy: { createdAt: "asc" } }, lifecyclePhases: { orderBy: { sequence: "asc" } }, indicators: true } }); if (!project) { const error = new Error(`Project not found: ${projectId}`); error.status = 404; throw error; } const riskHeaders = ["riskId", "title", "dimension", "status", "probability", "impact", "normalizedScore", "residualScore", "createdAt"]; const riskRows = project.risks.map(r => ({ riskId: r.id, title: r.title, dimension: r.dimension, status: r.status, probability: r.probability, impact: r.impact, normalizedScore: r.normalizedScore ?? "", residualScore: r.residualScore ?? "", createdAt: r.createdAt?.toISOString?.() ?? r.createdAt })); const mitigationRows = []; for (const risk of project.risks) { for (const m of risk.mitigations) { mitigationRows.push({ riskId: risk.id, riskTitle: risk.title, mitigationId: m.id, title: m.title, type: m.type, status: m.status, sequence: m.sequence, createdAt: m.createdAt?.toISOString?.() ?? m.createdAt }); } } const mitHeaders = ["riskId", "riskTitle", "mitigationId", "title", "type", "status", "sequence", "createdAt"]; let csv = "=== RISKS ===\n"; csv += toCsv(riskHeaders, riskRows) + "\n\n"; csv += "=== MITIGATIONS ===\n"; csv += toCsv(mitHeaders, mitigationRows) + "\n\n"; if (project.lifecyclePhases.length > 0) { const phaseHeaders = ["phaseId", "name", "status", "sequence"]; const phaseRows = project.lifecyclePhases.map(p => ({ phaseId: p.id, name: p.name, status: p.gateStatus ?? "", sequence: p.sequence })); csv += "=== PHASES ===\n"; csv += toCsv(phaseHeaders, phaseRows) + "\n\n"; } if (project.indicators.length > 0) { const indHeaders = ["indicatorId", "name", "category", "value", "target", "trend"]; const indRows = project.indicators.map(i => ({ indicatorId: i.id, name: i.name, category: i.category ?? "", value: i.value ?? "", target: i.target ?? "", trend: i.trend ?? "" })); csv += "=== INDICATORS ===\n"; csv += toCsv(indHeaders, indRows) + "\n\n"; } return { contentType: "text/csv; charset=utf-8", body: csv }; } function buildExecutiveHtml(dashboard) { const { project, summary, scoredRisks, indicators, lifecyclePhases, dimensionalScores } = dashboard; const score = summary.overallScore; const color = riskScoreColor(score); const riskRows = scoredRisks.map(r => { const rc = riskScoreColor(r.normalizedScore ?? 0); return ` ${csvEscape(r.title)} ${r.dimension ?? ""} ${r.status ?? ""} ${r.normalizedScore ?? ""} ${r.residualScore ?? ""} `; }).join("\n"); const dimRows = (dimensionalScores ? Object.entries(dimensionalScores) : []).map(([dim, dimData]) => { const ds = typeof dimData === "object" ? (dimData.score ?? dimData) : dimData; const dc = riskScoreColor(ds); return `${csvEscape(dim)}${ds}`; }).join("\n"); const phaseRows = (lifecyclePhases || []).map(p => `${csvEscape(p.name)}${p.gateStatus ?? ""}${p.sequence ?? ""}` ).join("\n"); const indRows = (indicators || []).map(i => `${csvEscape(i.name)}${i.category ?? ""}${i.value ?? ""}${i.target ?? ""}${i.trend ?? ""}` ).join("\n"); const barWidth = Math.min(score * 5, 100); return ` Executive Report - ${csvEscape(project.name)}

${csvEscape(project.name)} — Executive Report

Generated: ${new Date().toISOString()} | ID: ${project.id}

Overall AI Risk Score: ${score}
MetricValue
Gate Status${summary.gateStatus ?? "N/A"}
Open Risks${summary.openRisks ?? 0}
Mitigated Risks${summary.mitigatedRisks ?? 0}
Total Risks${(scoredRisks || []).length}
${dimRows ? `

Dimensional Scores

${dimRows}
DimensionScore
` : ""}

Risk Register

${riskRows}
TitleDimensionStatusScoreResidual
${phaseRows ? `

Lifecycle Phases

${phaseRows}
PhaseStatusSequence
` : ""} ${indRows ? `

Indicators

${indRows}
NameCategoryValueTargetTrend
` : ""} `; } async function getReport(projectId, reportType) { if (reportType === "full-export.csv") { return getFullExportCsv(projectId); } const dashboard = await getProjectDashboard(projectId); if (reportType === "risk-register.csv") { return { contentType: "text/csv; charset=utf-8", body: toCsv(["id", "title", "dimension", "status", "normalizedScore", "residualScore"], dashboard.scoredRisks) }; } if (reportType === "executive.html") { return { contentType: "text/html; charset=utf-8", body: buildExecutiveHtml(dashboard) }; } if (reportType === "executive.json") { return { contentType: "application/json; charset=utf-8", body: JSON.stringify({ project: { id: dashboard.project.id, name: dashboard.project.name }, summary: dashboard.summary, dimensionalScores: dashboard.dimensionalScores, scoredRisks: dashboard.scoredRisks.map(r => ({ id: r.id, title: r.title, dimension: r.dimension, status: r.status, normalizedScore: r.normalizedScore, residualScore: r.residualScore })), generatedAt: new Date().toISOString() }, null, 2) }; } return { contentType: "application/json; charset=utf-8", body: JSON.stringify({ reportType, dashboard }, null, 2) }; } module.exports = { getReport, buildExecutiveHtml };