import { writeFile } from 'node:fs/promises'; import fs from 'node:fs'; import path from 'node:path'; import { AUDIT_ARTIFACTS_DIR, AUDIT_QUOTATION_CODE, REPORT_JSON_PATH, REPORT_MARKDOWN_PATH, buildAuditPayload, createSqlClient, extractTemplateFields, getEffectiveMappedKeys, getDuplicateFieldNames, getMappingsForVersion, getUnknownFieldNames, summarizeIssues } from './pdf-audit-utils.ts'; async function main() { const sql = createSqlClient(); try { const payload = await buildAuditPayload(sql); const fields = extractTemplateFields(payload.templateSchema); const { mappings, columns } = await getMappingsForVersion(sql, payload.templateVersionId); const fieldNames = new Set(fields.map((field) => field.name).filter(Boolean)); const placeholderTokens = [...new Set(fields.flatMap((field) => field.tokens))].sort(); const mappedKeys = getEffectiveMappedKeys(fields, mappings); const unmappedTokens = placeholderTokens.filter((token) => { if (token === 'item_topic') { return false; } if (token === 'topic' || token === 'data_topic') { return false; } if (token.startsWith('topic_') || token.startsWith('item_topic_')) { return false; } return !mappedKeys.has(token); }); const orphanMappings = mappings .filter( (mapping) => !placeholderTokens.includes(mapping.placeholderKey) && !fieldNames.has(mapping.placeholderKey) && mapping.placeholderKey !== 'items_table' ) .map((mapping) => mapping.placeholderKey); const duplicateFields = getDuplicateFieldNames(fields); const unknownFields = getUnknownFieldNames(fields); const topicRows = Object.keys(payload.topicInputs).filter((key) => key.startsWith('topic_')).length; const topicItemRows = Object.keys(payload.topicInputs).filter((key) => key.startsWith('item_topic_') ).length; const itemTableMapping = mappings.find((mapping) => mapping.placeholderKey === 'items_table') ?? null; const itemTableColumns = itemTableMapping ? columns.filter((column) => column.mappingId === itemTableMapping.id) : []; const snapshotTemplateInput = payload.approvedSnapshot && typeof payload.approvedSnapshot === 'object' && 'templateInput' in payload.approvedSnapshot ? (payload.approvedSnapshot.templateInput as Record | null) : null; const sharedKeys = snapshotTemplateInput ? Object.keys(payload.templateInput).filter( (key) => !(key.startsWith('topic_') || key.startsWith('item_topic_')) && key in snapshotTemplateInput ) : []; const inconsistentKeys = sharedKeys.filter( (key) => JSON.stringify(payload.templateInput[key]) !== JSON.stringify(snapshotTemplateInput?.[key]) ); const consistencyStatus = snapshotTemplateInput ? inconsistentKeys.length ? 'FAIL' : 'PASS' : 'WARNING'; const overallStatus = summarizeIssues([ unmappedTokens.length ? 'FAIL' : 'PASS', orphanMappings.length ? 'WARNING' : 'PASS', duplicateFields.length ? 'FAIL' : 'PASS', unknownFields.length ? 'WARNING' : 'PASS', consistencyStatus ]); const report = { generatedAt: new Date().toISOString(), overallStatus, quotationCode: payload.quotationCode, expectedFixtureCode: AUDIT_QUOTATION_CODE, template: { templateName: payload.templateName, version: payload.templateVersion, versionId: payload.templateVersionId, fieldCount: fields.length, duplicateFields, unknownFields }, metrics: { topicCount: payload.quotationMetrics.topicCount, itemCount: payload.quotationMetrics.itemCount, partyCount: payload.quotationMetrics.partyCount, dynamicTopicFieldCount: topicRows, dynamicTopicItemFieldCount: topicItemRows, mappingCount: mappings.length }, mappingCoverage: { placeholderTokenCount: placeholderTokens.length, unmappedTokens, orphanMappings, itemTableColumns: itemTableColumns.map((column) => ({ columnName: column.columnName, sourceField: column.sourceField })) }, runtimePayload: { templateInputKeys: Object.keys(payload.templateInput).length, topicInputKeys: Object.keys(payload.topicInputs).length }, consistency: { status: consistencyStatus, approvedSnapshotAvailable: Boolean(snapshotTemplateInput), approvedArtifactReference: payload.approvedArtifactReference, inconsistentKeys }, integritySummary: { available: fs.existsSync(path.resolve(AUDIT_ARTIFACTS_DIR, 'summary.json')), artifactDir: AUDIT_ARTIFACTS_DIR } }; const markdown = [ '# PDF Audit Report', '', `- Generated At: ${report.generatedAt}`, `- Overall Status: ${report.overallStatus}`, `- Quotation Code: ${report.quotationCode}`, `- Template: ${report.template.templateName} v${report.template.version}`, '', '## Metrics', '', `- Topic Count: ${report.metrics.topicCount}`, `- Item Count: ${report.metrics.itemCount}`, `- Project Party Count: ${report.metrics.partyCount}`, `- Mapping Count: ${report.metrics.mappingCount}`, '', '## Mapping Coverage', '', `- Placeholder Tokens: ${report.mappingCoverage.placeholderTokenCount}`, `- Unmapped Tokens: ${report.mappingCoverage.unmappedTokens.length ? report.mappingCoverage.unmappedTokens.join(', ') : 'None'}`, `- Orphan Mappings: ${report.mappingCoverage.orphanMappings.length ? report.mappingCoverage.orphanMappings.join(', ') : 'None'}`, '', '## Visual / Consistency', '', `- Approved Snapshot Available: ${report.consistency.approvedSnapshotAvailable ? 'Yes' : 'No'}`, `- Consistency Status: ${report.consistency.status}`, `- Inconsistent Keys: ${report.consistency.inconsistentKeys.length ? report.consistency.inconsistentKeys.join(', ') : 'None'}`, '', '## Integrity Layer', '', `- Integrity Summary Available: ${report.integritySummary.available ? 'Yes' : 'No'}`, `- Artifact Directory: ${report.integritySummary.artifactDir}` ].join('\n'); await writeFile(REPORT_JSON_PATH, `${JSON.stringify(report, null, 2)}\n`, 'utf8'); await writeFile(REPORT_MARKDOWN_PATH, `${markdown}\n`, 'utf8'); console.log( JSON.stringify( { audit: 'pdf-report', overallStatus, jsonPath: REPORT_JSON_PATH, markdownPath: REPORT_MARKDOWN_PATH, report }, null, 2 ) ); } finally { await sql.end({ timeout: 5 }); } } main().catch((error) => { console.error(`[generate-pdf-audit-report] ${error.message}`); process.exit(1); });