noodlelab 0.1.0__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 (323) hide show
  1. noodlelab-0.1.0/.claude/skills/noodlelab/SKILL.md +138 -0
  2. noodlelab-0.1.0/.devcontainer/devcontainer.json +21 -0
  3. noodlelab-0.1.0/.dockerignore +15 -0
  4. noodlelab-0.1.0/.github/workflows/ci.yml +89 -0
  5. noodlelab-0.1.0/.github/workflows/release.yml +287 -0
  6. noodlelab-0.1.0/.gitignore +21 -0
  7. noodlelab-0.1.0/AGENTS.md +83 -0
  8. noodlelab-0.1.0/CLAUDE.md +1 -0
  9. noodlelab-0.1.0/Dockerfile +84 -0
  10. noodlelab-0.1.0/Dockerfile.test +42 -0
  11. noodlelab-0.1.0/Dockerfile.test.dockerignore +12 -0
  12. noodlelab-0.1.0/LICENSE +21 -0
  13. noodlelab-0.1.0/Makefile +38 -0
  14. noodlelab-0.1.0/PKG-INFO +335 -0
  15. noodlelab-0.1.0/README.md +217 -0
  16. noodlelab-0.1.0/docker-compose.yml +19 -0
  17. noodlelab-0.1.0/docs/agents/adopt-in-existing-project.md +127 -0
  18. noodlelab-0.1.0/docs/agents/graph.schema.json +432 -0
  19. noodlelab-0.1.0/docs/agents/graphs-and-reports.md +114 -0
  20. noodlelab-0.1.0/docs/agents/quickstart.md +78 -0
  21. noodlelab-0.1.0/docs/editor.md +24 -0
  22. noodlelab-0.1.0/docs/images/agent.png +0 -0
  23. noodlelab-0.1.0/docs/images/groundwater.png +0 -0
  24. noodlelab-0.1.0/docs/images/hero.png +0 -0
  25. noodlelab-0.1.0/docs/images/report.png +0 -0
  26. noodlelab-0.1.0/docs/images/reporting.png +0 -0
  27. noodlelab-0.1.0/docs/images/satellite.png +0 -0
  28. noodlelab-0.1.0/docs/installation.md +56 -0
  29. noodlelab-0.1.0/docs/llms-full.txt +1007 -0
  30. noodlelab-0.1.0/docs/releasing.md +80 -0
  31. noodlelab-0.1.0/docs/server.md +51 -0
  32. noodlelab-0.1.0/docs/slurm.md +80 -0
  33. noodlelab-0.1.0/examples/noodlelab-example-pack/README.md +14 -0
  34. noodlelab-0.1.0/examples/noodlelab-example-pack/pyproject.toml +18 -0
  35. noodlelab-0.1.0/examples/noodlelab-example-pack/src/noodlelab_example/__init__.py +151 -0
  36. noodlelab-0.1.0/frontend/admin.html +29 -0
  37. noodlelab-0.1.0/frontend/index.html +106 -0
  38. noodlelab-0.1.0/frontend/package-lock.json +1164 -0
  39. noodlelab-0.1.0/frontend/package.json +22 -0
  40. noodlelab-0.1.0/frontend/scripts/vendor-litegraph.mjs +34 -0
  41. noodlelab-0.1.0/frontend/src/admin.ts +486 -0
  42. noodlelab-0.1.0/frontend/src/agent.ts +258 -0
  43. noodlelab-0.1.0/frontend/src/annotation.ts +194 -0
  44. noodlelab-0.1.0/frontend/src/api.ts +174 -0
  45. noodlelab-0.1.0/frontend/src/compare.ts +190 -0
  46. noodlelab-0.1.0/frontend/src/connection.ts +50 -0
  47. noodlelab-0.1.0/frontend/src/dom.ts +99 -0
  48. noodlelab-0.1.0/frontend/src/env.d.ts +2 -0
  49. noodlelab-0.1.0/frontend/src/explorer.ts +217 -0
  50. noodlelab-0.1.0/frontend/src/filepicker.ts +275 -0
  51. noodlelab-0.1.0/frontend/src/graph.ts +643 -0
  52. noodlelab-0.1.0/frontend/src/health.ts +66 -0
  53. noodlelab-0.1.0/frontend/src/help.ts +408 -0
  54. noodlelab-0.1.0/frontend/src/history.ts +211 -0
  55. noodlelab-0.1.0/frontend/src/inspector.ts +306 -0
  56. noodlelab-0.1.0/frontend/src/litegraph.d.ts +11 -0
  57. noodlelab-0.1.0/frontend/src/main.ts +1672 -0
  58. noodlelab-0.1.0/frontend/src/monitor.ts +181 -0
  59. noodlelab-0.1.0/frontend/src/nodes.ts +650 -0
  60. noodlelab-0.1.0/frontend/src/outputs.ts +482 -0
  61. noodlelab-0.1.0/frontend/src/panes.ts +105 -0
  62. noodlelab-0.1.0/frontend/src/prefs.ts +40 -0
  63. noodlelab-0.1.0/frontend/src/probe.ts +69 -0
  64. noodlelab-0.1.0/frontend/src/reports.ts +374 -0
  65. noodlelab-0.1.0/frontend/src/repro.ts +120 -0
  66. noodlelab-0.1.0/frontend/src/requirements.ts +166 -0
  67. noodlelab-0.1.0/frontend/src/runstate.ts +115 -0
  68. noodlelab-0.1.0/frontend/src/select.ts +39 -0
  69. noodlelab-0.1.0/frontend/src/sharing.ts +142 -0
  70. noodlelab-0.1.0/frontend/src/study.ts +352 -0
  71. noodlelab-0.1.0/frontend/src/style.css +2584 -0
  72. noodlelab-0.1.0/frontend/src/subgraph.ts +642 -0
  73. noodlelab-0.1.0/frontend/src/theme.ts +88 -0
  74. noodlelab-0.1.0/frontend/src/tracked.ts +233 -0
  75. noodlelab-0.1.0/frontend/src/types.ts +545 -0
  76. noodlelab-0.1.0/frontend/src/zones.ts +498 -0
  77. noodlelab-0.1.0/frontend/tsconfig.json +16 -0
  78. noodlelab-0.1.0/frontend/vite.config.ts +22 -0
  79. noodlelab-0.1.0/llms.txt +37 -0
  80. noodlelab-0.1.0/pyproject.toml +180 -0
  81. noodlelab-0.1.0/render.yaml +22 -0
  82. noodlelab-0.1.0/scripts/check_tier.py +108 -0
  83. noodlelab-0.1.0/scripts/gen_llms.py +229 -0
  84. noodlelab-0.1.0/scripts/make_example_data.py +993 -0
  85. noodlelab-0.1.0/scripts/make_example_graphs.py +3285 -0
  86. noodlelab-0.1.0/src/noodlelab/__init__.py +64 -0
  87. noodlelab-0.1.0/src/noodlelab/__main__.py +3 -0
  88. noodlelab-0.1.0/src/noodlelab/agent/__init__.py +138 -0
  89. noodlelab-0.1.0/src/noodlelab/agent/guide.md +129 -0
  90. noodlelab-0.1.0/src/noodlelab/agent/mcp.py +219 -0
  91. noodlelab-0.1.0/src/noodlelab/agent/tools.py +613 -0
  92. noodlelab-0.1.0/src/noodlelab/cli.py +934 -0
  93. noodlelab-0.1.0/src/noodlelab/config.py +221 -0
  94. noodlelab-0.1.0/src/noodlelab/core/__init__.py +1 -0
  95. noodlelab-0.1.0/src/noodlelab/core/about.py +212 -0
  96. noodlelab-0.1.0/src/noodlelab/core/baseline.py +363 -0
  97. noodlelab-0.1.0/src/noodlelab/core/bundle.py +148 -0
  98. noodlelab-0.1.0/src/noodlelab/core/cache.py +66 -0
  99. noodlelab-0.1.0/src/noodlelab/core/checkpoint.py +230 -0
  100. noodlelab-0.1.0/src/noodlelab/core/checks.py +92 -0
  101. noodlelab-0.1.0/src/noodlelab/core/codecs.py +426 -0
  102. noodlelab-0.1.0/src/noodlelab/core/context.py +113 -0
  103. noodlelab-0.1.0/src/noodlelab/core/convert.py +224 -0
  104. noodlelab-0.1.0/src/noodlelab/core/events.py +185 -0
  105. noodlelab-0.1.0/src/noodlelab/core/executor.py +1693 -0
  106. noodlelab-0.1.0/src/noodlelab/core/export.py +405 -0
  107. noodlelab-0.1.0/src/noodlelab/core/figures.py +110 -0
  108. noodlelab-0.1.0/src/noodlelab/core/files.py +577 -0
  109. noodlelab-0.1.0/src/noodlelab/core/fold.py +121 -0
  110. noodlelab-0.1.0/src/noodlelab/core/graph.py +510 -0
  111. noodlelab-0.1.0/src/noodlelab/core/health.py +462 -0
  112. noodlelab-0.1.0/src/noodlelab/core/meta.py +164 -0
  113. noodlelab-0.1.0/src/noodlelab/core/monitor.py +142 -0
  114. noodlelab-0.1.0/src/noodlelab/core/montecarlo.py +306 -0
  115. noodlelab-0.1.0/src/noodlelab/core/node.py +795 -0
  116. noodlelab-0.1.0/src/noodlelab/core/ports.py +461 -0
  117. noodlelab-0.1.0/src/noodlelab/core/preview.py +201 -0
  118. noodlelab-0.1.0/src/noodlelab/core/probe.py +989 -0
  119. noodlelab-0.1.0/src/noodlelab/core/provenance.py +477 -0
  120. noodlelab-0.1.0/src/noodlelab/core/registry.py +278 -0
  121. noodlelab-0.1.0/src/noodlelab/core/reqlog.py +138 -0
  122. noodlelab-0.1.0/src/noodlelab/core/requirements.py +430 -0
  123. noodlelab-0.1.0/src/noodlelab/core/rundiff.py +168 -0
  124. noodlelab-0.1.0/src/noodlelab/core/sample.py +144 -0
  125. noodlelab-0.1.0/src/noodlelab/core/studies.py +309 -0
  126. noodlelab-0.1.0/src/noodlelab/core/suggest.py +144 -0
  127. noodlelab-0.1.0/src/noodlelab/core/tracked.py +161 -0
  128. noodlelab-0.1.0/src/noodlelab/core/types.py +107 -0
  129. noodlelab-0.1.0/src/noodlelab/core/typesys.py +706 -0
  130. noodlelab-0.1.0/src/noodlelab/core/uncertainty.py +421 -0
  131. noodlelab-0.1.0/src/noodlelab/core/units.py +230 -0
  132. noodlelab-0.1.0/src/noodlelab/examples/01-getting-started.graph.json +71 -0
  133. noodlelab-0.1.0/src/noodlelab/examples/02-units.graph.json +113 -0
  134. noodlelab-0.1.0/src/noodlelab/examples/03-plot-a-function.graph.json +77 -0
  135. noodlelab-0.1.0/src/noodlelab/examples/04-noise-and-smoothing.graph.json +156 -0
  136. noodlelab-0.1.0/src/noodlelab/examples/05-first-csv.graph.json +87 -0
  137. noodlelab-0.1.0/src/noodlelab/examples/06-straight-line-fit.graph.json +195 -0
  138. noodlelab-0.1.0/src/noodlelab/examples/07-filter-and-group.graph.json +133 -0
  139. noodlelab-0.1.0/src/noodlelab/examples/08-calibration.graph.json +276 -0
  140. noodlelab-0.1.0/src/noodlelab/examples/09-uncertainty.graph.json +211 -0
  141. noodlelab-0.1.0/src/noodlelab/examples/10-curve-fit-subgraph.graph.json +559 -0
  142. noodlelab-0.1.0/src/noodlelab/examples/11-time-series.graph.json +215 -0
  143. noodlelab-0.1.0/src/noodlelab/examples/12-signals.graph.json +196 -0
  144. noodlelab-0.1.0/src/noodlelab/examples/13-symbolic.graph.json +242 -0
  145. noodlelab-0.1.0/src/noodlelab/examples/14-first-report.graph.json +409 -0
  146. noodlelab-0.1.0/src/noodlelab/examples/15-repeat-zone.graph.json +264 -0
  147. noodlelab-0.1.0/src/noodlelab/examples/16-until-converged.graph.json +370 -0
  148. noodlelab-0.1.0/src/noodlelab/examples/17-sweep-and-optimize.graph.json +430 -0
  149. noodlelab-0.1.0/src/noodlelab/examples/18-gps-track.graph.json +208 -0
  150. noodlelab-0.1.0/src/noodlelab/examples/19-earthquakes.graph.json +173 -0
  151. noodlelab-0.1.0/src/noodlelab/examples/20-groundwater-nitrate.graph.json +2197 -0
  152. noodlelab-0.1.0/src/noodlelab/examples/21-fixed-beam.graph.json +2866 -0
  153. noodlelab-0.1.0/src/noodlelab/examples/22-watershed.graph.json +3279 -0
  154. noodlelab-0.1.0/src/noodlelab/examples/23-satellite-link.graph.json +2059 -0
  155. noodlelab-0.1.0/src/noodlelab/examples/24-cantilever-bracket.graph.json +591 -0
  156. noodlelab-0.1.0/src/noodlelab/examples/__init__.py +0 -0
  157. noodlelab-0.1.0/src/noodlelab/examples/data/05-first-csv/README.md +16 -0
  158. noodlelab-0.1.0/src/noodlelab/examples/data/05-first-csv/pendulum.csv +56 -0
  159. noodlelab-0.1.0/src/noodlelab/examples/data/06-straight-line-fit/README.md +16 -0
  160. noodlelab-0.1.0/src/noodlelab/examples/data/06-straight-line-fit/pendulum.csv +56 -0
  161. noodlelab-0.1.0/src/noodlelab/examples/data/07-filter-and-group/README.md +20 -0
  162. noodlelab-0.1.0/src/noodlelab/examples/data/07-filter-and-group/field_trial.csv +73 -0
  163. noodlelab-0.1.0/src/noodlelab/examples/data/08-calibration/README.md +11 -0
  164. noodlelab-0.1.0/src/noodlelab/examples/data/08-calibration/samples.csv +13 -0
  165. noodlelab-0.1.0/src/noodlelab/examples/data/08-calibration/standards.csv +25 -0
  166. noodlelab-0.1.0/src/noodlelab/examples/data/10-curve-fit-subgraph/README.md +17 -0
  167. noodlelab-0.1.0/src/noodlelab/examples/data/10-curve-fit-subgraph/kinetics.csv +49 -0
  168. noodlelab-0.1.0/src/noodlelab/examples/data/11-time-series/README.md +16 -0
  169. noodlelab-0.1.0/src/noodlelab/examples/data/11-time-series/station_daily.csv +5480 -0
  170. noodlelab-0.1.0/src/noodlelab/examples/data/12-signals/README.md +17 -0
  171. noodlelab-0.1.0/src/noodlelab/examples/data/12-signals/accelerometer.csv +10001 -0
  172. noodlelab-0.1.0/src/noodlelab/examples/data/14-first-report/README.md +16 -0
  173. noodlelab-0.1.0/src/noodlelab/examples/data/14-first-report/pendulum.csv +56 -0
  174. noodlelab-0.1.0/src/noodlelab/examples/data/18-gps-track/README.md +19 -0
  175. noodlelab-0.1.0/src/noodlelab/examples/data/18-gps-track/hike.csv +3526 -0
  176. noodlelab-0.1.0/src/noodlelab/examples/data/18-gps-track/waypoints.csv +6 -0
  177. noodlelab-0.1.0/src/noodlelab/examples/data/19-earthquakes/README.md +20 -0
  178. noodlelab-0.1.0/src/noodlelab/examples/data/19-earthquakes/catalog.csv +2012 -0
  179. noodlelab-0.1.0/src/noodlelab/examples/data/19-earthquakes/regions.geojson +1 -0
  180. noodlelab-0.1.0/src/noodlelab/examples/data/20-groundwater-nitrate/README.md +22 -0
  181. noodlelab-0.1.0/src/noodlelab/examples/data/20-groundwater-nitrate/districts.geojson +1 -0
  182. noodlelab-0.1.0/src/noodlelab/examples/data/20-groundwater-nitrate/river.geojson +1 -0
  183. noodlelab-0.1.0/src/noodlelab/examples/data/20-groundwater-nitrate/wells.csv +161 -0
  184. noodlelab-0.1.0/src/noodlelab/examples/data/21-fixed-beam/README.md +18 -0
  185. noodlelab-0.1.0/src/noodlelab/examples/data/21-fixed-beam/strain_gauges.csv +12 -0
  186. noodlelab-0.1.0/src/noodlelab/examples/data/22-watershed/README.md +20 -0
  187. noodlelab-0.1.0/src/noodlelab/examples/data/22-watershed/catchments.geojson +1 -0
  188. noodlelab-0.1.0/src/noodlelab/examples/data/22-watershed/dem.asc +156 -0
  189. noodlelab-0.1.0/src/noodlelab/examples/data/22-watershed/dem.prj +1 -0
  190. noodlelab-0.1.0/src/noodlelab/examples/data/22-watershed/nir.asc +156 -0
  191. noodlelab-0.1.0/src/noodlelab/examples/data/22-watershed/nir.prj +1 -0
  192. noodlelab-0.1.0/src/noodlelab/examples/data/22-watershed/rain_gauges.csv +13 -0
  193. noodlelab-0.1.0/src/noodlelab/examples/data/22-watershed/red.asc +156 -0
  194. noodlelab-0.1.0/src/noodlelab/examples/data/22-watershed/red.prj +1 -0
  195. noodlelab-0.1.0/src/noodlelab/examples/data/23-satellite-link/README.md +19 -0
  196. noodlelab-0.1.0/src/noodlelab/examples/data/23-satellite-link/radios.csv +9 -0
  197. noodlelab-0.1.0/src/noodlelab/nodes/__init__.py +1 -0
  198. noodlelab-0.1.0/src/noodlelab/nodes/checks.py +197 -0
  199. noodlelab-0.1.0/src/noodlelab/nodes/core.py +500 -0
  200. noodlelab-0.1.0/src/noodlelab/nodes/engineering/__init__.py +33 -0
  201. noodlelab-0.1.0/src/noodlelab/nodes/engineering/controls.py +181 -0
  202. noodlelab-0.1.0/src/noodlelab/nodes/engineering/decibels.py +85 -0
  203. noodlelab-0.1.0/src/noodlelab/nodes/engineering/fluids.py +83 -0
  204. noodlelab-0.1.0/src/noodlelab/nodes/engineering/materials.py +126 -0
  205. noodlelab-0.1.0/src/noodlelab/nodes/engineering/sections.py +120 -0
  206. noodlelab-0.1.0/src/noodlelab/nodes/engineering/structures.py +217 -0
  207. noodlelab-0.1.0/src/noodlelab/nodes/engineering/thermal.py +80 -0
  208. noodlelab-0.1.0/src/noodlelab/nodes/engineering/trade.py +88 -0
  209. noodlelab-0.1.0/src/noodlelab/nodes/geo/__init__.py +31 -0
  210. noodlelab-0.1.0/src/noodlelab/nodes/geo/plot.py +353 -0
  211. noodlelab-0.1.0/src/noodlelab/nodes/geo/raster.py +837 -0
  212. noodlelab-0.1.0/src/noodlelab/nodes/geo/seismology.py +128 -0
  213. noodlelab-0.1.0/src/noodlelab/nodes/geo/track.py +151 -0
  214. noodlelab-0.1.0/src/noodlelab/nodes/geo/types.py +378 -0
  215. noodlelab-0.1.0/src/noodlelab/nodes/geo/vector.py +440 -0
  216. noodlelab-0.1.0/src/noodlelab/nodes/maths/__init__.py +26 -0
  217. noodlelab-0.1.0/src/noodlelab/nodes/maths/_common.py +72 -0
  218. noodlelab-0.1.0/src/noodlelab/nodes/maths/arrays.py +100 -0
  219. noodlelab-0.1.0/src/noodlelab/nodes/maths/calculus.py +60 -0
  220. noodlelab-0.1.0/src/noodlelab/nodes/maths/fitting.py +484 -0
  221. noodlelab-0.1.0/src/noodlelab/nodes/maths/plot.py +169 -0
  222. noodlelab-0.1.0/src/noodlelab/nodes/report.py +531 -0
  223. noodlelab-0.1.0/src/noodlelab/nodes/requirements.py +171 -0
  224. noodlelab-0.1.0/src/noodlelab/nodes/science/__init__.py +27 -0
  225. noodlelab-0.1.0/src/noodlelab/nodes/science/plot.py +218 -0
  226. noodlelab-0.1.0/src/noodlelab/nodes/science/signal.py +247 -0
  227. noodlelab-0.1.0/src/noodlelab/nodes/science/stats.py +495 -0
  228. noodlelab-0.1.0/src/noodlelab/nodes/science/studies.py +267 -0
  229. noodlelab-0.1.0/src/noodlelab/nodes/science/tables.py +619 -0
  230. noodlelab-0.1.0/src/noodlelab/nodes/science/timeseries.py +164 -0
  231. noodlelab-0.1.0/src/noodlelab/nodes/symbolic/__init__.py +23 -0
  232. noodlelab-0.1.0/src/noodlelab/nodes/symbolic/nodes.py +634 -0
  233. noodlelab-0.1.0/src/noodlelab/nodes/symbolic/parse.py +264 -0
  234. noodlelab-0.1.0/src/noodlelab/nodes/symbolic/types.py +200 -0
  235. noodlelab-0.1.0/src/noodlelab/nodes/symbolic/typst.py +280 -0
  236. noodlelab-0.1.0/src/noodlelab/nodes/uncertainty.py +275 -0
  237. noodlelab-0.1.0/src/noodlelab/nodes/units.py +138 -0
  238. noodlelab-0.1.0/src/noodlelab/py.typed +0 -0
  239. noodlelab-0.1.0/src/noodlelab/reports/__init__.py +6 -0
  240. noodlelab-0.1.0/src/noodlelab/reports/document.py +274 -0
  241. noodlelab-0.1.0/src/noodlelab/reports/math.py +81 -0
  242. noodlelab-0.1.0/src/noodlelab/reports/render.py +46 -0
  243. noodlelab-0.1.0/src/noodlelab/reports/templates/default.typ +160 -0
  244. noodlelab-0.1.0/src/noodlelab/server/__init__.py +16 -0
  245. noodlelab-0.1.0/src/noodlelab/server/admin.py +410 -0
  246. noodlelab-0.1.0/src/noodlelab/server/agents.py +368 -0
  247. noodlelab-0.1.0/src/noodlelab/server/api.py +879 -0
  248. noodlelab-0.1.0/src/noodlelab/server/app.py +83 -0
  249. noodlelab-0.1.0/src/noodlelab/server/auth.py +87 -0
  250. noodlelab-0.1.0/src/noodlelab/server/cluster.py +406 -0
  251. noodlelab-0.1.0/src/noodlelab/server/hub.py +117 -0
  252. noodlelab-0.1.0/src/noodlelab/server/permissions.py +72 -0
  253. noodlelab-0.1.0/src/noodlelab/server/runs.py +306 -0
  254. noodlelab-0.1.0/src/noodlelab/server/sharing.py +117 -0
  255. noodlelab-0.1.0/src/noodlelab/server/state.py +94 -0
  256. noodlelab-0.1.0/src/noodlelab/server/store.py +328 -0
  257. noodlelab-0.1.0/src/noodlelab/server/workspaces.py +207 -0
  258. noodlelab-0.1.0/src/noodlelab/server/ws.py +643 -0
  259. noodlelab-0.1.0/src/noodlelab/static/admin.html +31 -0
  260. noodlelab-0.1.0/src/noodlelab/static/assets/admin-CdIecA1j.js +8 -0
  261. noodlelab-0.1.0/src/noodlelab/static/assets/agent-6GBZ9nXN.css +32 -0
  262. noodlelab-0.1.0/src/noodlelab/static/assets/agent-BSxoem_1.js +41 -0
  263. noodlelab-0.1.0/src/noodlelab/static/assets/editor-DWWQ0JL2.js +25 -0
  264. noodlelab-0.1.0/src/noodlelab/static/assets/sharing-Bnota0Ch.js +1 -0
  265. noodlelab-0.1.0/src/noodlelab/static/assets/sharing-DgCDYlte.css +1 -0
  266. noodlelab-0.1.0/src/noodlelab/static/index.html +108 -0
  267. noodlelab-0.1.0/src/noodlelab/static/vendor/litegraph/litegraph.css +680 -0
  268. noodlelab-0.1.0/src/noodlelab/static/vendor/litegraph/litegraph.js +14427 -0
  269. noodlelab-0.1.0/src/noodlelab/tiers.py +201 -0
  270. noodlelab-0.1.0/src/noodlelab/verify.py +917 -0
  271. noodlelab-0.1.0/tests/__init__.py +0 -0
  272. noodlelab-0.1.0/tests/conftest.py +50 -0
  273. noodlelab-0.1.0/tests/sample_nodes.py +432 -0
  274. noodlelab-0.1.0/tests/test_about.py +153 -0
  275. noodlelab-0.1.0/tests/test_agent.py +231 -0
  276. noodlelab-0.1.0/tests/test_agent_terminal.py +143 -0
  277. noodlelab-0.1.0/tests/test_api.py +493 -0
  278. noodlelab-0.1.0/tests/test_checkpoint.py +80 -0
  279. noodlelab-0.1.0/tests/test_checks.py +179 -0
  280. noodlelab-0.1.0/tests/test_cli.py +18 -0
  281. noodlelab-0.1.0/tests/test_codecs.py +246 -0
  282. noodlelab-0.1.0/tests/test_convert.py +168 -0
  283. noodlelab-0.1.0/tests/test_engineering.py +182 -0
  284. noodlelab-0.1.0/tests/test_examples.py +152 -0
  285. noodlelab-0.1.0/tests/test_executor.py +169 -0
  286. noodlelab-0.1.0/tests/test_export.py +25 -0
  287. noodlelab-0.1.0/tests/test_figures.py +131 -0
  288. noodlelab-0.1.0/tests/test_files.py +147 -0
  289. noodlelab-0.1.0/tests/test_fold.py +179 -0
  290. noodlelab-0.1.0/tests/test_generics.py +199 -0
  291. noodlelab-0.1.0/tests/test_geo_nodes.py +523 -0
  292. noodlelab-0.1.0/tests/test_health.py +52 -0
  293. noodlelab-0.1.0/tests/test_llms.py +29 -0
  294. noodlelab-0.1.0/tests/test_monitor.py +30 -0
  295. noodlelab-0.1.0/tests/test_montecarlo.py +469 -0
  296. noodlelab-0.1.0/tests/test_node_spec.py +72 -0
  297. noodlelab-0.1.0/tests/test_node_views.py +36 -0
  298. noodlelab-0.1.0/tests/test_permissions.py +223 -0
  299. noodlelab-0.1.0/tests/test_ports.py +184 -0
  300. noodlelab-0.1.0/tests/test_probe.py +134 -0
  301. noodlelab-0.1.0/tests/test_provenance.py +129 -0
  302. noodlelab-0.1.0/tests/test_report_tab.py +76 -0
  303. noodlelab-0.1.0/tests/test_reports.py +277 -0
  304. noodlelab-0.1.0/tests/test_reproducibility.py +29 -0
  305. noodlelab-0.1.0/tests/test_reqlog.py +94 -0
  306. noodlelab-0.1.0/tests/test_requirements.py +219 -0
  307. noodlelab-0.1.0/tests/test_rundiff.py +65 -0
  308. noodlelab-0.1.0/tests/test_sample.py +110 -0
  309. noodlelab-0.1.0/tests/test_science_nodes.py +340 -0
  310. noodlelab-0.1.0/tests/test_slurm.py +274 -0
  311. noodlelab-0.1.0/tests/test_studies.py +247 -0
  312. noodlelab-0.1.0/tests/test_subgraphs.py +160 -0
  313. noodlelab-0.1.0/tests/test_symbolic.py +294 -0
  314. noodlelab-0.1.0/tests/test_tiers.py +76 -0
  315. noodlelab-0.1.0/tests/test_tracked.py +135 -0
  316. noodlelab-0.1.0/tests/test_typesys.py +80 -0
  317. noodlelab-0.1.0/tests/test_uncertainty.py +221 -0
  318. noodlelab-0.1.0/tests/test_units.py +288 -0
  319. noodlelab-0.1.0/tests/test_verify.py +193 -0
  320. noodlelab-0.1.0/tests/test_workspaces.py +83 -0
  321. noodlelab-0.1.0/tests/test_zones.py +372 -0
  322. noodlelab-0.1.0/tests/unit_nodes.py +103 -0
  323. noodlelab-0.1.0/uv.lock +4300 -0
