Spaces:
Running
Running
Download engine.js from bitsnaps/dz-phone-value: direct link, hf CLI and curl.
- Browser
- Download file 5.34 kB
-
https://huggingface.co/spaces/bitsnaps/dz-phone-value/resolve/main/engine.js
- Command line
-
hf download hf://spaces/bitsnaps/dz-phone-value/engine.js
-
curl -L -o engine.js https://huggingface.co/spaces/bitsnaps/dz-phone-value/resolve/main/engine.js
5.34 kB
| /** | |
| * DZ Phone Value — ONNX inference engine (static HuggingFace Space). | |
| * | |
| * Feature engineering + post-processing are ported 1:1 from the production | |
| * engine sp_repo/src/lib/model/predict.ts (sp10-a2-refined), which is pinned | |
| * by two gates: model_export/sp_parity_check.mjs (12 Python-exported fixtures, | |
| * 0.1%) and scripts/check-golden.ts (byte-exact API goldens). The tree | |
| * ensembles run through onnxruntime-web from model.onnx (point / q10 / q90 | |
| * log-price margins, base_values baked — ORT output equals the TS engine's | |
| * pLog exactly; see scripts/sp_export_onnx.py for the conversion contract). | |
| */ | |
| (function (global) { | |
| "use strict"; | |
| let ART = null; | |
| function init(artifacts) { ART = artifacts; } | |
| function getArtifacts() { return ART; } | |
| function modelAgeYears(releaseYear) { | |
| var launch = new Date(releaseYear, 5, 30).getTime(); // ~mid-year launch assumption | |
| var age = (Date.now() - launch) / (365.25 * 24 * 3600 * 1000); | |
| return Math.max(0, Math.min(14, age)); | |
| } | |
| /** Mirror of predict.ts buildFeatures() — same order, same truthiness. */ | |
| function buildFeatures(inp) { | |
| var a = ART; | |
| var m = a.medians; | |
| var f = {}; | |
| // numeric block | |
| f.log2_storage = inp.storageGb ? Math.log2(inp.storageGb) : m.log2_storage; | |
| f.storage_missing = inp.storageGb ? 0 : 1; | |
| f.ram_gb = (inp.ramGb === null || inp.ramGb === undefined) ? m.ram_gb : inp.ramGb; | |
| f.ram_missing = inp.ramGb ? 0 : 1; | |
| f.model_age_years = inp.releaseYear ? modelAgeYears(inp.releaseYear) : m.model_age_years; | |
| f.age_missing = inp.releaseYear ? 0 : 1; | |
| f.battery_health = (inp.batteryHealth === null || inp.batteryHealth === undefined) ? m.battery_health : inp.batteryHealth; | |
| f.battery_missing = inp.batteryHealth ? 0 : 1; | |
| f.desc_damage = inp.damage ? 1 : 0; | |
| f.desc_original_parts = inp.originalParts ? 1 : 0; | |
| f.is_5g = inp.is5g ? 1 : 0; | |
| f.dual_sim = inp.dualSim ? 1 : 0; | |
| f.acc_count = (inp.accessories.box ? 1 : 0) + (inp.accessories.charger ? 1 : 0) + (inp.accessories.case ? 1 : 0); | |
| f.log_repost = Math.log1p(1); // fresh listing | |
| // tier dummies (reference: tier_none) | |
| var tiers = ["base", "compact", "plus", "pro", "promax", "ultra"]; | |
| for (var i = 0; i < tiers.length; i++) f["tier_" + tiers[i]] = inp.tier === tiers[i] ? 1 : 0; | |
| // brand dummies (reference: other) | |
| for (var b = 0; b < a.brands_top.length; b++) f["brand_" + a.brands_top[b]] = inp.brand === a.brands_top[b] ? 1 : 0; | |
| // region dummies (reference: other) | |
| for (var r = 0; r < a.regions_top.length; r++) f["region_" + a.regions_top[r]] = inp.region === a.regions_top[r] ? 1 : 0; | |
| // priceType dummies (reference: NEGOTIABLE) | |
| var pts = ["FIXED", "NONE", "OFFERED"]; | |
| for (var p = 0; p < pts.length; p++) f["priceType_" + pts[p]] = inp.priceType === pts[p] ? 1 : 0; | |
| // condition dummies (reference: like_new) | |
| var conds = ["good", "new", "refurb", "used"]; | |
| for (var c = 0; c < conds.length; c++) f["condition_" + conds[c]] = inp.condition === conds[c] ? 1 : 0; | |
| return f; | |
| } | |
| /** b_cols-ordered float vector (the ONNX input). */ | |
| function vecFromFeatures(f) { | |
| return ART.b_cols.map(function (c) { return f[c]; }); | |
| } | |
| function runSession(session, vec48) { | |
| var t = new ort.Tensor("float32", Float32Array.from(vec48), [1, 48]); | |
| return session.run({ input: t }).then(function (o) { | |
| return { point: o.point.data[0], q10: o.q10.data[0], q90: o.q90.data[0] }; | |
| }); | |
| } | |
| /** predict.ts post-processing: smear, CQR Q, clamp, round to 100 DZD. */ | |
| function postProcess(p_log, q10_log, q90_log) { | |
| var a = ART; | |
| var price = Math.exp(p_log) * a.smear; | |
| var lo = Math.exp(q10_log - a.cqr_Q_log) * a.smear; | |
| var hi = Math.exp(q90_log + a.cqr_Q_log) * a.smear; | |
| lo = Math.min(lo, price); // display clamp: band must contain the point | |
| hi = Math.max(hi, price); // (it can only widen the band, never shrink) | |
| var round100 = function (x) { return Math.round(x / 100) * 100; }; | |
| return { price: round100(price), lo: round100(lo), hi: round100(hi), priceRaw: price }; | |
| } | |
| async function predictAsync(inp, session) { | |
| var f = buildFeatures(inp); | |
| var o = await runSession(session, vecFromFeatures(f)); | |
| var r = postProcess(o.point, o.q10, o.q90); | |
| r.features = f; | |
| r.modelVersion = ART.model_version; | |
| return r; | |
| } | |
| /** Parity bridge: raw 48-feature vector (b_cols order) -> UNROUNDED | |
| * price/lo/hi — mirrors predict.ts predictPriceRawFromFeatures (no | |
| * round100, no clamp) so fixture parity matches the app's 0.1% contract. */ | |
| async function predictFromVectorAsync(vec48, session) { | |
| var o = await runSession(session, vec48); | |
| var a = ART; | |
| return { | |
| price: Math.exp(o.point) * a.smear, | |
| lo: Math.exp(o.q10 - a.cqr_Q_log) * a.smear, | |
| hi: Math.exp(o.q90 + a.cqr_Q_log) * a.smear, | |
| }; | |
| } | |
| var api = { init: init, getArtifacts: getArtifacts, buildFeatures: buildFeatures, | |
| modelAgeYears: modelAgeYears, vecFromFeatures: vecFromFeatures, | |
| postProcess: postProcess, predictAsync: predictAsync, | |
| predictFromVectorAsync: predictFromVectorAsync }; | |
| global.__spEngine = api; | |
| if (typeof module !== "undefined" && module.exports) module.exports = api; | |
| })(typeof window !== "undefined" ? window : globalThis); | |