import fs from 'node:fs';
|
import path from 'node:path';
|
import crypto from 'node:crypto';
|
|
const REPO_ROOT = process.cwd();
|
const CASE_ROOT = path.join(
|
REPO_ROOT,
|
'ana-data',
|
'cases',
|
'机器人案例',
|
'ANA-ROBOT-INDUSTRY-001'
|
);
|
const ROBOT_CONTAINER_ROOT = path.join(REPO_ROOT, 'ana-data', 'cases', '机器人案例');
|
const INDUSTRY_ROOT = process.env.INDUSTRY_SOURCE_ROOT || 'G:\\industry';
|
const INDUSTRY_ALIAS = 'industry_source_current';
|
const AS_OF = '2026-08-05';
|
const BOUNDARY =
|
'PENDING_VERIFICATION_MAX_NO_UPGRADE|NO_AUTOMATIC_FORMAL_POOL_CHANGE|CONTENT_ASSIMILATION_ONLY';
|
|
const PATHS = {
|
master: path.join(CASE_ROOT, 'outputs', '数据表', 'robot_company_master.csv'),
|
snapshot: path.join(
|
ROBOT_CONTAINER_ROOT,
|
'manifest',
|
'robot_source_snapshot_CMP-ANA-ROBOT-INDUSTRY-20260723-001.csv'
|
),
|
delta: path.join(
|
ROBOT_CONTAINER_ROOT,
|
'manifest',
|
'robot_source_delta_CMP-ANA-ROBOT-INDUSTRY-20260723-001.csv'
|
),
|
n071: path.join(
|
CASE_ROOT,
|
'evidence',
|
'next_robot_071_remaining231_content_deepening_evidence_20260804.csv'
|
),
|
n072: path.join(
|
CASE_ROOT,
|
'evidence',
|
'next_robot_072_remaining231_content_deepening_quality_corrections_20260804.csv'
|
),
|
coreIndex: path.join(
|
INDUSTRY_ROOT,
|
'data',
|
'public',
|
'robotics',
|
'core_components',
|
'sources_index.json'
|
),
|
coreStructured: path.join(
|
INDUSTRY_ROOT,
|
'data',
|
'public',
|
'robotics',
|
'core_components',
|
'结构化摘录.json'
|
),
|
corePrice: path.join(
|
INDUSTRY_ROOT,
|
'data',
|
'public',
|
'robotics',
|
'core_components',
|
'价格字段候选.json'
|
),
|
dexIndex: path.join(
|
INDUSTRY_ROOT,
|
'data',
|
'public',
|
'robotics',
|
'dexterous_hand',
|
'sources_index.json'
|
),
|
bodyIndex: path.join(
|
INDUSTRY_ROOT,
|
'data',
|
'public',
|
'robotics',
|
'body_oem_supply_chain',
|
'sources_index.json'
|
),
|
bodyHardIndex: path.join(
|
INDUSTRY_ROOT,
|
'data',
|
'public',
|
'robotics',
|
'body_oem_supply_chain',
|
'hard_evidence_20260622',
|
'sources_index.json'
|
),
|
bodySecondIndex: path.join(
|
INDUSTRY_ROOT,
|
'data',
|
'public',
|
'robotics',
|
'body_oem_supply_chain',
|
'second_batch',
|
'sources_index.json'
|
),
|
newsIndex: path.join(
|
INDUSTRY_ROOT,
|
'data',
|
'public',
|
'robotics',
|
'news_rolling_snowball_corrected',
|
'sources_index.json'
|
),
|
reportDir: path.join(
|
INDUSTRY_ROOT,
|
'data',
|
'report',
|
'embodied_intelligence'
|
),
|
upstreamResearch: path.join(INDUSTRY_ROOT, 'doc', 'research'),
|
projectResearch: path.join(
|
REPO_ROOT,
|
'ana-data',
|
'cases',
|
'机器人案例',
|
'raw',
|
'existing_research'
|
),
|
sourceRegistry: path.join(
|
CASE_ROOT,
|
'evidence',
|
'next_robot_073_g_industry_source_registry_20260805.csv'
|
),
|
factRegister: path.join(
|
CASE_ROOT,
|
'evidence',
|
'next_robot_073_g_industry_fact_register_20260805.csv'
|
),
|
entityMapping: path.join(
|
CASE_ROOT,
|
'evidence',
|
'next_robot_073_g_industry_entity_mapping_20260805.csv'
|
),
|
priorityRegister: path.join(
|
CASE_ROOT,
|
'evidence',
|
'next_robot_073_g_industry_priority_register_20260805.csv'
|
),
|
p0Semantic: path.join(
|
CASE_ROOT,
|
'evidence',
|
'next_robot_073_g_industry_p0_semantic_verification_20260805.csv'
|
),
|
companyInfo: path.join(
|
CASE_ROOT,
|
'outputs',
|
'数据表',
|
'robot_company_information_v01_20260805.csv'
|
),
|
summary: path.join(
|
CASE_ROOT,
|
'outputs',
|
'核心文档',
|
'G盘有用信息语义吸收_第一批_20260805.md'
|
),
|
validation: path.join(
|
CASE_ROOT,
|
'manifest',
|
'next_robot_073_g_industry_semantic_assimilation_validation_20260805.csv'
|
)
|
};
|
|
const P0_CORE_IDS = new Set([
|
'CC-028',
|
'CC-029',
|
'CC-030',
|
'CC-031',
|
'CC-032',
|
'CC-033',
|
'CC-034',
|
'CC-035',
|
'CC-036',
|
'CC-053',
|
'CC-058'
|
]);
|
|
const FACT_COLUMNS = [
|
'fact_id',
|
'source_record_id',
|
'source_collection',
|
'original_source_id',
|
'company_id',
|
'company_name',
|
'source_object',
|
'source_title',
|
'source_root_alias',
|
'source_relative_path',
|
'source_locator',
|
'source_document_sha256',
|
'source_text_sha256',
|
'fact_field',
|
'fact_text',
|
'value',
|
'unit',
|
'period',
|
'semantic_disposition',
|
'evidence_grade',
|
'integration_target',
|
'evidence_boundary',
|
'assimilation_status'
|
];
|
|
function fail(message) {
|
throw new Error(message);
|
}
|
|
function shaText(value) {
|
return crypto.createHash('sha256').update(String(value), 'utf8').digest('hex');
|
}
|
|
function shaFile(filePath) {
|
return crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex');
|
}
|
|
function readText(filePath) {
|
return fs.readFileSync(filePath, 'utf8').replace(/^\uFEFF/, '');
|
}
|
|
function readJson(filePath) {
|
return JSON.parse(readText(filePath));
|
}
|
|
function parseCsv(text) {
|
const rows = [];
|
let row = [];
|
let field = '';
|
let quoted = false;
|
for (let index = 0; index < text.length; index += 1) {
|
const char = text[index];
|
if (quoted) {
|
if (char === '"') {
|
if (text[index + 1] === '"') {
|
field += '"';
|
index += 1;
|
} else {
|
quoted = false;
|
}
|
} else {
|
field += char;
|
}
|
} else if (char === '"') {
|
quoted = true;
|
} else if (char === ',') {
|
row.push(field);
|
field = '';
|
} else if (char === '\n') {
|
row.push(field.replace(/\r$/, ''));
|
rows.push(row);
|
row = [];
|
field = '';
|
} else {
|
field += char;
|
}
|
}
|
if (field.length > 0 || row.length > 0) {
|
row.push(field.replace(/\r$/, ''));
|
rows.push(row);
|
}
|
if (rows.length === 0) {
|
return [];
|
}
|
const headers = rows[0];
|
return rows
|
.slice(1)
|
.filter((values) => values.some((value) => value !== ''))
|
.map((values) =>
|
Object.fromEntries(headers.map((header, index) => [header, values[index] || '']))
|
);
|
}
|
|
function readCsv(filePath) {
|
return parseCsv(readText(filePath));
|
}
|
|
function csvCell(value) {
|
const text = value === null || value === undefined ? '' : String(value);
|
return '"' + text.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 normalizeLine(value) {
|
return String(value || '').replace(/\s+/g, ' ').trim();
|
}
|
|
function normalizeName(value) {
|
return String(value || '')
|
.toLowerCase()
|
.replace(/technologies/g, 'technology')
|
.replace(/[()()【】\[\]·•,,.。::;;'"“”‘’\s_\-\/\\]/g, '')
|
.replace(/股份有限公司|有限责任公司|有限公司|控股集团|集团|科技|机器人/g, '');
|
}
|
|
function relFromIndustry(filePath) {
|
return path.relative(INDUSTRY_ROOT, filePath).replace(/\\/g, '/');
|
}
|
|
function fileIdentity(filePath) {
|
if (!filePath || !fs.existsSync(filePath) || !fs.statSync(filePath).isFile()) {
|
return { exists: 'NO', bytes: '', sha256: '', mtime: '' };
|
}
|
const stat = fs.statSync(filePath);
|
return {
|
exists: 'YES',
|
bytes: String(stat.size),
|
sha256: shaFile(filePath),
|
mtime: stat.mtime.toISOString()
|
};
|
}
|
|
function shortId(prefix, value) {
|
return prefix + '-' + shaText(value).slice(0, 24);
|
}
|
|
function arrayValue(value) {
|
if (Array.isArray(value)) {
|
return value;
|
}
|
if (value === null || value === undefined || value === '') {
|
return [];
|
}
|
return [String(value)];
|
}
|
|
function unique(values) {
|
return [...new Set(values.filter(Boolean))];
|
}
|
|
function groupCount(rows, field) {
|
const result = new Map();
|
for (const row of rows) {
|
const key = row[field] || '';
|
result.set(key, (result.get(key) || 0) + 1);
|
}
|
return result;
|
}
|
|
function findWaveEvidenceFiles() {
|
const evidenceDir = path.join(CASE_ROOT, 'evidence');
|
return fs
|
.readdirSync(evidenceDir)
|
.filter((name) => /company_content_wave_.+_evidence_.*\.csv$/i.test(name))
|
.sort()
|
.map((name) => path.join(evidenceDir, name));
|
}
|
|
for (const requiredPath of [
|
PATHS.master,
|
PATHS.snapshot,
|
PATHS.delta,
|
PATHS.n071,
|
PATHS.n072,
|
PATHS.coreIndex,
|
PATHS.coreStructured,
|
PATHS.corePrice,
|
PATHS.dexIndex,
|
PATHS.bodyIndex,
|
PATHS.bodyHardIndex,
|
PATHS.bodySecondIndex,
|
PATHS.newsIndex,
|
PATHS.reportDir
|
]) {
|
if (!fs.existsSync(requiredPath)) {
|
fail('REQUIRED_INPUT_MISSING:' + requiredPath);
|
}
|
}
|
|
const canonicalMasterBefore = fileIdentity(PATHS.master);
|
const master = readCsv(PATHS.master);
|
const deltaRows = readCsv(PATHS.delta);
|
const snapshotRows = readCsv(PATHS.snapshot);
|
const n071Rows = readCsv(PATHS.n071);
|
const n072Rows = readCsv(PATHS.n072);
|
const masterById = new Map(master.map((row) => [row.company_id, row]));
|
const masterBySourcePath = new Map(
|
master.map((row) => [String(row.source_relative_path || '').replace(/\\/g, '/'), row])
|
);
|
const companyVariants = new Map();
|
|
for (const company of master) {
|
const variants = [
|
company.canonical_name,
|
company.source_company_name,
|
...(() => {
|
try {
|
return JSON.parse(company.aliases || '[]');
|
} catch {
|
return [];
|
}
|
})()
|
];
|
for (const variant of variants) {
|
const key = normalizeName(variant);
|
if (key && !companyVariants.has(key)) {
|
companyVariants.set(key, company);
|
}
|
}
|
}
|
|
function companyByCanonical(name) {
|
return master.find((row) => row.canonical_name === name) || null;
|
}
|
|
const multiObjectRules = [
|
['优必选', ['优必选']],
|
['智元机器人', ['智元机器人']],
|
['宇树科技', ['宇树科技']],
|
['Tesla Optimus', ['Tesla Optimus']],
|
['Figure AI', ['Figure AI']],
|
['Agility Robotics', ['Agility Robotics']],
|
['海康威视 / 海康机器人', ['海康威视-海康机器人']],
|
['Universal Robots / Teradyne', ['Universal Robots']],
|
['中鼎股份 / 星汇传感', ['中鼎股份-星汇传感']],
|
['江苏雷利 / 鼎智科技 / 智元', ['江苏雷利-鼎智科技', '智元机器人']],
|
['五洲 / 贝斯特 / 北特 / 恒立', ['五洲新春', '贝斯特', '北特科技', '恒立液压']],
|
['拓普 / 三花 / 银轮 / 英搏尔', ['拓普集团', '三花智控', '银轮股份', '英搏尔']],
|
['苏州能斯达', ['汉威科技-苏州能斯达']],
|
['中鼎股份', ['中鼎股份-星汇传感']],
|
['江苏雷利', ['江苏雷利-鼎智科技']],
|
['鼎智科技', ['江苏雷利-鼎智科技']],
|
['奥普光电', ['奥普光电-长春禹衡光学']],
|
['豪威集团', ['韦尔股份-豪威科技']],
|
['宝武镁业', ['宝武镁业-云海金属']],
|
['小鹏机器人', ['小鹏汽车']],
|
['因时机器人', ['因时机器人']],
|
['灵心巧手', ['灵心巧手']],
|
['帕西尼', ['帕西尼']],
|
['他山科技', ['他山科技']],
|
['坤维科技', ['坤维科技']],
|
['星汇传感', ['星汇传感']],
|
['雷赛智能', ['雷赛智能']]
|
];
|
|
function mapObjectToCompanies(objectName) {
|
const text = String(objectName || '').trim();
|
if (!text) {
|
return [];
|
}
|
if (text.includes('高校/科研采购样本')) {
|
return [];
|
}
|
for (const [needle, names] of multiObjectRules) {
|
if (text.includes(needle)) {
|
return names.map(companyByCanonical).filter(Boolean);
|
}
|
}
|
const candidates = unique([
|
text,
|
text.split('/')[0].trim(),
|
text.split('/')[0].trim(),
|
text.replace(/\b(RH56BFX|RH56DFTP|RH56|DexH13 GEN2|DexH13|L20|O6|T10|T20)\b/gi, '').trim(),
|
text.replace(/产品矩阵|产品中心|官网|公司信息梳理/g, '').trim()
|
]);
|
for (const candidate of candidates) {
|
const exact = companyVariants.get(normalizeName(candidate));
|
if (exact) {
|
return [exact];
|
}
|
}
|
for (const company of master) {
|
if (
|
text.includes(company.canonical_name) ||
|
(company.source_company_name && text.includes(company.source_company_name))
|
) {
|
return [company];
|
}
|
}
|
return [];
|
}
|
|
function mappingStatus(objectName, companies, collection) {
|
if (companies.length > 1) {
|
return 'MULTI_COMPANY_MAPPING';
|
}
|
if (companies.length === 1) {
|
if (String(objectName).includes('小鹏机器人')) {
|
return 'ALIAS_TO_EXISTING_COMPANY';
|
}
|
return 'MAPPED_TO_EXISTING_COMPANY';
|
}
|
if (['中欣氟材', '兴福新材'].includes(String(objectName))) {
|
return 'NEW_COMPANY_CANDIDATE';
|
}
|
if (String(objectName).includes('高校/科研采购样本')) {
|
return 'EVIDENCE_OBJECT_NOT_COMPANY';
|
}
|
if (String(objectName).includes('人形机器人 / 五指灵巧手')) {
|
return 'EVIDENCE_OBJECT_NOT_COMPANY';
|
}
|
if (
|
['NEWS_ROLLING_SNOWBALL', 'EMBODIED_INTELLIGENCE_REPORTS'].includes(collection) ||
|
(collection === 'CHANGED_RESEARCH_DOCUMENT' && companies.length === 0)
|
) {
|
return 'TOPIC_OR_GENERAL_SOURCE';
|
}
|
return 'UNMAPPED_REQUIRES_ENTITY_REVIEW';
|
}
|
|
const sourceRegistry = [];
|
const entityMappings = [];
|
const registryLookup = new Map();
|
|
function addSource(input) {
|
const identity = fileIdentity(input.physicalPath);
|
const companies = input.companies || mapObjectToCompanies(input.objectName);
|
const status = mappingStatus(input.objectName, companies, input.collection);
|
const sourceRecordId = 'N073-SRC-' + String(sourceRegistry.length + 1).padStart(4, '0');
|
const row = {
|
source_record_id: sourceRecordId,
|
source_collection: input.collection,
|
original_source_id: input.originalId,
|
source_root_alias: INDUSTRY_ALIAS,
|
source_relative_path: input.relativePath || relFromIndustry(input.physicalPath),
|
source_locator: input.locator || '',
|
source_object: input.objectName || '',
|
mapped_company_ids: companies.map((company) => company.company_id).join('|'),
|
mapped_company_names: companies.map((company) => company.canonical_name).join('|'),
|
mapping_status: status,
|
title: input.title || '',
|
source_type: input.sourceType || '',
|
publisher: input.publisher || '',
|
source_date: input.sourceDate || '',
|
source_url: input.url || '',
|
evidence_grade: input.evidenceGrade || '',
|
physical_file_exists: identity.exists,
|
physical_file_sha256: identity.sha256,
|
physical_file_bytes: identity.bytes,
|
physical_file_mtime: identity.mtime,
|
extracted_relative_path: input.extractedRelativePath || '',
|
extraction_status: input.extractionStatus || '',
|
semantic_use_scope: input.semanticUseScope || '',
|
comparison_status: input.comparisonStatus || '',
|
evidence_boundary: BOUNDARY,
|
assimilation_status: input.assimilationStatus || 'SOURCE_REGISTERED_PENDING_FACT_REVIEW',
|
notes: input.notes || ''
|
};
|
sourceRegistry.push(row);
|
const exactKey = [input.collection, input.originalId, input.objectName || '', input.title || ''].join('|');
|
registryLookup.set(exactKey, row);
|
const simpleKey = [input.collection, input.originalId].join('|');
|
if (!registryLookup.has(simpleKey)) {
|
registryLookup.set(simpleKey, row);
|
}
|
entityMappings.push({
|
object_mapping_id: shortId('N073-MAP', exactKey),
|
source_record_id: sourceRecordId,
|
source_collection: input.collection,
|
original_source_id: input.originalId,
|
source_object: input.objectName || '',
|
mapping_status: status,
|
mapped_company_ids: companies.map((company) => company.company_id).join('|'),
|
mapped_company_names: companies.map((company) => company.canonical_name).join('|'),
|
source_role:
|
status === 'EVIDENCE_OBJECT_NOT_COMPANY'
|
? 'NON_COMPANY_EVIDENCE_OBJECT'
|
: status === 'TOPIC_OR_GENERAL_SOURCE'
|
? 'TOPIC_OR_GENERAL_CONTEXT'
|
: 'COMPANY_OR_COMPANY_GROUP',
|
unresolved_reason:
|
status === 'NEW_COMPANY_CANDIDATE'
|
? 'G_SOURCE_HAS_STANDALONE_COMPANY_OBJECT_NOT_PRESENT_IN_307_MASTER'
|
: status === 'UNMAPPED_REQUIRES_ENTITY_REVIEW'
|
? 'NO_EXACT_CANONICAL_OR_ALIAS_MATCH'
|
: '',
|
recommended_action:
|
status === 'NEW_COMPANY_CANDIDATE'
|
? 'CREATE_VERSIONED_COMPANY_CANDIDATE_RECORD_BEFORE_ANY_CANONICAL_CUTOVER'
|
: status === 'UNMAPPED_REQUIRES_ENTITY_REVIEW'
|
? 'MANUAL_ENTITY_RESOLUTION'
|
: status === 'EVIDENCE_OBJECT_NOT_COMPANY'
|
? 'KEEP_AS_EVIDENCE_OBJECT_DO_NOT_ADD_TO_COMPANY_MASTER'
|
: 'USE_EXISTING_COMPANY_ID',
|
evidence_boundary: BOUNDARY
|
});
|
return row;
|
}
|
|
const coreIndex = readJson(PATHS.coreIndex);
|
for (const [index, item] of coreIndex.entries()) {
|
const root = path.dirname(PATHS.coreIndex);
|
const extractedPath = item.extractedFile ? path.join(root, item.extractedFile) : '';
|
const localPath = item.localFile ? path.join(root, item.localFile) : '';
|
const physicalPath = extractedPath && fs.existsSync(extractedPath) ? extractedPath : localPath;
|
addSource({
|
collection: 'CORE_COMPONENTS',
|
originalId: String(item.id || 'CORE-' + String(index + 1).padStart(3, '0')),
|
objectName: item.object,
|
title: item.title,
|
sourceType: item.type,
|
publisher: item.source,
|
sourceDate: item.sourceDate || item.downloadDate,
|
url: item.url || item.finalUrl,
|
evidenceGrade: item.evidenceGrade || item.strength,
|
physicalPath,
|
relativePath: relFromIndustry(physicalPath),
|
locator: 'sources_index.json item ' + String(index + 1),
|
extractedRelativePath: item.extractedFile
|
? 'data/public/robotics/core_components/' + item.extractedFile.replace(/\\/g, '/')
|
: '',
|
extractionStatus: item.extractedFile ? 'EXTRACTED_LOCAL_TEXT_AVAILABLE' : 'LOCAL_SOURCE_FILE_ONLY',
|
semanticUseScope: arrayValue(item.fields).join('|'),
|
assimilationStatus: 'SOURCE_REGISTERED_AND_STRUCTURED_EXTRACTION_LINKED',
|
notes: item.note || ''
|
});
|
}
|
|
const dexIndex = readJson(PATHS.dexIndex);
|
for (const [index, item] of dexIndex.entries()) {
|
const root = path.dirname(PATHS.dexIndex);
|
const markdownPath = item.markdownFile ? path.join(root, item.markdownFile) : '';
|
const rawPath = item.rawFile ? path.join(root, item.rawFile) : '';
|
const physicalPath = markdownPath && fs.existsSync(markdownPath) ? markdownPath : rawPath;
|
addSource({
|
collection: 'DEXTEROUS_HAND',
|
originalId: 'DEX-' + String(item.id).padStart(3, '0'),
|
objectName: item.object,
|
title: item.title,
|
sourceType: item.type,
|
publisher: item.source,
|
sourceDate: item.sourceDate || item.downloadDate,
|
url: item.url || item.finalUrl,
|
evidenceGrade: item.strength,
|
physicalPath,
|
relativePath: relFromIndustry(physicalPath),
|
locator: 'sources_index.json item ' + String(index + 1),
|
extractedRelativePath: item.markdownFile
|
? 'data/public/robotics/dexterous_hand/' + item.markdownFile.replace(/\\/g, '/')
|
: '',
|
extractionStatus: item.markdownFile ? 'MARKDOWN_AVAILABLE' : 'RAW_ONLY',
|
semanticUseScope: arrayValue(item.fields).join('|'),
|
assimilationStatus: 'SOURCE_REGISTERED_PRODUCT_PARAMETER_EXTRACTION_READY',
|
notes: item.note || ''
|
});
|
}
|
|
function addBodySources(items, collection, indexPath) {
|
for (const [index, item] of items.entries()) {
|
const objectName = item.object || item.company || '';
|
const sourceType =
|
item.sourceType || item.objectType || (item.batch ? 'CURATED_PACKAGE_INDEX' : '');
|
const sourceDate = item.sourceDate || '';
|
const usable = arrayValue(item.usableFields);
|
const limitations = arrayValue(item.limitations);
|
addSource({
|
collection,
|
originalId: String(item.id || collection + '-' + String(index + 1).padStart(3, '0')),
|
objectName,
|
title: objectName + ' source package entry',
|
sourceType,
|
publisher: item.publisher || '',
|
sourceDate,
|
url: item.url || '',
|
evidenceGrade: item.evidenceLevel || '',
|
physicalPath: indexPath,
|
relativePath: relFromIndustry(indexPath),
|
locator: 'JSON_ITEM:' + String(item.id || index + 1),
|
extractedRelativePath: arrayValue(item.localFiles || item.localCompanyFile).join('|'),
|
extractionStatus: 'CURATED_INDEX_ENTRY_AVAILABLE',
|
semanticUseScope: usable.join('|'),
|
assimilationStatus:
|
collection === 'BODY_OEM_HARD_EVIDENCE'
|
? 'HARD_EVIDENCE_SOURCE_REGISTERED_PENDING_CLAIM_VERIFICATION'
|
: 'CURATED_SOURCE_PACKAGE_REGISTERED',
|
notes: limitations.join('|')
|
});
|
}
|
}
|
|
const bodyFirst = readJson(PATHS.bodyIndex);
|
const bodyHard = readJson(PATHS.bodyHardIndex);
|
const bodySecond = readJson(PATHS.bodySecondIndex);
|
addBodySources(bodyFirst, 'BODY_OEM_FIRST_BATCH', PATHS.bodyIndex);
|
addBodySources(bodyHard, 'BODY_OEM_HARD_EVIDENCE', PATHS.bodyHardIndex);
|
addBodySources(bodySecond, 'BODY_OEM_SECOND_BATCH', PATHS.bodySecondIndex);
|
|
const newsIndex = readJson(PATHS.newsIndex);
|
const newsRoot = path.dirname(PATHS.newsIndex);
|
for (const [index, item] of newsIndex.entries()) {
|
const correctedName = path.basename(String(item.corrected || item.source || ''));
|
const physicalPath = path.join(newsRoot, correctedName);
|
addSource({
|
collection: 'NEWS_ROLLING_SNOWBALL',
|
originalId: 'NEWS-' + String(index + 1).padStart(3, '0'),
|
objectName: '机器人行业动态',
|
title: correctedName.replace(/_校正\.txt$/i, ''),
|
sourceType: 'CORRECTED_NEWS_TRANSCRIPT',
|
publisher: '',
|
sourceDate: item.date || '',
|
url: '',
|
evidenceGrade: 'SECONDARY_CONTEXT',
|
physicalPath,
|
relativePath: relFromIndustry(physicalPath),
|
locator: 'FULL_DOCUMENT',
|
extractionStatus: 'CORRECTED_TEXT_AVAILABLE',
|
semanticUseScope: 'EVENT_CHAIN_AND_CONTEXT_CANDIDATE',
|
assimilationStatus: 'SOURCE_REGISTERED_PENDING_EVENT_LEVEL_DEDUP_AND_VERIFICATION',
|
notes: 'Corrected transcript; not a primary-source fact.'
|
});
|
}
|
|
const reportFiles = fs
|
.readdirSync(PATHS.reportDir)
|
.filter((name) => name.toLowerCase().endsWith('.md'))
|
.sort((left, right) => left.localeCompare(right, 'zh-CN'));
|
for (const [index, name] of reportFiles.entries()) {
|
const physicalPath = path.join(PATHS.reportDir, name);
|
addSource({
|
collection: 'EMBODIED_INTELLIGENCE_REPORTS',
|
originalId: 'REPORT-' + String(index + 1).padStart(3, '0'),
|
objectName: '具身智能专题',
|
title: name.replace(/\.md$/i, ''),
|
sourceType: 'ARCHIVED_RESEARCH_REPORT',
|
publisher: '',
|
sourceDate: '',
|
url: '',
|
evidenceGrade: 'LOW_CONFIDENCE_SECONDARY_REPORT',
|
physicalPath,
|
relativePath: relFromIndustry(physicalPath),
|
locator: 'FULL_DOCUMENT',
|
extractionStatus: 'MARKDOWN_AVAILABLE',
|
semanticUseScope: 'TOPIC_CONTEXT_AND_GAP_DISCOVERY_ONLY',
|
assimilationStatus: 'SOURCE_REGISTERED_NO_DIRECT_FACT_UPGRADE',
|
notes: 'Use only for context and source leads; do not promote claims without primary-source verification.'
|
});
|
}
|
|
const changedRows = deltaRows
|
.filter(
|
(row) =>
|
row.delta_type === 'CHANGED' &&
|
row.scope_status === 'IN_SCOPE'
|
)
|
.sort((left, right) => left.relative_path.localeCompare(right.relative_path, 'zh-CN'));
|
|
for (const row of changedRows) {
|
const physicalPath = path.join(PATHS.upstreamResearch, row.relative_path.replace(/\//g, path.sep));
|
const company = masterBySourcePath.get(row.relative_path.replace(/\\/g, '/')) || null;
|
addSource({
|
collection: 'CHANGED_RESEARCH_DOCUMENT',
|
originalId: row.delta_id,
|
objectName: company ? company.canonical_name : row.affected_object || '机器人行业专题',
|
title: path.basename(row.relative_path, '.md'),
|
sourceType: row.content_type,
|
publisher: 'FROZEN_UPSTREAM_RESEARCH',
|
sourceDate: '',
|
url: '',
|
evidenceGrade: 'FROZEN_LOCAL_RESEARCH_PENDING_SEMANTIC_ADJUDICATION',
|
physicalPath,
|
relativePath: 'doc/research/' + row.relative_path.replace(/\\/g, '/'),
|
locator: 'FULL_DOCUMENT_DIFF_AGAINST_PROJECT_COPY',
|
extractionStatus: 'LINE_LEVEL_DIFF_READY',
|
semanticUseScope: row.content_type,
|
comparisonStatus: 'CHANGED_VS_PROJECT_COPY',
|
assimilationStatus: 'SOURCE_REGISTERED_LINE_LEVEL_SEMANTIC_DIFF_INCLUDED',
|
notes: 'Upstream changed document; only G-only useful lines are added to the fact register.',
|
companies: company ? [company] : undefined
|
});
|
}
|
|
const facts = [];
|
|
function pushFact(input) {
|
const company = input.company || null;
|
const text = normalizeLine(input.factText);
|
const key = [
|
input.sourceRecord.source_record_id,
|
input.locator || '',
|
input.factField || '',
|
text,
|
input.value || ''
|
].join('|');
|
facts.push({
|
fact_id: shortId('N073-FACT', key),
|
source_record_id: input.sourceRecord.source_record_id,
|
source_collection: input.sourceRecord.source_collection,
|
original_source_id: input.sourceRecord.original_source_id,
|
company_id: company ? company.company_id : '',
|
company_name: company ? company.canonical_name : '',
|
source_object: input.sourceObject || input.sourceRecord.source_object,
|
source_title: input.sourceTitle || input.sourceRecord.title,
|
source_root_alias: INDUSTRY_ALIAS,
|
source_relative_path: input.relativePath || input.sourceRecord.source_relative_path,
|
source_locator: input.locator || input.sourceRecord.source_locator,
|
source_document_sha256:
|
input.documentSha || input.sourceRecord.physical_file_sha256,
|
source_text_sha256: shaText(text),
|
fact_field: input.factField || 'UNCLASSIFIED_CONTENT',
|
fact_text: text,
|
value: input.value || '',
|
unit: input.unit || '',
|
period: input.period || '',
|
semantic_disposition: input.disposition || 'CONTEXT_CANDIDATE',
|
evidence_grade: input.evidenceGrade || input.sourceRecord.evidence_grade,
|
integration_target: input.integrationTarget || 'ROBOT_COMPANY_INFORMATION_V01',
|
evidence_boundary: BOUNDARY,
|
assimilation_status:
|
input.assimilationStatus || 'ASSIMILATED_AS_CANDIDATE_PENDING_VERIFICATION'
|
});
|
}
|
|
function semanticFieldForText(text, fallback) {
|
if (/风险|不确定|待补|未披露|未知|不足|不能|不等同|受限|缺少/.test(text)) {
|
return 'RISK_GAP_AND_UNCERTAINTY';
|
}
|
if (/产能|募投|扩产|产线|工厂|交期|交付|质保|保修|采购/.test(text)) {
|
return 'CAPACITY_DELIVERY_AND_PROCUREMENT';
|
}
|
if (/收入|营收|毛利|利润|销量|出货|价格|售价|ASP|金额|亿元|万元|美元|人民币/.test(text)) {
|
return 'REVENUE_PRICE_AND_OPERATIONAL_METRIC';
|
}
|
if (/客户|供应商|供货|合作|订单|定点|部署|应用案例|采用/.test(text)) {
|
return 'CUSTOMER_AND_COMMERCIAL_RELATIONSHIP';
|
}
|
if (/产品|型号|平台|方案|参数|自由度|传感|电机|减速器|丝杠|控制器|编码器|灵巧手/.test(text)) {
|
return 'PRODUCT_AND_TECHNICAL_CAPABILITY';
|
}
|
return fallback || 'CHAIN_POSITION_AND_CONTEXT';
|
}
|
|
function semanticDisposition(text, sectionKey) {
|
const normalized = normalizeLine(text);
|
const noise =
|
normalized.length < 12 ||
|
/^[-|::\s\d.]+$/.test(normalized) ||
|
/目录|公司简介和主要财务指标|本报告中如有涉及未来|所有董事均已出席|公开发行证券的公司信息披露解释性公告|金融负债的现时义务|会计确认和终止确认/.test(
|
normalized
|
) ||
|
/\.{4,}/.test(normalized);
|
if (noise) {
|
return 'EXCLUDED_STRUCTURAL_OR_GENERIC';
|
}
|
const robotAnchor = /机器人|人形|具身|AGV|AMR|灵巧手|关节|触觉|伺服|减速器|丝杠|编码器|运动控制|3D视觉/.test(
|
normalized
|
);
|
const factAnchor =
|
/客户|供应|供货|订单|定点|交付|收入|营收|毛利|利润|产能|价格|售价|销量|出货|参数|自由度|精度|扭矩|寿命|质保|产品|型号|募投|合作|部署/.test(
|
normalized
|
);
|
if (robotAnchor && factAnchor) {
|
return 'DIRECT_HIGH_VALUE_CANDIDATE';
|
}
|
if (
|
['客户与供货', '产能与募投', '产品参数', '质保与交付', '分产品收入'].includes(
|
sectionKey
|
) &&
|
factAnchor
|
) {
|
return 'CONTEXT_USEFUL_CANDIDATE';
|
}
|
return normalized.length >= 30
|
? 'CONTEXT_USEFUL_CANDIDATE'
|
: 'EXCLUDED_STRUCTURAL_OR_GENERIC';
|
}
|
|
const sectionField = {
|
管理层讨论: 'OPERATING_PROGRESS_AND_STRATEGY',
|
分产品收入: 'REVENUE_AND_PRODUCT_MIX',
|
客户与供货: 'CUSTOMER_AND_SUPPLY_RELATIONSHIP',
|
产能与募投: 'CAPACITY_AND_CAPEX',
|
产品参数: 'PRODUCT_AND_TECHNICAL_PARAMETERS',
|
质保与交付: 'DELIVERY_WARRANTY_AND_PROCUREMENT',
|
风险提示: 'RISK_AND_UNCERTAINTY'
|
};
|
|
const coreStructured = readJson(PATHS.coreStructured);
|
let coreLocatorPending = 0;
|
for (const document of coreStructured) {
|
const exactKey = ['CORE_COMPONENTS', document.id, document.object || '', document.title || ''].join('|');
|
const sourceRecord =
|
registryLookup.get(exactKey) || registryLookup.get('CORE_COMPONENTS|' + document.id);
|
if (!sourceRecord) {
|
fail('CORE_STRUCTURED_SOURCE_NOT_REGISTERED:' + document.id);
|
}
|
const companies = mapObjectToCompanies(document.object);
|
const company = companies.length === 1 ? companies[0] : null;
|
const extractedPath = document.extractedFile
|
? path.join(path.dirname(PATHS.coreIndex), document.extractedFile)
|
: '';
|
const extractedIdentity = fileIdentity(extractedPath);
|
const sourceLines =
|
extractedIdentity.exists === 'YES' ? readText(extractedPath).split(/\r?\n/) : [];
|
for (const section of arrayValue(document.sections)) {
|
for (const hit of arrayValue(section.hits)) {
|
const text = normalizeLine(hit.text);
|
const actualLine = normalizeLine(sourceLines[Number(hit.line) - 1] || '');
|
if (!actualLine || actualLine !== text) {
|
coreLocatorPending += 1;
|
}
|
pushFact({
|
sourceRecord,
|
company,
|
sourceObject: document.object,
|
sourceTitle: document.title,
|
relativePath: document.extractedFile
|
? 'data/public/robotics/core_components/' + document.extractedFile.replace(/\\/g, '/')
|
: sourceRecord.source_relative_path,
|
locator: 'L' + String(hit.line),
|
documentSha: extractedIdentity.sha256 || sourceRecord.physical_file_sha256,
|
factField: sectionField[section.key] || semanticFieldForText(text),
|
factText: text,
|
disposition: semanticDisposition(text, section.key),
|
evidenceGrade: sourceRecord.evidence_grade,
|
integrationTarget: 'ROBOT_COMPANY_INFORMATION_V01|FACT_LEVEL_EVIDENCE_REGISTER',
|
assimilationStatus:
|
actualLine && actualLine === text
|
? 'ASSIMILATED_WITH_EXACT_LINE_IDENTITY_PENDING_VERIFICATION'
|
: 'ASSIMILATED_FROM_STRUCTURED_EXTRACTION_PENDING_LOCATOR_RECHECK'
|
});
|
}
|
}
|
}
|
|
const corePrices = readJson(PATHS.corePrice);
|
for (const item of corePrices) {
|
const exactKey = ['CORE_COMPONENTS', item.id, item.object || '', item.title || ''].join('|');
|
const sourceRecord =
|
registryLookup.get(exactKey) || registryLookup.get('CORE_COMPONENTS|' + item.id);
|
if (!sourceRecord) {
|
fail('CORE_PRICE_SOURCE_NOT_REGISTERED:' + item.id);
|
}
|
const companies = mapObjectToCompanies(item.object);
|
pushFact({
|
sourceRecord,
|
company: companies.length === 1 ? companies[0] : null,
|
sourceObject: item.object,
|
sourceTitle: item.title,
|
relativePath: item.extractedFile
|
? 'data/public/robotics/core_components/' + item.extractedFile.replace(/\\/g, '/')
|
: sourceRecord.source_relative_path,
|
locator: 'L' + String(item.line),
|
factField: 'PRICE_ASP_AND_TRANSACTION_CANDIDATE',
|
factText: item.text,
|
value: arrayValue(item.prices).join('|'),
|
disposition: 'DIRECT_HIGH_VALUE_CANDIDATE',
|
evidenceGrade: sourceRecord.evidence_grade,
|
integrationTarget: 'ROBOT_COMPANY_INFORMATION_V01.PRICE_ASP_AND_TRANSACTION',
|
assimilationStatus: 'PRICE_CANDIDATE_ASSIMILATED_PENDING_CONTEXT_VERIFICATION'
|
});
|
}
|
|
for (const item of coreIndex) {
|
const exactKey = ['CORE_COMPONENTS', item.id, item.object || '', item.title || ''].join('|');
|
const sourceRecord =
|
registryLookup.get(exactKey) || registryLookup.get('CORE_COMPONENTS|' + item.id);
|
const companies = mapObjectToCompanies(item.object);
|
pushFact({
|
sourceRecord,
|
company: companies.length === 1 ? companies[0] : null,
|
sourceObject: item.object,
|
sourceTitle: item.title,
|
locator: sourceRecord.source_locator + '.fields',
|
factField: 'SOURCE_COVERAGE_DIMENSION',
|
factText:
|
'该来源覆盖字段:' +
|
arrayValue(item.fields).join('、') +
|
(arrayValue(item.links).length ? ';关联:' + arrayValue(item.links).join('、') : '') +
|
(item.note ? ';备注:' + item.note : ''),
|
disposition: 'INDEXED_COVERAGE_CANDIDATE',
|
evidenceGrade: item.evidenceGrade || item.strength,
|
integrationTarget: item.fillTarget || 'ROBOT_COMPANY_INFORMATION_V01',
|
assimilationStatus: 'CORE_SOURCE_SEMANTIC_SCOPE_ASSIMILATED_PENDING_FACT_VERIFICATION'
|
});
|
}
|
|
function sourceForBody(collection, id) {
|
const source = registryLookup.get(collection + '|' + id);
|
if (!source) {
|
fail('BODY_SOURCE_NOT_REGISTERED:' + collection + ':' + id);
|
}
|
return source;
|
}
|
|
function addCuratedPackageFacts(items, collection) {
|
for (const item of items) {
|
const sourceRecord = sourceForBody(collection, String(item.id));
|
const companies = mapObjectToCompanies(item.object || item.company || '');
|
const company = companies.length === 1 ? companies[0] : null;
|
const objectName = item.object || item.company || '';
|
if (item.currentStage) {
|
pushFact({
|
sourceRecord,
|
company,
|
sourceObject: objectName,
|
locator: sourceRecord.source_locator + '.currentStage',
|
factField: semanticFieldForText(item.currentStage, 'OPERATING_AND_COMMERCIAL_STAGE'),
|
factText: item.currentStage,
|
disposition: 'CURATED_STAGE_CANDIDATE',
|
evidenceGrade: item.evidenceLevel,
|
integrationTarget: 'ROBOT_COMPANY_INFORMATION_V01.COMMERCIAL_STAGE'
|
});
|
}
|
for (const [index, field] of arrayValue(item.usableFields).entries()) {
|
pushFact({
|
sourceRecord,
|
company,
|
sourceObject: objectName,
|
locator: sourceRecord.source_locator + '.usableFields[' + String(index) + ']',
|
factField: semanticFieldForText(field, 'SOURCE_COVERAGE_DIMENSION'),
|
factText: '资料包将“' + field + '”标记为可用信息维度,正式引用前仍需回到对应原文。',
|
disposition:
|
collection === 'BODY_OEM_HARD_EVIDENCE'
|
? 'HARD_EVIDENCE_INDEXED_CANDIDATE'
|
: 'INDEXED_COVERAGE_CANDIDATE',
|
evidenceGrade: item.evidenceLevel,
|
integrationTarget: 'ROBOT_COMPANY_INFORMATION_V01|SOURCE_BACKTRACE_QUEUE'
|
});
|
}
|
for (const [index, limitation] of arrayValue(item.limitations).entries()) {
|
pushFact({
|
sourceRecord,
|
company,
|
sourceObject: objectName,
|
locator: sourceRecord.source_locator + '.limitations[' + String(index) + ']',
|
factField: 'RISK_GAP_AND_UNCERTAINTY',
|
factText: limitation,
|
disposition: 'EXPLICIT_GAP_RETAINED',
|
evidenceGrade: item.evidenceLevel,
|
integrationTarget: 'ROBOT_COMPANY_INFORMATION_V01.HIGH_VALUE_GAPS',
|
assimilationStatus: 'ASSIMILATED_AS_EXPLICIT_GAP_NO_FACT_UPGRADE'
|
});
|
}
|
}
|
}
|
|
addCuratedPackageFacts(bodyFirst, 'BODY_OEM_FIRST_BATCH');
|
addCuratedPackageFacts(bodyHard, 'BODY_OEM_HARD_EVIDENCE');
|
addCuratedPackageFacts(bodySecond, 'BODY_OEM_SECOND_BATCH');
|
|
for (const item of dexIndex) {
|
const sourceRecord = registryLookup.get(
|
'DEXTEROUS_HAND|DEX-' + String(item.id).padStart(3, '0')
|
);
|
const companies = mapObjectToCompanies(item.object);
|
pushFact({
|
sourceRecord,
|
company: companies.length === 1 ? companies[0] : null,
|
sourceObject: item.object,
|
locator: sourceRecord.source_locator + '.fields',
|
factField: 'PRODUCT_PARAMETER_SOURCE_COVERAGE',
|
factText:
|
'该来源覆盖字段:' +
|
arrayValue(item.fields).join('、') +
|
(item.note ? ';备注:' + item.note : ''),
|
disposition: 'INDEXED_COVERAGE_CANDIDATE',
|
evidenceGrade: item.strength,
|
integrationTarget: 'ROBOT_COMPANY_INFORMATION_V01.PRODUCT_AND_TECHNICAL_CAPABILITY'
|
});
|
}
|
|
function usefulChangedLine(line) {
|
const text = normalizeLine(line);
|
if (text.length < 8) {
|
return false;
|
}
|
if (/^#{1,6}\s*$/.test(text) || /^[-|::\s]+$/.test(text)) {
|
return false;
|
}
|
if (/^>\s*(日期|版本|角色|资料口径|状态)[::]/.test(text)) {
|
return false;
|
}
|
if (/^\|(?:\s*:?-+:?\s*\|)+$/.test(text)) {
|
return false;
|
}
|
return true;
|
}
|
|
let changedProfileFactCount = 0;
|
for (const row of changedRows.filter((item) => item.content_type === 'COMPANY_PROFILE')) {
|
const upstreamPath = path.join(PATHS.upstreamResearch, row.relative_path.replace(/\//g, path.sep));
|
const projectPath = path.join(PATHS.projectResearch, row.relative_path.replace(/\//g, path.sep));
|
if (!fs.existsSync(upstreamPath) || !fs.existsSync(projectPath)) {
|
continue;
|
}
|
const sourceRecord = registryLookup.get('CHANGED_RESEARCH_DOCUMENT|' + row.delta_id);
|
const company = masterBySourcePath.get(row.relative_path.replace(/\\/g, '/')) || null;
|
const projectSet = new Set(
|
readText(projectPath)
|
.split(/\r?\n/)
|
.map(normalizeLine)
|
.filter(Boolean)
|
);
|
const upstreamLines = readText(upstreamPath).split(/\r?\n/);
|
const upstreamSha = shaFile(upstreamPath);
|
for (let index = 0; index < upstreamLines.length; index += 1) {
|
const text = normalizeLine(upstreamLines[index]);
|
if (!usefulChangedLine(text) || projectSet.has(text)) {
|
continue;
|
}
|
changedProfileFactCount += 1;
|
const field = semanticFieldForText(text, 'CHAIN_POSITION_AND_CONTEXT');
|
pushFact({
|
sourceRecord,
|
company,
|
sourceObject: company ? company.canonical_name : row.affected_object,
|
sourceTitle: path.basename(row.relative_path, '.md'),
|
relativePath: 'doc/research/' + row.relative_path.replace(/\\/g, '/'),
|
locator: 'L' + String(index + 1),
|
documentSha: upstreamSha,
|
factField: field,
|
factText: text,
|
disposition: semanticDisposition(text, field),
|
evidenceGrade: 'FROZEN_LOCAL_RESEARCH_PENDING_PRIMARY_SOURCE_BACKTRACE',
|
integrationTarget: 'ROBOT_COMPANY_INFORMATION_V01|CHANGED_PROFILE_FACT_BACKLOG',
|
assimilationStatus: 'G_ONLY_CHANGED_PROFILE_LINE_ASSIMILATED_PENDING_VERIFICATION'
|
});
|
}
|
}
|
|
const correctionByEvidenceId = new Map(
|
n072Rows.map((row) => [row.supersedes_evidence_id, row])
|
);
|
const existingEvidenceRows = [];
|
for (const waveFile of findWaveEvidenceFiles()) {
|
for (const row of readCsv(waveFile)) {
|
existingEvidenceRows.push(row);
|
}
|
}
|
for (const row of n071Rows) {
|
const correction = correctionByEvidenceId.get(row.evidence_id);
|
existingEvidenceRows.push(
|
correction
|
? {
|
...row,
|
fact_text: correction.corrected_fact_text,
|
content_locator: correction.corrected_content_locator,
|
evidence_boundary: correction.evidence_boundary,
|
status: correction.status
|
}
|
: row
|
);
|
}
|
|
const factsByCompany = new Map();
|
for (const fact of facts) {
|
if (!fact.company_id) {
|
continue;
|
}
|
if (!factsByCompany.has(fact.company_id)) {
|
factsByCompany.set(fact.company_id, []);
|
}
|
factsByCompany.get(fact.company_id).push(fact);
|
}
|
|
const existingByCompany = new Map();
|
for (const row of existingEvidenceRows) {
|
if (!existingByCompany.has(row.company_id)) {
|
existingByCompany.set(row.company_id, []);
|
}
|
existingByCompany.get(row.company_id).push(row);
|
}
|
|
const sourceRowsByCompany = new Map();
|
for (const source of sourceRegistry) {
|
for (const companyId of String(source.mapped_company_ids || '').split('|').filter(Boolean)) {
|
if (!sourceRowsByCompany.has(companyId)) {
|
sourceRowsByCompany.set(companyId, []);
|
}
|
sourceRowsByCompany.get(companyId).push(source);
|
}
|
}
|
|
function firstUsefulFact(companyFacts, patterns, textPattern = null) {
|
const rank = {
|
DIRECT_HIGH_VALUE_CANDIDATE: 1,
|
HARD_EVIDENCE_INDEXED_CANDIDATE: 2,
|
CURATED_STAGE_CANDIDATE: 3,
|
CONTEXT_USEFUL_CANDIDATE: 4,
|
EXPLICIT_GAP_RETAINED: 5,
|
INDEXED_COVERAGE_CANDIDATE: 6
|
};
|
const usable = companyFacts
|
.filter(
|
(fact) =>
|
fact.semantic_disposition !== 'EXCLUDED_STRUCTURAL_OR_GENERIC' &&
|
fact.fact_field !== 'SOURCE_COVERAGE_DIMENSION' &&
|
fact.fact_field !== 'PRODUCT_PARAMETER_SOURCE_COVERAGE'
|
)
|
.filter((fact) => patterns.some((pattern) => pattern.test(fact.fact_field)))
|
.filter((fact) => !textPattern || textPattern.test(fact.fact_text))
|
.sort(
|
(left, right) =>
|
(rank[left.semantic_disposition] || 99) -
|
(rank[right.semantic_disposition] || 99)
|
);
|
const found = usable[0];
|
return found ? found.fact_text : '';
|
}
|
|
function firstExisting(existingFacts, patterns) {
|
const found = existingFacts.find((fact) =>
|
patterns.some((pattern) => pattern.test(fact.fact_type || ''))
|
);
|
return found ? normalizeLine(found.fact_text) : '';
|
}
|
|
const companyInfo = master.map((company) => {
|
const companyFacts = factsByCompany.get(company.company_id) || [];
|
const existingFacts = existingByCompany.get(company.company_id) || [];
|
const companySources = sourceRowsByCompany.get(company.company_id) || [];
|
const directFacts = companyFacts.filter((fact) =>
|
['DIRECT_HIGH_VALUE_CANDIDATE', 'HARD_EVIDENCE_INDEXED_CANDIDATE'].includes(
|
fact.semantic_disposition
|
)
|
);
|
const usefulFacts = companyFacts.filter(
|
(fact) => fact.semantic_disposition !== 'EXCLUDED_STRUCTURAL_OR_GENERIC'
|
);
|
const product =
|
firstExisting(existingFacts, [/PRODUCT/, /CAPABILITY/]) ||
|
firstUsefulFact(companyFacts, [
|
/^PRODUCT_AND_TECHNICAL_CAPABILITY$/,
|
/^PRODUCT_AND_TECHNICAL_PARAMETERS$/
|
]);
|
const commercial =
|
firstExisting(existingFacts, [/CUSTOMER/, /COMMERCIAL/]) ||
|
firstUsefulFact(companyFacts, [
|
/^CUSTOMER_AND_COMMERCIAL_RELATIONSHIP$/,
|
/^CUSTOMER_AND_SUPPLY_RELATIONSHIP$/
|
]);
|
const operation =
|
firstExisting(existingFacts, [/QUANTITATIVE/, /OPERATIONAL/, /MANUFACTURING/]) ||
|
firstUsefulFact(
|
companyFacts,
|
[/^REVENUE_AND_PRODUCT_MIX$/, /^REVENUE_PRICE_AND_OPERATIONAL_METRIC$/],
|
/收入|营收|毛利|利润|价格|售价|销量|出货|亿元|万元|美元|人民币/
|
);
|
const capacity =
|
firstExisting(existingFacts, [/CAPACITY/, /DELIVERY/]) ||
|
firstUsefulFact(companyFacts, [
|
/^CAPACITY_AND_CAPEX$/,
|
/^DELIVERY_WARRANTY_AND_PROCUREMENT$/,
|
/^CAPACITY_DELIVERY_AND_PROCUREMENT$/
|
]);
|
const risk =
|
firstExisting(existingFacts, [/GAP/]) ||
|
firstUsefulFact(
|
companyFacts,
|
[/^RISK_/, /UNCERTAINTY/],
|
/风险|待补|未披露|未知|不足|不能|不等同|受限|缺少/
|
);
|
let assimilationStatus = 'IDENTITY_AND_CLASSIFICATION_RETAINED';
|
if (directFacts.length > 0) {
|
assimilationStatus = 'G_DIRECT_CANDIDATES_ASSIMILATED_PENDING_VERIFICATION';
|
} else if (usefulFacts.length > 0) {
|
assimilationStatus = 'G_CONTEXT_AND_COVERAGE_ASSIMILATED_PENDING_VERIFICATION';
|
} else if (companySources.length > 0) {
|
assimilationStatus = 'G_SOURCES_REGISTERED_FACT_EXTRACTION_PENDING';
|
} else if (existingFacts.length > 0 || company.formal_output_status === 'EXISTING_FORMAL_OUTPUT') {
|
assimilationStatus = 'EXISTING_PROJECT_CONTENT_RETAINED_NO_NEW_G_SOURCE';
|
}
|
return {
|
company_id: company.company_id,
|
canonical_name: company.canonical_name,
|
aliases: company.aliases,
|
company_type: company.company_type,
|
region: company.region,
|
listed_status: company.listed_status,
|
primary_track: company.primary_track,
|
detail_track: company.detail_track,
|
product_and_technical_capability: product,
|
customer_and_commercial_relationship: commercial,
|
revenue_and_operational_metric: operation,
|
capacity_delivery_and_procurement: capacity,
|
market_position_and_chain_role: company.primary_track + ' / ' + company.detail_track,
|
high_value_gap_and_risk: risk,
|
g_source_record_count: String(companySources.length),
|
g_fact_candidate_count: String(companyFacts.length),
|
g_useful_fact_candidate_count: String(usefulFacts.length),
|
g_direct_high_value_candidate_count: String(directFacts.length),
|
existing_project_fact_count: String(existingFacts.length),
|
primary_g_source_ids: unique(companySources.map((row) => row.original_source_id))
|
.slice(0, 20)
|
.join('|'),
|
primary_g_source_urls: unique(companySources.map((row) => row.source_url))
|
.slice(0, 10)
|
.join('|'),
|
source_profile_status: company.source_profile_status,
|
formal_output_status: company.formal_output_status,
|
evidence_ceiling: 'PENDING_VERIFICATION_MAX_NO_UPGRADE',
|
formal_pool_effect: 'NO_AUTOMATIC_FORMAL_POOL_CHANGE',
|
assimilation_status: assimilationStatus,
|
as_of: AS_OF
|
};
|
});
|
|
const priorityRows = [];
|
function addPriority(source, tier, focus, reason) {
|
const relatedFacts = facts.filter((fact) => fact.source_record_id === source.source_record_id);
|
const direct = relatedFacts.filter((fact) =>
|
['DIRECT_HIGH_VALUE_CANDIDATE', 'HARD_EVIDENCE_INDEXED_CANDIDATE'].includes(
|
fact.semantic_disposition
|
)
|
);
|
priorityRows.push({
|
priority_id: 'N073-PRI-' + String(priorityRows.length + 1).padStart(3, '0'),
|
priority_tier: tier,
|
source_record_id: source.source_record_id,
|
original_source_id: source.original_source_id,
|
source_collection: source.source_collection,
|
mapped_company_ids: source.mapped_company_ids,
|
mapped_company_names: source.mapped_company_names,
|
source_object: source.source_object,
|
focus_area: focus,
|
priority_reason: reason,
|
fact_candidate_count: String(relatedFacts.length),
|
direct_candidate_count: String(direct.length),
|
representative_fact_ids: direct.slice(0, 5).map((row) => row.fact_id).join('|'),
|
representative_fact_text:
|
direct[0]?.fact_text || relatedFacts[0]?.fact_text || '',
|
recommended_target_fields:
|
focus === 'P0_PRIMARY_SOURCE'
|
? '产品参数|客户与供货|价格/经营|产能/交付'
|
: focus === 'OEM_HARD_RELATIONSHIP'
|
? '客户部署|合作关系|商业里程碑|边界'
|
: '实体主数据|别名|候选公司',
|
evidence_boundary: BOUNDARY,
|
next_action:
|
source.mapping_status === 'NEW_COMPANY_CANDIDATE'
|
? 'ENTITY_REVIEW_THEN_VERSIONED_COMPANY_CANDIDATE'
|
: 'SEMANTIC_VERIFICATION_THEN_COMPANY_CARD_OR_TOPIC_UPDATE'
|
});
|
}
|
|
const p0SemanticRows = [];
|
const p0CoreConfig = [
|
{
|
id: 'CC-028',
|
needle: 'Sale price $24,240.00 USD',
|
after: 2,
|
field: 'PRICE_AND_PRODUCT_POSITIONING',
|
summary: '智元灵犀 X2 客户侧产品页列示售价 24,240 美元,并将其定位为面向娱乐与商业演出的半尺寸人形机器人;商城价格不等同批量成交 ASP。'
|
},
|
{
|
id: 'CC-029',
|
needle: 'Price from $13.5K',
|
after: 35,
|
field: 'PRICE_AND_PRODUCT_PARAMETERS',
|
summary: '宇树 G1 官网列示起售价 13.5K 美元、约 35kg 重量、23至43个关节电机,并说明可选 Dex3-1 三指灵巧手与触觉阵列;起售价不等同批量成交价。'
|
},
|
{
|
id: 'CC-030',
|
needle: '大型人形机器人应用场景',
|
after: 5,
|
field: 'PRODUCT_APPLICATION_SCOPE',
|
summary: '优必选官网列示大型人形机器人的工业制造、展厅展馆、科研教育和仓储物流应用场景;场景展示不等同客户采购或部署规模。'
|
},
|
{
|
id: 'CC-031',
|
needle: 'RH56 系列灵巧手是一款',
|
after: 19,
|
field: 'PRODUCT_PARAMETERS_AND_INTERFACE',
|
summary: '因时 RH56 手册披露 6 个微型伺服电缸、RS232/RS485/CAN 接口、12个关节、6自由度、6个力传感器、0.5N分辨率及0.2mm指尖重复定位精度。'
|
},
|
{
|
id: 'CC-032',
|
needle: '具备20个',
|
after: 4,
|
field: 'PRODUCT_PARAMETERS_AND_SENSING',
|
summary: '灵心 LinkerHand L20 手册披露 20 自由度、连杆传动、自研电机驱动,并配置力觉、视觉、触觉多模态传感,兼容 ROS/QT 与二次开发。'
|
},
|
{
|
id: 'CC-033',
|
needle: 'Multidimensional Tactile Adaptive Dexterous Hand',
|
after: 1,
|
field: 'PRODUCT_CLASS_PRESENCE',
|
summary: '帕西尼官网页面确认多维触觉自适应灵巧手产品类别,但当前本地抽取未承载 DexH13 GEN2 的具体参数,不能据此写入型号级性能。'
|
},
|
{
|
id: 'CC-034',
|
needle: '测力分辨精度可达0.01N',
|
after: 4,
|
field: 'TACTILE_SENSOR_PARAMETERS',
|
summary: '他山科技展会资料披露触觉传感器支持一维至三维力测量、0.01N测力分辨精度、30多种材质识别及接近觉能力;仍需区分公司自述与第三方测试。'
|
},
|
{
|
id: 'CC-035',
|
needle: '关节扭矩传感器',
|
after: 3,
|
field: 'FORCE_SENSOR_PRODUCT_MATRIX',
|
summary: '坤维官网产品中心列示关节扭矩传感器、动态扭矩传感器和应变计等力学传感产品;产品存在不等同机器人客户导入或量产。'
|
},
|
{
|
id: 'CC-036',
|
needle: 'XJCSENSOR at robotics',
|
after: 13,
|
field: 'ROBOT_FORCE_SENSOR_APPLICATION_MATRIX',
|
summary: '星汇传感官网列示协作机器人末端、机器人关节、灵巧手、手腕和脚踝场景,以及六维力、关节扭矩和微型力传感器产品。'
|
},
|
{
|
id: 'CC-053',
|
needle: '部分客户进入批量交付阶段',
|
after: 5,
|
field: 'CUSTOMER_STAGE_AND_CAPACITY_PLAN',
|
summary: '柯力传感年报披露力/扭矩传感器部分客户进入批量交付阶段,并计划推进机器人传感器从送样向量产跨越、建设专用车间;规划不等同已实现产能。'
|
},
|
{
|
id: 'CC-058',
|
needle: '已与全球 500 强企业日立集团',
|
after: 6,
|
field: 'NAMED_CUSTOMERS_AND_MARKET_CONTEXT',
|
summary: '奥比中光年报披露与日立集团、韩国移动机器人方案商 Twinny、护理机器人公司 RoboCare 达成业务合作,并引用韩国商用及工业移动机器人 3D 视觉市场数据;第三方份额口径需保留来源边界。'
|
}
|
];
|
|
for (const config of p0CoreConfig) {
|
const item = coreIndex.find((row) => row.id === config.id);
|
const exactKey = ['CORE_COMPONENTS', item.id, item.object || '', item.title || ''].join('|');
|
const sourceRecord =
|
registryLookup.get(exactKey) || registryLookup.get('CORE_COMPONENTS|' + item.id);
|
const extractedPath = path.join(path.dirname(PATHS.coreIndex), item.extractedFile);
|
const lines = readText(extractedPath).split(/\r?\n/);
|
const startIndex = lines.findIndex((line) => normalizeLine(line).includes(config.needle));
|
if (startIndex < 0) {
|
fail('P0_SEMANTIC_ANCHOR_NOT_FOUND:' + config.id + ':' + config.needle);
|
}
|
const endIndex = Math.min(lines.length - 1, startIndex + config.after);
|
const excerpt = lines.slice(startIndex, endIndex + 1).map(normalizeLine).filter(Boolean).join(' / ');
|
p0SemanticRows.push({
|
semantic_record_id: 'N073-P0-' + String(p0SemanticRows.length + 1).padStart(3, '0'),
|
priority_tier: 'P0',
|
source_record_id: sourceRecord.source_record_id,
|
original_source_id: config.id,
|
source_collection: 'CORE_COMPONENTS',
|
company_ids: sourceRecord.mapped_company_ids,
|
company_names: sourceRecord.mapped_company_names,
|
source_title: sourceRecord.title,
|
source_url: sourceRecord.source_url,
|
source_relative_path: sourceRecord.extracted_relative_path || sourceRecord.source_relative_path,
|
source_locator: 'L' + String(startIndex + 1) + '-L' + String(endIndex + 1),
|
source_document_sha256: shaFile(extractedPath),
|
source_excerpt_sha256: shaText(excerpt),
|
semantic_field: config.field,
|
semantic_summary: config.summary,
|
verification_result:
|
config.id === 'CC-033'
|
? 'SOURCE_PRESENT_MODEL_PARAMETER_GAP_RETAINED'
|
: 'LOCAL_PRIMARY_SOURCE_SEMANTIC_REVIEWED_PENDING_INDEPENDENT_CONFIRMATION',
|
evidence_boundary: BOUNDARY,
|
integration_target: 'ROBOT_COMPANY_INFORMATION_V01|COMPANY_CARD_CONTENT_UPDATE_QUEUE'
|
});
|
}
|
|
const hardSummary = {
|
'HE-20260622-001': 'GXO客户侧公告确认与Agility签署多年RaaS协议,Digit进入GXO物流运营并在SPANX设施与其他自动化系统协同;部署台数、合同金额和收费方式未披露。',
|
'HE-20260622-002': 'Agility官方披露Digit在GXO Flowery Branch设施完成超过10万次tote搬运;任务次数不等同收入、寿命或部署台数。',
|
'HE-20260622-003': 'BMW客户侧公告确认Figure 02在Spartanburg真实生产环境进行钣金件放置试验;当时未设定正式引入时间表。',
|
'HE-20260622-004': 'BMW客户侧公告补充Spartanburg 2025试点的10小时班次、3万台X3、9万个组件和约1250小时等运营口径;不披露合同金额、Figure收入或供应链。',
|
'HE-20260622-005': 'Figure官方披露BMW部署的运行时长、任务量和KPI口径;供应商侧数据需以BMW客户侧公告交叉验证,不能写成商业收入。',
|
'HE-20260622-006': '优必选2025年报是全尺寸具身智能人形机器人收入、销量、毛利和产能的正式回源入口;倒算ASP不等同分型号售价。',
|
'HE-20260622-007': '鼎智官网支持其获智元首届供应商大会优秀合作伙伴奖,以及PRSM和高性能伺服电机的参与方向;不支持供货金额、数量、份额或具体型号。',
|
'HE-20260622-008': '江苏雷利IR披露鼎智获评智元优秀供应商、与南京蔚蓝科技战略合作及机器人产品矩阵和灵巧手试点;IR不等同采购合同。',
|
'HE-20260622-009': '雷赛官网产品页支持无框电机、空心杯电机、编码器、驱动器、关节模组和灵巧手方案的产品矩阵;公司大规模交付自述不等同客户侧确认。',
|
'HE-20260622-010': '媒体转述提供雷赛无框力矩电机交付超12万台、获智元优秀供应商伙伴及模组/灵巧手批量供应线索;必须回到交易所公告或IR后才能作为强事实。'
|
};
|
|
for (const item of bodyHard) {
|
const sourceRecord = sourceForBody('BODY_OEM_HARD_EVIDENCE', String(item.id));
|
const itemText = JSON.stringify(item);
|
p0SemanticRows.push({
|
semantic_record_id: 'N073-P0-' + String(p0SemanticRows.length + 1).padStart(3, '0'),
|
priority_tier: 'P0',
|
source_record_id: sourceRecord.source_record_id,
|
original_source_id: item.id,
|
source_collection: 'BODY_OEM_HARD_EVIDENCE',
|
company_ids: sourceRecord.mapped_company_ids,
|
company_names: sourceRecord.mapped_company_names,
|
source_title: sourceRecord.title,
|
source_url: sourceRecord.source_url,
|
source_relative_path: sourceRecord.source_relative_path,
|
source_locator: 'JSON_ITEM:' + item.id,
|
source_document_sha256: sourceRecord.physical_file_sha256,
|
source_excerpt_sha256: shaText(itemText),
|
semantic_field: 'OEM_CUSTOMER_SUPPLIER_RELATIONSHIP_AND_OPERATION',
|
semantic_summary: hardSummary[item.id],
|
verification_result:
|
item.id === 'HE-20260622-010'
|
? 'MEDIA_LEAD_ONLY_PRIMARY_BACKTRACE_REQUIRED'
|
: 'CURATED_HARD_SOURCE_SEMANTIC_REVIEWED_PENDING_INDEPENDENT_CONFIRMATION',
|
evidence_boundary: BOUNDARY,
|
integration_target: 'COMPANY_CARD|OEM_RELATIONSHIP_TABLE|GAP_REGISTER'
|
});
|
}
|
|
for (const source of sourceRegistry.filter(
|
(row) => row.source_collection === 'CORE_COMPONENTS' && P0_CORE_IDS.has(row.original_source_id)
|
)) {
|
addPriority(
|
source,
|
'P0',
|
'P0_PRIMARY_SOURCE',
|
'Direct product, customer-side, manual or annual-report source already present in G package.'
|
);
|
}
|
for (const source of sourceRegistry.filter(
|
(row) => row.source_collection === 'BODY_OEM_HARD_EVIDENCE'
|
)) {
|
addPriority(
|
source,
|
'P0',
|
'OEM_HARD_RELATIONSHIP',
|
'Customer-side or supplier-side hard evidence for Figure/BMW, Agility/GXO and related OEM chains.'
|
);
|
}
|
for (const source of sourceRegistry.filter((row) =>
|
['NEW_COMPANY_CANDIDATE', 'ALIAS_TO_EXISTING_COMPANY', 'EVIDENCE_OBJECT_NOT_COMPANY'].includes(
|
row.mapping_status
|
)
|
)) {
|
if (!priorityRows.some((row) => row.source_record_id === source.source_record_id)) {
|
addPriority(
|
source,
|
source.mapping_status === 'NEW_COMPANY_CANDIDATE' ? 'P1' : 'P2',
|
'ENTITY_AND_ALIAS',
|
'Resolve source object into a new candidate, existing alias or non-company evidence object.'
|
);
|
}
|
}
|
|
const sourceColumns = [
|
'source_record_id',
|
'source_collection',
|
'original_source_id',
|
'source_root_alias',
|
'source_relative_path',
|
'source_locator',
|
'source_object',
|
'mapped_company_ids',
|
'mapped_company_names',
|
'mapping_status',
|
'title',
|
'source_type',
|
'publisher',
|
'source_date',
|
'source_url',
|
'evidence_grade',
|
'physical_file_exists',
|
'physical_file_sha256',
|
'physical_file_bytes',
|
'physical_file_mtime',
|
'extracted_relative_path',
|
'extraction_status',
|
'semantic_use_scope',
|
'comparison_status',
|
'evidence_boundary',
|
'assimilation_status',
|
'notes'
|
];
|
const mappingColumns = [
|
'object_mapping_id',
|
'source_record_id',
|
'source_collection',
|
'original_source_id',
|
'source_object',
|
'mapping_status',
|
'mapped_company_ids',
|
'mapped_company_names',
|
'source_role',
|
'unresolved_reason',
|
'recommended_action',
|
'evidence_boundary'
|
];
|
const priorityColumns = [
|
'priority_id',
|
'priority_tier',
|
'source_record_id',
|
'original_source_id',
|
'source_collection',
|
'mapped_company_ids',
|
'mapped_company_names',
|
'source_object',
|
'focus_area',
|
'priority_reason',
|
'fact_candidate_count',
|
'direct_candidate_count',
|
'representative_fact_ids',
|
'representative_fact_text',
|
'recommended_target_fields',
|
'evidence_boundary',
|
'next_action'
|
];
|
const companyInfoColumns = [
|
'company_id',
|
'canonical_name',
|
'aliases',
|
'company_type',
|
'region',
|
'listed_status',
|
'primary_track',
|
'detail_track',
|
'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',
|
'g_source_record_count',
|
'g_fact_candidate_count',
|
'g_useful_fact_candidate_count',
|
'g_direct_high_value_candidate_count',
|
'existing_project_fact_count',
|
'primary_g_source_ids',
|
'primary_g_source_urls',
|
'source_profile_status',
|
'formal_output_status',
|
'evidence_ceiling',
|
'formal_pool_effect',
|
'assimilation_status',
|
'as_of'
|
];
|
|
writeCsv(PATHS.sourceRegistry, sourceRegistry, sourceColumns);
|
writeCsv(PATHS.factRegister, facts, FACT_COLUMNS);
|
writeCsv(PATHS.entityMapping, entityMappings, mappingColumns);
|
writeCsv(PATHS.priorityRegister, priorityRows, priorityColumns);
|
writeCsv(
|
PATHS.p0Semantic,
|
p0SemanticRows,
|
[
|
'semantic_record_id',
|
'priority_tier',
|
'source_record_id',
|
'original_source_id',
|
'source_collection',
|
'company_ids',
|
'company_names',
|
'source_title',
|
'source_url',
|
'source_relative_path',
|
'source_locator',
|
'source_document_sha256',
|
'source_excerpt_sha256',
|
'semantic_field',
|
'semantic_summary',
|
'verification_result',
|
'evidence_boundary',
|
'integration_target'
|
]
|
);
|
writeCsv(PATHS.companyInfo, companyInfo, companyInfoColumns);
|
|
const sourceCounts = groupCount(sourceRegistry, 'source_collection');
|
const dispositionCounts = groupCount(facts, 'semantic_disposition');
|
const mappingCounts = groupCount(sourceRegistry, 'mapping_status');
|
const companiesWithNewSources = new Set(
|
sourceRegistry
|
.flatMap((row) => String(row.mapped_company_ids || '').split('|'))
|
.filter(Boolean)
|
);
|
const companiesWithUsefulFacts = new Set(
|
facts
|
.filter((row) => row.semantic_disposition !== 'EXCLUDED_STRUCTURAL_OR_GENERIC')
|
.map((row) => row.company_id)
|
.filter(Boolean)
|
);
|
const p0Registered = new Set(
|
sourceRegistry
|
.filter(
|
(row) => row.source_collection === 'CORE_COMPONENTS' && P0_CORE_IDS.has(row.original_source_id)
|
)
|
.map((row) => row.original_source_id)
|
);
|
const unmatchedSpecial = unique(
|
sourceRegistry
|
.filter((row) =>
|
['NEW_COMPANY_CANDIDATE', 'ALIAS_TO_EXISTING_COMPANY', 'EVIDENCE_OBJECT_NOT_COMPANY'].includes(
|
row.mapping_status
|
)
|
)
|
.map((row) => row.source_object)
|
);
|
|
const summaryLines = [
|
'# G盘有用信息语义吸收(第一批,2026-08-05)',
|
'',
|
'本批按“有用信息进入机器人体系、来源可追溯、候选不冒充事实”的原则执行。没有原封不动复制目录,也没有改写已经审核通过的 canonical company master、正式公司池或 evidence map。',
|
'',
|
'## 已完成',
|
'',
|
'- 建立 ' + String(sourceRegistry.length) + ' 条来源注册记录:core components ' + String(sourceCounts.get('CORE_COMPONENTS') || 0) + '、dexterous hand ' + String(sourceCounts.get('DEXTEROUS_HAND') || 0) + '、body OEM ' + String((sourceCounts.get('BODY_OEM_FIRST_BATCH') || 0) + (sourceCounts.get('BODY_OEM_HARD_EVIDENCE') || 0) + (sourceCounts.get('BODY_OEM_SECOND_BATCH') || 0)) + '、news ' + String(sourceCounts.get('NEWS_ROLLING_SNOWBALL') || 0) + '、embodied reports ' + String(sourceCounts.get('EMBODIED_INTELLIGENCE_REPORTS') || 0) + '、changed research documents ' + String(sourceCounts.get('CHANGED_RESEARCH_DOCUMENT') || 0) + '。',
|
'- 建立 ' + String(facts.length) + ' 条事实/线索记录,其中高价值直接候选 ' + String(dispositionCounts.get('DIRECT_HIGH_VALUE_CANDIDATE') || 0) + '、硬证据索引候选 ' + String(dispositionCounts.get('HARD_EVIDENCE_INDEXED_CANDIDATE') || 0) + '、上下文可用候选 ' + String(dispositionCounts.get('CONTEXT_USEFUL_CANDIDATE') || 0) + '、显式缺口 ' + String(dispositionCounts.get('EXPLICIT_GAP_RETAINED') || 0) + ';结构性或通用噪声仍保留在事实表但标为排除,不进入企业摘要。',
|
'- 28份 changed company profiles 的 G-only 有效行已形成 ' + String(changedProfileFactCount) + ' 条逐行候选,保留行号、文档哈希和文本哈希。',
|
'- 11项 P0 core source 与 10项 Figure/BMW、Agility/GXO 等 hard evidence source 已形成21条精选语义记录;每条保留来源定位、摘要、使用边界和目标字段。',
|
'- 307家公司生成统一信息表,字段对齐半导体体系的产品、客户、经营量化、产能交付、市场位置、缺口、主源和证据边界;已有 N052-N070 与 N071/N072 内容继续复用。',
|
'',
|
'## 企业与产业链映射结果',
|
'',
|
'- 新增 G 来源映射到现有公司:' + String(companiesWithNewSources.size) + ' 家。',
|
'- 具有至少一条非噪声 G 事实候选:' + String(companiesWithUsefulFacts.size) + ' 家。',
|
'- 映射到既有公司:' + String(mappingCounts.get('MAPPED_TO_EXISTING_COMPANY') || 0) + ' 条来源;多公司组合:' + String(mappingCounts.get('MULTI_COMPANY_MAPPING') || 0) + ' 条。',
|
'- 四类特殊对象已显式处理:中欣氟材、兴福新材为新公司候选;小鹏机器人映射为小鹏汽车别名;高校/科研采购样本保留为非公司证据对象。',
|
'',
|
'## 使用边界',
|
'',
|
'- 来源注册不等于事实确认;changed research、二手研报和新闻转录只作为回源线索或上下文。',
|
'- 价格、客户、订单、供货、量产、产能、收入、利润等强字段仍维持 PENDING_VERIFICATION_MAX_NO_UPGRADE。',
|
'- 本批不触发正式公司池扩张,不修改 A/B、formal evidence map、migration 或现有 canonical master。',
|
'',
|
'## 下一步(内容优先)',
|
'',
|
'1. 先把 P0 11项主源和 Figure/BMW、Agility/GXO 关系做逐条语义确认,直接回填对应企业卡/产业链关系页。',
|
'2. 再处理 changed company profiles 的高价值直接候选,按产品、客户、经营量化、产能交付四组集中更新。',
|
'3. 中欣氟材、兴福新材只建立版本化候选公司记录;小鹏机器人仅补别名;科研采购样本进入证据对象表。',
|
'4. 低置信 embodied reports 与新闻转录只用于发现主源,不直接形成强结论。',
|
'',
|
'## 数据入口',
|
'',
|
'- 来源注册表:evidence/next_robot_073_g_industry_source_registry_20260805.csv',
|
'- 事实与线索表:evidence/next_robot_073_g_industry_fact_register_20260805.csv',
|
'- 实体映射表:evidence/next_robot_073_g_industry_entity_mapping_20260805.csv',
|
'- P0与实体优先表:evidence/next_robot_073_g_industry_priority_register_20260805.csv',
|
'- P0精选语义表:evidence/next_robot_073_g_industry_p0_semantic_verification_20260805.csv',
|
'- 307家公司统一信息表:outputs/数据表/robot_company_information_v01_20260805.csv',
|
''
|
];
|
writeText(PATHS.summary, summaryLines.join('\n') + '\n');
|
|
const canonicalMasterAfter = fileIdentity(PATHS.master);
|
const validationRows = [];
|
function check(id, description, expected, actual) {
|
validationRows.push({
|
check_id: id,
|
check_description: description,
|
expected: String(expected),
|
actual: String(actual),
|
status: String(expected) === String(actual) ? 'PASS' : 'FAIL'
|
});
|
}
|
|
check('N073-VAL-001', 'source registry row uniqueness', sourceRegistry.length, new Set(sourceRegistry.map((row) => row.source_record_id)).size);
|
check('N073-VAL-002', 'base package source count', 295, sourceRegistry.filter((row) => row.source_collection !== 'CHANGED_RESEARCH_DOCUMENT').length);
|
check('N073-VAL-003', 'core components source count', 180, sourceCounts.get('CORE_COMPONENTS') || 0);
|
check('N073-VAL-004', 'dexterous hand source count', 30, sourceCounts.get('DEXTEROUS_HAND') || 0);
|
check('N073-VAL-005', 'body OEM total source count', 36, (sourceCounts.get('BODY_OEM_FIRST_BATCH') || 0) + (sourceCounts.get('BODY_OEM_HARD_EVIDENCE') || 0) + (sourceCounts.get('BODY_OEM_SECOND_BATCH') || 0));
|
check('N073-VAL-006', 'news source count', 21, sourceCounts.get('NEWS_ROLLING_SNOWBALL') || 0);
|
check('N073-VAL-007', 'embodied report source count', 28, sourceCounts.get('EMBODIED_INTELLIGENCE_REPORTS') || 0);
|
check('N073-VAL-008', 'changed document source count', 37, sourceCounts.get('CHANGED_RESEARCH_DOCUMENT') || 0);
|
check('N073-VAL-008A', 'changed company-profile source count', 28, changedRows.filter((row) => row.content_type === 'COMPANY_PROFILE').length);
|
check('N073-VAL-009', 'all registered physical source files exist', 0, sourceRegistry.filter((row) => row.physical_file_exists !== 'YES').length);
|
check('N073-VAL-010', 'fact id uniqueness', facts.length, new Set(facts.map((row) => row.fact_id)).size);
|
check('N073-VAL-011', 'core structured hit count', 2468, coreStructured.reduce((sum, document) => sum + arrayValue(document.sections).reduce((sectionSum, section) => sectionSum + arrayValue(section.hits).length, 0), 0));
|
check('N073-VAL-012', 'core price candidate count', 90, corePrices.length);
|
check('N073-VAL-013', 'P0 core source set count', 11, p0Registered.size);
|
check('N073-VAL-014', 'body hard evidence source count', 10, sourceCounts.get('BODY_OEM_HARD_EVIDENCE') || 0);
|
check('N073-VAL-014A', 'P0 semantic reviewed row count', 21, p0SemanticRows.length);
|
check('N073-VAL-014B', 'P0 semantic source-id uniqueness', 21, new Set(p0SemanticRows.map((row) => row.original_source_id)).size);
|
check('N073-VAL-015', 'company information row count', 307, companyInfo.length);
|
check('N073-VAL-016', 'company information company-id uniqueness', 307, new Set(companyInfo.map((row) => row.company_id)).size);
|
check('N073-VAL-017', 'company information/master bidirectional set mismatch', 0, [...new Set([...master.map((row) => row.company_id), ...companyInfo.map((row) => row.company_id)])].filter((id) => !masterById.has(id) || !companyInfo.some((row) => row.company_id === id)).length);
|
check('N073-VAL-018', 'special entity object coverage', 4, new Set(unmatchedSpecial.filter((name) => ['中欣氟材', '兴福新材', '小鹏机器人', '高校/科研采购样本'].includes(name))).size);
|
check('N073-VAL-019', 'fact boundary drift', 0, facts.filter((row) => row.evidence_boundary !== BOUNDARY).length);
|
check('N073-VAL-020', 'company formal-pool effect drift', 0, companyInfo.filter((row) => row.formal_pool_effect !== 'NO_AUTOMATIC_FORMAL_POOL_CHANGE').length);
|
check('N073-VAL-021', 'canonical master bytes unchanged', canonicalMasterBefore.bytes, canonicalMasterAfter.bytes);
|
check('N073-VAL-022', 'canonical master sha256 unchanged', canonicalMasterBefore.sha256, canonicalMasterAfter.sha256);
|
check('N073-VAL-023', 'generated output physical path uniqueness', 8, new Set([PATHS.sourceRegistry, PATHS.factRegister, PATHS.entityMapping, PATHS.priorityRegister, PATHS.p0Semantic, PATHS.companyInfo, PATHS.summary, PATHS.validation]).size);
|
check('N073-VAL-024', 'changed company profile G-only candidate rows are nonzero', 'YES', changedProfileFactCount > 0 ? 'YES' : 'NO');
|
check('N073-VAL-025', 'core locator rows pending exact physical-line recheck are explicitly carried', coreLocatorPending, facts.filter((row) => row.assimilation_status === 'ASSIMILATED_FROM_STRUCTURED_EXTRACTION_PENDING_LOCATOR_RECHECK').length);
|
|
writeCsv(
|
PATHS.validation,
|
validationRows,
|
['check_id', 'check_description', 'expected', 'actual', 'status']
|
);
|
|
const failed = validationRows.filter((row) => row.status === 'FAIL');
|
const result = {
|
status: failed.length === 0 ? 'PASS' : 'FAIL',
|
sourceRegistryRows: sourceRegistry.length,
|
factRows: facts.length,
|
changedCompanyProfileFacts: changedProfileFactCount,
|
companyInfoRows: companyInfo.length,
|
companiesWithNewSources: companiesWithNewSources.size,
|
companiesWithUsefulFacts: companiesWithUsefulFacts.size,
|
priorityRows: priorityRows.length,
|
validation: {
|
pass: validationRows.filter((row) => row.status === 'PASS').length,
|
fail: failed.length
|
},
|
outputs: [
|
PATHS.sourceRegistry,
|
PATHS.factRegister,
|
PATHS.entityMapping,
|
PATHS.priorityRegister,
|
PATHS.p0Semantic,
|
PATHS.companyInfo,
|
PATHS.summary,
|
PATHS.validation
|
]
|
};
|
|
console.log(JSON.stringify(result, null, 2));
|
if (failed.length > 0) {
|
process.exitCode = 1;
|
}
|