mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-08-17 20:00:34 +00:00
294 lines
11 KiB
TypeScript
294 lines
11 KiB
TypeScript
import { createHash } from "crypto";
|
|
import { readFile, mkdir, writeFile } from "fs/promises";
|
|
import { dirname, join } from "path";
|
|
import { SOURCE_ADAPTERS, AUTHORITY_WEIGHTS, canImportNow, makeBlockedRecord, summarizeAdapterCoverage } from "./measurement_source_adapters.js";
|
|
|
|
/**
|
|
* Constants Registry Builder
|
|
*
|
|
* Builds a normalized constants registry from local seed packs and the measurement
|
|
* source adapter catalog.
|
|
*
|
|
* This module is deliberately conservative: it preserves exact/measured/environmental
|
|
* distinctions and refuses to flatten all values into generic numbers.
|
|
*/
|
|
|
|
type ConstantStatus =
|
|
| "exact"
|
|
| "derived_exact"
|
|
| "mathematical"
|
|
| "measured"
|
|
| "conventional"
|
|
| "environmental"
|
|
| "model"
|
|
| "terminology"
|
|
| "standard_definition"
|
|
| "experimental_dataset"
|
|
| "computed_reference"
|
|
| "time_series_reference"
|
|
| "unknown";
|
|
|
|
type RegistryRecord = {
|
|
record_id: string;
|
|
source_name: string;
|
|
source_version: string;
|
|
symbol: string;
|
|
display_symbol?: string;
|
|
name: string;
|
|
value: string;
|
|
unit: string;
|
|
unit_system: string;
|
|
dimension: string;
|
|
quantity_kind: string;
|
|
status: ConstantStatus;
|
|
standard_uncertainty: string | null;
|
|
relative_uncertainty: string | null;
|
|
coverage: string;
|
|
conditions: string;
|
|
source_uri: string;
|
|
provenance_hash: string;
|
|
license_scope: string;
|
|
audit_status: "seed" | "imported" | "verified" | "stale" | "conflict" | "deprecated" | "blocked";
|
|
authority_weight: number;
|
|
forest_role: string;
|
|
blocked_uses: string[];
|
|
notes: string;
|
|
adapter_id?: string;
|
|
};
|
|
|
|
type AnyRecord = Record<string, unknown>;
|
|
|
|
const ROOT = process.cwd();
|
|
|
|
const DEFAULT_INPUTS = [
|
|
"research-stack/constants/si_2019_exact_constants_v0.json",
|
|
"research-stack/constants/physical_constants_foundation_v0.json",
|
|
];
|
|
|
|
const STATUS_WEIGHT: Record<ConstantStatus, number> = {
|
|
exact: 0.98,
|
|
derived_exact: 0.95,
|
|
mathematical: 0.95,
|
|
measured: 0.76,
|
|
conventional: 0.70,
|
|
environmental: 0.48,
|
|
model: 0.62,
|
|
terminology: 0.55,
|
|
standard_definition: 0.80,
|
|
experimental_dataset: 0.60,
|
|
computed_reference: 0.58,
|
|
time_series_reference: 0.64,
|
|
unknown: 0.20,
|
|
};
|
|
|
|
function hashJson(value: unknown): string {
|
|
return createHash("sha256").update(JSON.stringify(value)).digest("hex");
|
|
}
|
|
|
|
function asString(value: unknown, fallback = ""): string {
|
|
if (value === null || value === undefined) return fallback;
|
|
return String(value);
|
|
}
|
|
|
|
function normalizeStatus(value: unknown): ConstantStatus {
|
|
const raw = asString(value, "unknown").trim();
|
|
if (raw in STATUS_WEIGHT) return raw as ConstantStatus;
|
|
if (raw === "definition_exact") return "exact";
|
|
if (raw === "reference") return "measured";
|
|
if (raw === "observational_time_series") return "time_series_reference";
|
|
return "unknown";
|
|
}
|
|
|
|
function blockedUsesFor(status: ConstantStatus): string[] {
|
|
switch (status) {
|
|
case "exact":
|
|
case "derived_exact":
|
|
case "mathematical":
|
|
return ["direct_basin_promotion", "substitute_for_empirical_conditions"];
|
|
case "measured":
|
|
return ["treat_as_exact", "use_without_uncertainty", "direct_basin_promotion"];
|
|
case "conventional":
|
|
return ["treat_as_local_measurement", "treat_as_universal_empirical_truth", "direct_basin_promotion"];
|
|
case "environmental":
|
|
return ["use_without_site_context", "treat_as_constant", "direct_basin_promotion"];
|
|
case "model":
|
|
return ["use_without_model_version", "use_outside_domain", "direct_basin_promotion"];
|
|
case "experimental_dataset":
|
|
return ["use_without_conditions", "use_without_instrument_metadata", "direct_basin_promotion"];
|
|
case "computed_reference":
|
|
return ["use_without_method_version", "treat_as_measurement", "direct_basin_promotion"];
|
|
case "time_series_reference":
|
|
return ["use_without_timestamp", "use_outside_temporal_validity", "direct_basin_promotion"];
|
|
default:
|
|
return ["route_without_audit", "direct_basin_promotion"];
|
|
}
|
|
}
|
|
|
|
function normalizeOne(raw: AnyRecord, sourceName: string, sourceVersion: string): RegistryRecord {
|
|
const status = normalizeStatus(raw.status ?? raw.exactness ?? raw.proof_status);
|
|
const symbol = asString(raw.symbol ?? raw.display_symbol ?? raw.name, "unknown_symbol");
|
|
const name = asString(raw.name ?? raw.title ?? symbol, symbol);
|
|
const unit = asString(raw.unit, "1");
|
|
const recordBasis = { sourceName, sourceVersion, symbol, name, value: raw.value ?? raw.exact_value ?? raw.formula ?? "" };
|
|
const provenance_hash = hashJson({ raw, recordBasis });
|
|
|
|
return {
|
|
record_id: `${sourceName}:${symbol}`.replace(/[^A-Za-z0-9_.:-]/g, "_"),
|
|
source_name: sourceName,
|
|
source_version: sourceVersion,
|
|
symbol,
|
|
display_symbol: asString(raw.display_symbol ?? raw.symbol, symbol),
|
|
name,
|
|
value: asString(raw.value ?? raw.exact_value ?? raw.formula, ""),
|
|
unit,
|
|
unit_system: asString(raw.unit_system, "SI_or_contextual"),
|
|
dimension: asString(raw.dimension, "unknown"),
|
|
quantity_kind: asString(raw.quantity_kind ?? raw.forest_role, "unknown_quantity"),
|
|
status,
|
|
standard_uncertainty: raw.standard_uncertainty == null ? null : asString(raw.standard_uncertainty),
|
|
relative_uncertainty: raw.relative_uncertainty == null ? null : asString(raw.relative_uncertainty),
|
|
coverage: asString(raw.coverage, "global_or_unspecified"),
|
|
conditions: asString(raw.conditions, "standard_or_unspecified"),
|
|
source_uri: asString(raw.source_uri, "local_seed_pack"),
|
|
provenance_hash,
|
|
license_scope: asString(raw.license_scope, "seed_metadata"),
|
|
audit_status: "seed",
|
|
authority_weight: Number(raw.authority_weight ?? STATUS_WEIGHT[status]),
|
|
forest_role: asString(raw.forest_role, "constant_anchor"),
|
|
blocked_uses: Array.isArray(raw.blocked_uses) ? raw.blocked_uses.map(String) : blockedUsesFor(status),
|
|
notes: asString(raw.notes ?? raw.exactness_note ?? raw.reason, ""),
|
|
};
|
|
}
|
|
|
|
function normalizeAdapterBoundary(adapter: (typeof SOURCE_ADAPTERS)[number]): RegistryRecord {
|
|
const blocked = makeBlockedRecord(adapter);
|
|
const status = normalizeStatus(blocked.status);
|
|
return {
|
|
record_id: blocked.record_id,
|
|
source_name: blocked.source_name,
|
|
source_version: blocked.source_version,
|
|
symbol: blocked.symbol,
|
|
display_symbol: blocked.symbol,
|
|
name: blocked.name,
|
|
value: blocked.value,
|
|
unit: blocked.unit,
|
|
unit_system: blocked.unit_system,
|
|
dimension: blocked.dimension,
|
|
quantity_kind: blocked.quantity_kind,
|
|
status,
|
|
standard_uncertainty: blocked.standard_uncertainty,
|
|
relative_uncertainty: blocked.relative_uncertainty,
|
|
coverage: blocked.coverage,
|
|
conditions: blocked.conditions,
|
|
source_uri: blocked.source_uri,
|
|
provenance_hash: blocked.provenance_hash,
|
|
license_scope: blocked.license_scope,
|
|
audit_status: blocked.audit_status,
|
|
authority_weight: blocked.authority_weight,
|
|
forest_role: blocked.forest_role,
|
|
blocked_uses: blocked.blocked_uses,
|
|
notes: `Source boundary from adapter ${adapter.id}. ${adapter.notes}`,
|
|
adapter_id: adapter.id,
|
|
};
|
|
}
|
|
|
|
function normalizeImportableAdapterBoundary(adapter: (typeof SOURCE_ADAPTERS)[number]): RegistryRecord {
|
|
const body = { adapter_id: adapter.id, source_home: adapter.sourceHome, status: "adapter_registered" };
|
|
return {
|
|
record_id: `${adapter.id}:adapter_registered`,
|
|
source_name: adapter.name,
|
|
source_version: "adapter_registered_v0",
|
|
symbol: adapter.id,
|
|
display_symbol: adapter.id,
|
|
name: `${adapter.name} adapter registration`,
|
|
value: "adapter_registered_no_values_imported_yet",
|
|
unit: "n/a",
|
|
unit_system: "n/a",
|
|
dimension: "n/a",
|
|
quantity_kind: "source_adapter",
|
|
status: normalizeStatus(adapter.defaultStatus),
|
|
standard_uncertainty: null,
|
|
relative_uncertainty: null,
|
|
coverage: adapter.group,
|
|
conditions: "Adapter registered; upstream values require explicit fetch/import/audit.",
|
|
source_uri: adapter.sourceHome || "",
|
|
provenance_hash: hashJson(body),
|
|
license_scope: adapter.licenseScope,
|
|
audit_status: "seed",
|
|
authority_weight: AUTHORITY_WEIGHTS[adapter.authorityClass],
|
|
forest_role: "measurement_source_adapter",
|
|
blocked_uses: ["routing_as_measurement_value", "direct_basin_promotion", "claiming_database_import_before_adapter_run"],
|
|
notes: adapter.notes,
|
|
adapter_id: adapter.id,
|
|
};
|
|
}
|
|
|
|
function extractRecordsFromPack(pack: AnyRecord): AnyRecord[] {
|
|
const records: AnyRecord[] = [];
|
|
for (const key of ["constants", "defining_constants", "exact_derived_from_defining_constants", "not_exact_after_2019"]) {
|
|
const value = pack[key];
|
|
if (Array.isArray(value)) records.push(...value as AnyRecord[]);
|
|
}
|
|
return records;
|
|
}
|
|
|
|
function dedupe(records: RegistryRecord[]): RegistryRecord[] {
|
|
const byId = new Map<string, RegistryRecord>();
|
|
for (const record of records) {
|
|
const key = record.record_id;
|
|
const existing = byId.get(key);
|
|
if (!existing || record.authority_weight > existing.authority_weight) {
|
|
byId.set(key, record);
|
|
}
|
|
}
|
|
return [...byId.values()].sort((a, b) => a.record_id.localeCompare(b.record_id));
|
|
}
|
|
|
|
async function loadPack(path: string): Promise<{ pack: AnyRecord; path: string }> {
|
|
const raw = await readFile(join(ROOT, path), "utf8");
|
|
return { pack: JSON.parse(raw) as AnyRecord, path };
|
|
}
|
|
|
|
export async function buildConstantsRegistry(inputPaths = DEFAULT_INPUTS, outputPath = "research-stack/constants/constants_registry_v0.json") {
|
|
const normalized: RegistryRecord[] = [];
|
|
|
|
for (const inputPath of inputPaths) {
|
|
const { pack } = await loadPack(inputPath);
|
|
const sourceName = asString(pack.pack_id ?? pack.catalog_id ?? inputPath, inputPath);
|
|
const sourceVersion = asString(pack.source_context && typeof pack.source_context === "object" ? (pack.source_context as AnyRecord).standard : pack.title, "v0");
|
|
const records = extractRecordsFromPack(pack);
|
|
for (const raw of records) normalized.push(normalizeOne(raw, sourceName, sourceVersion));
|
|
}
|
|
|
|
for (const adapter of SOURCE_ADAPTERS) {
|
|
normalized.push(canImportNow(adapter) ? normalizeImportableAdapterBoundary(adapter) : normalizeAdapterBoundary(adapter));
|
|
}
|
|
|
|
const deduped = dedupe(normalized);
|
|
const output = {
|
|
registry_id: "constants_registry_v0",
|
|
generated_utc: new Date().toISOString(),
|
|
source_inputs: inputPaths,
|
|
adapter_coverage: summarizeAdapterCoverage(),
|
|
record_count: deduped.length,
|
|
rule: "Exactness, uncertainty, source boundaries, and allowed-use limits are preserved. This registry does not promote basins by itself.",
|
|
records: deduped,
|
|
};
|
|
|
|
const fullOut = join(ROOT, outputPath);
|
|
await mkdir(dirname(fullOut), { recursive: true });
|
|
await writeFile(fullOut, JSON.stringify(output, null, 2) + "\n", "utf8");
|
|
return output;
|
|
}
|
|
|
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
buildConstantsRegistry()
|
|
.then((out) => {
|
|
console.log(`Wrote constants registry with ${out.record_count} records.`);
|
|
console.log(`Adapter count: ${out.adapter_coverage.adapter_count}`);
|
|
})
|
|
.catch((error) => {
|
|
console.error(error);
|
|
process.exit(1);
|
|
});
|
|
}
|