// The model: replace this file with yours. server.mts calls runModel with the // case the platform sent (only the inputs manifest.json declares) and // reports what it returns. It runs on a thread of its own for each case, so // it may compute for as long as it needs, and it may be async. // // This one is the textbook scaled-distance law, as a worked example: the // peak particle velocity at each receiver from the site's own law and the // maximum instantaneous charge, PPV = K·(D/√Q)^−β, with its percentiles from // the law's log-normal spread. It declines what it cannot answer and says // why, which is what the platform checks a model for. type Receiver = { id: string; distanceM?: number }; type SiteLaw = { metric: string; basis: 'max_axis' | 'pvs'; scaling: string; k: number; beta: number; sigmaLn: number; scaledDistance?: { min?: number; max?: number }; }; type BlastCase = { receivers?: Receiver[]; design?: { maxInstantaneousCharge?: { massKg: number } }; calibration?: { siteLaw?: SiteLaw }; request: { outputs: string[]; receiverIds?: string[]; percentiles?: number[]; }; }; export type ModelAnswer = { outputs: unknown[]; unavailable: unknown[]; warnings: unknown[]; inputsUsed: string[]; }; /** Below this scaled distance (m/√kg) the law says nothing: the near field. */ const NEAR_FIELD = 2; /** * The standard normal quantile (Acklam's rational approximation, relative * error below 1.2e-9), for the percentiles. */ function normalQuantile(p: number): number { const a = [ -39.69683028665376, 220.9460984245205, -275.9285104469687, 138.357751867269, -30.66479806614716, 2.506628277459239, ]; const b = [ -54.47609879822406, 161.5858368580409, -155.6989798598866, 66.80131188771972, -13.28068155288572, ]; const c = [ -0.007784894002430293, -0.3223964580411365, -2.400758277161838, -2.549732539343734, 4.374664141464968, 2.938163982698783, ]; const d = [ 0.007784695709041462, 0.3224671290700398, 2.445134137142996, 3.754408661907416, ]; const low = 0.02425; if (p < low) { const q = Math.sqrt(-2 * Math.log(p)); return ( (((((c[0]! * q + c[1]!) * q + c[2]!) * q + c[3]!) * q + c[4]!) * q + c[5]!) / ((((d[0]! * q + d[1]!) * q + d[2]!) * q + d[3]!) * q + 1) ); } if (p > 1 - low) return -normalQuantile(1 - p); const q = p - 0.5; const r = q * q; return ( ((((((a[0]! * r + a[1]!) * r + a[2]!) * r + a[3]!) * r + a[4]!) * r + a[5]!) * q) / (((((b[0]! * r + b[1]!) * r + b[2]!) * r + b[3]!) * r + b[4]!) * r + 1) ); } export function runModel(blastCase: BlastCase): ModelAnswer { const inputsUsed = [ 'receivers', 'calibration.siteLaw', 'design.maxInstantaneousCharge', ]; const answer: ModelAnswer = { outputs: [], unavailable: [], warnings: [], inputsUsed, }; if (!blastCase.request.outputs.includes('receiver_ppv')) return answer; const named = blastCase.request.receiverIds; const receivers = (blastCase.receivers ?? []).filter( (receiver) => !named || named.includes(receiver.id), ); const law = blastCase.calibration?.siteLaw; const charge = blastCase.design?.maxInstantaneousCharge?.massKg; const decline = (receiverId: string | null, reason: string, detail: string) => answer.unavailable.push({ kind: 'receiver_ppv', receiverId, reason, detail, }); if (receivers.length === 0 || !law || !(charge! > 0)) { decline( null, 'missing_input', 'Needs receivers, the site law and the maximum instantaneous charge.', ); return answer; } if (law.metric !== 'ppv' || law.scaling !== 'square_root') { decline( null, 'insufficient_calibration', 'Needs a PPV law on square-root scaled distance.', ); return answer; } const percentiles = (blastCase.request.percentiles ?? [50, 95]).filter( (p) => p > 0 && p < 100 && p !== 50, ); for (const receiver of receivers) { if (!(receiver.distanceM! > 0)) { decline( receiver.id, 'missing_input', 'The receiver has no resolved distance.', ); continue; } const scaledDistance = receiver.distanceM! / Math.sqrt(charge!); if (scaledDistance < NEAR_FIELD) { decline( receiver.id, 'outside_validity', 'Inside the near field, where the law gives no figure.', ); continue; } const median = law.k * scaledDistance ** -law.beta; const range = law.scaledDistance ?? {}; const below = range.min !== undefined && scaledDistance < range.min; const above = range.max !== undefined && scaledDistance > range.max; answer.outputs.push({ kind: 'receiver_ppv', receiverId: receiver.id, basis: law.basis, ppvMmS: { median, percentiles: percentiles.map((p) => ({ p, value: median * Math.exp(law.sigmaLn * normalQuantile(p / 100)), })), }, scaledDistance, validity: below || above ? { status: 'extrapolated', code: below ? 'below_fitted_range' : 'above_fitted_range', } : { status: 'within' }, }); } answer.warnings.push({ code: 'interval.approximate', message: 'The percentiles use the law’s residual spread alone, not the uncertainty of its fit.', }); return answer; }