KevinIsInCoding Claude Sonnet 4.6 commited on
Commit
2aa1bda
·
1 Parent(s): 450de16

Initial release: Beacon rare disease clinical trial finder

Browse files

Conversational AI assistant that interviews patients, geocodes their location,
and searches ClinicalTrials.gov in real time to surface nearby recruiting trials.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

Files changed (4) hide show
  1. README.md +63 -0
  2. clinical_trials_guru.py +114 -0
  3. pyproject.toml +3 -0
  4. uv.lock +66 -0
README.md CHANGED
@@ -1,3 +1,4 @@
 
1
  ---
2
  title: Beacon Trial Finder
3
  emoji: 📚
@@ -12,12 +13,15 @@ license: mit
12
  short_description: help patients with rare disease to find clinical trials
13
  ---
14
 
 
 
15
  # Beacon — Rare Disease Clinical Trial Finder
16
 
17
  Beacon is a conversational AI assistant that helps patients with rare diseases find relevant recruiting clinical trials near them. It conducts a warm intake interview, geocodes the patient's location, queries [ClinicalTrials.gov](https://clinicaltrials.gov) in real time, and produces a ranked report of the closest matching trials.
18
 
19
  ## How it works
20
 
 
21
  1. **Intake agent** (Claude Sonnet) — interviews the patient conversationally to collect disease, age, symptom onset date, formal diagnosis date, location, preferred trial phases, and whether they are interested in Expanded Access Programs (EAP / compassionate use).
22
  2. **Research agent** (Claude Opus) — searches ClinicalTrials.gov via the official v2 API for clinical trials and/or EAP listings, retrying with synonyms or wider radii if results are sparse, then outputs a ranked report with eligibility notes and next steps.
23
  3. **LangGraph** orchestrates the two-node pipeline (intake → research).
@@ -36,6 +40,12 @@ Beacon is a conversational AI assistant that helps patients with rare diseases f
36
 
37
  EAP (also called compassionate use) allows patients who do not qualify for or cannot access a clinical trial to receive an investigational drug or device outside of a formal trial. The treatment is not yet FDA-approved; a physician must submit the EAP request to the drug sponsor and obtain FDA authorization. Beacon can search for available EAP listings alongside clinical trials.
38
 
 
 
 
 
 
 
39
  ## Project setup
40
 
41
  ### Prerequisites
@@ -73,6 +83,7 @@ cp .env.example .env
73
  Open `.env` and replace the placeholder values:
74
 
75
  ```env
 
76
  ANTHROPIC_API_KEY=your_anthropic_api_key_here
77
  ```
78
 
@@ -97,11 +108,43 @@ uv run gradio app.py
97
  | Environment variable | Default | Description |
98
  |----------------------|-------------|-------------------|
99
  | `ANTHROPIC_API_KEY` | *(required)*| Anthropic API key |
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
 
101
  ## Project structure
102
 
103
  ```
104
  beacon/
 
105
  ├── app.py # Gradio web UI entry point
106
  ├── main.py # Terminal entry point
107
  ├── clinical_trials_guru.py # Intake + research agents, LangGraph pipeline
@@ -110,3 +153,23 @@ beacon/
110
  ├── pyproject.toml
111
  └── .env # Local secrets — not committed
112
  ```
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <<<<<<< HEAD
2
  ---
3
  title: Beacon Trial Finder
4
  emoji: 📚
 
13
  short_description: help patients with rare disease to find clinical trials
14
  ---
15
 
16
+ =======
17
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
18
  # Beacon — Rare Disease Clinical Trial Finder
19
 
20
  Beacon is a conversational AI assistant that helps patients with rare diseases find relevant recruiting clinical trials near them. It conducts a warm intake interview, geocodes the patient's location, queries [ClinicalTrials.gov](https://clinicaltrials.gov) in real time, and produces a ranked report of the closest matching trials.
21
 
22
  ## How it works
23
 
24
+ <<<<<<< HEAD
25
  1. **Intake agent** (Claude Sonnet) — interviews the patient conversationally to collect disease, age, symptom onset date, formal diagnosis date, location, preferred trial phases, and whether they are interested in Expanded Access Programs (EAP / compassionate use).
26
  2. **Research agent** (Claude Opus) — searches ClinicalTrials.gov via the official v2 API for clinical trials and/or EAP listings, retrying with synonyms or wider radii if results are sparse, then outputs a ranked report with eligibility notes and next steps.
27
  3. **LangGraph** orchestrates the two-node pipeline (intake → research).
 
40
 
41
  EAP (also called compassionate use) allows patients who do not qualify for or cannot access a clinical trial to receive an investigational drug or device outside of a formal trial. The treatment is not yet FDA-approved; a physician must submit the EAP request to the drug sponsor and obtain FDA authorization. Beacon can search for available EAP listings alongside clinical trials.
42
 
43
+ =======
44
+ 1. **Intake agent** (Claude Sonnet) — interviews the patient conversationally to collect disease, age, symptom onset, location, and optional benchmark scores.
45
+ 2. **Research agent** (Claude Opus) — searches ClinicalTrials.gov via the official v2 API, retrying with synonyms or wider radii if results are sparse, then outputs a ranked trial report with eligibility notes and next steps.
46
+ 3. **LangGraph** orchestrates the two-node pipeline (intake → research).
47
+
48
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
49
  ## Project setup
50
 
51
  ### Prerequisites
 
83
  Open `.env` and replace the placeholder values:
84
 
85
  ```env
86
+ <<<<<<< HEAD
87
  ANTHROPIC_API_KEY=your_anthropic_api_key_here
88
  ```
89
 
 
108
  | Environment variable | Default | Description |
109
  |----------------------|-------------|-------------------|
110
  | `ANTHROPIC_API_KEY` | *(required)*| Anthropic API key |
111
+ =======
112
+ # ⚠️ Replace with your actual API keys — never commit real keys to version control
113
+ OPENAI_API_KEY=your_openai_api_key_here
114
+ ANTHROPIC_API_KEY=your_anthropic_api_key_here
115
+ ```
116
+
117
+ - Get your Anthropic key at <https://console.anthropic.com>
118
+ - Get your OpenAI key at <https://platform.openai.com/api-keys>
119
+
120
+ ### 4. Run
121
+
122
+ ```bash
123
+ uv run python main.py
124
+ ```
125
+
126
+ Or if using a plain virtualenv:
127
+
128
+ ```bash
129
+ python main.py
130
+ ```
131
+
132
+ ## Configuration
133
+
134
+ | Environment variable | Default | Description |
135
+ |----------------------|-------------|--------------------------------------------------|
136
+ | `ANTHROPIC_API_KEY` | *(required)*| Anthropic API key |
137
+ | `OPENAI_API_KEY` | *(optional)*| OpenAI API key (only needed for OpenAI provider) |
138
+ | `LLM_PROVIDER` | `anthropic` | LLM backend: `anthropic` or `openai` |
139
+
140
+ To switch to the OpenAI backend, set `LLM_PROVIDER=openai` in `.env`.
141
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
142
 
143
  ## Project structure
144
 
145
  ```