@@ -0,0 +1,138 @@
1
+ ---
2
+ name: noodlelab
3
+ description: Verifiable scientific and engineering calculations with noodlelab - units and
4
+ uncertainty on every number, requirements checked with margins, checks, and a provenance
5
+ record of every run. Use whenever code computes physical quantities, measurement results,
6
+ design margins or other results people rely on, or when asked to build or change a
7
+ noodlelab graph, report or requirement.
8
+ ---
9
+
10
+ # noodlelab for AI agents
11
+
12
+ noodlelab makes scientific and engineering calculations verifiable. Use it
13
+ whenever code computes a physical quantity, a measurement result, a design
14
+ margin or anything a person will rely on. The point is that a reviewer (or
15
+ another agent) can check what you did, not just trust it.
16
+
17
+ ## The rules
18
+
19
+ 1. **Units on every number.** Use quantities (`q("9.81 m/s^2")`), never bare
20
+ floats with the unit in a variable name or a comment. Mixing dimensions then
21
+ raises instead of silently giving nonsense. Dimensionless results say so
22
+ (`unit="1"`).
23
+ 2. **Uncertainty on every measured input, and a source.** Write
24
+ `"9.81 ± 0.02 m/s^2"` and say where it came from (an instrument, datasheet,
25
+ paper or dataset). Uncertainty propagates by itself (GUM, first order, with
26
+ correlations kept). `budget()` shows which input dominates. For strongly
27
+ non-linear models, check with `monte_carlo()`.
28
+ 3. **Write the requirements down before checking them**, one per line:
29
+ `COM-001 link_margin >= 3 dB [Analysis] # The link shall close with 3 dB to spare`.
30
+ Verify each one: a check reports its margin, not just pass or fail.
31
+ 4. **Leave a record.** Wrap the calculation in `with nv.record(...)`, or build
32
+ it as a graph. Every run then writes a `provenance.json` holding inputs,
33
+ results, checks, the code (hash, git commit), files (SHA-256) and the
34
+ environment.
35
+ 5. **Verify before you say you are done.** Run `noodlelab verify <script.py |
36
+ graph.json> --json` and fix what it reports. Exit code 0 means every check
37
+ passed and every requirement was verified. Report the margins and any
38
+ warnings to the user; don't hide failures.
39
+
40
+ Never invent a measured value or its uncertainty. When a number is assumed,
41
+ say so with `rec.note(...)` and `source="assumed: ..."`.
42
+
43
+ ## Python: `noodlelab.verify`
44
+
45
+ ```python
46
+ import noodlelab.verify as nv
47
+
48
+ with nv.record("link budget") as rec: # writes runs/<time>-link-budget-<id>/provenance.json
49
+ p_tx = rec.input("p_tx", "10.0 ± 0.3 W", source="PA datasheet rev C")
50
+ d = rec.input("d", nv.q("1200 km"), source="orbit design")
51
+ ...
52
+ margin = rec.result("link_margin", computed_margin) # a quantity, e.g. in dB
53
+ rec.require("COM-001 link_margin >= 3 dB [Analysis] # The link shall close")
54
+ rec.verify("COM-001", margin) # Check(passed, margin=+1.2 dB, ...)
55
+ rec.expect(0 < efficiency.m < 1, "efficiency is a fraction")
56
+ rec.close_to("vs. textbook", result, nv.q("2.006 s"), rtol=0.01)
57
+ print(rec.summary())
58
+ ```
59
+
60
+ - `nv.q(text_or_number, unit)` returns an exact quantity. `nv.measure("x ± u unit")`
61
+ or `nv.measure(x, u, unit, name=...)` returns an uncertain one.
62
+ - `nv.requirements(text)` and `nv.verify(req, value)` work outside a record too.
63
+ - `@nv.traced` records every call of a function (arguments, result, source hash)
64
+ inside a record.
65
+ - `nv.monte_carlo(model, trials)`, where `model` makes its inputs with
66
+ `measure()` and returns the result, checks whether the GUM result can be
67
+ trusted (JCGM 101).
68
+ - `nv.audit(record)` lists findings NL001–NL010: failed checks, unverified
69
+ requirements, results without units or uncertainty, inputs without a source,
70
+ and code that was dirty or has changed since.
71
+
72
+ ## Graphs: node pipelines the user can open in the editor
73
+
74
+ A graph is a JSON file `<name>.graph.json` in the workspace. Nodes are typed
75
+ Python functions (`list_nodes`, `describe_node`). Every input is either a
76
+ value or a link to another node's output:
77
+
78
+ ```json
79
+ {"nodes": [
80
+ {"id": "1", "type": "core.number", "inputs": {"value": {"value": 3}}},
81
+ {"id": "2", "type": "core.math",
82
+ "inputs": {"operation": {"value": "power"},
83
+ "a": {"link": {"node": "1", "output": "result"}},
84
+ "b": {"value": 2}}}
85
+ ],
86
+ "report": [
87
+ {"id": "1", "type": "report.new_report", "inputs": {"title": {"value": "Results"}}},
88
+ {"id": "2", "type": "report.add_value",
89
+ "inputs": {"report": {"link": {"node": "1", "output": "result"}},
90
+ "label": {"value": "3 squared"},
91
+ "value": {"link": {"node": "2", "output": "result", "tab": "processing"}}}},
92
+ {"id": "3", "type": "report.render_report",
93
+ "inputs": {"report": {"link": {"node": "2", "output": "result"}}}}
94
+ ]}
95
+ ```
96
+
97
+ - `nodes` is the processing canvas and `report` is the Reporting canvas. A
98
+ report node reads a processing output with `"tab": "processing"` in its
99
+ link. Data only flows from processing into the report.
100
+ - The usual shape for a verified analysis:
101
+ - **Requirements** (`requirements.requirements`, one per line), then the
102
+ inputs, with units and uncertainty (`units.*`, `uncertainty.*`), then the
103
+ model.
104
+ - **Verify Requirement** for each requirement, chaining its `log` output
105
+ from one to the next.
106
+ - A report: **New Report**, Add Heading / Text / Value / Figure /
107
+ Requirements, then Add Compliance Matrix (from the last `log`), Run
108
+ Details (provenance), and Render Report (the PDF).
109
+ - Start from a similar example (`list_examples`, `get_example`). Example 23
110
+ (satellite link budget) and example 24 (cantilever bracket) are complete
111
+ verified studies with reports.
112
+
113
+ ## MCP tools (server `noodlelab mcp`)
114
+
115
+ | Tool | Use |
116
+ |---|---|
117
+ | `guide` | this text |
118
+ | `list_nodes`, `describe_node` | find nodes and their inputs, outputs, units and options |
119
+ | `list_examples`, `get_example` | complete graphs to copy from |
120
+ | `list_graphs`, `get_graph` | the workspace's graphs |
121
+ | `save_graph` | write a whole graph (validated; the open editor reloads it) |
122
+ | `edit_graph` | add, set, remove, retitle or track nodes without resending the graph |
123
+ | `check_graph` | problems before running: types, units, missing inputs |
124
+ | `run_graph` | run it; returns each node's result, checks, requirements, files and provenance |
125
+ | `requirements` | each requirement's latest verdict, margin and history |
126
+ | `verify` | audit a script, graph or records, as `noodlelab verify --json` does |
127
+
128
+ When you are started from the editor's Agent panel, the user is watching the
129
+ canvas: every graph you save opens there, laid out automatically.
130
+ `NOODLELAB_GRAPH` names the graph they had open.
131
+
132
+ The workflow:
133
+ 1. `guide`, then `list_examples` / `list_nodes`.
134
+ 2. `save_graph`, then `check_graph` until there are no errors.
135
+ 3. `run_graph`, then read the checks and margins.
136
+ 4. Fix and iterate. Finish with `verify`.
137
+ 5. Tell the user what passed, the margins, what was assumed, and where the
138
+ report PDF and provenance are.
@@ -0,0 +1,21 @@
1
+ // "Open in GitHub Codespaces": a private, throwaway copy of noodlelab for each visitor.
2
+ // A codespace is billed to the account that creates it (GitHub includes free monthly
3
+ // hours), not to this repository's owner.
4
+ {
5
+ "name": "noodlelab",
6
+ "image": "mcr.microsoft.com/devcontainers/python:3.12",
7
+ "features": {
8
+ "ghcr.io/devcontainers/features/node:1": { "version": "22" }
9
+ },
10
+ "postCreateCommand": "pip install --user uv && uv sync --extra full && cd frontend && npm ci && npm run build",
11
+ // A workspace outside the repository, so trying things out leaves git clean.
12
+ "postAttachCommand": "mkdir -p ~/noodlelab-workspace && uv run noodlelab serve ~/noodlelab-workspace --no-browser",
13
+ "forwardPorts": [8000],
14
+ "portsAttributes": {
15
+ "8000": { "label": "noodlelab", "onAutoForward": "openBrowser" }
16
+ },
17
+ "customizations": {
18
+ "vscode": { "extensions": ["ms-python.python", "charliermarsh.ruff"] },
19
+ "codespaces": { "openFiles": ["README.md"] }
20
+ }
21
+ }
@@ -0,0 +1,15 @@
1
+ .git
2
+ .github
3
+ .venv
4
+ **/__pycache__
5
+ **/*.pyc
6
+ .pytest_cache
7
+ .ruff_cache
8
+ noodlelab-data
9
+ noodlelab-output
10
+ dist
11
+ frontend/node_modules
12
+ frontend/public/vendor
13
+ src/noodlelab/static
14
+ examples
15
+ tests
@@ -0,0 +1,89 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ # the release workflow runs these checks before it builds anything
8
+ workflow_call:
9
+
10
+ permissions:
11
+ contents: read
12
+
13
+ jobs:
14
+ python:
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ matrix:
18
+ python-version: ["3.11", "3.12", "3.13"]
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ with:
22
+ fetch-depth: 0 # the version comes from the git tags
23
+ - uses: astral-sh/setup-uv@v6
24
+ with:
25
+ python-version: ${{ matrix.python-version }}
26
+ enable-cache: true
27
+ - run: uv sync --locked --all-extras
28
+ - run: uv run ruff check .
29
+ - run: uv run ruff format --check .
30
+ - run: uv run pytest -q
31
+
32
+ frontend:
33
+ runs-on: ubuntu-latest
34
+ defaults:
35
+ run:
36
+ working-directory: frontend
37
+ steps:
38
+ - uses: actions/checkout@v4
39
+ - uses: actions/setup-node@v4
40
+ with:
41
+ node-version: 22
42
+ cache: npm
43
+ cache-dependency-path: frontend/package-lock.json
44
+ - run: npm ci
45
+ - run: npm run typecheck
46
+ - run: npm run build
47
+
48
+ # Each installation tier on its own: exactly its packs load (a bigger tier's
49
+ # dependency leaking into a smaller extra fails here), and every example it
50
+ # covers runs.
51
+ tiers:
52
+ runs-on: ubuntu-latest
53
+ strategy:
54
+ fail-fast: false
55
+ matrix:
56
+ tier: [library, basic, maths, science, engineering, geo, full]
57
+ steps:
58
+ - uses: actions/checkout@v4
59
+ with:
60
+ fetch-depth: 0
61
+ - uses: astral-sh/setup-uv@v6
62
+ with:
63
+ python-version: "3.12"
64
+ enable-cache: true
65
+ - name: Install the ${{ matrix.tier }} tier
66
+ run: |
67
+ uv venv .tier
68
+ case "${{ matrix.tier }}" in
69
+ library) spec="." ;;
70
+ basic) spec=".[app]" ;;
71
+ *) spec=".[${{ matrix.tier }}]" ;;
72
+ esac
73
+ VIRTUAL_ENV=.tier uv pip install "$spec"
74
+ - name: Check its packs and run its examples
75
+ run: .tier/bin/python scripts/check_tier.py ${{ matrix.tier }} --examples
76
+
77
+ docker:
78
+ runs-on: ubuntu-latest
79
+ needs: [python, frontend]
80
+ steps:
81
+ - uses: actions/checkout@v4
82
+ - uses: docker/setup-buildx-action@v3
83
+ - uses: docker/build-push-action@v6
84
+ with:
85
+ context: .
86
+ push: false
87
+ tags: noodlelab:ci
88
+ cache-from: type=gha,scope=full
89
+ cache-to: type=gha,mode=max,scope=full
@@ -0,0 +1,287 @@
1
+ name: Release
2
+
3
+ # Push a version tag to release it:
4
+ #
5
+ # git tag v0.3.0 && git push origin v0.3.0 # PyPI, GitHub Release, Docker images
6
+ # git tag v0.3.0rc1 && git push origin v0.3.0rc1 # a pre-release: TestPyPI instead of PyPI
7
+ #
8
+ # checks -> build -> smoke -> GitHub Release -> PyPI / TestPyPI + Docker (GHCR) -> verify
9
+ #
10
+ # One-time setup (see docs/releasing.md): trusted publishers on PyPI and TestPyPI
11
+ # for this workflow, with the environments "pypi" and "testpypi".
12
+ on:
13
+ push:
14
+ tags: ["v*"]
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ concurrency:
20
+ group: release-${{ github.ref }}
21
+ cancel-in-progress: false
22
+
23
+ env:
24
+ TAG: ${{ github.ref_name }}
25
+
26
+ jobs:
27
+ checks:
28
+ uses: ./.github/workflows/ci.yml
29
+
30
+ build:
31
+ needs: checks
32
+ runs-on: ubuntu-latest
33
+ outputs:
34
+ version: ${{ steps.version.outputs.version }}
35
+ prerelease: ${{ steps.version.outputs.prerelease }}
36
+ steps:
37
+ - uses: actions/checkout@v4
38
+ with:
39
+ fetch-depth: 0 # hatch-vcs reads the version from the tag
40
+ - name: Version from the tag
41
+ id: version
42
+ run: |
43
+ version="${TAG#v}"
44
+ if ! [[ "$version" =~ ^[0-9]+\.[0-9]+\.[0-9]+((a|b|rc)[0-9]+)?$ ]]; then
45
+ echo "::error::Tag $TAG is not vX.Y.Z or vX.Y.Z(a|b|rc)N"
46
+ exit 1
47
+ fi
48
+ echo "version=$version" >> "$GITHUB_OUTPUT"
49
+ if [[ "$version" =~ (a|b|rc) ]]; then pre=true; else pre=false; fi
50
+ echo "prerelease=$pre" >> "$GITHUB_OUTPUT"
51
+ echo "Releasing $version (pre-release: $pre)"
52
+ - uses: actions/setup-node@v4
53
+ with:
54
+ node-version: 22
55
+ cache: npm
56
+ cache-dependency-path: frontend/package-lock.json
57
+ - name: Build the editor into the package
58
+ working-directory: frontend
59
+ run: npm ci && npm run build
60
+ - uses: astral-sh/setup-uv@v6
61
+ - run: uv build
62
+ - name: Check the distributions
63
+ env:
64
+ VERSION: ${{ steps.version.outputs.version }}
65
+ run: |
66
+ ls -l dist
67
+ wheel="dist/noodlelab-${VERSION}-py3-none-any.whl"
68
+ test -f "$wheel" || { echo "::error::No $wheel: the built version is not the tag's"; exit 1; }
69
+ test -f "dist/noodlelab-${VERSION}.tar.gz"
70
+ for page in index.html admin.html; do
71
+ unzip -l "$wheel" | grep -q "noodlelab/static/$page" \
72
+ || { echo "::error::The wheel has no editor ($page)"; exit 1; }
73
+ done
74
+ uvx twine check --strict dist/*
75
+ - uses: actions/upload-artifact@v4
76
+ with:
77
+ name: dist
78
+ path: dist/
79
+ if-no-files-found: error
80
+
81
+ # The wheel installs and serves on every OS, before anything is published.
82
+ smoke:
83
+ needs: build
84
+ strategy:
85
+ fail-fast: false
86
+ matrix:
87
+ os: [ubuntu-latest, macos-latest, windows-latest]
88
+ tier: [basic]
89
+ include:
90
+ - os: ubuntu-latest
91
+ tier: full
92
+ runs-on: ${{ matrix.os }}
93
+ defaults:
94
+ run:
95
+ shell: bash
96
+ env:
97
+ VERSION: ${{ needs.build.outputs.version }}
98
+ TIER: ${{ matrix.tier }}
99
+ steps:
100
+ - uses: actions/download-artifact@v4
101
+ with:
102
+ name: dist
103
+ path: dist
104
+ - uses: astral-sh/setup-uv@v6
105
+ with:
106
+ python-version: "3.12"
107
+ - name: Install the wheel as a uv tool
108
+ run: |
109
+ if [ "$TIER" = basic ]; then spec="noodlelab[app]==$VERSION"; else spec="noodlelab[$TIER]==$VERSION"; fi
110
+ uv tool install "$spec" --find-links dist
111
+ uv tool dir --bin >> "$GITHUB_PATH"
112
+ - name: Version and tier
113
+ run: |
114
+ noodlelab --version | tee version.txt
115
+ grep -qF "$VERSION" version.txt
116
+ noodlelab tiers --json > tiers.json
117
+ uv run --no-project python -c "import json; r = json.load(open('tiers.json')); assert r['installed'] == '$TIER', r"
118
+ - name: The server starts
119
+ run: |
120
+ mkdir ws
121
+ noodlelab serve ws --no-browser --port 8123 > serve.log 2>&1 &
122
+ for _ in $(seq 1 60); do
123
+ if curl -fsS http://127.0.0.1:8123/api/health; then exit 0; fi
124
+ sleep 1
125
+ done
126
+ cat serve.log
127
+ exit 1
128
+
129
+ github-release:
130
+ needs: [build, smoke]
131
+ runs-on: ubuntu-latest
132
+ permissions:
133
+ contents: write
134
+ env:
135
+ VERSION: ${{ needs.build.outputs.version }}
136
+ PRERELEASE: ${{ needs.build.outputs.prerelease }}
137
+ GH_TOKEN: ${{ github.token }}
138
+ GH_REPO: ${{ github.repository }}
139
+ steps:
140
+ - uses: actions/download-artifact@v4
141
+ with:
142
+ name: dist
143
+ path: dist
144
+ - name: Create the release (or update its files when re-run)
145
+ run: |
146
+ if gh release view "$TAG" > /dev/null 2>&1; then
147
+ gh release upload "$TAG" dist/* --clobber
148
+ else
149
+ flags=()
150
+ if [ "$PRERELEASE" = true ]; then flags+=(--prerelease); fi
151
+ gh release create "$TAG" dist/* --verify-tag --generate-notes \
152
+ --title "noodlelab $VERSION" "${flags[@]}"
153
+ fi
154
+
155
+ publish-pypi:
156
+ needs: [build, github-release]
157
+ if: needs.build.outputs.prerelease == 'false'
158
+ runs-on: ubuntu-latest
159
+ environment:
160
+ name: pypi
161
+ url: https://pypi.org/project/noodlelab/${{ needs.build.outputs.version }}/
162
+ permissions:
163
+ id-token: write # trusted publishing: no API token
164
+ steps:
165
+ - uses: actions/download-artifact@v4
166
+ with:
167
+ name: dist
168
+ path: dist
169
+ - uses: astral-sh/setup-uv@v6
170
+ - run: uv publish --trusted-publishing always --check-url https://pypi.org/simple/
171
+
172
+ publish-testpypi:
173
+ needs: [build, github-release]
174
+ if: needs.build.outputs.prerelease == 'true'
175
+ runs-on: ubuntu-latest
176
+ environment:
177
+ name: testpypi
178
+ url: https://test.pypi.org/project/noodlelab/${{ needs.build.outputs.version }}/
179
+ permissions:
180
+ id-token: write
181
+ steps:
182
+ - uses: actions/download-artifact@v4
183
+ with:
184
+ name: dist
185
+ path: dist
186
+ - uses: astral-sh/setup-uv@v6
187
+ - run: >-
188
+ uv publish --trusted-publishing always
189
+ --publish-url https://test.pypi.org/legacy/
190
+ --check-url https://test.pypi.org/simple/
191
+
192
+ # One image per tier, for amd64 and arm64:
193
+ # ghcr.io/<owner>/noodlelab:0.3.0 the full tier (also :0.3.0-full)
194
+ # ghcr.io/<owner>/noodlelab:0.3.0-maths a smaller tier
195
+ # A final release also moves :0.3(-<tier>), :<tier> and :latest (the full tier).
196
+ docker:
197
+ needs: [build, github-release]
198
+ runs-on: ubuntu-latest
199
+ permissions:
200
+ contents: read
201
+ packages: write
202
+ strategy:
203
+ fail-fast: false
204
+ matrix:
205
+ tier: [basic, maths, science, engineering, geo, full]
206
+ env:
207
+ VERSION: ${{ needs.build.outputs.version }}
208
+ PRERELEASE: ${{ needs.build.outputs.prerelease }}
209
+ TIER: ${{ matrix.tier }}
210
+ steps:
211
+ - uses: actions/checkout@v4
212
+ - name: Image tags
213
+ id: tags
214
+ run: |
215
+ image="ghcr.io/${GITHUB_REPOSITORY,,}"
216
+ minor="$(echo "$VERSION" | cut -d. -f1,2)"
217
+ tags="$image:$VERSION-$TIER"
218
+ if [ "$TIER" = full ]; then tags="$tags,$image:$VERSION"; fi
219
+ if [ "$PRERELEASE" = false ]; then
220
+ tags="$tags,$image:$minor-$TIER,$image:$TIER"
221
+ if [ "$TIER" = full ]; then tags="$tags,$image:$minor,$image:latest"; fi
222
+ fi
223
+ echo "tags=$tags" >> "$GITHUB_OUTPUT"
224
+ echo "$tags" | tr , '\n'
225
+ - uses: docker/setup-qemu-action@v3
226
+ - uses: docker/setup-buildx-action@v3
227
+ - uses: docker/login-action@v3
228
+ with:
229
+ registry: ghcr.io
230
+ username: ${{ github.actor }}
231
+ password: ${{ secrets.GITHUB_TOKEN }}
232
+ - uses: docker/build-push-action@v6
233
+ with:
234
+ context: .
235
+ platforms: linux/amd64,linux/arm64
236
+ push: true
237
+ tags: ${{ steps.tags.outputs.tags }}
238
+ build-args: |
239
+ NOODLELAB_TIER=${{ matrix.tier }}
240
+ NOODLELAB_VERSION=${{ needs.build.outputs.version }}
241
+ labels: |
242
+ org.opencontainers.image.title=noodlelab (${{ matrix.tier }})
243
+ org.opencontainers.image.version=${{ needs.build.outputs.version }}
244
+ org.opencontainers.image.source=${{ github.server_url }}/${{ github.repository }}
245
+ org.opencontainers.image.revision=${{ github.sha }}
246
+ cache-from: type=gha,scope=${{ matrix.tier }}
247
+ cache-to: type=gha,mode=max,scope=${{ matrix.tier }}
248
+
249
+ # What was published installs: from the package index with uv, and from GHCR.
250
+ verify:
251
+ needs: [build, publish-pypi, publish-testpypi, docker]
252
+ if: >-
253
+ always() && needs.docker.result == 'success' &&
254
+ (needs.publish-pypi.result == 'success' || needs.publish-testpypi.result == 'success')
255
+ runs-on: ubuntu-latest
256
+ permissions:
257
+ contents: read
258
+ packages: read
259
+ env:
260
+ VERSION: ${{ needs.build.outputs.version }}
261
+ PRERELEASE: ${{ needs.build.outputs.prerelease }}
262
+ steps:
263
+ - uses: astral-sh/setup-uv@v6
264
+ - name: uvx runs the published package
265
+ run: |
266
+ index=()
267
+ if [ "$PRERELEASE" = true ]; then
268
+ index=(--index https://test.pypi.org/simple/ --index-strategy unsafe-best-match)
269
+ fi
270
+ # the index can take a few minutes to list a new release
271
+ for _ in $(seq 1 20); do
272
+ if uvx --refresh "${index[@]}" --from "noodlelab[maths]==$VERSION" noodlelab tiers; then
273
+ exit 0
274
+ fi
275
+ sleep 15
276
+ done
277
+ exit 1
278
+ - uses: docker/login-action@v3
279
+ with:
280
+ registry: ghcr.io
281
+ username: ${{ github.actor }}
282
+ password: ${{ secrets.GITHUB_TOKEN }}
283
+ - name: The images run
284
+ run: |
285
+ image="ghcr.io/${GITHUB_REPOSITORY,,}"
286
+ docker run --rm "$image:$VERSION-basic" noodlelab --version | grep -F "$VERSION"
287
+ docker run --rm "$image:$VERSION" noodlelab tiers
@@ -0,0 +1,21 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ .venv/
5
+ dist/
6
+ build/
7
+ *.egg-info/
8
+ .pytest_cache/
9
+ .ruff_cache/
10
+
11
+ # Frontend
12
+ frontend/node_modules/
13
+ frontend/public/vendor/
14
+ src/noodlelab/static/*
15
+ !src/noodlelab/static/.gitkeep
16
+
17
+ # Runtime data (workspace state; run outputs are yours to keep or ignore)
18
+ .noodlelab/
19
+ noodlelab-data/
20
+ noodlelab-output/
21
+ .env
@@ -0,0 +1,83 @@
1
+ # AGENTS.md
2
+
3
+ Notes for AI coding agents (Claude Code, Codex CLI, Cursor, Copilot and
4
+ others). There are two cases: **using** noodlelab in someone's project, and
5
+ **working on** this repository.
6
+
7
+ ## Using noodlelab in a project
8
+
9
+ noodlelab makes calculations verifiable. Every number carries its unit and
10
+ uncertainty, requirements are checked with margins, and every run leaves a
11
+ provenance record. When a user asks you to "use noodlelab", "make this
12
+ verifiable", or to build a noodlelab graph or report:
13
+
14
+ 1. `pip install noodlelab`, or `uv add noodlelab` (the library: `noodlelab.verify`).
15
+ Add `[app]` for the browser editor, and `[full]` for every node pack.
16
+ 2. Run `noodlelab init-agent` in the project. It writes an `AGENTS.md` section,
17
+ a `CLAUDE.md` import, a skill (`.claude/skills/noodlelab/SKILL.md`) and the
18
+ MCP server in `.mcp.json`. For Codex, run
19
+ `codex mcp add noodlelab -- noodlelab mcp`.
20
+ 3. Read [`src/noodlelab/agent/guide.md`](src/noodlelab/agent/guide.md) (the MCP
21
+ `guide` tool returns the same text). It has the rules, the Python API, the
22
+ graph format and the workflow.
23
+ 4. Before saying you are done, run `noodlelab verify <script.py | graph.json> --json`.
24
+ Exit code 0 means every check passed and every requirement was verified.
25
+
26
+ Recipes:
27
+ - [docs/agents/quickstart.md](docs/agents/quickstart.md): a verified calculation in 10 lines
28
+ - [docs/agents/adopt-in-existing-project.md](docs/agents/adopt-in-existing-project.md):
29
+ the steps for "extend my project to use noodlelab"
30
+ - [docs/agents/graphs-and-reports.md](docs/agents/graphs-and-reports.md): building
31
+ graphs and PDF reports over MCP or as JSON
32
+ - [llms.txt](llms.txt): an index of everything, and
33
+ [docs/llms-full.txt](docs/llms-full.txt), all the API docs and every node in one file
34
+
35
+ ## Working on this repository
36
+
37
+ ```bash
38
+ uv sync --all-extras # Python env with every pack and the dev tools
39
+ cd frontend && npm ci && cd .. # editor dependencies
40
+ make test # uv run pytest -q (parallel; -n0 for serial)
41
+ make lint # ruff check + ruff format --check + tsc
42
+ make frontend # build the editor into src/noodlelab/static
43
+ ```
44
+
45
+ CI (`.github/workflows/ci.yml`) runs lint and tests on Python 3.11–3.13, the
46
+ frontend build, and each installation tier on its own (`scripts/check_tier.py`).
47
+ After changing a docstring, a node or the guide, run
48
+ `uv run python scripts/gen_llms.py` (a test fails when `llms.txt` is stale).
49
+
50
+ ### Map
51
+
52
+ | Path | What |
53
+ |---|---|
54
+ | `src/noodlelab/verify.py` | `noodlelab.verify`: records, measure/q, requirements, audit, Monte Carlo check |
55
+ | `src/noodlelab/core/` | the engine, independent of the editor: graph model (`graph.py`), `executor.py`, `units.py`, `uncertainty.py` (GUM), `montecarlo.py` (JCGM 101), `requirements.py`, `checks.py`, `provenance.py`, `tracked.py` / `reqlog.py` (run histories), `registry.py`, `typesys.py` |
56
+ | `src/noodlelab/nodes/` | the built-in node packs (entry points in `pyproject.toml`), one per tier |
57
+ | `src/noodlelab/agent/` | agent support: `guide.md`, the MCP server (`mcp.py`, `tools.py`), `init-agent` |
58
+ | `src/noodlelab/server/` | FastAPI app (`app.py`), REST API (`api.py`), WebSocket runs (`ws.py`), workspaces, auth, Slurm, agent terminal |
59
+ | `src/noodlelab/reports/` | Typst PDF reports |
60
+ | `src/noodlelab/examples/` | 24 example graphs (`*.graph.json`) with data: the best reference for graph JSON |
61
+ | `src/noodlelab/cli.py` | `noodlelab serve / run / test / verify / mcp / init-agent / nodes / export ...` |
62
+ | `src/noodlelab/tiers.py` | installation tiers (library, basic = `[app]`, maths, science, engineering, geo, full) |
63
+ | `frontend/src/` | the editor: TypeScript + litegraph, no framework (`main.ts` wires everything) |
64
+ | `tests/` | pytest; `conftest.py` has the `registry`, `packs` and `executor` fixtures and the `graph`/`link`/`value` helpers |
65
+
66
+ ### Conventions
67
+
68
+ - Docstrings and comments are plain prose saying *why*. Modules open with a
69
+ docstring that explains the concept (read `core/graph.py` and
70
+ `core/uncertainty.py` for the style).
71
+ - A node is a type-hinted function with `@node`. Its hints decide the sockets,
72
+ units (`Quantity["m"]`) and widgets (`Annotated[int, Param(min=1)]`), and its
73
+ docstring is the help text. A node pack checks its dependencies with
74
+ `noodlelab.tiers.require()`.
75
+ - Anything that records history or provenance must never fail a run: catch,
76
+ log, and carry on.
77
+ - The bare library must not import the web server or typst at module level
78
+ (CI's `library` tier checks this).
79
+ - Line length 100, ruff rules E, F, I, UP, B, SIM. TypeScript is checked with
80
+ `tsc --noEmit`.
81
+ - Tests describe behaviour in their names
82
+ (`test_a_run_without_requirements_records_nothing`), and use real graphs
83
+ through `executor.run(graph(...))` rather than mocks.
@@ -0,0 +1 @@
1
+ @AGENTS.md