Download dev/constraints/models.js from Harp404/Space: direct link, hf CLI and curl.
- Browser
- Download file 8.3 kB
-
https://huggingface.co/spaces/Harp404/Space/resolve/main/dev/constraints/models.js
- Command line
-
hf download hf://spaces/Harp404/Space/dev/constraints/models.js
-
curl -L -o models.js https://huggingface.co/spaces/Harp404/Space/resolve/main/dev/constraints/models.js
8.3 kB
| /** | |
| * Model artefact loader. dev/constraints/models.js | |
| * ============================================================================= | |
| * Runs models that were trained offline (see ml/) as plain arithmetic. | |
| * | |
| * THE CONTRACT | |
| * | |
| * Training happens on a GPU, days before, in Python. What ships is a JSON | |
| * artefact of a few hundred numbers. This file walks those numbers. There is | |
| * no PyTorch in the request path, no Python service to keep alive, no model | |
| * download at demo time, and nothing that can be slow or absent when it | |
| * matters. | |
| * | |
| * THE OTHER HALF OF THE CONTRACT — and this is the part that matters | |
| * | |
| * A missing model is NOT a reason to guess. Every consumer of this module | |
| * must treat `null` as UNEVALUATED, which propagates to UNRESOLVED. The rules | |
| * are written that way already; this file simply never pretends. | |
| * | |
| * Concretely: no model here ever returns a default, a fallback, or a | |
| * "reasonable estimate". It returns a number or it returns null. | |
| * | |
| * VALIDATION | |
| * | |
| * An artefact whose feature list we do not recognise is REFUSED rather than | |
| * partially applied. A model silently fed the wrong columns is worse than no | |
| * model at all, because it is confidently wrong. | |
| * | |
| * Zero dependencies. | |
| * ============================================================================= | |
| */ | |
| ; | |
| const fs = require('fs'); | |
| const path = require('path'); | |
| const MODEL_DIR = path.join(__dirname, '..', 'cache', 'models'); | |
| const registry = new Map(); | |
| /** Feature names each artefact is allowed to reference — the anti-mismatch check. */ | |
| const KNOWN_FEATURES = new Set([ | |
| 'time_to_tca', 'miss_distance', 'relative_speed', | |
| 'relative_position_r', 'relative_position_t', 'relative_position_n', | |
| 't_sigma_r', 't_sigma_t', 't_sigma_n', | |
| 'c_sigma_r', 'c_sigma_t', 'c_sigma_n', | |
| 'max_risk_estimate', 'max_risk_scaling', 'mahalanobis_distance', | |
| 'c_object_type', 'risk', | |
| // maneuver detector / classifier | |
| 'mean_motion', 'eccentricity', 'inclination', 'bstar', 'rcs_class', | |
| 'delta_mean_motion', 'delta_bstar', 'apogee_km', 'perigee_km', | |
| // density model | |
| 'kp', 'f107', 'altitude_km', 'latitude', 'local_solar_time', 'doy', | |
| ]); | |
| /** Load every artefact present. Absent models are simply absent. */ | |
| function loadAll() { | |
| registry.clear(); | |
| let found = 0, refused = 0; | |
| if (!fs.existsSync(MODEL_DIR)) return { found, refused, models: [] }; | |
| for (const file of fs.readdirSync(MODEL_DIR)) { | |
| if (!file.endsWith('.json')) continue; | |
| try { | |
| const art = JSON.parse(fs.readFileSync(path.join(MODEL_DIR, file), 'utf8')); | |
| const check = validateArtefact(art); | |
| if (!check.ok) { | |
| console.log(`[models] REFUSED ${file}: ${check.reason}`); | |
| refused++; | |
| continue; | |
| } | |
| registry.set(art.name, art); | |
| found++; | |
| } catch (e) { | |
| console.log(`[models] REFUSED ${file}: ${e.message}`); | |
| refused++; | |
| } | |
| } | |
| return { | |
| found, refused, | |
| models: [...registry.values()].map((m) => ({ | |
| name: m.name, kind: m.kind, features: m.features.length, | |
| trained_on: m.training && m.training.dataset, | |
| held_out: m.training && (m.training.held_out_mae ?? m.training.held_out_score), | |
| feeds: m.feeds, | |
| })), | |
| }; | |
| } | |
| function validateArtefact(art) { | |
| if (!art || typeof art !== 'object') return { ok: false, reason: 'not an object' }; | |
| if (!art.name) return { ok: false, reason: 'no name' }; | |
| if (!Array.isArray(art.features)) return { ok: false, reason: 'no feature list' }; | |
| const unknown = art.features.filter((f) => !KNOWN_FEATURES.has(f)); | |
| if (unknown.length) { | |
| return { ok: false, reason: `unrecognised features ${unknown.join(', ')} — refusing rather than feeding the model the wrong columns` }; | |
| } | |
| if (art.kind === 'gradient_boosted_trees') { | |
| if (!Array.isArray(art.trees) || !art.trees.length) return { ok: false, reason: 'no trees' }; | |
| if (typeof art.init !== 'number') return { ok: false, reason: 'no init value' }; | |
| } | |
| if (art.kind === 'random_forest_classifier') { | |
| if (!Array.isArray(art.trees) || !art.trees.length) return { ok: false, reason: 'no trees' }; | |
| if (!Array.isArray(art.classes) || !art.classes.length) return { ok: false, reason: 'no class list' }; | |
| } | |
| return { ok: true }; | |
| } | |
| const get = (name) => registry.get(name) || null; | |
| const has = (name) => registry.has(name); | |
| // --------------------------------------------------------------------------- | |
| // Inference — walking exported decision trees | |
| // --------------------------------------------------------------------------- | |
| function walkTree(tree, x) { | |
| let node = 0; | |
| // -2 is scikit-learn's leaf marker. | |
| while (tree.feature[node] !== -2) { | |
| node = x[tree.feature[node]] <= tree.threshold[node] ? tree.left[node] : tree.right[node]; | |
| } | |
| return tree.value[node]; | |
| } | |
| /** | |
| * Score a feature object against a loaded model. | |
| * Returns null when the model is absent OR any required feature is missing. | |
| * Never substitutes a default — an incomplete input is an unknown. | |
| */ | |
| function walkTreeVec(tree, x) { | |
| let node = 0; | |
| while (tree.feature[node] !== -2) { | |
| node = x[tree.feature[node]] <= tree.threshold[node] ? tree.left[node] : tree.right[node]; | |
| } | |
| return tree.value[node]; | |
| } | |
| function predict(name, featureObj) { | |
| const art = get(name); | |
| if (!art) return null; | |
| const x = new Array(art.features.length); | |
| for (let i = 0; i < art.features.length; i++) { | |
| const f = art.features[i]; | |
| let v = featureObj[f]; | |
| const map = art.categorical_maps && art.categorical_maps[f]; | |
| if (map) v = map[String(v)]; | |
| const n = Number(v); | |
| // A missing feature makes the whole prediction unavailable. Imputing here | |
| // would manufacture confidence out of absence, which is the one thing this | |
| // system exists to refuse. | |
| if (!Number.isFinite(n)) { | |
| return { value: null, missing: f, reason: `feature "${f}" not available — prediction withheld` }; | |
| } | |
| x[i] = n; | |
| } | |
| if (art.kind === 'gradient_boosted_trees') { | |
| let acc = art.init; | |
| for (const t of art.trees) acc += art.learning_rate * walkTree(t, x); | |
| return { value: acc, model: art.name, held_out: art.training && art.training.held_out_mae }; | |
| } | |
| if (art.kind === 'random_forest_classifier') { | |
| // Average the per-class probability vectors across trees. | |
| const nCls = art.classes.length; | |
| const acc = new Array(nCls).fill(0); | |
| for (const t of art.trees) { | |
| const counts = walkTreeVec(t, x); | |
| const total = counts.reduce((a, b) => a + b, 0) || 1; | |
| for (let i = 0; i < nCls; i++) acc[i] += counts[i] / total; | |
| } | |
| let best = 0; | |
| for (let i = 1; i < nCls; i++) if (acc[i] > acc[best]) best = i; | |
| const confidence = acc[best] / art.trees.length; | |
| return { | |
| value: art.classes[best], | |
| confidence: +confidence.toFixed(3), | |
| probabilities: Object.fromEntries(art.classes.map((c, i) => [c, +(acc[i] / art.trees.length).toFixed(3)])), | |
| model: art.name, | |
| held_out: art.training && art.training.held_out_score, | |
| }; | |
| } | |
| if (art.kind === 'linear') { | |
| let acc = art.intercept || 0; | |
| for (let i = 0; i < x.length; i++) acc += (art.coefficients[i] || 0) * x[i]; | |
| return { value: acc, model: art.name, held_out: art.training && art.training.held_out_score }; | |
| } | |
| return null; | |
| } | |
| /** What the gateway reports about its own ML layer — including what is absent. */ | |
| function status() { | |
| const models = [...registry.values()]; | |
| return { | |
| loaded: models.length, | |
| models: models.map((m) => ({ | |
| name: m.name, | |
| kind: m.kind, | |
| feeds: m.feeds, | |
| trained_on: m.training && m.training.dataset, | |
| held_out: m.training && (m.training.held_out_mae ?? m.training.held_out_score), | |
| baseline: m.training && (m.training.baseline_persistence_mae ?? m.training.baseline_median_mae), | |
| honesty: m.honesty || [], | |
| })), | |
| contract: 'Models are trained offline and shipped as JSON coefficients; inference is plain arithmetic. A model that is absent, or whose inputs are incomplete, yields no prediction — the rule it feeds reports UNEVALUATED and the signal goes UNRESOLVED. No model ever supplies a rule verdict directly.', | |
| absent_is_not_a_guess: true, | |
| }; | |
| } | |
| module.exports = { loadAll, get, has, predict, status, validateArtefact, MODEL_DIR }; | |