nx_supp_claims.nx source
↩ module page · 221 lines · 9112 B
1// nx_supp_claims.nx -- SUPPLEMENT GTM SUITE / LABEL CLAIMS rung. Classifies a
2// marketing sentence as a lawful structure/function claim or an unlawful
3// disease claim, because that line is where supplement companies actually get
4// warning letters -- more often than for the formula itself.
5//
6// THE LAW IN ONE SENTENCE. A supplement may say what an ingredient does to
7// the normal structure or function of the body. The moment it says the
8// product diagnoses, mitigates, treats, cures or prevents a DISEASE, it is
9// making a drug claim, and an unapproved drug claim makes the product an
10// unapproved drug -- 21 CFR 101.93(g) sets out the criteria this implements.
11//
12// THE PAIRS THAT MATTER, and they are closer than they look:
13// LAWFUL "helps maintain healthy cholesterol levels already in normal range"
14// UNLAWFUL "lowers cholesterol" (implies treating hypercholesterolemia)
15// LAWFUL "supports healthy sexual function"
16// UNLAWFUL "treats female sexual arousal disorder"
17// UNLAWFUL "works alongside your antidepressant to fix SSRI side effects"
18// -- this one is doubly bad: it is a disease claim AND an
19// AUGMENTS-A-THERAPY claim under 101.93(g)(2)(viii).
20//
21// THAT LAST CASE IS WORTH SAYING PLAINLY: the framing "famously used to treat
22// antidepressant-induced sexual dysfunction", which is how ginkgo is usually
23// described in this category, CANNOT go on a label. The underlying research
24// may be perfectly real -- the sentence is still a drug claim.
25//
26// NATURAL STATES ARE NOT DISEASES. FDA is explicit that menopause, pregnancy,
27// aging and adolescence are normal life stages, so "for the relief of hot
28// flashes associated with menopause" is a lawful structure/function claim
29// while "for the treatment of osteoporosis" is not. A classifier that
30// flagged "menopause" as a disease term would be wrong in a way that costs
31// real, lawful marketing -- so the lexicon carves them out explicitly.
32//
33// FAIL-CLOSED, AND DELIBERATELY SO. A sentence with no disease trigger but
34// also no recognisable structure/function frame returns NEEDS_REVIEW, not OK.
35// This organ can retire the easy calls; it cannot replace counsel, and
36// pretending otherwise would be the expensive kind of wrong.
37//
38// Grounding (cited; researcher-groundable):
39// fda_21cfr101_93_structure_function_vs_disease_claims
40// fda_21cfr101_93_g_2_disease_claim_criteria
41// dshea_disclaimer_and_30_day_notification_101_93_c
42// fda_guidance_natural_state_menopause_pregnancy_aging
43//
44// genealogy_id: supplement_gtm + nishi_food_science_suite
45
46import "nx_syscalls.nx"
47
48// ===== Verdicts (sealed) ==============================================
49
50const CL_NEEDS_REVIEW: i64 = 0 // no trigger, no clear S/F frame
51const CL_STRUCTURE_FUNC: i64 = 1 // lawful, with disclaimer + notification
52const CL_DISEASE_CLAIM: i64 = 2 // unlawful without drug approval
53const CL_DRUG_REFERENCE: i64 = 3 // substitutes for / augments a therapy
54const CL_EMPTY: i64 = 4
55
56// A lawful structure/function claim carries BOTH obligations.
57const CL_DISCLAIMER_REQUIRED: i64 = 1
58const CL_NOTIFY_FDA_DAYS: i64 = 30
59
60// ===== Case-insensitive substring search ==============================
61
62func cl_lower(c: i64) -> i64 {
63 if c >= 65 { if c <= 90 { return c + 32 } }
64 return c
65}
66
67func cl_len(s: *u8) -> i64 {
68 var i: i64 = 0
69 while s[i] != (0 as u8) { i = i + 1 }
70 return i
71}
72
73// Index of needle in hay, case-insensitive, or -1. No break in this language,
74// so the scan latches the first hit and runs to completion.
75func cl_find(hay: *u8, needle: *u8) -> i64 {
76 let hn: i64 = cl_len(hay)
77 let nn: i64 = cl_len(needle)
78 if nn <= 0 { return 0 - 1 }
79 if nn > hn { return 0 - 1 }
80 var i: i64 = 0
81 var found: i64 = 0 - 1
82 let last: i64 = hn - nn
83 while i <= last {
84 if found < 0 {
85 var j: i64 = 0
86 var ok: i64 = 1
87 while j < nn {
88 let a: i64 = cl_lower(hay[i + j] as i64)
89 let b: i64 = cl_lower(needle[j] as i64)
90 if a != b { ok = 0 }
91 j = j + 1
92 }
93 if ok == 1 { found = i }
94 }
95 i = i + 1
96 }
97 return found
98}
99
100func cl_has(hay: *u8, needle: *u8) -> i64 {
101 let p: i64 = cl_find(hay, needle)
102 if p >= 0 { return 1 }
103 return 0
104}
105
106// ===== Lexicons =======================================================
107//
108// Split into small predicates rather than one giant scan so each category
109// can be tested, cited and extended on its own.
110
111// Verbs that assert a therapeutic effect on a disease.
112func cl_has_disease_verb(s: *u8) -> i64 {
113 if cl_has(s, "treat" as *u8) == 1 { return 1 }
114 if cl_has(s, "cure" as *u8) == 1 { return 1 }
115 if cl_has(s, "prevent" as *u8) == 1 { return 1 }
116 if cl_has(s, "diagnos" as *u8) == 1 { return 1 }
117 if cl_has(s, "mitigat" as *u8) == 1 { return 1 }
118 if cl_has(s, "remedy" as *u8) == 1 { return 1 }
119 if cl_has(s, "heals" as *u8) == 1 { return 1 }
120 if cl_has(s, "reverses" as *u8) == 1 { return 1 }
121 return 0
122}
123
124// Nouns naming a disease or disease class. "dysfunction" and "disorder" are
125// the two that catch this product category.
126func cl_has_disease_noun(s: *u8) -> i64 {
127 if cl_has(s, "disease" as *u8) == 1 { return 1 }
128 if cl_has(s, "disorder" as *u8) == 1 { return 1 }
129 if cl_has(s, "dysfunction" as *u8) == 1 { return 1 }
130 if cl_has(s, "syndrome" as *u8) == 1 { return 1 }
131 if cl_has(s, "impotence" as *u8) == 1 { return 1 }
132 if cl_has(s, "infertility" as *u8) == 1 { return 1 }
133 if cl_has(s, "depression" as *u8) == 1 { return 1 }
134 if cl_has(s, "hypogonadism" as *u8) == 1 { return 1 }
135 if cl_has(s, "osteoporosis" as *u8) == 1 { return 1 }
136 return 0
137}
138
139// Naming a drug, or positioning against one. 101.93(g)(2)(vii) and (viii).
140func cl_has_drug_reference(s: *u8) -> i64 {
141 if cl_has(s, "viagra" as *u8) == 1 { return 1 }
142 if cl_has(s, "addyi" as *u8) == 1 { return 1 }
143 if cl_has(s, "vyleesi" as *u8) == 1 { return 1 }
144 if cl_has(s, "cialis" as *u8) == 1 { return 1 }
145 if cl_has(s, "antidepressant" as *u8) == 1 { return 1 }
146 if cl_has(s, "ssri" as *u8) == 1 { return 1 }
147 if cl_has(s, "prescription" as *u8) == 1 { return 1 }
148 if cl_has(s, "alternative to" as *u8) == 1 { return 1 }
149 if cl_has(s, "instead of" as *u8) == 1 { return 1 }
150 if cl_has(s, "replaces your" as *u8) == 1 { return 1 }
151 if cl_has(s, "works alongside your" as *u8) == 1 { return 1 }
152 return 0
153}
154
155// Recognised lawful framings.
156func cl_has_sf_frame(s: *u8) -> i64 {
157 if cl_has(s, "supports" as *u8) == 1 { return 1 }
158 if cl_has(s, "helps maintain" as *u8) == 1 { return 1 }
159 if cl_has(s, "promotes" as *u8) == 1 { return 1 }
160 if cl_has(s, "maintains healthy" as *u8) == 1 { return 1 }
161 if cl_has(s, "helps support" as *u8) == 1 { return 1 }
162 if cl_has(s, "contributes to" as *u8) == 1 { return 1 }
163 return 0
164}
165
166// Normal life stages FDA does NOT treat as diseases. Present so the disease
167// lexicon cannot swallow lawful menopause and aging claims.
168func cl_has_natural_state(s: *u8) -> i64 {
169 if cl_has(s, "menopause" as *u8) == 1 { return 1 }
170 if cl_has(s, "pregnan" as *u8) == 1 { return 1 }
171 if cl_has(s, "aging" as *u8) == 1 { return 1 }
172 if cl_has(s, "adolescen" as *u8) == 1 { return 1 }
173 return 0
174}
175
176// ===== The classifier =================================================
177//
178// Order matters: a drug reference is checked FIRST because "an alternative to
179// Viagra" carries no disease noun at all yet is squarely a drug claim, and
180// checking disease terms first would let it through as merely unreviewed.
181
182func cl_classify(s: *u8) -> i64 {
183 let n: i64 = cl_len(s)
184 if n <= 0 { return CL_EMPTY }
185 if cl_has_drug_reference(s) == 1 { return CL_DRUG_REFERENCE }
186 if cl_has_disease_noun(s) == 1 { return CL_DISEASE_CLAIM }
187 let nat: i64 = cl_has_natural_state(s)
188 // A therapeutic verb applied to a NATURAL STATE is genuinely ambiguous --
189 // "relieves hot flashes of menopause" is lawful, "treats menopause" is
190 // not, and the difference is context a lexicon cannot resolve. Send it to
191 // review rather than guess in either direction.
192 if cl_has_disease_verb(s) == 1 {
193 if nat == 1 { return CL_NEEDS_REVIEW }
194 return CL_DISEASE_CLAIM
195 }
196 if nat == 1 {
197 if cl_has(s, "relief" as *u8) == 1 { return CL_STRUCTURE_FUNC }
198 }
199 if cl_has_sf_frame(s) == 1 { return CL_STRUCTURE_FUNC }
200 return CL_NEEDS_REVIEW
201}
202
203func cl_is_lawful(s: *u8) -> i64 {
204 let v: i64 = cl_classify(s)
205 if v == CL_STRUCTURE_FUNC { return 1 }
206 return 0
207}
208
209// A lawful S/F claim triggers the DSHEA disclaimer and a 30-day notification
210// to FDA under 101.93(c). Both, not either.
211func cl_requires_disclaimer(s: *u8) -> i64 {
212 let v: i64 = cl_classify(s)
213 if v == CL_STRUCTURE_FUNC { return CL_DISCLAIMER_REQUIRED }
214 return 0
215}
216
217func cl_notification_days(s: *u8) -> i64 {
218 let v: i64 = cl_classify(s)
219 if v == CL_STRUCTURE_FUNC { return CL_NOTIFY_FDA_DAYS }
220 return 0
221}