/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ /** * IDS Export Service * * Pure functions that generate downloadable JSON and HTML reports * from IDS validation results. No React dependencies. */ import type { IDSValidationReport, SupportedLocale } from '@ifc-lite/ids'; import { posthog } from '../../lib/analytics'; // ============================================================================ // JSON Export // ============================================================================ /** * Generate a JSON export object from a validation report. * Returns a plain object suitable for JSON.stringify. */ export function buildReportJSON(report: IDSValidationReport): Record { return { document: report.document, modelInfo: report.modelInfo, timestamp: report.timestamp.toISOString(), summary: report.summary, specificationResults: report.specificationResults.map(spec => ({ specification: spec.specification, status: spec.status, applicableCount: spec.applicableCount, passedCount: spec.passedCount, failedCount: spec.failedCount, passRate: spec.passRate, entityResults: spec.entityResults.map(entity => ({ expressId: entity.expressId, modelId: entity.modelId, entityType: entity.entityType, entityName: entity.entityName, globalId: entity.globalId, passed: entity.passed, requirementResults: entity.requirementResults.map(req => ({ requirement: req.requirement, status: req.status, facetType: req.facetType, checkedDescription: req.checkedDescription, failureReason: req.failureReason, actualValue: req.actualValue, expectedValue: req.expectedValue, })), })), })), }; } /** * Trigger a JSON report download in the browser. */ export function downloadReportJSON(report: IDSValidationReport): void { const exportData = buildReportJSON(report); const blob = new Blob([JSON.stringify(exportData, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = globalThis.document.createElement('a'); a.href = url; a.download = `ids-report-${new Date().toISOString().split('T')[0]}.json`; a.click(); URL.revokeObjectURL(url); posthog.capture('ids_report_exported', { format: 'json', total_specifications: report.summary.totalSpecifications }); } // ============================================================================ // HTML Export // ============================================================================ /** HTML escape helper to prevent XSS */ function escapeHtml(str: string | undefined | null): string { if (str == null) return ''; return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, '''); } /** Build entity rows HTML for a specification table */ function buildEntityRows( spec: IDSValidationReport['specificationResults'][0], esc: typeof escapeHtml, ): string { return spec.entityResults.map(entity => { const failedReqs = entity.requirementResults.filter(r => r.status === 'fail'); const passedReqs = entity.requirementResults.filter(r => r.status === 'pass'); const allReqs = entity.requirementResults.filter(r => r.status !== 'not_applicable'); const reqDetails = failedReqs.length > 0 ? failedReqs.map(req => `
${esc(req.facetType)} ${esc(req.checkedDescription)} ${req.failureReason ? `
${esc(req.failureReason)}
` : ''} ${req.expectedValue || req.actualValue ? `
${req.expectedValue ? `Expected: ${esc(req.expectedValue)}` : ''}${req.actualValue ? `Actual: ${esc(req.actualValue)}` : ''}
` : ''}
`).join('') : 'All requirements passed'; return ` ${entity.passed ? 'PASS' : 'FAIL'} ${esc(entity.entityType)} ${esc(entity.entityName) || 'unnamed'} ${esc(entity.globalId) || '\u2014'} ${entity.expressId} ${passedReqs.length}/${allReqs.length}
${failedReqs.length > 0 ? `${failedReqs.length} failure${failedReqs.length > 1 ? 's' : ''}` : 'Details'}
${reqDetails}
`; }).join(''); } /** * Generate an interactive HTML report with search, filtering, sorting, * and click-to-copy GlobalId support. */ export function buildReportHTML(report: IDSValidationReport, locale: SupportedLocale): string { const esc = escapeHtml; const totalChecks = report.summary.totalEntitiesChecked; const totalPassed = report.specificationResults.reduce((s, sp) => s + sp.passedCount, 0); const totalFailed = report.specificationResults.reduce((s, sp) => s + sp.failedCount, 0); const overallPassRate = totalChecks > 0 ? Math.round((totalPassed / totalChecks) * 100) : 0; return ` IDS Validation Report - ${esc(report.document.info.title)}

${esc(report.document.info.title)}

${report.document.info.description ? `

${esc(report.document.info.description)}

` : ''}
${report.document.info.author ? `Author: ${esc(report.document.info.author)}` : ''} Generated: ${esc(report.timestamp.toLocaleString())} Schema: ${esc(report.modelInfo.schemaVersion)}

Summary

${overallPassRate}% of entity checks passed
${report.summary.totalSpecifications}
Specifications
${report.summary.passedSpecifications}
Specs Passed
${report.summary.failedSpecifications}
Specs Failed
${totalChecks}
Entities Checked
${totalPassed}
Passed
${totalFailed}
Failed

Specifications

${report.specificationResults.map((spec, i) => `

${spec.status.toUpperCase()} ${esc(spec.specification.name)}

${spec.specification.description ? `
${esc(spec.specification.description)}
` : ''}
${spec.applicableCount} applicable ${spec.passedCount} passed ${spec.failedCount} failed ${spec.passRate}% pass rate
${buildEntityRows(spec, esc)}
Status ▴▾ IFC Class ▴▾ Name ▴▾ GlobalId ▴▾ ID ▴▾ Reqs Details
`).join('')}
`; } /** * Trigger an HTML report download in the browser. */ export function downloadReportHTML(report: IDSValidationReport, locale: SupportedLocale): void { const html = buildReportHTML(report, locale); const blob = new Blob([html], { type: 'text/html' }); const url = URL.createObjectURL(blob); const a = globalThis.document.createElement('a'); a.href = url; a.download = `ids-report-${new Date().toISOString().split('T')[0]}.html`; a.click(); URL.revokeObjectURL(url); posthog.capture('ids_report_exported', { format: 'html', locale, total_specifications: report.summary.totalSpecifications }); }