import { createSqlClient, extractTemplateFields, getActiveTemplateVersions, getEffectiveMappedKeys, getMappingsForVersion, isDesignerLabelField, summarizeIssues, writeAuditArtifact } from './pdf-audit-utils.ts'; const TYPO_PATTERNS = [/lable/i, /affter/i, /__+/, /\s{2,}/]; const RESERVED_DYNAMIC_FIELD_NAMES = new Set(['topic', 'data_topic']); function detectTypo(fieldName: string) { if (fieldName.startsWith('__section_role__')) { return false; } return TYPO_PATTERNS.some((pattern) => pattern.test(fieldName)); } async function main() { const sql = createSqlClient(); try { const templates = await getActiveTemplateVersions(sql); const results = []; for (const template of templates) { const fields = extractTemplateFields(template.schemaJson); const { mappings } = await getMappingsForVersion(sql, template.versionId); const fieldNames = new Set(fields.map((field) => field.name).filter(Boolean)); const mappedKeys = getEffectiveMappedKeys(fields, mappings); const tokens = [...new Set(fields.flatMap((field) => field.tokens))].sort(); const duplicateFieldNames = [...fieldNames].filter( (fieldName) => fields.filter((field) => field.name === fieldName).length > 1 ); const typoFields = fields .filter((field) => field.name && detectTypo(field.name) && !isDesignerLabelField(field)) .map((field) => field.name); const unmappedPlaceholders = tokens.filter( (token) => token !== 'item_topic' && !token.startsWith('topic_') && !token.startsWith('item_topic_') && !mappedKeys.has(token) ); const deadMappings = mappings .filter( (mapping) => !fieldNames.has(mapping.placeholderKey) && !tokens.includes(mapping.placeholderKey) && mapping.placeholderKey !== 'items_table' ) .map((mapping) => mapping.placeholderKey); const invalidReservedMappings = mappings .filter( (mapping) => mapping.placeholderKey.startsWith('topic_') || mapping.placeholderKey.startsWith('item_topic_') ) .map((mapping) => mapping.placeholderKey); const missingReservedFields = [...RESERVED_DYNAMIC_FIELD_NAMES].filter( (name) => !fieldNames.has(name) ); const status = summarizeIssues([ duplicateFieldNames.length ? 'FAIL' : 'PASS', typoFields.length ? 'FAIL' : 'PASS', unmappedPlaceholders.length ? 'FAIL' : 'PASS', deadMappings.length ? 'FAIL' : 'PASS', invalidReservedMappings.length ? 'FAIL' : 'PASS', missingReservedFields.length ? 'FAIL' : 'PASS' ]); results.push({ templateName: template.templateName, organizationId: template.organizationId, version: template.version, duplicateFieldNames, typoFields, unmappedPlaceholders, deadMappings, invalidReservedMappings, missingReservedFields, status }); } const overallStatus = summarizeIssues( results.map((result) => result.status) as Array<'PASS' | 'WARNING' | 'FAIL'> ); const report = { audit: 'placeholder-integrity', overallStatus, results }; const artifactPath = await writeAuditArtifact('placeholder-integrity-report.json', report); console.log(JSON.stringify({ ...report, artifactPath }, null, 2)); } finally { await sql.end({ timeout: 5 }); } } main().catch((error) => { console.error(`[audit-placeholder-integrity] ${error.message}`); process.exit(1); });