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 +1 -1
- package/server.json +2 -2
- package/tools/analytics.js +11 -1
- package/vendor/equipment-calibration.js +9 -3
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "formlab-mcp",
|
|
3
|
-
"version": "0.6.
|
|
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.
|
|
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.
|
|
16
|
+
"version": "0.6.55",
|
|
17
17
|
"runtimeHint": "npx",
|
|
18
18
|
"transport": {
|
|
19
19
|
"type": "stdio"
|
package/tools/analytics.js
CHANGED
|
@@ -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
|
|
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 || (
|
|
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 || '') : (
|
|
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>
|