Cai
2026-08-10 8b48cb451e26bc39e5e7d5ef76de5787e9d8ef0d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
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-05';
const BOUNDARY = 'OPEN_RETAINED_GAP|PENDING_VERIFICATION_MAX_NO_UPGRADE|NO_AUTOMATIC_FORMAL_POOL_CHANGE|NO_UPGRADE';
 
const PATHS = {
  v2: path.join(CASE_ROOT, 'outputs', '数据表', 'robot_company_information_v02_20260805.csv'),
  candidates: path.join(CASE_ROOT, 'evidence', 'detail_strengthening_p0_primary_source_candidate_register_repair001_20260730.csv'),
  gapRegister: path.join(CASE_ROOT, 'evidence', 'next_robot_075_high_value_gap_register_20260805.csv'),
  candidateReview: path.join(CASE_ROOT, 'evidence', 'next_robot_075_p0_candidate_semantic_verification_20260805.csv'),
  sourceBacktrace: path.join(CASE_ROOT, 'evidence', 'next_robot_075_primary_source_backtrace_20260805.csv'),
  v3: path.join(CASE_ROOT, 'outputs', '数据表', 'robot_company_information_v03_20260805.csv'),
  summary: path.join(CASE_ROOT, 'outputs', '核心文档', '机器人企业高价值缺口集中回源_第一轮_20260805.md'),
  validation: path.join(CASE_ROOT, 'manifest', 'next_robot_075_high_value_gap_content_sprint_validation_20260805.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 gapRules = [
  ['CUSTOMER', /客户|终端客户|具名客户/],
  ['ORDER', /订单|合同|定点|采购|预订|复购|回款/],
  ['MASS_PRODUCTION', /量产|小批量|送样|交付|出货|销量|发货|装机|部署台数/],
  ['CAPACITY', /产能|利用率|产量|良率|排产/],
  ['ASP', /ASP|售价|价格|单机价值|收费|订阅/i],
  ['LIFETIME', /寿命|可靠性|MTBF|循环|质保|故障率|返修|维护/i],
  ['REVENUE_PROFIT', /收入|利润|毛利|经营现金流|财务|营业/]
];
 
const evidenceNeeded = {
  CUSTOMER: '客户公告、客户案例、验收材料或双方正式披露',
  ORDER: '合同、定点通知、采购公告、订单或回款原始披露',
  MASS_PRODUCTION: '量产公告、交付清单、出货/装机/销量原始数据',
  CAPACITY: '产线公告、环评/募投、产能产量良率与利用率原始表',
  ASP: '产品报价、采购单价、合同金额与数量或正式收费页',
  LIFETIME: '规格书、测试报告、质保条款、现场可靠性或循环寿命数据',
  REVENUE_PROFIT: '年报、公告、IR或分部收入利润与毛利原表'
};
 
const companies = readCsv(PATHS.v2);
const candidates = readCsv(PATHS.candidates);
const v2IdentityBefore = { bytes: fs.statSync(PATHS.v2).size, sha256: shaFile(PATHS.v2) };
const companyById = new Map(companies.map((row) => [row.company_id, row]));
const companyByName = new Map(companies.map((row) => [row.canonical_name, row]));
 
const gapRegister = [];
for (const company of companies) {
  const text = normalize(company.high_value_gap_and_risk);
  const priority = /^P0/.test(text) ? 'P0' : /^P1/.test(text) ? 'P1' : 'P2';
  for (const [gapType, pattern] of gapRules) {
    if (!pattern.test(text)) continue;
    gapRegister.push({
      gap_id: `N075-GAP-${String(gapRegister.length + 1).padStart(4, '0')}`,
      company_id: company.company_id,
      canonical_name: company.canonical_name,
      priority,
      gap_type: gapType,
      original_gap_text: text,
      required_primary_evidence: evidenceNeeded[gapType],
      source_profile_status: company.source_profile_status,
      formal_output_status: company.formal_output_status,
      current_status: 'OPEN_RETAINED_GAP',
      claim_strength_ceiling: 'PENDING_VERIFICATION_MAX_NO_UPGRADE',
      formal_pool_effect: 'NO_AUTOMATIC_FORMAL_POOL_CHANGE',
      evidence_strength_effect: 'NO_UPGRADE',
      sprint_priority: priority === 'P0' ? 'FIRST' : priority === 'P1' ? 'SECOND' : 'BACKLOG'
    });
  }
}
 
const semanticDecisions = {
  'P0DISCR1-CAND-001': ['中鼎股份-星汇传感', 'VERIFIED_COMPANY_DIRECT', 'CUSTOMER|ORDER|MASS_PRODUCTION', '交易所问询回复可直接支持中鼎机器人产品认证、定点与小批量阶段,但不支持大批量订单。'],
  'P0DISCR1-CAND-002': ['', 'VERIFIED_INDUSTRY_CONTEXT_NOT_COMPANY_FACT', 'CUSTOMER', '政府采购内容支持认证与测试基础设施存在,不能绑定具体机器人企业客户关系。'],
  'P0DISCR1-CAND-003': ['', 'VERIFIED_INDUSTRY_CONTEXT_NOT_COMPANY_FACT', 'ORDER', '教学采购文件中的BOM与订单流程只可作为方法背景,不能作为产业链BOM或企业订单。'],
  'P0DISCR1-CAND-004': ['Universal Robots', 'VERIFIED_COMPANY_PRODUCT_PROCUREMENT', 'ORDER|MASS_PRODUCTION', '官方中标公告可支持UR12e与夹爪采购事实;合同主体与金额边界按公告保留。'],
  'P0DISCR1-CAND-005': ['埃夫特', 'VERIFIED_COMPANY_DIRECT', 'REVENUE_PROFIT', '交易所半年度报告可直接支持机器人业务收入占比和毛利率,但时点为2024年上半年。'],
  'P0DISCR1-CAND-006': ['', 'VERIFIED_EXTERNAL_COMPANY_UNMAPPED', 'REVENUE_PROFIT', 'Serve Robotics年报是公司直接经营口径,但该公司当前不在307家主表中,先保留为扩展候选。'],
  'P0DISCR1-CAND-007': ['', 'VERIFIED_MARKET_PRIMARY', 'REVENUE_PROFIT', 'IFR原始报告入口可支持市场安装与销量口径,不映射为单家公司收入或销量。'],
  'P0DISCR1-CAND-008': ['', 'VERIFIED_MARKET_FORECAST_PRIMARY', 'REVENUE_PROFIT', 'Omdia官方预测可支持人形出货预测口径,不能替代实际出货或企业业绩。'],
  'P0DISCR1-CAND-009': ['小米集团', 'VERIFIED_COMPANY_TECHNICAL_PRIMARY', '', '小米机器人官方技术页可支持模型与数据规模的公司自述,不支持客户、订单或收入,因此不计入本轮七类商业缺口回补。'],
  'P0DISCR1-CAND-010': ['', 'VERIFIED_TECHNICAL_CONTEXT_NOT_COMPANY_FACT', '', '欧盟项目报告可支持工业机器人模型集成研究,不映射为OEM销售或客户采购,因此不计入本轮七类商业缺口回补。'],
  'P0DISCR1-CAND-011': ['', 'VERIFIED_PRODUCT_PRIMARY_UNMAPPED', '', '华为云产品文档可支持CloudRobo能力;华为不在当前307家主表,也不计入本轮七类商业缺口回补。'],
  'P0DISCR1-CAND-012': ['Universal Robots', 'DUPLICATE_SOURCE_REUSED_FOR_PRODUCT_GAP', 'ORDER|MASS_PRODUCTION', '与CAND-004为同一采购公告,语义可复用但不计为第二个独立来源。'],
  'P0DISCR1-CAND-013': ['', 'VERIFIED_PROCUREMENT_PRIMARY_UNNAMED_SUPPLIER', 'ORDER|MASS_PRODUCTION', '开标信息支持数量与交付要求,但不能在未中标绑定时归入具体机器人企业。'],
  'P0DISCR1-CAND-014': ['', 'VERIFIED_PROCUREMENT_PRIMARY_NO_AWARD', 'ORDER', '框架采购入口支持需求存在,但未形成具体供应商订单或收入。']
};
 
const candidateReview = candidates.map((candidate) => {
  const [mappedName, disposition, gapTypes, rationale] = semanticDecisions[candidate.candidate_id] || ['', 'UNREVIEWED', '', ''];
  const mappedCompany = mappedName ? companyByName.get(mappedName) : null;
  return {
    review_id: `N075-SEM-${candidate.candidate_id.slice(-3)}`,
    candidate_id: candidate.candidate_id,
    query_id: candidate.query_id,
    conceptual_gap_key: candidate.conceptual_gap_key,
    source_class: candidate.source_class,
    publisher: candidate.publisher,
    title: candidate.title,
    canonical_url: candidate.canonical_url,
    original_relevance_status: candidate.relevance_status,
    mapped_company_id: mappedCompany?.company_id || '',
    mapped_company_name: mappedName,
    gap_types_addressed: gapTypes,
    semantic_verification_result: disposition,
    semantic_rationale: rationale,
    primary_source_status_after: disposition === 'DUPLICATE_SOURCE_REUSED_FOR_PRODUCT_GAP' ? 'PRIMARY_SOURCE_DUPLICATE' : 'PRIMARY_SOURCE_SEMANTICALLY_REVIEWED',
    gap_effect: 'GAP_REMAINS_OPEN_CONTENT_SCOPE_NARROWED_ONLY',
    claim_strength_after: 'PENDING_VERIFICATION_MAX_NO_UPGRADE',
    formal_pool_effect: 'NO_AUTOMATIC_FORMAL_POOL_CHANGE',
    evidence_strength_effect: 'NO_UPGRADE',
    reviewed_at: `${AS_OF}T16:30:00+08:00`
  };
});
 
function companyId(name) {
  const row = companyByName.get(name);
  if (!row) throw new Error(`UNRESOLVED_COMPANY:${name}`);
  return row.company_id;
}
 
const sourceBacktrace = [
  {
    source_update_id: 'N075-SRC-001', company_id: companyId('中鼎股份-星汇传感'), canonical_name: '中鼎股份-星汇传感', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'CUSTOMER|ORDER|MASS_PRODUCTION',
    source_class: 'EXCHANGE_OR_REGULATOR_FILING', publisher: '深圳证券交易所', source_title: '中鼎股份可转债发行审核问询回复', publication_date: '2026-05-19', canonical_url: 'https://disc.static.szse.cn/download/disc/disk03/finalpage/2026-05-19/26188182-330f-4372-ba4a-d34aef7643c3.PDF', source_locator: 'web.open:turn179view0#L2944-L2998',
    content_update_text: '中鼎披露:谐波减速器已完成认证并进入小批量供货,传感器已通过验证并取得定点,关节总成处客户验证,本体完成首台样机;文件还说明客户A发出定点通知、傅利叶签署战略合作协议。', limitation: '未披露批量订单规模;定点、战略协议、小批量供货和批量供货必须分层。'
  },
  {
    source_update_id: 'N075-SRC-002', company_id: companyId('中鼎股份-星汇传感'), canonical_name: '中鼎股份-星汇传感', target_field: 'capacity_delivery_and_procurement', gap_types_addressed: 'CAPACITY|MASS_PRODUCTION',
    source_class: 'EXCHANGE_OR_REGULATOR_FILING', publisher: '深圳证券交易所', source_title: '中鼎股份可转债发行审核问询回复', publication_date: '2026-05-19', canonical_url: 'https://disc.static.szse.cn/download/disc/disk03/finalpage/2026-05-19/26188182-330f-4372-ba4a-d34aef7643c3.PDF', source_locator: 'web.open:turn179view0#L3002-L3073',
    content_update_text: '募投文件规划传感器15万台/年、谐波减速器15万台/年、关节合计28万个/年、控制硬件及线束3万套/年、软件授权与服务400套/年,以及人形与双足本体合计2万台/年。', limitation: '全部为项目达产规划,不等于当前产能、产量或实际订单消化。'
  },
  {
    source_update_id: 'N075-SRC-003', company_id: companyId('埃夫特'), canonical_name: '埃夫特', target_field: 'revenue_and_operational_metric', gap_types_addressed: 'REVENUE_PROFIT',
    source_class: 'EXCHANGE_OR_REGULATOR_FILING', publisher: '上海证券交易所', source_title: '埃夫特2024年半年度报告', publication_date: '2024-08-30', canonical_url: 'https://big5.sse.com.cn/site/cht/www.sse.com.cn/disclosure/listedinfo/announcement/c/new/2024-08-30/688165_20240830_7UCG.pdf', source_locator: 'N040:P0DISCR1-CAND-005',
    content_update_text: '埃夫特2024年半年度报告披露机器人业务占营业收入58.81%、系统集成业务占39.77%,机器人业务毛利率18.77%。', limitation: '为2024年上半年口径,不代表2025/2026最新经营状态,也不是人形机器人专项收入。'
  },
  {
    source_update_id: 'N075-SRC-004', company_id: companyId('FANUC'), canonical_name: 'FANUC', target_field: 'revenue_and_operational_metric', gap_types_addressed: 'REVENUE_PROFIT',
    source_class: 'COMPANY_IR_OR_ANNUAL_REPORT', publisher: 'FANUC Corporation', source_title: 'Financial Results for the Year Ended March 31, 2026', publication_date: '2026-04-24', canonical_url: 'https://www.fanuc.co.jp/en/ir/announce/pdf/2026/financialresult202603_e.pdf', source_locator: 'web.open:turn179view2#L282-L301',
    content_update_text: 'FANUC截至2026年3月的财年Robot分部销售额为3,786.10亿日元,同比增长14.9%,占集团净销售额44.1%;中国EV及一般工业需求较强,美洲销售同比提高。', limitation: '文件未在该分部口径披露营业利润、销量或ASP。'
  },
  {
    source_update_id: 'N075-SRC-005', company_id: companyId('FANUC'), canonical_name: 'FANUC', target_field: 'customer_and_commercial_relationship', gap_types_addressed: 'ORDER',
    source_class: 'COMPANY_IR_OR_ANNUAL_REPORT', publisher: 'FANUC Corporation', source_title: 'FY2025 Q3 Financial Results Q&A', publication_date: '2026-01-26', canonical_url: 'https://www.fanuc.co.jp/en/ir/announce/pdf/2026/qasummary202512_e.pdf', source_locator: 'web.open:turn182view0#L9-L18',
    content_update_text: 'FANUC披露Physical AI开放平台发布后收到超过1,000台CRX系列订单并计入三季度订单,另有数千台项目询盘。', limitation: '询盘不是订单;已披露订单未给出客户、金额、交付与收入确认。'
  },
  {
    source_update_id: 'N075-SRC-006', company_id: companyId('Yaskawa'), canonical_name: 'Yaskawa', target_field: 'revenue_and_operational_metric', gap_types_addressed: 'REVENUE_PROFIT',
    source_class: 'COMPANY_IR_OR_ANNUAL_REPORT', publisher: 'Yaskawa Electric Corporation', source_title: 'Results Briefing for FY2025', publication_date: '2026-04-10', canonical_url: 'https://www.yaskawa-global.com/wp-content/uploads/2026/04/20260410_haifu_en.pdf', source_locator: 'web.open:turn179view3#L143-L163',
    content_update_text: 'Yaskawa FY2025 Robotics收入2,470亿日元,同比增长4.0%;营业利润204亿日元,同比下降14.0%,营业利润率8.3%,低于上年的10.0%。', limitation: 'Robotics分部包含工业机器人等多类业务,不是具身/人形机器人专项收入。'
  },
  {
    source_update_id: 'N075-SRC-007', company_id: companyId('KUKA'), canonical_name: 'KUKA', target_field: 'revenue_and_operational_metric', gap_types_addressed: 'ORDER|REVENUE_PROFIT',
    source_class: 'COMPANY_IR_OR_ANNUAL_REPORT', publisher: 'KUKA Group', source_title: 'KUKA Group Annual Report 2025', publication_date: '2026-03-01', canonical_url: 'https://www.kuka.com/-/media/kuka-corporate/documents/ir/reports-and-presentations/en/annual-report/annual-report-2025.pdf', source_locator: 'Annual Report 2025 p.5,p.17,p.22; web.open:turn185view2#L86-L93; web.open:turn185view3#L728-L756',
    content_update_text: 'KUKA集团2025年订单41.573亿欧元、收入38.972亿欧元、EBIT率1.5%,期末订单积压32.728亿欧元;收入同比增长4.4%,利润率受重组费用等影响下降。', limitation: '为KUKA集团口径,包含机器人、系统和Swisslog等业务,不能当作机器人单体销量或利润。'
  },
  {
    source_update_id: 'N075-SRC-008', company_id: companyId('Universal Robots'), canonical_name: 'Universal Robots', target_field: 'revenue_and_operational_metric', gap_types_addressed: 'MASS_PRODUCTION|REVENUE_PROFIT',
    source_class: 'REGULATOR_FILING', publisher: 'U.S. SEC / Teradyne', source_title: 'Teradyne 2025 Form 10-K', publication_date: '2026-02-19', canonical_url: 'https://www.sec.gov/Archives/edgar/data/97210/000119312526059002/ter-20251231.htm', source_locator: 'web.search:turn180search0',
    content_update_text: 'Teradyne 2025年Robotics分部收入3.083亿美元,同比下降15.5%,主要因协作机器人手臂和AMR销售减少;文件同时披露Universal Robots累计售出超过11万台协作机器人。', limitation: 'Robotics分部还包含MiR,不能据此拆出UR单体收入、利润、年度销量或ASP。'
  }
].map((row) => ({
  ...row,
  source_fact_status: 'PRIMARY_SOURCE_CONTENT_CAPTURED_PENDING_VERIFICATION_NO_UPGRADE',
  gap_effect: 'OPEN_RETAINED_GAP_PARTIALLY_NARROWED',
  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}T16:35: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 candidateByCompany = new Map();
for (const review of candidateReview) {
  if (!review.mapped_company_id) continue;
  if (!candidateByCompany.has(review.mapped_company_id)) candidateByCompany.set(review.mapped_company_id, []);
  candidateByCompany.get(review.mapped_company_id).push(review.candidate_id);
}
const gapByCompany = new Map();
for (const gap of gapRegister) {
  if (!gapByCompany.has(gap.company_id)) gapByCompany.set(gap.company_id, []);
  gapByCompany.get(gap.company_id).push(gap);
}
 
const v3 = 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;
    next[field] = [normalize(row[field]), `【N075主源回补】${additions.map((update) => `〔${update.source_update_id}〕${update.content_update_text}(边界:${update.limitation})`).join(';')}`].filter(Boolean).join(';');
  }
  const gaps = gapByCompany.get(row.company_id) || [];
  next.n075_high_value_gap_count = String(gaps.length);
  next.n075_p0_gap_count = String(gaps.filter((gap) => gap.priority === 'P0').length);
  next.n075_primary_source_update_ids = updates.map((update) => update.source_update_id).join('|');
  next.n075_candidate_semantic_ids = candidateByCompany.get(row.company_id)?.join('|') || '';
  next.n075_content_update_status = updates.length ? 'PRIMARY_SOURCE_CONTENT_ENRICHED_GAP_REMAINS_OPEN_NO_UPGRADE' : 'NO_N075_PRIMARY_SOURCE_UPDATE';
  next.content_update_batch = updates.length ? unique(String(row.content_update_batch || '').split('|').concat('NEXT-ROBOT-075')).join('|') : row.content_update_batch;
  next.as_of = AS_OF;
  return next;
});
 
