pdf task-h
This commit is contained in:
194
scripts/generate-pdf-audit-report.ts
Normal file
194
scripts/generate-pdf-audit-report.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
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<string, unknown> | 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);
|
||||
});
|
||||
Reference in New Issue
Block a user