146
  beacon/
147
+ <<<<<<< HEAD
148
  ├── app.py # Gradio web UI entry point
149
  ├── main.py # Terminal entry point
150
  ├── clinical_trials_guru.py # Intake + research agents, LangGraph pipeline
 
153
  ├── pyproject.toml
154
  └── .env # Local secrets — not committed
155
  ```
156
+ =======
157
+ ├── main.py # Entry point
158
+ ├── clinical_trials_guru.py # Intake + research agents, LangGraph pipeline
159
+ ├── llm.py # LLM provider abstraction (Anthropic / OpenAI)
160
+ ├── pyproject.toml
161
+ ├── .env # Local secrets — not committed
162
+ └── .gitignore
163
+ ```
164
+
165
+ ## Dependencies
166
+
167
+ | Package | Purpose |
168
+ |------------------|--------------------------------------|
169
+ | `anthropic` | Claude API client |
170
+ | `openai` | OpenAI API client |
171
+ | `langgraph` | Agent pipeline orchestration |
172
+ | `rich` | Terminal UI (panels, markdown, etc.) |
173
+ | `python-dotenv` | `.env` file loading |
174
+ | `httpx` | HTTP client for ClinicalTrials.gov |
175
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
clinical_trials_guru.py CHANGED
@@ -41,10 +41,13 @@ SUBMIT_PROFILE_TOOL: anthropic.types.ToolParam = {
41
  "type": "integer",
42
  "description": "Months since first symptom onset",
43
  },
 
44
  "diagnosis_months": {
45
  "type": "integer",
46
  "description": "Months since formal/official diagnosis",
47
  },
 
 
48
  "benchmarks": {
49
  "type": "object",
50
  "description": "Disease-specific scores, e.g. {\"ALSFRS-R\": \"38\"}",
@@ -62,6 +65,7 @@ SUBMIT_PROFILE_TOOL: anthropic.types.ToolParam = {
62
  "phases": {
63
  "type": "array",
64
  "items": {"type": "string", "enum": ["0", "1", "2", "3", "4"]},
 
65
  "description": "Desired trial phases (0=Early Phase 1, 1=Phase 1, 2=Phase 2, 3=Phase 3, 4=Phase 4). Empty = all phases.",
66
  },
67
  "include_eap": {
@@ -70,17 +74,30 @@ SUBMIT_PROFILE_TOOL: anthropic.types.ToolParam = {
70
  },
71
  },
72
  "required": ["disease", "age", "onset_months", "diagnosis_months", "zip_code"],
 
 
 
 
 
 
73
  },
74
  }
75
 
