|
13 | 13 |
|
14 | 14 | from modelchoice_mcp.bridge import ModelChoiceBridge |
15 | 15 | from modelchoice_mcp.schemas import ( |
| 16 | + AnalysisRun, |
16 | 17 | BranchView, |
17 | 18 | EvpiResult, |
18 | 19 | NodeDiff, |
19 | 20 | NodeResultView, |
20 | 21 | NodeView, |
21 | 22 | RollbackVerification, |
22 | 23 | RollupResponse, |
| 24 | + SheetData, |
23 | 25 | TreeList, |
24 | 26 | TreeStructure, |
25 | 27 | TreeSummary, |
26 | 28 | ) |
27 | 29 | from modelchoice_mcp.server import mcp |
28 | 30 | from modelchoice_mcp.tree import DecisionTree, parse_model, rollup |
29 | 31 |
|
| 32 | +# Friendly analysis name -> ModelChoice headless ExcelCommand. |
| 33 | +_ANALYSES: dict[str, str] = { |
| 34 | + "risk_profile": "MC_RiskProfile_Auto", |
| 35 | + "robustness": "MC_Robustness_Auto", |
| 36 | + "sensitivity": "MC_SensitivityAnalysis_Auto", |
| 37 | + "strategy_table": "MC_ExportStrategyTable_Auto", |
| 38 | + "policy_suggestion": "MC_PolicySuggestion_Auto", |
| 39 | + "decision_brief": "MC_DecisionBrief_Auto", |
| 40 | + "mcda_report": "MC_McdaReport_Auto", |
| 41 | + "evpi": "MC_EVPI_Auto", |
| 42 | +} |
| 43 | + |
30 | 44 | _bridge: ModelChoiceBridge | None = None |
31 | 45 |
|
32 | 46 |
|
@@ -281,11 +295,79 @@ def run_evpi(workbook_name: str | None = None) -> EvpiResult: |
281 | 295 | ) |
282 | 296 |
|
283 | 297 |
|
| 298 | +@mcp.tool( |
| 299 | + description=( |
| 300 | + "ModelChoice: Run a decision-analysis on the active tree and report the " |
| 301 | + "result sheets it produced. `analysis` is one of: 'risk_profile', " |
| 302 | + "'robustness' (how much inputs must change to flip the decision), " |
| 303 | + "'sensitivity', 'strategy_table', 'policy_suggestion', 'decision_brief', " |
| 304 | + "'mcda_report', 'evpi'. Drives ModelChoice's headless command and lists " |
| 305 | + "the new sheets — read them with read_sheet. Requires the ModelChoice " |
| 306 | + "add-in loaded in Excel with a tree open." |
| 307 | + ) |
| 308 | +) |
| 309 | +def run_analysis( |
| 310 | + analysis: Annotated[ |
| 311 | + str, Field(description="One of: " + ", ".join(sorted(_ANALYSES))) |
| 312 | + ], |
| 313 | + workbook_name: str | None = None, |
| 314 | +) -> AnalysisRun: |
| 315 | + key = analysis.lower() |
| 316 | + if key not in _ANALYSES: |
| 317 | + raise ValueError( |
| 318 | + f"Unknown analysis {analysis!r}. Choose from: {', '.join(sorted(_ANALYSES))}." |
| 319 | + ) |
| 320 | + command = _ANALYSES[key] |
| 321 | + r = get_bridge().run_analysis(command, workbook_name) |
| 322 | + new = r.get("new_sheets", []) |
| 323 | + note = ( |
| 324 | + f"{analysis} wrote: {', '.join(new)} — read with read_sheet." |
| 325 | + if new |
| 326 | + else f"{analysis} ran but added no new sheet; it may have updated an existing one." |
| 327 | + ) |
| 328 | + return AnalysisRun( |
| 329 | + analysis=key, |
| 330 | + command=command, |
| 331 | + new_sheets=new, |
| 332 | + sheets=r.get("sheets", []), |
| 333 | + note=note, |
| 334 | + ) |
| 335 | + |
| 336 | + |
| 337 | +@mcp.tool( |
| 338 | + description=( |
| 339 | + "ModelChoice: Read a worksheet's used range (capped) as rows of cell " |
| 340 | + "values — for pulling back the result sheet an analysis produced (e.g. " |
| 341 | + "'MC_EVPI', 'MC_RB_Verdict', a sensitivity report). Returns numbers, " |
| 342 | + "text, or null per cell." |
| 343 | + ) |
| 344 | +) |
| 345 | +def read_sheet( |
| 346 | + sheet_name: Annotated[str, Field(description="Worksheet name to read.")], |
| 347 | + workbook_name: str | None = None, |
| 348 | + max_rows: Annotated[ |
| 349 | + int, Field(ge=1, le=2000, description="Max rows to return (default 200).") |
| 350 | + ] = 200, |
| 351 | + max_cols: Annotated[ |
| 352 | + int, Field(ge=1, le=100, description="Max columns to return (default 20).") |
| 353 | + ] = 20, |
| 354 | +) -> SheetData: |
| 355 | + rows = get_bridge().read_sheet(sheet_name, workbook_name, max_rows=max_rows, max_cols=max_cols) |
| 356 | + return SheetData( |
| 357 | + sheet=sheet_name, |
| 358 | + row_count=len(rows), |
| 359 | + rows=rows, |
| 360 | + truncated=len(rows) >= max_rows, |
| 361 | + ) |
| 362 | + |
| 363 | + |
284 | 364 | __all__ = [ |
285 | 365 | "get_bridge", |
286 | 366 | "get_tree", |
287 | 367 | "list_trees", |
| 368 | + "read_sheet", |
288 | 369 | "roll_up", |
| 370 | + "run_analysis", |
289 | 371 | "run_evpi", |
290 | 372 | "set_bridge_for_testing", |
291 | 373 | "verify_rollback", |
|
0 commit comments