| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238 |
- /**
- * 审计:`company_info` 里的字段,有多少真的落进了 DMS 的企业两栏目。
- *
- * 为什么要有这个脚本:`company_info`(企查查原始结构)字段很多,而 1888 的列
- * 是照着它建的(47 列里大部分是工商信息),**但我们的同步只写其中一小部分**。
- * 字段一多,靠记忆或靠文档核对必然漏;这个脚本把三边对齐:
- * ① 真实载荷里实际有哪些字段(`company / profile.data / profile.source / honors`)
- * ② DMS 模型里实际有哪些列(**实时** `getModelById`,不用记录值)
- * ③ 我们的代码实际写哪些列(在脚本里显式列出,与 src 对齐)
- *
- * 怎么跑(需要 dev server 起着,代理注入 DMS token):
- * node harness/tools/audit-company-info-coverage.mjs <company-info.json> [dms 代理地址]
- *
- * 载荷怎么来:把 `/api/chat` 的 `result` 事件里 `data.company_info` 对象存成 JSON 文件
- * (整个对象,不含外层 company_info 键)。注意用 node 写文件,别用 shell 里的 curl -d 传中文。
- */
- process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
- import { readFileSync } from 'node:fs';
- const payloadPath = process.argv[2];
- const dmsBase = process.argv[3] || process.env.DMS_BASE || 'https://localhost:8083/dms-api';
- if (!payloadPath) {
- console.error('用法:node audit-company-info-coverage.mjs <company-info.json> [dms 代理地址]');
- process.exit(2);
- }
- globalThis.VITE_DMS_API = dmsBase;
- // ⚠️ 注意取的是 **modelId**(2036/2032),不是 columnId(1888/1886)——
- // 传错会把别的模型拉回来(踩过:1888 当 modelId 用,拉回来一个带 c_milvus_chunk_ids 的知识库模型)
- const {
- DMS_MODEL_ENTERPRISE,
- DMS_MODEL_HONOR,
- PROFILE_FIELD_MAP,
- } = await import('./_company-classify.mjs');
- /* ------------------------------------------------------------------ *
- * 我们的代码实际写入的列(与 src 对齐,改代码时记得同步这里)
- * 来源:src/network/api/dms/company-info-sync.ts 与 classification-sync-utils.ts
- * ------------------------------------------------------------------ */
- const WE_WRITE_1888 = [
- // 工商信息:**直接从代码的映射表派生**,避免这份清单与 src 漂移
- ...Object.values(PROFILE_FIELD_MAP),
- 'c_created_at', // 仅新增时
- // 六个分类角度标签(JSON 数组字符串)
- 'c_tag_basic',
- 'c_tag_honor',
- 'c_tag_sector',
- 'c_tag_operation',
- 'c_tag_industry',
- 'c_tag_license',
- ];
- const WE_WRITE_1886 = [
- 'c_id', // 只传 0(must 字段,DMS 自动填)
- 'c_credit_code',
- 'c_name',
- 'c_honor', // 荣誉名(并写系统字段 title)
- 'c_level',
- 'c_source',
- 'c_publish_office',
- 'c_publish_date',
- 'c_beging_date',
- 'c_dead_line',
- 'c_certificate_code',
- 'c_created_at',
- ];
- /** company_info 字段 → 1888 列(语义对应;null = DMS 没有这一列) */
- const MAP_PROFILE_DATA = {
- Name: 'c_name',
- CreditCode: 'c_credit_code',
- OperName: 'c_oper_name',
- BelongOrg: 'c_belong_org',
- OperId: 'c_oper_id',
- EntType: 'c_ent_type',
- EconKind: 'c_econ_kind',
- Status: 'c_status',
- Province: 'c_province',
- AreaCode: 'c_area_code',
- Address: 'c_address',
- StartDate: 'c_start_date',
- EndDate: 'c_end_date',
- TermStart: 'c_term_start',
- TermEnd: 'c_term_end',
- CheckDate: 'c_check_date',
- UpdatedDate: 'c_updated_date',
- RegistCapi: 'c_regist_capi',
- RegisteredCapital: 'c_registered_capital',
- RegisteredCapitalUnit: 'c_registered_capital_unit',
- RegisteredCapitalCCY: 'c_registered_capital_ccy',
- RecCap: 'c_rec_cap',
- PaidUpCapital: 'c_paid_up_capital',
- PaidUpCapitalUnit: 'c_paid_up_capital_unit',
- PaidUpCapitalCCY: 'c_paid_up_capital_ccy',
- IsOnStock: 'c_is_on_stock',
- StockNumber: 'c_stock_number',
- StockType: 'c_stock_type',
- OrgNo: 'c_org_no',
- No: 'c_no',
- ImageUrl: 'c_image_url',
- Scope: 'c_scope',
- DesignatedRepresentativeList: 'c_designated_representative_list',
- OriginalName: 'c_original_name',
- RevokeInfo: 'c_revoke_info',
- Area: 'c_area',
- };
- const MAP_COMPANY = {
- KeyNo: null,
- Name: 'c_name',
- CreditCode: 'c_credit_code',
- StartDate: 'c_start_date',
- OperName: 'c_oper_name',
- Status: 'c_status',
- No: 'c_no',
- Address: 'c_address',
- };
- const MAP_HONOR_RECORD = {
- Name: 'c_honor(并写系统 title)',
- Level: 'c_level',
- Source: 'c_source',
- PublishOffice: 'c_publish_office',
- PublishDate: 'c_publish_date',
- BegingDate: 'c_beging_date',
- DeadLine: 'c_dead_line',
- CertificateCode: 'c_certificate_code',
- };
- /* ------------------------------------------------------------------ *
- * 读输入
- * ------------------------------------------------------------------ */
- const payload = JSON.parse(readFileSync(payloadPath, 'utf8'));
- const fetchModelFields = async (modelId, expectAlias) => {
- const form = new URLSearchParams({ modelId: String(modelId) }).toString();
- const res = await fetch(`${dmsBase}/model/getModelById`, {
- method: 'POST',
- headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
- body: form,
- });
- const json = await res.json().catch(() => null);
- if (!json || json.code !== 200) {
- throw new Error(`取模型 ${modelId} 失败:${json ? `${json.code} ${json.message}` : res.status}`);
- }
- // 防呆:modelId 传错时 DMS 不会报错、只会返回另一个模型,所以校验别名
- if (json.content.modelAlias !== expectAlias) {
- throw new Error(
- `模型 ${modelId} 的别名是「${json.content.modelAlias}」,期望「${expectAlias}」——modelId 传错了吧?`
- );
- }
- const list = JSON.parse(json.content.fieldList);
- return Object.values(list)
- .filter((f) => f.name) // 个别条目 name 为空
- .sort((a, b) => a.sequence - b.sequence);
- };
- const fields1888 = await fetchModelFields(DMS_MODEL_ENTERPRISE, '企业基础信息');
- const fields1886 = await fetchModelFields(DMS_MODEL_HONOR, '企业荣誉信息');
- const cols1888 = fields1888.map((f) => f.name);
- const cols1886 = fields1886.map((f) => f.name);
- const out = [];
- const line = (s = '') => out.push(s);
- line(`载荷:${payloadPath}`);
- line(`DMS 模型:1888 企业基础信息 ${cols1888.length} 字段 / 1886 企业荣誉信息 ${cols1886.length} 字段`);
- line();
- line('===== 载荷结构 =====');
- line(`顶层键:${Object.keys(payload).join(', ')}`);
- line(`company:${Object.keys(payload.company || {}).join(', ')}`);
- const profileData = payload.profile?.data || {};
- line(`profile.data(${Object.keys(profileData).length}):${Object.keys(profileData).join(', ')}`);
- line(`profile.source:${Object.keys(payload.profile?.source || {}).join(', ')}`);
- const honorRecordData = payload.honors?.records?.[0]?.data || {};
- line(`honors:${Object.keys(payload.honors || {}).join(', ')}`);
- line(`honors.records[].data:${Object.keys(honorRecordData).join(', ')}`);
- line(`honors.sources[]:${Object.keys(payload.honors?.sources?.[0] || {}).join(', ')}`);
- line();
- line('===== ① profile.data → 1888:已写 / 未写 =====');
- const wrote = [];
- const notWrote = [];
- const noColumn = [];
- for (const key of Object.keys(profileData)) {
- const col = MAP_PROFILE_DATA[key];
- if (col === undefined) noColumn.push(key);
- else if (col === null) noColumn.push(key);
- else if (WE_WRITE_1888.includes(col)) wrote.push([key, col]);
- else notWrote.push([key, col]);
- }
- line(`【已写入 DMS】${wrote.length} 个:`);
- for (const [k, c] of wrote) line(` profile.data.${k.padEnd(30)} → ${c}`);
- line(`【未写入(DMS 里已有现成的列,只是同步没写)】${notWrote.length} 个:`);
- for (const [k, c] of notWrote) line(` profile.data.${k.padEnd(30)} → ${c}`);
- line(`【DMS 没有对应列】${noColumn.length} 个:${noColumn.join(', ') || '(无)'}`);
- line();
- line('===== ② company 段 =====');
- for (const key of Object.keys(payload.company || {})) {
- const col = MAP_COMPANY[key];
- const state = col === null || col === undefined ? '**DMS 无对应列**' : WE_WRITE_1888.includes(col) ? `已写(${col},走 profile 同名字段)` : `未写(${col})`;
- line(` company.${key.padEnd(16)} → ${state}`);
- }
- line();
- line('===== ③ honors → 1886 =====');
- line('honors.records[].data:');
- for (const [k, c] of Object.entries(MAP_HONOR_RECORD)) {
- const ok = c.startsWith('c_honor') ? WE_WRITE_1886.includes('c_honor') : WE_WRITE_1886.includes(c);
- line(` ${k.padEnd(16)} → ${c} ${ok ? '【已写】' : '【未写】'}`);
- }
- line(` honors.complete / error、honors.sources[](${Object.keys(payload.honors?.sources?.[0] || {}).join('/')}):**不写**(只用于判断能否删除)`);
- line();
- line('===== ④ profile.source =====');
- line(` ${Object.keys(payload.profile?.source || {}).join(', ')}`);
- line(' → **全部不写**:1888 没有对应列,属溯源元信息(荣誉那边的 c_source 是"来源通知名",不是这个)');
- line();
- line('===== ⑤ 汇总:1888 的 47 列里,我们在写哪些 =====');
- const unused1888 = cols1888.filter((c) => !WE_WRITE_1888.includes(c));
- line(` 在写 ${WE_WRITE_1888.length} 列`);
- line(` 没写 ${unused1888.length} 列:${unused1888.join(', ')}`);
- line();
- line('===== ⑥ 汇总:1886 的 13 列 =====');
- const unused1886 = cols1886.filter((c) => !WE_WRITE_1886.includes(c));
- line(` 在写 ${WE_WRITE_1886.length} 列`);
- line(` 没写 ${unused1886.length} 列:${unused1886.join(', ') || '(无)'}`);
- const report = out.join('\n');
- console.log(report);
- if (process.env.WRITE_REPORT) {
- const { writeFileSync } = await import('node:fs');
- writeFileSync(process.env.WRITE_REPORT, report, 'utf8');
- console.log(`\n(已写入 ${process.env.WRITE_REPORT})`);
- }
|