KevinIsInCoding claude[bot] KevinIsInCoding commited on
Commit
7389f19
Β·
unverified Β·
1 Parent(s): f27b72f

feat: add Observational study type support (#7)

Browse files

Add support for filtering observational studies from ClinicalTrials.gov
alongside existing Interventional and Expanded Access searches.

- Extend SEARCH_TRIALS_TOOL enum with OBSERVATIONAL study_type
- Add studyType:obs aggFilter handling in search_trials_api
- Add include_observational field to PatientProfile and SUBMIT_PROFILE_TOOL
- Update INTAKE_SYSTEM prompt to present observational studies as an option
- Update RESEARCH_SYSTEM prompt to guide separate observational searches
- Propagate include_observational from intake tool result in both CLI and web app

Co-authored-by: claude[bot] <41898282+claude[bot]@users.noreply.github.com>
Co-authored-by: KevinIsInCoding <KevinIsInCoding@users.noreply.github.com>

Files changed (2) hide show
  1. app.py +1 -0
  2. clinical_trials_guru.py +27 -7
app.py CHANGED
@@ -65,6 +65,7 @@ def _intake_turn(
65
  radius_miles=data.get("radius_miles", 100),
66
  phases=data.get("phases") or [],
67
  include_eap=data.get("include_eap", False),
 
68
  lang=lang,
69
  )
70
  return text or UI[lang]["got_it"], messages, profile
 
65
  radius_miles=data.get("radius_miles", 100),
66
  phases=data.get("phases") or [],
67
  include_eap=data.get("include_eap", False),
68
+ include_observational=data.get("include_observational", False),
69
  lang=lang,
70
  )
71
  return text or UI[lang]["got_it"], messages, profile
clinical_trials_guru.py CHANGED
@@ -70,6 +70,10 @@ SUBMIT_PROFILE_TOOL: anthropic.types.ToolParam = {
70
  "type": "boolean",
71
  "description": "Whether patient is interested in Expanded Access Programs (compassionate use)",
72
  },
 
 
 
 
73
  },
74
  "required": ["disease", "age", "onset_months", "diagnosis_months", "zip_code"],
75
  },
@@ -82,7 +86,8 @@ SEARCH_TRIALS_TOOL: anthropic.types.ToolParam = {
82
  "Results are pre-ranked by distance from the patient's location. "
83
  "Call multiple times with different parameters (synonyms, broader radius, "
84
  "different phases) if initial results are sparse. "
85
- "Use study_type='EXPANDED_ACCESS' to search for Expanded Access Programs (EAP / compassionate use)."
 
86
  ),
87
  "input_schema": {
88
  "type": "object",
@@ -107,8 +112,8 @@ SEARCH_TRIALS_TOOL: anthropic.types.ToolParam = {
107
  },
108
  "study_type": {
109
  "type": "string",
110
- "enum": ["INTERVENTIONAL", "EXPANDED_ACCESS"],
111
- "description": "INTERVENTIONAL (default) for clinical trials; EXPANDED_ACCESS for EAP/compassionate use.",
112
  },
113
  },
114
  "required": ["condition", "lat", "lon", "radius_miles"],
@@ -161,13 +166,17 @@ OPTIONAL (ask based on disease):
161
  Not Applicable β€” Studies that do not fall into the standard phase framework
162
  (e.g., device feasibility studies, behavioral/observational trials,
163
  or studies where phase designation is not required by FDA).
 
 
 
 
164
  - Expanded Access Programs (EAP / compassionate use) β€” a pathway for patients who
165
  do not qualify for or cannot access a clinical trial to receive an investigational
166
  drug, biologic, or device outside of a trial. Also called "compassionate use."
167
  The treatment is not yet FDA-approved; a physician must submit the EAP request
168
  to the drug sponsor and obtain FDA authorization. EAP does not guarantee efficacy
169
  but may be an option when no approved treatments remain.
170
- - Or both; or all phases (default if no preference)
171
 
172
  Ask naturally. You may infer disease synonyms and convert dates to months, but never infer or skip the ZIP/postal code β€” always ask the patient for it directly. Once you have every required field confirmed by the patient, call submit_profile.\
