nx_water_quality.nx source
↩ module page · 164 lines · 5365 B
1// nx_water_quality.nx -- WATER SYSTEMS / TASTE + HEALTH + SAFETY. Scores a
2// water for TASTE (WHO total-dissolved-solids palatability + mineral balance
3// + pH) and HEALTH (WHO desirable calcium + magnesium), and gates SAFETY by
4// construction: a NEVER-POISON refusal if any regulated contaminant exceeds
5// its EPA/WHO limit or bacteria are present.
6//
7// INTEGER-EXACT. TDS + minerals in mg/L; pH x10; trace contaminants in
8// their regulatory units (lead/arsenic ppb, nitrate mg/L-as-N, PFAS ppt,
9// THM ppb). Grounded limits:
10// lead <= 15 ppb (EPA action level, Lead and Copper Rule)
11// arsenic <= 10 ppb; nitrate-N <= 10 mg/L; THM(total) <= 80 ppb (EPA MCL)
12// PFOA+PFOS <= 4 ppt (EPA 2024 MCL); bacteria (E. coli/coliform) must be 0
13// Taste bands (WHO GDWQ): <300 excellent, 300-600 good, 600-900 fair,
14// 900-1200 poor, >1200 unacceptable; very low TDS (<50) tastes flat.
15// Health minerals (WHO desalination remineralization guidance): Ca >= 20,
16// Mg >= 10 mg/L desirable (optimum Ca 40-80, Mg 20-30).
17//
18// THE exceed: taste + health scored AND a never-poison safety gate that
19// STRUCTURALLY refuses lead/arsenic/nitrate/PFAS/bacteria/THM -- a TDS meter
20// reads a number, this decides drinkable / delicious / healthful.
21//
22// grounded: who_gdwq_tds_palatability + epa_national_primary_drinking_water
23// + epa_2024_pfas_mcl + who_remineralization_calcium_magnesium
24// genealogy_id: water_quality_taste_health + nishi_never_poison
25
26import "nx_syscalls.nx"
27
28// ===== Regulated safety limits ====================================
29
30const WQ_LEAD_PPB: i64 = 15
31const WQ_ARSENIC_PPB: i64 = 10
32const WQ_NITRATE_MGL: i64 = 10
33const WQ_PFAS_PPT: i64 = 4
34const WQ_THM_PPB: i64 = 80
35
36// Health-desirable minerals (mg/L).
37const WQ_CA_MIN: i64 = 20
38const WQ_MG_MIN: i64 = 10
39
40// ===== Sealed enums ===============================================
41
42const WQ_INSIPID: i64 = 0 // TDS < 50 (flat)
43const WQ_EXCELLENT: i64 = 1 // < 300
44const WQ_GOOD: i64 = 2 // 300-600
45const WQ_FAIR: i64 = 3 // 600-900
46const WQ_POOR: i64 = 4 // 900-1200
47const WQ_UNACCEPTABLE: i64 = 5 // > 1200
48
49const WQ_SAFE: i64 = 0
50const WQ_REFUSED_LEAD: i64 = 1
51const WQ_REFUSED_ARSENIC: i64 = 2
52const WQ_REFUSED_NITRATE: i64 = 3
53const WQ_REFUSED_PFAS: i64 = 4
54const WQ_REFUSED_BACTERIA: i64 = 5
55const WQ_REFUSED_THM: i64 = 6
56
57// ===== Struct: NxWaterSample ======================================
58
59struct NxWaterSample {
60 tds: i64,
61 ca: i64,
62 mg: i64,
63 na: i64,
64 hco3: i64,
65 sulfate: i64,
66 chloride: i64,
67 ph_x10: i64,
68 lead_ppb: i64,
69 arsenic_ppb: i64,
70 nitrate_mgl: i64,
71 pfas_ppt: i64,
72 bacteria: i64,
73 thm_ppb: i64,
74}
75
76func nx_water_sample_new() -> *NxWaterSample {
77 let s: *NxWaterSample = (sys_mmap(128)) as *NxWaterSample
78 s.tds = 0
79 s.ca = 0
80 s.mg = 0
81 s.na = 0
82 s.hco3 = 0
83 s.sulfate = 0
84 s.chloride = 0
85 s.ph_x10 = 70
86 s.lead_ppb = 0
87 s.arsenic_ppb = 0
88 s.nitrate_mgl = 0
89 s.pfas_ppt = 0
90 s.bacteria = 0
91 s.thm_ppb = 0
92 return s
93}
94
95// ===== Taste ======================================================
96
97func wq_tds_taste_class(tds: i64) -> i64 {
98 if tds < 50 { return WQ_INSIPID }
99 if tds < 300 { return WQ_EXCELLENT }
100 if tds < 600 { return WQ_GOOD }
101 if tds < 900 { return WQ_FAIR }
102 if tds < 1200 { return WQ_POOR }
103 return WQ_UNACCEPTABLE
104}
105
106// TDS taste points (max 60): optimum band 150-400.
107func wq_tds_points(tds: i64) -> i64 {
108 if tds < 50 { return 10 }
109 if tds < 150 { return 40 }
110 if tds <= 400 { return 60 }
111 if tds <= 600 { return 45 }
112 if tds <= 900 { return 30 }
113 if tds <= 1200 { return 15 }
114 return 0
115}
116
117func wq_ca_points(ca: i64) -> i64 {
118 if ca >= 40 { if ca <= 100 { return 13 } return 8 }
119 if ca >= 20 { return 8 }
120 return 0
121}
122
123func wq_mg_points(mg: i64) -> i64 {
124 if mg >= 20 { if mg <= 50 { return 12 } return 7 }
125 if mg >= 10 { return 7 }
126 return 0
127}
128
129func wq_ph_points(ph_x10: i64) -> i64 {
130 if ph_x10 >= 70 { if ph_x10 <= 80 { return 15 } }
131 if ph_x10 >= 65 { if ph_x10 <= 85 { return 10 } }
132 if ph_x10 >= 60 { if ph_x10 <= 90 { return 5 } }
133 return 0
134}
135
136// Composite taste score 0-100.
137func wq_taste_score(s: *NxWaterSample) -> i64 {
138 return wq_tds_points(s.tds) + wq_ca_points(s.ca) + wq_mg_points(s.mg) + wq_ph_points(s.ph_x10)
139}
140
141// ===== Safety (never-poison) ======================================
142
143func wq_safety_admit(s: *NxWaterSample) -> i64 {
144 if s.lead_ppb > WQ_LEAD_PPB { return WQ_REFUSED_LEAD }
145 if s.arsenic_ppb > WQ_ARSENIC_PPB { return WQ_REFUSED_ARSENIC }
146 if s.nitrate_mgl > WQ_NITRATE_MGL { return WQ_REFUSED_NITRATE }
147 if s.pfas_ppt > WQ_PFAS_PPT { return WQ_REFUSED_PFAS }
148 if s.bacteria != 0 { return WQ_REFUSED_BACTERIA }
149 if s.thm_ppb > WQ_THM_PPB { return WQ_REFUSED_THM }
150 return WQ_SAFE
151}
152
153func wq_is_potable(s: *NxWaterSample) -> i64 {
154 if wq_safety_admit(s) == WQ_SAFE { return 1 }
155 return 0
156}
157
158// Healthful = potable AND meets the WHO desirable mineral minimums.
159func wq_is_healthful(s: *NxWaterSample) -> i64 {
160 if wq_is_potable(s) == 0 { return 0 }
161 if s.ca < WQ_CA_MIN { return 0 }
162 if s.mg < WQ_MG_MIN { return 0 }
163 return 1
164}