modelwright 0.1.0a1__py3-none-any.whl

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.
@@ -0,0 +1,148 @@
1
+ """Modelwright package skeleton."""
2
+
3
+ from modelwright.extraction import (
4
+ CellRecord,
5
+ ExtractionDiagnostic,
6
+ FormulaRecord,
7
+ NamedRangeRecord,
8
+ SheetRecord,
9
+ TableRecord,
10
+ WorkbookRecord,
11
+ extract_workbook,
12
+ )
13
+ from modelwright.execution import (
14
+ ExecutionDiagnostic,
15
+ GeneratedExecutionResult,
16
+ execute_generated_model,
17
+ )
18
+ from modelwright.evaluation import (
19
+ ValidationEvaluationResult,
20
+ evaluate_generated_model,
21
+ )
22
+ from modelwright.conversion import (
23
+ ConversionPlan,
24
+ ConversionSource,
25
+ CoverageSummary,
26
+ DiagnosticSummary,
27
+ GenerationSummary,
28
+ PlanRecommendation,
29
+ PrivacyReview,
30
+ ResidualBlocker,
31
+ ValidationSummary,
32
+ WorkflowStatus,
33
+ build_conversion_plan,
34
+ )
35
+ from modelwright.formulas import (
36
+ FormulaExpression,
37
+ FormulaExpressionNode,
38
+ FormulaTranslationDiagnostic,
39
+ build_formula_reference_index,
40
+ translate_formula_cell,
41
+ )
42
+ from modelwright.formulas_oracle import FormulasWorkbookOracle
43
+ from modelwright.generation import (
44
+ GeneratedContractInferenceResult,
45
+ GeneratedModuleContract,
46
+ GeneratedSymbol,
47
+ GenerationDiagnostic,
48
+ GenerationResult,
49
+ generate_python_module,
50
+ infer_generated_module_contract,
51
+ )
52
+ from modelwright.graph import (
53
+ DependencyEdge,
54
+ DependencyGraph,
55
+ build_dependency_graph,
56
+ )
57
+ from modelwright.oracles import (
58
+ OracleDiagnostic,
59
+ OracleRequest,
60
+ OracleResult,
61
+ WorkbookOracle,
62
+ )
63
+ from modelwright.oracle_validation import build_oracle_validation_report
64
+ from modelwright.references import (
65
+ WorkbookReference,
66
+ normalize_cell_reference,
67
+ normalize_reference,
68
+ )
69
+ from modelwright.validation import (
70
+ ComparisonResult,
71
+ ComparisonRules,
72
+ Diagnostic,
73
+ MISSING_VALUE,
74
+ OracleConfig,
75
+ ScenarioInput,
76
+ ScenarioOutput,
77
+ ValidationReport,
78
+ ValidationScenario,
79
+ build_validation_report,
80
+ compare_scalar_output,
81
+ load_validation_scenario,
82
+ )
83
+
84
+ __version__ = "0.1.0a1"
85
+
86
+ __all__ = [
87
+ "CellRecord",
88
+ "ComparisonResult",
89
+ "ComparisonRules",
90
+ "ConversionPlan",
91
+ "ConversionSource",
92
+ "CoverageSummary",
93
+ "DependencyEdge",
94
+ "DependencyGraph",
95
+ "Diagnostic",
96
+ "DiagnosticSummary",
97
+ "ExtractionDiagnostic",
98
+ "ExecutionDiagnostic",
99
+ "FormulaExpression",
100
+ "FormulaExpressionNode",
101
+ "FormulaRecord",
102
+ "FormulaTranslationDiagnostic",
103
+ "FormulasWorkbookOracle",
104
+ "GeneratedModuleContract",
105
+ "GeneratedExecutionResult",
106
+ "GeneratedContractInferenceResult",
107
+ "GeneratedSymbol",
108
+ "GenerationSummary",
109
+ "GenerationDiagnostic",
110
+ "GenerationResult",
111
+ "MISSING_VALUE",
112
+ "NamedRangeRecord",
113
+ "OracleConfig",
114
+ "OracleDiagnostic",
115
+ "OracleRequest",
116
+ "OracleResult",
117
+ "PlanRecommendation",
118
+ "PrivacyReview",
119
+ "ResidualBlocker",
120
+ "ScenarioInput",
121
+ "ScenarioOutput",
122
+ "SheetRecord",
123
+ "TableRecord",
124
+ "ValidationReport",
125
+ "ValidationEvaluationResult",
126
+ "ValidationScenario",
127
+ "ValidationSummary",
128
+ "WorkbookOracle",
129
+ "WorkbookReference",
130
+ "WorkbookRecord",
131
+ "WorkflowStatus",
132
+ "__version__",
133
+ "build_dependency_graph",
134
+ "build_conversion_plan",
135
+ "build_formula_reference_index",
136
+ "build_oracle_validation_report",
137
+ "build_validation_report",
138
+ "compare_scalar_output",
139
+ "execute_generated_model",
140
+ "evaluate_generated_model",
141
+ "extract_workbook",
142
+ "generate_python_module",
143
+ "infer_generated_module_contract",
144
+ "load_validation_scenario",
145
+ "normalize_cell_reference",
146
+ "normalize_reference",
147
+ "translate_formula_cell",
148
+ ]
modelwright/cli.py ADDED
@@ -0,0 +1,466 @@
1
+ """Typer-based command-line wrappers for Modelwright JSON workflows."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from collections.abc import Sequence
7
+ from pathlib import Path
8
+ from typing import Any, cast
9
+
10
+ import typer
11
+ from typer.main import get_command
12
+
13
+ from modelwright.conversion import BenchmarkRole, build_conversion_plan
14
+ from modelwright.evaluation import evaluate_generated_model
15
+ from modelwright.execution import execute_generated_model
16
+ from modelwright.extraction import WorkbookRecord, extract_workbook
17
+ from modelwright.formulas import FormulaExpression, build_formula_reference_index, translate_formula_cell
18
+ from modelwright.generation import GeneratedModuleContract, generate_python_module
19
+ from modelwright.graph import build_dependency_graph
20
+ from modelwright.oracles import OracleResult
21
+ from modelwright.validation import build_validation_report, load_validation_scenario
22
+
23
+
24
+ JsonValue = str | int | float | bool | None | list[Any] | dict[str, Any]
25
+
26
+ app = typer.Typer(
27
+ add_completion=False,
28
+ no_args_is_help=True,
29
+ help="Inspect spreadsheet workbooks and assemble transparent Python-model workflow artifacts.",
30
+ )
31
+ workbook_app = typer.Typer(
32
+ add_completion=False,
33
+ no_args_is_help=True,
34
+ help="Extract workbook facts and dependency graphs.",
35
+ )
36
+ model_app = typer.Typer(
37
+ add_completion=False,
38
+ no_args_is_help=True,
39
+ help="Generate standalone Python model artifacts.",
40
+ )
41
+ validation_app = typer.Typer(
42
+ add_completion=False,
43
+ no_args_is_help=True,
44
+ help="Build validation reports from observed output values.",
45
+ )
46
+ conversion_app = typer.Typer(
47
+ add_completion=False,
48
+ no_args_is_help=True,
49
+ help="Assemble conversion planning reports.",
50
+ )
51
+
52
+ app.add_typer(workbook_app, name="workbook")
53
+ app.add_typer(model_app, name="model")
54
+ app.add_typer(validation_app, name="validation")
55
+ app.add_typer(conversion_app, name="conversion")
56
+
57
+
58
+ def main(argv: Sequence[str] | None = None) -> int:
59
+ """Run the Modelwright CLI with an explicit argv sequence."""
60
+
61
+ command = get_command(app)
62
+ command.main(args=list(argv) if argv is not None else None, prog_name="modelwright", standalone_mode=False)
63
+ return 0
64
+
65
+
66
+ @workbook_app.command("extract")
67
+ def workbook_extract(
68
+ workbook: Path = typer.Argument(..., exists=True, dir_okay=False, readable=True, help="Source workbook path."),
69
+ ) -> None:
70
+ """Extract workbook facts as JSON."""
71
+
72
+ _emit_json(_extract_payload(workbook))
73
+
74
+
75
+ @workbook_app.command("graph")
76
+ def workbook_graph(
77
+ workbook: Path = typer.Argument(..., exists=True, dir_okay=False, readable=True, help="Source workbook path."),
78
+ ) -> None:
79
+ """Build dependency graph JSON from a workbook."""
80
+
81
+ _emit_json(_graph_payload(workbook))
82
+
83
+
84
+ @model_app.command("generate")
85
+ def model_generate(
86
+ contract: Path = typer.Option(
87
+ ...,
88
+ "--contract",
89
+ exists=True,
90
+ dir_okay=False,
91
+ readable=True,
92
+ help="Generated module contract JSON file.",
93
+ ),
94
+ expressions: Path = typer.Option(
95
+ ...,
96
+ "--expressions",
97
+ exists=True,
98
+ dir_okay=False,
99
+ readable=True,
100
+ help="Formula expressions JSON object.",
101
+ ),
102
+ constants: Path | None = typer.Option(
103
+ None,
104
+ "--constants",
105
+ exists=True,
106
+ dir_okay=False,
107
+ readable=True,
108
+ help="Optional input constants JSON object.",
109
+ ),
110
+ output: Path | None = typer.Option(
111
+ None,
112
+ "--out",
113
+ help="Optional path for generated Python source.",
114
+ ),
115
+ ) -> None:
116
+ """Generate Python from explicit JSON contracts."""
117
+
118
+ _emit_json(_generate_payload(contract=contract, expressions=expressions, constants=constants, output=output))
119
+
120
+
121
+ @model_app.command("execute")
122
+ def model_execute(
123
+ contract: Path = typer.Option(
124
+ ...,
125
+ "--contract",
126
+ exists=True,
127
+ dir_okay=False,
128
+ readable=True,
129
+ help="Generated module contract JSON file.",
130
+ ),
131
+ model: Path = typer.Option(
132
+ ...,
133
+ "--model",
134
+ exists=True,
135
+ dir_okay=False,
136
+ readable=True,
137
+ help="Generated Python model file.",
138
+ ),
139
+ inputs: Path | None = typer.Option(
140
+ None,
141
+ "--inputs",
142
+ exists=True,
143
+ dir_okay=False,
144
+ readable=True,
145
+ help="Optional generated model input overrides JSON object.",
146
+ ),
147
+ ) -> None:
148
+ """Execute generated Python model JSON outputs."""
149
+
150
+ _emit_json(_execute_payload(contract=contract, model=model, inputs=inputs))
151
+
152
+
153
+ @validation_app.command("report")
154
+ def validation_report(
155
+ scenario: Path = typer.Option(
156
+ ...,
157
+ "--scenario",
158
+ exists=True,
159
+ dir_okay=False,
160
+ readable=True,
161
+ help="Validation scenario JSON file.",
162
+ ),
163
+ generated_values: Path = typer.Option(
164
+ ...,
165
+ "--generated-values",
166
+ exists=True,
167
+ dir_okay=False,
168
+ readable=True,
169
+ help="Generated model output values JSON object.",
170
+ ),
171
+ oracle_values: Path = typer.Option(
172
+ ...,
173
+ "--oracle-values",
174
+ exists=True,
175
+ dir_okay=False,
176
+ readable=True,
177
+ help="Oracle output values JSON object.",
178
+ ),
179
+ ) -> None:
180
+ """Build validation report JSON."""
181
+
182
+ _emit_json(
183
+ _validate_report_payload(
184
+ scenario=scenario,
185
+ generated_values=generated_values,
186
+ oracle_values=oracle_values,
187
+ )
188
+ )
189
+
190
+
191
+ @validation_app.command("evaluate")
192
+ def validation_evaluate(
193
+ contract: Path = typer.Option(
194
+ ...,
195
+ "--contract",
196
+ exists=True,
197
+ dir_okay=False,
198
+ readable=True,
199
+ help="Generated module contract JSON file.",
200
+ ),
201
+ model: Path = typer.Option(
202
+ ...,
203
+ "--model",
204
+ exists=True,
205
+ dir_okay=False,
206
+ readable=True,
207
+ help="Generated Python model file.",
208
+ ),
209
+ scenario: Path = typer.Option(
210
+ ...,
211
+ "--scenario",
212
+ exists=True,
213
+ dir_okay=False,
214
+ readable=True,
215
+ help="Validation scenario JSON file.",
216
+ ),
217
+ workbook: Path | None = typer.Option(
218
+ None,
219
+ "--workbook",
220
+ exists=True,
221
+ dir_okay=False,
222
+ readable=True,
223
+ help="Optional source workbook path used for cached workbook validation.",
224
+ ),
225
+ workbook_record: Path | None = typer.Option(
226
+ None,
227
+ "--workbook-record",
228
+ exists=True,
229
+ dir_okay=False,
230
+ readable=True,
231
+ help="Optional extracted WorkbookRecord JSON used for cached workbook validation.",
232
+ ),
233
+ oracle_result: Path | None = typer.Option(
234
+ None,
235
+ "--oracle-result",
236
+ exists=True,
237
+ dir_okay=False,
238
+ readable=True,
239
+ help="Optional OracleResult JSON used for oracle-backed validation.",
240
+ ),
241
+ verbose: bool = typer.Option(
242
+ False,
243
+ "--verbose",
244
+ help="Print progress messages to stderr while keeping stdout as JSON.",
245
+ ),
246
+ ) -> None:
247
+ """Execute generated model and build available validation reports."""
248
+
249
+ _emit_json(
250
+ _evaluate_payload(
251
+ contract=contract,
252
+ model=model,
253
+ scenario=scenario,
254
+ workbook=workbook,
255
+ workbook_record=workbook_record,
256
+ oracle_result=oracle_result,
257
+ verbose=verbose,
258
+ )
259
+ )
260
+
261
+
262
+ @conversion_app.command("plan")
263
+ def conversion_plan(
264
+ workbook: Path = typer.Argument(..., exists=True, dir_okay=False, readable=True, help="Source workbook path."),
265
+ plan_id: str | None = typer.Option(
266
+ None,
267
+ "--plan-id",
268
+ help="Stable plan identifier. Defaults to conversion-plan:<workbook filename>.",
269
+ ),
270
+ benchmark_role: str = typer.Option(
271
+ "ad_hoc_private",
272
+ "--benchmark-role",
273
+ help="Benchmark role for the source workbook.",
274
+ ),
275
+ modelwright_commit: str = typer.Option(
276
+ "unknown",
277
+ "--modelwright-commit",
278
+ help="Modelwright commit identifier to record in the plan.",
279
+ ),
280
+ include_source_path: bool = typer.Option(
281
+ False,
282
+ "--include-source-path",
283
+ help="Include the local workbook path in JSON output. Off by default for safer sharing.",
284
+ ),
285
+ ) -> None:
286
+ """Build a conversion plan from workbook extraction, graphing, and translation."""
287
+
288
+ _emit_json(
289
+ _conversion_plan_payload(
290
+ workbook=workbook,
291
+ plan_id=plan_id,
292
+ benchmark_role=_parse_benchmark_role(benchmark_role),
293
+ modelwright_commit=modelwright_commit,
294
+ include_source_path=include_source_path,
295
+ )
296
+ )
297
+
298
+
299
+ def _extract_payload(workbook: Path) -> dict[str, JsonValue]:
300
+ return extract_workbook(workbook).to_dict()
301
+
302
+
303
+ def _graph_payload(workbook: Path) -> dict[str, JsonValue]:
304
+ workbook_record = extract_workbook(workbook)
305
+ graph = build_dependency_graph(workbook_record)
306
+ return graph.to_dict()
307
+
308
+
309
+ def _generate_payload(
310
+ *,
311
+ contract: Path,
312
+ expressions: Path,
313
+ constants: Path | None,
314
+ output: Path | None,
315
+ ) -> dict[str, JsonValue]:
316
+ module_contract = GeneratedModuleContract.from_dict(_load_object(contract))
317
+ formula_expressions = {
318
+ cell_ref: FormulaExpression.from_dict(expression)
319
+ for cell_ref, expression in _load_object(expressions).items()
320
+ }
321
+ result = generate_python_module(
322
+ contract=module_contract,
323
+ expressions=formula_expressions,
324
+ constants=_load_object(constants) if constants else {},
325
+ output_path=output,
326
+ )
327
+ return result.to_dict()
328
+
329
+
330
+ def _execute_payload(
331
+ *,
332
+ contract: Path,
333
+ model: Path,
334
+ inputs: Path | None,
335
+ ) -> dict[str, JsonValue]:
336
+ result = execute_generated_model(
337
+ contract=GeneratedModuleContract.from_dict(_load_object(contract)),
338
+ module_path=model,
339
+ inputs=_load_object(inputs) if inputs else {},
340
+ )
341
+ return result.to_dict()
342
+
343
+
344
+ def _validate_report_payload(
345
+ *,
346
+ scenario: Path,
347
+ generated_values: Path,
348
+ oracle_values: Path,
349
+ ) -> dict[str, JsonValue]:
350
+ report = build_validation_report(
351
+ scenario=load_validation_scenario(scenario),
352
+ generated_values=_load_object(generated_values),
353
+ oracle_values=_load_object(oracle_values),
354
+ )
355
+ return report.to_dict()
356
+
357
+
358
+ def _evaluate_payload(
359
+ *,
360
+ contract: Path,
361
+ model: Path,
362
+ scenario: Path,
363
+ workbook: Path | None,
364
+ workbook_record: Path | None,
365
+ oracle_result: Path | None,
366
+ verbose: bool,
367
+ ) -> dict[str, JsonValue]:
368
+ if workbook is not None and workbook_record is not None:
369
+ raise typer.BadParameter("use --workbook or --workbook-record, not both")
370
+
371
+ def progress(message: str) -> None:
372
+ if verbose:
373
+ typer.echo(message, err=True)
374
+
375
+ progress("load contract")
376
+ module_contract = GeneratedModuleContract.from_dict(_load_object(contract))
377
+ progress("load scenario")
378
+ validation_scenario = load_validation_scenario(scenario)
379
+
380
+ extracted_workbook = None
381
+ if workbook is not None:
382
+ progress("extract workbook start")
383
+ extracted_workbook = extract_workbook(workbook, progress=progress if verbose else None)
384
+ progress("extract workbook done")
385
+ elif workbook_record is not None:
386
+ progress("load workbook record")
387
+ extracted_workbook = WorkbookRecord.from_dict(_load_object(workbook_record))
388
+
389
+ loaded_oracle_result = None
390
+ if oracle_result is not None:
391
+ progress("load oracle result")
392
+ loaded_oracle_result = OracleResult.from_dict(_load_object(oracle_result))
393
+
394
+ progress("evaluate generated model")
395
+ result = evaluate_generated_model(
396
+ contract=module_contract,
397
+ module_path=model,
398
+ scenario=validation_scenario,
399
+ workbook=extracted_workbook,
400
+ oracle_result=loaded_oracle_result,
401
+ )
402
+ progress("evaluate generated model done")
403
+ return result.to_dict()
404
+
405
+
406
+ def _conversion_plan_payload(
407
+ *,
408
+ workbook: Path,
409
+ plan_id: str | None,
410
+ benchmark_role: BenchmarkRole,
411
+ modelwright_commit: str,
412
+ include_source_path: bool,
413
+ ) -> dict[str, JsonValue]:
414
+ workbook_record = extract_workbook(workbook)
415
+ graph = build_dependency_graph(workbook_record)
416
+ reference_index = build_formula_reference_index(graph)
417
+ expressions = {
418
+ cell.cell_ref: translate_formula_cell(cell, graph, reference_index)
419
+ for cell in workbook_record.cells
420
+ if cell.formula is not None
421
+ }
422
+ plan = build_conversion_plan(
423
+ plan_id=plan_id or f"conversion-plan:{workbook.name}",
424
+ workbook=workbook_record,
425
+ graph=graph,
426
+ expressions=expressions,
427
+ benchmark_role=benchmark_role,
428
+ modelwright_commit=modelwright_commit,
429
+ include_source_path=include_source_path,
430
+ )
431
+ return plan.to_dict()
432
+
433
+
434
+ def _emit_json(payload: dict[str, JsonValue]) -> None:
435
+ typer.echo(json.dumps(payload, indent=2, sort_keys=True))
436
+
437
+
438
+ def _load_object(path: str | Path) -> dict[str, JsonValue]:
439
+ try:
440
+ data = json.loads(Path(path).read_text(encoding="utf-8"))
441
+ except OSError as error:
442
+ raise typer.BadParameter(f"could not read JSON file {path}: {error}") from error
443
+ except json.JSONDecodeError as error:
444
+ raise typer.BadParameter(f"could not parse JSON file {path}: {error}") from error
445
+
446
+ if not isinstance(data, dict):
447
+ raise typer.BadParameter(f"expected JSON object in {path}")
448
+ return data
449
+
450
+
451
+ def _parse_benchmark_role(value: str) -> BenchmarkRole:
452
+ supported: tuple[BenchmarkRole, ...] = (
453
+ "primary_benchmark",
454
+ "stress_benchmark",
455
+ "broken_reference_regression",
456
+ "synthetic_fixture",
457
+ "ad_hoc_private",
458
+ )
459
+ if value not in supported:
460
+ supported_values = ", ".join(supported)
461
+ raise typer.BadParameter(f"unsupported benchmark role {value!r}; expected one of: {supported_values}")
462
+ return cast(BenchmarkRole, value)
463
+
464
+
465
+ if __name__ == "__main__":
466
+ app()