173
  """
@@ -180,20 +189,22 @@ Results are already ranked by geographic distance from the patient.
180
  Workflow:
181
  1. Search for the patient's disease. Use both the full medical name and common abbreviation.
182
  - If the patient wants clinical trials, search with study_type="INTERVENTIONAL".
 
183
  - If the patient wants Expanded Access Programs (EAP), also search with study_type="EXPANDED_ACCESS".
184
- - If the patient wants both, run separate searches for each study_type.
185
  2. IMPORTANT β€” phase filtering: Never pass phases=["1","2","3","4"] to mean "all phases."
186
  Always pass phases=[] (omit the field) when the patient has no phase preference.
187
  NA-phase trials (device feasibility studies, unphased interventions) only appear
188
  when no phase filter is applied. Passing explicit phase numbers silently excludes them.
 
189
  3. If fewer than 3 results are found, retry with: a wider radius, a disease synonym,
190
  or drop phase filters entirely (phases=[]).
191
- 4. Produce a final report. Use separate sections for Clinical Trials and Expanded Access if both apply.
192
  List the top 10 results per section ranked by site proximity.
193
  For EACH entry use exactly this format (repeat the block per entry):
194
 
195
  πŸ“ **[Closest hospital/facility name]** β€” [City, State] ([X] mi)
196
- **Trial:** [Full title] ([Phase] β€” or "Expanded Access" for EAP)
197
  **Sponsor:** [Lead sponsor]
198
  **Principal Investigator:** [Name β€” or "Not listed" if absent]
199
  **Contact:** [Phone number] | [Email address] (use "Not listed" for any missing field)
@@ -207,6 +218,7 @@ Workflow:
207
  ---
208
 
209
  5. After the results add a short "Next steps" section (bullet points).
 
210
  For EAP results, note that patients typically need a physician to submit the EAP request.
211
 
212
  IMPORTANT: Only report trials returned by the search_clinical_trials tool. Do NOT suggest,
@@ -233,6 +245,7 @@ class PatientProfile:
233
  radius_miles: int = 100
234
  phases: list[str] = field(default_factory=list)
235
  include_eap: bool = False
 
236
  lang: str = "en"
237
 
238
  def summary(self) -> str:
@@ -259,6 +272,8 @@ class PatientProfile:
259
  labels = [_phase_label(p) for p in self.phases]
260
  lines.append(f"Phases: {', '.join(labels)}")
261
  interests = ["Clinical trials"]
 
 
262
  if self.include_eap:
263
  interests.append("Expanded Access Programs (EAP)")
264
  lines.append(f"Study type interest: {', '.join(interests)}")
@@ -302,6 +317,7 @@ def search_trials_api(
302
  study_type: str = "INTERVENTIONAL",
303
  ) -> list[dict]:
304
  is_eap = study_type == "EXPANDED_ACCESS"
 
305
  params: dict[str, str | int] = {
306
  "query.cond": condition,
307
  "filter.overallStatus": "AVAILABLE" if is_eap else "RECRUITING",
@@ -312,8 +328,11 @@ def search_trials_api(
312
  # aggFilters accepts only one value; studyType and phase can't be combined.
313
  # RECRUITING status already excludes EAPs, so studyType:int is only needed
314
  # when no phase filter is applied. studyType:int returns all phases including N/A.
 
315
  if is_eap:
316
  params["aggFilters"] = "studyType:exp"
 
 
317
  elif phases:
318
  # Exclude "na" from the phase filter β€” N/A trials have no phase value to match on;
319
  # they appear naturally when no phase filter is applied (studyType:int branch).
@@ -471,6 +490,7 @@ def run_intake_agent(client: anthropic.Anthropic) -> PatientProfile:
471
  radius_miles=data.get("radius_miles", 100),
472
  phases=data.get("phases") or [],
473
  include_eap=data.get("include_eap", False),
 
474
  )
475
 
476
  messages.append({"role": "assistant", "content": response.content})
 
70
  "type": "boolean",
71
  "description": "Whether patient is interested in Expanded Access Programs (compassionate use)",
72
  },
73
+ "include_observational": {
74
+ "type": "boolean",
75
+ "description": "Whether patient is interested in observational studies (no experimental treatment; researchers observe and measure outcomes)",
76
+ },
77
  },
78
  "required": ["disease", "age", "onset_months", "diagnosis_months", "zip_code"],
79
  },
 
86
  "Results are pre-ranked by distance from the patient's location. "
87
  "Call multiple times with different parameters (synonyms, broader radius, "
88
  "different phases) if initial results are sparse. "
89
+ "Use study_type='EXPANDED_ACCESS' to search for Expanded Access Programs (EAP / compassionate use). "
90
+ "Use study_type='OBSERVATIONAL' to search for observational studies (no experimental treatment assigned)."
91
  ),
92
  "input_schema": {
93
  "type": "object",
 
112
  },
113
  "study_type": {
114
  "type": "string",
115
+ "enum": ["INTERVENTIONAL", "EXPANDED_ACCESS", "OBSERVATIONAL"],
116
+ "description": "INTERVENTIONAL (default) for clinical trials; EXPANDED_ACCESS for EAP/compassionate use; OBSERVATIONAL for observational studies.",
117
  },
118
  },
119
  "required": ["condition", "lat", "lon", "radius_miles"],
 
166
  Not Applicable β€” Studies that do not fall into the standard phase framework
167
  (e.g., device feasibility studies, behavioral/observational trials,
168
  or studies where phase designation is not required by FDA).
169
+ - Observational studies β€” studies where researchers observe participants and collect
170
+ data without assigning treatments. No experimental drug or intervention is given.
171
+ Patients may contribute valuable data to disease understanding, registries, or
172
+ natural history studies. Often have broader eligibility than interventional trials.
173
  - Expanded Access Programs (EAP / compassionate use) β€” a pathway for patients who
174
  do not qualify for or cannot access a clinical trial to receive an investigational
175
  drug, biologic, or device outside of a trial. Also called "compassionate use."
176
  The treatment is not yet FDA-approved; a physician must submit the EAP request
177
  to the drug sponsor and obtain FDA authorization. EAP does not guarantee efficacy
178
  but may be an option when no approved treatments remain.
179
+ - Or any combination; or all types (default if no preference)
180
 
181
  Ask naturally. You may infer disease synonyms and convert dates to months, but never infer or skip the ZIP/postal code β€” always ask the patient for it directly. Once you have every required field confirmed by the patient, call submit_profile.\
182
  """
 
