iints-sdk-python35 1.1.1__tar.gz → 1.1.3__tar.gz

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 (158) hide show
  1. {iints_sdk_python35-1.1.1/src/iints_sdk_python35.egg-info → iints_sdk_python35-1.1.3}/PKG-INFO +28 -6
  2. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/README.md +27 -5
  3. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/pyproject.toml +2 -1
  4. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/__init__.py +9 -1
  5. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/ai/__init__.py +2 -0
  6. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/ai/backends/ollama.py +84 -1
  7. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/ai/cli.py +157 -20
  8. iints_sdk_python35-1.1.3/src/iints/ai/prepare.py +342 -0
  9. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/cli/cli.py +29 -0
  10. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3/src/iints_sdk_python35.egg-info}/PKG-INFO +28 -6
  11. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints_sdk_python35.egg-info/SOURCES.txt +2 -0
  12. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints_sdk_python35.egg-info/entry_points.txt +1 -0
  13. iints_sdk_python35-1.1.3/tests/test_install_doctor.py +32 -0
  14. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/LICENSE +0 -0
  15. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/setup.cfg +0 -0
  16. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/ai/assistant.py +0 -0
  17. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/ai/backends/__init__.py +0 -0
  18. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/ai/backends/base.py +0 -0
  19. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/ai/backends/mistral_api.py +0 -0
  20. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/ai/mdmp_guard.py +0 -0
  21. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/ai/model_catalog.py +0 -0
  22. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/ai/prompts.py +0 -0
  23. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/__init__.py +0 -0
  24. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/algorithm_xray.py +0 -0
  25. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/baseline.py +0 -0
  26. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/clinical_benchmark.py +0 -0
  27. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/clinical_metrics.py +0 -0
  28. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/clinical_tir_analyzer.py +0 -0
  29. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/diabetes_metrics.py +0 -0
  30. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/edge_efficiency.py +0 -0
  31. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/edge_performance_monitor.py +0 -0
  32. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/explainability.py +0 -0
  33. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/explainable_ai.py +0 -0
  34. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/hardware_benchmark.py +0 -0
  35. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/metrics.py +0 -0
  36. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/population_report.py +0 -0
  37. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/reporting.py +0 -0
  38. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/safety_index.py +0 -0
  39. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/sensor_filtering.py +0 -0
  40. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/analysis/validator.py +0 -0
  41. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/api/__init__.py +0 -0
  42. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/api/base_algorithm.py +0 -0
  43. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/api/registry.py +0 -0
  44. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/api/template_algorithm.py +0 -0
  45. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/assets/iints_logo.png +0 -0
  46. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/cli/__init__.py +0 -0
  47. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/__init__.py +0 -0
  48. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/algorithms/__init__.py +0 -0
  49. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/algorithms/battle_runner.py +0 -0
  50. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/algorithms/correction_bolus.py +0 -0
  51. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/algorithms/discovery.py +0 -0
  52. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/algorithms/fixed_basal_bolus.py +0 -0
  53. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/algorithms/hybrid_algorithm.py +0 -0
  54. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/algorithms/lstm_algorithm.py +0 -0
  55. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/algorithms/mock_algorithms.py +0 -0
  56. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/algorithms/pid_controller.py +0 -0
  57. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/algorithms/standard_pump_algo.py +0 -0
  58. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/device.py +0 -0
  59. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/device_manager.py +0 -0
  60. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/devices/__init__.py +0 -0
  61. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/devices/models.py +0 -0
  62. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/patient/__init__.py +0 -0
  63. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/patient/bergman_model.py +0 -0
  64. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/patient/models.py +0 -0
  65. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/patient/patient_factory.py +0 -0
  66. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/patient/profile.py +0 -0
  67. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/safety/__init__.py +0 -0
  68. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/safety/config.py +0 -0
  69. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/safety/input_validator.py +0 -0
  70. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/safety/supervisor.py +0 -0
  71. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/simulation/__init__.py +0 -0
  72. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/simulation/scenario_parser.py +0 -0
  73. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/simulator.py +0 -0
  74. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/core/supervisor.py +0 -0
  75. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/__init__.py +0 -0
  76. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/adapter.py +0 -0
  77. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/column_mapper.py +0 -0
  78. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/contracts.py +0 -0
  79. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/datasets.json +0 -0
  80. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/demo/__init__.py +0 -0
  81. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/demo/demo_cgm.csv +0 -0
  82. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/guardians.py +0 -0
  83. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/importer.py +0 -0
  84. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/ingestor.py +0 -0
  85. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/mdmp_visualizer.py +0 -0
  86. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/nightscout.py +0 -0
  87. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/quality_checker.py +0 -0
  88. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/registry.py +0 -0
  89. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/runner.py +0 -0
  90. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/synthetic_mirror.py +0 -0
  91. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/tidepool.py +0 -0
  92. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/universal_parser.py +0 -0
  93. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/virtual_patients/clinic_safe_baseline.yaml +0 -0
  94. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/virtual_patients/clinic_safe_hyper_challenge.yaml +0 -0
  95. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/virtual_patients/clinic_safe_hypo_prone.yaml +0 -0
  96. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/virtual_patients/clinic_safe_midnight.yaml +0 -0
  97. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/virtual_patients/clinic_safe_pizza.yaml +0 -0
  98. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/virtual_patients/clinic_safe_stress_meal.yaml +0 -0
  99. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/virtual_patients/default_patient.yaml +0 -0
  100. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/data/virtual_patients/patient_559_config.yaml +0 -0
  101. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/emulation/__init__.py +0 -0
  102. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/emulation/legacy_base.py +0 -0
  103. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/emulation/medtronic_780g.py +0 -0
  104. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/emulation/omnipod_5.py +0 -0
  105. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/emulation/tandem_controliq.py +0 -0
  106. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/highlevel.py +0 -0
  107. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/learning/__init__.py +0 -0
  108. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/learning/autonomous_optimizer.py +0 -0
  109. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/learning/learning_system.py +0 -0
  110. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/mdmp/__init__.py +0 -0
  111. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/mdmp/backend.py +0 -0
  112. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/metrics.py +0 -0
  113. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/population/__init__.py +0 -0
  114. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/population/generator.py +0 -0
  115. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/population/runner.py +0 -0
  116. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/presets/__init__.py +0 -0
  117. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/presets/evidence_sources.yaml +0 -0
  118. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/presets/forecast_calibration_profiles.yaml +0 -0
  119. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/presets/golden_benchmark.yaml +0 -0
  120. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/presets/presets.json +0 -0
  121. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/presets/safety_contract_default.yaml +0 -0
  122. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/presets/validation_profiles.yaml +0 -0
  123. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/research/__init__.py +0 -0
  124. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/research/audit.py +0 -0
  125. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/research/calibration_gate.py +0 -0
  126. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/research/config.py +0 -0
  127. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/research/dataset.py +0 -0
  128. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/research/evaluation.py +0 -0
  129. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/research/losses.py +0 -0
  130. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/research/metrics.py +0 -0
  131. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/research/model_registry.py +0 -0
  132. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/research/predictor.py +0 -0
  133. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/scenarios/__init__.py +0 -0
  134. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/scenarios/generator.py +0 -0
  135. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/templates/__init__.py +0 -0
  136. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/templates/default_algorithm.py +0 -0
  137. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/templates/scenarios/__init__.py +0 -0
  138. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/templates/scenarios/chaos_insulin_stacking.json +0 -0
  139. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/templates/scenarios/chaos_runaway_ai.json +0 -0
  140. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/templates/scenarios/example_scenario.json +0 -0
  141. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/templates/scenarios/exercise_stress.json +0 -0
  142. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/utils/__init__.py +0 -0
  143. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/utils/plotting.py +0 -0
  144. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/utils/run_io.py +0 -0
  145. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/validation/__init__.py +0 -0
  146. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/validation/golden.py +0 -0
  147. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/validation/replay.py +0 -0
  148. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/validation/run_validation.py +0 -0
  149. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/validation/safety_contract.py +0 -0
  150. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/validation/schemas.py +0 -0
  151. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/visualization/__init__.py +0 -0
  152. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/visualization/cockpit.py +0 -0
  153. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints/visualization/uncertainty_cloud.py +0 -0
  154. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints_sdk_python35.egg-info/dependency_links.txt +0 -0
  155. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints_sdk_python35.egg-info/requires.txt +0 -0
  156. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/src/iints_sdk_python35.egg-info/top_level.txt +0 -0
  157. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/tests/test_bergman.py +0 -0
  158. {iints_sdk_python35-1.1.1 → iints_sdk_python35-1.1.3}/tests/test_population.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: iints-sdk-python35
