Files
alla-allaos-fullstack/scripts/audit-placeholder-integrity.ts
phaichayon a1cac84e2b task p-4.5
2026-06-29 16:07:03 +07:00

110 lines
3.6 KiB
TypeScript

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);
});