189
  Workflow:
190
  1. Search for the patient's disease. Use both the full medical name and common abbreviation.
191
  - If the patient wants clinical trials, search with study_type="INTERVENTIONAL".
192
+ - If the patient wants observational studies, also search with study_type="OBSERVATIONAL".
193
  - If the patient wants Expanded Access Programs (EAP), also search with study_type="EXPANDED_ACCESS".
194
+ - Run a separate search for each study_type the patient is interested in.
195
  2. IMPORTANT β€” phase filtering: Never pass phases=["1","2","3","4"] to mean "all phases."
196
  Always pass phases=[] (omit the field) when the patient has no phase preference.
197
  NA-phase trials (device feasibility studies, unphased interventions) only appear
198
  when no phase filter is applied. Passing explicit phase numbers silently excludes them.
199
+ Phase filters do not apply to OBSERVATIONAL or EXPANDED_ACCESS searches.
200
  3. If fewer than 3 results are found, retry with: a wider radius, a disease synonym,
201
  or drop phase filters entirely (phases=[]).
202
+ 4. Produce a final report. Use separate sections for Clinical Trials, Observational Studies, and Expanded Access as applicable.
203
  List the top 10 results per section ranked by site proximity.
204
  For EACH entry use exactly this format (repeat the block per entry):
205
 
206
  πŸ“ **[Closest hospital/facility name]** β€” [City, State] ([X] mi)
207
+ **Trial:** [Full title] ([Phase] β€” or "Observational" / "Expanded Access" as applicable)
208
  **Sponsor:** [Lead sponsor]
209
  **Principal Investigator:** [Name β€” or "Not listed" if absent]
210
  **Contact:** [Phone number] | [Email address] (use "Not listed" for any missing field)
 
218
  ---
219
 
220
  5. After the results add a short "Next steps" section (bullet points).
221
+ For observational studies, note that participation typically involves check-ins, surveys, or sample collection with no experimental treatment.
222
  For EAP results, note that patients typically need a physician to submit the EAP request.
223
 
224
  IMPORTANT: Only report trials returned by the search_clinical_trials tool. Do NOT suggest,
 
245
  radius_miles: int = 100
246
  phases: list[str] = field(default_factory=list)
247
  include_eap: bool = False
248
+ include_observational: bool = False
249
  lang: str = "en"
250
 
251
  def summary(self) -> str:
 
272
  labels = [_phase_label(p) for p in self.phases]
273
  lines.append(f"Phases: {', '.join(labels)}")
274
  interests = ["Clinical trials"]
275
+ if self.include_observational:
276
+ interests.append("Observational studies")
277
  if self.include_eap:
278
  interests.append("Expanded Access Programs (EAP)")
279
  lines.append(f"Study type interest: {', '.join(interests)}")
 
317
  study_type: str = "INTERVENTIONAL",
318
  ) -> list[dict]:
319
  is_eap = study_type == "EXPANDED_ACCESS"
320
+ is_observational = study_type == "OBSERVATIONAL"
321
  params: dict[str, str | int] = {
322
  "query.cond": condition,
323
  "filter.overallStatus": "AVAILABLE" if is_eap else "RECRUITING",
 
328
  # aggFilters accepts only one value; studyType and phase can't be combined.
329
  # RECRUITING status already excludes EAPs, so studyType:int is only needed
330
  # when no phase filter is applied. studyType:int returns all phases including N/A.
331
+ # Observational studies use studyType:obs; phases don't apply to them.
332
  if is_eap:
333
  params["aggFilters"] = "studyType:exp"
334
+ elif is_observational:
335
+ params["aggFilters"] = "studyType:obs"
336
  elif phases:
337
  # Exclude "na" from the phase filter β€” N/A trials have no phase value to match on;
338
  # they appear naturally when no phase filter is applied (studyType:int branch).
 
490
  radius_miles=data.get("radius_miles", 100),
491
  phases=data.get("phases") or [],
492
  include_eap=data.get("include_eap", False),
493
+ include_observational=data.get("include_observational", False),
494
  )
495
 
496
  messages.append({"role": "assistant", "content": response.content})