3
- Version: 1.1.1
3
+ Version: 1.1.3
4
4
  Summary: A pre-clinical Edge-AI SDK for diabetes management validation.
5
5
  Author-email: Rune Bobbaers <rune.bobbaers@gmail.com>
6
6
  Project-URL: Homepage, https://github.com/python35/IINTS-SDK
@@ -110,15 +110,23 @@ ollama pull ministral-3:8b
110
110
  iints ai local-check --model ministral-3:8b
111
111
  ```
112
112
 
113
- Example commands:
113
+ `local-check` now performs a tiny generation smoke-test by default, so it verifies both model presence and real inference readiness.
114
+
115
+ Recommended flow:
116
+
117
+ ```bash
118
+ iints quickstart --project-name iints_quickstart
119
+ cd iints_quickstart
120
+ iints presets run --name baseline_t1d --algo algorithms/example_algorithm.py
121
+ iints ai prepare results/<run_id>
122
+ iints ai report results/<run_id>
123
+ ```
124
+
125
+ Direct JSON mode still works if you already have your own payloads and signed MDMP artifact:
114
126
 
115
127
  ```bash
116
128
  iints ai explain results/step.json \
117
129
  --mdmp-cert results/report.signed.mdmp
118
-
119
- iints ai report results/simulation_run.json \
120
- --mdmp-cert results/report.signed.mdmp \
121
- --output results/ai_report.md
122
130
  ```
123
131
 
124
132
  Notes:
@@ -127,8 +135,22 @@ Notes:
127
135
  - The SDK now targets the open local `Ministral 3` Ollama model by default.
128
136
  - Users can choose a larger or smaller local Mistral-family model with `--model ...`.
129
137
  - Large JSON payloads are clipped automatically before prompt generation to keep local inference stable.
138
+ - `iints ai prepare <run_dir>` now creates AI-ready JSON payloads and, when MDMP is installed, a local development certificate plus keypair in `<run_dir>/ai/`.
139
+ - If Ollama closes the connection during generation, the SDK now surfaces an explicit recovery hint and points users toward `ministral-3:3b` for lower-memory systems.
140
+ - After `iints ai prepare`, you can point `iints ai explain|trends|anomalies|report` directly at the run directory.
130
141
  - Output is research-only and not medical advice.
131
142
 
143
+ Troubleshooting:
144
+ - If `iints ai ...` says `No such command 'ai'`, your environment usually still has a legacy `iints` package installed alongside `iints-sdk-python35`.
145
+ - Run `iints-sdk-doctor` first.
146
+ - If it reports a conflict, repair the environment with:
147
+
148
+ ```bash
149
+ python -m pip uninstall -y iints iints-sdk-python35
150
+ python -m pip install -U "iints-sdk-python35[mdmp]==1.1.3"
151
+ hash -r
152
+ ```
153
+
132
154
  ## MDMP (Short)
133
155
  MDMP is the data-quality protocol used by IINTS.
134
156
 
@@ -59,15 +59,23 @@ ollama pull ministral-3:8b
59
59
  iints ai local-check --model ministral-3:8b
60
60
  ```