76
  SEARCH_TRIALS_TOOL: anthropic.types.ToolParam = {
77
  "name": "search_clinical_trials",
78
  "description": (
 
79
  "Search ClinicalTrials.gov for studies within a geographic radius. "
80
  "Results are pre-ranked by distance from the patient's location. "
81
  "Call multiple times with different parameters (synonyms, broader radius, "
82
  "different phases) if initial results are sparse. "
83
  "Use study_type='EXPANDED_ACCESS' to search for Expanded Access Programs (EAP / compassionate use)."
 
 
 
 
 
 
84
  ),
85
  "input_schema": {
86
  "type": "object",
@@ -95,12 +112,16 @@ SEARCH_TRIALS_TOOL: anthropic.types.ToolParam = {
95
  "phases": {
96
  "type": "array",
97
  "items": {"type": "string"},
 
98
  "description": "Phase numbers to filter ['1','2','3']. Empty = all. Ignored for EAP.",
99
  },
100
  "study_type": {
101
  "type": "string",
102
  "enum": ["INTERVENTIONAL", "EXPANDED_ACCESS"],
103
  "description": "INTERVENTIONAL (default) for clinical trials; EXPANDED_ACCESS for EAP/compassionate use.",
 
 
 
104
  },
105
  "max_results": {"type": "integer", "description": "Max trials to return (default 20)"},
106
  },
@@ -118,11 +139,15 @@ REQUIRED:
118
  • Disease/condition (standardize: "Lou Gehrig's" → "Amyotrophic Lateral Sclerosis")
119
  • Patient age
120
  • Months since first symptom onset (convert dates/years as needed)
 
121
  • Months since formal/official diagnosis (convert dates/years as needed; may differ from onset)
 
 
122
  • ZIP/postal code and country for geographic search
123
 
124
  OPTIONAL (ask based on disease):
125
  • Disease-specific benchmark scores:
 
126
  ALS → ALSFRS-R (0-48) + FVC % predicted (0-100%) + ALS subtype;
127
  FVC (Forced Vital Capacity) measures how much air a person can forcibly exhale —
128
  it reflects respiratory muscle strength. In ALS it is expressed as a percentage
@@ -160,6 +185,15 @@ OPTIONAL (ask based on disease):
160
  - Or both; or all phases (default if no preference)
161
 
162
  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.\
 
 
 
 
 
 
 
 
 
163
  """
164
 
165
  RESEARCH_SYSTEM = """\
@@ -169,6 +203,7 @@ Results are already ranked by geographic distance from the patient.
169
 
170
  Workflow:
171
  1. Search for the patient's disease. Use both the full medical name and common abbreviation.
 
172
  - If the patient wants clinical trials, search with study_type="INTERVENTIONAL".
173
  - If the patient wants Expanded Access Programs (EAP), also search with study_type="EXPANDED_ACCESS".
174
  - If the patient wants both, run separate searches for each study_type.
@@ -188,10 +223,25 @@ Workflow:
188
  **Qualification criteria:** [Key inclusion AND exclusion criteria relevant to this patient,
189
  including age range, functional score thresholds, FVC cutoffs,
190
  and any red flags. Be specific — use exact numbers from the data.]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
191
  **Link:** https://clinicaltrials.gov/study/[NCT_ID]
192
 
193
  ---
194
 
 
195
  4. After the results add a short "Next steps" section (bullet points).
196
  For EAP results, note that patients typically need a physician to submit the EAP request.
197
 
@@ -199,6 +249,9 @@ IMPORTANT: Only report trials returned by the search_clinical_trials tool. Do NO
199
  list, or recommend any hospitals, centers, or trials that were not in the tool results —
200
  even well-known institutions. If no results are found, say so clearly and suggest the patient
201
  ask their neurologist or contact the ALS Association for a referral.
 
 
 
202
 
203
  Be accurate. Do not fabricate details. If data is missing, say so.\
204
  """
@@ -210,7 +263,10 @@ class PatientProfile:
210
  disease: str
211
  age: int
212
  onset_months: int
 
213
  diagnosis_months: int = 0
 
 
214
  benchmarks: dict[str, str] = field(default_factory=dict)
215
  zip_code: str = ""
216
  country_code: str = "US"
@@ -218,14 +274,20 @@ class PatientProfile:
218
  lon: float = 0.0
219
  radius_miles: int = 100
220
  phases: list[str] = field(default_factory=list)
 
221
  include_eap: bool = False
 
 
222
 
223
  def summary(self) -> str:
224
  lines = [
225
  f"Disease: {self.disease}",
226
  f"Age: {self.age}",
227
  f"Symptom onset: {self.onset_months} months ago",
 
228
  f"Formal diagnosis: {self.diagnosis_months} months ago",
 
 
229
  ]
230
  if self.benchmarks:
231
  lines.append("Benchmarks: " + ", ".join(f"{k}={v}" for k, v in self.benchmarks.items()))
@@ -237,10 +299,13 @@ class PatientProfile:
237
  if self.phases:
238
  labels = ["Early Phase 1" if p == "0" else f"Phase {p}" for p in self.phases]
239
  lines.append(f"Phases: {', '.join(labels)}")
 
240
  interests = ["Clinical trials"]
241
  if self.include_eap:
242
  interests.append("Expanded Access Programs (EAP)")
243
  lines.append(f"Study type interest: {', '.join(interests)}")
 
 
244
  return "\n".join(lines)
245
 
246
 
@@ -278,6 +343,7 @@ def search_trials_api(
278
  lon: float,
279
  radius_miles: int = 100,
280
  phases: list[str] | None = None,
 
281
  study_type: str = "INTERVENTIONAL",
282
  max_results: int = 20,
283
  ) -> list[dict]:
@@ -285,10 +351,18 @@ def search_trials_api(
285
  params: dict[str, str | int] = {
286
  "query.cond": condition,
287
  "filter.overallStatus": "AVAILABLE" if is_eap else "RECRUITING",
 
 
 
 
 
 
 
288
  "filter.geo": f"distance({lat},{lon},{radius_miles}mi)",
289
  "pageSize": max_results,
290
  "format": "json",
291
  }
 
292
  # aggFilters accepts only one value; studyType and phase can't be combined.
293
  # RECRUITING status already excludes EAPs, so studyType:int is only needed
294
  # when no phase filter is applied.
@@ -298,6 +372,10 @@ def search_trials_api(
298
  params["aggFilters"] = "phase:" + " ".join(phases)
299
  else:
300
  params["aggFilters"] = "studyType:int"
 
 
 
 
301
  for attempt in range(3):
302
  try:
303
  resp = httpx.get(CTGOV_BASE, params=params, timeout=30)
@@ -322,6 +400,7 @@ def _flatten_and_rank(studies: list[dict], patient_lat: float, patient_lon: floa
322
  sponsor_mod = proto.get("sponsorCollaboratorsModule", {})
323
  design_mod = proto.get("designModule", {})
324
 
 
325
  # Central (overall) contacts
326
  central_contacts = contacts_mod.get("centralContacts", [])
327
  central_phone = next((c.get("phone", "") for c in central_contacts if c.get("phone")), "")
@@ -335,10 +414,14 @@ def _flatten_and_rank(studies: list[dict], patient_lat: float, patient_lon: floa
335
  )
336
 
337
  sites_with_dist: list[tuple[float, dict]] = []
 
 
 
338
  for loc in contacts_mod.get("locations", []):
339
  geo = loc.get("geoPoint", {})
340
  if geo.get("lat") and geo.get("lon"):
341
  d = haversine_miles(patient_lat, patient_lon, geo["lat"], geo["lon"])
 
342
  loc_contacts = loc.get("contacts", [])
343
  loc_phone = next((c.get("phone", "") for c in loc_contacts if c.get("phone")), "")
344
  loc_email = next((c.get("email", "") for c in loc_contacts if c.get("email")), "")
@@ -356,6 +439,15 @@ def _flatten_and_rank(studies: list[dict], patient_lat: float, patient_lon: floa
356
  "phone": loc_phone or central_phone,
357
  "email": loc_email or central_email,
358
  }))
 
 
 
 
 
 
 
 
 
359
  sites_with_dist.sort(key=lambda x: x[0])
360
 
361
  closest_dist = sites_with_dist[0][0] if sites_with_dist else None
@@ -364,15 +456,22 @@ def _flatten_and_rank(studies: list[dict], patient_lat: float, patient_lon: floa
364
  "title": id_mod.get("briefTitle", ""),
365
  "phase": ", ".join(design_mod.get("phases", [])) or "N/A",
366
  "sponsor": sponsor_mod.get("leadSponsor", {}).get("name", ""),
 
367
  "principal_investigator": pi,
368
  "contact_phone": central_phone,
369
  "contact_email": central_email,
 
 
370
  "summary": desc_mod.get("briefSummary", "")[:500],
371
  "eligibility": elig_mod.get("eligibilityCriteria", "")[:1000],
372
  "min_age": elig_mod.get("minimumAge", ""),
373
  "max_age": elig_mod.get("maximumAge", ""),
374
  "closest_site_miles": round(closest_dist, 1) if closest_dist is not None else None,
 
375
  "nearest_sites": [info for _, info in sites_with_dist[:5]],
 
 
 
376
  })
377
 
378
  result.sort(key=lambda x: x["closest_site_miles"] if x["closest_site_miles"] is not None else float("inf"))
@@ -425,7 +524,10 @@ def run_intake_agent(client: anthropic.Anthropic) -> PatientProfile:
425
  disease=data["disease"],
426
  age=data["age"],
427
  onset_months=data["onset_months"],
 
428
  diagnosis_months=data.get("diagnosis_months", 0),
 
 
429
  benchmarks=data.get("benchmarks") or {},
430
  zip_code=data["zip_code"],
431
  country_code=data.get("country_code", "US"),
@@ -433,7 +535,10 @@ def run_intake_agent(client: anthropic.Anthropic) -> PatientProfile:
433
  lon=lon,
434
  radius_miles=data.get("radius_miles", 100),
435
  phases=data.get("phases") or [],
 
436
  include_eap=data.get("include_eap", False),
 
 
437
  )
438
 
439
  messages.append({"role": "assistant", "content": response.content})
@@ -478,11 +583,17 @@ def run_research_agent(client: anthropic.Anthropic, profile: PatientProfile) ->
478
  args = block.input
479
  radius = args.get("radius_miles", profile.radius_miles)
480
  phases = args.get("phases") or None
 
481
  study_type = args.get("study_type", "INTERVENTIONAL")
482
  status_msg = (
483
  f"[cyan]Searching:[/cyan] '[bold]{args['condition']}[/bold]' | "
484
  f"radius=[bold]{radius}[/bold] mi | "
485
  f"type=[bold]{study_type}[/bold] | "
 
 
 
 
 
486
  f"phases=[bold]{phases or 'all'}[/bold]"
487
  )
488
  try:
@@ -493,7 +604,10 @@ def run_research_agent(client: anthropic.Anthropic, profile: PatientProfile) ->
493
  lon=args["lon"],
494
  radius_miles=radius,
495
  phases=phases,
 
496
  study_type=study_type,
 
 
497
  max_results=args.get("max_results", 20),
498
  )
499
  ranked = _flatten_and_rank(studies, profile.lat, profile.lon)
 
41
  "type": "integer",
42
  "description": "Months since first symptom onset",
43
  },
44
+ <<<<<<< HEAD
45
  "diagnosis_months": {
46
  "type": "integer",
47
  "description": "Months since formal/official diagnosis",
48
  },
49
+ =======
50
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
51
  "benchmarks": {
52
  "type": "object",
53
  "description": "Disease-specific scores, e.g. {\"ALSFRS-R\": \"38\"}",
 
65
  "phases": {
66
  "type": "array",
67
  "items": {"type": "string", "enum": ["0", "1", "2", "3", "4"]},
68
+ <<<<<<< HEAD
69
  "description": "Desired trial phases (0=Early Phase 1, 1=Phase 1, 2=Phase 2, 3=Phase 3, 4=Phase 4). Empty = all phases.",
70
  },
71
  "include_eap": {
 
74
  },
75
  },
76
  "required": ["disease", "age", "onset_months", "diagnosis_months", "zip_code"],
77
+ =======
78
+ "description": "Desired trial phases. Empty = all phases.",
79
+ },
80
+ },
81
+ "required": ["disease", "age", "onset_months", "zip_code"],
82
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
83
  },
84
  }
85
 
86
  SEARCH_TRIALS_TOOL: anthropic.types.ToolParam = {
87
  "name": "search_clinical_trials",
88
  "description": (
89
+ <<<<<<< HEAD
90
  "Search ClinicalTrials.gov for studies within a geographic radius. "
91
  "Results are pre-ranked by distance from the patient's location. "
92
  "Call multiple times with different parameters (synonyms, broader radius, "
93
  "different phases) if initial results are sparse. "
94
  "Use study_type='EXPANDED_ACCESS' to search for Expanded Access Programs (EAP / compassionate use)."
95
+ =======
96
+ "Search ClinicalTrials.gov for recruiting trials within a geographic radius. "
97
+ "Results are pre-ranked by distance from the patient's location. "
98
+ "Call multiple times with different parameters (synonyms, broader radius, "
99
+ "different phases) if initial results are sparse."
100
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
101
  ),
102
  "input_schema": {
103
  "type": "object",
 
112
  "phases": {
113
  "type": "array",
114
  "items": {"type": "string"},
115
+ <<<<<<< HEAD
116
  "description": "Phase numbers to filter ['1','2','3']. Empty = all. Ignored for EAP.",
117
  },
118
  "study_type": {
119
  "type": "string",
120
  "enum": ["INTERVENTIONAL", "EXPANDED_ACCESS"],
121
  "description": "INTERVENTIONAL (default) for clinical trials; EXPANDED_ACCESS for EAP/compassionate use.",
122
+ =======
123
+ "description": "Phase numbers to filter ['1','2','3']. Empty = all.",
124
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
125
  },
126
  "max_results": {"type": "integer", "description": "Max trials to return (default 20)"},
127
  },
 
139
  • Disease/condition (standardize: "Lou Gehrig's" → "Amyotrophic Lateral Sclerosis")
140
  • Patient age
141
  • Months since first symptom onset (convert dates/years as needed)
142
+ <<<<<<< HEAD
143
  • Months since formal/official diagnosis (convert dates/years as needed; may differ from onset)
144
+ =======
145
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
146
  • ZIP/postal code and country for geographic search
147
 
148
  OPTIONAL (ask based on disease):
149
  • Disease-specific benchmark scores:
150
+ <<<<<<< HEAD
151
  ALS → ALSFRS-R (0-48) + FVC % predicted (0-100%) + ALS subtype;
152
  FVC (Forced Vital Capacity) measures how much air a person can forcibly exhale —
153
  it reflects respiratory muscle strength. In ALS it is expressed as a percentage
 
185
  - Or both; or all phases (default if no preference)
186
 
187
  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.\
188
+ =======
189
+ ALS → ALSFRS-R (0-48); MS → EDSS (0-10); Parkinson's → MDS-UPDRS III;
190
+ Huntington's → TFC (0-13) + CAG repeats; SMA → HFMS + SMA type;
191
+ Duchenne/Pompe → 6-Minute Walk Test; Friedreich's → SARA score
192
+ • Preferred search radius in miles (default 100)
193
+ • Trial phases of interest (1 / 2 / 3 / 4 / early)
194
+
195
+ Ask naturally. Infer what you can. Once you have the required fields, call submit_profile.\
196
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
197
  """
198
 
199
  RESEARCH_SYSTEM = """\
 
203
 
204
  Workflow:
205
  1. Search for the patient's disease. Use both the full medical name and common abbreviation.
206
+ <<<<<<< HEAD
207
  - If the patient wants clinical trials, search with study_type="INTERVENTIONAL".
208
  - If the patient wants Expanded Access Programs (EAP), also search with study_type="EXPANDED_ACCESS".
209
  - If the patient wants both, run separate searches for each study_type.
 
223
  **Qualification criteria:** [Key inclusion AND exclusion criteria relevant to this patient,
224
  including age range, functional score thresholds, FVC cutoffs,
225
  and any red flags. Be specific — use exact numbers from the data.]
226
+ =======
227
+ 2. If fewer than 3 results are found, retry with: a wider radius, a disease synonym,
228
+ or fewer phase filters.
229
+ 3. Produce a final report listing the top 5 trials ranked by site proximity.
230
+ For EACH trial use exactly this format (repeat the block per trial):
231
+
232
+ 📍 **[Closest hospital name]** — [City, State] ([X] mi)
233
+ **Trial:** [Full trial title] ([Phase])
234
+ **Sponsor:** [Lead sponsor]
235
+ **Summary:** [2–3 sentence plain-language description of what the trial is testing
236
+ and why it may matter for this patient]
237
+ **Eligibility notes:** [Key inclusion/exclusion criteria relevant to this patient,
238
+ including any red flags]
239
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
240
  **Link:** https://clinicaltrials.gov/study/[NCT_ID]
241
 
242
  ---
243
 
244
+ <<<<<<< HEAD
245
  4. After the results add a short "Next steps" section (bullet points).
246
  For EAP results, note that patients typically need a physician to submit the EAP request.
247
 
 
249
  list, or recommend any hospitals, centers, or trials that were not in the tool results —
250
  even well-known institutions. If no results are found, say so clearly and suggest the patient
251
  ask their neurologist or contact the ALS Association for a referral.
252
+ =======
253
+ 4. After the trial list add a short "Next steps" section (bullet points).
254
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
255
 
256
  Be accurate. Do not fabricate details. If data is missing, say so.\
257
  """
 
263
  disease: str
264
  age: int
265
  onset_months: int
266
+ <<<<<<< HEAD
267
  diagnosis_months: int = 0
268
+ =======
269
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
270
  benchmarks: dict[str, str] = field(default_factory=dict)
271
  zip_code: str = ""
272
  country_code: str = "US"
 
274
  lon: float = 0.0
275
  radius_miles: int = 100
276
  phases: list[str] = field(default_factory=list)
277
+ <<<<<<< HEAD
278
  include_eap: bool = False
279
+ =======
280
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
281
 
282
  def summary(self) -> str:
283
  lines = [
284
  f"Disease: {self.disease}",
285
  f"Age: {self.age}",
286
  f"Symptom onset: {self.onset_months} months ago",
287
+ <<<<<<< HEAD
288
  f"Formal diagnosis: {self.diagnosis_months} months ago",
289
+ =======
290
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
291
  ]
292
  if self.benchmarks:
293
  lines.append("Benchmarks: " + ", ".join(f"{k}={v}" for k, v in self.benchmarks.items()))
 
299
  if self.phases:
300
  labels = ["Early Phase 1" if p == "0" else f"Phase {p}" for p in self.phases]
301
  lines.append(f"Phases: {', '.join(labels)}")
302
+ <<<<<<< HEAD
303
  interests = ["Clinical trials"]
304
  if self.include_eap:
305
  interests.append("Expanded Access Programs (EAP)")
306
  lines.append(f"Study type interest: {', '.join(interests)}")
307
+ =======
308
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
309
  return "\n".join(lines)
310
 
311
 
 
343
  lon: float,
344
  radius_miles: int = 100,
345
  phases: list[str] | None = None,
346
+ <<<<<<< HEAD
347
  study_type: str = "INTERVENTIONAL",
348
  max_results: int = 20,
349
  ) -> list[dict]:
 
351
  params: dict[str, str | int] = {
352
  "query.cond": condition,
353
  "filter.overallStatus": "AVAILABLE" if is_eap else "RECRUITING",
354
+ =======
355
+ max_results: int = 20,
356
+ ) -> list[dict]:
357
+ params: dict[str, str | int] = {
358
+ "query.cond": condition,
359
+ "filter.overallStatus": "RECRUITING",
360
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
361
  "filter.geo": f"distance({lat},{lon},{radius_miles}mi)",
362
  "pageSize": max_results,
363
  "format": "json",
364
  }
365
+ <<<<<<< HEAD
366
  # aggFilters accepts only one value; studyType and phase can't be combined.
367
  # RECRUITING status already excludes EAPs, so studyType:int is only needed
368
  # when no phase filter is applied.
 
372
  params["aggFilters"] = "phase:" + " ".join(phases)
373
  else:
374
  params["aggFilters"] = "studyType:int"
375
+ =======
376
+ if phases:
377
+ params["aggFilters"] = "phase:" + " ".join(phases)
378
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
379
  for attempt in range(3):
380
  try:
381
  resp = httpx.get(CTGOV_BASE, params=params, timeout=30)
 
400
  sponsor_mod = proto.get("sponsorCollaboratorsModule", {})
401
  design_mod = proto.get("designModule", {})
402
 
403
+ <<<<<<< HEAD
404
  # Central (overall) contacts
405
  central_contacts = contacts_mod.get("centralContacts", [])
406
  central_phone = next((c.get("phone", "") for c in central_contacts if c.get("phone")), "")
 
414
  )
