nx_thermal_process.nx
buildroot/runtime/nx_thermal_process.nx
about
nx_thermal_process.nx -- FOOD-SCIENCE SUITE / THERMAL PROCESS rung. The
heat side of food safety: how much lethality a time-temperature hold
actually delivers, whether a proposed process meets the legal
pasteurization schedule, and -- the decision that governs everything --
whether a food needs a botulinum cook at all.
This completes a triangle the ecosystem already had two corners of.
nx_ferment_safety refuses unsafe FERMENTS on the pH 4.6 botulinum line.
nx_chem_solution owns that line as chemistry (CS_HIGH_ACID_PH_MILLI).
Neither could say anything about HEAT, so a mix that is pasteurised
rather than acidified had no safety path -- and ice cream mix is exactly
that case, since 21 CFR 135.110 requires the mix to be pasteurised.
ACID AND HEAT ARE ALTERNATIVE BARRIERS, NOT A CHECKLIST. That is the
whole logic of tp_barrier_required, and it is why this imports
nx_chem_solution instead of restating 4.6:
pH < 4.6 -> the ACID barrier holds; C. botulinum
cannot grow; pasteurisation suffices.
pH >= 4.6, shelf-stable -> nothing but heat stands between the
product and botulism -> full 12D cook.
pH >= 4.6, kept cold -> pasteurise AND hold the cold chain;
refrigeration is the second barrier.
Ice cream mix is the third case. Treating it as the second would be a
pointless retort; treating it as the first would be dangerous.
WHAT IS ASSERTED VS WHAT IS DERIVED -- read this before trusting a number.
The only organism constant here is C. botulinum proteolytic spores,
D(121.1 C) = 0.21 min, z = 10 C. That pair is canonical, universally
published, and is the entire basis of the 12D cook and of F0. I did NOT
bake in D/z values for the pasteurisation organisms, because I could not
verify them to the precision a safety calculation deserves.
Instead the z-value for pasteurisation is DERIVED FROM THE LAW ITSELF.
The PMO gives two schedules it considers equivalent -- 145 F/30 min and
161 F/15 s -- and two equivalent schedules uniquely determine a z:
z = (T2 - T1) / log10(t1/t2)
tp_implied_z_milli computes it and gets 4.279 C (exact arithmetic on the
pair gives 4.275, so the integer path is good to 0.1%). That sits just
BELOW the commonly quoted 4.3-5.0 C band for the milk-pasteurisation design
organism -- close agreement, but stated precisely rather than rounded up
dependencies 3 imports · 3 importers
imports: nx_syscalls.nxnx_pow10.nxnx_chem_solution.nx
imported by: nx_labsci_gate.nxnx_labsci_svc.nxnx_water_activity.nx
structs
| none |
consts
| 61 | const TP_REF_STERIL_MILLI_C: i64 = 121100 // 121.1 C = 250 F |
| 62 | const TP_BOT_D_REF_MS: i64 = 12600 // D(121.1) = 0.21 min |
| 63 | const TP_BOT_Z_MILLI: i64 = 10000 // z = 10 C |
| 64 | const TP_BOT_TARGET_LOGS: i64 = 12 // the 12D cook |
| 65 | const TP_F0_STANDARD_MS: i64 = 180000 // 3.0 min, the industry target |
| 74 | const TP_MILK_VAT_TEMP_MILLI: i64 = 62778 // 145 F |
| 75 | const TP_MILK_VAT_TIME_MS: i64 = 1800000 // 30 min |
| 76 | const TP_MILK_HTST_TEMP_MILLI: i64 = 71667 // 161 F |
| 77 | const TP_MILK_HTST_TIME_MS: i64 = 15000 // 15 s |
| 78 | const TP_HIGHFAT_BUMP_MILLI: i64 = 2778 // +5 F |
| 80 | const TP_SCHED_MILK_VAT: i64 = 0 |
| 81 | const TP_SCHED_MILK_HTST: i64 = 1 |
| 82 | const TP_SCHED_MIX_VAT: i64 = 2 |
| 83 | const TP_SCHED_MIX_HTST: i64 = 3 |
| 87 | const TP_BARRIER_NONE: i64 = 0 |
| 88 | const TP_BARRIER_ACID: i64 = 1 |
| 89 | const TP_BARRIER_BOT_COOK: i64 = 2 |
| 90 | const TP_BARRIER_REFRIGERATION: i64 = 3 |
functions
| 94 | func tp_sched_temp_milli(id: i64) -> i64 called by 1: tp_pasteurization_equiv_ms |
| 102 | func tp_sched_time_ms(id: i64) -> i64 called by 1: tp_pasteurization_compliant |
| 119 | func tp_d_at_temp_ms(d_ref_ms: i64, t_ref_milli: i64, z_milli: i64, t_milli: i64) -> i64 calls 1: ipow10_q3 |
| 133 | func tp_log_reductions_q3(hold_ms: i64, d_ms: i64) -> i64 |
| 139 | func tp_equiv_time_at_ref_ms(hold_ms: i64, temp_milli: i64, t_ref_milli: i64, z_milli: i64) -> i64 |
| 154 | func tp_f0_ms(hold_ms: i64, temp_milli: i64) -> i64 |
| 160 | func tp_bot_12d_ms() -> i64 called by 1: tp_bot_cook_adequate |
| 175 | func tp_bot_cook_adequate(f0_ms: i64) -> i64 |
| 187 | func tp_implied_z_milli(t1_ms: i64, temp1_milli: i64, t2_ms: i64, temp2_milli: i64) -> i64 |
| 199 | func tp_pasteurization_z_milli() -> i64 |
| 210 | func tp_htst_sched_for(highfat_or_sweetened: i64) -> i64 |
| 215 | func tp_pasteurization_equiv_ms(temp_milli: i64, hold_ms: i64, highfat_or_sweetened: i64) -> i64 |
| 223 | func tp_pasteurization_compliant(temp_milli: i64, hold_ms: i64, highfat_or_sweetened: i64) -> i64 |
| 234 | func tp_ice_cream_mix_compliant(temp_milli: i64, hold_ms: i64) -> i64 calls 1: tp_pasteurization_compliant |
| 240 | func tp_barrier_required(ph_milli: i64, ambient_stable: i64) -> i64 |
| 249 | func tp_process_adequate(ph_milli: i64, ambient_stable: i64, f0_ms: i64, pasteurized: i64) -> i64 |