61
61
 
62
- Example commands:
62
+ `local-check` now performs a tiny generation smoke-test by default, so it verifies both model presence and real inference readiness.
63
+
64
+ Recommended flow:
65
+
66
+ ```bash
67
+ iints quickstart --project-name iints_quickstart
68
+ cd iints_quickstart
69
+ iints presets run --name baseline_t1d --algo algorithms/example_algorithm.py
70
+ iints ai prepare results/<run_id>
71
+ iints ai report results/<run_id>
72
+ ```
73
+
74
+ Direct JSON mode still works if you already have your own payloads and signed MDMP artifact:
63
75
 
64
76
  ```bash
65
77
  iints ai explain results/step.json \
66
78
  --mdmp-cert results/report.signed.mdmp
67
-
68
- iints ai report results/simulation_run.json \
69
- --mdmp-cert results/report.signed.mdmp \
70
- --output results/ai_report.md
71
79
  ```
72
80
 
73
81
  Notes:
@@ -76,8 +84,22 @@ Notes:
76
84
  - The SDK now targets the open local `Ministral 3` Ollama model by default.
77
85
  - Users can choose a larger or smaller local Mistral-family model with `--model ...`.
78
86
  - Large JSON payloads are clipped automatically before prompt generation to keep local inference stable.
87
+ - `iints ai prepare <run_dir>` now creates AI-ready JSON payloads and, when MDMP is installed, a local development certificate plus keypair in `<run_dir>/ai/`.
88
+ - If Ollama closes the connection during generation, the SDK now surfaces an explicit recovery hint and points users toward `ministral-3:3b` for lower-memory systems.
89
+ - After `iints ai prepare`, you can point `iints ai explain|trends|anomalies|report` directly at the run directory.
79
90
  - Output is research-only and not medical advice.
80
91
 
92
+ Troubleshooting:
93
+ - If `iints ai ...` says `No such command 'ai'`, your environment usually still has a legacy `iints` package installed alongside `iints-sdk-python35`.
94
+ - Run `iints-sdk-doctor` first.
95
+ - If it reports a conflict, repair the environment with:
96
+
97
+ ```bash
98
+ python -m pip uninstall -y iints iints-sdk-python35
99
+ python -m pip install -U "iints-sdk-python35[mdmp]==1.1.3"
100
+ hash -r
101
+ ```
102
+
81
103
  ## MDMP (Short)