415
 
416
  sites_with_dist: list[tuple[float, dict]] = []
417
+ =======
418
+ sites_with_dist: list[tuple[float, str]] = []
419
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
420
  for loc in contacts_mod.get("locations", []):
421
  geo = loc.get("geoPoint", {})
422
  if geo.get("lat") and geo.get("lon"):
423
  d = haversine_miles(patient_lat, patient_lon, geo["lat"], geo["lon"])
424
+ <<<<<<< HEAD
425
  loc_contacts = loc.get("contacts", [])
426
  loc_phone = next((c.get("phone", "") for c in loc_contacts if c.get("phone")), "")
427
  loc_email = next((c.get("email", "") for c in loc_contacts if c.get("email")), "")
 
439
  "phone": loc_phone or central_phone,
440
  "email": loc_email or central_email,
441
  }))
442
+ =======
443
+ label = (
444
+ f"{loc.get('facility', '').strip()} — "
445
+ f"{loc.get('city', '')}, "
446
+ f"{loc.get('state', loc.get('country', ''))} "
447
+ f"({d:.0f} mi)"
448
+ )
449
+ sites_with_dist.append((d, label))
450
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
451
  sites_with_dist.sort(key=lambda x: x[0])
452
 
453
  closest_dist = sites_with_dist[0][0] if sites_with_dist else None
 
