Files
alla-allaos-fullstack/scripts/audit-pdf-mapping-coverage.ts
phaichayon 51d67ef7c2 task-h.5.3
2026-06-19 17:15:28 +07:00

156 lines
4.8 KiB
TypeScript

import {
PDFME_STATIC_LABEL_FIELD_KEYS,
createSqlClient,
extractTemplateFields,
getActiveTemplateVersions,
getEffectiveMappedKeys,
getMappingsForVersion,
summarizeIssues
} from './pdf-audit-utils.ts';
function classifyToken(token: string, mappedKeys: Set<string>) {
if (token === 'topic' || token === 'data_topic') {
return 'dynamic_topic_field';
}
if (token === 'item_topic') {
return 'dynamic_topic_field';
}
if (token.startsWith('topic_') || token.startsWith('item_topic_')) {
return 'dynamic_runtime_field';
}
if (mappedKeys.has(token)) {
return 'mapped_field';
}
return 'unmapped_field';
}
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, columns } = await getMappingsForVersion(sql, template.versionId);
const mappedKeys = getEffectiveMappedKeys(fields, mappings);
const fieldNames = new Set(fields.map((field) => field.name).filter(Boolean));
const schemaTokens = [...new Set(fields.flatMap((field) => field.tokens))].sort();
const tableFieldCoverage = fields
.filter((field) => field.type === 'table' && field.name)
.map((field) => {
const mapping = mappings.find((candidate) => candidate.placeholderKey === field.name) ?? null;
return {
fieldName: field.name,
hasMapping: Boolean(mapping),
sourcePath: mapping?.sourcePath ?? null,
dataType: mapping?.dataType ?? null
};
});
const coverage = schemaTokens.map((token) => {
const mapping =
mappings.find((candidate) => candidate.placeholderKey === token) ??
mappings.find(
(candidate) =>
fields.some(
(field) =>
field.name === candidate.placeholderKey &&
field.tokens.length === 1 &&
field.tokens[0] === token
)
) ??
null;
return {
token,
classification: classifyToken(token, mappedKeys),
sourcePath: mapping?.sourcePath ?? null,
dataType: mapping?.dataType ?? null
};
});
const unmappedFields = coverage.filter((item) => item.classification === 'unmapped_field');
const invalidTableMappings = tableFieldCoverage.filter(
(item) =>
item.fieldName === 'quotation_price_data' &&
(!item.hasMapping ||
item.sourcePath !== 'pdfme.quotation_price_data' ||
item.dataType !== 'table')
);
const orphanMappings = mappings
.filter(
(mapping) =>
!schemaTokens.includes(mapping.placeholderKey) &&
!fieldNames.has(mapping.placeholderKey) &&
mapping.placeholderKey !== 'items_table'
)
.map((mapping) => ({
placeholderKey: mapping.placeholderKey,
sourcePath: mapping.sourcePath
}));
const missingStaticLabelMappings = [...PDFME_STATIC_LABEL_FIELD_KEYS].filter((fieldName) => {
if (!fieldNames.has(fieldName)) {
return false;
}
return !mappings.some((mapping) => mapping.placeholderKey === fieldName);
});
const tableMapping = mappings.find((mapping) => mapping.placeholderKey === 'items_table') ?? null;
const tableColumns = tableMapping
? columns
.filter((column) => column.mappingId === tableMapping.id)
.map((column) => ({
columnName: column.columnName,
sourceField: column.sourceField
}))
: [];
const status = summarizeIssues([
unmappedFields.length ? 'FAIL' : 'PASS',
invalidTableMappings.length ? 'FAIL' : 'PASS',
missingStaticLabelMappings.length ? 'FAIL' : 'PASS',
orphanMappings.length ? 'WARNING' : 'PASS'
]);
results.push({
templateName: template.templateName,
version: template.version,
organizationId: template.organizationId,
tokenCount: schemaTokens.length,
coverage,
tableFieldCoverage,
unmappedFields,
missingStaticLabelMappings,
orphanMappings,
tableColumns,
status
});
}
const overallStatus = summarizeIssues(
results.map((result) => result.status) as Array<'PASS' | 'WARNING' | 'FAIL'>
);
console.log(
JSON.stringify(
{
audit: 'mapping-coverage',
overallStatus,
results
},
null,
2
)
);
} finally {
await sql.end({ timeout: 5 });
}
}
main().catch((error) => {
console.error(`[audit-pdf-mapping-coverage] ${error.message}`);
process.exit(1);
});