82
104
  MDMP is the data-quality protocol used by IINTS.
83
105
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "iints-sdk-python35"
7
- version = "1.1.1"
7
+ version = "1.1.3"
8
8
  authors = [
9
9
  { name="Rune Bobbaers", email="rune.bobbaers@gmail.com" },
10
10
  ]
@@ -65,6 +65,7 @@ mdmp = [
65
65
 
66
66
  [project.scripts]
67
67
  iints = "iints.cli.cli:app"
68
+ iints-sdk-doctor = "iints_sdk_python35_doctor:main"
68
69
 
69
70
  [project.entry-points."iints.algorithms"]
70
71
  "PID Controller" = "iints.core.algorithms.pid_controller:PIDController"
@@ -3,7 +3,15 @@
3
3
  import pandas as pd # Required for type hints like pd.DataFrame
4
4
  from typing import Optional
5
5
 
6
- __version__ = "0.1.22"
6
+ try:
7
+ from importlib.metadata import PackageNotFoundError, version
8
+ except ImportError: # pragma: no cover - Python < 3.8 fallback
9
+ from importlib_metadata import PackageNotFoundError, version # type: ignore
10
+
11
+ try:
12
+ __version__ = version("iints-sdk-python35")
13
+ except PackageNotFoundError: # pragma: no cover - source tree fallback
14
+ __version__ = "1.1.3"
7
15
 
8
16
  # Note to developers: this SDK is currently maintained by a single author.
9
17
  # Please report bugs via GitHub issues and feel free to contribute fixes via PRs.
@@ -2,6 +2,7 @@ from .assistant import AIResponse, IINTSAssistant
2
2
  from .backends import DEFAULT_MINISTRAL_MODEL, DEFAULT_OLLAMA_HOST, OllamaBackend
3
3
  from .mdmp_guard import GuardResult, MDMPGuard
4
4
  from .model_catalog import LocalMistralModelProfile, list_local_mistral_models
5
+ from .prepare import prepare_ai_ready_artifacts
5
6
 
6
7
  __all__ = [
7
8
  "AIResponse",
@@ -13,4 +14,5 @@ __all__ = [
13
14
  "MDMPGuard",
14
15
  "LocalMistralModelProfile",
15
16
  "list_local_mistral_models",
17
+ "prepare_ai_ready_artifacts",
16
18
  ]
@@ -2,6 +2,8 @@ from __future__ import annotations
2
2
 
3
3
  import json
4
4
  import os
5
+ from http.client import IncompleteRead, RemoteDisconnected
6
+ from time import sleep
5
7
  from urllib import error, request
6
8
 
7
9
 
@@ -40,6 +42,20 @@ class OllamaBackend:
40
42
  def _pull_hint(self) -> str:
41
43
  return f"ollama pull {self.model_name}"
42
44
 
45
+ def _generation_failure_hint(self) -> str:
46
+ resolved = self.resolved_model_name or self.model_name
47
+ return (
48
+ "Ollama closed the generation connection before returning a response.\n"
49
+ f"Endpoint: {self.base_url}\n"
50
+ f"Model: {resolved}\n"
51
+ "This usually means the model crashed while loading, the daemon restarted, "
52
+ "or the machine ran out of memory.\n"
53
+ "Try one of these:\n"
54
+ f" 1. Run `ollama run {resolved} \"Reply with OK.\"` to confirm direct inference works.\n"
55
+ " 2. Run `iints ai local-check --smoke-test` to validate a real generation path.\n"
56
+ " 3. Switch to a smaller local model such as `ministral-3:3b` if memory is tight."
57
+ )
58
+
43
59
  def _requires_ministral_3_runtime(self) -> bool:
44
60
  requested = self.model_name.strip().lower()
45
61
  return requested.startswith("ministral-3") or requested == "ministral"
@@ -68,6 +84,15 @@ class OllamaBackend:
68
84
  payload: dict[str, object] | None = None,
69
85
  *,
70
86
  method: str = "POST",
87
+ ) -> dict[str, object]:
88
+ return self._request_json_once(path, payload, method=method)
89
+
90
+ def _request_json_once(
91
+ self,
92
+ path: str,
93
+ payload: dict[str, object] | None = None,
94
+ *,
95
+ method: str = "POST",
71
96
  ) -> dict[str, object]:
72
97
  url = f"{self.base_url}{path}"
73
98
  body = None
@@ -92,6 +117,12 @@ class OllamaBackend:
92
117
  f"Could not reach Ollama at {self.base_url}. "
93
118
  "Start Ollama or set OLLAMA_HOST to the correct endpoint."
94
119
  ) from exc
