2026-05-20 21:39:12 +08:00

168 lines
5.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/env node
'use strict';
const fs = require('fs');
function emit(stage, message, status = 'running', extra = {}) {
process.stderr.write(JSON.stringify({
type: 'process_event',
stage,
message,
status,
timestamp: new Date().toISOString(),
...extra,
}) + '\n');
}
function readInput() {
const raw = String(process.argv[2] || process.env.SKILL_INPUT || process.env.AIFLOW_SKILL_INPUT || fs.readFileSync(0, 'utf8')).trim();
if (!raw) return {};
return JSON.parse(raw);
}
function normalizeSide(side) {
const value = String(side || 'front').toLowerCase();
if (['front', 'back'].includes(value)) return value;
if (['正面', '人像面'].includes(value)) return 'front';
if (['反面', '国徽面'].includes(value)) return 'back';
return 'front';
}
function normalizeDate(value) {
const text = String(value || '').trim();
if (!text) return '';
const compact = text.replace(/[.年/]/g, '-').replace(/月/g, '-').replace(/日/g, '').replace(/\s+/g, '');
const match = compact.match(/^(\d{4})-?(\d{1,2})-?(\d{1,2})$/);
if (!match) return text;
const [, y, m, d] = match;
return `${y}-${String(m).padStart(2, '0')}-${String(d).padStart(2, '0')}`;
}
function parseJsonText(value) {
if (!value) return null;
if (typeof value === 'object') return value;
const text = String(value).trim();
if (!text) return null;
try {
return JSON.parse(text);
} catch {
const match = text.match(/\{[\s\S]*\}/);
if (!match) return null;
try {
return JSON.parse(match[0]);
} catch {
return null;
}
}
}
function firstNonEmpty(...values) {
for (const value of values) {
if (value !== undefined && value !== null && value !== '') return value;
}
return '';
}
function readField(source, names) {
if (!source || typeof source !== 'object') return '';
for (const name of names) {
const value = source[name];
if (value === undefined || value === null || value === '') continue;
if (typeof value === 'object') {
return firstNonEmpty(value.words, value.word, value.value, value.text);
}
return String(value).trim();
}
return '';
}
function getCandidate(input) {
return parseJsonText(input.modelResult)
|| parseJsonText(input.ocrResult)
|| parseJsonText(input.mockResult)
|| parseJsonText(input.result)
|| (input.fields ? { fields: input.fields } : null);
}
function normalizeIdCard(candidate, side) {
const root = candidate && typeof candidate === 'object' ? candidate : {};
const fields = root.fields && typeof root.fields === 'object' ? root.fields : root;
const words = root.words_result && typeof root.words_result === 'object' ? root.words_result : fields;
const normalizedFields = {
name: readField(words, ['name', '姓名']),
idNumber: readField(words, ['idNumber', 'idNo', 'cardNumber', '公民身份号码', '身份号码', '身份证号', '证件号码']),
gender: readField(words, ['gender', 'sex', '性别']),
ethnicity: readField(words, ['ethnicity', 'nation', '民族']),
birthDate: normalizeDate(readField(words, ['birthDate', 'birthday', '出生', '出生日期'])),
address: readField(words, ['address', '住址', '地址']),
issueAuthority: readField(words, ['issueAuthority', 'authority', '签发机关', '签发单位']),
validFrom: normalizeDate(readField(words, ['validFrom', 'validStart', '有效期开始', '签发日期'])),
validTo: readField(words, ['validTo', 'validEnd', '有效期结束', '失效日期']) || readField(words, ['有效期限', '有效期']),
};
if (normalizedFields.validTo && normalizedFields.validTo !== '长期') {
normalizedFields.validTo = normalizeDate(normalizedFields.validTo);
}
return {
success: true,
side: root.side || side,
fields: normalizedFields,
quality: root.quality || {},
risk: root.risk || {},
raw: root,
};
}
function buildPrompt(input, side) {
return [
'请使用多模态大模型识别中国居民身份证图片只输出合法JSON不输出解释。',
'无法确认的字段输出空字符串,不要猜测。',
'日期统一 YYYY-MM-DD有效期长期输出“长期”。',
`当前识别面:${side === 'back' ? '反面/国徽面' : '正面/人像面'}`,
'输出schema{"fields":{"name":"","idNumber":"","gender":"","ethnicity":"","birthDate":"","address":"","issueAuthority":"","validFrom":"","validTo":""},"quality":{},"risk":{}}',
input.imageUrl ? `图片URL${input.imageUrl}` : '',
input.imageBase64 ? '已提供 imageBase64请直接看图识别。' : '',
].filter(Boolean).join('\n');
}
async function run(input) {
const side = normalizeSide(input.side);
const candidate = getCandidate(input);
if (candidate) {
emit('normalize', '归一化多模态身份证识别结果', 'completed');
return normalizeIdCard(candidate, side);
}
return {
success: false,
needsModelVision: true,
side,
error: '未提供多模态模型识别结果。请先让多模态模型查看 imageUrl/imageBase64并把模型返回JSON作为 modelResult 传入本skill。',
prompt: buildPrompt(input, side),
fields: {
name: '',
idNumber: '',
gender: '',
ethnicity: '',
birthDate: '',
address: '',
issueAuthority: '',
validFrom: '',
validTo: '',
},
};
}
(async () => {
try {
const input = readInput();
process.stdout.write(JSON.stringify(await run(input)));
} catch (err) {
process.stdout.write(JSON.stringify({
success: false,
error: err && err.message ? err.message : String(err),
}));
}
})();