code wiki / (root) / nx_gtm_svc.nx

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}