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 = {
|
v3: path.join(CASE_ROOT, 'outputs', '数据表', 'robot_company_information_v03_20260805.csv'),
|
gapRegister: path.join(CASE_ROOT, 'evidence', 'next_robot_075_high_value_gap_register_20260805.csv'),
|
targetPriority: path.join(CASE_ROOT, 'evidence', 'next_robot_076_p0_target_priority_20260806.csv'),
|
sourceBacktrace: path.join(CASE_ROOT, 'evidence', 'next_robot_076_primary_source_backtrace_20260806.csv'),
|
gapEffect: path.join(CASE_ROOT, 'evidence', 'next_robot_076_gap_effect_register_20260806.csv'),
|
v4: path.join(CASE_ROOT, 'outputs', '数据表', 'robot_company_information_v04_20260806.csv'),
|
summary: path.join(CASE_ROOT, 'outputs', '核心文档', '机器人企业高价值缺口集中回源_第二轮_20260806.md'),
|
validation: path.join(CASE_ROOT, 'manifest', 'next_robot_076_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.v3);
|
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 v3IdentityBefore = { bytes: fs.statSync(PATHS.v3).size, sha256: shaFile(PATHS.v3) };
|
|
function companyId(name) {
|
const row = companyByName.get(name);
|
if (!row) throw new Error(`UNRESOLVED_COMPANY:${name}`);
|
return row.company_id;
|
}
|
|
const targetNames = [
|
'广和通', '速腾聚创', '1X Technologies', 'Agility Robotics', 'Apptronik',
|
'Omron', 'Boston Dynamics', 'Tesla Optimus', 'Figure AI'
|
];
|
|
const sourceBacktrace = [
|
{
|
source_update_id: 'N076-SRC-001', canonical_name: '1X Technologies', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'ORDER|ASP',
|
source_class: 'COMPANY_OFFICIAL_ORDER_PAGE', publisher: '1X Technologies', source_title: 'Order NEO', publication_date: 'CURRENT_AS_OF_2026-08-06', canonical_url: 'https://www.1x.tech/order', source_locator: 'Order page: ownership/subscription/deposit/delivery terms',
|
content_update_text: 'NEO 官方销售页列示买断价2万美元、订阅价499美元/月、可退订金200美元,并称美国首批交付从2026年开始。',
|
limitation: '预订不等于公司已接受的最终订单;页面未披露有效预订量、已交付量、退款率或收入确认。'
|
},
|
{
|
source_update_id: 'N076-SRC-002', canonical_name: '1X Technologies', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'MASS_PRODUCTION|CAPACITY|ORDER',
|
source_class: 'COMPANY_OFFICIAL_FACTORY_UPDATE', publisher: '1X Technologies', source_title: 'NEO Factory update', publication_date: '2026-04-30', canonical_url: 'https://www.1x.tech/ja_jp/discover/neo-factory', source_locator: 'Factory update: Hayward footprint, workforce, capacity and bookings',
|
content_update_text: '公司称Hayward工厂面积5.8万平方英尺、员工超过200人并已开始全面生产;Hayward与San Carlos合计年产能最高1万台,首年1万台产能在5天内被预订,目标到2027年底超过10万台/年。',
|
limitation: '首年产能被预订不等于1万份最终订单或已交付;10万台/年为规划能力,不是当前产量。'
|
},
|
{
|
source_update_id: 'N076-SRC-003', canonical_name: 'Agility Robotics', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'CUSTOMER|ORDER|MASS_PRODUCTION',
|
source_class: 'COMPANY_OFFICIAL_COMMERCIAL_AGREEMENT', publisher: 'Agility Robotics', source_title: 'GXO signs industry-first multi-year agreement with Agility Robotics', publication_date: '2024-06-27', canonical_url: 'https://www.agilityrobotics.com/content/gxo-signs-industry-first-multi-year-agreement-with-agility-robotics', source_locator: 'Release: multi-year RaaS agreement and live SPANX operation',
|
content_update_text: 'Agility与GXO签署多年RaaS协议,Digit在GXO为SPANX运营的仓库中进入实际物流流程并与Arc系统协同,公司称该部署开始产生服务收入。',
|
limitation: '协议未披露机器人台数、合同总额、单台月费、毛利或续约条件;供应商的“产生收入”表述不替代财务报表。'
|
},
|
{
|
source_update_id: 'N076-SRC-004', canonical_name: 'Agility Robotics', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'MASS_PRODUCTION|CAPACITY',
|
source_class: 'COMPANY_OFFICIAL_FACTORY_UPDATE', publisher: 'Agility Robotics', source_title: 'Announcing RoboFab', publication_date: '2023-09-20', canonical_url: 'https://www.agilityrobotics.com/videos/announcing-robofab-worlds-first-factory-for-humanoid-robots', source_locator: 'RoboFab announcement: footprint and designed output',
|
content_update_text: 'RoboFab占地约7万平方英尺,公司披露首年计划生产数百台Digit,工厂具备年产超过1万台的设计能力。',
|
limitation: '设计能力和首年计划不等于实际产量、交付量或产能利用率。'
|
},
|
{
|
source_update_id: 'N076-SRC-005', canonical_name: 'Apptronik', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'CUSTOMER|ORDER|MASS_PRODUCTION',
|
source_class: 'COMPANY_OFFICIAL_COMMERCIAL_AGREEMENT', publisher: 'Apptronik', source_title: 'Apptronik and Mercedes-Benz enter commercial agreement', publication_date: '2024-03-15', canonical_url: 'https://apptronik.com/news-collection/apptronik-and-mercedes-benz-enter-commercial-agreement', source_locator: 'Release: commercial agreement and manufacturing/logistics pilot',
|
content_update_text: 'Apptronik与Mercedes-Benz达成商业协议,并将Apollo用于制造和物流场景试点;公司将其称为首个公开商业部署协议。',
|
limitation: '未披露部署台数、采购义务、合同金额、任务良率或由试点转量产的条件。'
|
},
|
{
|
source_update_id: 'N076-SRC-006', canonical_name: 'Apptronik', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'MASS_PRODUCTION|CAPACITY',
|
source_class: 'COMPANY_OFFICIAL_MANUFACTURING_PARTNERSHIP', publisher: 'Apptronik', source_title: 'Apptronik and Jabil collaborate to scale production', publication_date: '2025-02-25', canonical_url: 'https://apptronik.com/news-collection/apptronik-and-jabil-collaborate-to-scale-production', source_locator: 'Release: global manufacturing partner and factory pilot',
|
content_update_text: 'Jabil成为Apptronik的全球制造合作伙伴,将生产Apollo,并在自身工厂试用Apollo执行制造任务。',
|
limitation: '合作公告未给出产能、已生产台数、交付节奏、良率或采购金额。'
|
},
|
{
|
source_update_id: 'N076-SRC-007', canonical_name: 'Apptronik', target_field: 'revenue_and_operational_metric', gap_types_addressed: 'REVENUE_PROFIT',
|
source_class: 'COMPANY_OFFICIAL_FINANCING_UPDATE', publisher: 'Apptronik', source_title: 'Apptronik closes over $935 million Series A', publication_date: '2026-02-11', canonical_url: 'https://apptronik.com/news-collection/apptronik-closes-over-935-million-series-a', source_locator: 'Release: financing totals and stated use',
|
content_update_text: 'Apptronik披露Series A累计融资超过9.35亿美元、公司累计融资接近10亿美元,资金用于扩大生产和商业部署。',
|
limitation: '融资金额不是营业收入、订单、利润或客户付款,不能用于推断商业化兑现。'
|
},
|
{
|
source_update_id: 'N076-SRC-008', canonical_name: 'Figure AI', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'CUSTOMER|MASS_PRODUCTION',
|
source_class: 'COMPANY_OFFICIAL_DEPLOYMENT_REPORT', publisher: 'Figure AI', source_title: 'Production at BMW', publication_date: '2025-11-19', canonical_url: 'https://www.figure.ai/news/production-at-bmw', source_locator: 'BMW deployment metrics: shifts, parts, hours and vehicles',
|
content_update_text: 'Figure披露Figure 02在BMW产线历时11个月部署,累计运行超过1250小时、处理超过9万件零件,并参与超过3万辆BMW X3的生产流程。',
|
limitation: '供应商披露的运营数据需继续以BMW客户侧材料交叉验证;未披露合同金额、部署台数、收入或供应链。'
|
},
|
{
|
source_update_id: 'N076-SRC-009', canonical_name: 'Figure AI', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'MASS_PRODUCTION|CAPACITY',
|
source_class: 'COMPANY_OFFICIAL_PRODUCTION_UPDATE', publisher: 'Figure AI', source_title: 'Ramping Figure 03 production', publication_date: '2026-04-29', canonical_url: 'https://www.figure.ai/news/ramping-figure-03-production', source_locator: 'Production update: units, rate, first-pass yield, batteries and actuators',
|
content_update_text: '公司披露已生产超过350台Figure 03,生产节拍由每天1台提升到每小时1台,整机一次通过率超过80%,电池线一次通过率99.3%,累计生产超过500块电池和9000个执行器。',
|
limitation: '生产节拍不等于持续满负荷年化产量;机器人分配到研发、数据和商业开发,不能全部视为客户交付。'
|
},
|
{
|
source_update_id: 'N076-SRC-010', canonical_name: 'Figure AI', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'CAPACITY',
|
source_class: 'COMPANY_OFFICIAL_PRODUCT_AND_FACTORY_UPDATE', publisher: 'Figure AI', source_title: 'Introducing Figure 03', publication_date: '2025-10-09', canonical_url: 'https://www.figure.ai/news/introducing-figure-03?id=Figure03', source_locator: 'BotQ capacity and four-year production goal',
|
content_update_text: 'Figure称BotQ首条产线设计年产能最高1.2万台,并提出未来四年累计生产10万台机器人的目标。',
|
limitation: '均为设计能力或目标,不是已实现产量、订单或交付。'
|
},
|
{
|
source_update_id: 'N076-SRC-011', canonical_name: 'Boston Dynamics', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'CUSTOMER|ORDER|MASS_PRODUCTION',
|
source_class: 'COMPANY_OFFICIAL_DEPLOYMENT_ANNOUNCEMENT', publisher: 'Boston Dynamics', source_title: 'Boston Dynamics unveils new Atlas robot to revolutionize industry', publication_date: '2026-01-05', canonical_url: 'https://bostondynamics.com/blog/boston-dynamics-unveils-new-atlas-robot-to-revolutionize-industry/', source_locator: 'Announcement: committed 2026 deployments and named fleets',
|
content_update_text: 'Boston Dynamics称2026年Atlas部署名额已全部落实,首批机器人将部署到Hyundai RMAC和Google DeepMind,并计划在2027年增加客户。',
|
limitation: '“部署名额已落实”未给出台数、合同金额、验收状态或已交付数量。'
|
},
|
{
|
source_update_id: 'N076-SRC-012', canonical_name: 'Boston Dynamics', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'MASS_PRODUCTION|CAPACITY',
|
source_class: 'COMPANY_OFFICIAL_PRODUCTION_ANNOUNCEMENT', publisher: 'Boston Dynamics', source_title: 'Boston Dynamics unveils new Atlas robot to revolutionize industry', publication_date: '2026-01-05', canonical_url: 'https://bostondynamics.com/blog/boston-dynamics-unveils-new-atlas-robot-to-revolutionize-industry/', source_locator: 'Announcement: production start and Hyundai factory plan',
|
content_update_text: '公司宣布立即启动新Atlas生产;同时披露Hyundai筹备的新机器人设施规划年产3万台,并提出未来在集团设施内部署数万台机器人的计划。',
|
limitation: '3万台/年和数万台部署属于Hyundai规划,不是Boston Dynamics当前产量或已经签收的Atlas订单。'
|
},
|
{
|
source_update_id: 'N076-SRC-013', canonical_name: 'Tesla Optimus', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'MASS_PRODUCTION|CUSTOMER',
|
source_class: 'REGULATOR_FILING', publisher: 'Tesla, Inc. / SEC', source_title: 'Q1 2026 Update', publication_date: '2026-04-22', canonical_url: 'https://ir.tesla.com/_flysystem/s3/sec/000162828026026551/tsla-20260422-gen.pdf', source_locator: 'Q1 2026 update: Optimus production lines',
|
content_update_text: 'Tesla在Q1 2026更新中称首代Optimus生产线正在安装,目的是为后续规模生产做准备。',
|
limitation: '“生产线安装中”说明仍处建设/爬坡准备阶段,不等于已经量产;该文件未披露外部客户、订单、交付量或机器人收入。'
|
},
|
{
|
source_update_id: 'N076-SRC-014', canonical_name: '速腾聚创', target_field: 'revenue_and_operational_metric', gap_types_addressed: 'REVENUE_PROFIT|MASS_PRODUCTION',
|
source_class: 'HKEX_RESULTS_ANNOUNCEMENT', publisher: 'RoboSense / HKEX', source_title: 'Fourth quarter and full year 2025 results', publication_date: '2026-03-25', canonical_url: 'https://www1.hkexnews.hk/listedco/listconews/sehk/2026/0325/2026032501103_c.pdf', source_locator: 'Q4 2025 robotics revenue, volume, margin and profit tables',
|
content_update_text: '公司披露2025年第四季度总收入7.507亿元、毛利率28.5%、经营利润1.301亿元、净利润1.037亿元;其中机器人业务收入3.467亿元,机器人及其他激光雷达销量22.12万台。',
|
limitation: '季度机器人业务口径包含多类机器人场景,不等于人形机器人专属收入、ASP或利润;全年与单季不可混用。'
|
},
|
{
|
source_update_id: 'N076-SRC-015', canonical_name: '速腾聚创', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'CUSTOMER|MASS_PRODUCTION',
|
source_class: 'COMPANY_OFFICIAL_BUSINESS_UPDATE', publisher: 'RoboSense', source_title: '2025 annual results business update', publication_date: '2026-03-25', canonical_url: 'https://www.robosense.ai/en/news-show-1976', source_locator: 'Robotics customer and shipment summary',
|
content_update_text: '公司称2025年机器人激光雷达出货约30.3万台,并已服务超过3400家机器人行业客户,第四季度机器人业务收入约3.47亿元。',
|
limitation: '客户数量和市场排名为公司披露口径;未拆分人形机器人客户装机、单客户收入、ASP、主供份额或合同。'
|
},
|
{
|
source_update_id: 'N076-SRC-016', canonical_name: '广和通', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'CUSTOMER|ORDER|MASS_PRODUCTION',
|
source_class: 'REGULATOR_ANNUAL_REPORT', publisher: '广和通 / 巨潮资讯', source_title: '2024年年度报告', publication_date: '2025-04-19', canonical_url: 'https://static.cninfo.com.cn/finalpage/2025-04-19/1223152460.PDF', source_locator: 'Annual report: robot product line milestones',
|
content_update_text: '广和通年报披露机器人产品线成立于2023年,2024年12月已交付首批客户订单;具身智能方案于2024年3月向一家全球知名具身机器人公司送样。',
|
limitation: '客户未具名,未披露订单数量、金额、收入确认、复购或Fibot单独商业化数据。'
|
},
|
{
|
source_update_id: 'N076-SRC-017', canonical_name: '广和通', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'MASS_PRODUCTION|CAPACITY',
|
source_class: 'COMPANY_OFFICIAL_PRODUCT_UPDATE', publisher: '广和通', source_title: '无边界AI割草机解决方案实现规模商用量产', publication_date: '2025-03-06', canonical_url: 'https://www.fibocom.com/newscenter/info_itemid_8604.html', source_locator: 'Release: mass commercial production for European market',
|
content_update_text: '广和通披露面向永强集团子公司的无边界AI割草机方案已实现面向欧洲市场的规模商用量产,方案集成主控板、算法板和驱动板。',
|
limitation: '这是割草机器人方案,不等于人形机器人或Fibot量产;未披露产能、出货量、ASP、毛利或客户订单金额。'
|
},
|
{
|
source_update_id: 'N076-SRC-018', canonical_name: 'Omron', target_field: 'revenue_and_operational_metric', gap_types_addressed: 'REVENUE_PROFIT|CUSTOMER',
|
source_class: 'COMPANY_INTEGRATED_REPORT', publisher: 'OMRON Corporation', source_title: 'Integrated Report 2025 - Industrial Automation Business', publication_date: '2025-09-01', canonical_url: 'https://www.omron.com/global/en/integrated_report/strategybusiness/iab/', source_locator: 'IAB performance and Output & Robotics product mix',
|
content_update_text: 'Omron披露FY2024工业自动化业务净销售额3608亿日元、营业利润363亿日元、营业利润率10.1%,创新自动化客户4290家;Output & Robotics占该业务产品销售结构13%。',
|
limitation: '工业自动化业务和Output & Robotics均为宽口径,后者还包含安全设备;不能据此拆出AMR/协作机器人收入、销量、客户或ASP。'
|
}
|
].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}T15: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: companyGaps.length >= 4 ? 'HIGH_YIELD_MULTI_GAP_PRIMARY_SOURCE_AVAILABLE' : 'HIGH_VALUE_CURRENT_PRODUCTION_OR_CUSTOMER_BOUNDARY',
|
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: `N076-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 ? 'OPEN_RETAINED_GAP_PARTIALLY_NARROWED' : '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 v4 = companies.map((row) => {
|
const next = { ...row };
|
const updates = updatesByCompany.get(row.company_id) || [];
|
for (const field of ['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 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]), `【N076主源回补】${appended}`].filter(Boolean).join(';');
|
}
|
const target = targetPriority.find((item) => item.company_id === row.company_id);
|
next.n076_target_status = target ? 'P0_HIGH_YIELD_TARGET_COMPLETED' : 'NOT_IN_N076_TARGET_BATCH';
|
next.n076_original_p0_gap_count = target?.original_p0_gap_count || '0';
|
next.n076_addressed_gap_types = target?.addressed_gap_types || '';
|
next.n076_primary_source_update_ids = updates.map((update) => update.source_update_id).join('|');
|
next.n076_content_update_status = updates.length ? 'PRIMARY_SOURCE_CONTENT_ENRICHED_GAPS_REMAIN_OPEN_NO_UPGRADE' : 'NO_N076_PRIMARY_SOURCE_UPDATE';
|
next.content_update_batch = updates.length ? unique(String(row.content_update_batch || '').split('|').concat('NEXT-ROBOT-076')).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 v4Columns = unique(Object.keys(companies[0]).concat(['n076_target_status','n076_original_p0_gap_count','n076_addressed_gap_types','n076_primary_source_update_ids','n076_content_update_status']));
|
|
writeCsv(PATHS.targetPriority, targetPriority, targetColumns);
|
writeCsv(PATHS.sourceBacktrace, sourceBacktrace, sourceColumns);
|
writeCsv(PATHS.gapEffect, gapEffect, gapEffectColumns);
|
writeCsv(PATHS.v4, v4, v4Columns);
|
|
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 === 'OPEN_RETAINED_GAP_PARTIALLY_NARROWED').length;
|
const remainingUnaddressed = gapEffect.length - partiallyNarrowed;
|
const summary = [
|
'# 机器人企业高价值缺口集中回源(第二轮)',
|
'',
|
`> 更新日期:${AS_OF}`,
|
'> 执行方式:内容补充优先;按高收益P0企业批量回源,不新增逐企业审核链。',
|
'> 证据边界:所有新增信息均为待验证内容增强,不自动入正式池、不提升证据强度、不宣称缺口关闭。',
|
'',
|
'## 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. 实用结论',
|
'',
|
'- 1X、Agility、Apptronik、Figure和Boston Dynamics的客户、协议、试点、生产和设计产能已按“已发生/计划/未披露”分层。',
|
'- Tesla Optimus当前可确认的是生产线安装和量产准备,不能写成已规模量产或已有外部客户。',
|
'- 速腾聚创和广和通补入了可量化的机器人业务/交付线索,但人形专属收入、ASP、单客户合同和利润仍未被证明。',
|
'- Omron补入宽口径IAB经营数据及Output & Robotics占比;继续禁止将该宽口径直接写成机器人单体收入。',
|
'- 下一批继续按同样方法处理剩余P0公司,优先选择一份主源可同时补客户、订单、量产或财务多个字段的企业。',
|
'',
|
'## 5. 数据入口',
|
'',
|
'- 目标优先级:`evidence/next_robot_076_p0_target_priority_20260806.csv`',
|
'- 主源回溯:`evidence/next_robot_076_primary_source_backtrace_20260806.csv`',
|
'- 缺口效果表:`evidence/next_robot_076_gap_effect_register_20260806.csv`',
|
'- 307家公司统一信息表v04:`outputs/数据表/robot_company_information_v04_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 v4) {
|
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 ['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','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 v3IdentityAfter = { bytes: fs.statSync(PATHS.v3).size, sha256: shaFile(PATHS.v3) };
|
const sourceText = readText(PATHS.sourceBacktrace);
|
|
check('N076-VAL-001', 'company v03 row count', 307, companies.length);
|
check('N076-VAL-002', 'company v03 id uniqueness', 307, new Set(companies.map((row) => row.company_id)).size);
|
check('N076-VAL-003', 'P0 target company count', 9, targetPriority.length);
|
check('N076-VAL-004', 'P0 target id uniqueness', 9, new Set(targetPriority.map((row) => row.company_id)).size);
|
check('N076-VAL-005', 'primary source update count', 18, sourceBacktrace.length);
|
check('N076-VAL-006', 'source update id uniqueness', 18, new Set(sourceBacktrace.map((row) => row.source_update_id)).size);
|
check('N076-VAL-007', 'source company ids unresolved', 0, sourceBacktrace.filter((row) => !companyById.has(row.company_id)).length);
|
check('N076-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('N076-VAL-009', 'source rows outside target set', 0, sourceBacktrace.filter((row) => !targetNames.includes(row.canonical_name)).length);
|
check('N076-VAL-010', 'P0 gap effect rows', 32, gapEffect.length);
|
check('N076-VAL-011', 'P0 gap effect id uniqueness', 32, new Set(gapEffect.map((row) => row.gap_effect_id)).size);
|
check('N076-VAL-012', 'P0 gaps with direct increment', 24, partiallyNarrowed);
|
check('N076-VAL-013', 'P0 gaps retained unaddressed', 8, remainingUnaddressed);
|
check('N076-VAL-014', 'company v04 row count', 307, v4.length);
|
check('N076-VAL-015', 'company v04 id uniqueness', 307, new Set(v4.map((row) => row.company_id)).size);
|
check('N076-VAL-016', 'immutable company field mismatch', 0, immutableMismatch);
|
check('N076-VAL-017', 'v04 enriched company count', 9, v4.filter((row) => row.n076_content_update_status === 'PRIMARY_SOURCE_CONTENT_ENRICHED_GAPS_REMAIN_OPEN_NO_UPGRADE').length);
|
check('N076-VAL-018', 'source update ids missing from v04', 0, sourceBacktrace.filter((update) => !v4.find((row) => row.company_id === update.company_id)?.n076_primary_source_update_ids.includes(update.source_update_id)).length);
|
check('N076-VAL-019', 'formal pool boundary drift', 0, v4.filter((row) => row.formal_pool_effect !== 'NO_AUTOMATIC_FORMAL_POOL_CHANGE').length);
|
check('N076-VAL-020', 'source backtrace forbidden upgrade literal count', 0, (sourceText.match(/FORMAL_POOL_APPROVED|EVIDENCE_UPGRADED|GAP_CLOSED/g) || []).length);
|
check('N076-VAL-021', 'v03 bytes unchanged', v3IdentityBefore.bytes, v3IdentityAfter.bytes);
|
check('N076-VAL-022', 'v03 sha256 unchanged', v3IdentityBefore.sha256, v3IdentityAfter.sha256);
|
check('N076-VAL-023', 'target original P0 gaps nonzero', 0, targetPriority.filter((row) => Number(row.original_p0_gap_count) < 1).length);
|
check('N076-VAL-024', 'gap closure count', 0, gapEffect.filter((row) => row.closure_effect !== 'NO_GAP_CLOSURE').length);
|
check('N076-VAL-025', 'evidence strength upgrade count', 0, sourceBacktrace.filter((row) => row.evidence_strength_effect !== 'NO_UPGRADE').length);
|
check('N076-VAL-026', 'automatic formal-pool change count', 0, sourceBacktrace.filter((row) => row.formal_pool_effect !== 'NO_AUTOMATIC_FORMAL_POOL_CHANGE').length);
|
check('N076-VAL-027', 'unauthorized inherited base-field mismatch', 0, unauthorizedBaseFieldMismatch);
|
check('N076-VAL-028', 'source update tags missing from v04 content fields', 0, sourceBacktrace.filter((update) => !Object.values(v4.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,
|
v4Companies: v4.length,
|
validationPass: validation.filter((row) => row.status === 'PASS').length,
|
validationFail: failed.length,
|
outputs: Object.values(PATHS).filter((value) => ![PATHS.v3, PATHS.gapRegister].includes(value))
|
}, null, 2));
|
if (failed.length) process.exitCode = 1;
|