simcon-toolkit 0.1.0__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.
Files changed (97) hide show
  1. simcon_toolkit/__init__.py +40 -0
  2. simcon_toolkit/__main__.py +7 -0
  3. simcon_toolkit/_kit/LICENSE +202 -0
  4. simcon_toolkit/_kit/NOTICE +37 -0
  5. simcon_toolkit/_kit/assets/parts/clip_frame.stl +0 -0
  6. simcon_toolkit/_kit/assets/parts/simple_plate.stl +0 -0
  7. simcon_toolkit/_kit/packages/.ruff.toml +10 -0
  8. simcon_toolkit/_kit/packages/cadmould_cloud/__init__.py +8 -0
  9. simcon_toolkit/_kit/packages/cadmould_cloud/auth.py +681 -0
  10. simcon_toolkit/_kit/packages/cadmould_cloud/client.py +235 -0
  11. simcon_toolkit/_kit/packages/cadmould_geometry/__init__.py +5 -0
  12. simcon_toolkit/_kit/packages/cadmould_geometry/mesh.py +210 -0
  13. simcon_toolkit/_kit/packages/cadmould_geometry/stl.py +168 -0
  14. simcon_toolkit/_kit/packages/cadmould_results/__init__.py +30 -0
  15. simcon_toolkit/_kit/packages/cadmould_results/loader.py +288 -0
  16. simcon_toolkit/_kit/packages/cadmould_scoring/__init__.py +7 -0
  17. simcon_toolkit/_kit/packages/cadmould_scoring/metrics.py +519 -0
  18. simcon_toolkit/_kit/pyproject.toml +232 -0
  19. simcon_toolkit/_kit/templates/_shared/AGENTS.base.md +101 -0
  20. simcon_toolkit/_kit/templates/gate-study/.gitignore +18 -0
  21. simcon_toolkit/_kit/templates/gate-study/AGENTS.md +46 -0
  22. simcon_toolkit/_kit/templates/gate-study/GATING_STUDY_PLAYBOOK.md +219 -0
  23. simcon_toolkit/_kit/templates/gate-study/INITIAL_PROMPT.md +26 -0
  24. simcon_toolkit/_kit/templates/gate-study/README.md +137 -0
  25. simcon_toolkit/_kit/templates/gate-study/main.py +344 -0
  26. simcon_toolkit/_kit/templates/gate-study/pipeline.py +281 -0
  27. simcon_toolkit/_kit/templates/process-window/.gitignore +20 -0
  28. simcon_toolkit/_kit/templates/process-window/AGENTS.md +49 -0
  29. simcon_toolkit/_kit/templates/process-window/METHOD.md +155 -0
  30. simcon_toolkit/_kit/templates/process-window/README.md +176 -0
  31. simcon_toolkit/_kit/templates/process-window/configs/simple-plate.yaml +116 -0
  32. simcon_toolkit/_kit/templates/process-window/doe_spec.schema.md +249 -0
  33. simcon_toolkit/_kit/templates/process-window/main.py +82 -0
  34. simcon_toolkit/_kit/templates/process-window/process_window/__init__.py +5 -0
  35. simcon_toolkit/_kit/templates/process-window/process_window/centre.py +298 -0
  36. simcon_toolkit/_kit/templates/process-window/process_window/design.py +144 -0
  37. simcon_toolkit/_kit/templates/process-window/process_window/economics.py +367 -0
  38. simcon_toolkit/_kit/templates/process-window/process_window/emit.py +591 -0
  39. simcon_toolkit/_kit/templates/process-window/process_window/guardrails.py +153 -0
  40. simcon_toolkit/_kit/templates/process-window/process_window/harness.py +360 -0
  41. simcon_toolkit/_kit/templates/process-window/process_window/identity.py +92 -0
  42. simcon_toolkit/_kit/templates/process-window/process_window/inspect_part.py +184 -0
  43. simcon_toolkit/_kit/templates/process-window/process_window/kpis.py +355 -0
  44. simcon_toolkit/_kit/templates/process-window/process_window/material_card.py +163 -0
  45. simcon_toolkit/_kit/templates/process-window/process_window/probe_proxy.py +169 -0
  46. simcon_toolkit/_kit/templates/process-window/process_window/run_confirm.py +403 -0
  47. simcon_toolkit/_kit/templates/process-window/process_window/run_epsilon_floor.py +198 -0
  48. simcon_toolkit/_kit/templates/process-window/process_window/run_feedback.py +322 -0
  49. simcon_toolkit/_kit/templates/process-window/process_window/run_refine.py +279 -0
  50. simcon_toolkit/_kit/templates/process-window/process_window/run_screening.py +370 -0
  51. simcon_toolkit/_kit/templates/process-window/process_window/run_sweep.py +166 -0
  52. simcon_toolkit/_kit/templates/process-window/process_window/setup_campaign.py +312 -0
  53. simcon_toolkit/_kit/templates/process-window/process_window/surrogate.py +201 -0
  54. simcon_toolkit/_kit/templates/process-window/process_window/test_centre.py +169 -0
  55. simcon_toolkit/_kit/templates/process-window/process_window/test_design.py +113 -0
  56. simcon_toolkit/_kit/templates/process-window/process_window/test_guardrails.py +157 -0
  57. simcon_toolkit/_kit/templates/process-window/process_window/test_surrogate.py +127 -0
  58. simcon_toolkit/_kit/templates/process-window/process_window/units.py +152 -0
  59. simcon_toolkit/_kit/templates/quoting/.gitignore +24 -0
  60. simcon_toolkit/_kit/templates/quoting/AGENTS.md +58 -0
  61. simcon_toolkit/_kit/templates/quoting/INTERVIEW.md +147 -0
  62. simcon_toolkit/_kit/templates/quoting/METHOD.md +256 -0
  63. simcon_toolkit/_kit/templates/quoting/PROMPT.md +46 -0
  64. simcon_toolkit/_kit/templates/quoting/QUOTING_PLAYBOOK.md +245 -0
  65. simcon_toolkit/_kit/templates/quoting/README.md +158 -0
  66. simcon_toolkit/_kit/templates/quoting/main.py +484 -0
  67. simcon_toolkit/_kit/templates/quoting/parts/.gitkeep +0 -0
  68. simcon_toolkit/_kit/templates/quoting/quoting/__init__.py +11 -0
  69. simcon_toolkit/_kit/templates/quoting/quoting/costing.py +725 -0
  70. simcon_toolkit/_kit/templates/quoting/quoting/geometry.py +398 -0
  71. simcon_toolkit/_kit/templates/quoting/quoting/shop.py +193 -0
  72. simcon_toolkit/_kit/templates/quoting/quoting/state.py +260 -0
  73. simcon_toolkit/_kit/templates/quoting/quoting/study.py +577 -0
  74. simcon_toolkit/_kit/templates/quoting/quoting/toolkit.py +50 -0
  75. simcon_toolkit/_kit/templates/quoting/shop/README.md +43 -0
  76. simcon_toolkit/_kit/templates/quoting/shop/commercial.md +86 -0
  77. simcon_toolkit/_kit/templates/quoting/shop/lessons.md +94 -0
  78. simcon_toolkit/_kit/templates/quoting/shop/machines.md +68 -0
  79. simcon_toolkit/_kit/templates/quoting/shop/materials.md +92 -0
  80. simcon_toolkit/_kit/templates/quoting/shop/shop-profile.md +87 -0
  81. simcon_toolkit/_kit/templates/quoting/shop/tooling.md +145 -0
  82. simcon_toolkit/_kit/templates/run-one-simulation/.gitignore +16 -0
  83. simcon_toolkit/_kit/templates/run-one-simulation/AGENTS.md +41 -0
  84. simcon_toolkit/_kit/templates/run-one-simulation/README.md +133 -0
  85. simcon_toolkit/_kit/templates/run-one-simulation/main.py +216 -0
  86. simcon_toolkit/_kit/templates.toml +83 -0
  87. simcon_toolkit/choices.py +11 -0
  88. simcon_toolkit/cli.py +381 -0
  89. simcon_toolkit/generate.py +590 -0
  90. simcon_toolkit/instructions.py +152 -0
  91. simcon_toolkit/manifest.py +86 -0
  92. simcon_toolkit/project.py +356 -0
  93. simcon_toolkit/wizard.py +160 -0
  94. simcon_toolkit-0.1.0.dist-info/METADATA +48 -0
  95. simcon_toolkit-0.1.0.dist-info/RECORD +97 -0
  96. simcon_toolkit-0.1.0.dist-info/WHEEL +4 -0
  97. simcon_toolkit-0.1.0.dist-info/entry_points.txt +2 -0