120
+ except (RemoteDisconnected, ConnectionResetError, IncompleteRead) as exc:
121
+ if path == "/api/generate":
122
+ raise RuntimeError(self._generation_failure_hint()) from exc
123
+ raise RuntimeError(
124
+ f"Ollama connection closed unexpectedly while calling {path} at {self.base_url}."
125
+ ) from exc
95
126
 
96
127
  try:
97
128
  payload_json = json.loads(text)
@@ -223,6 +254,43 @@ class OllamaBackend:
223
254
  "version_ok": version_ok,
224
255
  }
225
256
 
257
+ def smoke_test(self) -> dict[str, object]:
258
+ resolved_model = self.ensure_model_ready()
259
+ payload = {
260
+ "model": resolved_model,
261
+ "system": "You are a health check. Reply with exactly: OK",
262
+ "prompt": "Reply with exactly: OK",
263
+ "stream": False,
264
+ "options": {
265
+ "temperature": 0,
266
+ "num_predict": 8,
267
+ },
268
+ }
269
+
270
+ last_error: Exception | None = None
271
+ for attempt in range(2):
272
+ try:
273
+ response = self._request_json_once("/api/generate", payload)
274
+ text = response.get("response")
275
+ if not isinstance(text, str) or not text.strip():
276
+ raise RuntimeError("Ollama returned an empty smoke-test completion.")
277
+ return {
278
+ "ok": True,
279
+ "response": text.strip(),
280
+ "attempts": attempt + 1,
281
+ }
282
+ except (RuntimeError, RemoteDisconnected, ConnectionResetError, IncompleteRead) as exc:
283
+ if not isinstance(exc, RuntimeError):
284
+ exc = RuntimeError(self._generation_failure_hint())
285
+ last_error = exc
286
+ if attempt == 0:
287
+ sleep(1.0)
288
+ continue
289
+ break
290
+
291
+ assert last_error is not None
292
+ raise last_error
293
+
226
294
  def complete(self, *, system_prompt: str, user_prompt: str) -> str:
227
295
  resolved_model = self.ensure_model_ready()
228
296
  payload = {
@@ -231,7 +299,22 @@ class OllamaBackend:
231
299
  "prompt": user_prompt,
232
300
  "stream": False,
233
301
  }
234
- response = self._request_json("/api/generate", payload)
302
+ last_error: Exception | None = None
303
+ for attempt in range(2):
304
+ try:
305
+ response = self._request_json_once("/api/generate", payload)
306
+ break
307
+ except (RuntimeError, RemoteDisconnected, ConnectionResetError, IncompleteRead) as exc:
308
+ if not isinstance(exc, RuntimeError):
309
+ exc = RuntimeError(self._generation_failure_hint())
310
+ last_error = exc
311
+ if attempt == 0:
312
+ sleep(1.0)
313
+ continue
314
+ raise exc
315
+ else:
316
+ assert last_error is not None
317
+ raise last_error
235
318
  text = response.get("response")
236
319
  if not isinstance(text, str) or not text.strip():
237
320
  raise RuntimeError("Ollama returned an empty completion.")
@@ -13,6 +13,7 @@ from typing_extensions import Annotated
13
13
  from .assistant import AIResponse, IINTSAssistant
14
14
  from .backends import DEFAULT_MINISTRAL_MODEL, OllamaBackend
15
15
  from .model_catalog import list_local_mistral_models
16
+ from .prepare import prepare_ai_ready_artifacts
16
17
 
17
18
 
18
19
  app = typer.Typer(help="Research-only AI assistant commands gated by MDMP certification.")
@@ -36,6 +37,57 @@ def _load_json_payload(path: Path, label: str) -> Any:
36
37
  return payload
37
38
 
38
39
 
