@barivia/barsom-mcp 0.15.3 → 0.17.0
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/README.md +14 -7
- package/dist/index.js +1 -1
- package/dist/job_status_format.js +62 -6
- package/dist/shared.js +25 -4
- package/dist/tools/datasets.js +24 -1
- package/dist/tools/explore_map.js +1 -1
- package/dist/tools/inference.js +43 -4
- package/dist/tools/jobs.js +16 -56
- package/dist/tools/results.js +34 -62
- package/dist/tools/train.js +73 -133
- package/dist/tools/training_core.js +126 -9
- package/dist/tools/training_guidance.js +1 -1
- package/dist/tools/training_monitor.js +65 -6
- package/dist/tools/training_prep.js +211 -21
- package/dist/views/src/views/training-monitor/index.html +30 -16
- package/dist/views/src/views/training-prep/index.html +25 -11
- package/dist/viz-server.js +19 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @barivia/barsom-mcp
|
|
2
2
|
|
|
3
|
-
MCP proxy for the Barivia Analytics Engine — connects any stdio MCP client (Cursor, Claude Desktop, etc.) to the barSOM cloud API. The npm package is **`@barivia/barsom-mcp`**; many configs label the server **`analytics-engine`** (the MCP server name in the client JSON). **`guide_barsom_workflow`** is the canonical bootstrap: it loads plan-scoped workflow text from the Barivia API when online (tool map, async rules, training modes, SOP). If the API is unreachable, it returns a short offline stub. Core tools are `datasets`, `train` (`map`, `siom_map`, `impute`, `floop_siom` where entitled
|
|
3
|
+
MCP proxy for the Barivia Analytics Engine — connects any stdio MCP client (Cursor, Claude Desktop, etc.) to the barSOM cloud API. The npm package is **`@barivia/barsom-mcp`**; many configs label the server **`analytics-engine`** (the MCP server name in the client JSON). **`guide_barsom_workflow`** is the canonical bootstrap: it loads plan-scoped workflow text from the Barivia API when online (tool map, async rules, training modes, SOP). If the API is unreachable, it returns a short offline stub. Core tools are `datasets`, `train` (`map`, `siom_map`, `impute`, `floop_siom` where entitled), `jobs` (status/list/compare/cancel/delete — lifecycle only), `results` (get/export/download/recolor), and `inference` (predict/batch_predict/impute_column/compare/project_columns/transition_flow/report). **Optional MCP App tools** (`training_prep`, `training_monitor`, `results_explorer`) add embedded or localhost viz; they are **not required** to complete upload → train → poll → results, though `results_explorer` is the preferred way to browse figures.
|
|
4
4
|
|
|
5
5
|
**Pre-training help (pick one):** **`prepare_training`** prompt = narrative checklist (tier-scoped from the API when online); **`training_guidance`** tool = structured presets and parameter hints; **`training_prep`** tool = interactive UI plus **`submit_prepared_training`**.
|
|
6
6
|
|
|
@@ -103,10 +103,11 @@ All training is submitted via the `train` tool; every action is async and return
|
|
|
103
103
|
| Action | Use when |
|
|
104
104
|
|--------|----------|
|
|
105
105
|
| `map` | New map training job — full control: model type, grid, epochs, cyclic/temporal features, transforms. Complete-case data only (no NaNs in training columns). |
|
|
106
|
-
| `impute` | Sparse training
|
|
106
|
+
| `impute` | Sparse training: missSOM / SIOM missSOM — map + `imputed.csv`. Supports **`transforms`**, **`temporal_features`**, **`cyclic_features`**, **`normalization_methods`** (zscore/mad/sigmoidal/none — **not** sepd or rank). Defaults: `model=auto`, `cv_folds=5`. Valid parent for `inference(impute_column)`. |
|
|
107
107
|
| `siom_map` | Self-interacting map — same grid-map workflow plus SIOM controls such as `gamma`, `siom_decay`, and penalty selection. |
|
|
108
108
|
| `floop_siom` | FLooP-SIOM — growing node-budget manifold; default `topology=free` (CHL); optional `topology=chain` for strict 1D linked list. Requires Premium/Enterprise (`all_algorithms`). |
|
|
109
|
-
|
|
109
|
+
|
|
110
|
+
For a quick first map, use `train(action=map, preset=quick)` (or `normalize=mad` with a grid sized from the preview row count) — see the `baseline_explore` preset in `training_guidance`.
|
|
110
111
|
|
|
111
112
|
### `jobs(action)` — lifecycle
|
|
112
113
|
|
|
@@ -118,7 +119,7 @@ All training is submitted via the `train` tool; every action is async and return
|
|
|
118
119
|
| `cancel` | Stopping a running job |
|
|
119
120
|
| `delete` | Permanently removing a job + its S3 files |
|
|
120
121
|
|
|
121
|
-
`jobs
|
|
122
|
+
`jobs` is **lifecycle only**. Submit training with the `train` tool and run scoring / post-training map ops with `inference`. (The old `jobs(action=train_*/run_baseline_study/batch_predict)` forwarders were **removed in 0.17.0** — see Migration notes.)
|
|
122
123
|
|
|
123
124
|
### `results(action)`
|
|
124
125
|
|
|
@@ -128,7 +129,8 @@ All training is submitted via the `train` tool; every action is async and return
|
|
|
128
129
|
| `export` | instant | Learning curve (training_log), weight matrix (weights), node stats (nodes) |
|
|
129
130
|
| `download` | instant | Saving figures to a local folder |
|
|
130
131
|
| `recolor` | async | Changing colormap or output format without retraining |
|
|
131
|
-
|
|
132
|
+
|
|
133
|
+
Temporal state-transition analysis moved to **`inference(action=transition_flow)`** in 0.17.0 (read the resulting figure with `results(action=get/download)`).
|
|
132
134
|
|
|
133
135
|
All visualizations and metrics come from **`results(action=get)`**. Grid-map jobs return combined map figures, component planes, and quality metrics; FLooP-SIOM jobs return structure/coverage figures, occupation/profile views, and metrics. Use `figures=all` or `export_type=...` for more. There is no separate `analyze` tool.
|
|
134
136
|
|
|
@@ -142,6 +144,7 @@ All actions use a frozen trained map — no retraining. Derived columns use **`d
|
|
|
142
144
|
| `impute_column` | Fill a numeric **target_column** not used in training: **requires** `dataset_id` + `target_column`. Parent job: completed **train(action=map)** or **train(action=impute)**. Dataset must contain all training features plus the target. Pools observed target values from rows mapped to this row's BMU and topology neighbors (BMU + neighbors, often 7 nodes on hex interior; fewer on borders unless the map is periodic). `only_missing` (default true); `impute_aggregation`: mean or median. **Not** held-out validated — map-local estimate. Output **`imputed.csv`**. For holes across many training columns, prefer **train(action=impute)** first. | 5–120s |
|
|
143
145
|
| `compare` | density-diff heatmap + top gained/lost nodes — drift, A/B, cohort | 30–120s |
|
|
144
146
|
| `project_columns` | Project one or more dataset columns onto the trained map (component planes) | async |
|
|
147
|
+
| `transition_flow` | Temporal state-transition arrows on the trained map (time-ordered rows only). Pass the training `job_id`; returns a new result `job_id` for `results(get/download)`. `lag`/`min_transitions`/`top_k` tune it. | 30–60s |
|
|
145
148
|
| `report` | Report **manifest** (figure names, download URLs, metrics, cluster summary) — sync; use with `results(download)` on the training `job_id` for `report.pdf` when present; build custom PDFs in Quarto/Jupyter | immediate |
|
|
146
149
|
|
|
147
150
|
### `account(action)`
|
|
@@ -161,7 +164,7 @@ All actions use a frozen trained map — no retraining. Derived columns use **`d
|
|
|
161
164
|
|------|------|
|
|
162
165
|
| `training_prep` | Review variables, transforms, hyperparameters before submit |
|
|
163
166
|
| `submit_prepared_training` | Submit the reviewed prep (`review_token` + `explicit_confirm=true`) |
|
|
164
|
-
| `training_monitor` |
|
|
167
|
+
| `training_monitor` | Live QE + topographic-error curves (ordering/convergence, dual y-axis), FLooP step progress when applicable; optional — `jobs(action=status)` is enough |
|
|
165
168
|
| `results_explorer` | Browse metrics and figures after training completes |
|
|
166
169
|
| `_fetch_figure` | **Host / App only** — Results Explorer invokes this for one raster figure; not for agent chat |
|
|
167
170
|
|
|
@@ -179,7 +182,11 @@ The right viewer depends on **(MCP App support)** **and** **(can the human reach
|
|
|
179
182
|
|
|
180
183
|
### Migration notes
|
|
181
184
|
|
|
182
|
-
- **
|
|
185
|
+
- **Surface cleanup (0.17.0, breaking):**
|
|
186
|
+
- `train(action=baseline_study)` **removed** — use `train(action=map, preset=quick)` (the `baseline_explore` preset in `training_guidance`).
|
|
187
|
+
- `results(action=transition_flow)` **removed** — it is now **`inference(action=transition_flow)`** (a frozen-map operation). Read the resulting figure with `results(action=get/download)` on the returned job_id.
|
|
188
|
+
- The deprecated `jobs(action=train_map | train_siom_map | train_impute | train_floop_siom | run_baseline_study | batch_predict)` forwarders are **removed**. Submit training via `train`; run scoring / map ops via `inference`. `jobs` is lifecycle-only and no longer accepts training parameters.
|
|
189
|
+
- **Training moved to the `train` tool (0.14.0):** submit training via `train(action=map | siom_map | impute | floop_siom)`; `jobs` now covers lifecycle only (`status`/`list`/`compare`/`cancel`/`delete`). `batch_predict` is `inference(action=batch_predict)`. (The 0.14.0–0.16.x deprecated `jobs(train_*)` forwarders were removed in 0.17.0; see above.) Update Cursor / Claude Desktop configs and prompts to the new tools.
|
|
183
190
|
- **`explore_map` removed (0.13.0):** the deprecated alias of `results_explorer` is gone. Update any Cursor / Claude Desktop / MCP config or call site to use **`results_explorer`**.
|
|
184
191
|
- **`train_floop_chain` removed (0.13.0):** the deprecated alias of `jobs(action=train_floop_siom)` is gone. Use **`train(action=floop_siom)`**.
|
|
185
192
|
- **Shorter `info` prompt:** Clients that relied on the old long `info` text should use **`guide_barsom_workflow`** or server **instructions** for the full story.
|
package/dist/index.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{McpServer as e}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as o}from"zod";import{getUiCapability as r,registerAppResource as n,RESOURCE_MIME_TYPE as s}from"@modelcontextprotocol/ext-apps/server";import{startVizServer as a}from"./viz-server.js";import{API_KEY as i,apiCall as l,apiRawCall as p,loadViewHtml as c,setVizPort as m,setClientSupportsMcpApps as d,CLIENT_VERSION as u}from"./shared.js";import{registerDatasetsTool as f}from"./tools/datasets.js";import{registerTrainTool as g}from"./tools/train.js";import{registerJobsTool as _,JOBS_DESCRIPTION_BASE as b}from"./tools/jobs.js";import{registerResultsTool as y}from"./tools/results.js";import{registerExploreMapTool as h,RESULTS_EXPLORER_URI as w}from"./tools/explore_map.js";import{registerAccountTool as j}from"./tools/account.js";import{registerInferenceTool as v}from"./tools/inference.js";import{registerGuideBarsomTool as P}from"./tools/guide_barsom.js";import{registerTrainingGuidanceTool as x}from"./tools/training_guidance.js";import{registerFeedbackTool as I}from"./tools/feedback.js";import{registerTrainingPrepTools as k,TRAINING_PREP_URI as T}from"./tools/training_prep.js";import{registerTrainingMonitorTool as M,TRAINING_MONITOR_URI as O}from"./tools/training_monitor.js";import{resolvePrepareTrainingPromptText as A}from"./prepare_training_prompt.js";i||(console.error("Error: BARIVIA_API_KEY not set. Set it in your MCP client config."),process.exit(1));const S=new e({name:"analytics-engine",version:u,instructions:'# Barivia Mapping Analytics Engine\n\nSelf-organizing map (SOM) analytics: project high-dimensional data to a 2D grid for clusters, gradients, and anomalies.\n\n## Workflow (short)\n\nUpload (`datasets(upload)`) → `datasets(preview)` and `datasets(analyze)` before train → submit one of `train(action=map)`, `train(action=siom_map)`, or `train(action=floop_siom)` (only if plan allows FLooP) → poll `jobs(status)` every 10–15s until `completed` → `results(get)` for metrics and figures (there is no separate analyze tool). Then `jobs(compare)`, `results(download/recolor/transition_flow)`, or `inference` as needed.\n\n**Full detail:** Call `guide_barsom_workflow` for plan-scoped tool map, training modes, async rules, optional MCP App UIs, and step-by-step SOP (from the Barivia API when online).\n\n## Tool map (compact)\n\n| Area | Tool | Notes |\n|------|------|--------|\n| Data | `datasets` | upload, preview, analyze, list, **get** (status/staging), subset, add_expression, reduce_spectral, delete |\n| Train | `train` | map, siom_map, impute (sparse data: map + dense imputed.csv), floop_siom (entitled), baseline_study — all async, returns job_id |\n| Jobs | `jobs` | status, list, compare, cancel, delete (lifecycle). jobs(train_*/run_baseline_study/batch_predict) still work as deprecated forwarders → use train / inference |\n| Results | `results` | get (figures="none" for metrics-only), export, download, recolor (async), transition_flow (async; time-ordered rows only) |\n| Inference | `inference` | predict (regime-aware; output="compact"|"annotated"), batch_predict (fan out many predict jobs), impute_column (neighbor-pool fill for a non-training column), compare, project_columns, report |\n| Account | `account` | status, burst/compute actions, history, add_funds |\n| Bootstrap | `guide_barsom_workflow` | orientation + SOP |\n| Parameters | `training_guidance` | presets and field hints (API-scoped) |\n| Prep | `prepare_training` prompt, `training_prep` + `submit_prepared_training` | checklist / interactive UI |\n| Explore | `results_explorer`, `training_monitor` | optional MCP Apps; `jobs(status)` and `results(get)` suffice without them |\n| Other | `send_feedback` | only after user agrees |\n\n## Async pattern\n\n- **Manual poll:** Training submits return `job_id` immediately — poll `jobs(status)` every 10–15s. **Running is not failed**; large grids or FLooP-SIOM can take many minutes. `max_nodes` (FLooP) is a total node budget, not grid side length.\n- **Often auto-polled:** `inference` actions, `results(recolor)`, `results(transition_flow)` may wait in-proxy; if you get a `job_id`, poll `jobs(status)` the same way.\n\nCredits: jobs consume compute credits; check `account(status)` before big runs. Per-request HTTP timeout defaults to 60s (`datasets(analyze)` is async + auto-polled, so it is not bound by it); on slow networks raise `BARIVIA_FETCH_TIMEOUT_MS`. Timeouts are NOT auto-retried (avoids duplicating slow/expensive work).\n\n## Constraints\n\n- Prep ladder: `prepare_training` prompt = narrative checklist; `training_guidance` = structured hints; `training_prep` = UI + guarded submit. Do not guess tiers or FLooP entitlement.\n- `inference(predict)`: prefer `dataset_id` for batch and for SIOM/irregular maps; single-row `rows` uses a fast path that can fail on some topologies — retry with `dataset_id`. FLooP-SIOM: if predict jobs fail while grid SIOM works, capture errors + `job_id`.\n- Column names are case-sensitive — match `datasets(preview)`.\n- Default training path is numeric/cyclic/temporal; use explicit `categorical_features` for baseline categoricals. `predict` must match the model contract.\n- After `recolor`, `transition_flow`, or `project_columns`, use the **new** `job_id` returned for follow-up `results` if applicable.'});n(S,w,w,{mimeType:s},async()=>{const e=await c("results-explorer");return{contents:[{uri:w,mimeType:s,text:e??"<html><body>Results Explorer view not built yet. Run: npm run build:views</body></html>"}]}}),n(S,T,T,{mimeType:s},async()=>{const e=await c("training-prep");return{contents:[{uri:T,mimeType:s,text:e??"<html><body>Training Preparation view not built yet.</body></html>"}]}}),n(S,O,O,{mimeType:s},async()=>{const e=await c("training-monitor");return{contents:[{uri:O,mimeType:s,text:e??"<html><body>Training Monitor view not built yet.</body></html>"}]}}),P(S),h(S),k(S),M(S),f(S),g(S),_(S,b),y(S),j(S),v(S),x(S),I(S),S.prompt("info","Short orientation for the Barivia Mapping MCP. For full plan-scoped workflow, tool map, and SOP, the model should call guide_barsom_workflow. Use when the user asks what this MCP can do or how to get started.",{},()=>({messages:[{role:"user",content:{type:"text",text:["Give a concise, scannable answer (headers + bullets):","","**What it is:** MCP client to the Barivia mapping engine (2D SOM / SIOM / FLooP-SIOM when entitled) over HTTPS.","","**First step:** Call `guide_barsom_workflow` for plan-scoped bootstrap (full tool list, async rules, training modes, optional MCP Apps, SOP).","","**Core path:** `datasets(upload)` → `datasets(preview)` + `datasets(analyze)` → choose training action → poll `jobs(status)` every 10–15s until completed → `results(get)` (all main figures/metrics; no separate analyze tool).","",'**Key tools:** `datasets` (data; reduce_spectral for spectra/long blocks), `train` (map/siom_map/impute/floop_siom/baseline_study; pass `label` for readable compare rows), `jobs` (status/list/compare/cancel/delete), `results` (get/download/export/recolor/transition_flow; figures="none" for metrics-only), `inference` (predict; batch_predict; impute_column for topology-neighbor pool fill; compare; project_columns; report), `account` (status/credits/queue).',"","**Prep help:** `prepare_training` prompt (checklist) · `training_guidance` (presets/JSON hints) · `training_prep` + `submit_prepared_training` (interactive UI).","","**Optional UI:** `results_explorer`, `training_monitor` — nice for browsing; not required if you use `results` + `jobs(status)`.","","**After training:** `jobs(compare)` across runs, `results(recolor)`, `inference(project_columns)` for variables not in training, `transition_flow` only if rows are time-ordered.","","**Rules:** Running ≠ failed. Column names must match `datasets(preview)` exactly. Do not call `_fetch_figure` from chat (host/UI only); use `results(get)` or `results_explorer`.","","Offer `send_feedback` only after asking the user."].join("\n")}}]})),S.prompt("prepare_training","Narrative pre-training checklist (prompt). Use after upload and before train. Content is tier-scoped from the API when online. Prep ladder: this prompt = story checklist; training_guidance tool = JSON presets/parameter hints; training_prep tool = interactive UI + submit_prepared_training.",{dataset_id:o.string().describe("Dataset ID to prepare for training")},async({dataset_id:e})=>({messages:[{role:"user",content:{type:"text",text:await A(e)}}]}));const C=new t;(async function(){try{const e=await a(l,p,c);m(e)}catch(e){process.env.BARIVIA_VIZ_PORT&&console.error("Barivia viz server failed to start:",e)}const e=S.server;e.oninitialized=()=>{const t=e.getClientCapabilities(),o=r(t);d(!!o?.mimeTypes?.includes(s))},await S.connect(C)})().catch(console.error);
|
|
2
|
+
import{McpServer as e}from"@modelcontextprotocol/sdk/server/mcp.js";import{StdioServerTransport as t}from"@modelcontextprotocol/sdk/server/stdio.js";import{z as o}from"zod";import{getUiCapability as r,registerAppResource as n,RESOURCE_MIME_TYPE as s}from"@modelcontextprotocol/ext-apps/server";import{startVizServer as i}from"./viz-server.js";import{API_KEY as a,apiCall as l,apiRawCall as p,loadViewHtml as c,setVizPort as m,setClientSupportsMcpApps as d,CLIENT_VERSION as u}from"./shared.js";import{registerDatasetsTool as f}from"./tools/datasets.js";import{registerTrainTool as g}from"./tools/train.js";import{registerJobsTool as _,JOBS_DESCRIPTION_BASE as h}from"./tools/jobs.js";import{registerResultsTool as b}from"./tools/results.js";import{registerExploreMapTool as y,RESULTS_EXPLORER_URI as w}from"./tools/explore_map.js";import{registerAccountTool as j}from"./tools/account.js";import{registerInferenceTool as v}from"./tools/inference.js";import{registerGuideBarsomTool as x}from"./tools/guide_barsom.js";import{registerTrainingGuidanceTool as P}from"./tools/training_guidance.js";import{registerFeedbackTool as k}from"./tools/feedback.js";import{registerTrainingPrepTools as I,TRAINING_PREP_URI as T}from"./tools/training_prep.js";import{registerTrainingMonitorTool as A,TRAINING_MONITOR_URI as M}from"./tools/training_monitor.js";import{resolvePrepareTrainingPromptText as S}from"./prepare_training_prompt.js";a||(console.error("Error: BARIVIA_API_KEY not set. Set it in your MCP client config."),process.exit(1));const O=new e({name:"analytics-engine",version:u,instructions:'# Barivia Mapping Analytics Engine\n\nSelf-organizing map (SOM) analytics: project high-dimensional data to a 2D grid for clusters, gradients, and anomalies.\n\n## Workflow (short)\n\nUpload (`datasets(upload)`) → `datasets(preview)` and `datasets(analyze)` before train → submit one of `train(action=map)`, `train(action=siom_map)`, or `train(action=floop_siom)` (only if plan allows FLooP) → poll `jobs(status)` every 10–15s until `completed` → `results(get)` for metrics and figures (there is no separate analyze tool), then `results_explorer(job_id)` to browse the figures interactively. Then `jobs(compare)`, `results(download/recolor)`, or `inference` (predict / compare / project_columns / impute_column / transition_flow) as needed.\n\n**Full detail:** Call `guide_barsom_workflow` for plan-scoped tool map, training modes, async rules, optional MCP App UIs, and step-by-step SOP (from the Barivia API when online).\n\n## Tool taxonomy (one tool per stage — do not mix)\n\n| Stage | Tool(s) | Contract |\n|-------|---------|----------|\n| Data | `datasets` | upload, preview, analyze, list, **get** (status/staging), subset, add_expression, reduce_spectral, delete — ingest + feature engineering, no map |\n| Train submit | `train` **or** `training_prep` → `submit_prepared_training` | map, siom_map, impute (sparse → map + dense imputed.csv), floop_siom (entitled). Async, returns job_id. Submit only |\n| Lifecycle | `jobs` | status, list, compare (**training runs**), cancel, delete. No training/scoring here |\n| Artifacts | `results` | get (figures="none" for metrics-only), export, download, recolor (async). Read/render completed jobs |\n| Frozen-map ops | `inference` | predict (regime-aware; "compact"|"annotated"), batch_predict, impute_column (neighbor-pool fill for a non-training column), compare (cohort density-diff), project_columns, transition_flow (time-ordered rows only), report. No weight updates |\n| Account | `account` | status, burst/compute actions, history, add_funds |\n| Bootstrap | `guide_barsom_workflow`, `training_guidance`, `prepare_training` prompt | orientation, parameter hints, narrative checklist (API/tier-scoped) |\n| Explore (UI) | `results_explorer`, `training_prep`, `training_monitor` | richer UX over the same API; `jobs(status)`+`results(get)` still suffice headless |\n| Other | `send_feedback` | only after user agrees |\n\n### Avoid these mis-routes\n- `jobs(compare)` = metrics table across **training runs** (pick best in a sweep). `inference(compare)` = density-diff of one map vs a **second dataset** (drift/cohort). Different tools.\n- Post-training map analysis (predict, compare, project_columns, impute_column, **transition_flow**) is all **inference** — not `results`. `results` is presentation/artifacts only (`recolor` stays here: it re-renders, it doesn\'t touch data).\n- Project a quantity onto the map: existing columns → `inference(project_columns)`; a formula → `datasets(add_expression, project_onto_job=<job>)`.\n\n## Async pattern\n\n- **Manual poll:** Training submits return `job_id` immediately — poll `jobs(status)` every 10–15s. **Running is not failed**; large grids or FLooP-SIOM can take many minutes. `max_nodes` (FLooP) is a total node budget, not grid side length.\n- **Often auto-polled:** `inference` actions (incl. `transition_flow`) and `results(recolor)` may wait in-proxy; if you get a `job_id`, poll `jobs(status)` the same way.\n\nCredits: jobs consume compute credits; check `account(status)` before big runs. Per-request HTTP timeout defaults to 60s (`datasets(analyze)` is async + auto-polled, so it is not bound by it); on slow networks raise `BARIVIA_FETCH_TIMEOUT_MS`. Timeouts are NOT auto-retried (avoids duplicating slow/expensive work).\n\n## Constraints\n\n- Prep ladder: `prepare_training` prompt = narrative checklist; `training_guidance` = structured hints; `training_prep` = interactive UI that shows the **resolved per-column normalization / transforms / grid / epochs** and lets the user tweak before a guarded `submit_prepared_training`. Prefer `training_prep` when the user wants to review or fine-tune preprocessing before committing a job; use `train` directly when parameters are already confirmed. Do not guess tiers or FLooP entitlement.\n- `inference(predict)`: prefer `dataset_id` for batch and for SIOM/irregular maps; single-row `rows` uses a fast path that can fail on some topologies — retry with `dataset_id`. FLooP-SIOM: if predict jobs fail while grid SIOM works, capture errors + `job_id`.\n- Column names are case-sensitive — match `datasets(preview)`.\n- Default training path is numeric/cyclic/temporal; use explicit `categorical_features` for baseline categoricals. `predict` must match the model contract.\n- After `results(recolor)`, `inference(transition_flow)`, or `inference(project_columns)`, use the **new** `job_id` returned for follow-up `results` if applicable.'});n(O,w,w,{mimeType:s},async()=>{const e=await c("results-explorer");return{contents:[{uri:w,mimeType:s,text:e??"<html><body>Results Explorer view not built yet. Run: npm run build:views</body></html>"}]}}),n(O,T,T,{mimeType:s},async()=>{const e=await c("training-prep");return{contents:[{uri:T,mimeType:s,text:e??"<html><body>Training Preparation view not built yet.</body></html>"}]}}),n(O,M,M,{mimeType:s},async()=>{const e=await c("training-monitor");return{contents:[{uri:M,mimeType:s,text:e??"<html><body>Training Monitor view not built yet.</body></html>"}]}}),x(O),y(O),I(O),A(O),f(O),g(O),_(O,h),b(O),j(O),v(O),P(O),k(O),O.prompt("info","Short orientation for the Barivia Mapping MCP. For full plan-scoped workflow, tool map, and SOP, the model should call guide_barsom_workflow. Use when the user asks what this MCP can do or how to get started.",{},()=>({messages:[{role:"user",content:{type:"text",text:["Give a concise, scannable answer (headers + bullets):","","**What it is:** MCP client to the Barivia mapping engine (2D SOM / SIOM / FLooP-SIOM when entitled) over HTTPS.","","**First step:** Call `guide_barsom_workflow` for plan-scoped bootstrap (full tool list, async rules, training modes, optional MCP Apps, SOP).","","**Core path:** `datasets(upload)` → `datasets(preview)` + `datasets(analyze)` → choose training action → poll `jobs(status)` every 10–15s until completed → `results(get)` (all main figures/metrics; no separate analyze tool) → `results_explorer(job_id)` to browse figures.","",'**Key tools:** `datasets` (data; reduce_spectral for spectra/long blocks), `train` (map/siom_map/impute/floop_siom; pass `label` for readable compare rows; preset=quick for a fast first map), `jobs` (status/list/compare/cancel/delete — lifecycle only), `results` (get/download/export/recolor; figures="none" for metrics-only), `inference` (predict; batch_predict; impute_column for topology-neighbor pool fill; compare; project_columns; transition_flow; report), `account` (status/credits/queue).',"","**Prep help:** `prepare_training` prompt (checklist) · `training_guidance` (presets/JSON hints) · `training_prep` + `submit_prepared_training` (interactive UI showing resolved normalization/params to review before submit).","","**Browse results:** `results_explorer(job_id)` is the preferred way to explore figures after a first `results(get)` glance; `training_monitor` shows live progress. Both optional — `results` + `jobs(status)` suffice headless.","","**After training:** `jobs(compare)` across runs, `results(recolor)`, `inference(project_columns)` for variables not in training, `inference(transition_flow)` only if rows are time-ordered.","","**Rules:** Running ≠ failed. Column names must match `datasets(preview)` exactly. Do not call `_fetch_figure` from chat (host/UI only); use `results(get)` or `results_explorer`.","","Offer `send_feedback` only after asking the user."].join("\n")}}]})),O.prompt("prepare_training","Narrative pre-training checklist (prompt). Use after upload and before train. Content is tier-scoped from the API when online. Prep ladder: this prompt = story checklist; training_guidance tool = JSON presets/parameter hints; training_prep tool = interactive UI + submit_prepared_training.",{dataset_id:o.string().describe("Dataset ID to prepare for training")},async({dataset_id:e})=>({messages:[{role:"user",content:{type:"text",text:await S(e)}}]}));const C=new t;(async function(){try{const e=await i(l,p,c);m(e)}catch(e){process.env.BARIVIA_VIZ_PORT&&console.error("Barivia viz server failed to start:",e)}const e=O.server;e.oninitialized=()=>{const t=e.getClientCapabilities(),o=r(t);d(!!o?.mimeTypes?.includes(s))},await O.connect(C)})().catch(console.error);
|
|
@@ -1,4 +1,36 @@
|
|
|
1
1
|
/** Format jobs(status) text from GET /v1/jobs/:id payload. Exported for tests. */
|
|
2
|
+
function formatElapsedPart(data, status) {
|
|
3
|
+
const wall = data.wall_elapsed_sec != null && !Number.isNaN(Number(data.wall_elapsed_sec))
|
|
4
|
+
? Number(data.wall_elapsed_sec)
|
|
5
|
+
: null;
|
|
6
|
+
const kernel = data.kernel_elapsed_sec != null && !Number.isNaN(Number(data.kernel_elapsed_sec))
|
|
7
|
+
? Number(data.kernel_elapsed_sec)
|
|
8
|
+
: data.training_elapsed_sec != null && !Number.isNaN(Number(data.training_elapsed_sec))
|
|
9
|
+
? Number(data.training_elapsed_sec)
|
|
10
|
+
: null;
|
|
11
|
+
if (wall != null && wall >= 0) {
|
|
12
|
+
const wallR = Math.round(wall);
|
|
13
|
+
if (status === "completed" &&
|
|
14
|
+
kernel != null &&
|
|
15
|
+
kernel >= 0 &&
|
|
16
|
+
Math.abs(wallR - Math.round(kernel)) >= 2) {
|
|
17
|
+
return `elapsed: ${wallR}s (kernel training: ${Math.round(kernel)}s)`;
|
|
18
|
+
}
|
|
19
|
+
return `elapsed: ${wallR}s`;
|
|
20
|
+
}
|
|
21
|
+
if (kernel != null && kernel >= 0) {
|
|
22
|
+
return `elapsed: ${Math.round(kernel)}s`;
|
|
23
|
+
}
|
|
24
|
+
const startedAt = data.started_at != null ? String(data.started_at) : null;
|
|
25
|
+
if (startedAt) {
|
|
26
|
+
const startedMs = Date.parse(startedAt);
|
|
27
|
+
if (!Number.isNaN(startedMs)) {
|
|
28
|
+
const elapsedSec = Math.max(0, Math.round((Date.now() - startedMs) / 1000));
|
|
29
|
+
return `elapsed: ${elapsedSec}s`;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
2
34
|
export function formatJobStatusText(job_id, data) {
|
|
3
35
|
const status = String(data.status ?? "unknown");
|
|
4
36
|
const progress = (data.progress ?? 0) * 100;
|
|
@@ -23,10 +55,20 @@ export function formatJobStatusText(job_id, data) {
|
|
|
23
55
|
const startedAt = data.started_at != null ? String(data.started_at) : null;
|
|
24
56
|
if (startedAt) {
|
|
25
57
|
parts.push(`started_at: ${startedAt}`);
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
58
|
+
}
|
|
59
|
+
const elapsedPart = formatElapsedPart(data, status);
|
|
60
|
+
if (elapsedPart) {
|
|
61
|
+
parts.push(elapsedPart);
|
|
62
|
+
}
|
|
63
|
+
if (status === "running") {
|
|
64
|
+
const etaSec = data.training_eta_sec != null ? Number(data.training_eta_sec) : null;
|
|
65
|
+
if (etaSec != null && etaSec > 0) {
|
|
66
|
+
parts.push(`ETA ~${Math.round(etaSec)}s`);
|
|
67
|
+
}
|
|
68
|
+
const epoch = data.epoch != null ? Number(data.epoch) : null;
|
|
69
|
+
const totalEpochs = data.total_epochs != null ? Number(data.total_epochs) : null;
|
|
70
|
+
if (epoch != null && totalEpochs != null && totalEpochs > 0) {
|
|
71
|
+
parts.push(`epoch ${epoch}/${totalEpochs}`);
|
|
30
72
|
}
|
|
31
73
|
}
|
|
32
74
|
const datasetRows = data.dataset_rows != null ? Number(data.dataset_rows) : null;
|
|
@@ -48,11 +90,25 @@ export function formatJobStatusText(job_id, data) {
|
|
|
48
90
|
const finalizeId = data.finalize_job_id != null && String(data.finalize_job_id) !== ""
|
|
49
91
|
? String(data.finalize_job_id)
|
|
50
92
|
: null;
|
|
51
|
-
|
|
93
|
+
const finalizeFailed = data.finalize_failed === true;
|
|
94
|
+
const finalizeStatus = data.finalize_status != null && String(data.finalize_status) !== ""
|
|
95
|
+
? String(data.finalize_status)
|
|
96
|
+
: null;
|
|
97
|
+
if (finalizeFailed) {
|
|
98
|
+
const stage = data.finalize_failure_stage != null && String(data.finalize_failure_stage) !== ""
|
|
99
|
+
? ` [${String(data.finalize_failure_stage)}]`
|
|
100
|
+
: "";
|
|
101
|
+
parts.push(`Training kernel completed but finalize_training failed${stage} (finalize_job_id=${finalizeId ?? "unknown"}). ` +
|
|
102
|
+
`${data.finalize_error ?? "No artifacts uploaded — poll finalize job or retry finalize before results(get)."}`);
|
|
103
|
+
}
|
|
104
|
+
else if (finalizeId) {
|
|
52
105
|
parts.push(`Training kernel done; finalize_training still running (finalize_job_id=${finalizeId}). Poll jobs(action=status) again to auto-wait, or poll that job directly before results(get).`);
|
|
53
106
|
}
|
|
107
|
+
else if (finalizeStatus === "pending" || finalizeStatus === "running") {
|
|
108
|
+
parts.push(`Training kernel done; finalize_training ${finalizeStatus}. Poll jobs(action=status) before results(get).`);
|
|
109
|
+
}
|
|
54
110
|
else {
|
|
55
|
-
parts.push(`Results ready. Use results(action=get, job_id="${job_id}") to
|
|
111
|
+
parts.push(`Results ready. Use results(action=get, job_id="${job_id}") for metrics, then results_explorer(job_id="${job_id}") to browse the figures interactively.`);
|
|
56
112
|
}
|
|
57
113
|
}
|
|
58
114
|
else if (status === "failed") {
|
package/dist/shared.js
CHANGED
|
@@ -26,7 +26,7 @@ export const RETRYABLE_STATUS = new Set([502, 503, 504]);
|
|
|
26
26
|
* X-Barsom-Client-Version so the server can annotate tool guidance with the
|
|
27
27
|
* wrapper version each action requires. Keep in sync with package.json on bump.
|
|
28
28
|
*/
|
|
29
|
-
export const CLIENT_VERSION = "0.
|
|
29
|
+
export const CLIENT_VERSION = "0.17.0";
|
|
30
30
|
/** User-facing links; keep aligned with barivia.se / api.barivia.se. */
|
|
31
31
|
export const PUBLIC_SITE_ORIGIN = "https://barivia.se";
|
|
32
32
|
/** Self-serve account dashboard (manage plan, billing, and API keys). */
|
|
@@ -499,6 +499,23 @@ function formatGuidanceScopeSuffix(config) {
|
|
|
499
499
|
}
|
|
500
500
|
return "";
|
|
501
501
|
}
|
|
502
|
+
/** Fetch normalization guide markdown from API. */
|
|
503
|
+
export async function fetchNormalizationGuideFromApi() {
|
|
504
|
+
try {
|
|
505
|
+
const data = (await apiCall("GET", "/v1/docs/normalization"));
|
|
506
|
+
const md = data?.markdown;
|
|
507
|
+
if (typeof md === "string" && md.trim().length > 0) {
|
|
508
|
+
return md.trim();
|
|
509
|
+
}
|
|
510
|
+
return null;
|
|
511
|
+
}
|
|
512
|
+
catch (e) {
|
|
513
|
+
if (e?.httpStatus === 401 || e?.httpStatus === 403) {
|
|
514
|
+
throw e;
|
|
515
|
+
}
|
|
516
|
+
return null;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
502
519
|
/** Fetch training guidance from API (used by training_guidance tool). Domain knowledge returned when asked; valid license required. Hints are filtered by plan (allowed_job_types). */
|
|
503
520
|
export async function fetchTrainingGuidanceFromApi() {
|
|
504
521
|
try {
|
|
@@ -508,17 +525,21 @@ export async function fetchTrainingGuidanceFromApi() {
|
|
|
508
525
|
const presetBlock = Array.isArray(presetDesc) && presetDesc.length > 0
|
|
509
526
|
? "Presets: " + presetDesc.join(" | ") + "\n\n"
|
|
510
527
|
: "";
|
|
528
|
+
const normGuide = await fetchNormalizationGuideFromApi();
|
|
529
|
+
const normBlock = normGuide
|
|
530
|
+
? "\n\n--- Normalization guide (MAD vs SEPD, decision ladder) ---\n\n" + normGuide
|
|
531
|
+
: "\n\n(Normalization guide: GET /v1/docs/normalization — MAD vs SEPD decision ladder.)";
|
|
511
532
|
const hints = config?.training_hints;
|
|
512
533
|
if (Array.isArray(hints) && hints.length > 0) {
|
|
513
|
-
return presetBlock + "Parameter guidance (from API):\n- " + hints.join("\n- ") + scopeSuffix;
|
|
534
|
+
return presetBlock + "Parameter guidance (from API):\n- " + hints.join("\n- ") + scopeSuffix + normBlock;
|
|
514
535
|
}
|
|
515
536
|
const pg = config?.parameter_guidance;
|
|
516
537
|
if (pg && typeof pg === "object") {
|
|
517
538
|
const lines = Object.entries(pg).map(([k, v]) => `${k}: ${v}`);
|
|
518
|
-
return presetBlock + "Parameter guidance (from API):\n- " + lines.join("\n- ") + scopeSuffix;
|
|
539
|
+
return presetBlock + "Parameter guidance (from API):\n- " + lines.join("\n- ") + scopeSuffix + normBlock;
|
|
519
540
|
}
|
|
520
541
|
if (presetBlock)
|
|
521
|
-
return presetBlock + "No additional parameter guidance in config." + scopeSuffix;
|
|
542
|
+
return presetBlock + "No additional parameter guidance in config." + scopeSuffix + normBlock;
|
|
522
543
|
}
|
|
523
544
|
catch (e) {
|
|
524
545
|
if (e?.httpStatus === 401 || e?.httpStatus === 403)
|
package/dist/tools/datasets.js
CHANGED
|
@@ -75,6 +75,29 @@ function formatAnalyzeResult(data, dataset_id) {
|
|
|
75
75
|
}
|
|
76
76
|
lines.push(``);
|
|
77
77
|
}
|
|
78
|
+
const normHints = data.normalization_hints;
|
|
79
|
+
if (normHints && Object.keys(normHints).length > 0) {
|
|
80
|
+
lines.push(`Normalization hints (suggestions only — confirm before train):`);
|
|
81
|
+
for (const col of columns) {
|
|
82
|
+
const h = normHints[col];
|
|
83
|
+
if (h)
|
|
84
|
+
lines.push(` • ${col}: ${h}`);
|
|
85
|
+
}
|
|
86
|
+
lines.push(` See training_guidance for MAD vs SEPD decision ladder.`, ``);
|
|
87
|
+
}
|
|
88
|
+
const stagingStats = data.column_staging_stats;
|
|
89
|
+
if (stagingStats && Object.keys(stagingStats).length > 0) {
|
|
90
|
+
lines.push(`Full-dataset staging stats (from stats.json; hints above are sample-based):`);
|
|
91
|
+
for (const col of columns) {
|
|
92
|
+
const st = stagingStats[col];
|
|
93
|
+
if (!st)
|
|
94
|
+
continue;
|
|
95
|
+
const count = st.count;
|
|
96
|
+
const nullCount = st.null_count;
|
|
97
|
+
lines.push(` • ${col}: count=${count ?? "?"}, null_count=${nullCount ?? "?"}`);
|
|
98
|
+
}
|
|
99
|
+
lines.push(``);
|
|
100
|
+
}
|
|
78
101
|
const nextSteps = [];
|
|
79
102
|
if (trainList.length > 0) {
|
|
80
103
|
nextSteps.push(`Train with columns: [${trainList.join(", ")}].`);
|
|
@@ -112,7 +135,7 @@ Formats: plain CSV/TSV or gzip (.csv.gz / .tsv.gz). For files above ~100 MB, pre
|
|
|
112
135
|
|
|
113
136
|
action=upload: PREFER file_path — server reads from workspace root (token-efficient; no file content in context). Accepts .csv, .tsv, .csv.gz, .tsv.gz. Use csv_data only for small inline pastes (e.g. <10KB). Returns dataset ID. Then use datasets(action=preview) before train(action=map). If plain CSV exceeds the 5 GB upload cap, gzip it first (.csv.gz).
|
|
114
137
|
|
|
115
|
-
Step 0 (before upload): (1) CSV with header; one row per observation. (2) Columns you will use for training must be numeric (or datetime if you will use temporal extraction). (3) Complete-case path (train action=map): no NaNs in training columns. Sparse path (train action=impute): missing cells in training columns are OK — the job trains missSOM and returns dense imputed.csv; plain numeric columns only. (4) Categoricals: encode (e.g. one-hot, label) or exclude — do not use raw categorical columns as training features; preview shows which columns are non-numeric. (5) Optional: if you plan to use transition_flow, sort rows chronologically before upload.
|
|
138
|
+
Step 0 (before upload): (1) CSV with header; one row per observation. (2) Columns you will use for training must be numeric (or datetime if you will use temporal extraction). (3) Complete-case path (train action=map): no NaNs in training columns. Sparse path (train action=impute): missing cells in training columns are OK — the job trains missSOM and returns dense imputed.csv; plain numeric columns only. (4) Categoricals: encode (e.g. one-hot, label) or exclude — do not use raw categorical columns as training features; preview shows which columns are non-numeric. (5) Optional: if you plan to use inference(action=transition_flow), sort rows chronologically before upload.
|
|
116
139
|
Step 1 (after upload): always datasets(action=preview) to verify column types and spot cyclics/datetime, then choose train_map (complete-case) or train_impute (sparse) before submit.
|
|
117
140
|
|
|
118
141
|
action=preview: Show columns, stats, sample rows, cyclic/datetime detections. ALWAYS preview before train(action=map) on an unfamiliar dataset.
|
|
@@ -158,7 +158,7 @@ async function handleResultsExplorer(job_id) {
|
|
|
158
158
|
export function registerExploreMapTool(server) {
|
|
159
159
|
const toolConfig = {
|
|
160
160
|
title: "Results Explorer",
|
|
161
|
-
description: "
|
|
161
|
+
description: "PREFERRED way to browse a completed map's figures after a first results(get) glance. Interactive explorer for a completed map job: a responsive view of key metrics and every available figure (combined, U-matrix, hit histogram, component planes). Embeds as an MCP App or falls back to a standalone localhost page. Offer it to the user once a train job completes; results(get) remains the headless/metrics path.",
|
|
162
162
|
inputSchema: {
|
|
163
163
|
job_id: z.string().describe("Job ID of a completed map training job"),
|
|
164
164
|
},
|
package/dist/tools/inference.js
CHANGED
|
@@ -62,9 +62,10 @@ export function registerInferenceTool(server) {
|
|
|
62
62
|
| impute_column | Fill a numeric column (not used in training) by pooling observed values on the BMU plus topology neighbors (typically 6 on hex; periodic maps wrap) | 5–120s |
|
|
63
63
|
| compare | Comparing hit distributions of a second dataset against training (drift, A/B); needs one trained job_id + dataset B. NOT jobs(compare), which tabulates metrics across multiple training runs. | 30–120s |
|
|
64
64
|
| project_columns | Project one or more dataset columns onto the map (component planes); dataset can be training set or partial-feature set | 10–90s |
|
|
65
|
+
| transition_flow | Temporal state-transition arrows on the trained map. Only meaningful when the scored rows are in chronological order. | 30–60s |
|
|
65
66
|
| report | Get a report manifest (artifact keys + URLs) to build your own report in Quarto/Notebook/script | Immediate (sync) |
|
|
66
67
|
|
|
67
|
-
Sync/async: predict, impute_column, and
|
|
68
|
+
Sync/async: predict, impute_column, compare, and transition_flow are async jobs that the proxy auto-polls and usually returns on completion; if one returns a job_id instead (e.g. timeout), poll jobs(action=status, job_id=...) then results(action=download, job_id=...). batch_predict does NOT auto-poll — it returns a list of submitted job_ids; poll each with jobs(action=status).
|
|
68
69
|
Artifacts: When complete, use results(action=download, job_id=<returned_job_id>) to get: predict (output="compact") → predictions.csv; predict (output="annotated") → annotated.csv; impute_column → imputed.csv; compare → density-diff figure (e.g. density_diff.png).
|
|
69
70
|
report is the only synchronous inference action — returns manifest immediately; no job to poll.
|
|
70
71
|
NOT FOR: Retraining or changing the map — all actions treat the trained map as frozen.
|
|
@@ -86,10 +87,11 @@ action=impute_column: Map-local imputation on the frozen trained map (read-only;
|
|
|
86
87
|
|
|
87
88
|
action=compare: density-diff of a cohort dataset against the training set on ONE trained map — NOT jobs(action=compare) (which tabulates QE/TE/EV metrics across multiple training runs to pick the best). dataset_id must refer to a dataset with the same feature set as training (same column names and preprocessing, including cyclic expansion). A = training dataset; B = cohort to compare. Density-diff: positive = B gained vs A; negative = A had more. Returns density-diff heatmap (e.g. density_diff.png).
|
|
88
89
|
action=project_columns: Project one or more columns from a dataset onto the trained map. Pass dataset_id (the dataset containing the columns) and columns (array of column names). Uses cached BMUs when dataset is the training set; supports partial-feature mapping when dataset has only a subset of training features. Returns one component plane image per column. Get files via results(action=download, job_id=<returned_job_id>).
|
|
90
|
+
action=transition_flow: Temporal state-transition arrows drawn on the trained map grid (U-matrix background). Post-training analysis of how observations move across the map over time — use ONLY when rows are in chronological order; on unordered data the arrows are meaningless. You pass the training job_id; the API returns a NEW result job_id (use it for results(action=get/download)). lag=1 (default) = consecutive rows; lag=N = N-step horizon (e.g. 24 for daily cycles in hourly data). min_transitions filters noisy arrows (increase for large datasets). Auto-polled.
|
|
89
91
|
action=report: Returns a report manifest for the given job_id (job must be completed). Includes figure_manifest (logical names → filenames), download_urls for all artifacts, cluster_summary when available, and summary metrics. Stakeholder report PDF (if generated) is available via results(action=download, job_id=<training_job_id>), filename e.g. report.pdf.`, {
|
|
90
92
|
action: z
|
|
91
|
-
.enum(["predict", "batch_predict", "impute_column", "compare", "project_columns", "report"])
|
|
92
|
-
.describe("predict: score rows; batch_predict: fan out many predict jobs at once; impute_column: topology-neighbor pool imputation for a column not in training; compare: drift/cohort diff heatmap; project_columns: project dataset columns onto map; report: manifest of primitives for custom report."),
|
|
93
|
+
.enum(["predict", "batch_predict", "impute_column", "compare", "project_columns", "transition_flow", "report"])
|
|
94
|
+
.describe("predict: score rows; batch_predict: fan out many predict jobs at once; impute_column: topology-neighbor pool imputation for a column not in training; compare: drift/cohort diff heatmap; project_columns: project dataset columns onto map; transition_flow: temporal state-transition arrows on the map (time-ordered rows only); report: manifest of primitives for custom report."),
|
|
93
95
|
job_id: z.string().describe("Job ID of a completed map training job"),
|
|
94
96
|
inputs: z
|
|
95
97
|
.array(z.object({
|
|
@@ -114,11 +116,48 @@ action=report: Returns a report manifest for the given job_id (job must be compl
|
|
|
114
116
|
.describe("action=impute_column: instantaneous (default) = pool mean/median; categorical = pool mode; cumulative = monotonic-counter (pool aggregation is rough; warns). A monotonic-counter warning is emitted automatically regardless."),
|
|
115
117
|
weighting: z.enum(["distance", "uniform"]).optional()
|
|
116
118
|
.describe("action=impute_column: distance (default) weights pooled values by map-neighbourhood proximity (closer BMU nodes count more); uniform is a flat pool."),
|
|
117
|
-
|
|
119
|
+
lag: z.number().int().min(1).optional().default(1)
|
|
120
|
+
.describe("action=transition_flow: step lag (default 1 = consecutive rows). Use larger for periodic horizons (e.g. 24 for daily cycles in hourly data)."),
|
|
121
|
+
min_transitions: z.number().int().min(1).optional()
|
|
122
|
+
.describe("action=transition_flow: minimum transition count to draw an arrow (default: auto). Increase to filter noise on large datasets."),
|
|
123
|
+
top_k: z.number().int().min(1).optional().default(10)
|
|
124
|
+
.describe("action=transition_flow: number of top-flow nodes in the statistics summary (default 10)."),
|
|
125
|
+
}, async ({ action, job_id, dataset_id, columns, rows, output, colormap, output_format, output_dpi, top_n, target_column, only_missing, impute_aggregation, cv_folds, target_column_kind, weighting, inputs, lag, min_transitions, top_k }) => {
|
|
118
126
|
const dpiMap = { standard: 1, retina: 2, print: 4 };
|
|
119
127
|
const numericDpi = dpiMap[output_dpi ?? "retina"] ?? 2;
|
|
120
128
|
if (action === "batch_predict")
|
|
121
129
|
return runBatchPredict({ job_id, inputs });
|
|
130
|
+
if (action === "transition_flow") {
|
|
131
|
+
const body = { lag: lag ?? 1, output_format: output_format ?? "png" };
|
|
132
|
+
if (min_transitions !== undefined)
|
|
133
|
+
body.min_transitions = min_transitions;
|
|
134
|
+
if (top_k !== undefined)
|
|
135
|
+
body.top_k = top_k;
|
|
136
|
+
if (colormap !== undefined)
|
|
137
|
+
body.colormap = colormap;
|
|
138
|
+
if (output_dpi && output_dpi !== "retina")
|
|
139
|
+
body.output_dpi = dpiMap[output_dpi] ?? 2;
|
|
140
|
+
const data = (await apiCall("POST", `/v1/results/${job_id}/transition-flow`, body));
|
|
141
|
+
const flowJobId = data.id;
|
|
142
|
+
const poll = await pollUntilComplete(flowJobId, 120_000);
|
|
143
|
+
if (poll.status === "completed") {
|
|
144
|
+
const res = (await apiCall("GET", `/v1/results/${flowJobId}`));
|
|
145
|
+
const summary = (res.summary ?? {});
|
|
146
|
+
const stats = (summary.flow_stats ?? {});
|
|
147
|
+
const content = [{ type: "text", text: [
|
|
148
|
+
`Transition Flow (job: ${flowJobId}) | Parent map: ${job_id} | Lag: ${lag ?? 1}`,
|
|
149
|
+
`Active flow nodes: ${stats.active_flow_nodes ?? "N/A"} | Total transitions: ${stats.total_transitions ?? "N/A"}`,
|
|
150
|
+
`Mean magnitude: ${stats.mean_magnitude !== undefined ? Number(stats.mean_magnitude).toFixed(4) : "N/A"}`,
|
|
151
|
+
`Arrows show net directional drift on the map; use a larger lag for longer-term structure. To persist: results(action=download, job_id="${flowJobId}").`,
|
|
152
|
+
].join("\n") }];
|
|
153
|
+
await tryAttachImage(content, flowJobId, `transition_flow_lag${lag ?? 1}.${output_format ?? "png"}`);
|
|
154
|
+
return { content };
|
|
155
|
+
}
|
|
156
|
+
if (poll.status === "failed") {
|
|
157
|
+
return { content: [{ type: "text", text: `inference(transition_flow) job ${flowJobId} failed: ${poll.error ?? "unknown error"}` }] };
|
|
158
|
+
}
|
|
159
|
+
return { content: [{ type: "text", text: `inference(transition_flow) job ${flowJobId} submitted. Poll with jobs(action=status, job_id="${flowJobId}"), then results(action=get, job_id="${flowJobId}").` }] };
|
|
160
|
+
}
|
|
122
161
|
if (action === "predict") {
|
|
123
162
|
const outputStyle = output ?? "compact";
|
|
124
163
|
if (outputStyle === "annotated" && rows) {
|
package/dist/tools/jobs.js
CHANGED
|
@@ -3,10 +3,7 @@ import { registerAuditedTool } from "../audit.js";
|
|
|
3
3
|
import { apiCall, textResult } from "../shared.js";
|
|
4
4
|
import { formatJobCancelText, formatJobStatusText } from "../job_status_format.js";
|
|
5
5
|
import { pollFinalizeIfPresent, refreshJobAfterFinalize } from "../train_finalize.js";
|
|
6
|
-
|
|
7
|
-
import { runBatchPredict } from "./inference.js";
|
|
8
|
-
import { TRAINING_INPUT_SCHEMA } from "./training_core.js";
|
|
9
|
-
export const JOBS_DESCRIPTION_BASE = `Manage and inspect jobs (lifecycle).
|
|
6
|
+
export const JOBS_DESCRIPTION_BASE = `Manage and inspect jobs (lifecycle only).
|
|
10
7
|
|
|
11
8
|
| Action | Use when |
|
|
12
9
|
|--------|----------|
|
|
@@ -16,7 +13,7 @@ export const JOBS_DESCRIPTION_BASE = `Manage and inspect jobs (lifecycle).
|
|
|
16
13
|
| cancel | Stopping a running or pending job to free the worker |
|
|
17
14
|
| delete | Permanently removing a job and all its S3 result files |
|
|
18
15
|
|
|
19
|
-
To SUBMIT training, use the **train** tool (train(action=map | siom_map | impute | floop_siom
|
|
16
|
+
To SUBMIT training, use the **train** tool (train(action=map | siom_map | impute | floop_siom)); to score data or run post-training map operations (predict, batch_predict, compare, project_columns, impute_column, transition_flow), use **inference**.
|
|
20
17
|
|
|
21
18
|
ASYNC POLLING PROTOCOL (action=status):
|
|
22
19
|
- Poll every 10-15 seconds. Do NOT poll faster — it wastes context.
|
|
@@ -66,6 +63,16 @@ export function renderCompareTable(comparisons) {
|
|
|
66
63
|
const candidates = [
|
|
67
64
|
{ key: "grid", label: "Grid", render: renderGrid },
|
|
68
65
|
{ key: "epochs", label: "Epochs", render: renderEpochs },
|
|
66
|
+
{ key: "topology", label: "Topology", render: fmtAny },
|
|
67
|
+
{ key: "max_nodes", label: "MaxNodes", render: fmtAny },
|
|
68
|
+
{ key: "n_initial", label: "NInitial", render: fmtAny },
|
|
69
|
+
{ key: "growth_interval", label: "GrowthInt", render: fmtAny },
|
|
70
|
+
{ key: "gamma", label: "Gamma", render: fmtAny },
|
|
71
|
+
{ key: "siom_decay", label: "SIOMDecay", render: fmtAny },
|
|
72
|
+
{ key: "elastic_lambda", label: "Elasticλ", render: fmtAny },
|
|
73
|
+
{ key: "elastic_mu", label: "Elasticμ", render: fmtAny },
|
|
74
|
+
{ key: "elastic_anchor", label: "ElasticAnchor", render: fmtAny },
|
|
75
|
+
{ key: "effort", label: "Effort", render: fmtAny },
|
|
69
76
|
{ key: "model", label: "Model", render: fmtAny },
|
|
70
77
|
{ key: "periodic", label: "Periodic", render: fmtAny },
|
|
71
78
|
{ key: "backend", label: "Backend", render: fmtAny },
|
|
@@ -127,27 +134,15 @@ export function renderCompareTable(comparisons) {
|
|
|
127
134
|
}
|
|
128
135
|
return lines.join("\n");
|
|
129
136
|
}
|
|
130
|
-
/** Deprecated jobs(train_*) / run_baseline_study actions -> the train tool's concise actions. */
|
|
131
|
-
const TRAIN_ALIAS = {
|
|
132
|
-
train_map: "map",
|
|
133
|
-
train_siom_map: "siom_map",
|
|
134
|
-
train_impute: "impute",
|
|
135
|
-
train_floop_siom: "floop_siom",
|
|
136
|
-
run_baseline_study: "baseline_study",
|
|
137
|
-
};
|
|
138
137
|
export function registerJobsTool(server, description) {
|
|
139
138
|
registerAuditedTool(server, "jobs", description, {
|
|
140
139
|
action: z
|
|
141
|
-
.enum([
|
|
142
|
-
"status
|
|
143
|
-
// Deprecated forwarders (kept for one release; use train / inference).
|
|
144
|
-
"train_map", "train_siom_map", "train_impute", "train_floop_siom", "run_baseline_study", "batch_predict",
|
|
145
|
-
])
|
|
146
|
-
.describe("status: check progress; list: see all jobs; compare: metrics table; cancel: stop job; delete: remove job + files. DEPRECATED forwarders: train_map/train_siom_map/train_impute/train_floop_siom/run_baseline_study (use the train tool); batch_predict (use inference)."),
|
|
140
|
+
.enum(["status", "list", "compare", "cancel", "delete"])
|
|
141
|
+
.describe("status: check progress; list: see all jobs; compare: metrics table; cancel: stop job; delete: remove job + files. (Training is submitted via the train tool; scoring / post-training map ops via inference.)"),
|
|
147
142
|
job_id: z
|
|
148
143
|
.string()
|
|
149
144
|
.optional()
|
|
150
|
-
.describe("Job ID — required for action=status, cancel, delete
|
|
145
|
+
.describe("Job ID — required for action=status, cancel, delete."),
|
|
151
146
|
job_ids: z
|
|
152
147
|
.array(z.string())
|
|
153
148
|
.optional()
|
|
@@ -155,44 +150,9 @@ export function registerJobsTool(server, description) {
|
|
|
155
150
|
dataset_id: z
|
|
156
151
|
.string()
|
|
157
152
|
.optional()
|
|
158
|
-
.describe("action=list: filter jobs by this dataset ID.
|
|
159
|
-
label: z
|
|
160
|
-
.string()
|
|
161
|
-
.max(120)
|
|
162
|
-
.optional()
|
|
163
|
-
.describe("Optional run label for the deprecated train_* forwarders (≤120 chars; appears in jobs(list) and the jobs(compare) table)."),
|
|
164
|
-
inputs: z
|
|
165
|
-
.array(z.object({
|
|
166
|
-
dataset_id: z.string().optional(),
|
|
167
|
-
rows: z.array(z.record(z.string(), z.number())).optional(),
|
|
168
|
-
}))
|
|
169
|
-
.optional()
|
|
170
|
-
.describe("Deprecated batch_predict forwarder: array of inputs; each item has dataset_id and/or rows."),
|
|
171
|
-
// Deprecated: training parameters accepted only by the jobs(train_*) forwarders.
|
|
172
|
-
// Submit training via the `train` tool instead; these are removed in a later release.
|
|
173
|
-
...TRAINING_INPUT_SCHEMA,
|
|
153
|
+
.describe("action=list: filter jobs by this dataset ID."),
|
|
174
154
|
}, async (args) => {
|
|
175
155
|
const { action, job_id, job_ids, dataset_id } = args;
|
|
176
|
-
// ---- Deprecated forwarders -> train / inference ----
|
|
177
|
-
const mapped = TRAIN_ALIAS[action];
|
|
178
|
-
if (mapped) {
|
|
179
|
-
const result = await runTrain(mapped, args);
|
|
180
|
-
return {
|
|
181
|
-
content: [
|
|
182
|
-
...result.content,
|
|
183
|
-
{ type: "text", text: `Note: jobs(action=${action}) is deprecated; use train(action=${mapped}).` },
|
|
184
|
-
],
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
if (action === "batch_predict") {
|
|
188
|
-
const result = await runBatchPredict({ job_id, inputs: args.inputs });
|
|
189
|
-
return {
|
|
190
|
-
content: [
|
|
191
|
-
...result.content,
|
|
192
|
-
{ type: "text", text: "Note: jobs(action=batch_predict) is deprecated; use inference(action=batch_predict)." },
|
|
193
|
-
],
|
|
194
|
-
};
|
|
195
|
-
}
|
|
196
156
|
// ---- Lifecycle ----
|
|
197
157
|
if (action === "status") {
|
|
198
158
|
if (!job_id)
|