import fs from 'node:fs';
|
import path from 'node:path';
|
import crypto from 'node:crypto';
|
|
const CASE_ROOT = path.join('ana-data', 'cases', '机器人案例', 'ANA-ROBOT-INDUSTRY-001');
|
const COMPANY_MASTER_PATH = path.join(CASE_ROOT, 'outputs', '数据表', 'robot_company_master.csv');
|
const TOPIC_MASTER_PATH = path.join(CASE_ROOT, 'outputs', '数据表', 'robot_subindustry_master.csv');
|
const CLASS_PATH = path.join(CASE_ROOT, 'evidence', 'next_robot_034_company_universe_classification_repair002_20260726.csv');
|
const SNAPSHOT_PATH = path.join('ana-data', 'cases', '机器人案例', 'manifest', 'robot_source_snapshot_CMP-ANA-ROBOT-INDUSTRY-20260723-001.csv');
|
|
const OUTPUTS = {
|
transition: path.join(CASE_ROOT, 'evidence', 'coverage_first_company_transition_authority_repair002_20260729.csv'),
|
fields: path.join(CASE_ROOT, 'evidence', 'coverage_first_transition_field_universe_repair002_20260729.csv'),
|
gaps: path.join(CASE_ROOT, 'evidence', 'coverage_first_gap_source_field_universe_repair002_20260729.csv'),
|
controls: path.join(CASE_ROOT, 'evidence', 'coverage_first_control_enum_condition_authority_repair002_20260729.csv')
|
};
|
|
const TRANSITION_COLUMNS = [
|
'authority_id', 'company_id', 'canonical_name', 'cohort', 'source_item_id', 'source_item_fk',
|
'source_root_alias', 'source_relative_path', 'source_snapshot_row_id', 'source_locator',
|
'source_locator_policy', 'source_locator_text_sha256', 'classification_rule_id',
|
'reviewed_universe_layer', 'reviewed_priority_bucket', 'before_company_type', 'before_region',
|
'before_listed_status', 'before_primary_track', 'before_detail_track', 'before_candidate_status',
|
'before_formal_output_status', 'before_row_sha256', 'expected_after_coverage_universe_layer',
|
'expected_after_coverage_priority_bucket', 'expected_after_classification_source_item_id',
|
'expected_after_classification_rule_id', 'allowed_action', 'modifiable_fields', 'immutable_fields',
|
'required_review_status', 'formal_pool_effect', 'evidence_strength_cap'
|
];
|
|
const FIELD_COLUMNS = [
|
'field_authority_id', 'company_id', 'field_name', 'before_value', 'expected_after_rule',
|
'expected_after_value', 'transition_authority_id', 'source_item_fk',
|
'source_locator_text_sha256', 'allowed_action'
|
];
|
|
const GAP_COLUMNS = [
|
'gap_source_id', 'gap_source_table', 'gap_source_row_id', 'gap_source_field', 'gap_predicate',
|
'register_required_when', 'register_forbidden_when', 'required_gap_type_rule', 'row_id_authority'
|
];
|
|
const CONTROL_COLUMNS = [
|
'control_authority_id', 'field_name', 'object_scope', 'allowed_value', 'required_when',
|
'requires_gap_register', 'paired_field', 'paired_allowed_values', 'forbidden_combination',
|
'formal_pool_effect', 'evidence_strength_cap'
|
];
|
|
function parseCsv(text) {
|
const rows = [];
|
let row = [];
|
let field = '';
|
let quoted = false;
|
for (let i = 0; i < text.length; i += 1) {
|
const ch = text[i];
|
if (quoted) {
|
if (ch === '"' && text[i + 1] === '"') { field += '"'; i += 1; }
|
else if (ch === '"') quoted = false;
|
else field += ch;
|
} else if (ch === '"') quoted = true;
|
else if (ch === ',') { row.push(field); field = ''; }
|
else if (ch === '\n') {
|
row.push(field.endsWith('\r') ? field.slice(0, -1) : field);
|
field = '';
|
if (row.some((value) => value !== '')) rows.push(row);
|
row = [];
|
} else field += ch;
|
}
|
if (field.length || row.length) {
|
row.push(field.endsWith('\r') ? field.slice(0, -1) : field);
|
if (row.some((value) => value !== '')) rows.push(row);
|
}
|
const header = rows.shift().map((name, index) => index === 0 ? name.replace(/^\uFEFF/, '') : name);
|
return rows.map((values) => Object.fromEntries(header.map((name, index) => [name, values[index] ?? ''])));
|
}
|
|
function quote(value) { return `"${String(value ?? '').replaceAll('"', '""')}"`; }
|
function serialize(columns, rows) {
|
return `${[columns.map(quote).join(','), ...rows.map((row) => columns.map((column) => quote(row[column])).join(','))].join('\r\n')}\r\n`;
|
}
|
function sha256(value) { return crypto.createHash('sha256').update(value).digest('hex'); }
|
function requireUnique(rows, field, expected, label) {
|
const values = rows.map((row) => row[field]);
|
const unique = new Set(values);
|
if (rows.length !== expected || unique.size !== expected || values.some((value) => !value)) {
|
throw new Error(`${label}: expected ${expected} unique nonempty ${field}; rows=${rows.length}; unique=${unique.size}`);
|
}
|
}
|
|
function buildTransition(companyMaster, reviewed, snapshot) {
|
requireUnique(companyMaster, 'company_id', 307, 'company master');
|
requireUnique(reviewed, 'company_id', 190, 'reviewed classification');
|
requireUnique(snapshot, 'source_snapshot_row_id', 690, 'source snapshot');
|
const reviewedByCompany = new Map(reviewed.map((row) => [row.company_id, row]));
|
const snapshotById = new Map(snapshot.map((row) => [row.source_snapshot_row_id, row]));
|
const sourceOnly = companyMaster.filter((row) => row.candidate_status === 'SOURCE_ONLY_CANDIDATE');
|
requireUnique(sourceOnly, 'company_id', 117, 'source-only cohort');
|
const masterColumns = Object.keys(companyMaster[0]);
|
const rows = [...companyMaster].sort((a, b) => a.company_id.localeCompare(b.company_id, 'en')).map((company, index) => {
|
const classification = reviewedByCompany.get(company.company_id);
|
const sourceSnapshot = snapshotById.get(company.source_snapshot_row_id);
|
if (!sourceSnapshot) throw new Error(`missing snapshot ${company.source_snapshot_row_id}`);
|
if (sourceSnapshot.relative_path !== company.source_relative_path) throw new Error(`snapshot path mismatch ${company.company_id}`);
|
if (classification && classification.source_relative_path !== company.source_relative_path) throw new Error(`classification path mismatch ${company.company_id}`);
|
const reviewedRow = Boolean(classification);
|
const sourceItem = reviewedRow ? classification.raw_profile_id : sourceSnapshot.source_snapshot_row_id;
|
const locatorHash = reviewedRow ? classification.source_locator_text_sha256 : sourceSnapshot.normalized_sha256;
|
if (!sourceItem || !locatorHash) throw new Error(`missing exact source identity ${company.company_id}`);
|
return {
|
authority_id: `COVTRANS2-${String(index + 1).padStart(3, '0')}`,
|
company_id: company.company_id,
|
canonical_name: company.canonical_name,
|
cohort: reviewedRow ? 'REVIEWED_190' : 'SOURCE_ONLY_117',
|
source_item_id: sourceItem,
|
source_item_fk: sourceItem,
|
source_root_alias: sourceSnapshot.source_root_alias,
|
source_relative_path: company.source_relative_path,
|
source_snapshot_row_id: company.source_snapshot_row_id,
|
source_locator: reviewedRow ? classification.source_profile_locator : 'FULL_DOCUMENT_NORMALIZED_TEXT',
|
source_locator_policy: reviewedRow ? 'REVIEWED_EXACT_LINE_RANGE_AND_TEXT_SHA256' : 'SNAPSHOT_NORMALIZED_SHA256_EXACT_FULL_DOCUMENT',
|
source_locator_text_sha256: locatorHash,
|
classification_rule_id: reviewedRow ? classification.classification_rule_id : 'LOCAL_SOURCE_CLASSIFICATION_OR_EXPLICIT_GAP_V2',
|
reviewed_universe_layer: reviewedRow ? classification.universe_layer : 'PENDING_LOCAL_SOURCE_REVIEW',
|
reviewed_priority_bucket: reviewedRow ? classification.priority_bucket : 'PENDING_LOCAL_SOURCE_REVIEW',
|
before_company_type: company.company_type,
|
before_region: company.region,
|
before_listed_status: company.listed_status,
|
before_primary_track: company.primary_track,
|
before_detail_track: company.detail_track,
|
before_candidate_status: company.candidate_status,
|
before_formal_output_status: company.formal_output_status,
|
before_row_sha256: sha256(masterColumns.map((column) => company[column] ?? '').join('\u001f')),
|
expected_after_coverage_universe_layer: reviewedRow ? classification.universe_layer : 'ADJUDICATE_FROM_EXACT_SOURCE_OR_EXPLICIT_GAP',
|
expected_after_coverage_priority_bucket: reviewedRow ? classification.priority_bucket : 'ADJUDICATE_FROM_EXACT_SOURCE_OR_EXPLICIT_GAP',
|
expected_after_classification_source_item_id: sourceItem,
|
expected_after_classification_rule_id: reviewedRow ? classification.classification_rule_id : 'LOCAL_SOURCE_CLASSIFICATION_OR_EXPLICIT_GAP_V2',
|
allowed_action: reviewedRow ? 'INHERIT_REVIEWED_CLASSIFICATION_EXACTLY' : 'CLASSIFY_FROM_EXACT_FROZEN_SOURCE_OR_EXPLICIT_GAP',
|
modifiable_fields: 'company_type|region|listed_status|primary_track|detail_track_ONLY_IF_PLACEHOLDER_AND_EXACT_SOURCE_IDENTITY;coverage_universe_layer|coverage_priority_bucket',
|
immutable_fields: 'company_id|canonical_name|canonical_name_key|parent_company_id|aliases|source_company_name|source_profile_status|source_relative_path|source_snapshot_row_id|mapping_id|raw_status|candidate_status|formal_output_status|inventory_captured_at',
|
required_review_status: 'PENDING_INDEPENDENT_EXECUTION_REVIEW',
|
formal_pool_effect: 'NO_AUTOMATIC_FORMAL_POOL_CHANGE',
|
evidence_strength_cap: 'PENDING_VERIFICATION'
|
};
|
});
|
requireUnique(rows, 'authority_id', 307, 'transition authority');
|
requireUnique(rows, 'company_id', 307, 'transition authority');
|
if (rows.filter((row) => row.cohort === 'REVIEWED_190').length !== 190) throw new Error('reviewed count mismatch');
|
if (rows.filter((row) => row.cohort === 'SOURCE_ONLY_117').length !== 117) throw new Error('source-only count mismatch');
|
return rows;
|
}
|
|
function buildFieldUniverse(transition) {
|
const fields = [
|
['company_type', 'before_company_type'], ['region', 'before_region'],
|
['listed_status', 'before_listed_status'], ['primary_track', 'before_primary_track'],
|
['detail_track', 'before_detail_track'], ['coverage_universe_layer', 'expected_after_coverage_universe_layer'],
|
['coverage_priority_bucket', 'expected_after_coverage_priority_bucket'],
|
['classification_source_item_id', 'expected_after_classification_source_item_id'],
|
['classification_rule_id', 'expected_after_classification_rule_id']
|
];
|
const rows = [];
|
for (const authority of transition) {
|
for (const [fieldName, valueColumn] of fields) {
|
const classificationField = fieldName.startsWith('coverage_') || fieldName.startsWith('classification_');
|
const reviewed = authority.cohort === 'REVIEWED_190';
|
rows.push({
|
field_authority_id: `COVFIELD-${authority.company_id}-${fieldName}`,
|
company_id: authority.company_id,
|
field_name: fieldName,
|
before_value: classificationField ? '' : authority[valueColumn],
|
expected_after_rule: classificationField
|
? (reviewed || fieldName.startsWith('classification_') ? 'EXACT_VALUE' : 'EXACT_SOURCE_ADJUDICATION_OR_EXPLICIT_GAP')
|
: (reviewed ? 'EXACT_CANONICAL_INHERITANCE' : 'EXACT_CANONICAL_OR_PLACEHOLDER_FILL_WITH_SOURCE'),
|
expected_after_value: authority[valueColumn],
|
transition_authority_id: authority.authority_id,
|
source_item_fk: authority.source_item_fk,
|
source_locator_text_sha256: authority.source_locator_text_sha256,
|
allowed_action: reviewed ? 'INHERIT_EXACT' : 'INHERIT_OR_SOURCE_SUPPORTED_FILL_NO_STRENGTH_UPGRADE'
|
});
|
}
|
}
|
requireUnique(rows, 'field_authority_id', 2763, 'field universe');
|
if (new Set(rows.map((row) => `${row.company_id}\u001f${row.field_name}`)).size !== 2763) throw new Error('field universe composite duplicate');
|
return rows;
|
}
|
|
function buildGapUniverse(companyMaster, topicMaster) {
|
requireUnique(topicMaster, 'topic_id', 24, 'topic master');
|
const companyMasterPath = 'outputs/数据表/robot_company_master_coverage_v03_20260729.csv';
|
const topicMasterPath = 'outputs/数据表/robot_subindustry_master_coverage_v03_20260729.csv';
|
const companyCoveragePath = 'evidence/robot_company_coverage_status_20260729.csv';
|
const topicCoveragePath = 'evidence/robot_chain_topic_coverage_status_20260729.csv';
|
const rows = [];
|
const add = (table, rowId, field, predicate, required, forbidden, gapRule, rowAuthority) => rows.push({
|
gap_source_id: `COVGAPSRC-${String(rows.length + 1).padStart(4, '0')}`,
|
gap_source_table: table,
|
gap_source_row_id: rowId,
|
gap_source_field: field,
|
gap_predicate: predicate,
|
register_required_when: required,
|
register_forbidden_when: forbidden,
|
required_gap_type_rule: gapRule,
|
row_id_authority: rowAuthority
|
});
|
for (const company of [...companyMaster].sort((a, b) => a.company_id.localeCompare(b.company_id, 'en'))) {
|
for (const field of ['company_type_status', 'region_status', 'listed_status_status', 'primary_track_status']) {
|
add(companyMasterPath, company.company_id, field, 'VALUE_IN_GAP_STATUS_SET', 'value!=KNOWN_FROM_CANONICAL_AND_value!=FILLED_FROM_FROZEN_SOURCE_AND_value!=NOT_APPLICABLE', 'value=KNOWN_FROM_CANONICAL_OR_FILLED_FROM_FROZEN_SOURCE_OR_NOT_APPLICABLE', 'FIELD_STATUS_TO_GAP_TYPE', 'company_id');
|
}
|
add(companyCoveragePath, company.company_id, 'gap_status', 'VALUE_NOT_NONE', 'value!=NONE', 'value=NONE', 'COVERAGE_GAP_STATUS_TO_GAP_TYPE', 'company_id');
|
add(companyCoveragePath, company.company_id, 'link_closure_status', 'VALUE_NO_LINK_WITH_EXPLICIT_GAP', 'value=NO_LINK_WITH_EXPLICIT_GAP', 'value=LINK_PRESENT', 'NO_TOPIC_LINK', 'company_id');
|
}
|
for (const topic of [...topicMaster].sort((a, b) => a.topic_id.localeCompare(b.topic_id, 'en'))) {
|
add(topicMasterPath, topic.topic_id, 'coverage_gap_status', 'VALUE_NOT_NONE', 'value!=NONE', 'value=NONE', 'COVERAGE_GAP_STATUS_TO_GAP_TYPE', 'topic_id');
|
add(topicCoveragePath, topic.topic_id, 'gap_status', 'VALUE_NOT_NONE', 'value!=NONE', 'value=NONE', 'COVERAGE_GAP_STATUS_TO_GAP_TYPE', 'topic_id');
|
add(topicCoveragePath, topic.topic_id, 'topology_closure_status', 'VALUE_NO_TOPOLOGY_WITH_EXPLICIT_GAP', 'value=NO_TOPOLOGY_WITH_EXPLICIT_GAP', 'value=TOPOLOGY_PRESENT', 'NO_TOPIC_TOPOLOGY', 'topic_id');
|
}
|
requireUnique(rows, 'gap_source_id', 1914, 'gap universe');
|
if (new Set(rows.map((row) => `${row.gap_source_table}\u001f${row.gap_source_row_id}\u001f${row.gap_source_field}`)).size !== 1914) throw new Error('gap source composite duplicate');
|
return rows;
|
}
|
|
function buildControls() {
|
const rows = [];
|
const add = (field, scope, value, required, requiresGap, pairedField, pairedValues, forbidden, formalPool = 'NO_AUTOMATIC_FORMAL_POOL_CHANGE', strength = 'NO_UPGRADE') => rows.push({
|
control_authority_id: `COVCTRL-${String(rows.length + 1).padStart(3, '0')}`,
|
field_name: field, object_scope: scope, allowed_value: value, required_when: required,
|
requires_gap_register: requiresGap, paired_field: pairedField, paired_allowed_values: pairedValues,
|
forbidden_combination: forbidden, formal_pool_effect: formalPool, evidence_strength_cap: strength
|
});
|
const classificationStatuses = ['KNOWN_FROM_CANONICAL', 'FILLED_FROM_FROZEN_SOURCE', 'UNKNOWN_NOT_DISCLOSED', 'UNKNOWN_CONFLICTING_SOURCE', 'PENDING_LOCAL_SOURCE_REVIEW', 'NOT_APPLICABLE'];
|
for (const field of ['company_type_status', 'region_status', 'listed_status_status', 'primary_track_status']) {
|
for (const value of classificationStatuses) add(field, 'VERSIONED_COMPANY_MASTER', value, 'EXACTLY_ONE_VALUE_PER_COMPANY', ['UNKNOWN_NOT_DISCLOSED', 'UNKNOWN_CONFLICTING_SOURCE', 'PENDING_LOCAL_SOURCE_REVIEW'].includes(value) ? 'YES' : 'NO', 'gap_type', 'FIELD_STATUS_TO_GAP_TYPE', 'KNOWN_OR_FILLED_WITH_NONEMPTY_GAP;UNKNOWN_OR_PENDING_WITHOUT_GAP');
|
}
|
for (const value of ['EXISTING_FORMAL_VIEW_CANONICAL_LINK', 'CARD_PENDING_STAGE3', 'NOT_APPLICABLE_WITH_REASON']) add('company_card_status', 'COMPANY_COVERAGE', value, 'EXACTLY_ONE_VALUE_PER_COMPANY', value === 'CARD_PENDING_STAGE3' ? 'YES' : 'NO', 'formal_company_view_status', 'CONTRACT_DEFINED_PAIR', 'FORMAL_VIEW_AND_AUTOMATIC_CARD_PROMOTION');
|
for (const value of ['EXISTING_FORMAL_VIEW', 'NO_FORMAL_VIEW_CANDIDATE', 'NO_FORMAL_VIEW_EXCLUDED', 'NO_FORMAL_VIEW_SUPPLEMENT_REQUIRED']) add('formal_company_view_status', 'COMPANY_COVERAGE', value, 'EXACTLY_ONE_VALUE_PER_COMPANY', 'NO', 'company_card_status', 'CONTRACT_DEFINED_PAIR', 'EXISTING_FORMAL_VIEW_WITH_FORMAL_OUTPUT_STATUS_NOT_APPROVED');
|
for (const value of ['FORMAL_PAIR_PRESENT', 'SOURCE_INPUT_ONLY_HUMAN_OUTPUT_PENDING', 'HUMAN_OUTPUT_NOT_APPLICABLE_WITH_REASON']) add('human_output_coverage_status', 'TOPIC_COVERAGE', value, 'EXACTLY_ONE_VALUE_PER_TOPIC', value === 'SOURCE_INPUT_ONLY_HUMAN_OUTPUT_PENDING' ? 'YES' : 'NO', 'source_doc_pair_status', 'PAIR_PRESENT_OR_SOURCE_INPUT_ONLY', 'FORMAL_PAIR_PRESENT_WITH_ZERO_OUTPUTS');
|
for (const field of ['coverage_gap_status', 'gap_status']) {
|
for (const value of ['NONE', 'UNKNOWN_NOT_DISCLOSED', 'UNKNOWN_CONFLICTING_SOURCE', 'PENDING_LOCAL_SOURCE_REVIEW', 'NOT_APPLICABLE']) add(field, 'COMPANY_OR_TOPIC_COVERAGE', value, 'EXACTLY_ONE_VALUE_PER_ROW', ['UNKNOWN_NOT_DISCLOSED', 'UNKNOWN_CONFLICTING_SOURCE', 'PENDING_LOCAL_SOURCE_REVIEW'].includes(value) ? 'YES' : 'NO', 'gap_type', 'STATUS_TO_GAP_TYPE', 'NONE_WITH_GAP_REGISTER;NONNONE_WITHOUT_GAP_REGISTER');
|
}
|
for (const value of ['MISSING_CLASSIFICATION', 'CONFLICTING_CLASSIFICATION', 'NO_TOPIC_LINK', 'NO_TOPIC_TOPOLOGY', 'MISSING_SOURCE_LOCATOR', 'MISSING_HUMAN_OUTPUT', 'NOT_APPLICABLE', 'OTHER_REVIEW_REQUIRED']) add('gap_type', 'GAP_REGISTER', value, 'ONE_PER_REQUIRED_GAP_SOURCE', 'YES', 'object_type', 'GAP_TYPE_OBJECT_PAIR', 'GAP_TYPE_SOURCE_PREDICATE_MISMATCH');
|
for (const value of ['TOPIC', 'COMPANY', 'COMPANY_TOPIC_LINK', 'TOPIC_TOPOLOGY', 'COMPANY_COVERAGE', 'TOPIC_COVERAGE']) add('object_type', 'GAP_REGISTER_OR_LEDGER', value, 'CONTRACT_DEFINED', 'CONDITIONAL', 'gap_source_table', 'OBJECT_TABLE_PAIR', 'OBJECT_ID_FK_MISMATCH');
|
for (const value of ['LINK_PRESENT', 'NO_LINK_WITH_EXPLICIT_GAP']) add('link_closure_status', 'COMPANY_COVERAGE', value, 'EXACTLY_ONE_VALUE_PER_COMPANY', value === 'NO_LINK_WITH_EXPLICIT_GAP' ? 'YES' : 'NO', 'valid_link_count|no_link_gap_count', value === 'LINK_PRESENT' ? 'GE1|0' : '0|1', 'LINK_PRESENT_WITH_ZERO_LINKS_OR_GAP;NO_LINK_WITH_LINK_OR_NOT_EXACTLY_ONE_GAP');
|
for (const value of ['TOPOLOGY_PRESENT', 'NO_TOPOLOGY_WITH_EXPLICIT_GAP']) add('topology_closure_status', 'TOPIC_COVERAGE', value, 'EXACTLY_ONE_VALUE_PER_TOPIC', value === 'NO_TOPOLOGY_WITH_EXPLICIT_GAP' ? 'YES' : 'NO', 'valid_topology_count|no_topology_gap_count', value === 'TOPOLOGY_PRESENT' ? 'GE1|0' : '0|1', 'TOPOLOGY_PRESENT_WITH_ZERO_ROWS_OR_GAP;NO_TOPOLOGY_WITH_ROW_OR_NOT_EXACTLY_ONE_GAP');
|
for (const value of ['EXISTING_FORMAL_OUTPUT', 'CORE_CANDIDATE_REVIEWED_NOT_FORMAL', 'ADJACENT_CANDIDATE', 'OEM_RESEARCH_LAYER', 'SUPPLEMENT_REQUIRED', 'NOISE_OR_OUT_OF_SCOPE', 'PENDING_LOCAL_SOURCE_REVIEW']) add('coverage_universe_layer', 'VERSIONED_COMPANY_MASTER', value, 'EXACTLY_ONE_VALUE_PER_COMPANY', value === 'PENDING_LOCAL_SOURCE_REVIEW' ? 'YES' : 'NO', 'coverage_priority_bucket', 'LAYER_PRIORITY_CONTRACT', 'REVIEWED190_MISMATCH');
|
for (const value of ['P0', 'P1', 'P2', 'HOLD', 'EXCLUDE', 'PENDING_LOCAL_SOURCE_REVIEW']) add('coverage_priority_bucket', 'VERSIONED_COMPANY_MASTER', value, 'EXACTLY_ONE_VALUE_PER_COMPANY', value === 'PENDING_LOCAL_SOURCE_REVIEW' ? 'YES' : 'NO', 'coverage_universe_layer', 'LAYER_PRIORITY_CONTRACT', 'REVIEWED190_MISMATCH');
|
add('review_status', 'ALL_STAGE1_ROWS', 'PENDING_INDEPENDENT_EXECUTION_REVIEW', 'ALL_ROWS', 'NO', '', '', 'ANY_OTHER_VALUE');
|
add('formal_pool_effect', 'COMPANY_MASTER_LINK_COVERAGE', 'NO_AUTOMATIC_FORMAL_POOL_CHANGE', 'ALL_ROWS', 'NO', '', '', 'ANY_OTHER_VALUE');
|
add('evidence_strength_effect', 'TRANSITION_LEDGER', 'NO_UPGRADE', 'ALL_ROWS', 'NO', '', '', 'ANY_OTHER_VALUE');
|
requireUnique(rows, 'control_authority_id', rows.length, 'control authority');
|
return rows;
|
}
|
|
function build() {
|
const companyMaster = parseCsv(fs.readFileSync(COMPANY_MASTER_PATH, 'utf8'));
|
const topicMaster = parseCsv(fs.readFileSync(TOPIC_MASTER_PATH, 'utf8'));
|
const reviewed = parseCsv(fs.readFileSync(CLASS_PATH, 'utf8'));
|
const snapshot = parseCsv(fs.readFileSync(SNAPSHOT_PATH, 'utf8'));
|
const transition = buildTransition(companyMaster, reviewed, snapshot);
|
const fields = buildFieldUniverse(transition);
|
const gaps = buildGapUniverse(companyMaster, topicMaster);
|
const controls = buildControls();
|
return {
|
transition: serialize(TRANSITION_COLUMNS, transition),
|
fields: serialize(FIELD_COLUMNS, fields),
|
gaps: serialize(GAP_COLUMNS, gaps),
|
controls: serialize(CONTROL_COLUMNS, controls)
|
};
|
}
|
|
const output = build();
|
const summary = Object.fromEntries(Object.entries(output).map(([name, text]) => [name, { rows: parseCsv(text).length, bytes: Buffer.byteLength(text), sha256: sha256(text) }]));
|
if (process.argv.includes('--self-test')) {
|
if (summary.transition.rows !== 307 || summary.fields.rows !== 2763 || summary.gaps.rows !== 1914 || summary.controls.rows < 60) throw new Error(`unexpected summary ${JSON.stringify(summary)}`);
|
process.stdout.write(`SELF_TEST_PASS ${JSON.stringify(summary)}\n`);
|
} else if (process.argv.includes('--execute')) {
|
for (const [name, text] of Object.entries(output)) fs.writeFileSync(OUTPUTS[name], text, 'utf8');
|
process.stdout.write(`WROTE ${JSON.stringify(summary)}\n`);
|
} else {
|
process.stdout.write(`PREVIEW ${JSON.stringify(summary)}\n`);
|
}
|