const gapColumns = ['gap_id','company_id','canonical_name','priority','gap_type','original_gap_text','required_primary_evidence','source_profile_status','formal_output_status','current_status','claim_strength_ceiling','formal_pool_effect','evidence_strength_effect','sprint_priority'];
const candidateColumns = ['review_id','candidate_id','query_id','conceptual_gap_key','source_class','publisher','title','canonical_url','original_relevance_status','mapped_company_id','mapped_company_name','gap_types_addressed','semantic_verification_result','semantic_rationale','primary_source_status_after','gap_effect','claim_strength_after','formal_pool_effect','evidence_strength_effect','reviewed_at'];
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 v3Columns = unique(Object.keys(companies[0]).concat(['n075_high_value_gap_count','n075_p0_gap_count','n075_primary_source_update_ids','n075_candidate_semantic_ids','n075_content_update_status']));
 
writeCsv(PATHS.gapRegister, gapRegister, gapColumns);
writeCsv(PATHS.candidateReview, candidateReview, candidateColumns);
writeCsv(PATHS.sourceBacktrace, sourceBacktrace, sourceColumns);
writeCsv(PATHS.v3, v3, v3Columns);
 
const categoryCounts = Object.fromEntries(gapRules.map(([gapType]) => [gapType, gapRegister.filter((row) => row.gap_type === gapType).length]));
const p0GapCount = gapRegister.filter((row) => row.priority === 'P0').length;
const affectedCompanies = new Set(sourceBacktrace.map((row) => row.company_id));
const dispositionCounts = candidateReview.reduce((acc, row) => {
  acc[row.semantic_verification_result] = (acc[row.semantic_verification_result] || 0) + 1;
  return acc;
}, {});
 
