nx_gtm_svc.nx source
↩ module page · 392 lines · 15949 B
1// nx_gtm_svc.nx -- GO-TO-MARKET SERVICE FACADE. One agentic tool over the
2// whole supplement/drug go-to-market stack: it composes the eight GTM library
3// modules and exposes them as verb-dispatched JSON endpoints, inheriting the
4// wire format, error shape and self-description from nx_service.
5//
6// THIS IS WHAT "MCP / API / AGENTIC / WORKFLOW READY" MEANS CONCRETELY.
7// MCP-ready: a single fork-exec organ with a stable name + argv verb
8// contract, registerable in tool_allowlist.conf and via
9// /api/tools/register (fixed-arg pinning not needed -- the
10// verb IS the first arg, and every handler is read-only).
11// API-ready: pure stdout JSON with a versioned envelope + CORS-safe
12// structured errors, drivable behind an HTTP route.
13// agentic-ready: the `describe` verb emits this facade's own verb catalog
14// with arg shapes -- an agent discovers what it can call,
15// the service-level analog of MCP tools/list.
16// workflow-ready: every verb is a PURE function of its arguments and returns
17// a self-contained JSON object, so a pipeline can fan a list
18// of ingredients through `ingredient.class` and feed the
19// survivors into `formula.assess` with no shared state.
20// scalable: stateless; a call is a fork-exec; concurrency is free.
21//
22// USAGE: nx_gtm_svc <verb> [args...]
23// describe
24// ingredient.info <id>
25// formula.assess <retail_cents> <margin_permil> <servings> <fmt 1|2>
26// claims.classify <sentence...> (joins remaining argv)
27// jurisdiction.path <market> <ingredient>
28// channel.best <ingredient>
29// pathway.programme <prior 0|1|2>
30// ethics.rank <jurisdiction>
31// supply.effective <base> <spend_k>
32//
33// The heavy lifting stays in the libraries; this file is dispatch + shape.
34// It adds NO new domain logic -- every number comes from a gated module -- so
35// the facade cannot drift from the science it exposes.
36//
37// genealogy_id: supplement_gtm + service_infrastructure
38
39import "nx_syscalls.nx"
40import "nx_service.nx"
41import "nx_grounding.nx"
42import "nx_supp_ingredient.nx"
43import "nx_supp_formula.nx"
44import "nx_supp_claims.nx"
45import "nx_supp_jurisdiction.nx"
46import "nx_supp_channel.nx"
47import "nx_drug_pathway.nx"
48import "nx_trial_ethics.nx"
49import "nx_supply_risk.nx"
50const GTM_MAGIC_1024: i64 = 1024
51const GTM_MAGIC_8192: i64 = 8192
52
53const GTM_SVC: *u8 = "gtm" as *u8
54
55// ===== describe: agentic self-discovery ===============================
56//
57// Emits the verb catalog so an agent can enumerate this facade the way it
58// would enumerate MCP tools. Data-driven in spirit: one line per verb, each
59// naming its args, so adding a verb is a localised edit.
60
61func gtm_describe(j: *NxJson) -> i64 {
62 nsvc_ok_open(j, GTM_SVC, "describe" as *u8)
63 nj_kv_str(j, "service" as *u8, "supplement + drug go-to-market" as *u8)
64 nj_comma(j)
65 nj_kv_int(j, "verb_count" as *u8, 8)
66 nj_comma(j)
67 nj_key(j, "verbs" as *u8)
68 nj_puts(j, "[" as *u8)
69 nj_puts(j, "{\"verb\":\"ingredient.info\",\"args\":[\"id:int\"]}," as *u8)
70 nj_puts(j, "{\"verb\":\"formula.assess\",\"args\":[\"retail_cents:int\",\"margin_permil:int\",\"servings:int\",\"fmt:int\"]}," as *u8)
71 nj_puts(j, "{\"verb\":\"claims.classify\",\"args\":[\"sentence:string\"]}," as *u8)
72 nj_puts(j, "{\"verb\":\"jurisdiction.path\",\"args\":[\"market:int\",\"ingredient:int\"]}," as *u8)
73 nj_puts(j, "{\"verb\":\"channel.best\",\"args\":[\"ingredient:int\"]}," as *u8)
74 nj_puts(j, "{\"verb\":\"pathway.programme\",\"args\":[\"prior:int\"]}," as *u8)
75 nj_puts(j, "{\"verb\":\"ethics.rank\",\"args\":[\"jurisdiction:int\"]}," as *u8)
76 nj_puts(j, "{\"verb\":\"supply.effective\",\"args\":[\"base:int\",\"spend_k:int\"]}" as *u8)
77 nj_puts(j, "]" as *u8)
78 nsvc_ok_close_g(j, GND_ASSERTED)
79 return 0
80}
81
82// ===== ingredient.info ================================================
83
84func gtm_ingredient_info(j: *NxJson, id: i64) -> i64 {
85 if si_valid_id(id) != 1 {
86 nsvc_error(j, GTM_SVC, "ingredient.info" as *u8, NSVC_ERR_NOT_FOUND, "no such ingredient id" as *u8)
87 return 0
88 }
89 nsvc_ok_open(j, GTM_SVC, "ingredient.info" as *u8)
90 nj_kv_int(j, "id" as *u8, id)
91 nj_comma(j)
92 nj_kv_str(j, "name" as *u8, si_name(id))
93 nj_comma(j)
94 nj_kv_int(j, "class" as *u8, si_class(id))
95 nj_comma(j)
96 nj_kv_bool(j, "lawful_dietary" as *u8, si_is_lawful_dietary(id))
97 nj_comma(j)
98 nj_kv_int(j, "clinical_dose_mg" as *u8, si_clinical_dose_mg(id))
99 nj_comma(j)
100 nj_kv_int(j, "cost_cents_per_kg" as *u8, si_cost_cents_per_kg(id))
101 nj_comma(j)
102 nj_kv_bool(j, "cost_indicative" as *u8, si_cost_is_indicative(id))
103 nsvc_ok_close_g(j, GND_ASSERTED)
104 return 0
105}
106
107// ===== formula.assess (the reference stack) ===========================
108//
109// Assesses the canonical 7-ingredient botanical/amino stack at clinical doses
110// for a given price/margin/format. A fuller API would accept an ingredient
111// list; the reference stack is the workflow's default subject.
112
113func gtm_build_ref_stack() -> *NxSuppFormula {
114 let f: *NxSuppFormula = nx_supp_formula_new()
115 sf_set_clinical(f, SI_L_CITRULLINE)
116 sf_set_clinical(f, SI_PYCNOGENOL)
117 sf_set_clinical(f, SI_ASHWAGANDHA)
118 sf_set_clinical(f, SI_MACA)
119 sf_set_clinical(f, SI_TRIBULUS)
120 sf_set_clinical(f, SI_PANAX_GINSENG)
121 sf_set_clinical(f, SI_GINKGO)
122 return f
123}
124
125func gtm_formula_assess(j: *NxJson, retail: i64, margin: i64, servings: i64, fmt: i64) -> i64 {
126 if retail <= 0 {
127 nsvc_error(j, GTM_SVC, "formula.assess" as *u8, NSVC_ERR_BAD_ARGS, "retail_cents must be positive" as *u8)
128 return 0
129 }
130 if servings <= 0 {
131 nsvc_error(j, GTM_SVC, "formula.assess" as *u8, NSVC_ERR_BAD_ARGS, "servings must be positive" as *u8)
132 return 0
133 }
134 let f: *NxSuppFormula = gtm_build_ref_stack()
135 let shell: i64 = SI_CAP_00
136 let verdict: i64 = sf_regulatory_verdict(f)
137 let caps: i64 = sf_capsules_needed(f, shell)
138 let cogs: i64 = sf_cogs_per_bottle_cents(f, servings, fmt, shell)
139 let margin_got: i64 = sf_gross_margin_permil(retail, cogs)
140 let budget: i64 = sf_actives_budget_per_serving_cents(f, retail, margin, servings, fmt, shell)
141 nsvc_ok_open(j, GTM_SVC, "formula.assess" as *u8)
142 nj_kv_int(j, "regulatory_verdict" as *u8, verdict)
143 nj_comma(j)
144 nj_kv_bool(j, "marketable" as *u8, sf_is_marketable(f))
145 nj_comma(j)
146 nj_kv_int(j, "serving_mass_mg" as *u8, sf_total_mass_mg(f))
147 nj_comma(j)
148 nj_kv_int(j, "capsules_size00" as *u8, caps)
149 nj_comma(j)
150 nj_kv_int(j, "bulkiest_ingredient" as *u8, sf_bulkiest_ingredient(f))
151 nj_comma(j)
152 nj_kv_int(j, "cogs_per_bottle_cents" as *u8, cogs)
153 nj_comma(j)
154 nj_kv_int(j, "gross_margin_permil" as *u8, margin_got)
155 nj_comma(j)
156 nj_kv_int(j, "actives_budget_per_serving_cents" as *u8, budget)
157 nj_comma(j)
158 nj_kv_int(j, "fairy_dusted_count" as *u8, sf_count_fairy_dusted(f))
159 nsvc_ok_close_g(j, GND_ASSERTED)
160 return 0
161}
162
163// ===== claims.classify ================================================
164//
165// Joins argv[2..] into one sentence (a marketing claim is many words) and
166// classifies it. Rebuilds the sentence into a scratch buffer with spaces.
167
168func gtm_join_args(argc: i64, argv: *i64, start: i64, out: *u8, cap: i64) -> i64 {
169 var w: i64 = 0
170 var k: i64 = start
171 while k < argc {
172 if w > 0 {
173 if w < cap { out[w] = 32 as u8; w = w + 1 }
174 }
175 let s: *u8 = argv[k] as *u8
176 var i: i64 = 0
177 while s[i] != (0 as u8) {
178 if w < cap { out[w] = s[i]; w = w + 1 }
179 i = i + 1
180 }
181 k = k + 1
182 }
183 if w < cap { out[w] = 0 as u8 } else { out[cap - 1] = 0 as u8 }
184 return w
185}
186
187func gtm_claims_classify(j: *NxJson, sentence: *u8) -> i64 {
188 if sentence[0] == (0 as u8) {
189 nsvc_error(j, GTM_SVC, "claims.classify" as *u8, NSVC_ERR_BAD_ARGS, "empty claim sentence" as *u8)
190 return 0
191 }
192 let v: i64 = cl_classify(sentence)
193 nsvc_ok_open(j, GTM_SVC, "claims.classify" as *u8)
194 nj_kv_str(j, "sentence" as *u8, sentence)
195 nj_comma(j)
196 nj_kv_int(j, "classification" as *u8, v)
197 nj_comma(j)
198 nj_kv_bool(j, "lawful" as *u8, cl_is_lawful(sentence))
199 nj_comma(j)
200 nj_kv_bool(j, "disclaimer_required" as *u8, cl_requires_disclaimer(sentence))
201 nj_comma(j)
202 nj_kv_int(j, "fda_notification_days" as *u8, cl_notification_days(sentence))
203 nsvc_ok_close_g(j, GND_ASSERTED)
204 return 0
205}
206
207// ===== jurisdiction.path ==============================================
208
209func gtm_jurisdiction_path(j: *NxJson, market: i64, ingredient: i64) -> i64 {
210 if sj_valid(market) != 1 {
211 nsvc_error(j, GTM_SVC, "jurisdiction.path" as *u8, NSVC_ERR_NOT_FOUND, "no such market" as *u8)
212 return 0
213 }
214 if si_valid_id(ingredient) != 1 {
215 nsvc_error(j, GTM_SVC, "jurisdiction.path" as *u8, NSVC_ERR_NOT_FOUND, "no such ingredient" as *u8)
216 return 0
217 }
218 nsvc_ok_open(j, GTM_SVC, "jurisdiction.path" as *u8)
219 nj_kv_str(j, "market" as *u8, sj_name(market))
220 nj_comma(j)
221 nj_kv_str(j, "regime" as *u8, sj_regime(market))
222 nj_comma(j)
223 nj_kv_int(j, "pathway" as *u8, sj_pathway(market, ingredient))
224 nj_comma(j)
225 nj_kv_bool(j, "premarket_required" as *u8, sj_premarket_required(market, ingredient))
226 nj_comma(j)
227 nj_kv_bool(j, "sellable" as *u8, sj_is_sellable(market, ingredient))
228 nj_comma(j)
229 nj_kv_int(j, "review_days" as *u8, sj_review_days(market))
230 nj_comma(j)
231 nj_kv_int(j, "dossier_cost_cents" as *u8, sj_dossier_cost_cents(market))
232 nj_comma(j)
233 nj_kv_bool(j, "sanctions_review_separate" as *u8, sj_requires_separate_sanctions_review(market))
234 nsvc_ok_close_g(j, GND_ASSERTED)
235 return 0
236}
237
238// ===== channel.best ===================================================
239
240func gtm_channel_best(j: *NxJson, ingredient: i64) -> i64 {
241 if si_valid_id(ingredient) != 1 {
242 nsvc_error(j, GTM_SVC, "channel.best" as *u8, NSVC_ERR_NOT_FOUND, "no such ingredient" as *u8)
243 return 0
244 }
245 let best: i64 = ch_best(ingredient)
246 nsvc_ok_open(j, GTM_SVC, "channel.best" as *u8)
247 nj_kv_str(j, "name" as *u8, si_name(ingredient))
248 nj_comma(j)
249 nj_kv_int(j, "best_channel" as *u8, best)
250 nj_comma(j)
251 nj_kv_str(j, "channel_name" as *u8, ch_name(best))
252 nj_comma(j)
253 nj_kv_bool(j, "requires_prescriber" as *u8, ch_requires_prescriber(best))
254 nj_comma(j)
255 nj_kv_bool(j, "has_lawful_route" as *u8, ch_has_any_route(ingredient))
256 nj_comma(j)
257 nj_kv_int(j, "channels_open" as *u8, ch_count_open(ingredient))
258 nsvc_ok_close_g(j, GND_ASSERTED)
259 return 0
260}
261
262// ===== pathway.programme ==============================================
263
264func gtm_pathway_programme(j: *NxJson, prior: i64) -> i64 {
265 nsvc_ok_open(j, GTM_SVC, "pathway.programme" as *u8)
266 nj_kv_int(j, "prior_data" as *u8, prior)
267 nj_comma(j)
268 nj_kv_bool(j, "reliance_available" as *u8, dp_reliance_available(prior))
269 nj_comma(j)
270 nj_kv_int(j, "programme_cost_k" as *u8, dp_programme_cost_k(prior))
271 nj_comma(j)
272 nj_kv_int(j, "programme_months" as *u8, dp_programme_months(prior))
273 nj_comma(j)
274 nj_kv_int(j, "reliance_saving_k" as *u8, dp_reliance_saving_k(prior))
275 nj_comma(j)
276 nj_kv_int(j, "cheapest_phase1_site" as *u8, dp_cheapest_site(DP_PHASE1))
277 nsvc_ok_close_g(j, GND_ASSERTED)
278 return 0
279}
280
281// ===== ethics.rank ====================================================
282
283func gtm_ethics_rank(j: *NxJson, jur: i64) -> i64 {
284 if te_valid(jur) != 1 {
285 nsvc_error(j, GTM_SVC, "ethics.rank" as *u8, NSVC_ERR_NOT_FOUND, "no such jurisdiction" as *u8)
286 return 0
287 }
288 nsvc_ok_open(j, GTM_SVC, "ethics.rank" as *u8)
289 nj_kv_str(j, "name" as *u8, te_name(jur))
290 nj_comma(j)
291 nj_kv_int(j, "integrity_score" as *u8, te_integrity_score(jur))
292 nj_comma(j)
293 nj_kv_int(j, "rank" as *u8, te_rank_of(jur))
294 nj_comma(j)
295 nj_kv_int(j, "cpi" as *u8, te_cpi(jur))
296 nj_comma(j)
297 nj_kv_bool(j, "mandatory_disclosure" as *u8, te_mandatory_results_disclosure(jur))
298 nj_comma(j)
299 nj_kv_bool(j, "no_fault_injury_cover" as *u8, te_no_fault_injury_compensation(jur))
300 nj_comma(j)
301 nj_kv_bool(j, "data_portable_to_fda_ema" as *u8, te_data_portable(jur))
302 nsvc_ok_close_g(j, GND_ASSERTED)
303 return 0
304}
305
306// ===== supply.effective ===============================================
307
308func gtm_supply_effective(j: *NxJson, base: i64, spend_k: i64) -> i64 {
309 if sr_valid(base) != 1 {
310 nsvc_error(j, GTM_SVC, "supply.effective" as *u8, NSVC_ERR_NOT_FOUND, "no such manufacturing base" as *u8)
311 return 0
312 }
313 if spend_k <= 0 {
314 nsvc_error(j, GTM_SVC, "supply.effective" as *u8, NSVC_ERR_BAD_ARGS, "spend_k must be positive" as *u8)
315 return 0
316 }
317 nsvc_ok_open(j, GTM_SVC, "supply.effective" as *u8)
318 nj_kv_str(j, "base" as *u8, sr_name(base))
319 nj_comma(j)
320 nj_kv_int(j, "sticker_cost_k" as *u8, sr_sticker_cost_k(base, spend_k))
321 nj_comma(j)
322 nj_kv_int(j, "audit_burden_k" as *u8, sr_audit_burden_k(base))
323 nj_comma(j)
324 nj_kv_int(j, "expected_delay_cost_k" as *u8, sr_expected_delay_cost_k(base))
325 nj_comma(j)
326 nj_kv_int(j, "effective_cost_k" as *u8, sr_effective_cost_k(base, spend_k))
327 nj_comma(j)
328 nj_kv_int(j, "saving_retained_permil" as *u8, sr_saving_retained_permil(base, spend_k))
329 nj_comma(j)
330 nj_kv_bool(j, "eu_gmp_available" as *u8, sr_eu_gmp_available(base))
331 nsvc_ok_close_g(j, GND_ASSERTED)
332 return 0
333}
334
335// ===== The dispatcher =================================================
336//
337// One entry point, verb in argv[1], args in argv[2..]. Unknown verb is a
338// structured 404, not a crash -- an agent gets a machine-readable miss it can
339// recover from. Returns the process exit code: 0 on ok, non-zero on error, so
340// a shell/workflow can branch on it too.
341
342func gtm_dispatch(argc: i64, argv: *i64, j: *NxJson) -> i64 {
343 let verb: *u8 = nsvc_arg(argc, argv, 1)
344 if verb[0] == (0 as u8) {
345 gtm_describe(j)
346 return 0
347 }
348 if nsvc_streq(verb, "describe" as *u8) == 1 { gtm_describe(j); return 0 }
349 if nsvc_streq(verb, "ingredient.info" as *u8) == 1 {
350 gtm_ingredient_info(j, nsvc_arg_int(argc, argv, 2))
351 return 0
352 }
353 if nsvc_streq(verb, "formula.assess" as *u8) == 1 {
354 gtm_formula_assess(j, nsvc_arg_int(argc, argv, 2), nsvc_arg_int(argc, argv, 3), nsvc_arg_int(argc, argv, 4), nsvc_arg_int(argc, argv, 5))
355 return 0
356 }
357 if nsvc_streq(verb, "claims.classify" as *u8) == 1 {
358 let sbuf: *u8 = sys_mmap(GTM_MAGIC_1024)
359 gtm_join_args(argc, argv, 2, sbuf, GTM_MAGIC_1024)
360 gtm_claims_classify(j, sbuf)
361 return 0
362 }
363 if nsvc_streq(verb, "jurisdiction.path" as *u8) == 1 {
364 gtm_jurisdiction_path(j, nsvc_arg_int(argc, argv, 2), nsvc_arg_int(argc, argv, 3))
365 return 0
366 }
367 if nsvc_streq(verb, "channel.best" as *u8) == 1 {
368 gtm_channel_best(j, nsvc_arg_int(argc, argv, 2))
369 return 0
370 }
371 if nsvc_streq(verb, "pathway.programme" as *u8) == 1 {
372 gtm_pathway_programme(j, nsvc_arg_int(argc, argv, 2))
373 return 0
374 }
375 if nsvc_streq(verb, "ethics.rank" as *u8) == 1 {
376 gtm_ethics_rank(j, nsvc_arg_int(argc, argv, 2))
377 return 0
378 }
379 if nsvc_streq(verb, "supply.effective" as *u8) == 1 {
380 gtm_supply_effective(j, nsvc_arg_int(argc, argv, 2), nsvc_arg_int(argc, argv, 3))
381 return 0
382 }
383 nsvc_error(j, GTM_SVC, verb, NSVC_ERR_UNKNOWN_VERB, "unknown verb; call describe for the catalog" as *u8)
384 return 1
385}
386
387func main(argc: i64, argv: *i64) -> i64 {
388 let j: *NxJson = nx_json_new(GTM_MAGIC_8192)
389 let rc: i64 = gtm_dispatch(argc, argv, j)
390 nj_flush(j)
391 return rc
392}