diff --git "a/app.py" "b/app.py"
new file mode 100644--- /dev/null
+++ "b/app.py"
@@ -0,0 +1,2015 @@
+import streamlit as st
+import streamlit.components.v1 as components
+from openai import OpenAI
+import os, json, re, csv, smtplib, logging
+from email.mime.multipart import MIMEMultipart
+from email.mime.text import MIMEText
+from email.mime.base import MIMEBase
+from email import encoders
+from reportlab.lib.pagesizes import letter
+from reportlab.lib.styles import getSampleStyleSheet, ParagraphStyle
+from reportlab.lib.units import inch
+from reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, HRFlowable, KeepTogether, PageBreak
+from reportlab.lib.enums import TA_CENTER, TA_LEFT, TA_RIGHT
+from reportlab.lib import colors
+from io import BytesIO, StringIO
+from datetime import datetime
+from config import *
+
+# ── OpenAI client (lazy — only created when actually needed)
+_openai_client = None
+def get_openai_client():
+ global _openai_client
+ if _openai_client is None:
+ api_key = os.getenv("OPENAI_API_KEY")
+ if not api_key:
+ st.error("**OPENAI_API_KEY not set.** Add it as an environment variable or a Hugging Face Space secret.")
+ st.stop()
+ _openai_client = OpenAI(api_key=api_key)
+ return _openai_client
+
+# ── Shorthand color references from config
+NAVY = BRAND["colors"]["navy"]
+TEAL = BRAND["colors"]["teal"]
+SKY = BRAND["colors"]["sky"]
+ORANGE = BRAND["colors"]["orange"]
+AMBER = BRAND["colors"]["amber"]
+WHITE = BRAND["colors"]["white"]
+
+# =============================================================================
+# CONTRAINDICATION CHECKING
+# =============================================================================
+def check_contraindications(service_list, patient_flags):
+ """Check which services trigger contraindication flags for the patient."""
+ if "None of the above" in patient_flags or not patient_flags:
+ return {}
+ hits = {}
+ for service in service_list:
+ triggered = [f for f in SERVICE_CONTRAINDICATIONS.get(service, []) if f in patient_flags]
+ if triggered:
+ hits[service] = triggered
+ return hits
+
+
+def get_stack_contraindication_summary(stack_name, patient_flags):
+ """Get contraindication summary for a specific wellness stack."""
+ return check_contraindications(WELLNESS_PROGRAMS[stack_name]["services"], patient_flags)
+
+
+# =============================================================================
+# PRICING AND GOAL MAPPING
+# =============================================================================
+def get_pricing_summary(stack_name):
+ """Get a readable pricing summary for a wellness program."""
+ stack = WELLNESS_PROGRAMS[stack_name]
+ tiers = stack["tiers"]
+ sorted_tiers = sorted(tiers.items(), key=lambda x: x[1]["sort_order"])
+ # Return the mid-tier price as representative
+ mid = sorted_tiers[len(sorted_tiers) // 2]
+ return {
+ "tier_name": mid[0],
+ "monthly_26wk": mid[1]["26_week"],
+ "monthly_52wk": mid[1]["52_week"],
+ "all_tiers": tiers,
+ }
+
+
+def map_goals_to_targets(goals_data):
+ """Map patient goals and symptoms to target health categories."""
+ ts = {cat: 0 for cat in TARGET_CATEGORIES}
+
+ # Primary goals mapping
+ for goal in goals_data.get("primary_goals", []):
+ for target, weight in PRIMARY_GOALS.get(goal, []):
+ ts[target] += weight
+
+ # Current symptoms mapping
+ for symptom in goals_data.get("current_issues", []):
+ for target, weight in CURRENT_SYMPTOMS.get(symptom, []):
+ ts[target] += weight
+
+ # Energy/recovery mapping
+ energy = goals_data.get("energy_recovery", "")
+ for target, weight in ENERGY_RECOVERY_OPTIONS.get(energy, []):
+ ts[target] += weight
+
+ return ts
+
+
+def recommend_programs(target_scores, num_primary_goals, patient_sex="any", patient_flags=None):
+ """
+ Recommend wellness programs filtered by patient sex and with contraindication awareness.
+
+ Args:
+ target_scores: Dict of target category scores
+ num_primary_goals: Number of primary goals selected
+ patient_sex: "Male" or "Female" for sex-based filtering
+ patient_flags: List of contraindication flags
+
+ Returns:
+ Tuple of (recommended_stack_names, top_target_categories)
+ """
+ if patient_flags is None:
+ patient_flags = []
+
+ # Get top 3 targets by score
+ sorted_targets = sorted(target_scores.items(), key=lambda x: x[1], reverse=True)
+ top_targets = [t[0] for t in sorted_targets if t[1] > 0][:3]
+
+ # Filter programs by sex using the sex_filter field in config
+ sex_key = patient_sex.lower() if patient_sex else "any"
+ eligible = {
+ sn: si for sn, si in WELLNESS_PROGRAMS.items()
+ if si.get("sex_filter", "any") in (sex_key, "any")
+ }
+
+ # Score eligible programs by how well they target the top goals
+ stack_scores = {
+ sn: sum(target_scores.get(t, 0) * 2 for t in si["targets"] if t in top_targets)
+ for sn, si in eligible.items()
+ }
+
+ # Sort by robustness first (lowest display_order = most comprehensive),
+ # then by goal-match score as tiebreaker within same robustness tier.
+ # This ensures the most robust package always leads (high-to-low strategy).
+ sorted_stacks = sorted(
+ stack_scores.items(),
+ key=lambda x: (eligible[x[0]].get("display_order", 50), -x[1]),
+ )
+
+ # Determine how many stacks to recommend
+ n = min(num_primary_goals, 2) if num_primary_goals <= 2 else 3
+ recommended = [s[0] for s in sorted_stacks[:n]]
+
+ # Contraindication-aware fallback: if ALL recommended stacks have contraindication hits,
+ # find highest-scoring stack with no contraindication hits and suggest it as an alternative
+ active_flags = [f for f in patient_flags if f != "None of the above"]
+ if active_flags and recommended:
+ all_have_contras = all(
+ check_contraindications(WELLNESS_PROGRAMS[sn]["services"], active_flags)
+ for sn in recommended
+ )
+ if all_have_contras:
+ # Look for a fallback with no contraindication hits
+ for sn, score in sorted_stacks:
+ if sn not in recommended:
+ hits = check_contraindications(WELLNESS_PROGRAMS[sn]["services"], active_flags)
+ if not hits:
+ recommended.append(sn)
+ break
+
+ # All sex-eligible programs (for "also available" display) — already in robustness order
+ all_eligible = [s[0] for s in sorted_stacks]
+
+ return recommended, top_targets, all_eligible
+
+
+# =============================================================================
+# GPT WELLNESS PATHWAY GENERATOR
+# =============================================================================
+def generate_wellness_pathway(intake_data, recommended_stacks, contraindication_flags):
+ """Generate a personalized wellness pathway using GPT-4."""
+ def price_info(sn):
+ stack = WELLNESS_PROGRAMS[sn]
+ tiers = stack["tiers"]
+ sorted_t = sorted(tiers.items(), key=lambda x: x[1]["sort_order"])
+ low = sorted_t[-1][1]["26_week"] # Cheapest tier
+ high = sorted_t[0][1]["26_week"] # Most expensive tier
+ return f"${low:,.2f} – ${high:,.2f}/month (26-week commitment, 15% off at 52 weeks)"
+
+ stacks_summary = "\n\n".join([
+ f"**{sn}**: {WELLNESS_PROGRAMS[sn]['description']}\n"
+ f"Services: {', '.join(WELLNESS_PROGRAMS[sn]['services'])}\n"
+ f"Targets: {', '.join(WELLNESS_PROGRAMS[sn]['targets'])}\n"
+ f"Pricing Range: {price_info(sn)}\n"
+ f"Cycle: {WELLNESS_PROGRAMS[sn]['cycle']}"
+ for sn in recommended_stacks
+ ])
+
+ contra_context = ""
+ active = [f for f in contraindication_flags if f != "None of the above"]
+ if active:
+ contra_context = (
+ f"\n\nCONTRAINDICATION FLAGS REPORTED BY PATIENT: {', '.join(active)}\n"
+ "Please address these flags explicitly in the Safety & Monitoring section. "
+ "Note any services requiring extra caution or modification. Do NOT recommend discontinuing — "
+ "advise physician-supervised evaluation of each flagged item."
+ )
+
+ intake_summary = (
+ f"Patient Demographics:\n"
+ f"- Name: {intake_data['name']} | Sex: {intake_data['sex']} | Age: {intake_data['age']}\n\n"
+ f"Primary Goals: {', '.join(intake_data['primary_goals'])}\n"
+ f"Energy & Recovery: {intake_data['energy_recovery']}\n"
+ f"Current Issues: {', '.join(intake_data['current_issues']) if intake_data['current_issues'] else 'None reported'}\n"
+ f"Physical Activity: {intake_data['activity_level']}\n"
+ f"Medical Considerations: {intake_data['medical_considerations']}"
+ f"{contra_context}\n\n"
+ f"RECOMMENDED PROGRAM(S):\n{stacks_summary}"
+ )
+
+ try:
+ response = get_openai_client().chat.completions.create(
+ model=GPT_CONFIG["model"],
+ messages=[
+ {"role": "system", "content": PATHWAY_SYSTEM_PROMPT},
+ {"role": "user", "content": f"Create a program recommendation:\n\n{intake_summary}"}
+ ],
+ temperature=GPT_CONFIG["temperature"],
+ )
+ return response.choices[0].message.content
+ except Exception as e:
+ return f"Error generating pathway: {str(e)}\n\nPlease ensure your OpenAI API key is configured."
+
+
+# =============================================================================
+# WELLNESS PATHWAY HTML RENDERER
+# =============================================================================
+def render_pathway_html(raw_text, stack_services):
+ """Convert GPT markdown to rich HTML with service subsection cards."""
+ lines = raw_text.split("\n")
+ output = []
+ service_set = set(stack_services)
+ in_ul = False
+ in_ol = False
+ in_pep = False
+ pep_bullets = []
+
+ def close_lists():
+ nonlocal in_ul, in_ol
+ if in_ul: output.append(""); in_ul = False
+ if in_ol: output.append(""); in_ol = False
+
+ def close_pep_card():
+ nonlocal in_pep
+ if in_pep:
+ if pep_bullets:
+ output.append('
')
+
+ for raw_line in lines:
+ stripped = raw_line.strip()
+ if not stripped:
+ close_lists()
+ if not in_pep:
+ output.append('
')
+ continue
+
+ # ── Major section **Bold** ─────────────────────────────────
+ if stripped.startswith("**") and stripped.endswith("**") and stripped.count("**") == 2:
+ close_lists()
+ close_pep_card()
+ clean = stripped.replace("**", "").strip()
+ output.append(f'
{clean}
')
+ continue
+
+ # ── Service subsection ### Name ────────────────────────────
+ if stripped.startswith("###"):
+ close_lists()
+ close_pep_card() # close any previously open card first
+ title = stripped.lstrip("#").strip()
+ title = re.split(r'\s*[\(\[]', title)[0].strip()
+ moa = SERVICE_MOA.get(title, "")
+ safe_moa = moa.replace('"', '"').replace("'", "'") if moa else ""
+ tip_attr = f' data-tip="{safe_moa}"' if safe_moa else ""
+ tooltip_span = (f'
{safe_moa}' if safe_moa else "")
+ output.append('
')
+ output.append(
+ f'
'
+ f'{title}{tooltip_span}
'
+ )
+ in_pep = True
+ continue
+
+ # ── H2 ## ──────────────────────────────────────────────────
+ if stripped.startswith("##"):
+ close_lists()
+ close_pep_card()
+ clean = stripped.lstrip("#").replace("**", "").strip()
+ output.append(f'
{fmt_inline(clean)}
')
+ continue
+
+ # ── Bullet ─────────────────────────────────────────────────
+ if stripped and stripped[0] in ("-", "*", "•"):
+ close_lists()
+ if not in_ul and not in_pep:
+ output.append('
'); in_ul = True
+ content = fmt_inline(stripped.lstrip("-*• "))
+ if in_pep:
+ pep_bullets.append(f"- {content}
")
+ else:
+ output.append(f"- {content}
")
+ continue
+
+ # ── Numbered list ───────────────────────────────────────────
+ nm = re.match(r'^(\d+)[.)]\s+(.*)', stripped)
+ if nm:
+ if in_ul:
+ output.append("
"); in_ul = False
+ if not in_ol:
+ output.append('
'); in_ol = True
+ output.append(f"- {fmt_inline(nm.group(2))}
")
+ continue
+
+ # ── Paragraph ───────────────────────────────────────────────
+ close_lists()
+ if in_pep:
+ close_pep_card()
+ output.append(f'{fmt_inline(stripped)}
')
+
+ close_lists()
+ close_pep_card()
+ output.append("
") # close pathway-wrapper
+ return "\n".join(output)
+
+
+# =============================================================================
+# PDF GENERATOR — with robust markdown parsing
+# =============================================================================
+def generate_wellness_pdf(pathway_content, patient_info, recommended_stacks, contraindication_flags=None):
+ """Generate a professional wellness PDF with robust markdown handling."""
+ from reportlab.platypus import HRFlowable, KeepTogether, PageBreak
+ from reportlab.lib.enums import TA_RIGHT
+
+ buffer = BytesIO()
+ doc = SimpleDocTemplate(buffer, pagesize=letter,
+ topMargin=0.55*inch, bottomMargin=0.6*inch,
+ leftMargin=0.65*inch, rightMargin=0.65*inch)
+ story = []
+ pw = letter[0] - 1.3*inch # usable page width
+
+ def ps(name, **kwargs):
+ return ParagraphStyle(name, **kwargs)
+
+ # ── Shared colours ────────────────────────────────────────────────────────
+ C_NAVY = colors.HexColor(NAVY)
+ C_TEAL = colors.HexColor(TEAL)
+ C_ORANGE = colors.HexColor(ORANGE)
+ C_AMBER = colors.HexColor(AMBER)
+ C_SKY = colors.HexColor(SKY)
+ C_LIGHT = colors.HexColor("#eef4f8")
+ C_CARD = colors.HexColor("#f0f6fa")
+ C_WARMWH = colors.HexColor("#fff8ec")
+ C_BORDER = colors.HexColor("#b8d0de")
+
+ # ── Type styles ───────────────────────────────────────────────────────────
+ brand_s = ps("Br", fontName="Helvetica-Bold", fontSize=9, textColor=C_ORANGE, spaceAfter=0, alignment=TA_CENTER)
+ title_s = ps("T", fontName="Helvetica-Bold", fontSize=22, leading=28, textColor=colors.white, spaceAfter=0, alignment=TA_CENTER)
+ sub_s = ps("Su", fontName="Helvetica", fontSize=10, textColor=C_SKY, spaceAfter=0, alignment=TA_CENTER)
+ meta_s = ps("Me", fontName="Helvetica", fontSize=9, leading=14, textColor=C_NAVY, spaceAfter=3)
+ h2_s = ps("H2", fontName="Helvetica-Bold", fontSize=11, textColor=colors.white, spaceBefore=0, spaceAfter=0)
+ h3_s = ps("H3", fontName="Helvetica-Bold", fontSize=10, textColor=C_NAVY, spaceBefore=4, spaceAfter=3)
+ body_s = ps("B", fontName="Helvetica", fontSize=9.5, leading=15, textColor=C_NAVY, spaceAfter=5)
+ bullet_s = ps("Bu", fontName="Helvetica", fontSize=9.5, leading=15, textColor=C_NAVY, leftIndent=14, spaceAfter=3)
+ num_s = ps("Nu", fontName="Helvetica", fontSize=9.5, leading=15, textColor=C_NAVY, leftIndent=14, spaceAfter=3)
+ warn_s = ps("W", fontName="Helvetica-Bold", fontSize=9, leading=13, textColor=C_ORANGE, spaceAfter=3)
+ disc_s = ps("Di", fontName="Helvetica-Oblique", fontSize=8, leading=12, textColor=colors.HexColor("#4a6070"), spaceAfter=4)
+ wk_lbl_s = ps("Wl", fontName="Helvetica-Bold", fontSize=7, leading=9, textColor=C_ORANGE, spaceAfter=2, alignment=TA_CENTER)
+ wk_s = ps("Wk", fontName="Helvetica-Bold", fontSize=16, leading=18, textColor=C_ORANGE, spaceAfter=3, alignment=TA_CENTER)
+ pr_s = ps("Pr", fontName="Helvetica", fontSize=8, leading=11, textColor=colors.HexColor("#555"), spaceAfter=1, alignment=TA_CENTER)
+ pep_hdr_s = ps("Ph", fontName="Helvetica-Bold", fontSize=10, textColor=colors.white, spaceBefore=0, spaceAfter=0)
+ sh_s = ps("Sh", fontName="Helvetica-Bold", fontSize=10, textColor=colors.white, spaceBefore=0, spaceAfter=0)
+ ft_s = ps("Ft", fontName="Helvetica", fontSize=8, leading=12, textColor=C_NAVY, spaceAfter=0, alignment=TA_CENTER)
+ sn_s = ps("Sn", fontName="Helvetica-Bold", fontSize=13, textColor=C_ORANGE, spaceAfter=0)
+
+ # ── Helper: full-width coloured banner ────────────────────────────────────
+ def banner(para, bg, line_below=None, pad_v=7, pad_h=12):
+ t = Table([[para]], colWidths=[pw])
+ cmds = [
+ ("BACKGROUND", (0,0),(-1,-1), bg),
+ ("LEFTPADDING", (0,0),(-1,-1), pad_h),
+ ("RIGHTPADDING",(0,0),(-1,-1), pad_h),
+ ("TOPPADDING", (0,0),(-1,-1), pad_v),
+ ("BOTTOMPADDING",(0,0),(-1,-1), pad_v),
+ ]
+ if line_below:
+ cmds.append(("LINEBELOW", (0,0),(-1,-1), 3, line_below))
+ t.setStyle(TableStyle(cmds))
+ return t
+
+ def section_hdr(text):
+ """Navy banner with orange underline."""
+ return KeepTogether([
+ Spacer(1, 8),
+ banner(Paragraph(text.upper(), h2_s), C_NAVY, line_below=C_ORANGE, pad_v=8),
+ Spacer(1, 6),
+ ])
+
+ # =========================================================================
+ # PAGE 1 — COVER / SUMMARY
+ # =========================================================================
+
+ hdr_tbl = Table([
+ [Paragraph("IGNITE PERFORMANCE & HEALTH", brand_s)],
+ [Paragraph("
Your Ignition Sequence™", title_s)],
+ [Paragraph("Your Program Recommendation", sub_s)],
+ ], colWidths=[pw])
+ hdr_tbl.setStyle(TableStyle([
+ ("BACKGROUND", (0,0),(-1,-1), C_NAVY),
+ ("LEFTPADDING", (0,0),(-1,-1), 20),
+ ("RIGHTPADDING", (0,0),(-1,-1), 20),
+ ("TOPPADDING", (0,0),(0,0), 14),
+ ("BOTTOMPADDING", (0,0),(0,0), 4),
+ ("TOPPADDING", (0,1),(0,1), 4),
+ ("BOTTOMPADDING", (0,1),(0,1), 4),
+ ("TOPPADDING", (0,2),(0,2), 2),
+ ("BOTTOMPADDING", (0,2),(0,2), 14),
+ ("ALIGN", (0,0),(-1,-1), "CENTER"),
+ ("VALIGN", (0,0),(-1,-1), "MIDDLE"),
+ ]))
+ story.append(hdr_tbl)
+ story.append(HRFlowable(width="100%", thickness=5, color=C_ORANGE, spaceAfter=12))
+
+ # ── Patient info ─────────────────────────────────────────────────────────
+ name = patient_info.get("name", "")
+ age = patient_info.get("age", "")
+ sex = patient_info.get("sex", "")
+ date = patient_info.get("date", "")
+
+ pi_tbl = Table([[
+ Paragraph(f"
Patient: {name}", meta_s),
+ Paragraph(f"
Age: {age}
Sex: {sex}", meta_s),
+ Paragraph(f"
Date: {date}", meta_s),
+ ]], colWidths=[pw*0.42, pw*0.22, pw*0.36])
+ pi_tbl.setStyle(TableStyle([
+ ("BACKGROUND", (0,0),(-1,-1), C_LIGHT),
+ ("BOX", (0,0),(-1,-1), 0.75, C_BORDER),
+ ("LINEBELOW", (0,0),(-1,-1), 2, C_ORANGE),
+ ("LEFTPADDING", (0,0),(-1,-1), 10), ("RIGHTPADDING", (0,0),(-1,-1), 10),
+ ("TOPPADDING", (0,0),(-1,-1), 9), ("BOTTOMPADDING",(0,0),(-1,-1), 9),
+ ("VALIGN", (0,0),(-1,-1), "MIDDLE"),
+ ]))
+ story.append(pi_tbl)
+ story.append(Spacer(1, 14))
+
+ # ── Contraindications (if any) ────────────────────────────────────────────
+ active_flags = [f for f in (contraindication_flags or []) if f != "None of the above"]
+ if active_flags:
+ contra_items = [
+ banner(Paragraph("⚠ PHYSICIAN REVIEW REQUIRED", h2_s),
+ colors.HexColor("#8B0000"), pad_v=9),
+ Spacer(1, 5),
+ Paragraph(
+ "The following patient-reported flags require physician evaluation before initiating therapy. "
+ "These flags do not automatically disqualify a patient from therapy.", body_s),
+ ]
+ # Show what the patient reported
+ flags_str = " · ".join(active_flags)
+ contra_items.append(Paragraph(f"
Reported: {flags_str}", warn_s))
+ contra_items.append(Spacer(1, 4))
+
+ # Show stack-specific impacts
+ has_hits = False
+ for sn in recommended_stacks:
+ hits = get_stack_contraindication_summary(sn, active_flags)
+ if hits:
+ has_hits = True
+ contra_items.append(Paragraph(f"
{sn} — affected services:", h3_s))
+ for service, flags in hits.items():
+ contra_items.append(Paragraph(f" •
{service}: {', '.join(flags)}", warn_s))
+ if not has_hits:
+ contra_items.append(Paragraph(
+ "No recommended services are directly contraindicated, but physician review is still required.", body_s))
+ contra_items.append(Spacer(1, 10))
+ story.append(KeepTogether(contra_items))
+
+ # ── Recommended stacks ────────────────────────────────────────────────────
+ story.append(section_hdr("Recommended Program(s)"))
+
+ # Service pill style
+ pep_pill_s = ps("Pp", fontName="Helvetica-Bold", fontSize=8, leading=11,
+ textColor=C_AMBER, spaceAfter=0, alignment=TA_CENTER)
+
+ for sn in recommended_stacks:
+ ps_data = get_pricing_summary(sn)
+ s = WELLNESS_PROGRAMS[sn]
+ targets_str = " · ".join(s["targets"])
+
+ # Build service pills
+ pep_cells = [Paragraph(f" {p} ", pep_pill_s) for p in s["services"]]
+ n_peps = len(s["services"])
+ pill_col_w = min(1.15*inch, (pw - 2.6*inch) / max(n_peps, 1))
+ pep_pills_tbl = Table([pep_cells], colWidths=[pill_col_w]*n_peps)
+ pep_pills_tbl.setStyle(TableStyle([
+ ("BACKGROUND", (0,0),(-1,-1), C_NAVY),
+ ("BOX", (0,0),(-1,-1), 0, colors.white),
+ ("INNERGRID", (0,0),(-1,-1), 4, colors.white),
+ ("ALIGN", (0,0),(-1,-1), "CENTER"),
+ ("VALIGN", (0,0),(-1,-1), "MIDDLE"),
+ ("TOPPADDING", (0,0),(-1,-1), 4),
+ ("BOTTOMPADDING", (0,0),(-1,-1), 4),
+ ("LEFTPADDING", (0,0),(-1,-1), 6),
+ ("RIGHTPADDING", (0,0),(-1,-1), 6),
+ ]))
+
+ # Left: description + targets + cycle + service pills
+ desc_para = Paragraph(
+ f"{s['description']}
"
+ f"
Targets: {targets_str}
"
+ f"
Cycle: {s['cycle']}", meta_s)
+
+ left_content = Table([
+ [desc_para],
+ [Spacer(1, 4)],
+ [pep_pills_tbl],
+ ], colWidths=[pw - 2.0*inch])
+ left_content.setStyle(TableStyle([
+ ("LEFTPADDING", (0,0),(-1,-1), 0),
+ ("RIGHTPADDING", (0,0),(-1,-1), 0),
+ ("TOPPADDING", (0,0),(-1,-1), 0),
+ ("BOTTOMPADDING", (0,0),(-1,-1), 0),
+ ]))
+
+ # Right: pricing box - mid-tier as representative (convert monthly to weekly)
+ wk_price_26 = ps_data['monthly_26wk'] * 12.0 / 52.0
+ wk_price_52 = ps_data['monthly_52wk'] * 12.0 / 52.0
+ price_inner = Table([
+ [Paragraph("26-WEEK · PER WEEK", wk_lbl_s)],
+ [Paragraph(f"${wk_price_26:,.2f}", wk_s)],
+ [HRFlowable(width="80%", thickness=0.5, color=C_BORDER, spaceAfter=2)],
+ [Paragraph("52-WEEK · PER WEEK", wk_lbl_s)],
+ [Paragraph(f"${wk_price_52:,.2f}", pr_s)],
+ ], colWidths=[1.55*inch])
+ price_inner.setStyle(TableStyle([
+ ("ALIGN", (0,0),(-1,-1), "CENTER"),
+ ("TOPPADDING", (0,0),(-1,-1), 2),
+ ("BOTTOMPADDING",(0,0),(-1,-1), 2),
+ ]))
+
+ card_tbl = Table([[left_content, price_inner]], colWidths=[pw - 1.7*inch, 1.7*inch])
+ card_tbl.setStyle(TableStyle([
+ ("BACKGROUND", (0,0),(0,0), C_CARD),
+ ("BACKGROUND", (1,0),(1,0), C_WARMWH),
+ ("BOX", (0,0),(-1,-1),0.75, C_BORDER),
+ ("LINEBEFORE", (1,0),(1,-1), 2, C_ORANGE),
+ ("LEFTPADDING", (0,0),(0,-1), 10), ("RIGHTPADDING", (0,0),(0,-1), 10),
+ ("TOPPADDING", (0,0),(-1,-1),10), ("BOTTOMPADDING",(0,0),(-1,-1),10),
+ ("VALIGN", (0,0),(0,0), "TOP"),
+ ("VALIGN", (1,0),(1,0), "MIDDLE"),
+ ]))
+
+ # Stack name banner
+ sn_banner = banner(Paragraph(f" {sn}", sn_s), colors.HexColor("#012233"), line_below=C_TEAL)
+
+ stack_block = KeepTogether([
+ sn_banner,
+ card_tbl,
+ Spacer(1, 12),
+ ])
+ story.append(stack_block)
+
+ story.append(Paragraph(
+ "* Pricing shown is the mid-tier option. Multiple tier options available with different service levels.", disc_s))
+ story.append(Spacer(1, 6))
+
+ # =========================================================================
+ # PAGE BREAK before the protocol narrative
+ # =========================================================================
+ story.append(PageBreak())
+
+ # ── Protocol narrative with robust markdown parsing ──────────────────────
+
+ seen_first_section = False
+ hit_treatment_page = False
+ current_pep_hdr = None
+ pending_bullets = []
+
+ skip_headings = {"how your stack works", "your personalized wellness protocol",
+ "your wellness protocol — how it works", "your wellness protocol - how it works",
+ "your ignition sequence"}
+
+ # Condensed styles for the final summary page
+ cond_bullet_s = ps("Cb", fontName="Helvetica", fontSize=8.5, leading=12,
+ textColor=C_NAVY, leftIndent=12, spaceAfter=2)
+ cond_sub_bullet_s = ps("Csb", fontName="Helvetica", fontSize=8, leading=11,
+ textColor=C_NAVY, leftIndent=26, spaceAfter=1)
+ cond_num_s = ps("Cn", fontName="Helvetica", fontSize=8.5, leading=12,
+ textColor=C_NAVY, leftIndent=12, spaceAfter=3)
+ cond_body_s = ps("CB", fontName="Helvetica", fontSize=8.5, leading=12,
+ textColor=C_NAVY, spaceAfter=3)
+ cond_sh_s = ps("CS", fontName="Helvetica-Bold", fontSize=9,
+ textColor=colors.white, spaceBefore=0, spaceAfter=0)
+
+ sub_bullet_s = ps("Sb", fontName="Helvetica", fontSize=9, leading=14,
+ textColor=C_NAVY, leftIndent=28, spaceAfter=2)
+
+ for raw_line in pathway_content.split("\n"):
+ line = raw_line.strip()
+ indent = len(raw_line) - len(raw_line.lstrip())
+
+ if not line:
+ if current_pep_hdr is None and seen_first_section and not hit_treatment_page:
+ story.append(Spacer(1, 2))
+ elif hit_treatment_page:
+ story.append(Spacer(1, 1))
+ continue
+
+ # ── Service subsection ### Name ──────────────────────────────────────
+ if line.startswith("###"):
+ seen_first_section = True
+ # Flush previous service block
+ if current_pep_hdr is not None:
+ story.append(KeepTogether(list(current_pep_hdr) + list(pending_bullets)))
+ pending_bullets.clear()
+
+ pep_name = re.split(r'\s*[\(\[]', line.replace("###","").strip())[0].strip()
+ pep_bar = banner(Paragraph(f" {pep_name}", pep_hdr_s),
+ C_TEAL, line_below=C_NAVY, pad_v=5)
+ moa = SERVICE_MOA.get(pep_name, "")
+ moa_paras = []
+ if moa:
+ moa_paras = [Paragraph(f"
{moa}", disc_s)]
+ current_pep_hdr = [Spacer(1, 5), pep_bar] + moa_paras
+ pending_bullets = []
+ continue
+
+ # ── Major section **Bold** or **Bold:** – ROBUST HANDLING ────────────
+ is_section = False
+ if line.startswith("**"):
+ # Handle trailing colon variants: **Header** or **Header:**
+ test = line.rstrip(":").rstrip()
+ if test.endswith("**") and test.count("**") == 2:
+ is_section = True
+
+ if is_section:
+ # Flush any open service block
+ if current_pep_hdr is not None:
+ story.append(KeepTogether(list(current_pep_hdr) + list(pending_bullets)))
+ pending_bullets.clear()
+ current_pep_hdr = None
+
+ clean = line.replace("**","").rstrip(":").strip()
+
+ # Skip redundant headings
+ if clean.lower() in skip_headings:
+ continue
+
+ # Treatment Protocol triggers a new page
+ if not hit_treatment_page and "Treatment Protocol" in clean:
+ hit_treatment_page = True
+ story.append(PageBreak())
+
+ sec_bar = banner(Paragraph(f" {clean}", cond_sh_s),
+ colors.HexColor("#034a6e"), line_below=C_ORANGE,
+ pad_v=5 if hit_treatment_page else 6)
+ spacer_before = 6 if hit_treatment_page else 10
+ spacer_after = 3 if hit_treatment_page else 5
+ story.append(KeepTogether([Spacer(1, spacer_before), sec_bar, Spacer(1, spacer_after)]))
+ continue
+
+ # ── H2 ## ────────────────────────────────────────────────────────────
+ if line.startswith("##"):
+ clean = line.replace("##","").replace("**","").strip()
+ if clean.lower() in skip_headings:
+ continue
+ sp = 2 if hit_treatment_page else 4
+ story.append(KeepTogether([
+ Spacer(1, sp),
+ Paragraph(clean, h3_s),
+ HRFlowable(width="100%", thickness=0.75, color=C_TEAL, spaceAfter=1 if hit_treatment_page else 2),
+ ]))
+ continue
+
+ # ── Bullet ────────────────────────────────────────────────────────────
+ if line and line[0] in ("-","*","•"):
+ clean = re.sub(r"\*\*([^*]+)\*\*", r"
\1", line.lstrip("-*• "))
+ is_sub = indent >= 2
+ if hit_treatment_page:
+ sty = cond_sub_bullet_s if is_sub else cond_bullet_s
+ else:
+ sty = sub_bullet_s if is_sub else bullet_s
+ marker = "–" if is_sub else "•"
+ p = Paragraph(f"{marker} {clean}", sty)
+ if current_pep_hdr is not None:
+ pending_bullets.append(p)
+ else:
+ story.append(p)
+ continue
+
+ # ── Numbered list ─────────────────────────────────────────────────────
+ nm = re.match(r'^(\d+)[.)]\s+(.*)', line)
+ if nm:
+ clean = re.sub(r"\*\*([^*]+)\*\*", r"
\1", nm.group(2))
+ sty = cond_num_s if hit_treatment_page else num_s
+ story.append(Paragraph(f"
{nm.group(1)}. {clean}", sty))
+ continue
+
+ # ── Paragraph — include as body text if it doesn't match any pattern ─
+ if not seen_first_section:
+ continue # drop intro text before first service
+
+ if current_pep_hdr is not None:
+ story.append(KeepTogether(list(current_pep_hdr) + list(pending_bullets)))
+ pending_bullets.clear()
+ current_pep_hdr = None
+
+ clean = re.sub(r"\*\*([^*]+)\*\*", r"
\1", line)
+ clean = re.sub(r"\*([^*]+)\*", r"
\1", clean)
+ sty = cond_body_s if hit_treatment_page else body_s
+ story.append(Paragraph(clean, sty))
+
+ # Flush any trailing service block
+ if current_pep_hdr is not None:
+ story.append(KeepTogether(list(current_pep_hdr) + list(pending_bullets)))
+
+ # ── Footer ────────────────────────────────────────────────────────────────
+ story.append(Spacer(1, 10))
+ story.append(HRFlowable(width="100%", thickness=3, color=C_ORANGE, spaceAfter=0))
+
+ ft_brand_s = ps("Fb", fontName="Helvetica-Bold", fontSize=10, textColor=C_ORANGE, spaceAfter=1, alignment=TA_CENTER)
+ ft_tag_s = ps("Fg", fontName="Helvetica-Bold", fontSize=7.5, textColor=C_ORANGE, spaceAfter=4, alignment=TA_CENTER)
+ ft_info_s = ps("Fi", fontName="Helvetica", fontSize=8, leading=12, textColor=colors.white, spaceAfter=1, alignment=TA_CENTER)
+ ft_web_s = ps("Fw", fontName="Helvetica-Bold", fontSize=8, textColor=C_SKY, spaceAfter=0, alignment=TA_CENTER)
+
+ ft_tbl = Table([
+ [Paragraph("IGNITE PERFORMANCE & HEALTH", ft_brand_s)],
+ [Paragraph("Ignition Sequence™ — Science · Strength · Nutrition", ft_tag_s)],
+ [Paragraph("14830 Clayton Rd, Chesterfield, MO 63017 | (314) 887-0858 | info@ignitepah.com", ft_info_s)],
+ [Paragraph("www.ignitepah.com", ft_web_s)],
+ ], colWidths=[pw])
+ ft_tbl.setStyle(TableStyle([
+ ("BACKGROUND", (0,0),(-1,-1), C_NAVY),
+ ("TOPPADDING", (0,0),(0,0), 8),
+ ("BOTTOMPADDING", (0,-1),(0,-1), 8),
+ ("TOPPADDING", (0,1),(-1,-1), 1),
+ ("BOTTOMPADDING", (0,1),(-1,-1), 1),
+ ("ALIGN", (0,0),(-1,-1), "CENTER"),
+ ]))
+ story.append(ft_tbl)
+
+ doc.build(story)
+ buffer.seek(0)
+ return buffer
+
+
+# =============================================================================
+# CSV EXPORT
+# =============================================================================
+def generate_csv_export(intake_data, recommended_stacks):
+ """Generate CSV export of assessment data."""
+ sio = StringIO()
+ fieldnames = [
+ "Timestamp","First_Name","Last_Name","Email","Phone","Age","Sex",
+ "Primary_Goals","Energy_Recovery_Level","Current_Issues","Physical_Activity_Level",
+ "Medical_Considerations","Medical_Details","Contraindication_Flags","Additional_Information",
+ "Recommended_Stack_1","Recommended_Stack_2","Recommended_Stack_3",
+ "Stack_1_26Week_Price","Stack_1_52Week_Price",
+ "Stack_2_26Week_Price","Stack_2_52Week_Price",
+ "Stack_3_26Week_Price","Stack_3_52Week_Price",
+ "All_Recommended_Services","Top_Targets",
+ ]
+ writer = csv.DictWriter(sio, fieldnames=fieldnames)
+ writer.writeheader()
+
+ def ps_str(idx, field):
+ if idx >= len(recommended_stacks): return ""
+ ps = get_pricing_summary(recommended_stacks[idx])
+ if field == "26_week":
+ return f"${ps['monthly_26wk']:,.2f}"
+ elif field == "52_week":
+ return f"${ps['monthly_52wk']:,.2f}"
+ return ""
+
+ flags = [f for f in intake_data.get("contraindication_flags", []) if f != "None of the above"]
+ row = {
+ "Timestamp": datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
+ "First_Name": intake_data.get("first_name",""),
+ "Last_Name": intake_data.get("last_name",""),
+ "Email": intake_data.get("email",""),
+ "Phone": intake_data.get("phone",""),
+ "Age": intake_data.get("age",""),
+ "Sex": intake_data.get("sex",""),
+ "Primary_Goals": "; ".join(intake_data.get("primary_goals",[])),
+ "Energy_Recovery_Level": intake_data.get("energy_recovery",""),
+ "Current_Issues": "; ".join(intake_data.get("current_issues",[])),
+ "Physical_Activity_Level": intake_data.get("activity_level",""),
+ "Medical_Considerations": intake_data.get("medical_considerations",""),
+ "Medical_Details": intake_data.get("medical_details",""),
+ "Contraindication_Flags": "; ".join(flags) if flags else "None",
+ "Additional_Information": intake_data.get("additional_info",""),
+ "Recommended_Stack_1": recommended_stacks[0] if len(recommended_stacks) > 0 else "",
+ "Recommended_Stack_2": recommended_stacks[1] if len(recommended_stacks) > 1 else "",
+ "Recommended_Stack_3": recommended_stacks[2] if len(recommended_stacks) > 2 else "",
+ "Stack_1_26Week_Price": ps_str(0,"26_week"), "Stack_1_52Week_Price": ps_str(0,"52_week"),
+ "Stack_2_26Week_Price": ps_str(1,"26_week"), "Stack_2_52Week_Price": ps_str(1,"52_week"),
+ "Stack_3_26Week_Price": ps_str(2,"26_week"), "Stack_3_52Week_Price": ps_str(2,"52_week"),
+ "All_Recommended_Services": "; ".join(set(p for sn in recommended_stacks for p in WELLNESS_PROGRAMS[sn]["services"])),
+ "Top_Targets": "; ".join(set(t for sn in recommended_stacks for t in WELLNESS_PROGRAMS[sn]["targets"])),
+ }
+ writer.writerow(row)
+ buf = BytesIO(sio.getvalue().encode("utf-8"))
+ buf.seek(0)
+ return buf
+
+
+# =============================================================================
+# JSON EXPORT
+# =============================================================================
+def generate_json_export(intake_data, recommended_stacks):
+ """Generate JSON export of assessment data."""
+ flags = [f for f in intake_data.get("contraindication_flags", []) if f != "None of the above"]
+ stack_details = []
+ for sn in recommended_stacks:
+ si = WELLNESS_PROGRAMS[sn]
+ ps = get_pricing_summary(sn)
+ stack_details.append({
+ "name": sn, "targets": si["targets"], "description": si["description"],
+ "services": si["services"], "type": si["type"],
+ "cycle": si["cycle"],
+ "contraindication_flags": get_stack_contraindication_summary(sn, flags),
+ "pricing": {
+ "monthly_26week": round(ps["monthly_26wk"], 2),
+ "monthly_52week": round(ps["monthly_52wk"], 2),
+ "all_tiers": ps["all_tiers"],
+ },
+ })
+ export_data = {
+ "timestamp": datetime.now().isoformat(),
+ "patient_info": {
+ "first_name": intake_data.get("first_name",""), "last_name": intake_data.get("last_name",""),
+ "email": intake_data.get("email",""), "phone": intake_data.get("phone",""),
+ "age": intake_data.get("age",""), "sex": intake_data.get("sex",""),
+ },
+ "assessment": {
+ "primary_goals": intake_data.get("primary_goals",[]),
+ "energy_recovery_level": intake_data.get("energy_recovery",""),
+ "current_issues": intake_data.get("current_issues",[]),
+ "physical_activity": intake_data.get("activity_level",""),
+ "medical_considerations": intake_data.get("medical_considerations",""),
+ "medical_details": intake_data.get("medical_details",""),
+ "contraindication_flags": flags,
+ "additional_info": intake_data.get("additional_info",""),
+ },
+ "recommendations": {
+ "stacks": stack_details,
+ "all_services": list(set(p for sn in recommended_stacks for p in WELLNESS_PROGRAMS[sn]["services"])),
+ "top_targets": list(set(t for sn in recommended_stacks for t in WELLNESS_PROGRAMS[sn]["targets"])),
+ },
+ }
+ buf = BytesIO(json.dumps(export_data, indent=2).encode("utf-8"))
+ buf.seek(0)
+ return buf
+
+
+# =============================================================================
+# EMAIL DELIVERY (Outlook SMTP)
+# =============================================================================
+log = logging.getLogger("ignite_email")
+
+def _smtp_send(to_addr, subject, body_text, attachments=None):
+ """Send an email via Outlook SMTP. Returns True on success, error string on failure."""
+ user = os.getenv("SMTP_USER", "").strip()
+ pw = os.getenv("SMTP_PASSWORD", "").strip()
+ if not user or not pw:
+ return "SMTP credentials not configured (SMTP_USER / SMTP_PASSWORD)"
+
+ msg = MIMEMultipart()
+ msg["From"] = user
+ msg["To"] = to_addr
+ msg["Subject"] = subject
+ msg.attach(MIMEText(body_text, "plain"))
+
+ for filename, data, mime_type in (attachments or []):
+ part = MIMEBase(*mime_type.split("/", 1))
+ part.set_payload(data)
+ encoders.encode_base64(part)
+ part.add_header("Content-Disposition", f'attachment; filename="{filename}"')
+ msg.attach(part)
+
+ try:
+ with smtplib.SMTP(EMAIL["smtp_server"], EMAIL["smtp_port"], timeout=15) as srv:
+ srv.ehlo()
+ srv.starttls()
+ srv.ehlo()
+ srv.login(user, pw)
+ srv.sendmail(user, to_addr, msg.as_string())
+ return True
+ except Exception as exc:
+ log.warning("SMTP send failed to %s: %s", to_addr, exc)
+ return str(exc)
+
+
+def send_client_email(patient_email, pdf_bytes):
+ """Send the wellness plan PDF to the patient."""
+ body = EMAIL["client_body"].format(scheduling_url=BRAND["scheduling_url"])
+ return _smtp_send(
+ to_addr=patient_email,
+ subject=EMAIL["client_subject"],
+ body_text=body,
+ attachments=[("Ignite_Ignition_Sequence.pdf", pdf_bytes, "application/pdf")],
+ )
+
+
+def send_clinic_email(intake_data, pdf_bytes, csv_bytes, json_bytes):
+ """Send full intake data + exports to the clinic."""
+ flags = intake_data.get("contraindication_flags", [])
+ flag_str = ", ".join(flags) if flags else "None"
+ body = (
+ f"New assessment completed — {intake_data.get('name', 'Unknown')}\n"
+ f"{'=' * 50}\n\n"
+ f"Email: {intake_data.get('email', '')}\n"
+ f"Phone: {intake_data.get('phone', '')}\n"
+ f"Age: {intake_data.get('age', '')} | Sex: {intake_data.get('sex', '')}\n\n"
+ f"Primary Goals: {', '.join(intake_data.get('primary_goals', []))}\n"
+ f"Energy & Recovery: {intake_data.get('energy_recovery', '')}\n"
+ f"Current Issues: {', '.join(intake_data.get('current_issues', []))}\n"
+ f"Activity Level: {intake_data.get('activity_level', '')}\n"
+ f"Contraindication Flags: {flag_str}\n"
+ f"Medical Considerations: {intake_data.get('medical_considerations', '')}\n"
+ f"Additional Info: {intake_data.get('additional_info', '')}\n\n"
+ f"PDF, CSV, and JSON exports attached."
+ )
+ safe_name = (intake_data.get("name", "patient")).replace(" ", "_")
+ return _smtp_send(
+ to_addr=EMAIL["clinic_recipient"],
+ subject=f"{EMAIL['clinic_subject']} — {intake_data.get('name', '')}",
+ body_text=body,
+ attachments=[
+ (f"Ignite_{safe_name}.pdf", pdf_bytes, "application/pdf"),
+ (f"Ignite_{safe_name}.csv", csv_bytes, "text/csv"),
+ (f"Ignite_{safe_name}.json", json_bytes, "application/json"),
+ ],
+ )
+
+
+# =============================================================================
+# FOOTER RENDERER
+# =============================================================================
+def render_footer():
+ """Render the footer with contact and disclaimer info."""
+ st.markdown(
+ '',
+ unsafe_allow_html=True,
+ )
+
+
+# =============================================================================
+# PAGE CONFIG & CSS
+# =============================================================================
+st.set_page_config(page_title="Ignite Performance & Health — Ignition Sequence", layout="wide", initial_sidebar_state="collapsed")
+
+st.markdown("""
+
+""", unsafe_allow_html=True)
+
+# ── Branded spinner overlay (separate block for dynamic logo injection) ──
+st.markdown(
+ f"""""",
+ unsafe_allow_html=True,
+)
+
+
+# =============================================================================
+# SESSION STATE INIT
+# =============================================================================
+if "wellness_pathway" not in st.session_state:
+ st.session_state["wellness_pathway"] = None
+ st.session_state["recommended_stacks"] = []
+ st.session_state["all_eligible_stacks"] = []
+ st.session_state["intake_data"] = {}
+ st.session_state["patient_name"] = ""
+ st.session_state["patient_email"] = ""
+ st.session_state["patient_phone"] = ""
+ st.session_state["patient_age"] = ""
+ st.session_state["patient_sex"] = ""
+ st.session_state["contraindication_flags"] = []
+ st.session_state["scroll_to_top"] = False
+ st.session_state["results_scroll_done"] = False
+
+
+# =============================================================================
+# RESULTS PAGE
+# =============================================================================
+if st.session_state["wellness_pathway"]:
+
+ import streamlit.components.v1 as components
+ st.markdown('
', unsafe_allow_html=True)
+
+ pname = st.session_state.get("patient_name", "")
+ pemail = st.session_state.get("patient_email", "")
+ pphone = st.session_state.get("patient_phone", "")
+ page = st.session_state.get("patient_age", "")
+ psex = st.session_state.get("patient_sex", "")
+
+ # ── Results hero ──────────────────────────────────────────────────────────
+ st.markdown(
+ '
'
+ f'

'
+ '
'
+ '
Your Program Recommendation
'
+ f'
Prepared for {pname} • Physician-Supervised • Personalized to Your Goals
'
+ '
',
+ unsafe_allow_html=True,
+ )
+
+ # ── All eligible programs — sorted: robust/expensive first, med-only last ─
+ primary_recs = set(st.session_state["recommended_stacks"])
+ all_eligible = st.session_state.get("all_eligible_stacks", list(primary_recs))
+ patient_flags = st.session_state.get("contraindication_flags", [])
+ active_flags = [f for f in patient_flags if f != "None of the above"]
+
+ display_stacks = sorted(
+ all_eligible,
+ key=lambda sn: WELLNESS_PROGRAMS[sn].get("display_order", 50),
+ )
+
+ all_stack_services = set()
+ for sn in display_stacks:
+ all_stack_services.update(WELLNESS_PROGRAMS[sn]["services"])
+
+ # Separate into best-match and also-available for section headers
+ best_match = [sn for sn in display_stacks if sn in primary_recs]
+ also_available = [sn for sn in display_stacks if sn not in primary_recs]
+
+ st.markdown("### Your Best-Match Program(s)")
+
+ for i, sn in enumerate(best_match + also_available):
+ # Insert section header when switching from best-match to also-available
+ if i == len(best_match) and also_available:
+ st.markdown('
', unsafe_allow_html=True)
+ st.markdown("### Also Available")
+
+ stack = WELLNESS_PROGRAMS[sn]
+ is_primary = sn in primary_recs
+ targets_str = " · ".join(stack["targets"])
+
+ # Badge label — tiered: Best (most robust) → Better → Good
+ if is_primary:
+ badge_idx = best_match.index(sn)
+ tier_label = ["BEST", "BETTER", "GOOD"][badge_idx] if badge_idx < 3 else "MATCH"
+ badge_html = f'
{tier_label}
'
+ else:
+ badge_html = f'
ALSO AVAILABLE
'
+
+ # Contraindication check
+ contra_html = ""
+ if active_flags:
+ hits = check_contraindications(stack["services"], active_flags)
+ if hits:
+ svc_list = ", ".join(hits.keys())
+ contra_html = (
+ f'
'
+ f'⚠ Physician Review Required — '
+ f'{svc_list} may need adjustment based on your health history. '
+ f'Our medical team will evaluate during your consultation.'
+ f'
'
+ )
+
+ # Service pills with MOA tooltips
+ badges = ""
+ for p in stack["services"]:
+ moa = SERVICE_MOA.get(p, "")
+ moa_html = f'
{moa}' if moa else ""
+ badges += f'
{p}{moa_html}
'
+
+ # Build tier pricing grid (config values are monthly — convert to weekly)
+ sorted_tiers = sorted(stack["tiers"].items(), key=lambda x: x[1]["sort_order"])
+ tier_cells = ""
+ for tier_name, tier_data in sorted_tiers:
+ tier_desc = ""
+ if stack["type"] == "training":
+ tier_info = TRAINING_TIERS.get(tier_name, {})
+ desc_text = tier_info.get("training_desc", "")
+ if desc_text:
+ tier_desc = f'
{desc_text}
'
+ us_text = tier_info.get("ultrasound", "")
+ if us_text:
+ tier_desc += f'
Ultrasound: {us_text}
'
+ # Config stores monthly rates — convert to per-week: monthly * 12 / 52
+ pw_52 = tier_data["52_week"] * 12.0 / 52.0
+ pw_26 = tier_data["26_week"] * 12.0 / 52.0
+ tier_cells += (
+ f'
'
+ f'
{tier_name}
'
+ f'{tier_desc}'
+ f'
${pw_52:,.2f}
'
+ f'
per week · 52-week
'
+ f'
${pw_26:,.2f}/wk · 26-week
'
+ f'
'
+ )
+
+ pricing_grid = f'
{tier_cells}
'
+
+ st.markdown(
+ f'
'
+ f'{badge_html}'
+ f'{contra_html}'
+ f'
{sn}
'
+ f'
{stack.get("subtitle", "")}
'
+ f'
{stack["description"]}
'
+ f'
'
+ f'
'
+ f'
Targets
'
+ f'
{targets_str}
'
+ f'
Cycle Protocol
'
+ f'
{stack["cycle"]}
'
+ f'
'
+ f'
'
+ f'
Included Services — hover for details
'
+ f'
{badges}
'
+ f'
'
+ f'
'
+ f'
PRICING OPTIONS
'
+ f'{pricing_grid}'
+ f'
',
+ unsafe_allow_html=True,
+ )
+
+ # ── Pathway content ───────────────────────────────────────────────────────
+ st.markdown('
', unsafe_allow_html=True)
+ pathway_html = render_pathway_html(
+ st.session_state["wellness_pathway"],
+ all_stack_services
+ )
+ st.markdown(pathway_html, unsafe_allow_html=True)
+
+ # ── Email status ──────────────────────────────────────────────────────────
+ for level, msg in st.session_state.get("email_status", []):
+ if level == "success":
+ st.success(msg)
+ else:
+ st.warning(msg)
+
+ # ── CTA box ───────────────────────────────────────────────────────────────
+ st.markdown(
+ f'
'
+ f'
Ready to Begin, {pname}?
'
+ f'
Your recommendation is ready. Our team will review your assessment '
+ f'and reach out to schedule your consultation within 24–48 hours.
'
+ f'
We\'ll contact you at: {pemail} | {pphone}
'
+ f'
Your next steps:
'
+ f'
'
+ f'- Our team contacts you within 24–48 hours
'
+ f'- Schedule your consultation
'
+ f'- Complete baseline bloodwork and health assessment
'
+ f'- Receive your program details and get started
'
+ f'- Ongoing support as your plan evolves
'
+ f'
'
+ f'
Visit us: 14830 Clayton Rd, Chesterfield, MO 63017 | '
+ f'314-887-0858 | info@ignitepah.com
'
+ f'
→ Schedule Your Consultation'
+ f'
',
+ unsafe_allow_html=True,
+ )
+
+ # ── Downloads ─────────────────────────────────────────────────────────────
+ st.markdown('
', unsafe_allow_html=True)
+ st.markdown("#### Download Your Results")
+ st.caption("Save your Ignition Sequence recommendation or export data for clinic use.")
+
+ c1, c2, c3 = st.columns(3)
+ with c1:
+ pdf_buf = generate_wellness_pdf(
+ st.session_state["wellness_pathway"],
+ {"name": pname, "email": pemail, "phone": pphone,
+ "age": page, "sex": psex,
+ "date": datetime.now().strftime("%B %d, %Y")},
+ st.session_state["recommended_stacks"],
+ st.session_state.get("contraindication_flags", []),
+ )
+ st.download_button(
+ "PDF Report", data=pdf_buf, file_name="ignite_ignition_sequence.pdf",
+ mime="application/pdf", use_container_width=True,
+ )
+ with c2:
+ csv_buf = generate_csv_export(st.session_state.get("intake_data", {}), st.session_state["recommended_stacks"])
+ safe_name = (pname or "patient").replace(" ", "_")
+ st.download_button(
+ "CSV Export", data=csv_buf, file_name=f"ignite_{safe_name}.csv",
+ mime="text/csv", use_container_width=True,
+ )
+ with c3:
+ json_buf = generate_json_export(st.session_state.get("intake_data", {}), st.session_state["recommended_stacks"])
+ st.download_button(
+ "JSON Export", data=json_buf, file_name=f"ignite_{safe_name}.json",
+ mime="application/json", use_container_width=True,
+ )
+
+ st.info(
+ "**Clinic staff:** CSV and JSON exports include full intake data, contraindication flags, "
+ "and per-week / per-month / total pricing — ready for your EMR or CRM.",
+ icon="ℹ️",
+ )
+
+ # ── Start over button ─────────────────────────────────────────────────────
+ st.markdown('
', unsafe_allow_html=True)
+ if st.button("← Start New Assessment", use_container_width=True):
+ for key in ["wellness_pathway", "recommended_stacks", "all_eligible_stacks", "intake_data",
+ "patient_name", "patient_email", "patient_phone", "patient_age", "patient_sex",
+ "contraindication_flags", "email_status"]:
+ st.session_state[key] = None if key == "wellness_pathway" else (
+ [] if key in ["recommended_stacks", "all_eligible_stacks", "contraindication_flags", "email_status"] else ""
+ )
+ st.session_state["scroll_to_top"] = True
+ st.rerun()
+
+ render_footer()
+
+ # ── Scroll to top (fires ONCE on first results render, then stops) ──────
+ if not st.session_state.get("results_scroll_done", False):
+ st.session_state["results_scroll_done"] = True
+ components.html(
+ """
+
+ """,
+ height=0,
+ )
+
+ st.stop()
+
+
+# =============================================================================
+# INTAKE FORM PAGE
+# =============================================================================
+
+st.markdown('
', unsafe_allow_html=True)
+
+if st.session_state.pop("scroll_to_top", False):
+ components.html(
+ """
+
+ """,
+ height=0,
+ )
+
+st.markdown(
+ '
'
+ f'

'
+ '
Ignition Sequence™
'
+ '
Find Your Program
'
+ '
Physician-Supervised • Personalized to Your Biology
'
+ '
',
+ unsafe_allow_html=True,
+)
+st.markdown(
+ "Answer a few questions about your goals and where you are right now. "
+ "We'll match you to the Ignition Sequence program that fits best. "
+ "Our team reviews every result and will reach out within 24\u201348 hours to schedule your consultation.",
+)
+
+# ── Intake form ───────────────────────────────────────────────────────────────
+with st.form("wellness_intake_form"):
+
+ st.markdown('
Your Information
', unsafe_allow_html=True)
+ c1, c2 = st.columns(2)
+ with c1:
+ first_name = st.text_input("First Name *")
+ last_name = st.text_input("Last Name *")
+ email = st.text_input("Email Address *")
+ with c2:
+ phone = st.text_input("Cell Phone *")
+ age = st.number_input("Age *", min_value=18, max_value=100, value=35)
+ sex = st.radio("Biological Sex *", ["Male", "Female"], index=None)
+
+ st.markdown('
1 — Current Symptoms
', unsafe_allow_html=True)
+ st.caption("Check anything you're currently experiencing.")
+
+ # Split symptoms dynamically into two columns
+ symptom_keys = list(CURRENT_SYMPTOMS.keys())
+ mid = (len(symptom_keys) + 1) // 2
+ symptom_checks = {}
+
+ c1, c2 = st.columns(2)
+ with c1:
+ for symptom in symptom_keys[:mid]:
+ symptom_checks[symptom] = st.checkbox(symptom, key=f"symptom_{symptom}")
+ with c2:
+ for symptom in symptom_keys[mid:]:
+ symptom_checks[symptom] = st.checkbox(symptom, key=f"symptom_{symptom}")
+
+ st.markdown('
2 — Primary Goals
', unsafe_allow_html=True)
+ st.caption("Select everything that applies to you.")
+
+ # Split goals dynamically into two columns
+ goal_keys = list(PRIMARY_GOALS.keys())
+ mid = (len(goal_keys) + 1) // 2
+ goal_checks = {}
+
+ c1, c2 = st.columns(2)
+ with c1:
+ for goal in goal_keys[:mid]:
+ goal_checks[goal] = st.checkbox(goal, key=f"goal_{goal}")
+ with c2:
+ for goal in goal_keys[mid:]:
+ goal_checks[goal] = st.checkbox(goal, key=f"goal_{goal}")
+
+ st.markdown('
3 — Physical Activity
', unsafe_allow_html=True)
+ activity_level = st.radio(
+ "What best describes your current activity level? *",
+ ACTIVITY_LEVELS,
+ index=None,
+ )
+
+ st.markdown('
4 — Energy & Recovery
', unsafe_allow_html=True)
+ energy_recovery = st.radio(
+ "How would you describe your current energy and physical recovery? *",
+ list(ENERGY_RECOVERY_OPTIONS.keys()),
+ index=None,
+ )
+
+ st.markdown('
5 — Medical History
', unsafe_allow_html=True)
+ medical_care = st.radio(
+ "Are you currently receiving care for hormone imbalances, cancer, or other chronic conditions not listed above? *",
+ ["No", "Yes — please describe below"], index=None,
+ )
+ medical_details = st.text_area(
+ "If yes, please provide details:",
+ placeholder="Current conditions, medications, or recent treatments...",
+ )
+
+ st.markdown('
6 — Safety Screening
', unsafe_allow_html=True)
+ st.caption(
+ "Check any of the following that apply. This allows our physician team to identify services "
+ "requiring modification or review. Checking an item does not exclude you from therapy."
+ )
+ flag_options = [f for f in ALL_CONTRAINDICATION_FLAGS if f != "None of the above"]
+ mid = (len(flag_options) + 1) // 2
+ contra_checks = {}
+ cf1, cf2 = st.columns(2)
+ with cf1:
+ for flag in flag_options[:mid]:
+ contra_checks[flag] = st.checkbox(flag, key=f"cf_{flag}")
+ with cf2:
+ for flag in flag_options[mid:]:
+ contra_checks[flag] = st.checkbox(flag, key=f"cf_{flag}")
+ none_of_above = st.checkbox("None of the above", key="cf_none")
+
+ st.markdown('
Anything Else?
', unsafe_allow_html=True)
+ additional_info = st.text_area(
+ "Previous experience with training programs, specific concerns, or timeline goals:",
+ placeholder="Optional — any context that may help our physician team.",
+ )
+
+ # ── Consent & Disclaimers ──────────────────────────────────────────────────
+ st.markdown('
Consent & Privacy
', unsafe_allow_html=True)
+
+ st.markdown(
+ '
'
+ 'Important: This platform is '
+ 'not HIPAA-compliant. Any personal or health information you '
+ 'enter may be transmitted and stored on servers that do not meet HIPAA security '
+ 'standards. Please do not share sensitive medical records, Social Security numbers, '
+ 'or information you would not be comfortable disclosing on a non-secure platform.
',
+ unsafe_allow_html=True,
+ )
+
+ consent_privacy = st.checkbox(
+ "I understand this platform is NOT HIPAA-compliant and that any information "
+ "I provide may not be protected under HIPAA privacy regulations.",
+ key="consent_privacy",
+ )
+ consent_contact = st.checkbox(
+ "I consent to being contacted by Ignite Performance & Health via the email "
+ "address and phone number I have provided above regarding my assessment "
+ "and available programs.",
+ key="consent_contact",
+ )
+
+ submitted = st.form_submit_button("Launch Your Ignition Sequence", use_container_width=True)
+
+# ── Form processing ───────────────────────────────────────────────────────────
+if submitted:
+ primary_goals = [g for g, checked in goal_checks.items() if checked]
+ current_issues = [i for i, checked in symptom_checks.items() if checked]
+
+ if none_of_above:
+ selected_flags = ["None of the above"]
+ else:
+ selected_flags = [flag for flag, checked in contra_checks.items() if checked]
+ if not selected_flags:
+ selected_flags = ["None of the above"]
+
+ errors = []
+ if not first_name: errors.append("First Name")
+ if not last_name: errors.append("Last Name")
+ if not email: errors.append("Email Address")
+ if not phone: errors.append("Cell Phone")
+ if sex is None: errors.append("Biological Sex")
+ if not primary_goals: errors.append("Primary Goals (select at least one)")
+ if energy_recovery is None: errors.append("Energy & Recovery level")
+ if activity_level is None: errors.append("Physical Activity level")
+ if medical_care is None: errors.append("Medical History")
+ if not consent_privacy: errors.append("Privacy acknowledgment (required)")
+ if not consent_contact: errors.append("Consent to contact (required)")
+
+ # ── Male-specific restrictions ────────────────────────────────────────────
+ male_conflicts = []
+ if sex == "Male":
+ male_restricted_goals = [g for g in primary_goals if g in MALE_RESTRICTED_GOALS]
+ male_restricted_syms = [s for s in current_issues if s in MALE_RESTRICTED_SYMPTOMS]
+ male_restricted_flags = [f for f in selected_flags if f in MALE_RESTRICTED_FLAGS]
+
+ if male_restricted_goals:
+ male_conflicts.append(f"Primary Goals: {', '.join(male_restricted_goals)}")
+ if male_restricted_syms:
+ male_conflicts.append(f"Symptoms: {', '.join(male_restricted_syms)}")
+ if male_restricted_flags:
+ male_conflicts.append(f"Flags: {', '.join(male_restricted_flags)}")
+
+ if errors:
+ st.error(f"Please complete the following required fields: **{', '.join(errors)}**")
+ st.stop()
+
+ # ── "None of the above" conflict check ────────────────────────────────────
+ other_flags_checked = [flag for flag, checked in contra_checks.items() if checked]
+ if none_of_above and other_flags_checked:
+ st.error(
+ "⚠ You selected **\"None of the above\"** in Safety Screening but also checked: "
+ f"**{', '.join(other_flags_checked)}**. Please select one or the other."
+ )
+ st.stop()
+
+ if male_conflicts:
+ st.error(
+ f"⚠ The following selections are not applicable for male patients: "
+ f"**{', '.join(male_conflicts)}**. Please uncheck them and resubmit."
+ )
+ st.stop()
+
+ # Scroll parent to top AND force-position spinner when it appears
+ components.html(
+ """""",
+ height=0,
+ )
+ with st.spinner("Developing your Ignition Sequence..."):
+ goals_data = {"primary_goals": primary_goals, "energy_recovery": energy_recovery,
+ "current_issues": current_issues, "activity_level": activity_level}
+ target_scores = map_goals_to_targets(goals_data)
+ rec_stacks, _, all_eligible = recommend_programs(target_scores, len(primary_goals), patient_sex=sex, patient_flags=selected_flags)
+ med = medical_details if medical_care == "Yes — please describe below" else "No current conditions reported"
+
+ intake_data = {
+ "name": f"{first_name} {last_name}", "first_name": first_name, "last_name": last_name,
+ "email": email, "phone": phone, "age": age, "sex": sex,
+ "primary_goals": primary_goals, "energy_recovery": energy_recovery,
+ "current_issues": current_issues, "activity_level": activity_level,
+ "medical_considerations": med, "medical_details": medical_details or "",
+ "contraindication_flags": selected_flags,
+ "additional_info": additional_info or "None provided",
+ }
+
+ pathway = generate_wellness_pathway(intake_data, rec_stacks, selected_flags)
+
+ st.session_state["wellness_pathway"] = pathway
+ st.session_state["results_scroll_done"] = False # allow one scroll-to-top on results load
+ st.session_state["recommended_stacks"] = rec_stacks
+ st.session_state["all_eligible_stacks"] = all_eligible
+ st.session_state["intake_data"] = intake_data
+ st.session_state["patient_name"] = f"{first_name} {last_name}"
+ st.session_state["patient_email"] = email
+ st.session_state["patient_phone"] = phone
+ st.session_state["patient_age"] = age
+ st.session_state["patient_sex"] = sex
+ st.session_state["contraindication_flags"] = selected_flags
+
+ # ── Auto-send emails ──────────────────────────────────────────────
+ patient_info = {
+ "name": f"{first_name} {last_name}", "email": email, "phone": phone,
+ "age": age, "sex": sex, "date": datetime.now().strftime("%B %d, %Y"),
+ }
+ pdf_buf = generate_wellness_pdf(pathway, patient_info, rec_stacks, selected_flags)
+ pdf_bytes = pdf_buf.getvalue()
+ csv_buf = generate_csv_export(intake_data, rec_stacks)
+ csv_bytes = csv_buf.getvalue()
+ json_buf = generate_json_export(intake_data, rec_stacks)
+ json_bytes = json_buf.getvalue()
+
+ client_result = send_client_email(email, pdf_bytes)
+ clinic_result = send_clinic_email(intake_data, pdf_bytes, csv_bytes, json_bytes)
+
+ email_status = []
+ if client_result is True and clinic_result is True:
+ email_status = [("success", "Your results have been sent to your email.")]
+ else:
+ if client_result is not True:
+ email_status.append(("warning", f"Patient email not sent: {client_result}"))
+ if clinic_result is not True:
+ email_status.append(("warning", f"Clinic email not sent: {clinic_result}"))
+ st.session_state["email_status"] = email_status
+
+ st.rerun()
+
+render_footer()