formlab-mcp 0.6.0 → 0.6.3

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.
Files changed (2) hide show
  1. package/package.json +3 -3
  2. package/tools/analytics.js +38 -2
package/package.json CHANGED
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "formlab-mcp",
3
- "version": "0.6.0",
3
+ "version": "0.6.3",
4
4
  "mcpName": "io.github.juliu1980/formlab-mcp",
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.",
5
+ "description": "Read-only Model Context Protocol server for FormLab — 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",
7
7
  "bin": {
8
8
  "formlab-mcp": "./index.js"
@@ -38,4 +38,4 @@
38
38
  "url": "https://github.com/juliu1980/FormLab",
39
39
  "directory": "mcp"
40
40
  }
41
- }
41
+ }
@@ -503,6 +503,11 @@ const _DOE_TYPE_LABELS = {
503
503
  full: 'Full-factorial', pb: 'Plackett-Burman', ccd: 'Central Composite',
504
504
  bbd: 'Box-Behnken', lhs: 'Latin Hypercube', simplex: 'Simplex-Lattice', dopt: 'D-optimal',
505
505
  };
506
+ // The model key a design was optimised for. Only D-optimal stores one; the other
507
+ // generators impose their own structure, so there is nothing to report.
508
+ function _doeModelKey(d) {
509
+ return d && d.designType === 'dopt' ? ((d.settings || {}).doptModel || null) : null;
510
+ }
506
511
  // Factor names come off the RECORD first so a design still reads correctly
507
512
  // after an ingredient is renamed or deleted.
508
513
  function _doeNamer(d, db) {
@@ -526,8 +531,28 @@ function _trimDoeDesign(d, db) {
526
531
  factors: (d.factors || []).map(f => ({ ingredient: f.name || nameOf(f.ingredientId), low: f.low, high: f.high, unit: f.unit || '%' })),
527
532
  constraints: (d.constraints || []).map(c => _doeConstraintText(c, nameOf)),
528
533
  mixtureMode: !!d.mixtureMode,
534
+ // The model the design was built for. Lives in settings, so without this a
535
+ // caller listing designs can see "D-optimal" but not whether it was fitted
536
+ // as a process or a Scheffé (mixture) model — which changes what the runs
537
+ // and the D-efficiency mean.
538
+ model: _doeModelKey(d) || null,
539
+ modelLabel: _DOE_MODEL_LABELS[_doeModelKey(d)] || _doeModelKey(d) || null,
529
540
  // Only D-optimal computes one; null here means "not applicable", not "failed".
530
541
  dEfficiency: Number.isFinite(d.dEfficiency) ? +d.dEfficiency.toFixed(2) : null,
542
+ dEfficiencyKind: Number.isFinite(d.dEfficiency)
543
+ ? (d.dEfficiencyKind || (/^scheffe-/.test(String((d.settings || {}).doptModel || '')) ? 'relative' : 'absolute'))
544
+ : null,
545
+ // The relative/absolute PAIR is the point: relative is the grade ("as good as
546
+ // this region allows"), absolute is what the rules cost. Shipping only the
547
+ // relative one lets a caller read 100% on a heavily restricted region and
548
+ // conclude the restrictions were free. Null unless the two actually differ.
549
+ // Both are percentages on a 0-100 scale. The absolute one goes very small on a
550
+ // tightly restricted region (a real mixture measured 0.027), so it keeps more
551
+ // decimals below 1 — toFixed(2) alone would report 0.03, and the UI's own
552
+ // toFixed(0) renders it "0%".
553
+ dEfficiencyAbsolute: Number.isFinite(d.dEfficiencyAbsolute)
554
+ ? +d.dEfficiencyAbsolute.toFixed(d.dEfficiencyAbsolute < 1 ? 4 : 2)
555
+ : null,
531
556
  droppedRuns: d.droppedRuns || 0,
532
557
  response: d.responseText || null,
533
558
  createdAt: d.createdAt,
@@ -615,7 +640,16 @@ const get_doe_design = {
615
640
  const linked = (d.formulationIds || []).map(id => formsById.get(id)).filter(Boolean);
616
641
  const out = {
617
642
  ...base,
618
- balanceFactor: d.mixtureMode ? nameOf(d.balanceFactorId) : null,
643
+ // A balance factor is the component that absorbs the slack when the mixture
644
+ // is imposed on a design that knows nothing about it (the classical types).
645
+ // Simplex-Lattice and Scheffé D-optimal design every component on the
646
+ // simplex, so there is nothing to absorb and balanceFactorId is legitimately
647
+ // null. Passing that null to nameOf() fell through its `id || '?'` tail and
648
+ // answered "?", which reads as missing data rather than as "not applicable".
649
+ balanceFactor: d.mixtureMode && d.balanceFactorId ? nameOf(d.balanceFactorId) : null,
650
+ balanceFactorNote: d.mixtureMode && !d.balanceFactorId
651
+ ? 'No balance factor: this design places its runs on the blend simplex directly, so every component is designed and each run already sums to the mixture total. Nothing has to absorb the remainder.'
652
+ : undefined,
619
653
  settings,
620
654
  augmentedFromProjectId: d.augmentedFromProjectId || null,
621
655
  formulationsGenerated: (d.formulationIds || []).length,
@@ -623,7 +657,9 @@ const get_doe_design = {
623
657
  notes: d.notes || null,
624
658
  };
625
659
  if (Number.isFinite(d.dEfficiency)) {
626
- out.dEfficiencyNote = 'D-efficiency compares designs fitting the SAME model at the SAME run count. It is a relative score, not a pass mark, and cannot be compared across different models.';
660
+ out.dEfficiencyNote = out.dEfficiencyKind === 'relative'
661
+ ? 'RELATIVE D-efficiency: this design against the best achievable for the same model and run budget IN ITS OWN DESIGN REGION, so 100% means "as good as this model and budget allow there". It is NOT the textbook absolute measure. It is used whenever the orthogonal ideal is unreachable by construction — a mixture (components collinear, so even a perfect mixture design scores about 33% on the absolute scale) or a region carved by linear constraints (an orthogonal design generally does not survive the cut). Do not compare this figure with an unconstrained design\'s D-efficiency or with another tool\'s.'
662
+ : 'ABSOLUTE D-efficiency: 100·|XᵀX|^(1/p)/N on coded factors, where 100% is the orthogonal ideal. Compare only against designs fitting the SAME model at the SAME run count; it is not a pass mark and does not transfer across model forms.';
627
663
  }
628
664
  if (args.include_runs !== false) {
629
665
  const facs = d.factors || [];