@@ -0,0 +1,116 @@
1
+ # Frontloaded DOE — runnable configuration against the sample part shipped with this repo.
2
+ #
3
+ # Paths are relative to this template's root (the folder containing main.py and configs/).
4
+ #
5
+ # This config points at the sample part in `sample/`, so the pipeline can be executed end to
6
+ # end without any private input. Replace the `part:` block to run it on your own geometry.
7
+ #
8
+ # Expect a modest answer: run
9
+ # python main.py inspect
10
+ # first. A thick, generously-proportioned plate fills long before its walls freeze, so the
11
+ # feasibility gate will very likely never bite and the whole box will come back green. That is a
12
+ # true result, not a broken run: it is what a thick-walled part looks like, and it is the signal
13
+ # to try a thinner-walled one.
14
+
15
+ part:
16
+ name: Simple Plate
17
+ stl: "sample/simple_plate.stl"
18
+ scale: 1.0 # 1.0 = already in mm
19
+
20
+ material:
21
+ # NOT SHIPPED. A .plb is a licensed Cadmould material file and is not in this repository.
22
+ #
23
+ # It is only needed for the G4 numerical decks (SimulationConfig.Builder wants a Material
24
+ # object, and PlbReader is the only loader). G0-G3 do not need it: the cloud catalogue's
25
+ # material version carries no_flow_temperature_K, the thermal data and the processing window,
26
+ # and they agree with the local card exactly.
27
+ #
28
+ # Point this at your own .plb export, or drop the G4 stage.
29
+ plb: "PP Generic.plb"
30
+ catalogue_name: "PP Generic"
31
+ is_placeholder: true
32
+
33
+ gates:
34
+ # `suggest` asks the SDK for an on-mesh gate. The surrogate requires the gate to be a real mesh
35
+ # node — never an off-mesh point, never (0,0,0).
36
+ mode: suggest
37
+ # positions_mm: [[x, y, z]] # set this to override, snapped to the nearest node
38
+
39
+ variables:
40
+ # The only three variables the AI filling solver can see. Bounds are DERIVED:
41
+ # melt, wall -> the material card's full processing window
42
+ # flow -> nominal +/- 50 %
43
+ # Temperature bounds are not a percentage: temperature is an interval scale, so "+/-50 %" of it
44
+ # is not a well-defined instruction. The card's own processing window is the honest wide box.
45
+ melt:
46
+ from: material_card
47
+ wall:
48
+ from: material_card
49
+ flow:
50
+ from: sdk_suggestion # suggest_fill_profile + estimate_fill_time, never a guessed time
51
+ rel_span: 0.50
52
+
53
+ # Switchover is NOT swept: the AI endpoint has no switchover input. It is a read-out axis on
54
+ # every run, and a swept input only in the G4 numerical decks.
55
+ switchover_readout_phi: [0.90, 0.95, 0.98, 0.99]
56
+
57
+ packing_cooling:
58
+ from: sdk_suggestion
59
+
60
+ feasibility_gate:
61
+ primary: no_flow_margin_end_K # absolute superheat above no-flow, in K
62
+ fill_completeness:
63
+ use_as_gate: auto # only if the residual is ~0 AND flow-independent
64
+ residual_tolerance: 0.005
65
+ report_always: true
66
+
67
+ budget:
68
+ # Deliberately small so a first run finishes quickly.
69
+ # Sobol is incremental, so raising this later reuses every run already done.
70
+ epsilon_floor_runs: 17
71
+ sweep_points: 256
72
+ refine_points: 96
73
+ confirmation_points: 14
74
+ fake_real_anchors: 3
75
+
76
+ solver:
77
+ num_timesteps: 50 # 50-64 safe; ~100 returns 502 from the gateway
78
+ htc_W_m2K: 1000.0 # fixed, not swept
79
+ max_concurrency: 4 # the endpoint sheds load above this
80
+ retry:
81
+ attempts: 4
82
+ backoff_s: 2.0
83
+ on_status: [429, 500, 502, 503, 504]
84
+
85
+ cloud:
86
+ rest_base_url: "https://api.simcon.ai/api/v1"
87
+ # Driven over REST rather than the SDK: the SDK cannot set num_timesteps (which controls both
88
+ # the phi resolution the KPIs depend on and the result file size) or pin model_version.
89
+ model_version: "v0.1.0"
90
+ project: "DOE Frontloading"
91
+ group_per_stage: true
92
+
93
+ storage:
94
+ # Results are 25-220 MB each. Score, write a ~2 kB summary, then delete the raw file — except
95
+ # where the run is worth keeping. A per-part subfolder is appended automatically.
96
+ #
97
+ # Under cache/ because it is ours and safe to delete: everything here can be recomputed,
98
+ # it only costs time and solver runs. The campaign's answer lands in output/.
99
+ runs_dir: "cache/runs"
100
+ keep_h5:
101
+ - boundary
102
+ - anomaly
103
+ - anchor
104
+
105
+ machine_limits:
106
+ # Applied as a final feasibility ANNOTATION only, never as a screening criterion: a machine
107
+ # limit is a constraint, and guardrail 1 forbids absolute pressure thresholds in criteria.
108
+ max_injection_pressure_bar: 1500.0
109
+ max_clamp_force_kN: 1000.0
110
+ applied_as: annotation_only
111
+
112
+ stated_limitations:
113
+ - "sample geometry, not a production part"
114
+ - "material is PP Generic as a PLACEHOLDER"
115
+ - "no runner system in the AI model, so any hot runner is absent rather than approximated"
116
+ - "AI solver under-predicts absolute pressure on multi-gate parts (~0.3x on 8 gates)"
@@ -0,0 +1,249 @@
1
+ # `doe_spec.yaml` — schema
2
+
3
+ No schema was supplied, so this is ours. Designed against one question: **can the process
4
+ engineer in Arbeitsvorbereitung take this file to the machine tomorrow morning, and can an
5
+ auditor later reconstruct why every number in it is what it is?**
6
+
7
+ Three rules shaped it:
8
+
9
+ 1. **The answer comes first.** `centre_point` and `factors` are at the top, because that is
10
+ what the engineer actually needs: which parameters to vary, where to start, how wide. The
11
+ provenance lives below and is for the auditor, not the operator.
12
+ 2. **Every bound carries its basis.** No number appears without `*_basis` saying where it came
13
+ from — material card, derived, measured, or judgement. A bound whose basis is "judgement"
14
+ is allowed; a bound with no basis is not.
15
+ 3. **Absolute values are quarantined.** Any field whose value depends on the solver's absolute
16
+ pressure scale sits under a key marked `absolute: true` and carries `caveats`. The
17
+ guardrail lint (`process_window/guardrails.py`) walks this file and fails if an absolute pressure
18
+ appears inside `acceptance_criteria`.
19
+
20
+ Temperatures are emitted in **both** °C and K. The engineer works in °C, the API works in K,
21
+ and the whole project's worst failure mode is a silent confusion between them — so the file
22
+ states both rather than making a reader convert.
23
+
24
+ ---
25
+
26
+ ## Structure
27
+
28
+ ```yaml
29
+ schema: simcon.doe_spec/1
30
+ generated_at: 2026-08-04T14:22:07+02:00
31
+ generated_by: "frontloaded-doe POC, commit <sha>"
32
+ status: draft | confirmed # confirmed only after G4 has run
33
+
34
+ # ---------------------------------------------------------------- the answer
35
+ centre_point:
36
+ definition: chebyshev_centre_shrunk # deepest interior point of the green region,
37
+ # shrunk inward by the safety margin.
38
+ # NOT the survivor mean, NOT the default settings.
39
+ melt: {value_C: 245.0, value_K: 518.15, basis: derived}
40
+ wall: {value_C: 40.0, value_K: 313.15, basis: derived}
41
+ flow: {value_cm3_s: 9.4, basis: derived}
42
+ safety_margin: # in VARIABLE space, not KPI space
43
+ melt_C: 6.2
44
+ wall_C: 3.1
45
+ flow_cm3_s: 0.7
46
+ basis: >
47
+ KPI uncertainty band divided by the local gradient of that KPI from the G2
48
+ surrogate. Band = derived noise floor (+ AI-to-numerical disagreement once G4
49
+ has run).
50
+ band_source: {kpi: p80, band: 1.8, unit: bar, floor_run_ids: [...]}
51
+
52
+ factors: # what to vary, ranked by effect share
53
+ - name: melt_temperature
54
+ unit_engineer: degC
55
+ unit_api: K
56
+ role: control # control | inert | readout | uncertainty
57
+ centre_C: 245.0
58
+ low_C: 232.0
59
+ high_C: 258.0
60
+ bounds_basis: derived # from the green region, inset by the margin
61
+ effect_share: 0.54 # normalised, per KPI below
62
+ effect_share_per_kpi: {p80: 0.61, weld_front_temp: 0.88, hotspot: 0.42}
63
+ rank: 1
64
+ above_noise_floor: true
65
+
66
+ - name: injection_rate
67
+ unit_engineer: cm3_s
68
+ role: control
69
+ centre_cm3_s: 9.4
70
+ low_cm3_s: 7.1
71
+ high_cm3_s: 11.8
72
+ bounds_basis: derived
73
+ effect_share: 0.39
74
+ rank: 2
75
+ above_noise_floor: true
76
+
77
+ fixed_factors: # held, with the reason it is held
78
+ - name: mould_wall_temperature
79
+ value_C: 40.0
80
+ value_K: 313.15
81
+ reason: inert # inert | tool_property | out_of_scope | judgement
82
+ detail: >
83
+ Entire effect across the card window sits below the derived floor for every KPI.
84
+ Declared inert and fixed, not ranked low (guardrail 2).
85
+ floor_comparison: {kpi: p80, effect: 0.9, floor: 1.8, unit: bar}
86
+
87
+ - name: htc
88
+ value_W_m2K: 1000.0
89
+ reason: out_of_scope
90
+ detail: "Melt-to-mould contact property, not a machine setting. Not swept (user directive)."
91
+
92
+ - name: gate_positions_mm
93
+ value: [[...]]
94
+ reason: tool_property
95
+ detail: "Gate layout is tool design, not a machine parameter. Fixed across the DOE."
96
+
97
+ readouts: # free axes, not swept inputs
98
+ - name: switchover_point
99
+ unit: phi # injected-volume fraction
100
+ values: [0.90, 0.95, 0.98, 0.99]
101
+ method: >
102
+ Field state read at fill fraction phi from runs already paid for. This is the state
103
+ AT switchover, not the effect OF switching there — the effect is in packing and is
104
+ swept in numerical_confirmation below.
105
+ caveats: [incompressible_melt_phi]
106
+
107
+ # ---------------------------------------------------------------- what was ruled out
108
+ excluded_regions: # "which 90%", and specifically why each
109
+ - id: X1
110
+ bounds: {melt_C: [220.0, 231.0]}
111
+ reason: no_flow_margin
112
+ detail: "Melt within <8 K of the card's no-flow temperature at the last-filled region."
113
+ kpi: no_flow_margin
114
+ evidence_run_ids: [...]
115
+ absolute: false
116
+ - id: X2
117
+ bounds: {flow_cm3_s: [13.1, 13.5], melt_C: [220.0, 236.0]}
118
+ reason: hotspot_rank
119
+ detail: "Thermal heterogeneity in the worst decile across the sweep."
120
+ kpi: hotspot
121
+ evidence_run_ids: [...]
122
+ absolute: false
123
+
124
+ # ---------------------------------------------------------------- how it was decided
125
+ acceptance_criteria: # MUST be rank / ratio / comparison only
126
+ - name: pressure_ordering
127
+ kind: rank
128
+ expression: "p80_rank <= 0.70" # keep the best 70% by rank
129
+ kpi: p80
130
+ absolute: false
131
+ note: "Ordering only. p80 never compared to a threshold in bar (guardrail 1)."
132
+ - name: no_flow_guard
133
+ kind: ratio
134
+ expression: "(T_min_at_front - no_flow_temp) / (melt_C - no_flow_temp) >= 0.15"
135
+ kpi: no_flow_margin
136
+ absolute: false
137
+
138
+ noise_floor: # derived here, never imported
139
+ method: epsilon_input_micro_experiment
140
+ mechanism: >
141
+ Solver is deterministic, so this is float16 storage quantization plus model
142
+ conditioning — not run-to-run scatter. Replicates carry zero information.
143
+ per_kpi:
144
+ p80: {floor: 1.8, unit: bar, n_runs: 16}
145
+ weld_front_temp: {floor: 0.6, unit: K, n_runs: 16}
146
+ hotspot: {floor: 0.02, unit: "-", n_runs: 16}
147
+
148
+ feasibility_gate:
149
+ primary: no_flow_margin # physics-based freeze-off guard
150
+ fill_completeness:
151
+ used_as_gate: false | true
152
+ residual_unfilled_at_best_point: 0.0
153
+ flow_dependence: none | present
154
+ detail: >
155
+ Fill completeness is used as a gate only if the residual unfilled fraction at the
156
+ best point is ~0 and shows no flow-rate dependence. A flow-dependent residual is a
157
+ known model artifact, not physics, and must not drive exclusion.
158
+
159
+ # ---------------------------------------------------------------- the confirmation set
160
+ numerical_confirmation:
161
+ solver: cadmould_numerical
162
+ mesh: same_as_ai # same .cfex — apples to apples
163
+ points:
164
+ - id: C0
165
+ role: centre # centre | face | probe
166
+ melt_C: 245.0
167
+ wall_C: 40.0
168
+ flow_cm3_s: 9.4
169
+ switchover_phi: 0.97
170
+ holding_pressure_bar: 420.0
171
+ holding_time_s: 4.5
172
+ cooling_time_s: 12.0
173
+ pack_cool_basis: sdk_suggestion
174
+ rm1: "cache/runs/<part-key>/G4_confirmation/C0.rm1"
175
+ rm1_validated: true # Rm1Writer -> Rm1Reader round trip, field-checked
176
+ submit: {tier: 1, endpoint: "POST /simulations"}
177
+ status: prepared | submitted | done
178
+ rule: >
179
+ Where a numerical result disagrees with the AI-solver ordering, step the bounds
180
+ INWARD, never outward.
181
+
182
+ # ---------------------------------------------------------------- provenance / audit
183
+ provenance:
184
+ part: {name: clip_frame, geometry_id: ..., cfex_sha256: ..., nodes: 6201,
185
+ volume_cm3: ..., max_wall_mm: ...}
186
+ material: {catalogue_name: "PP Generic", material_id: ..., version_id: ...,
187
+ plb_sha256: ..., no_flow_temp_C: ...,
188
+ window: {melt_C: [.., ..], mold_C: [.., ..]}}
189
+ machine_limits:
190
+ max_injection_pressure_bar: 1500.0
191
+ max_clamp_force_kN: 1000.0
192
+ applied_as: annotation_only # a constraint, never a screening criterion
193
+ absolute: true
194
+ caveats: [multi_gate_pressure_underprediction, cavities_1_of_32, no_runner_system]
195
+ wide_box:
196
+ melt_C: {low: .., high: .., basis: material_card}
197
+ mold_C: {low: .., high: .., basis: material_card}
198
+ flow_cm3_s: {low: .., high: .., basis: nominal_ratio, rel_span: 0.5}
199
+ ai_runs:
200
+ stages:
201
+ - {stage: G1, group_id: ..., design: sobol, seed: 0, n_planned: 1024, n_completed: ...}
202
+ - {stage: G3, group_id: ..., design: adaptive_boundary, n_completed: ...}
203
+ total_completed: ...
204
+ determinism_note: "Identical request returns identical result; no replicates run."
205
+ surrogate:
206
+ type: rbf | quadratic_rs
207
+ loo_error_per_kpi: {p80: 0.016, weld_front_temp: 0.008}
208
+ self_diagnosis:
209
+ verdict: green | warn | alarm
210
+ thresholds_basis: derived_from_anchor_spread
211
+ anchors: [...]
212
+ fake_real_source: ai_with_hidden_deviation | numerical_with_hidden_deviation
213
+ limitation: >
214
+ AI-vs-AI-with-hidden-deviation measures sensitivity to input misspecification,
215
+ not the AI-to-physics offset.
216
+
217
+ guardrail_report:
218
+ absolute_pressure_in_criteria: pass # lint fails the run otherwise
219
+ noise_floor_derived_not_imported: pass
220
+ caveats_attached_to_absolute_claims: pass
221
+
222
+ caveats: # auto-attached to absolute-valued claims
223
+ cavities_1_of_32: "1 of 32 cavities modelled, single gate."
224
+ no_runner_system: "No runner system in the model; the hot runner is absent, not approximated."
225
+ no_process_description: "Almost no description of the real process beyond the material."
226
+ multi_gate_pressure_underprediction: >
227
+ AI solver under-predicts absolute pressure on multi-gate parts (~0.3x on an 8-gate
228
+ part). Every criterion is therefore a rank, ratio or comparison.
229
+ weld_temp_circularity: >
230
+ Weld-front temperature ordered points well (Spearman ~0.95, roughly constant offset)
231
+ but was tested against a melt perturbation while melt drives that KPI roughly 1:1.
232
+ incompressible_melt_phi: >
233
+ The phi axis equates injected with filled volume. Sub-percent during fill, worst as
234
+ phi -> 1, which is where switchover lives.
235
+ fill_race_not_transferable: >
236
+ Fill-race metrics measured not to transfer (Spearman 0.10-0.23). Computed as an
237
+ anomaly detector only, never as a screening criterion.
238
+ ```
239
+
240
+ ## Field conventions
241
+
242
+ | Convention | Rule |
243
+ |---|---|
244
+ | `*_C` / `*_K` | Temperatures are always emitted in both. |
245
+ | `*_basis` | `material_card` \| `derived` \| `measured` \| `sdk_suggestion` \| `judgement` \| `user_directive`. Required next to every bound. |
246
+ | `absolute: true` | Marks a value that depends on the solver's absolute pressure scale. Requires non-empty `caveats`. Forbidden inside `acceptance_criteria`. |
247
+ | `role` | `control` (swept, kept) \| `inert` (below floor, fixed) \| `readout` (free axis) \| `uncertainty` (robustness axis). |
248
+ | `unset` | Explicit, not omitted — so the dossier can state that a limit was unavailable rather than silently skipping it. |
249
+ | run ids | Every exclusion and every threshold cites the run ids it rests on. |
@@ -0,0 +1,82 @@
1
+ """Find the process window for a part, before you book machine time.
2
+
3
+ One campaign, run a step at a time:
4
+
5
+ python main.py --list what the steps are, in order
6
+ python main.py inspect is this part worth a campaign at all?
7
+ python main.py sweep configs/simple-plate.yaml
8
+
9
+ Each step is resumable and writes what the next one reads, so they run in the order
10
+ `--list` prints. Judgement belongs between them, which is why there is no command that
11
+ runs the whole campaign: a sweep costs thousands of solver runs, and you decide whether
12
+ the result is worth refining before paying for the next stage.
13
+
14
+ Everything after the step name belongs to that step — `python main.py refine --help`
15
+ prints the refinement step's own options. The steps do not share a flag set on purpose:
16
+ `--grid` means one thing to `refine` and another to `economics`, and folding them into
17
+ one option here would silently change what a campaign ran.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import importlib
23
+ import sys
24
+
25
+ # Step name -> the module that implements it, in the order a campaign runs them.
26
+ # The name is the verb a reader types; the module keeps the name the campaign uses
27
+ # for the stage internally.
28
+ STEPS: dict[str, tuple[str, str]] = {
29
+ "inspect": ("inspect_part", "G0 — geometry, wall, L/t. Free; run it first"),
30
+ "epsilon-floor": ("run_epsilon_floor", "G1a — this part's resolution floor"),
31
+ "probe": ("probe_proxy", "G1b — does the feasibility gate discriminate?"),
32
+ "sweep": ("run_sweep", "G1 — space-filling sweep, resumable"),
33
+ "screen": ("run_screening", "G2 — rank the variables, map what is excluded"),
34
+ "refine": ("run_refine", "G3 — adaptive boundary refinement + centre point"),
35
+ "confirm": ("run_confirm", "G4 — numerical confirmation decks"),
36
+ "feedback": ("run_feedback", "G6 — hidden-deviation self-diagnosis"),
37
+ "emit": ("emit", "G5 — write doe_spec.yaml and dossier.md"),
38
+ "economics": ("economics", "cycle time vs robustness"),
39
+ }
40
+
41
+
42
+ def print_steps() -> None:
43
+ width = max(len(name) for name in STEPS)
44
+ print("Campaign steps, in the order they run:\n")
45
+ for name, (_module, blurb) in STEPS.items():
46
+ print(f" {name:<{width}} {blurb}")
47
+ print("\nRun one with: python main.py <step> [its own options]")
48
+
49
+
50
+ def main(argv: list[str] | None = None) -> int:
51
+ argv = list(sys.argv[1:] if argv is None else argv)
52
+
53
+ if not argv or argv[0] in {"-h", "--help"}:
54
+ print(__doc__)
55
+ print_steps()
56
+ return 0
57
+ if argv[0] in {"-l", "--list"}:
58
+ print_steps()
59
+ return 0
60
+
61
+ step, rest = argv[0], argv[1:]
62
+ if step not in STEPS:
63
+ print(f"unknown step: {step}", file=sys.stderr)
64
+ print(f"expected one of: {', '.join(STEPS)}", file=sys.stderr)
65
+ return 2
66
+
67
+ module_name, _blurb = STEPS[step]
68
+ # Imported here rather than at module level, so listing the steps and reading this
69
+ # help never needs the licensed cadmould wheel. Every step reaches it — one directly,
70
+ # the rest through the module that builds the campaign — so an eager command table
71
+ # would make `--list` impossible without a licence.
72
+ module = importlib.import_module(f"process_window.{module_name}")
73
+
74
+ # Re-present the command line as though the step had been run directly: each step
75
+ # owns its own parser, and this is what keeps `main.py refine --grid 41` identical
76
+ # to what that step has always accepted.
77
+ sys.argv = [f"main.py {step}", *rest]
78
+ return int(module.main() or 0)
79
+
80
+
81
+ if __name__ == "__main__":
82
+ raise SystemExit(main())
@@ -0,0 +1,5 @@
1
+ """The process-window campaign: a design-of-experiments study run before a machine trial.
2
+
3
+ Each module under here is one stage of the campaign, or a library those stages share.
4
+ Run them through `main.py` at the project root rather than directly.
5
+ """