import fs from 'node:fs';
|
import path from 'node:path';
|
import crypto from 'node:crypto';
|
|
const ROOT = process.cwd();
|
const CASE_ROOT = path.join(ROOT, 'ana-data', 'cases', '机器人案例', 'ANA-ROBOT-INDUSTRY-001');
|
const AS_OF = '2026-08-06';
|
const BOUNDARY = 'OPEN_RETAINED_GAP_PARTIALLY_NARROWED';
|
|
const PATHS = {
|
v4: path.join(CASE_ROOT, 'outputs', '数据表', 'robot_company_information_v04_20260806.csv'),
|
gapRegister: path.join(CASE_ROOT, 'evidence', 'next_robot_075_high_value_gap_register_20260805.csv'),
|
targetPriority: path.join(CASE_ROOT, 'evidence', 'next_robot_077_p0_target_priority_20260806.csv'),
|
sourceBacktrace: path.join(CASE_ROOT, 'evidence', 'next_robot_077_primary_source_backtrace_20260806.csv'),
|
gapEffect: path.join(CASE_ROOT, 'evidence', 'next_robot_077_gap_effect_register_20260806.csv'),
|
v5: path.join(CASE_ROOT, 'outputs', '数据表', 'robot_company_information_v05_20260806.csv'),
|
summary: path.join(CASE_ROOT, 'outputs', '核心文档', '机器人企业高价值缺口集中回源_第三轮_20260806.md'),
|
validation: path.join(CASE_ROOT, 'manifest', 'next_robot_077_high_value_gap_content_sprint_validation_20260806.csv')
|
};
|
|
function readText(filePath) {
|
return fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '');
|
}
|
|
function parseCsv(text) {
|
const records = [];
|
let record = [];
|
let value = '';
|
let quoted = false;
|
for (let index = 0; index < text.length; index += 1) {
|
const char = text[index];
|
if (quoted) {
|
if (char === '"') {
|
if (text[index + 1] === '"') {
|
value += '"';
|
index += 1;
|
} else quoted = false;
|
} else value += char;
|
} else if (char === '"') quoted = true;
|
else if (char === ',') {
|
record.push(value);
|
value = '';
|
} else if (char === '\n') {
|
record.push(value.replace(/\r$/, ''));
|
records.push(record);
|
record = [];
|
value = '';
|
} else value += char;
|
}
|
if (value.length || record.length) {
|
record.push(value.replace(/\r$/, ''));
|
records.push(record);
|
}
|
if (!records.length) return [];
|
const columns = records[0];
|
return records.slice(1).filter((row) => row.some(Boolean)).map((row) =>
|
Object.fromEntries(columns.map((column, index) => [column, row[index] || '']))
|
);
|
}
|
|
function readCsv(filePath) {
|
return parseCsv(readText(filePath));
|
}
|
|
function csvCell(value) {
|
return `"${String(value ?? '').replace(/"/g, '""')}"`;
|
}
|
|
function writeCsv(filePath, rows, columns) {
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
const lines = [columns.map(csvCell).join(',')];
|
for (const row of rows) lines.push(columns.map((column) => csvCell(row[column])).join(','));
|
fs.writeFileSync(filePath, `${lines.join('\n')}\n`, 'utf8');
|
}
|
|
function writeText(filePath, text) {
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
fs.writeFileSync(filePath, text.replace(/\r\n/g, '\n').replace(/\r/g, '\n'), 'utf8');
|
}
|
|
function shaFile(filePath) {
|
return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
}
|
|
function normalize(value) {
|
return String(value || '').replace(/\s+/g, ' ').trim();
|
}
|
|
function unique(values) {
|
return [...new Set(values.filter(Boolean))];
|
}
|
|
function md(value) {
|
return normalize(value).replace(/\|/g, '/');
|
}
|
|
const companies = readCsv(PATHS.v4);
|
const gaps = readCsv(PATHS.gapRegister);
|
const companyByName = new Map(companies.map((row) => [row.canonical_name, row]));
|
const companyById = new Map(companies.map((row) => [row.company_id, row]));
|
const v4IdentityBefore = { bytes: fs.statSync(PATHS.v4).size, sha256: shaFile(PATHS.v4) };
|
|
function companyId(name) {
|
const row = companyByName.get(name);
|
if (!row) throw new Error(`UNRESOLVED_COMPANY:${name}`);
|
return row.company_id;
|
}
|
|
const targetNames = ['云深处科技', '魔法原子', '银河通用', '擎朗智能', '艾利特机器人', '微亿智造'];
|
|
const sourceBacktrace = [
|
{
|
source_update_id: 'N077-SRC-001', canonical_name: '云深处科技', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'CUSTOMER',
|
source_class: 'REGULATOR_IPO_PROSPECTUS_DRAFT', publisher: '云深处科技 / 上交所申报材料', source_title: '首次公开发行股票并在科创板上市招股说明书(申报稿)', publication_date: '2026-05-18', canonical_url: 'https://dataclouds.cninfo.com.cn/sjother2/documents/2026/2026-05-18/3ef52e260c1cc31a8c57ab5e0abf2c14.pdf', source_locator: 'P4 L80-L82; P130 L4325-L4355',
|
content_update_text: '申报稿披露公司行业级应用客户超过500家、产品进入45个以上国家或地区;2025年前五大客户销售收入合计6354.86万元、占营业收入18.83%,其中具名客户包括四川具身人形机器人科技、闹奇机器人和Inmotion Robotic。',
|
limitation: '招股书仍为申报稿;部分客户以A/C代称,前五大客户销售额不等于在手订单、独供关系或未来收入。'
|
},
|
{
|
source_update_id: 'N077-SRC-002', canonical_name: '云深处科技', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'MASS_PRODUCTION|CAPACITY',
|
source_class: 'REGULATOR_IPO_PROSPECTUS_DRAFT', publisher: '云深处科技 / 上交所申报材料', source_title: '首次公开发行股票并在科创板上市招股说明书(申报稿)', publication_date: '2026-05-18', canonical_url: 'https://dataclouds.cninfo.com.cn/sjother2/documents/2026/2026-05-18/3ef52e260c1cc31a8c57ab5e0abf2c14.pdf', source_locator: 'P128 L4286-L4288; P129 L4306-L4320',
|
content_update_text: '报告期内绝影X、绝影Lite及山猫M累计产量超过5500台;2025年四足及轮足机器人产量3936台、销量2908台、产销率73.88%。公司采用柔性生产,明确说明不存在固定设计产能或最大产能口径。',
|
limitation: '产量与销量均为四足及轮足合计,不能拆作人形机器人数据;库存增加且产销率低于100%,不能把产量全部写成客户交付。'
|
},
|
{
|
source_update_id: 'N077-SRC-003', canonical_name: '云深处科技', target_field: 'revenue_and_operational_metric', gap_types_addressed: 'REVENUE_PROFIT|ASP',
|
source_class: 'REGULATOR_IPO_PROSPECTUS_DRAFT', publisher: '云深处科技 / 上交所申报材料', source_title: '首次公开发行股票并在科创板上市招股说明书(申报稿)', publication_date: '2026-05-18', canonical_url: 'https://dataclouds.cninfo.com.cn/sjother2/documents/2026/2026-05-18/3ef52e260c1cc31a8c57ab5e0abf2c14.pdf', source_locator: 'P27 L805-L816; P180-P181 L6188-L6215; P188-P190 L6414-L6472',
|
content_update_text: '2025年营业收入33749.06万元、净利润2868.40万元、经营现金流净额6375.22万元、综合毛利率52.83%;具身智能机器人收入32237.73万元。绝影X系列2025年销售单价28.75万元/台、毛利率54.35%。',
|
limitation: '财务数据来自申报稿;绝影X单价不能代表Lite、山猫M或DR,也不能外推公司整体ASP与未来利润率。'
|
},
|
{
|
source_update_id: 'N077-SRC-004', canonical_name: '魔法原子', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'CUSTOMER|ORDER',
|
source_class: 'COMPANY_OFFICIAL_ORDER_ANNOUNCEMENT', publisher: '魔法原子', source_title: '新行业记录!魔法原子成功斩获大健康行业1.5亿元大单', publication_date: '2026-04-21', canonical_url: 'https://www.magiclab.top/news/63', source_locator: 'L41-L52',
|
content_update_text: '公司宣布正式签订1.5亿元机器人采购订单,面向家庭健康管理与智能陪护,并称合作将连接1万名高净值家庭用户。',
|
limitation: '客户未具名,未披露台数、单价、付款、验收、交付周期和收入确认;连接1万家庭为合作规划,不是已服务客户数。'
|
},
|
{
|
source_update_id: 'N077-SRC-005', canonical_name: '魔法原子', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'MASS_PRODUCTION',
|
source_class: 'COMPANY_OFFICIAL_APPLICATION_UPDATE', publisher: '魔法原子', source_title: '新行业记录!魔法原子成功斩获大健康行业1.5亿元大单', publication_date: '2026-04-21', canonical_url: 'https://www.magiclab.top/news/63', source_locator: 'L52-L56',
|
content_update_text: '公司称工业方案已在汽车零部件、3C电子和智能制造装备等用户场景完成测试,并进入标杆工厂交付和规模化落地阶段。',
|
limitation: '未披露具名工厂、机器人台数、验收指标或实际交付量;“规模化落地阶段”仍按供应商口径保留待客户侧复核。'
|
},
|
{
|
source_update_id: 'N077-SRC-006', canonical_name: '魔法原子', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'MASS_PRODUCTION|CAPACITY',
|
source_class: 'COMPANY_OFFICIAL_FINANCING_AND_PRODUCTION_PLAN', publisher: '魔法原子', source_title: '魔法原子完成1.5亿元天使轮融资,2025开启量产交付', publication_date: '2024-12-25', canonical_url: 'https://www.magiclab.top/news/29', source_locator: 'L41-L56',
|
content_update_text: '公司在2024年末称已具备整机大规模量产能力,并预计2025年交付数百台MagicBot、2026年达到千台级。',
|
limitation: '这是历史预测与能力自述,不是截至2026年的实际交付结果;融资1.5亿元也不等于收入或订单。'
|
},
|
{
|
source_update_id: 'N077-SRC-007', canonical_name: '银河通用', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'CUSTOMER',
|
source_class: 'CUSTOMER_OFFICIAL_STRATEGIC_COOPERATION', publisher: '延锋国际', source_title: '延锋国际与银河通用机器人签署战略合作协议', publication_date: '2026-04-13', canonical_url: 'https://www.yanfeng.com/cn/yanfengguojiyuyinhetongyongjiqirenqianshuzhanehezuoxieyi', source_locator: 'L36-L52',
|
content_update_text: '延锋官方披露双方签署战略合作,银河通用上海子公司将参与延锋智造能力中心训练场景建设,面向汽车零部件制造探索可复制的工业应用模式。',
|
limitation: '战略合作不等于采购订单或已部署;未披露台数、金额、验收、排他性及量产时间。'
|
},
|
{
|
source_update_id: 'N077-SRC-008', canonical_name: '银河通用', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'ORDER|MASS_PRODUCTION',
|
source_class: 'COMPANY_PRESS_RELEASE_DISTRIBUTION', publisher: '银河通用 / 美通社', source_title: '银河通用机器人拿下1000台机器人订单,推进具身智能工业场景深度应用', publication_date: '2025-12-23', canonical_url: 'https://www.prnasia.com/story/517052-1.shtml', source_locator: 'L112-L126; L145-L159',
|
content_update_text: '银河通用新闻稿称与百达精工签署战略合作,计划在百达及其生态内部署超过1000台具身智能机器人,并将其称为“一千台订单”。',
|
limitation: '这是企业发布稿,未见合同全文;未披露单价、付款、交付批次、验收或截至目前已交付数量,不能将计划部署直接记作完成出货。'
|
},
|
{
|
source_update_id: 'N077-SRC-009', canonical_name: '擎朗智能', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'CUSTOMER|MASS_PRODUCTION',
|
source_class: 'COMPANY_OFFICIAL_SHIPMENT_UPDATE', publisher: 'KEENON Robotics', source_title: 'KEENON Showcased its General-Purpose + Specialized Robot Ecosystem at LEAP East 2026', publication_date: '2026-07-23', canonical_url: 'https://www.keenon.com/en/news/ksigpsreale2', source_locator: 'L132-L137',
|
content_update_text: '擎朗称全球累计出货超过10万台机器人,部署覆盖70多个国家和地区,并援引IDC称全球出货份额为22.7%。',
|
limitation: '均为公司新闻稿口径,未按产品、人形/专用机器人、年度、客户或收入拆分;IDC份额需报告原文复核。'
|
},
|
{
|
source_update_id: 'N077-SRC-010', canonical_name: '艾利特机器人', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'CUSTOMER|MASS_PRODUCTION',
|
source_class: 'COMPANY_OFFICIAL_PROFILE', publisher: '艾利特机器人', source_title: '关于艾利特', publication_date: 'CURRENT_AS_OF_2026-08-06', canonical_url: 'https://www.elibot.com/about/profile', source_locator: 'L345-L382',
|
content_update_text: '公司当前简介称全球累计销售近2万台协作机器人,覆盖50多个国家,拥有500多家经销商和系统集成商及110余家生态伙伴。',
|
limitation: '这是公司累计宽口径,未给出年度、产品系列、终端客户、退换货或收入拆分,也不等于人形/复合机器人销量。'
|
},
|
{
|
source_update_id: 'N077-SRC-011', canonical_name: '艾利特机器人', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'ORDER|MASS_PRODUCTION',
|
source_class: 'COMPANY_OFFICIAL_PARTNER_DEPLOYMENT_REPORT', publisher: '艾利特机器人 / 迈幸机器人', source_title: '艾利特×迈幸机器人:开启大模型驱动下的复合机器人新纪元', publication_date: '2025-12-30', canonical_url: 'https://www.elibot.com/about/news/162', source_locator: 'L420-L429',
|
content_update_text: '合作方称其复合机器人在某全球科技公司供应链CNC产线落地近200台,并实现单笔订单百台级;在光模块测试场景也完成批量部署。',
|
limitation: '数据来自合作方在艾利特官网的发言,终端客户未具名;近200台为复合设备口径,不能全部等同艾利特机械臂销量或收入。'
|
},
|
{
|
source_update_id: 'N077-SRC-012', canonical_name: '艾利特机器人', target_field: 'revenue_and_operational_metric', gap_types_addressed: 'LIFETIME',
|
source_class: 'COMPANY_OFFICIAL_CERTIFICATION_RECORD', publisher: '艾利特机器人', source_title: '关于艾利特 / MTBF可靠性证书记录', publication_date: '2023-12-29', canonical_url: 'https://www.elibot.com/about/profile', source_locator: 'L453-L465',
|
content_update_text: '公司荣誉页记录2023年12月29日取得10万小时MTBF可靠性证书;官网另称该测试由上海机器人研发与转化功能型平台实施。',
|
limitation: 'MTBF为统计可靠性指标,不等于单台连续实跑10万小时、质保期或现场寿命;本轮未取得完整测试报告与适用型号清单。'
|
},
|
{
|
source_update_id: 'N077-SRC-013', canonical_name: '微亿智造', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'CUSTOMER|MASS_PRODUCTION',
|
source_class: 'COMPANY_OFFICIAL_PROFILE', publisher: '微亿智造', source_title: '关于微亿智造', publication_date: 'CURRENT_AS_OF_2026-08-06', canonical_url: 'https://www.micro-i.com.cn/us', source_locator: 'L0-L5; L106-L120',
|
content_update_text: '公司称工业具身智能产品已实现规模商业化,并向世界领先的3C消费电子及新能源汽车品牌客户批量部署AI表面缺陷检测设备;同时称MIM行业AI视觉质检方案市场占有率70%。',
|
limitation: '客户未具名,未披露设备数量、合同金额、收入或利润;70%为公司引用口径,需IDC/行业报告原文复核,且不等于全部具身智能产品份额。'
|
}
|
].map((row) => ({
|
...row,
|
company_id: companyId(row.canonical_name),
|
source_fact_status: 'PRIMARY_SOURCE_CONTENT_CAPTURED_PENDING_VERIFICATION_NO_UPGRADE',
|
gap_effect: BOUNDARY,
|
claim_strength_after: 'PENDING_VERIFICATION_MAX_NO_UPGRADE',
|
formal_pool_effect: 'NO_AUTOMATIC_FORMAL_POOL_CHANGE',
|
evidence_strength_effect: 'NO_UPGRADE',
|
captured_at: `${AS_OF}T18:30:00+08:00`
|
}));
|
|
const updatesByCompany = new Map();
|
for (const update of sourceBacktrace) {
|
if (!updatesByCompany.has(update.company_id)) updatesByCompany.set(update.company_id, []);
|
updatesByCompany.get(update.company_id).push(update);
|
}
|
|
const targetPriority = targetNames.map((name, index) => {
|
const company = companyByName.get(name);
|
const companyGaps = gaps.filter((row) => row.company_id === company.company_id && row.priority === 'P0');
|
const updates = updatesByCompany.get(company.company_id) || [];
|
return {
|
priority_order: String(index + 1),
|
company_id: company.company_id,
|
canonical_name: name,
|
original_p0_gap_count: String(companyGaps.length),
|
original_p0_gap_types: companyGaps.map((row) => row.gap_type).sort().join('|'),
|
official_source_update_count: String(updates.length),
|
addressed_gap_types: unique(updates.flatMap((row) => row.gap_types_addressed.split('|'))).sort().join('|'),
|
selection_reason: 'HIGH_YIELD_MULTI_GAP_PRIMARY_SOURCE_AVAILABLE',
|
execution_mode: 'CONTENT_STRENGTHENING_NO_MICRO_REVIEW',
|
boundary: 'PENDING_VERIFICATION_MAX_NO_UPGRADE|NO_AUTOMATIC_FORMAL_POOL_CHANGE|NO_UPGRADE'
|
};
|
});
|
|
const gapEffect = [];
|
for (const target of targetPriority) {
|
const companyUpdates = updatesByCompany.get(target.company_id) || [];
|
const companyGaps = gaps.filter((row) => row.company_id === target.company_id && row.priority === 'P0');
|
for (const gap of companyGaps) {
|
const matched = companyUpdates.filter((row) => row.gap_types_addressed.split('|').includes(gap.gap_type));
|
gapEffect.push({
|
gap_effect_id: `N077-GFX-${String(gapEffect.length + 1).padStart(3, '0')}`,
|
source_gap_id: gap.gap_id,
|
company_id: gap.company_id,
|
canonical_name: gap.canonical_name,
|
gap_type: gap.gap_type,
|
source_update_ids: matched.map((row) => row.source_update_id).join('|'),
|
evidence_added_count: String(matched.length),
|
status_after: matched.length ? BOUNDARY : 'OPEN_RETAINED_GAP_NOT_ADDRESSED_THIS_SPRINT',
|
closure_effect: 'NO_GAP_CLOSURE',
|
claim_strength_after: 'PENDING_VERIFICATION_MAX_NO_UPGRADE',
|
formal_pool_effect: 'NO_AUTOMATIC_FORMAL_POOL_CHANGE',
|
evidence_strength_effect: 'NO_UPGRADE'
|
});
|
}
|
}
|
|
const contentFields = ['product_and_technical_capability', 'customer_and_commercial_relationship', 'revenue_and_operational_metric', 'capacity_delivery_and_procurement', 'market_position_and_chain_role', 'high_value_gap_and_risk'];
|
const v5 = companies.map((row) => {
|
const next = { ...row };
|
const updates = updatesByCompany.get(row.company_id) || [];
|
for (const field of contentFields) {
|
const additions = updates.filter((update) => update.target_field === field);
|
if (!additions.length) continue;
|
const appended = additions.map((update) => `〔${update.source_update_id}〕${update.content_update_text}(边界:${update.limitation})`).join(';');
|
next[field] = [normalize(row[field]), `【N077主源回补】${appended}`].filter(Boolean).join(';');
|
}
|
const target = targetPriority.find((item) => item.company_id === row.company_id);
|
next.n077_target_status = target ? 'P0_HIGH_YIELD_TARGET_COMPLETED' : 'NOT_IN_N077_TARGET_BATCH';
|
next.n077_original_p0_gap_count = target?.original_p0_gap_count || '0';
|
next.n077_addressed_gap_types = target?.addressed_gap_types || '';
|
next.n077_primary_source_update_ids = updates.map((update) => update.source_update_id).join('|');
|
next.n077_content_update_status = updates.length ? 'PRIMARY_SOURCE_CONTENT_ENRICHED_GAPS_REMAIN_OPEN_NO_UPGRADE' : 'NO_N077_PRIMARY_SOURCE_UPDATE';
|
next.content_update_batch = updates.length ? unique(String(row.content_update_batch || '').split('|').concat('NEXT-ROBOT-077')).join('|') : row.content_update_batch;
|
next.as_of = AS_OF;
|
return next;
|
});
|
|
const targetColumns = ['priority_order','company_id','canonical_name','original_p0_gap_count','original_p0_gap_types','official_source_update_count','addressed_gap_types','selection_reason','execution_mode','boundary'];
|
const sourceColumns = ['source_update_id','company_id','canonical_name','target_field','gap_types_addressed','source_class','publisher','source_title','publication_date','canonical_url','source_locator','content_update_text','limitation','source_fact_status','gap_effect','claim_strength_after','formal_pool_effect','evidence_strength_effect','captured_at'];
|
const gapEffectColumns = ['gap_effect_id','source_gap_id','company_id','canonical_name','gap_type','source_update_ids','evidence_added_count','status_after','closure_effect','claim_strength_after','formal_pool_effect','evidence_strength_effect'];
|
const v5Columns = unique(Object.keys(companies[0]).concat(['n077_target_status','n077_original_p0_gap_count','n077_addressed_gap_types','n077_primary_source_update_ids','n077_content_update_status']));
|
|
writeCsv(PATHS.targetPriority, targetPriority, targetColumns);
|
writeCsv(PATHS.sourceBacktrace, sourceBacktrace, sourceColumns);
|
writeCsv(PATHS.gapEffect, gapEffect, gapEffectColumns);
|
writeCsv(PATHS.v5, v5, v5Columns);
|
|
const affectedCompanies = new Set(sourceBacktrace.map((row) => row.company_id));
|
const addressedGapTypes = unique(sourceBacktrace.flatMap((row) => row.gap_types_addressed.split('|'))).sort();
|
const partiallyNarrowed = gapEffect.filter((row) => row.status_after === BOUNDARY).length;
|
const remainingUnaddressed = gapEffect.length - partiallyNarrowed;
|
const summary = [
|
'# 机器人企业高价值缺口集中回源(第三轮)',
|
'',
|
`> 更新日期:${AS_OF}`,
|
'> 执行方式:内容优先;把监管披露、客户公告和公司主源直接纳入统一企业表,不新增逐企业审核链。',
|
'> 证据边界:新增信息只缩窄缺口,不自动入正式池、不提升证据强度、不宣称缺口关闭。',
|
'',
|
'## 1. 本轮产出',
|
'',
|
`- 处理企业:${affectedCompanies.size}家。`,
|
`- 新增监管/客户/公司主源字段更新:${sourceBacktrace.length}条。`,
|
`- 覆盖原始P0公司×缺口:${gapEffect.length}项,其中${partiallyNarrowed}项获得直接增量证据,${remainingUnaddressed}项继续保留。`,
|
`- 涉及缺口类型:${addressedGapTypes.join('、')}。`,
|
'',
|
'## 2. 企业与增量事实',
|
'',
|
'| 企业 | 原P0缺口 | 新增主源数 | 本轮补充重点 |',
|
'|---|---|---:|---|',
|
...targetPriority.map((row) => `| ${md(row.canonical_name)} | ${md(row.original_p0_gap_types)} | ${row.official_source_update_count} | ${md(row.addressed_gap_types)} |`),
|
'',
|
'## 3. 主源事实与边界',
|
'',
|
'| 企业 | 字段 | 新增事实 | 保留边界 |',
|
'|---|---|---|---|',
|
...sourceBacktrace.map((row) => `| ${md(row.canonical_name)} | ${row.target_field} | ${md(row.content_update_text)} | ${md(row.limitation)} |`),
|
'',
|
'## 4. 实用结论',
|
'',
|
'- 云深处已形成当前企业库中较完整的财务、产销、客户与ASP主源剖面:2025年收入3.37亿元、净利润2868万元、四/轮足产量3936台、销量2908台;但招股书仍为申报稿且人形数据不可从四足口径外推。',
|
'- 魔法原子的1.5亿元大健康订单可作为订单事实保留;客户、台数、验收与收入确认未披露。2024年提出的数百/千台交付只能记为历史计划,不能当成实际完成。',
|
'- 银河通用与延锋的合作由客户侧确认;百达精工千台项目来自银河通用发布稿,当前只能写成签约/计划部署,不能写成已交付千台。',
|
'- 擎朗、艾利特和微亿补入累计出货、批量部署与可靠性信息;相关数据仍按公司口径保留,未拆出的机器人单品收入、ASP和利润继续列缺口。',
|
'- 下一轮继续优先处理一份监管/客户/公司主源能同时补两类以上商业缺口的P0企业。',
|
'',
|
'## 5. 数据入口',
|
'',
|
'- 目标优先级:`evidence/next_robot_077_p0_target_priority_20260806.csv`',
|
'- 主源回溯:`evidence/next_robot_077_primary_source_backtrace_20260806.csv`',
|
'- 缺口效果表:`evidence/next_robot_077_gap_effect_register_20260806.csv`',
|
'- 307家公司统一信息表v05:`outputs/数据表/robot_company_information_v05_20260806.csv`'
|
];
|
writeText(PATHS.summary, `${summary.join('\n')}\n`);
|
|
const validation = [];
|
function check(id, description, expected, actual) {
|
validation.push({ check_id: id, check_description: description, expected: String(expected), actual: String(actual), status: String(expected) === String(actual) ? 'PASS' : 'FAIL' });
|
}
|
|
const immutable = ['company_id','canonical_name','aliases','company_type','region','listed_status','primary_track','detail_track','source_profile_status','formal_output_status','evidence_ceiling','formal_pool_effect'];
|
let immutableMismatch = 0;
|
let unauthorizedBaseFieldMismatch = 0;
|
for (const row of v5) {
|
const before = companyById.get(row.company_id);
|
for (const column of immutable) if ((before?.[column] || '') !== (row[column] || '')) immutableMismatch += 1;
|
const allowedChanged = new Set(['as_of']);
|
if (targetNames.includes(row.canonical_name)) {
|
for (const column of [...contentFields, 'content_update_batch']) allowedChanged.add(column);
|
}
|
for (const column of Object.keys(companies[0])) {
|
if (!allowedChanged.has(column) && (before?.[column] || '') !== (row[column] || '')) unauthorizedBaseFieldMismatch += 1;
|
}
|
}
|
|
const v4IdentityAfter = { bytes: fs.statSync(PATHS.v4).size, sha256: shaFile(PATHS.v4) };
|
const sourceText = readText(PATHS.sourceBacktrace);
|
check('N077-VAL-001', 'company v04 row count', 307, companies.length);
|
check('N077-VAL-002', 'company v04 id uniqueness', 307, new Set(companies.map((row) => row.company_id)).size);
|
check('N077-VAL-003', 'P0 target company count', 6, targetPriority.length);
|
check('N077-VAL-004', 'P0 target id uniqueness', 6, new Set(targetPriority.map((row) => row.company_id)).size);
|
check('N077-VAL-005', 'primary source update count', 13, sourceBacktrace.length);
|
check('N077-VAL-006', 'source update id uniqueness', 13, new Set(sourceBacktrace.map((row) => row.source_update_id)).size);
|
check('N077-VAL-007', 'source company ids unresolved', 0, sourceBacktrace.filter((row) => !companyById.has(row.company_id)).length);
|
check('N077-VAL-008', 'source rows missing URL/locator/content/boundary', 0, sourceBacktrace.filter((row) => !/^https:\/\//.test(row.canonical_url) || !row.source_locator || !row.content_update_text || !row.limitation).length);
|
check('N077-VAL-009', 'source rows outside target set', 0, sourceBacktrace.filter((row) => !targetNames.includes(row.canonical_name)).length);
|
check('N077-VAL-010', 'P0 gap effect rows', 38, gapEffect.length);
|
check('N077-VAL-011', 'P0 gap effect id uniqueness', 38, new Set(gapEffect.map((row) => row.gap_effect_id)).size);
|
check('N077-VAL-012', 'P0 gaps with direct increment', 20, partiallyNarrowed);
|
check('N077-VAL-013', 'P0 gaps retained unaddressed', 18, remainingUnaddressed);
|
check('N077-VAL-014', 'company v05 row count', 307, v5.length);
|
check('N077-VAL-015', 'company v05 id uniqueness', 307, new Set(v5.map((row) => row.company_id)).size);
|
check('N077-VAL-016', 'immutable company field mismatch', 0, immutableMismatch);
|
check('N077-VAL-017', 'v05 enriched company count', 6, v5.filter((row) => row.n077_content_update_status === 'PRIMARY_SOURCE_CONTENT_ENRICHED_GAPS_REMAIN_OPEN_NO_UPGRADE').length);
|
check('N077-VAL-018', 'source update ids missing from v05', 0, sourceBacktrace.filter((update) => !v5.find((row) => row.company_id === update.company_id)?.n077_primary_source_update_ids.includes(update.source_update_id)).length);
|
check('N077-VAL-019', 'formal pool boundary drift', 0, v5.filter((row) => row.formal_pool_effect !== 'NO_AUTOMATIC_FORMAL_POOL_CHANGE').length);
|
check('N077-VAL-020', 'source backtrace forbidden upgrade literal count', 0, (sourceText.match(/FORMAL_POOL_APPROVED|EVIDENCE_UPGRADED|GAP_CLOSED/g) || []).length);
|
check('N077-VAL-021', 'v04 bytes unchanged', v4IdentityBefore.bytes, v4IdentityAfter.bytes);
|
check('N077-VAL-022', 'v04 sha256 unchanged', v4IdentityBefore.sha256, v4IdentityAfter.sha256);
|
check('N077-VAL-023', 'target original P0 gaps nonzero', 0, targetPriority.filter((row) => Number(row.original_p0_gap_count) < 1).length);
|
check('N077-VAL-024', 'gap closure count', 0, gapEffect.filter((row) => row.closure_effect !== 'NO_GAP_CLOSURE').length);
|
check('N077-VAL-025', 'evidence strength upgrade count', 0, sourceBacktrace.filter((row) => row.evidence_strength_effect !== 'NO_UPGRADE').length);
|
check('N077-VAL-026', 'automatic formal-pool change count', 0, sourceBacktrace.filter((row) => row.formal_pool_effect !== 'NO_AUTOMATIC_FORMAL_POOL_CHANGE').length);
|
check('N077-VAL-027', 'unauthorized inherited base-field mismatch', 0, unauthorizedBaseFieldMismatch);
|
check('N077-VAL-028', 'source update tags missing from v05 content fields', 0, sourceBacktrace.filter((update) => !Object.values(v5.find((row) => row.company_id === update.company_id) || {}).some((value) => String(value).includes(`〔${update.source_update_id}〕`))).length);
|
|
writeCsv(PATHS.validation, validation, ['check_id','check_description','expected','actual','status']);
|
const failed = validation.filter((row) => row.status === 'FAIL');
|
console.log(JSON.stringify({
|
status: failed.length ? 'FAIL' : 'PASS',
|
targetCompanies: targetPriority.length,
|
primarySourceUpdates: sourceBacktrace.length,
|
p0GapEffects: gapEffect.length,
|
partiallyNarrowed,
|
remainingUnaddressed,
|
v5Companies: v5.length,
|
validationPass: validation.filter((row) => row.status === 'PASS').length,
|
validationFail: failed.length,
|
outputs: Object.values(PATHS).filter((value) => ![PATHS.v4, PATHS.gapRegister].includes(value))
|
}, null, 2));
|
if (failed.length) process.exitCode = 1;
|