456
  "title": id_mod.get("briefTitle", ""),
457
  "phase": ", ".join(design_mod.get("phases", [])) or "N/A",
458
  "sponsor": sponsor_mod.get("leadSponsor", {}).get("name", ""),
459
+ <<<<<<< HEAD
460
  "principal_investigator": pi,
461
  "contact_phone": central_phone,
462
  "contact_email": central_email,
463
+ =======
464
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
465
  "summary": desc_mod.get("briefSummary", "")[:500],
466
  "eligibility": elig_mod.get("eligibilityCriteria", "")[:1000],
467
  "min_age": elig_mod.get("minimumAge", ""),
468
  "max_age": elig_mod.get("maximumAge", ""),
469
  "closest_site_miles": round(closest_dist, 1) if closest_dist is not None else None,
470
+ <<<<<<< HEAD
471
  "nearest_sites": [info for _, info in sites_with_dist[:5]],
472
+ =======
473
+ "nearest_sites": [label for _, label in sites_with_dist[:5]],
474
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
475
  })
476
 
477
  result.sort(key=lambda x: x["closest_site_miles"] if x["closest_site_miles"] is not None else float("inf"))
 
524
  disease=data["disease"],
525
  age=data["age"],
526
  onset_months=data["onset_months"],
527
+ <<<<<<< HEAD
528
  diagnosis_months=data.get("diagnosis_months", 0),
529
+ =======
530
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
531
  benchmarks=data.get("benchmarks") or {},
532
  zip_code=data["zip_code"],
533
  country_code=data.get("country_code", "US"),
 
535
  lon=lon,
536
  radius_miles=data.get("radius_miles", 100),
537
  phases=data.get("phases") or [],
538
+ <<<<<<< HEAD
539
  include_eap=data.get("include_eap", False),
540
+ =======
541
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
542
  )
543
 
544
  messages.append({"role": "assistant", "content": response.content})
 
583
  args = block.input
584
  radius = args.get("radius_miles", profile.radius_miles)
585
  phases = args.get("phases") or None
586
+ <<<<<<< HEAD
587
  study_type = args.get("study_type", "INTERVENTIONAL")
588
  status_msg = (
589
  f"[cyan]Searching:[/cyan] '[bold]{args['condition']}[/bold]' | "
590
  f"radius=[bold]{radius}[/bold] mi | "
591
  f"type=[bold]{study_type}[/bold] | "
592
+ =======
593
+ status_msg = (
594
+ f"[cyan]Searching:[/cyan] '[bold]{args['condition']}[/bold]' | "
595
+ f"radius=[bold]{radius}[/bold] mi | "
596
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
597
  f"phases=[bold]{phases or 'all'}[/bold]"
598
  )
599
  try:
 
604
  lon=args["lon"],
605
  radius_miles=radius,
606
  phases=phases,
607
+ <<<<<<< HEAD
608
  study_type=study_type,
609
+ =======
610
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
611
  max_results=args.get("max_results", 20),
612
  )
