PageSourceSearch

https://app.bioprofile.com/app/data/questionnaire.js

js bioprofile.com collected 2026-10-10 00:47:00 UTC 34,469 bytes, 412 lines download raw bytes

1/* ============================================================================
2   BioProfile Analysis - Full Questionnaire definition (v1).
3   Source of truth for the runnable questionnaire. Derived from
4   ../../analysis/questionnaire-item-bank.md. Loaded as JS (not JSON) so it runs
5   from file:// with no server, matching the design-system convention.
6
7   LICENCE POSTURE (important): this runnable build ships LICENCE-CLEAN wording.
8   - WHO-5 items are the free/open official wording (verbatim).
9   - Every other item is CUSTOM wording modelled on the validated instrument
10     (labelled `source: "derive:<X>"`), so nothing copyright-gated ships before
11     its licence is cleared. On clearing PSS-10 / FAS / FINDRISC / MEDAS the
12     wording + scoring swap in without changing the engine contract. See
13     ../../analysis/science-backing.md section 6 and the Emre briefing.
14
15   Each scored option carries `p` = imbalance points (0..4), already in the
16   imbalance direction (reverse items are pre-inverted here, so the engine never
17   reverse-scores). Positive-behaviour items (aging) carry `h` = health points.
18   WHO-5 options carry `w` = wellbeing points (0..5). Red-flag options carry
19   `flag`. FINDRISC risk items carry `p` used as risk points by the engine.
20   ========================================================================== */
21globalThis.BP_Q = {
22  version: "1.1.0",
23  title: "BioProfile Analysis",
24  subtitle: "Analyse your health, without diagnosing it.",
25  /* Sections are the wizard steps, in order. `gate` hides a whole section. */
26  sections: [
27    {
28      id: "intro", kind: "intro", title: "Before we start",
29      body: "This takes about 10 to 15 minutes. It builds a picture of where your body may be out of balance, to guide you and your coach. It is an analysis, not a diagnosis, and only a doctor can diagnose. Your answers are private (GDPR)."
30    },
31
32    /* ---- Stage 0 preamble: sets gating context + risk-tool inputs -------- */
33    {
34      id: "about", kind: "questions", title: "About you",
35      note: "These help us ask the right questions. They do not change your scores.",
36      items: [
37        { key: "pre.goal", basis: "preamble", input: "single", prompt: "What do you most want to improve right now?", options: [
38          { value: "more_energy", label: "More energy" }, { value: "better_sleep", label: "Better sleep" },
39          { value: "lose_weight", label: "Lose weight" }, { value: "sharper_focus", label: "Sharper focus" },
40          { value: "healthy_aging", label: "Healthy aging / prevention" }, { value: "gut_comfort", label: "Gut comfort" },
41          { value: "hormonal_balance", label: "Hormonal balance" }, { value: "general_vitality", label: "General vitality" },
42          /* T553 (Jesse 2026-10-06): the one list of goals (app/data/health-goals.js, migration 191), five added; offered
43             only while their switch is on (the end of this file), so the live scan never offers a goal before its release */
44          { value: "build_muscle", label: "Build muscle and strength", gate: "new_goals" }, { value: "less_stress", label: "Less stress", gate: "new_goals" },
45          { value: "strong_resistance", label: "Strong resistance", gate: "new_goals" }, { value: "heart_health", label: "Heart and blood vessels", gate: "new_goals" },
46          { value: "sport_recovery", label: "Sport and recovery", gate: "new_goals" } ] },
47        { key: "pre.sex", basis: "preamble", input: "single", prompt: "What was your biological gender at birth?",
48          help: "We ask because reference ranges and some questions differ by biology. Prefer not to say is fine - it just makes a few results a little less precise.", options: [
49          { value: "f", label: "Female" }, { value: "m", label: "Male" }, { value: "x", label: "Prefer not to say" } ] },
50        /* Year of birth (Jesse 2026-07-18): asked as a 4-digit year, stored with
51           date-of-birth semantics (dob = YYYY-01-01, precision "year"; full date
52           addable later, migration 003). The ENGINE still consumes age - it is
53           derived from the year at scoring time (server + client preview), and
54           legacy stored ages (values < 200) keep working via the same mapping. */
55        { key: "pre.birthdate", basis: "preamble", input: "number", numKind: "yob", prompt: "Your year of birth", suffix: "", help: "The year is enough for now; you can add your full birth date later.",
56          min: new Date().getFullYear() - 110, max: new Date().getFullYear() - 16 },
57        { key: "pre.stage_f", basis: "preamble", input: "single", gate: { sex: "f" }, prompt: "Which best describes your current stage?", options: [
58          { value: "premenopause", label: "Before menopause (regular cycles)" }, { value: "perimenopause", label: "Perimenopause (cycles changing)" },
59          { value: "menopause", label: "Menopause" }, { value: "postmenopause", label: "Postmenopause" }, { value: "unsure", label: "Not sure" } ] },
60        { key: "pre.stage_m", basis: "preamble", input: "single", gate: { sex: "m" }, prompt: "How has your vitality been over the last year?", options: [
61          { value: "stable", label: "Stable" }, { value: "some_dip", label: "Some dip" }, { value: "marked_dip", label: "A marked dip" } ] },
62        { key: "pre.glp1", basis: "preamble", input: "single", prompt: "Do you currently use a GLP-1 medication (e.g. semaglutide)?", options: [
63          { value: "no", label: "No" }, { value: "yes", label: "Yes" } ] },
64        { key: "pre.height_cm", basis: "preamble", input: "number", prompt: "Your height", suffix: "cm", min: 120, max: 230 },
65        { key: "pre.weight_kg", basis: "preamble", input: "number", prompt: "Your weight", suffix: "kg", min: 35, max: 300 },
66        { key: "pre.waist_cm", basis: "preamble", input: "number", skippable: true, prompt: "Your waist at the navel", suffix: "cm", help: "Measure around your middle, at the belly button. Not sure? You can skip this - it only sharpens one result.", min: 50, max: 200 }
67      ]
68    },
69
70    /* ---- Red-flag safety battery: overrides scoring --------------------- */
71    {
72      id: "safety", kind: "questions", title: "A last few safety checks",
73      note: "Almost done. These tell us when to point you to a doctor rather than give you a plan. If anything here is a yes, that is what matters, not a score.",
74      items: [
75        { key: "rf.chest_pain", basis: "redflag", input: "single", prompt: "Do you get chest pain or pressure when you exert yourself?", options: [ { value: "no", label: "No" }, { value: "yes", label: "Yes", flag: "chest_pain_exertion" } ] },
76        { key: "rf.headache", basis: "redflag", input: "single", prompt: "Have you had a sudden, severe, worst-ever headache?", options: [ { value: "no", label: "No" }, { value: "yes", label: "Yes", flag: "thunderclap_headache" } ] },
77        { key: "rf.neuro", basis: "redflag", input: "single", prompt: "Any sudden face drooping, arm weakness, or trouble speaking?", options: [ { value: "no", label: "No" }, { value: "yes", label: "Yes", flag: "neuro_deficit" } ] },
78        { key: "rf.weight_loss", basis: "redflag", input: "single", prompt: "Have you lost weight recently without trying?", options: [ { value: "no", label: "No" }, { value: "yes", label: "Yes", flag: "unexplained_weight_loss" } ] },
79        { key: "rf.blood_stool", basis: "redflag", input: "single", prompt: "Have you noticed blood in your stool or unex
79plained rectal bleeding?", options: [ { value: "no", label: "No" }, { value: "yes", label: "Yes", flag: "rectal_bleeding" } ] },
80        { key: "rf.other_bleeding", basis: "redflag", input: "single", prompt: "Any other unexplained bleeding (in urine, coughing blood, or bleeding after menopause)?", options: [ { value: "no", label: "No" }, { value: "yes", label: "Yes", flag: "unexplained_bleeding" } ] },
81        { key: "rf.lump", basis: "redflag", input: "single", prompt: "Any new unexplained lump, or a change in a breast?", options: [ { value: "no", label: "No" }, { value: "yes", label: "Yes", flag: "new_lump" } ] },
82        { key: "rf.swallow", basis: "redflag", input: "single", prompt: "Any new or ongoing difficulty swallowing?", options: [ { value: "no", label: "No" }, { value: "yes", label: "Yes", flag: "dysphagia" } ] },
83        { key: "rf.faint", basis: "redflag", input: "single", prompt: "Have you fainted or blacked out unexpectedly?", options: [ { value: "no", label: "No" }, { value: "yes", label: "Yes", flag: "syncope" } ] },
84        { key: "rf.mood", basis: "redflag", input: "single", prompt: "In the last 2 weeks, have you had thoughts that you would be better off dead, or of hurting yourself?",
85          help: "If yes, we will show you support right away.", options: [ { value: "no", label: "No" }, { value: "yes", label: "Yes", flag: "suicidal_ideation" } ] }
86      ]
87    },
88
89    /* ---- WHO-5 wellbeing (verbatim, free/open) -------------------------- */
90    {
91      id: "wellbeing", kind: "questions", title: "Your wellbeing", profile: "who5",
92      note: "Over the last two weeks...",
93      items: who5Item("who5.cheerful", "I have felt cheerful and in good spirits") .concat(
94             who5Item("who5.calm", "I have felt calm and relaxed"),
95             who5Item("who5.active", "I have felt active and vigorous"),
96             who5Item("who5.rested", "I woke up feeling fresh and rested"),
97             who5Item("who5.interest", "My daily life has been filled with things that interest me"))
98    },
99
100    /* ---- Gated mood step (only if WHO-5 <= 50%) ------------------------- */
101    {
102      id: "mood", kind: "questions", title: "A closer look", gate: { whenWho5AtMost: 50 },
103      note: "Over the last two weeks, how often have you been bothered by...",
104      items: [
105        mood4("mood.phq2_low", "Feeling down, depressed, or hopeless"),
106        mood4("mood.phq2_anhedonia", "Little interest or pleasure in doing things"),
107        mood4("mood.gad2_nervous", "Feeling nervous, anxious, or on edge"),
108        mood4("mood.gad2_worry", "Not being able to stop or control worrying")
109      ]
110    },
111
112    /* ---- A1 Sleep & Recovery (derive:ISI) ------------------------------ */
113    {
114      id: "sleep", kind: "questions", title: "Sleep & recovery", profile: "sleep", note: "Over the last two weeks...",
115      items: [
116        s4("sleep.onset_latency", "How long does it usually take you to fall asleep?", ["Under 15 min","15 to 30 min","30 to 60 min","Over an hour","I rarely get to sleep"], [0,1,2,3,4]),
117        s4("sleep.night_waking", "How often do you wake in the night and struggle to get back to sleep?", ["Rarely","Occasionally","Some nights","Most nights","Every night"], [0,1,2,3,4]),
118        s4("sleep.rested_waking", "How often do you wake feeling rested?", ["Every day","Most days","Some days","Rarely","Never"], [0,1,2,3,4]),
119        s4("sleep.satisfaction", "Overall, how satisfied are you with your sleep?", ["Very satisfied","Satisfied","Neutral","Dissatisfied","Very dissatisfied"], [0,1,2,3,4]),
120        s4("sleep.daytime_interference", "How much does poor sleep interfere with your day?", ["Not at all","A little","Somewhat","Much","Very much"], [0,1,2,3,4])
121      ]
122    },
123
124    /* ---- A3 Energy & Vitality (derive:FAS) ----------------------------- */
125    {
126      id: "energy", kind: "questions", title: "Energy & vitality", profile: "energy",
127      items: [
128        s4("energy.daily_stability", "How steady is your energy across the day?", ["Steady","Mostly steady","Up and down","A clear afternoon dip","Drained most days"], [0,1,2,3,4]),
129        s4("energy.midday_dip", "How often do you hit an energy crash during the day?", ["Rarely","Weekly","A few times a week","Most days","Daily"], [0,1,2,3,4]),
130        s4("energy.stimulant_reliance", "How much do you rely on caffeine or stimulants to get through?", ["None","A little","Moderate","A lot","Cannot function without"], [0,1,2,3,4]),
131        s4("energy.physical_tire", "How easily do you tire physically doing everyday things?", ["Not easily","Slightly","Moderately","Easily","Very easily"], [0,1,2,3,4]),
132        s4("energy.motivation", "How often do you feel too worn out to do things you want to?", ["Rarely","Occasionally","Sometimes","Often","Almost always"], [0,1,2,3,4])
133      ]
134    },
135
136    /* ---- A2 Stress Resilience (derive:PSS, licence-clean variant) ------- */
137    {
138      id: "stress", kind: "questions", title: "Stress & resilience", profile: "stress", note: "In the last month...",
139      items: [
140        s4("stress.control", "How often have you felt unable to control the important things in your life?", ["Never","Almost never","Sometimes","Fairly often","Very often"], [0,1,2,3,4]),
141        s4("stress.overwhelm", "How often have you felt difficulties were piling up too high to overcome?", ["Never","Almost never","Sometimes","Fairly often","Very often"], [0,1,2,3,4]),
142        s4("stress.coping", "How often have you felt confident about handling your problems?", ["Very often","Fairly often","Sometimes","Almost never","Never"], [0,1,2,3,4]),
143        s4("stress.on_top", "How often have you felt that things were going your way?", ["Very often","Fairly often","Sometimes","Almost never","Never"], [0,1,2,3,4]),
144        s4("stress.switch_off", "How hard is it to switch off and relax at the end of the day?", ["Easy","Fairly easy","Somewhat hard","Hard","Very hard"], [0,1,2,3,4])
145      ]
146    },
147
148    /* ---- A4 Gut Health (derive) ---------------------------------------- */
149    {
150      id: "gut", kind: "questions", title: "Digestion & gut", profile: "gut", note: "Over the last two weeks...",
151      items: [
152        s4("gut.stool_form", "Which best matches your usual stool?", ["Hard lumps","Lumpy and firm","Cracked but formed","Smooth and soft","Soft blobs","Mushy","Watery"], [3,2,0,0,1,2,3]),
153        s4("gut.pain_frequency", "How often do you get abdominal pain or discomfort?", ["Rarely","About 1 day a week","2 to 3 days a week","Most days","Daily"], [0,1,2,3,4]),
154        s4("gut.bloating", "How troubled are you by bloating or distension?", ["Not at all","Mild","Moderate","Quite a bit","Severe"], [0,1,2,3,4]),
155        s4("gut.regularity", "How regular are your bowel movements?", ["Very regular","Mostly regular","Irregular","Often constipated or loose","Very irregular"], [0,1,2,3,4]),
156        s4("gut.reflux_indigestion", "How often do you get reflux, heartburn, or indigestion?", ["Rarely","Monthly","Weekly","Several times a week","Daily"], [0,1,2,3,4]),
157        s4("gut.food_relation", "How often do symptoms follow eating specific foods?", ["Never","Rarely","Sometimes","Often","Almost always"], [0,1,2,3,4])
158      ]
159    },
160
161    /* ---- C Brain Performance (derive:CFQ, PERCEIVED) ------------------- */
162    {
163      id: "brain", kind: "questions", title: "Focus & mind", profile: "brain", note: "This is about how your thinking feels to you (perceived), over the last two weeks.",
164      items: [
165        s4("brain.clarity_fog", "How often do you feel mentally fogg
165y or unclear?", ["Rarely","Occasionally","Sometimes","Often","Most days"], [0,1,2,3,4]),
166        s4("brain.focus", "How often do you lose focus or get easily distracted?", ["Rarely","Occasionally","Sometimes","Often","Almost constantly"], [0,1,2,3,4]),
167        s4("brain.memory_lapses", "How often do you forget names, words, or why you entered a room?", ["Rarely","Occasionally","Sometimes","Often","Daily"], [0,1,2,3,4]),
168        s4("brain.mental_fatigue", "How often does thinking feel effortful or tiring?", ["Rarely","Occasionally","Sometimes","Often","Most days"], [0,1,2,3,4])
169      ]
170    },
171
172    /* ---- B Hormonal Balance (cohort-gated) ----------------------------- */
173    {
174      id: "hormonal_f", kind: "questions", title: "Hormonal balance", profile: "hormonal",
175      gate: { sex: "f", stageIn: ["perimenopause","menopause","postmenopause","unsure"] },
176      note: "How troubled have you been by...",
177      items: [
178        s4("hormonal.hot_flushes", "Hot flushes or night sweats?", ["None","Mild","Moderate","Marked","Severe"], [0,1,2,3,4]),
179        s4("hormonal.vaginal_dryness", "Vaginal dryness or discomfort?", ["None","Mild","Moderate","Marked","Severe"], [0,1,2,3,4]),
180        s4("hormonal.mood_swings_f", "Irritability or mood swings?", ["None","Mild","Moderate","Marked","Severe"], [0,1,2,3,4]),
181        s4("hormonal.sleep_hormonal", "Sleep disturbed by night sweats or hormonal changes?", ["None","Mild","Moderate","Marked","Severe"], [0,1,2,3,4])
182      ]
183    },
184    {
185      id: "hormonal_m", kind: "questions", title: "Vitality", profile: "hormonal", gate: { sex: "m" },
186      note: "Over the last year...",
187      items: [
188        s4("hormonal.libido_m", "How would you describe your sex drive?", ["Normal","Slightly lower","Moderately lower","Much lower","Absent"], [0,1,2,3,4]),
189        s4("hormonal.vitality_m", "How is your drive, strength, and get-up-and-go?", ["Unchanged","Slightly lower","Moderately lower","Much lower","Markedly lower"], [0,1,2,3,4]),
190        s4("hormonal.mood_m", "How often do you feel low, flat, or irritable?", ["Rarely","Occasionally","Sometimes","Often","Most days"], [0,1,2,3,4])
191      ]
192    },
193
194    /* ---- D1 Metabolic Health (risk: FINDRISC-style) -------------------- */
195    {
196      id: "metabolic", kind: "questions", title: "Metabolic risk", profile: "metabolic",
197      note: "This is a risk picture, not a symptom score. A blood test confirms it.",
198      items: [
199        { key: "metabolic.activity_findrisc", profile: "metabolic", basis: "risk", input: "single", prompt: "Do you do at least 30 minutes of physical activity most days?", options: [ { value: "yes", label: "Yes", p: 0 }, { value: "no", label: "No", p: 2 } ] },
200        { key: "metabolic.fruitveg", profile: "metabolic", basis: "risk", input: "single", prompt: "How often do you eat vegetables or fruit?", options: [ { value: "daily", label: "Every day", p: 0 }, { value: "not_daily", label: "Not every day", p: 1 } ] },
201        { key: "metabolic.bp_meds", profile: "metabolic", basis: "risk", input: "single", prompt: "Have you ever taken blood-pressure medication regularly?", options: [ { value: "no", label: "No", p: 0 }, { value: "yes", label: "Yes", p: 2 } ] },
202        { key: "metabolic.high_glucose_history", profile: "metabolic", basis: "risk", input: "single", prompt: "Have you ever been told you had high blood sugar (a test, or in pregnancy)?", options: [ { value: "no", label: "No", p: 0 }, { value: "yes", label: "Yes", p: 5 } ] },
203        { key: "metabolic.family_diabetes", profile: "metabolic", basis: "risk", input: "single", prompt: "Family history of diabetes?", options: [ { value: "none", label: "None", p: 0 }, { value: "distant", label: "Grandparent, aunt, uncle, cousin", p: 3 }, { value: "immediate", label: "Parent, sibling, or child", p: 5 } ] },
204        s4("metabolic.sweet_cravings", "How often do you get strong sugar or carb cravings?", ["Rarely","Weekly","A few times a week","Most days","Daily"], [0,1,2,3,4], "colour")
205      ]
206    },
207
208    /* ---- D2 Cardiovascular Resilience (risk/behaviour) ----------------- */
209    {
210      id: "cardio", kind: "questions", title: "Movement & heart", profile: "cardio",
211      note: "A risk and fitness picture. A validated heart-risk score needs a measured blood pressure.",
212      items: [
213        s4("cardio.activity_ipaq", "In a typical week, how much moderate-to-vigorous activity do you get?", ["150+ minutes","90 to 150 min","30 to 90 min","Under 30 min","None"], [0,1,2,3,4]),
214        { key: "cardio.smoking", profile: "cardio", basis: "risk", input: "single", prompt: "Do you smoke?", options: [ { value: "never", label: "Never", p: 0 }, { value: "ex_smoker", label: "Ex-smoker", p: 1 }, { value: "occasionally", label: "Occasionally", p: 2 }, { value: "daily", label: "Daily", p: 3 } ] },
215        { key: "cardio.family_cvd", profile: "cardio", basis: "risk", input: "single", prompt: "Family history of heart attack or stroke before age 60?", options: [ { value: "no", label: "No", p: 0 }, { value: "yes", label: "Yes", p: 2 } ] },
216        { key: "cardio.conditions", profile: "cardio", basis: "risk", input: "single", prompt: "Any diagnosed high blood pressure, high cholesterol, diabetes, or atrial fibrillation?", options: [ { value: "none", label: "None", p: 0 }, { value: "one", label: "One", p: 2 }, { value: "two_plus", label: "Two or more", p: 3 } ] },
217        s4("cardio.exertion_capacity", "Can you climb two flights of stairs without stopping?", ["Easily","With some effort","I need to stop","I cannot"], [0,1,2,3])
218      ]
219    },
220
221    /* ---- E Inflammation & Recovery (proxy - NOT a measure) ------------- */
222    {
223      id: "inflam", kind: "questions", title: "Recovery", profile: "inflam",
224      note: "This is a recovery and diet proxy, not a measure of inflammation. A blood test (hs-CRP) is the real measure.",
225      items: [
226        s4("inflam.recovery_speed", "After exertion or illness, how quickly do you recover?", ["Quickly","N
226ormally","Slowly","Very slowly"], [0,1,2,3]),
227        s4("inflam.aches_stiffness", "How often do you have unexplained aches or stiffness?", ["Rarely","Occasionally","Often","Most days"], [0,1,2,3]),
228        s4("inflam.diet_tendency", "How often do you eat ultra-processed foods, sugary drinks, or fried food?", ["Rarely","Weekly","Most days","Daily"], [0,1,2,3])
229      ]
230    },
231
232    /* ---- D3 Healthy Aging (behaviour, POSITIVE, derive:MEDAS/HPLP-II) -- */
233    {
234      id: "aging", kind: "questions", title: "Daily habits", profile: "aging",
235      note: "These habits are scored positively: good habits add health.",
236      items: [
237        { key: "aging.med_olive_veg", profile: "aging", basis: "behaviour", input: "single", prompt: "Do you use olive oil as your main fat and eat 2+ veg servings daily?", options: [ { value: "yes_both", label: "Yes, both", h: 2 }, { value: "one", label: "One of the two", h: 1 }, { value: "neither", label: "Neither", h: 0 } ] },
238        { key: "aging.med_fish_legumes", profile: "aging", basis: "behaviour", input: "single", prompt: "Do you eat fish and legumes several times a week?", options: [ { value: "yes", label: "Yes", h: 2 }, { value: "sometimes", label: "Sometimes", h: 1 }, { value: "rarely", label: "Rarely", h: 0 } ] },
239        { key: "aging.med_processed", profile: "aging", basis: "behaviour", input: "single", prompt: "How often do you eat processed meat or commercial sweets?", options: [ { value: "rarely", label: "Rarely", h: 2 }, { value: "weekly", label: "Weekly", h: 1 }, { value: "most_days", label: "Most days", h: 0 } ] },
240        { key: "aging.prevention_mindset", profile: "aging", basis: "behaviour", input: "single", prompt: "Do you take active steps for prevention (check-ups, screening, health goals)?", options: [ { value: "regularly", label: "Regularly", h: 2 }, { value: "sometimes", label: "Sometimes", h: 1 }, { value: "rarely", label: "Rarely", h: 0 } ] },
241        { key: "aging.health_responsibility", profile: "aging", basis: "behaviour", input: "single", prompt: "Do you monitor your own health and act on it?", options: [ { value: "actively", label: "Actively", h: 2 }, { value: "somewhat", label: "Somewhat", h: 1 }, { value: "not_really", label: "Not really", h: 0 } ] }
242      ]
243    },
244
245    /* ---- The intent (T325c, Jesse D1, 2026-09-17) ------------------------
246       How a person wants to get to their result, and what they will spend
247       once. Stored as codes, scored nowhere, and read by the routing matrix
248       and the profile. T368d (2026-09-29) places them: while the questions'
249       switch is on for the run (`BP_INTENT_QUESTIONS`, pinned to the run at its
250       first answer), buildSteps() asks this section before the e-mail step,
251       after the last health area in the short scan and after its one area in a
252       section scan, and never again after it. The top band was split on
253       Jesse's word of 2026-09-29 (migration 104): 1000_12000 and 12000_plus
254       replace 1000_plus. */
255    {
256      id: "intent", kind: "questions", title: "Your way to the result",
257      items: [
258        { key: "intent.guidance", basis: "preference", input: "single",
259          prompt: "If you had to pick one way to get to the result you want, which would it be?",
260          help: "We use your answers to show you help that fits, and to send you matching advice emails if you say yes to them in the next step.",
261          options: [
262            { value: "lab_testing", label: "Lab testing (blood work): understand what is happening inside my body" },
263            { value: "personal_plan", label: "A personal plan I carry out on my own" },
264            { value: "plan_with_coaching", label: "A personal plan plus coaching from an expert (be guided)" },
265            { value: "supplement_advice", label: "Personal supplement advice: I just want to take the right supplements" },
266            { value: "all_in", label: "All in: full blood work plus guidance and coaching from a true expert I choose" } ] },
267        { key: "intent.budget", basis: "preference", input: "single",
268          prompt: "How much are you willing to spend, once, to get there?",
269          help: "We use your answers to show you help that fits, and to send you matching advice emails if you say yes to them in the next step.",
270          options: [
271            { value: "under_50", label: "Below EUR 50" },
272            { value: "under_100", label: "EUR 50 to 100" },
273            { value: "under_500", label: "EUR 100 to 500" },
274            { value: "under_1000", label: "EUR 500 to 1,000" },
275            { value: "1000_12000", label: "EUR 1,000 to 12,000" },
276            { value: "12000_plus", label: "EUR 12,000 or more: whatever it takes to make a massive change" } ] }
277      ]
278    },
279
280    /* ---- Optional objective inputs (deferred wedge) --------------------- */
281    {
282      id: "extras", kind: "extras", title: "Optional: add your data",
283      body: "You can add blood results or wearable data to sharpen your picture. This is optional and can be done later. It is the only way some profiles (metabolic, cardiovascular, inflammation, hormonal) reach high confidence."
284    },
285
286    { id: "finish", kind: "finish", title: "See your BioProfile" }
287  ]
288};
289
290/* ---- Progressive single-session model (mini -> opt-in -> full) ----------
291   The mini-test is NOT a separate analysis. It is the opening block of ONE
292   analysis session: the questions below (plus preamble, safety, WHO-5) are asked
293   first and give a cross-profile read; the opt-in gate sits right after them; the
294   person then continues the SAME session to complete the full analysis. The
295   engine already scores any partial set of answers (confidence rises with
296   coverage), so the "mini result" is just this session scored at the gate, and
297   the "full result" is the same session scored at completion. One session, one
298   date, two result snapshots. See ../../analysis/questionnaire-item-bank.md. */
299/* Mini set at THREE questions per main area (Jesse 2026-07-18: "3 questions per
300   area instead of 2... worth making the foundation stronger"). Every third
301   question is taken from the EXISTING item bank; nothing invented. */
302globalThis.BP_Q.miniKeys = [
303  "energy.daily_stability", "energy.midday_dip", "energy.stimulant_reliance",
304  "sleep.onset_latency", "sleep.night_waking", "sleep.rested_waking",
305  "stress.control", "stress.overwhelm", "stress.coping",
306  "gut.stool_form", "gut.pain_frequency", "gut.bloating",
307  "brain.clarity_fog", "brain.focus", "brain.memory_lapses",
308  "hormonal.hot_flushes", "hormonal.mood_swings_f", "hormonal.sleep_hormonal",
309  "hormonal.libido_m", "hormonal.vitality_m", "hormonal.mood_m",
310  "metabolic.high_glucose_history", "metabolic.activity_findrisc", "metabolic.family_diabetes",
311  "cardio.activity_ipaq", "cardio.smoking", "cardio.exertion_capacity",
312  "inflam.recovery_speed", "inflam.aches_stiffness", "inflam.diet_tendency",
313  "aging.med_olive_veg", "aging.prevention_mindset", "aging.med_fish_legumes"
314];
315/* Whole sections that belong to the pre-gate (mini) phase regardless of item. */
316globalThis.BP_Q.miniSections = ["intro", "about", "safety", "wellbeing", "mood"];
317/* T368d (T325b's ruling): while the intent questions are asked, these items of
318   the About you step move behind the e-mail step, as the first step after it.
319   Nothing scores on them. */
320globalThis.BP_Q.postGateKeys = ["pre.glp1"];
321
322/* ---- Section scans (Jesse 2026-07-28) ------------------------------------
323   The mini test becomes ONE section of the questionnaire IN FULL, chosen by the
324   `?scan=` URL parameter. Same page, same funnel, same opt-in gate, same landing
325   - only the questions change. The safety battery is skipped on a scan (his
326   explicit allowance) and asked after the gate instead. With no parameter, or an
327   unknown one, the standard mini test runs exactly as before.
328
329   This registry is the ONE source of truth: the browser flow builds the step
330   list from it and the server canonicalises the submitted value against it
331   (api/lead.js), so an advertorial link can never write a junk value onto a lead.
332   `sections` = questionnaire section ids (gating still applies, so the two
333   hormonal cohorts resolve to whichever one fits the person). `area`/`icon`/
334   `color` drive the front-page chip; the display strings live in the i18n packs
335   under `scan.name.<slug>` and never in here. */
336globalThis.BP_Q.scans = {
337  "energy":           { sections: ["energy"],                  area: "energy",    icon: "energy",     color: "lime" },
338  "sleep":            { sections: ["sleep"],                   area: "sleep",     icon: "sleep",      color: "forest" },
339  "stress":           { sections: ["stress"],                  area: "stress",    icon: "stress",     color: "slate" },
340  "gut-health":       { sections: ["gut"],                     area: "gut",       icon: "gut",        color: "emerald" },
341  "brain-health":     { sections: ["brain"],                   area: "brain",     icon: "brain",      color: "teal" },
342  "hormonal-balance": { sections: ["hormonal_f", "hormonal_m"], area: "hormonal", icon: "hormonal_f", color: "slate" },
343  "metabolic-health": { sections: ["metabolic"],               area: "metabolic", icon: "metabolic",  color: "olive" },
344  "heart-health":     { sections: ["cardio"],                  area: "cardio",    icon: "cardio",     color: "teal" },
345  "recovery":         { sections: ["inflam"],                  area: "inflam",    icon: "inflam",     color: "emerald" },
346  "healthy-aging":    { sections: ["aging"],                   area: "aging",     icon: "aging",      color: "forest" },
347  "wellbeing":        { sections: ["wellbeing", "mood"],       area: "wellbeing", icon: "wellbeing",  color: "teal" }
348};
349
350/* Forgiving aliases so an advertorial link that says the obvious thing still
351   lands on the right scan (plain section ids, common synonyms, Dutch words).
352   Anything not in either map falls back to the standard mini test. */
353globalThis.BP_Q.scanAliases = {
354  "brain": "brain-health", "focus": "brain-health", "brain-fog": "brain-health", "brein": "brain-health",
355  "gut": "gut-health", "digestion": "gut-health", "darm": "gut-health", "darmen": "gut-health", "darmgezondheid": "gut-health",
356  "metabolic": "metabolic-health", "metabolism": "metabolic-health", "stofwisseling": "metabolic-health",
357  "cardio": "heart-health", "heart": "heart-health", "hart": "heart-health",
358  "hormonal": "hormonal-balance", "hormones": "hormonal-balance", "hormonen": "hormonal-balance", "hormoonbalans": "hormonal-balance",
359  "inflam": "recovery", "inflammation": "recovery", "herstel": "recovery",
360  "aging": "healthy-aging", "ageing": "healthy-aging", "longevity": "healthy-aging", "leefstijl": "healthy-aging",
361  "energie": "energy", "vitaliteit": "energy",
362  "slaap": "sleep", "sleep-recovery": "sleep",
363  "mood": "wellbeing", "welzijn": "wellbeing", "mental-wellbeing": "wellbeing"
364};
365
366/* Normalise + resolve a raw parameter value to a canonical slug, or null.
367   Null is the documented fallback: the standard mini test, never a broken page. */
368globalThis.BP_Q.resolveScan = function (raw) {
369  if (raw == null) return null;
370  var s = String(raw).slice(0, 64).toLowerCase().trim()
371    .replace(/[\s_]+/g, "-").replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-").replace(/^-|-$/g, "");
372  if (!s) return null;
373  if (globalThis.BP_Q.scans[s]) return s;
374  var a = globalThis.BP_Q.scanAliases[s];
375  return a && globalThis.BP_Q.scans[a] ? a : null;
376};
377
378/* ---- tiny item factories (keep the definition above compact) ---------- */
379function s4(key, prompt, labels, points, basis) {
380  var profileGuess = key.split(".")[0];
381  return { key: key, profile: profileGuess, basis: basis || (profileGuess === "brain" ? "perceived" : "symptom"),
382    input: labels.length > 4 ? "single" : "scale",
383    prompt: prompt, options: labels.map(function (l, i) { return { value: slug(l, i), label: l, p: points[i] }; }) };
384}
385function who5Item(key, prompt) {
386  var labels = ["At no time","Some of the time","Less than half the time","More than half the time","Most of the time","All of the time"];
387  return [{ key: key, profile: "who5", basis: "wellbeing", input: "single", prompt: prompt,
388    options: labels.map(function (l, i) { return { value: slug(l, i), label: l, w: i }; }) }];
389}
390function mood4(key, prompt) {
391  var labels = ["Not at all","Several days","More than half the days","Nearly every day"];
392  return { key: key, profile: "mood", basis: "mood", input: "scale", prompt: prompt,
393    options: labels.map(function (l, i) { return { value: slug(l, i), label: l, m: i }; }) };
394}
395function slug(label, i) { return label.toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_|_$/g, "") || ("o" + i); }
396
397/* T553: an answer behind a switch is offered, and accepted by the server, only while its switch is on: in the browser
398   BP_CONFIG.newGoals "1" (app/config.js), on the server the environment's BP_NEW_GOALS "1". Off by default, so the five
399   new goals reach the live scan only by the hub's release, after their ActiveCampaign tags exist (Astra's code read 2). */
400(function () {
401  var on = false;
402  try {
403    if (typeof process !== "undefined" && process.env && process.env.BP_NEW_GOALS === "1") on = true;
404    if (typeof window !== "undefined" && window.BP_CONFIG && window.BP_CONFIG.newGoals === "1") on = true;
405  } catch (e) { on = false; }
406  if (on) return;
407  globalThis.BP_Q.sections.forEach(function (sec) {
408    (sec.items || []).forEach(function (it) {
409      if (it.options) it.options = it.options.filter(function (o) { return o.gate !== "new_goals"; });
410    });
411  });
412})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.