40
+ def _default_prepared_payload(task: str, ai_dir: Path) -> Path:
41
+ candidates = {
42
+ "explain": ["step_riskiest.json", "step_latest.json"],
43
+ "trends": ["trends_payload.json"],
44
+ "anomalies": ["anomalies_payload.json"],
45
+ "report": ["report_payload.json"],
46
+ }.get(task, [])
47
+ for filename in candidates:
48
+ candidate = ai_dir / filename
49
+ if candidate.is_file():
50
+ return candidate
51
+ expected = ", ".join(candidates) if candidates else "prepared payload"
52
+ raise typer.BadParameter(
53
+ f"No prepared AI payload found in {ai_dir}. Expected one of: {expected}. "
54
+ "Run `iints ai prepare <run_dir>` first."
55
+ )
56
+
57
+
58
+ def _resolve_cli_inputs(
59
+ *,
60
+ task: str,
61
+ input_path: Path,
62
+ mdmp_cert: Path | None,
63
+ public_key: Path | None,
64
+ trust_store: Path | None,
65
+ ) -> tuple[Path, Path, Path | None]:
66
+ resolved_input = input_path
67
+ resolved_cert = mdmp_cert
68
+ resolved_public_key = public_key
69
+
70
+ if input_path.is_dir():
71
+ ai_dir = input_path / "ai"
72
+ resolved_input = _default_prepared_payload(task, ai_dir)
73
+ if resolved_cert is None:
74
+ candidate_cert = ai_dir / "report.signed.mdmp"
75
+ if candidate_cert.is_file():
76
+ resolved_cert = candidate_cert
77
+ if resolved_public_key is None and trust_store is None:
78
+ candidate_public_key = ai_dir / "keys" / "mdmp_pub_v1.pem"
79
+ if candidate_public_key.is_file():
80
+ resolved_public_key = candidate_public_key
81
+
82
+ if resolved_cert is None:
83
+ raise typer.BadParameter(
84
+ "No MDMP certificate provided. Pass --mdmp-cert or run "
85
+ "`iints ai prepare <run_dir>` to generate a local development certificate."
86
+ )
87
+
88
+ return resolved_input, resolved_cert, resolved_public_key
89
+
90
+
39
91
  def _write_output(path: Path | None, response: AIResponse) -> None:
40
92
  if path is None:
41
93
  return
@@ -59,6 +111,7 @@ def _render_local_check(console: Console, status: dict[str, object]) -> None:
59
111
  installed_text = ", ".join(str(item) for item in installed) if isinstance(installed, list) and installed else "none"
60
112
  ready = bool(status.get("ready"))
61
113
  resolved_model = status.get("resolved_model") or "not found"
114
+ smoke_text = status.get("smoke_test") or "not run"
62
115
  console.print(
63
116
  Panel(
64
117
  "\n".join(
@@ -74,6 +127,7 @@ def _render_local_check(console: Console, status: dict[str, object]) -> None:
74
127
  if status.get("pull_command")
75
128
  else "Pull command: not needed"
76
129
  ),
130
+ f"Generate smoke-test: {smoke_text}",
77
131
  ]
78
132
  ),
79
133
  title="IINTS AI Local Check",
@@ -117,6 +171,47 @@ def models() -> None:
117
171
  )
118
172
 
119
173
 