613
  ranked = _flatten_and_rank(studies, profile.lat, profile.lon)
pyproject.toml CHANGED
@@ -10,5 +10,8 @@ dependencies = [
10
  "openai>=1.0.0",
11
  "python-dotenv>=1.2.2",
12
  "rich>=13.0.0",
 
13
  "gradio>=4.0.0",
 
 
14
  ]
 
10
  "openai>=1.0.0",
11
  "python-dotenv>=1.2.2",
12
  "rich>=13.0.0",
13
+ <<<<<<< HEAD
14
  "gradio>=4.0.0",
15
+ =======
16
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
17
  ]
uv.lock CHANGED
@@ -1,6 +1,7 @@
1
  version = 1
2
  revision = 3
3
  requires-python = ">=3.11"
 
4
  resolution-markers = [
5
  "python_full_version >= '3.14' and sys_platform == 'win32'",
6
  "python_full_version >= '3.14' and sys_platform == 'emscripten'",
@@ -21,6 +22,8 @@ sdist = { url = "https://files.pythonhosted.org/packages/57/ba/046ceea2734456098
21
  wheels = [
22
  { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
23
  ]
 
 
24
 
25
  [[package]]
26
  name = "annotated-types"
@@ -64,6 +67,7 @@ wheels = [
64
  ]
65
 
66
  [[package]]
 
67
  name = "audioop-lts"
68
  version = "0.2.2"
69
  source = { registry = "https://pypi.org/simple" }
@@ -120,12 +124,17 @@ wheels = [
120
  ]
121
 
122
  [[package]]
 
 
123
  name = "beacon"
124
  version = "0.1.0"
125
  source = { virtual = "." }
126
  dependencies = [
127
  { name = "anthropic" },
 
128
  { name = "gradio" },
 
 
129
  { name = "langgraph" },
130
  { name = "openai" },
131
  { name = "python-dotenv" },
@@ -135,7 +144,10 @@ dependencies = [
135
  [package.metadata]
136
  requires-dist = [
137
  { name = "anthropic", specifier = ">=0.50.0" },
 
138
  { name = "gradio", specifier = ">=4.0.0" },
 
 
139
  { name = "langgraph", specifier = ">=1.2.0" },
140
  { name = "openai", specifier = ">=1.0.0" },
141
  { name = "python-dotenv", specifier = ">=1.2.2" },
@@ -143,6 +155,7 @@ requires-dist = [
143
  ]
144
 
145
  [[package]]
 
146
  name = "brotli"
147
  version = "1.2.0"
148
  source = { registry = "https://pypi.org/simple" }
@@ -191,6 +204,8 @@ wheels = [
191
  ]
192
 
193
  [[package]]
 
 
194
  name = "certifi"
195
  version = "2026.4.22"
196
  source = { registry = "https://pypi.org/simple" }
@@ -289,6 +304,7 @@ wheels = [
289
  ]
290
 
291
  [[package]]
 
292
  name = "click"
293
  version = "8.3.3"
294
  source = { registry = "https://pypi.org/simple" }
@@ -301,6 +317,8 @@ wheels = [
301
  ]
302
 
303
  [[package]]
 
 
304
  name = "colorama"
305
  version = "0.4.6"
306
  source = { registry = "https://pypi.org/simple" }
@@ -328,6 +346,7 @@ wheels = [
328
  ]
329
 
330
  [[package]]
 
331
  name = "fastapi"
332
  version = "0.136.1"
333
  source = { registry = "https://pypi.org/simple" }
@@ -426,6 +445,8 @@ wheels = [
426
  ]
427
 
428
  [[package]]
 
 
429
  name = "h11"
430
  version = "0.16.0"
431
  source = { registry = "https://pypi.org/simple" }
@@ -435,6 +456,7 @@ wheels = [
435
  ]
436
 
437
  [[package]]
 
438
  name = "hf-gradio"
439
  version = "0.4.1"
440
  source = { registry = "https://pypi.org/simple" }
@@ -480,6 +502,8 @@ wheels = [
480
  ]
481
 
482
  [[package]]
 
 
483
  name = "httpcore"
484
  version = "1.0.9"
485
  source = { registry = "https://pypi.org/simple" }
@@ -508,6 +532,7 @@ wheels = [
508
  ]
509
 
510
  [[package]]
 
511
  name = "huggingface-hub"
512
  version = "1.14.0"
513
  source = { registry = "https://pypi.org/simple" }
@@ -528,6 +553,8 @@ wheels = [
528
  ]
529
 
530
  [[package]]
 
 
531
  name = "idna"
532
  version = "3.14"
533
  source = { registry = "https://pypi.org/simple" }
@@ -537,6 +564,7 @@ wheels = [
537
  ]
538
 
539
  [[package]]
 
540
  name = "jinja2"
541
  version = "3.1.6"
542
  source = { registry = "https://pypi.org/simple" }
@@ -549,6 +577,8 @@ wheels = [
549
  ]
550
 
551
  [[package]]
 
 
552
  name = "jiter"
553
  version = "0.14.0"
554
  source = { registry = "https://pypi.org/simple" }
@@ -780,6 +810,7 @@ wheels = [
780
  ]
781
 
782
  [[package]]
 
783
  name = "markupsafe"
784
  version = "3.0.3"
785
  source = { registry = "https://pypi.org/simple" }
@@ -854,6 +885,8 @@ wheels = [
854
  ]
855
 
856
  [[package]]
 
 
857
  name = "mdurl"
858
  version = "0.1.2"
859
  source = { registry = "https://pypi.org/simple" }
@@ -863,6 +896,7 @@ wheels = [
863
  ]
864
 
865
  [[package]]
 
866
  name = "numpy"
867
  version = "2.4.4"
868
  source = { registry = "https://pypi.org/simple" }
@@ -942,6 +976,8 @@ wheels = [
942
  ]
943
 
944
  [[package]]
 
 
945
  name = "openai"
946
  version = "2.36.0"
947
  source = { registry = "https://pypi.org/simple" }
@@ -1086,6 +1122,7 @@ wheels = [
1086
  ]
1087
 
1088
  [[package]]
 
1089
  name = "pandas"
1090
  version = "3.0.3"
1091
  source = { registry = "https://pypi.org/simple" }
@@ -1233,6 +1270,8 @@ wheels = [
1233
  ]
1234
 
1235
  [[package]]
 
 
1236
  name = "pydantic"
1237
  version = "2.13.4"
1238
  source = { registry = "https://pypi.org/simple" }
@@ -1350,6 +1389,7 @@ wheels = [
1350
  ]
1351
 
1352
  [[package]]
 
1353
  name = "pydub"
1354
  version = "0.25.1"
1355
  source = { registry = "https://pypi.org/simple" }
@@ -1359,6 +1399,8 @@ wheels = [
1359
  ]
1360
 
1361
  [[package]]
 
 
1362
  name = "pygments"
1363
  version = "2.20.0"
1364
  source = { registry = "https://pypi.org/simple" }
@@ -1368,6 +1410,7 @@ wheels = [
1368
  ]
1369
 
1370
  [[package]]
 
1371
  name = "python-dateutil"
1372
  version = "2.9.0.post0"
1373
  source = { registry = "https://pypi.org/simple" }
@@ -1380,6 +1423,8 @@ wheels = [
1380
  ]
1381
 
1382
  [[package]]
 
 
1383
  name = "python-dotenv"
1384
  version = "1.2.2"
1385
  source = { registry = "https://pypi.org/simple" }
@@ -1389,6 +1434,7 @@ wheels = [
1389
  ]
1390
 
1391
  [[package]]
 
1392
  name = "python-multipart"
1393
  version = "0.0.28"
1394
  source = { registry = "https://pypi.org/simple" }
@@ -1407,6 +1453,8 @@ wheels = [
1407
  ]
1408
 
1409
  [[package]]
 
 
1410
  name = "pyyaml"
1411
  version = "6.0.3"
1412
  source = { registry = "https://pypi.org/simple" }
@@ -1502,6 +1550,7 @@ wheels = [
1502
  ]
1503
 
1504
  [[package]]
 
1505
  name = "safehttpx"
1506
  version = "0.1.7"
1507
  source = { registry = "https://pypi.org/simple" }
@@ -1541,6 +1590,8 @@ wheels = [
1541
  ]
1542
 
1543
  [[package]]
 
 
1544
  name = "sniffio"
1545
  version = "1.3.1"
1546
  source = { registry = "https://pypi.org/simple" }
@@ -1550,6 +1601,7 @@ wheels = [
1550
  ]
1551
 
1552
  [[package]]
 
1553
  name = "starlette"
1554
  version = "1.0.0"
1555
  source = { registry = "https://pypi.org/simple" }
@@ -1563,6 +1615,8 @@ wheels = [
1563
  ]
1564
 
1565
  [[package]]
 
 
1566
  name = "tenacity"
1567
  version = "9.1.4"
1568
  source = { registry = "https://pypi.org/simple" }
@@ -1572,6 +1626,7 @@ wheels = [
1572
  ]
1573
 
1574
  [[package]]
 
1575
  name = "tomlkit"
1576
  version = "0.14.0"
1577
  source = { registry = "https://pypi.org/simple" }
@@ -1581,6 +1636,8 @@ wheels = [
1581
  ]
1582
 
1583
  [[package]]
 
 
1584
  name = "tqdm"
1585
  version = "4.67.3"
1586
  source = { registry = "https://pypi.org/simple" }
@@ -1593,6 +1650,7 @@ wheels = [
1593
  ]
1594
 
1595
  [[package]]
 
1596
  name = "typer"
1597
  version = "0.25.1"
1598
  source = { registry = "https://pypi.org/simple" }
@@ -1608,6 +1666,8 @@ wheels = [
1608
  ]
1609
 
1610
  [[package]]
 
 
1611
  name = "typing-extensions"
1612
  version = "4.15.0"
1613
  source = { registry = "https://pypi.org/simple" }
@@ -1629,6 +1689,7 @@ wheels = [
1629
  ]
1630
 
1631
  [[package]]
 
1632
  name = "tzdata"
1633
  version = "2026.2"
1634
  source = { registry = "https://pypi.org/simple" }
@@ -1638,6 +1699,8 @@ wheels = [
1638
  ]
1639
 
1640
  [[package]]
 
 
1641
  name = "urllib3"
1642
  version = "2.7.0"
1643
  source = { registry = "https://pypi.org/simple" }
@@ -1747,6 +1810,7 @@ wheels = [
1747
  ]
1748
 
1749
  [[package]]
 
1750
  name = "uvicorn"
1751
  version = "0.47.0"
1752
  source = { registry = "https://pypi.org/simple" }
@@ -1760,6 +1824,8 @@ wheels = [
1760
  ]
1761
 
1762
  [[package]]
 
 
1763
  name = "xxhash"
1764
  version = "3.7.0"
1765
  source = { registry = "https://pypi.org/simple" }
 
1
  version = 1
2
  revision = 3
3
  requires-python = ">=3.11"
4
+ <<<<<<< HEAD
5
  resolution-markers = [
6
  "python_full_version >= '3.14' and sys_platform == 'win32'",
7
  "python_full_version >= '3.14' and sys_platform == 'emscripten'",
 
22
  wheels = [
23
  { url = "https://files.pythonhosted.org/packages/1e/d3/26bf1008eb3d2daa8ef4cacc7f3bfdc11818d111f7e2d0201bc6e3b49d45/annotated_doc-0.0.4-py3-none-any.whl", hash = "sha256:571ac1dc6991c450b25a9c2d84a3705e2ae7a53467b5d111c24fa8baabbed320", size = 5303, upload-time = "2025-11-10T22:07:40.673Z" },
24
  ]
25
+ =======
26
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
27
 
28
  [[package]]
29
  name = "annotated-types"
 
67
  ]
68
 
69
  [[package]]
70
+ <<<<<<< HEAD
71
  name = "audioop-lts"
72
  version = "0.2.2"
73
  source = { registry = "https://pypi.org/simple" }
 
124
  ]
125
 
126
  [[package]]
127
+ =======
128
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
129
  name = "beacon"
130
  version = "0.1.0"
131
  source = { virtual = "." }
132
  dependencies = [
133
  { name = "anthropic" },
134
+ <<<<<<< HEAD
135
  { name = "gradio" },
136
+ =======
137
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
138
  { name = "langgraph" },
139
  { name = "openai" },
140
  { name = "python-dotenv" },
 
144
  [package.metadata]
145
  requires-dist = [
146
  { name = "anthropic", specifier = ">=0.50.0" },
147
+ <<<<<<< HEAD
148
  { name = "gradio", specifier = ">=4.0.0" },
149
+ =======
150
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
151
  { name = "langgraph", specifier = ">=1.2.0" },
152
  { name = "openai", specifier = ">=1.0.0" },
153
  { name = "python-dotenv", specifier = ">=1.2.2" },
 
155
  ]
156
 
157
  [[package]]
158
+ <<<<<<< HEAD
159
  name = "brotli"
160
  version = "1.2.0"
161
  source = { registry = "https://pypi.org/simple" }
 
204
  ]
205
 
206
  [[package]]
207
+ =======
208
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
209
  name = "certifi"
210
  version = "2026.4.22"
211
  source = { registry = "https://pypi.org/simple" }
 
304
  ]
305
 
306
  [[package]]
307
+ <<<<<<< HEAD
308
  name = "click"
309
  version = "8.3.3"
310
  source = { registry = "https://pypi.org/simple" }
 
317
  ]
318
 
319
  [[package]]
320
+ =======
321
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
322
  name = "colorama"
323
  version = "0.4.6"
324
  source = { registry = "https://pypi.org/simple" }
 
346
  ]
347
 
348
  [[package]]
349
+ <<<<<<< HEAD
350
  name = "fastapi"
351
  version = "0.136.1"
352
  source = { registry = "https://pypi.org/simple" }
 
445
  ]
446
 
447
  [[package]]
448
+ =======
449
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
450
  name = "h11"
451
  version = "0.16.0"
452
  source = { registry = "https://pypi.org/simple" }
 
456
  ]
