formlab-mcp 0.6.54 → 0.6.55

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "formlab-mcp",
3
- "version": "0.6.54",
3
+ "version": "0.6.55",
4
4
  "mcpName": "io.github.juliu1980/formlab-mcp",
5
5
  "description": "Read-only Model Context Protocol server for FormLab \u2014 lets Claude (and other MCP clients) read and analyze your FormLab data, from a local export file OR your live cloud workspace.",
6
6
  "type": "module",
package/server.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
3
3
  "name": "io.github.juliu1980/formlab-mcp",
4
4
  "description": "Read-only MCP for FormLab — let Claude query your formulation lab. Free reads a local export; Pro connects to your live cloud workspace with a dedicated read-only token.",
5
- "version": "0.6.54",
5
+ "version": "0.6.55",
6
6
  "websiteUrl": "https://formvix.com/mcp",
7
7
  "repository": {
8
8
  "url": "https://github.com/juliu1980/FormLab",
@@ -13,7 +13,7 @@
13
13
  {
14
14
  "registryType": "npm",
15
15
  "identifier": "formlab-mcp",
16
- "version": "0.6.54",
16
+ "version": "0.6.55",
17
17
  "runtimeHint": "npx",
18
18
  "transport": {
19
19
  "type": "stdio"
@@ -1279,6 +1279,16 @@ function _iChart(ys) {
1279
1279
 
1280
1280
  // Shelf life: fit vs elapsed months, extrapolate to the spec limit the trend
1281
1281
  // heads toward. Point-estimate crossing + the conservative 95% CI-bound crossing.
1282
+ // One-sided 95% Student t for df = n − 2 (ICH Q1E) — a copy of the app's _stabT95
1283
+ // (js/views/stability.js); test/stability-ich-t.test.js holds the two equal.
1284
+ const _T95_TABLE = [null, 6.314, 2.920, 2.353, 2.132, 2.015, 1.943, 1.895, 1.860, 1.833, 1.812, 1.796, 1.782, 1.771, 1.761, 1.753, 1.746, 1.740, 1.734, 1.729, 1.725, 1.721, 1.717, 1.714, 1.711, 1.708, 1.706, 1.703, 1.701, 1.699, 1.697];
1285
+ function _t95(df) {
1286
+ df = Math.floor(df);
1287
+ if (!(df >= 1)) return Infinity;
1288
+ if (df < _T95_TABLE.length) return _T95_TABLE[df];
1289
+ const z = 1.6448536;
1290
+ return z + (z ** 3 + z) / (4 * df) + (5 * z ** 5 + 16 * z ** 3 + 3 * z) / (96 * df * df);
1291
+ }
1282
1292
  function _shelfLife(points, spec) {
1283
1293
  if (!points || points.length < 3 || (spec.min == null && spec.max == null)) return null;
1284
1294
  const t0 = Math.min(...points.map(p => p.date.getTime()));
@@ -1295,7 +1305,7 @@ function _shelfLife(points, spec) {
1295
1305
  const ssX = xs.reduce((a, b) => a + (b - mx) ** 2, 0);
1296
1306
  let ssR = 0; xs.forEach((x, i) => { const pr = fit.intercept + fit.slope * x; ssR += (ys[i] - pr) ** 2; });
1297
1307
  const sResid = Math.sqrt(ssR / Math.max(1, n - 2));
1298
- const tcrit = n > 30 ? 1.96 : n > 10 ? 2.1 : 2.5;
1308
+ const tcrit = _t95(n - 2); // ICH Q1E one-sided 95% (app 22t, js/views/stability.js _stabT95)
1299
1309
  const sign = fit.slope < 0 ? -1 : 1;
1300
1310
  const bound = (tt) => fit.intercept + fit.slope * tt + sign * tcrit * sResid * Math.sqrt(1 / n + ((tt - mx) ** 2) / Math.max(1e-9, ssX));
1301
1311
  let ciRemaining = null;
@@ -23,6 +23,12 @@ const EQ_CAL_RESULTS = [
23
23
  ];
24
24
  const _EQ_CAL_RESULT_LABEL = Object.fromEntries(EQ_CAL_RESULTS.map(r => [r.value, r.label]));
25
25
 
26
+ // Today in LOCAL time (flTodayIso when utils.js is loaded; the MCP copy passes its own).
27
+ function _eqCalToday() {
28
+ if (typeof flTodayIso === 'function') return flTodayIso();
29
+ const d = new Date();
30
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
31
+ }
26
32
  function eqCalOn() { return typeof hasFeature !== 'function' || hasFeature('equipment.calibration'); }
27
33
 
28
34
  // Newest first; entries without a date last.
@@ -54,7 +60,7 @@ function _eqCalDays(fromIso, toIso) {
54
60
  // 'ok'
55
61
  // due: the next due date (the latest entry's own nextDue, else its date + the interval)
56
62
  function eqCalStatus(e, todayIso) {
57
- const today = todayIso || (typeof flTodayIso === 'function' ? flTodayIso() : new Date().toISOString().slice(0, 10));
63
+ const today = todayIso || _eqCalToday();
58
64
  const interval = Number(e && e.calIntervalMonths) || 0;
59
65
  const hist = eqCalHistory(e);
60
66
  const last = hist[0] || null;
@@ -174,13 +180,13 @@ function openEqCalLogModal(eqId, calId) {
174
180
  if (!e) return;
175
181
  const c = calId ? (e.calibrations || []).find(x => x && x.id === calId) : null;
176
182
  const interval = Number(e.calIntervalMonths) || 0;
177
- const date = c ? (c.date || '') : (typeof flTodayIso === 'function' ? flTodayIso() : new Date().toISOString().slice(0, 10));
183
+ const date = c ? (c.date || '') : _eqCalToday();
178
184
  const files = Array.isArray(e.fileAttachments) ? e.fileAttachments : [];
179
185
  const certOpts = `<option value="">— None —</option>` + files.map(a => `<option value="${esc(a.id)}"${c && c.certificateId === a.id ? ' selected' : ''}>${esc(a.name || a.title || a.url || a.id)}</option>`).join('');
180
186
  const hint = (t) => `<div style="font-size:11.5px;color:var(--text-muted);margin-top:4px;line-height:1.4">${t}</div>`;
181
187
  const body = `<div class="form-grid">
182
188
  <div class="form-group"><label>Date calibrated *</label><input id="eq-cal-date" type="date" value="${esc(date)}" onchange="_eqCalAutoDue(${interval})"></div>
183
- <div class="form-group"><label>Performed by</label><input id="eq-cal-by" value="${esc(c ? (c.by || '') : _eqCalMe())}" placeholder="Name, or the service provider"></div>
189
+ <div class="form-group"><label>Performed by</label><input id="eq-cal-by" value="${esc(c ? (c.by || '') : _eqCalMe())}" placeholder="Name, or the service provider" data-fl-person="equipment.calibrations[].by testResults.performedBy equipment.owner"></div>
184
190
  <div class="form-group span-2"><label>Result *</label>
185
191
  <select id="eq-cal-result">${EQ_CAL_RESULTS.map(r => `<option value="${r.value}"${(c ? c.result : 'pass') === r.value ? ' selected' : ''}>${esc(r.label)} — ${esc(r.hint)}</option>`).join('')}</select>
186
192
  </div>