174
+ @app.command("prepare")
175
+ def prepare(
176
+ run_dir: Annotated[Path, typer.Argument(help="Run output directory containing results.csv and run_metadata.json.")],
177
+ create_dev_mdmp_cert: Annotated[
178
+ bool,
179
+ typer.Option(
180
+ "--create-dev-mdmp-cert/--no-create-dev-mdmp-cert",
181
+ help="Generate a local development MDMP certificate and keypair for AI commands.",
182
+ ),
183
+ ] = True,
184
+ grade: Annotated[str, typer.Option(help="Grade to embed in the local development MDMP certificate.")] = "research_grade",
185
+ expires_days: Annotated[int, typer.Option(help="Certificate expiry window in days for local development certs.")] = 30,
186
+ key_dir: Annotated[Optional[Path], typer.Option(help="Optional directory to store the generated local MDMP keypair.")] = None,
187
+ ) -> None:
188
+ console = Console()
189
+ try:
190
+ outputs = prepare_ai_ready_artifacts(
191
+ run_dir,
192
+ create_dev_mdmp_cert=create_dev_mdmp_cert,
193
+ grade=grade,
194
+ expires_days=expires_days,
195
+ key_dir=key_dir,
196
+ )
197
+ except Exception as exc:
198
+ console.print(f"[bold red]Error:[/bold red] {exc}")
199
+ raise typer.Exit(code=1)
200
+
201
+ table = Table(title="IINTS AI Prepared Artifacts")
202
+ table.add_column("Artifact", style="cyan")
203
+ table.add_column("Path", overflow="fold")
204
+ for key, value in outputs.items():
205
+ table.add_row(key, value)
206
+ console.print(table)
207
+ console.print("[green]Prepared AI payloads are ready.[/green]")
208
+ if "mdmp_cert" in outputs:
209
+ console.print(
210
+ "[green]You can now run:[/green] "
211
+ f"`iints ai report {run_dir}` or `iints ai explain {run_dir}`"
212
+ )
213
+
214
+
120
215
  def _build_assistant(
121
216
  *,
122
217
  mdmp_cert: Path,
@@ -146,6 +241,13 @@ def local_check(
146
241
  model: Annotated[str, typer.Option(help="Ollama model name to validate locally.")] = DEFAULT_MINISTRAL_MODEL,
147
242
  ollama_host: Annotated[Optional[str], typer.Option(help="Override the Ollama base URL.")] = None,
148
243
  timeout_seconds: Annotated[float, typer.Option(help="HTTP timeout for Ollama health checks.")] = 120.0,
244
+ smoke_test: Annotated[
245
+ bool,
246
+ typer.Option(
247
+ "--smoke-test/--no-smoke-test",
248
+ help="Run a tiny generation request after health checks to prove the model can actually answer.",
249
+ ),
250
+ ] = True,
149
251
  ) -> None:
150
252
  console = Console()
151
253
  backend = OllamaBackend(model_name=model, base_url=ollama_host, timeout_seconds=timeout_seconds)
@@ -157,6 +259,13 @@ def local_check(
157
259
  )
158
260
  raise typer.Exit(code=1)
159
261
  status = backend.healthcheck()
262
+ if smoke_test and bool(status.get("ready")):
263
+ smoke = backend.smoke_test()
264
+ status["smoke_test"] = f"OK ({smoke.get('response')})"
265
+ elif smoke_test:
266
+ status["smoke_test"] = "skipped (model not ready)"
267
+ else:
268
+ status["smoke_test"] = "disabled"
160
269
  _render_local_check(console, status)
161
270
  if not bool(status.get("ready")):
162
271
  raise typer.Exit(code=1)
@@ -169,8 +278,8 @@ def local_check(
169
278
 
170
279
  @app.command("explain")
171
280
  def explain(
172
- input_json: Annotated[Path, typer.Argument(help="JSON file with a single simulation step or decision context.")],
173
- mdmp_cert: Annotated[Path, typer.Option(help="Signed MDMP artifact required before AI analysis can run.")],
281
+ input_json: Annotated[Path, typer.Argument(help="Prepared run directory or JSON file with a single simulation step or decision context.")],
282
+ mdmp_cert: Annotated[Optional[Path], typer.Option(help="Signed MDMP artifact required before AI analysis can run.")] = None,
174
283
  mode: Annotated[str, typer.Option(help="AI backend mode. Use 'local' for Ollama/Ministral.")] = "auto",
175
284
  model: Annotated[str, typer.Option(help="Ollama model name to use.")] = DEFAULT_MINISTRAL_MODEL,
176
285
  minimum_grade: Annotated[str, typer.Option(help="Minimum MDMP grade required to allow analysis.")] = "research_grade",
@@ -182,13 +291,20 @@ def explain(
182
291
  ) -> None:
183
292
  console = Console()
184
293
  try:
185
- payload = _load_json_payload(input_json, "Input JSON")
186
- assistant = _build_assistant(
294
+ resolved_input, resolved_cert, resolved_public_key = _resolve_cli_inputs(
295
+ task="explain",
296
+ input_path=input_json,
187
297
  mdmp_cert=mdmp_cert,
298
+ public_key=public_key,
299
+ trust_store=trust_store,
300
+ )
301
+ payload = _load_json_payload(resolved_input, "Input JSON")
302
+ assistant = _build_assistant(
303
+ mdmp_cert=resolved_cert,
188
304
  mode=mode,
189
305
  model=model,
190
306
  minimum_grade=minimum_grade,
191
- public_key=public_key,
307
+ public_key=resolved_public_key,
192
308
  trust_store=trust_store,
193
309
  ollama_host=ollama_host,
194
310
  timeout_seconds=timeout_seconds,
@@ -203,8 +319,8 @@ def explain(
203
319
 
204
320
  @app.command("trends")
205
321
  def trends(
206
- input_json: Annotated[Path, typer.Argument(help="JSON file with glucose trace data or a run payload.")],
207
- mdmp_cert: Annotated[Path, typer.Option(help="Signed MDMP artifact required before AI analysis can run.")],
322
+ input_json: Annotated[Path, typer.Argument(help="Prepared run directory or JSON file with glucose trace data or a run payload.")],
323
+ mdmp_cert: Annotated[Optional[Path], typer.Option(help="Signed MDMP artifact required before AI analysis can run.")] = None,
208
324
  mode: Annotated[str, typer.Option(help="AI backend mode. Use 'local' for Ollama/Ministral.")] = "auto",
209
325
  model: Annotated[str, typer.Option(help="Ollama model name to use.")] = DEFAULT_MINISTRAL_MODEL,
210
326
  minimum_grade: Annotated[str, typer.Option(help="Minimum MDMP grade required to allow analysis.")] = "research_grade",
@@ -216,13 +332,20 @@ def trends(
216
332
  ) -> None:
217
333
  console = Console()
218
334
  try:
219
- payload = _load_json_payload(input_json, "Input JSON")
220
- assistant = _build_assistant(
335
+ resolved_input, resolved_cert, resolved_public_key = _resolve_cli_inputs(
336
+ task="trends",
337
+ input_path=input_json,
221
338
  mdmp_cert=mdmp_cert,
339
+ public_key=public_key,
340
+ trust_store=trust_store,
341
+ )
342
+ payload = _load_json_payload(resolved_input, "Input JSON")
343
+ assistant = _build_assistant(
344
+ mdmp_cert=resolved_cert,
222
345
  mode=mode,
223
346
  model=model,
224
347
  minimum_grade=minimum_grade,
225
- public_key=public_key,
348
+ public_key=resolved_public_key,
226
349
  trust_store=trust_store,
227
350
  ollama_host=ollama_host,
228
351
  timeout_seconds=timeout_seconds,
@@ -237,8 +360,8 @@ def trends(
237
360
 
238
361
  @app.command("anomalies")
239
362
  def anomalies(
240
- input_json: Annotated[Path, typer.Argument(help="JSON file with simulation results or run summary.")],
241
- mdmp_cert: Annotated[Path, typer.Option(help="Signed MDMP artifact required before AI analysis can run.")],
363
+ input_json: Annotated[Path, typer.Argument(help="Prepared run directory or JSON file with simulation results or run summary.")],
364
+ mdmp_cert: Annotated[Optional[Path], typer.Option(help="Signed MDMP artifact required before AI analysis can run.")] = None,
242
365
  mode: Annotated[str, typer.Option(help="AI backend mode. Use 'local' for Ollama/Ministral.")] = "auto",
243
366
  model: Annotated[str, typer.Option(help="Ollama model name to use.")] = DEFAULT_MINISTRAL_MODEL,
244
367
  minimum_grade: Annotated[str, typer.Option(help="Minimum MDMP grade required to allow analysis.")] = "research_grade",
@@ -250,13 +373,20 @@ def anomalies(
250
373
  ) -> None:
251
374
  console = Console()
252
375
  try:
253
- payload = _load_json_payload(input_json, "Input JSON")
254
- assistant = _build_assistant(
376
+ resolved_input, resolved_cert, resolved_public_key = _resolve_cli_inputs(
377
+ task="anomalies",
378
+ input_path=input_json,
255
379
  mdmp_cert=mdmp_cert,
380
+ public_key=public_key,
381
+ trust_store=trust_store,
382
+ )
383
+ payload = _load_json_payload(resolved_input, "Input JSON")
384
+ assistant = _build_assistant(
385
+ mdmp_cert=resolved_cert,
256
386
  mode=mode,
257
387
  model=model,
258
388
  minimum_grade=minimum_grade,
259
- public_key=public_key,
389
+ public_key=resolved_public_key,
260
390
  trust_store=trust_store,
261
391
  ollama_host=ollama_host,
262
392
  timeout_seconds=timeout_seconds,
@@ -271,8 +401,8 @@ def anomalies(
271
401
 
272
402
  @app.command("report")
273
403
  def report(
274
- input_json: Annotated[Path, typer.Argument(help="JSON file with run-level simulation outputs.")],
275
- mdmp_cert: Annotated[Path, typer.Option(help="Signed MDMP artifact required before AI analysis can run.")],
404
+ input_json: Annotated[Path, typer.Argument(help="Prepared run directory or JSON file with run-level simulation outputs.")],
405
+ mdmp_cert: Annotated[Optional[Path], typer.Option(help="Signed MDMP artifact required before AI analysis can run.")] = None,
276
406
  mode: Annotated[str, typer.Option(help="AI backend mode. Use 'local' for Ollama/Ministral.")] = "auto",
277
407
  model: Annotated[str, typer.Option(help="Ollama model name to use.")] = DEFAULT_MINISTRAL_MODEL,
278
408
  minimum_grade: Annotated[str, typer.Option(help="Minimum MDMP grade required to allow analysis.")] = "research_grade",
@@ -284,13 +414,20 @@ def report(
284
414
  ) -> None:
285
415
  console = Console()
286
416
  try:
287
- payload = _load_json_payload(input_json, "Input JSON")
288
- assistant = _build_assistant(
417
+ resolved_input, resolved_cert, resolved_public_key = _resolve_cli_inputs(
418
+ task="report",
419
+ input_path=input_json,
289
420
  mdmp_cert=mdmp_cert,
421
+ public_key=public_key,
422
+ trust_store=trust_store,
423
+ )
424
+ payload = _load_json_payload(resolved_input, "Input JSON")
425
+ assistant = _build_assistant(
426
+ mdmp_cert=resolved_cert,
290
427
  mode=mode,
291
428
  model=model,
292
429
  minimum_grade=minimum_grade,
293
- public_key=public_key,
430
+ public_key=resolved_public_key,
294
431
  trust_store=trust_store,
295
432
  ollama_host=ollama_host,
296
433
  timeout_seconds=timeout_seconds,