457
 
458
  [[package]]
459
+ <<<<<<< HEAD
460
  name = "hf-gradio"
461
  version = "0.4.1"
462
  source = { registry = "https://pypi.org/simple" }
 
502
  ]
503
 
504
  [[package]]
505
+ =======
506
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
507
  name = "httpcore"
508
  version = "1.0.9"
509
  source = { registry = "https://pypi.org/simple" }
 
532
  ]
533
 
534
  [[package]]
535
+ <<<<<<< HEAD
536
  name = "huggingface-hub"
537
  version = "1.14.0"
538
  source = { registry = "https://pypi.org/simple" }
 
553
  ]
554
 
555
  [[package]]
556
+ =======
557
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
558
  name = "idna"
559
  version = "3.14"
560
  source = { registry = "https://pypi.org/simple" }
 
564
  ]
565
 
566
  [[package]]
567
+ <<<<<<< HEAD
568
  name = "jinja2"
569
  version = "3.1.6"
570
  source = { registry = "https://pypi.org/simple" }
 
577
  ]
578
 
579
  [[package]]
580
+ =======
581
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
582
  name = "jiter"
583
  version = "0.14.0"
584
  source = { registry = "https://pypi.org/simple" }
 
810
  ]
811
 
812
  [[package]]
813
+ <<<<<<< HEAD
814
  name = "markupsafe"
