oneblackmage/session-scripts / generate_medication_sessions.py
oneblackmage's picture
download
raw
22.7 kB
#!/usr/bin/env python3
"""
Pixelated Empathy — Medication Management Session Generator
Fills Gap 6: psychiatric medication management sessions.
4 categories × 50 sessions = 200 sessions total.
Clinician is a Psychiatric Nurse Practitioner (NP), not a therapist.
Shorter sessions (8-12 turns) reflecting med management appointments.
Usage:
python generate_medication_sessions.py [--categories all] [--sessions-per-category 50] [--resume]
"""
import json
import os
import random
import sys
import time
import argparse
from pathlib import Path
from session_base import (
OLLAMA_BASE,
THERAPIST_MODEL,
PATIENT_MODEL,
THERAPIST_TEMP,
PATIENT_TEMP,
MAX_RETRIES,
RETRY_DELAY,
STYLE_MAX_RETRIES,
FORBIDDEN_OUTPUT_OPENINGS,
PLATITUDE_PATTERNS,
ROBOTIC_SIGNALS,
SYCOPHANCY_MARKERS,
THERAPIST_STYLE_PROFILES,
PATIENT_SYSTEM,
ollama_chat,
_check_style,
session_exists,
)
NP_SYSTEM_BASE = (
"You are a Psychiatric Nurse Practitioner conducting a medication management appointment. "
"You are warm but more direct and medical than a therapist. You discuss psychiatric medications, "
"their efficacy, side effects, dosage adjustments, and safety. You explain risks and benefits clearly. "
"You screen for substance use and drug interactions. You prioritize patient safety and informed consent.\n\n"
"CRITICAL — Sound like a REAL human clinician:\n"
"- NEVER use formulaic phrases like 'I hear that you feel', 'I want to validate', "
"'That sounds really difficult', 'I can see how that would be', or 'What I'm hearing is...'\n"
"- Use natural, conversational language — contractions, varied sentence length.\n"
"- Be direct about medical matters without being cold.\n"
"- NEVER start responses with: " + ", ".join(f"'{p}'" for p in FORBIDDEN_OUTPUT_OPENINGS[:10]) + "\n"
"- NEVER use platitudes like: " + ", ".join(f"'{p}'" for p in PLATITUDE_PATTERNS) + "\n"
"- NEVER use robotic AI language like: " + ", ".join(f"'{p}'" for p in ROBOTIC_SIGNALS) + "\n"
)
CATEGORIES = {
"initial_med_consult": {
"name": "Initial Medication Consultation",
"difficulty": "low-medium",
"presentations": [
"first-time SSRI — patient nervous about starting antidepressants, NP explains mechanism, addresses fears",
"anxiety medication exploration — patient wants something for anxiety, NP discusses options (SSRI vs buspirone vs short-term benzo)",
"ADHD evaluation — adult seeking ADHD evaluation, NP conducts screening, discusses stimulant vs non-stimulant options",
"sleep medication consult — patient on insomnia meds, wants to stop, NP discusses tapering and sleep hygiene",
],
"patient_personas": [
{
"age": "28",
"gender": "female",
"occupation": "teacher",
"presenting": "Therapist recommended meds, nervous about side effects, never taken psych meds before",
},
{
"age": "35",
"gender": "male",
"occupation": "engineer",
"presenting": "Anxiety interfering with work, wants something to take the edge off, worried about dependence",
},
{
"age": "31",
"gender": "non-binary",
"occupation": "designer",
"presenting": "Struggled with focus since college, wondering about ADHD, worried about stimulant stigma",
},
{
"age": "45",
"gender": "female",
"occupation": "nurse",
"presenting": "Been on zolpidem for 2 years, wants to stop, worried about rebound insomnia",
},
],
"therapist_techniques": [
"shared decision making — present options, explain pros/cons, let patient choose",
"psychoeducation — explain how SSRIs work, timeline for effect, common side effects",
"risk-benefit framing — weigh medication risks against untreated condition risks",
"fear normalization — acknowledge fear of psych meds, provide factual reassurance",
"start low go slow — explain dosing strategy, reassure about gradual onset",
"informed consent process — explain off-label use, alternatives, right to refuse",
],
"therapist_prompt_addon": (
"You're a Psychiatric NP doing an initial medication consult. You take a brief history, "
"discuss medication options, and address fears about psychiatric medications. You explain "
"mechanisms in plain language. You're patient with first-timers. You present options as "
"choices, not orders. You always explain the timeline (SSRIs take 4-6 weeks) and set "
"realistic expectations."
),
},
"side_effects_management": {
"name": "Side Effects Management",
"difficulty": "medium",
"presentations": [
"SSRI sexual dysfunction — patient on sertraline, experiencing loss of libido, embarrassed to bring it up",
"weight gain on antipsychotic — patient on quetiapine, gained 15 lbs, frustrated and considering stopping",
"sedation and fatigue — patient on mirtazapine, can't function during day, considering switching",
"GI side effects — patient on new SSRI, nausea and digestive issues in first 2 weeks",
"emotional numbing — patient on SSRI, feels 'flat,' can't cry or feel joy, questioning if meds are working",
],
"patient_personas": [
{
"age": "30",
"gender": "male",
"occupation": "software developer",
"presenting": "On sertraline 3 months, lost all sex drive, embarrassed, relationship suffering",
},
{
"age": "38",
"gender": "female",
"occupation": "teacher",
"presenting": "On quetiapine for bipolar, gained 15 pounds in 2 months, angry and considering stopping",
},
{
"age": "42",
"gender": "male",
"occupation": "truck driver",
"presenting": "On mirtazapine, can't stay awake at work, nearly got in accident, desperate",
},
{
"age": "26",
"gender": "female",
"occupation": "barista",
"presenting": "Started fluoxetine 2 weeks ago, constant nausea, wondering if it's worth it",
},
],
"therapist_techniques": [
"side effect assessment — systematically review side effects, normalize as common and often temporary",
"dosage adjustment — discuss lowering dose, splitting dosing, or timing changes",
"medication switching — explain cross-taper process, what to expect during switch",
"augmentation strategies — discuss adding medications to counteract side effects (e.g., bupropion for SSRI sexual dysfunction)",
"lifestyle interventions — discuss dietary changes, exercise, timing strategies for specific side effects",
"risk-benefit re-evaluation — weigh side effects against therapeutic benefit, patient-centered decision",
],
"therapist_prompt_addon": (
"You're a Psychiatric NP managing medication side effects. You take side effects seriously "
"and never dismiss them. You're comfortable discussing sexual side effects without awkwardness. "
"You explain which side effects are temporary vs persistent. You offer concrete options: "
"dose adjustment, switching meds, augmentation. You involve the patient in the decision. "
"You never just say 'give it time' when the patient is suffering."
),
},
"medication_adherence": {
"name": "Medication Adherence",
"difficulty": "medium",
"presentations": [
"stopped because feeling better — patient stopped meds when symptoms improved, now symptoms returning",
"stopped because side effects — patient quit without telling NP, now in withdrawal, scared",
"stigma and shame — patient embarrassed about taking psych meds, hides from family, considering stopping",
"cost barrier — patient can't afford medication, rationing doses, NP explores alternatives",
"intermittent adherence — patient takes meds sometimes, forgets often, NP problem-solves",
],
"patient_personas": [
{
"age": "33",
"gender": "female",
"occupation": "marketing manager",
"presenting": "Stopped SSRIs 3 weeks ago because she felt better, now crashing, scared and ashamed",
},
{
"age": "40",
"gender": "male",
"occupation": "construction worker",
"presenting": "Stopped bupropion cold turkey, having brain zaps and mood swings, didn't know you shouldn't quit abruptly",
},
{
"age": "24",
"gender": "male",
"occupation": "student",
"presenting": "Won't take meds where roommates can see, feels weak for needing them, considering stopping",
},
{
"age": "50",
"gender": "female",
"occupation": "home health aide",
"presenting": "Can't afford $200/month for brand name, been taking half doses, running out",
},
],
"therapist_techniques": [
"motivational interviewing — explore reasons for stopping, validate concerns, evoke adherence motivation",
"psychoeducation about relapse — explain why meds need to continue even when feeling better",
"tapering protocol — for those who stopped, explain safe restart or taper plan",
"stigma exploration — discuss societal attitudes, help patient reframe meds as healthcare, not weakness",
"cost navigation — explore generic alternatives, patient assistance programs, sliding scale pharmacies",
"practical adherence strategies — pill organizers, phone alarms, tying to daily routine, 90-day supplies",
],
"therapist_prompt_addon": (
"You're a Psychiatric NP addressing medication adherence. You never shame patients for stopping "
"meds. You explore reasons with curiosity, not judgment. You explain the biology of relapse — "
"feeling better means meds are working, not that they're cured. You're practical about barriers "
"(cost, stigma, forgetfulness) and offer concrete solutions. You understand that adherence is "
"a process, not an event."
),
},
"controlled_substance_boundaries": {
"name": "Controlled Substance Boundaries",
"difficulty": "high",
"presentations": [
"early refill request — patient says they 'lost' their stimulant prescription, requesting early refill",
"dose escalation pressure — patient on benzodiazepines, pushing for higher dose, signs of tolerance",
"drug-seeking patterns — new patient with specific benzo/stimulant request, vague history, insist on controlled substance",
"diversion concern — family member says patient is selling their stimulant medication, NP must address",
"polysubstance risk — patient on multiple controlled substances, requesting another, NP must assess safety",
],
"patient_personas": [
{
"age": "27",
"gender": "male",
"occupation": "sales rep",
"presenting": "Called saying Adderall prescription was 'stolen,' wants early refill, third time this year",
},
{
"age": "34",
"gender": "female",
"occupation": "nurse",
"presenting": "On Xanax 2 years, wants higher dose, gets angry when NP suggests tapering, signs of tolerance",
},
{
"age": "29",
"gender": "male",
"occupation": "unemployed",
"presenting": "New patient, knows exactly which benzo he wants, vague symptoms, refuses non-controlled alternatives",
},
{
"age": "22",
"gender": "female",
"occupation": "student",
"presenting": "Mother called saying daughter is selling her Vyvanse, NP needs to address with patient directly",
},
{
"age": "45",
"gender": "male",
"occupation": "business owner",
"presenting": "Already on opioid pain meds from PCP, asking for benzodiazepines for 'sleep,' high overdose risk",
},
],
"therapist_techniques": [
"boundary setting — clear, firm, compassionate refusal when clinically inappropriate",
"controlled substance agreement — review PDMP, establish expectations, document everything",
"risk assessment — screen for substance use disorder, assess overdose risk, consider naloxone",
"motivational interviewing for taper — explore ambivalence about controlled substances, plant seeds for change",
"alternative treatment framing — offer non-controlled alternatives with clear rationale",
"safety-first approach — when diversion or polysubstance risk identified, prioritize patient safety",
],
"therapist_prompt_addon": (
"You're a Psychiatric NP navigating controlled substance management. You set firm, compassionate "
"boundaries. You can say no without being punitive. You check the PDMP (prescription drug monitoring "
"program). You document everything. You recognize drug-seeking behavior without assuming everyone "
"who asks for meds is drug-seeking. You offer alternatives. You prioritize patient safety over "
"patient satisfaction. You never prescribe controlled substances when it's not clinically appropriate, "
"even under pressure."
),
},
}
def generate_np_turn(persona, presentation, category, conversation, turn_num, style_profile):
"""Generate NP response with technique injection."""
technique = random.choice(category["therapist_techniques"])
technique_guidance = (
f"\n\n[INTERNAL CLINICAL GUIDANCE — embody, never state explicitly]: "
f"Use this approach naturally: {technique}. "
f"Weave it into the conversation — don't announce it. "
f"Respond to what the patient actually said."
)
style_guidance = (
f"\n\nSTYLE: {style_profile['description']}\n"
f"NEVER start with: {', '.join(style_profile['forbidden_openings'])}\n"
f"Good examples: {'; '.join(style_profile['good_examples'][:3])}\n"
f"MAX {style_profile['max_sentences']} sentences, {style_profile['max_words']} words."
)
addon = f"\n\n{category['therapist_prompt_addon']}"
system_content = NP_SYSTEM_BASE + technique_guidance + style_guidance + addon
messages = [{"role": "system", "content": system_content}, *conversation]
return ollama_chat(messages, model=THERAPIST_MODEL, temperature=THERAPIST_TEMP, num_predict=400)
def generate_np_turn_validated(persona, presentation, category, conversation, turn_num, style_profile):
for attempt in range(STYLE_MAX_RETRIES):
output = generate_np_turn(persona, presentation, category, conversation, turn_num, style_profile)
passed, reason = _check_style(output, style_profile)
if passed:
return output
print(f" [style retry {attempt + 1}/{STYLE_MAX_RETRIES}] {reason}")
return output
def generate_med_session(category_key, category, persona, presentation, session_idx):
"""Generate a complete medication management session."""
min_turns = 8
max_turns = 12
total_turns = random.randint(min_turns // 2, max_turns // 2) * 2
total_patient_turns = total_turns // 2
style_keys = list(THERAPIST_STYLE_PROFILES.keys())
style_profile = THERAPIST_STYLE_PROFILES[style_keys[session_idx % len(style_keys)]]
conversation = []
for turn in range(1, total_patient_turns + 1):
# Reuse patient turn from session_base logic
if turn == 1:
direction = f"The patient is arriving for a medication management appointment. Their presenting concern: {presentation}."
elif turn <= 3:
direction = "The patient is sharing more about their experience with medications."
elif turn == total_patient_turns:
direction = "Final turn. The patient is wrapping up, maybe asking a final question or expressing a concern."
else:
direction = "The patient is responding to the NP's explanation or recommendation."
conv_text = ""
for msg in conversation:
role = "Patient" if msg["role"] == "user" else "NP"
conv_text += f"{role}: {msg['content']}\n"
prompt = f"""You are playing a patient in a medication management appointment. Stay completely in character.
PATIENT:
Age: {persona["age"]}, Gender: {persona["gender"]}, Occupation: {persona["occupation"]}
Presenting concern: {persona["presenting"]}
SESSION FOCUS: {category["name"]}{presentation}
DIRECTION FOR THIS TURN:
{direction}
This is patient turn {turn} of {total_patient_turns}.
CONVERSATION SO FAR:
{conv_text if conv_text else "(First turn — arriving at appointment.)"}
What does the patient say next? Generate ONLY spoken words — no labels, no narration. 2-5 sentences."""
messages = [
{"role": "system", "content": PATIENT_SYSTEM},
{"role": "user", "content": prompt},
]
patient_msg = ollama_chat(messages, model=PATIENT_MODEL, temperature=PATIENT_TEMP, num_predict=250)
conversation.append({"role": "user", "content": patient_msg})
np_msg = generate_np_turn_validated(persona, presentation, category, conversation, turn, style_profile)
conversation.append({"role": "assistant", "content": np_msg})
session_id = f"medication_{category_key}_{session_idx:04d}"
return {
"messages": [
{"role": "system", "content": NP_SYSTEM_BASE},
*conversation,
],
"metadata": {
"source_family": "medication_management",
"category": category_key,
"category_name": category["name"],
"presentation": presentation,
"session_id": session_id,
"persona_age": persona["age"],
"persona_gender": persona["gender"],
"persona_occupation": persona["occupation"],
"presenting_concern": persona["presenting"],
"style_profile": style_profile["description"][:50],
"turns": len(conversation),
"difficulty": category["difficulty"],
"clinician_role": "psychiatric_np",
},
}
def main():
parser = argparse.ArgumentParser(description="Medication Management Session Generation")
parser.add_argument("--categories", default="all", help="Comma-separated category keys or 'all'")
parser.add_argument("--sessions-per-category", type=int, default=50)
parser.add_argument("--resume", action="store_true")
parser.add_argument("--spot-check", type=int, default=None, help="Generate N sessions from first category only")
args = parser.parse_args()
output_dir = Path("data/medication_sessions")
output_dir.mkdir(parents=True, exist_ok=True)
output_file = output_dir / "medication_sessions.jsonl"
if args.categories == "all":
cats = list(CATEGORIES.keys())
else:
cats = [c.strip() for c in args.categories.split(",")]
if args.spot_check:
cats = cats[:1]
total_sessions = args.spot_check
else:
total_sessions = len(cats) * args.sessions_per_category
print(f"\n=== MEDICATION MANAGEMENT SESSION GENERATION ===")
print(f"Categories: {len(cats)} ({', '.join(cats)})")
print(f"Sessions per category: {args.spot_check or args.sessions_per_category}")
print(f"Total sessions: {total_sessions}")
print(f"Output: {output_file}")
print(f"Clinician: {THERAPIST_MODEL} (Psychiatric NP persona)")
print(f"Patient: {PATIENT_MODEL}")
print(f"Turns: 8-12 (med management appointments)")
print()
completed = 0
skipped = 0
failed = 0
start_time = time.time()
for cat_key in cats:
category = CATEGORIES[cat_key]
n_sessions = args.spot_check or args.sessions_per_category
print(f"\n--- {category['name']} ({cat_key}) ---")
for i in range(n_sessions):
presentation = category["presentations"][i % len(category["presentations"])]
persona = category["patient_personas"][i % len(category["patient_personas"])]
session_id = f"medication_{cat_key}_{i:04d}"
if args.resume and session_exists(output_file, session_id):
skipped += 1
continue
try:
session = generate_med_session(cat_key, category, persona, presentation, i)
with open(output_file, "a") as f:
f.write(json.dumps(session) + "\n")
completed += 1
elapsed = time.time() - start_time
rate = completed / (elapsed / 3600) if elapsed > 0 else 0
remaining = (total_sessions - completed - skipped) / rate if rate > 0 else 0
print(
f" ✓ {session_id} {len(session['messages'])} msgs | done: {completed}/{total_sessions} | ~{remaining:.1f}h left"
)
except Exception as e:
failed += 1
print(f" ✗ {session_id} FAILED: {e}")
with open(output_dir / "errors.log", "a") as f:
f.write(f"{session_id}: {e}\n")
elapsed = time.time() - start_time
print(f"\n=== COMPLETE ===")
print(f"Generated: {completed}")
print(f"Skipped: {skipped}")
print(f"Failed: {failed}")
print(f"Elapsed: {elapsed / 3600:.1f}h")
print(f"Output: {output_file}")
if __name__ == "__main__":
main()

Xet Storage Details

Size:
22.7 kB
·
Xet hash:
90a40d12023634c822df2d3a39489a1ed8720766edc1393ac8742aa813b8485d

Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.