const summary = [];
summary.push('# 机器人企业高价值缺口集中回源(第一轮)');
summary.push('');
summary.push(`> 更新日期:${AS_OF}`);
summary.push('> 原则:按客户、订单、量产、产能、ASP、寿命、收入与利润合并去重;只将具体企业主源写入企业表,行业背景不冒充公司事实。');
summary.push('');
summary.push('## 1. 缺口全集');
summary.push('');
summary.push(`- 307家公司中263家存在七类高价值缺口,共${gapRegister.length}个“公司×缺口类型”组合。`);
summary.push(`- P0公司83家,对应P0缺口组合${p0GapCount}个;本轮先集中处理能用公开主源直接回补的对象。`);
summary.push('');
summary.push('| 缺口类型 | 公司数 | 回源目标 |');
summary.push('|---|---:|---|');
for (const [gapType] of gapRules) summary.push(`| ${gapType} | ${categoryCounts[gapType]} | ${evidenceNeeded[gapType]} |`);
summary.push('');
summary.push('## 2. 旧P0候选语义核验');
summary.push('');
summary.push(`- 已对N040留下的14条主源候选逐条完成语义归类;没有把采购制度、认证规则或研究项目直接写成企业订单/收入。`);
summary.push(`- 结果分布:${Object.entries(dispositionCounts).map(([key, value]) => `${key}=${value}`).join(',')}。`);
summary.push('');
summary.push('| 候选 | 归属企业 | 核验结果 | 核心判断 |');
summary.push('|---|---|---|---|');
for (const row of candidateReview) summary.push(`| ${row.candidate_id} | ${md(row.mapped_company_name || '不映射具体企业')} | ${row.semantic_verification_result} | ${md(row.semantic_rationale)} |`);
summary.push('');
summary.push('## 3. 首批企业主源回补');
summary.push('');
summary.push(`- 新增${sourceBacktrace.length}条主源字段更新,落入${affectedCompanies.size}家公司;所有更新仍保留待核验、无自动入池、无证据升级。`);
summary.push('');
summary.push('| 企业 | 字段 | 主源事实 | 保留边界 |');
summary.push('|---|---|---|---|');
for (const row of sourceBacktrace) summary.push(`| ${md(row.canonical_name)} | ${row.target_field} | ${md(row.content_update_text)} | ${md(row.limitation)} |`);
summary.push('');
summary.push('## 4. 实用结论');
summary.push('');
summary.push('- 中鼎:认证、定点、小批量供货和募投达产规划已分层写清,不能把规划产能写成当前产量。');
summary.push('- FANUC、Yaskawa、KUKA、Teradyne Robotics:最新分部或集团经营数据已进入统一企业表;其中KUKA与Teradyne仍不是单一机器人产品/UR单体口径。');
summary.push('- 埃夫特:补入交易所披露的机器人业务结构和毛利率,但保留2024年上半年时点。');
summary.push('- 后续优先级:继续处理剩余P0公司,先找客户侧/交易所/年报能一次关闭多个字段的来源;无法找到的直接保留缺口,不用二手转述填空。');
summary.push('');
summary.push('## 5. 数据入口');
summary.push('');
summary.push('- 高价值缺口登记:`evidence/next_robot_075_high_value_gap_register_20260805.csv`');
summary.push('- P0候选语义核验:`evidence/next_robot_075_p0_candidate_semantic_verification_20260805.csv`');
summary.push('- 主源回溯与字段更新:`evidence/next_robot_075_primary_source_backtrace_20260805.csv`');
summary.push('- 307家公司统一信息表v03:`outputs/数据表/robot_company_information_v03_20260805.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;
for (const row of v3) {
  const before = companyById.get(row.company_id);
  for (const column of immutable) if ((before?.[column] || '') !== (row[column] || '')) immutableMismatch += 1;
}
const candidateById = new Map(candidates.map((row) => [row.candidate_id, row]));
const candidateIdentityMismatch = candidateReview.filter((row) => {
  const source = candidateById.get(row.candidate_id);
  return !source || source.canonical_url !== row.canonical_url || source.query_id !== row.query_id || source.conceptual_gap_key !== row.conceptual_gap_key;
}).length;
const sourceText = readText(PATHS.sourceBacktrace);
const v2IdentityAfter = { bytes: fs.statSync(PATHS.v2).size, sha256: shaFile(PATHS.v2) };
 
check('N075-VAL-001', 'company v02 row count', 307, companies.length);
check('N075-VAL-002', 'company v02 id uniqueness', 307, new Set(companies.map((row) => row.company_id)).size);
check('N075-VAL-003', 'companies with seven-class high-value gaps', 263, new Set(gapRegister.map((row) => row.company_id)).size);
check('N075-VAL-004', 'company-gap pair count', 891, gapRegister.length);
check('N075-VAL-005', 'P0 company count', 83, new Set(gapRegister.filter((row) => row.priority === 'P0').map((row) => row.company_id)).size);
check('N075-VAL-006', 'P0 company-gap pair count', 378, p0GapCount);
check('N075-VAL-007', 'customer gap company count', 174, categoryCounts.CUSTOMER);
check('N075-VAL-008', 'order gap company count', 98, categoryCounts.ORDER);
check('N075-VAL-009', 'mass-production gap company count', 125, categoryCounts.MASS_PRODUCTION);
check('N075-VAL-010', 'capacity gap company count', 92, categoryCounts.CAPACITY);
check('N075-VAL-011', 'ASP gap company count', 135, categoryCounts.ASP);
check('N075-VAL-012', 'lifetime gap company count', 100, categoryCounts.LIFETIME);
check('N075-VAL-013', 'revenue-profit gap company count', 167, categoryCounts.REVENUE_PROFIT);
check('N075-VAL-014', 'N040 candidate review row count', 14, candidateReview.length);
check('N075-VAL-015', 'candidate review id uniqueness', 14, new Set(candidateReview.map((row) => row.candidate_id)).size);
check('N075-VAL-016', 'candidate authority identity mismatch', 0, candidateIdentityMismatch);
check('N075-VAL-017', 'mapped candidate company ids unresolved', 0, candidateReview.filter((row) => row.mapped_company_id && !companyById.has(row.mapped_company_id)).length);
check('N075-VAL-018', 'candidate semantic result missing', 0, candidateReview.filter((row) => !row.semantic_verification_result || row.semantic_verification_result === 'UNREVIEWED').length);
check('N075-VAL-019', 'primary source backtrace row count', 8, sourceBacktrace.length);
check('N075-VAL-020', 'primary source update id uniqueness', 8, new Set(sourceBacktrace.map((row) => row.source_update_id)).size);
check('N075-VAL-021', 'primary source company ids unresolved', 0, sourceBacktrace.filter((row) => !companyById.has(row.company_id)).length);
check('N075-VAL-022', 'primary 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('N075-VAL-023', 'company v03 row count', 307, v3.length);
check('N075-VAL-024', 'company v03 id uniqueness', 307, new Set(v3.map((row) => row.company_id)).size);
check('N075-VAL-025', 'immutable company field mismatch', 0, immutableMismatch);
check('N075-VAL-026', 'v03 affected company count', 6, v3.filter((row) => row.n075_content_update_status === 'PRIMARY_SOURCE_CONTENT_ENRICHED_GAP_REMAINS_OPEN_NO_UPGRADE').length);
check('N075-VAL-027', 'source update ids missing from v03', 0, sourceBacktrace.filter((update) => !v3.find((row) => row.company_id === update.company_id)?.n075_primary_source_update_ids.includes(update.source_update_id)).length);
check('N075-VAL-028', 'formal pool boundary drift', 0, v3.filter((row) => row.formal_pool_effect !== 'NO_AUTOMATIC_FORMAL_POOL_CHANGE').length);
check('N075-VAL-029', 'source backtrace forbidden upgrade literal count', 0, (sourceText.match(/FORMAL_POOL_APPROVED|EVIDENCE_UPGRADED|GAP_CLOSED/g) || []).length);
check('N075-VAL-030', 'v02 bytes unchanged', v2IdentityBefore.bytes, v2IdentityAfter.bytes);
check('N075-VAL-031', 'v02 sha256 unchanged', v2IdentityBefore.sha256, v2IdentityAfter.sha256);
 
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',
  companies: companies.length,
  companiesWithGaps: new Set(gapRegister.map((row) => row.company_id)).size,
  companyGapPairs: gapRegister.length,
  p0Companies: new Set(gapRegister.filter((row) => row.priority === 'P0').map((row) => row.company_id)).size,
  p0CompanyGapPairs: p0GapCount,
  candidateReviews: candidateReview.length,
  primarySourceUpdates: sourceBacktrace.length,
  enrichedCompanies: affectedCompanies.size,
  validationPass: validation.filter((row) => row.status === 'PASS').length,
  validationFail: failed.length,
  outputs: Object.values(PATHS).filter((value) => value !== PATHS.v2 && value !== PATHS.candidates)
}, null, 2));
if (failed.length) process.exitCode = 1;