815
  version = "3.0.3"
816
  source = { registry = "https://pypi.org/simple" }
 
885
  ]
886
 
887
  [[package]]
888
+ =======
889
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
890
  name = "mdurl"
891
  version = "0.1.2"
892
  source = { registry = "https://pypi.org/simple" }
 
896
  ]
897
 
898
  [[package]]
899
+ <<<<<<< HEAD
900
  name = "numpy"
901
  version = "2.4.4"
902
  source = { registry = "https://pypi.org/simple" }
 
976
  ]
977
 
978
  [[package]]
979
+ =======
980
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
981
  name = "openai"
982
  version = "2.36.0"
983
  source = { registry = "https://pypi.org/simple" }
 
1122
  ]
1123
 
1124
  [[package]]
1125
+ <<<<<<< HEAD
1126
  name = "pandas"
1127
  version = "3.0.3"
1128
  source = { registry = "https://pypi.org/simple" }
 
1270
  ]
1271
 
1272
  [[package]]
1273
+ =======
1274
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
1275
  name = "pydantic"
1276
  version = "2.13.4"
1277
  source = { registry = "https://pypi.org/simple" }
 
1389
  ]
1390
 
1391
  [[package]]
1392
+ <<<<<<< HEAD
1393
  name = "pydub"
1394
  version = "0.25.1"
1395
  source = { registry = "https://pypi.org/simple" }
 
1399
  ]
1400
 
1401
  [[package]]
1402
+ =======
1403
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
1404
  name = "pygments"
1405
  version = "2.20.0"
1406
  source = { registry = "https://pypi.org/simple" }
 
1410
  ]
1411
 
1412
  [[package]]
1413
+ <<<<<<< HEAD
1414
  name = "python-dateutil"
1415
  version = "2.9.0.post0"
1416
  source = { registry = "https://pypi.org/simple" }
 
1423
  ]
1424
 
1425
  [[package]]
1426
+ =======
1427
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
1428
  name = "python-dotenv"
1429
  version = "1.2.2"
1430
  source = { registry = "https://pypi.org/simple" }
 
1434
  ]
1435
 
1436
  [[package]]
1437
+ <<<<<<< HEAD
1438
  name = "python-multipart"
1439
  version = "0.0.28"
1440
  source = { registry = "https://pypi.org/simple" }
 
1453
  ]
1454
 
1455
  [[package]]
1456
+ =======
1457
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
1458
  name = "pyyaml"
1459
  version = "6.0.3"
1460
  source = { registry = "https://pypi.org/simple" }
 
1550
  ]
1551
 
1552
  [[package]]
1553
+ <<<<<<< HEAD
1554
  name = "safehttpx"
1555
  version = "0.1.7"
1556
  source = { registry = "https://pypi.org/simple" }
 
1590
  ]
1591
 
1592
  [[package]]
1593
+ =======
1594
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
1595
  name = "sniffio"
1596
  version = "1.3.1"
1597
  source = { registry = "https://pypi.org/simple" }
 
1601
  ]
1602
 
1603
  [[package]]
1604
+ <<<<<<< HEAD
1605
  name = "starlette"
1606
  version = "1.0.0"
1607
  source = { registry = "https://pypi.org/simple" }
 
1615
  ]
1616
 
1617
  [[package]]
1618
+ =======
1619
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
1620
  name = "tenacity"
1621
  version = "9.1.4"
1622
  source = { registry = "https://pypi.org/simple" }
 
1626
  ]
1627
 
1628
  [[package]]
1629
+ <<<<<<< HEAD
1630
  name = "tomlkit"
1631
  version = "0.14.0"
1632
  source = { registry = "https://pypi.org/simple" }
 
1636
  ]
1637
 
1638
  [[package]]
1639
+ =======
1640
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
1641
  name = "tqdm"
1642
  version = "4.67.3"
1643
  source = { registry = "https://pypi.org/simple" }
 
1650
  ]
1651
 
1652
  [[package]]
1653
+ <<<<<<< HEAD
1654
  name = "typer"
1655
  version = "0.25.1"
1656
  source = { registry = "https://pypi.org/simple" }
 
1666
  ]
1667
 
1668
  [[package]]
1669
+ =======
1670
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
1671
  name = "typing-extensions"
1672
  version = "4.15.0"
1673
  source = { registry = "https://pypi.org/simple" }
 
1689
  ]
1690
 
1691
  [[package]]
1692
+ <<<<<<< HEAD
1693
  name = "tzdata"
1694
  version = "2026.2"
1695
  source = { registry = "https://pypi.org/simple" }
 
1699
  ]
1700
 
1701
  [[package]]
1702
+ =======
1703
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
1704
  name = "urllib3"
1705
  version = "2.7.0"
1706
  source = { registry = "https://pypi.org/simple" }
 
1810
  ]
1811
 
1812
  [[package]]
1813
+ <<<<<<< HEAD
1814
  name = "uvicorn"
1815
  version = "0.47.0"
1816
  source = { registry = "https://pypi.org/simple" }
 
1824
  ]
1825
 
1826
  [[package]]
1827
+ =======
1828
+ >>>>>>> 6977736 (Initial release: Beacon rare disease clinical trial finder)
1829
  name = "xxhash"
1830
  version = "3.7.0"
1831
  source = { registry = "https://pypi.org/simple" }