open-pharma-plugins 2.2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mcp_framework.py +495 -0
- open_pharma_plugins-2.2.0.dist-info/METADATA +135 -0
- open_pharma_plugins-2.2.0.dist-info/RECORD +136 -0
- open_pharma_plugins-2.2.0.dist-info/WHEEL +5 -0
- open_pharma_plugins-2.2.0.dist-info/entry_points.txt +8 -0
- open_pharma_plugins-2.2.0.dist-info/licenses/LICENSE +202 -0
- open_pharma_plugins-2.2.0.dist-info/top_level.txt +8 -0
- open_pharma_plugins_campaign_studio/__init__.py +14 -0
- open_pharma_plugins_campaign_studio/__main__.py +11 -0
- open_pharma_plugins_campaign_studio/_campaign_store.py +162 -0
- open_pharma_plugins_campaign_studio/_claim_engine.py +262 -0
- open_pharma_plugins_campaign_studio/_renderer.py +119 -0
- open_pharma_plugins_campaign_studio/fixtures/brand_kit/legal.json +21 -0
- open_pharma_plugins_campaign_studio/fixtures/brand_kit/logo.svg +4 -0
- open_pharma_plugins_campaign_studio/fixtures/brand_kit/palette.json +11 -0
- open_pharma_plugins_campaign_studio/fixtures/brand_kit/product.png +1 -0
- open_pharma_plugins_campaign_studio/fixtures/brand_kit/typography.json +14 -0
- open_pharma_plugins_campaign_studio/fixtures/sample_approved_claims.json +119 -0
- open_pharma_plugins_campaign_studio/models/__init__.py +34 -0
- open_pharma_plugins_campaign_studio/models/_common.py +12 -0
- open_pharma_plugins_campaign_studio/models/brief.py +72 -0
- open_pharma_plugins_campaign_studio/models/claims.py +15 -0
- open_pharma_plugins_campaign_studio/models/copy.py +47 -0
- open_pharma_plugins_campaign_studio/models/journey.py +21 -0
- open_pharma_plugins_campaign_studio/models/message.py +25 -0
- open_pharma_plugins_campaign_studio/models/mlr.py +29 -0
- open_pharma_plugins_campaign_studio/models/validation.py +32 -0
- open_pharma_plugins_campaign_studio/policy/rules.json +79 -0
- open_pharma_plugins_campaign_studio/templates/banner.svg.j2 +26 -0
- open_pharma_plugins_campaign_studio/templates/email.html.j2 +54 -0
- open_pharma_plugins_campaign_studio/tools/__init__.py +0 -0
- open_pharma_plugins_campaign_studio/tools/create_campaign_brief.py +212 -0
- open_pharma_plugins_campaign_studio/tools/generate_audience_journey.py +129 -0
- open_pharma_plugins_campaign_studio/tools/generate_channel_copy.py +199 -0
- open_pharma_plugins_campaign_studio/tools/generate_message_architecture.py +121 -0
- open_pharma_plugins_campaign_studio/tools/package_mlr_submission.py +230 -0
- open_pharma_plugins_campaign_studio/tools/render_banner.py +99 -0
- open_pharma_plugins_campaign_studio/tools/render_email.py +101 -0
- open_pharma_plugins_campaign_studio/tools/render_poster.py +222 -0
- open_pharma_plugins_campaign_studio/tools/retrieve_approved_claims.py +71 -0
- open_pharma_plugins_campaign_studio/tools/retrieve_brand_components.py +76 -0
- open_pharma_plugins_campaign_studio/tools/validate_claims_and_fair_balance.py +285 -0
- open_pharma_plugins_competitive_intelligence/__init__.py +13 -0
- open_pharma_plugins_competitive_intelligence/__main__.py +11 -0
- open_pharma_plugins_competitive_intelligence/_artifacts.py +87 -0
- open_pharma_plugins_competitive_intelligence/_cache.py +144 -0
- open_pharma_plugins_competitive_intelligence/_clinical_trials.py +569 -0
- open_pharma_plugins_competitive_intelligence/_dailymed.py +260 -0
- open_pharma_plugins_competitive_intelligence/_fda.py +255 -0
- open_pharma_plugins_competitive_intelligence/_pubmed.py +342 -0
- open_pharma_plugins_competitive_intelligence/_regulatory.py +140 -0
- open_pharma_plugins_competitive_intelligence/_runs.py +278 -0
- open_pharma_plugins_competitive_intelligence/_transport.py +83 -0
- open_pharma_plugins_competitive_intelligence/_watchlist.py +113 -0
- open_pharma_plugins_competitive_intelligence/_web_search.py +331 -0
- open_pharma_plugins_competitive_intelligence/models.py +525 -0
- open_pharma_plugins_competitive_intelligence/tools/__init__.py +0 -0
- open_pharma_plugins_competitive_intelligence/tools/ci_extract_events.py +247 -0
- open_pharma_plugins_competitive_intelligence/tools/ci_landscape.py +195 -0
- open_pharma_plugins_competitive_intelligence/tools/ci_refresh.py +101 -0
- open_pharma_plugins_competitive_intelligence/tools/ci_report.py +402 -0
- open_pharma_plugins_competitive_intelligence/tools/ci_scan_news.py +48 -0
- open_pharma_plugins_competitive_intelligence/tools/ci_scan_publications.py +46 -0
- open_pharma_plugins_competitive_intelligence/tools/ci_scan_regulatory.py +64 -0
- open_pharma_plugins_competitive_intelligence/tools/ci_scan_trials.py +85 -0
- open_pharma_plugins_competitive_intelligence/tools/ci_status.py +106 -0
- open_pharma_plugins_competitive_intelligence/tools/ci_timeline.py +453 -0
- open_pharma_plugins_competitive_intelligence/tools/ci_track.py +121 -0
- open_pharma_plugins_competitive_intelligence/tools/ci_trial_detail.py +55 -0
- open_pharma_plugins_field_training/__init__.py +13 -0
- open_pharma_plugins_field_training/__main__.py +11 -0
- open_pharma_plugins_field_training/_content_store.py +131 -0
- open_pharma_plugins_field_training/_grounding.py +75 -0
- open_pharma_plugins_field_training/_html_renderers.py +546 -0
- open_pharma_plugins_field_training/fixtures/sample_product_message.pdf +156 -0
- open_pharma_plugins_field_training/fixtures/sample_training_deck.pptx +0 -0
- open_pharma_plugins_field_training/models.py +265 -0
- open_pharma_plugins_field_training/tools/__init__.py +0 -0
- open_pharma_plugins_field_training/tools/get_document_page.py +67 -0
- open_pharma_plugins_field_training/tools/ingest_document.py +147 -0
- open_pharma_plugins_field_training/tools/list_documents.py +51 -0
- open_pharma_plugins_field_training/tools/render_output.py +118 -0
- open_pharma_plugins_field_training/tools/search_content.py +57 -0
- open_pharma_plugins_hcp_intelligence/__init__.py +14 -0
- open_pharma_plugins_hcp_intelligence/__main__.py +11 -0
- open_pharma_plugins_hcp_intelligence/_crm_store.py +70 -0
- open_pharma_plugins_hcp_intelligence/batch.py +891 -0
- open_pharma_plugins_hcp_intelligence/batch_cli.py +221 -0
- open_pharma_plugins_hcp_intelligence/batch_csv.py +206 -0
- open_pharma_plugins_hcp_intelligence/fixtures/sample_accounts.csv +27 -0
- open_pharma_plugins_hcp_intelligence/models.py +356 -0
- open_pharma_plugins_hcp_intelligence/tools/__init__.py +0 -0
- open_pharma_plugins_hcp_intelligence/tools/get_account.py +48 -0
- open_pharma_plugins_hcp_intelligence/tools/list_accounts.py +66 -0
- open_pharma_plugins_hcp_intelligence/tools/search_clinical_trials.py +175 -0
- open_pharma_plugins_hcp_intelligence/tools/search_congresses.py +134 -0
- open_pharma_plugins_hcp_intelligence/tools/search_grants.py +181 -0
- open_pharma_plugins_hcp_intelligence/tools/search_guidelines.py +238 -0
- open_pharma_plugins_hcp_intelligence/tools/search_hco_web.py +73 -0
- open_pharma_plugins_hcp_intelligence/tools/search_hcp_web.py +199 -0
- open_pharma_plugins_hcp_intelligence/tools/search_orcid.py +214 -0
- open_pharma_plugins_hcp_intelligence/tools/search_publications.py +207 -0
- open_pharma_plugins_hcp_intelligence/tools/update_account.py +84 -0
- open_pharma_plugins_next_best_engagement/__init__.py +14 -0
- open_pharma_plugins_next_best_engagement/__main__.py +11 -0
- open_pharma_plugins_next_best_engagement/_optimizer.py +400 -0
- open_pharma_plugins_next_best_engagement/_renderer.py +149 -0
- open_pharma_plugins_next_best_engagement/_scoring.py +82 -0
- open_pharma_plugins_next_best_engagement/_universe.py +145 -0
- open_pharma_plugins_next_best_engagement/fixtures/sample_universe.csv +81 -0
- open_pharma_plugins_next_best_engagement/models.py +135 -0
- open_pharma_plugins_next_best_engagement/tools/__init__.py +0 -0
- open_pharma_plugins_next_best_engagement/tools/load_universe.py +47 -0
- open_pharma_plugins_next_best_engagement/tools/recommend_engagements.py +80 -0
- open_pharma_plugins_next_best_engagement/tools/render_plan.py +90 -0
- open_pharma_plugins_territory_alignment/__init__.py +14 -0
- open_pharma_plugins_territory_alignment/__main__.py +11 -0
- open_pharma_plugins_territory_alignment/data.py +300 -0
- open_pharma_plugins_territory_alignment/fixtures/constraints.csv +11 -0
- open_pharma_plugins_territory_alignment/fixtures/current_alignment.csv +81 -0
- open_pharma_plugins_territory_alignment/fixtures/hcps.csv +81 -0
- open_pharma_plugins_territory_alignment/fixtures/reps.csv +9 -0
- open_pharma_plugins_territory_alignment/geo.py +175 -0
- open_pharma_plugins_territory_alignment/models.py +201 -0
- open_pharma_plugins_territory_alignment/scoring.py +125 -0
- open_pharma_plugins_territory_alignment/solver.py +504 -0
- open_pharma_plugins_territory_alignment/tools/__init__.py +0 -0
- open_pharma_plugins_territory_alignment/tools/ta_align.py +138 -0
- open_pharma_plugins_territory_alignment/tools/ta_cluster.py +247 -0
- open_pharma_plugins_territory_alignment/tools/ta_compare.py +173 -0
- open_pharma_plugins_territory_alignment/tools/ta_evaluate.py +119 -0
- open_pharma_plugins_territory_alignment/tools/ta_status.py +34 -0
- open_pharma_plugins_territory_alignment/tools/ta_visualize.py +504 -0
- shared/__init__.py +11 -0
- shared/env.py +217 -0
- shared/filesystem.py +110 -0
|
@@ -0,0 +1,356 @@
|
|
|
1
|
+
"""Pydantic output models for HCP/HCO intelligence profiles.
|
|
2
|
+
|
|
3
|
+
Every profile claim carries its own provenance chain so downstream consumers
|
|
4
|
+
(commercial engagement, medical affairs, compliance) can trace each assertion
|
|
5
|
+
back to a retrievable source.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from enum import Enum
|
|
11
|
+
from typing import Literal
|
|
12
|
+
|
|
13
|
+
from pydantic import BaseModel, Field
|
|
14
|
+
|
|
15
|
+
# ---------------------------------------------------------------------------
|
|
16
|
+
# Provenance primitives
|
|
17
|
+
# ---------------------------------------------------------------------------
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class SourceType(str, Enum):
|
|
21
|
+
PUBMED = "pubmed"
|
|
22
|
+
CLINICAL_TRIALS = "clinical_trials"
|
|
23
|
+
WEB = "web"
|
|
24
|
+
REGISTRY = "registry"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Confidence(str, Enum):
|
|
28
|
+
HIGH = "high"
|
|
29
|
+
MEDIUM = "medium"
|
|
30
|
+
LOW = "low"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class SourceCitation(BaseModel):
|
|
34
|
+
"""A single retrievable source backing a claim."""
|
|
35
|
+
|
|
36
|
+
url: str = Field(description="Permanent URL or identifier (PubMed link, NCT URL, web page)")
|
|
37
|
+
source_type: SourceType = Field(description="Category of the data source")
|
|
38
|
+
title: str | None = Field(default=None, description="Title of the source document or page")
|
|
39
|
+
accessed_date: str = Field(description="ISO-8601 date when the source was retrieved")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class EvidencedClaim(BaseModel):
|
|
43
|
+
"""A single assertion paired with the sources that support it."""
|
|
44
|
+
|
|
45
|
+
value: str = Field(description="The factual claim or data point")
|
|
46
|
+
sources: list[SourceCitation] = Field(min_length=1, description="One or more sources supporting this claim")
|
|
47
|
+
confidence: Confidence = Field(
|
|
48
|
+
description=(
|
|
49
|
+
"high = corroborated by multiple authoritative sources; "
|
|
50
|
+
"medium = single authoritative source or multiple informal sources; "
|
|
51
|
+
"low = single informal source or inferred"
|
|
52
|
+
)
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
# ---------------------------------------------------------------------------
|
|
57
|
+
# Search result models (returned by the search_* tools)
|
|
58
|
+
# ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class Publication(BaseModel):
|
|
62
|
+
"""A single publication record from PubMed or equivalent."""
|
|
63
|
+
|
|
64
|
+
pmid: str | None = Field(default=None, description="PubMed ID")
|
|
65
|
+
title: str = Field(description="Publication title")
|
|
66
|
+
authors: list[str] = Field(description="Author names as listed")
|
|
67
|
+
journal: str = Field(description="Journal name")
|
|
68
|
+
year: int = Field(description="Publication year")
|
|
69
|
+
doi: str | None = Field(default=None, description="Digital Object Identifier")
|
|
70
|
+
abstract: str | None = Field(default=None, description="Abstract text (truncated if long)")
|
|
71
|
+
mesh_terms: list[str] = Field(default_factory=list, description="MeSH descriptor terms")
|
|
72
|
+
publication_types: list[str] = Field(default_factory=list, description="e.g. Clinical Trial, Review, Meta-Analysis")
|
|
73
|
+
source_url: str = Field(description="Direct URL to the publication record")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
class PublicationSearchResult(BaseModel):
|
|
77
|
+
"""Structured result from search_publications."""
|
|
78
|
+
|
|
79
|
+
query: str = Field(description="The query that was executed")
|
|
80
|
+
total_count: int = Field(description="Total matching records in the database")
|
|
81
|
+
publications: list[Publication] = Field(description="Retrieved publication records")
|
|
82
|
+
source: Literal["pubmed", "europe_pmc"] = Field(description="Which API was queried")
|
|
83
|
+
searched_at: str = Field(description="ISO-8601 timestamp of the search")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ClinicalTrial(BaseModel):
|
|
87
|
+
"""A single clinical trial record from ClinicalTrials.gov."""
|
|
88
|
+
|
|
89
|
+
nct_id: str = Field(description="ClinicalTrials.gov identifier (NCT number)")
|
|
90
|
+
title: str = Field(description="Official study title")
|
|
91
|
+
status: str = Field(description="Overall recruitment status")
|
|
92
|
+
phase: str | None = Field(default=None, description="Trial phase (Phase 1, 2, 3, 4, N/A)")
|
|
93
|
+
conditions: list[str] = Field(description="Conditions or diseases studied")
|
|
94
|
+
interventions: list[str] = Field(description="Drugs, devices, or procedures under study")
|
|
95
|
+
sponsor: str | None = Field(default=None, description="Lead sponsor organization")
|
|
96
|
+
collaborators: list[str] = Field(default_factory=list, description="Collaborating organizations")
|
|
97
|
+
start_date: str | None = Field(default=None, description="Study start date")
|
|
98
|
+
completion_date: str | None = Field(default=None, description="Primary completion date")
|
|
99
|
+
investigator_name: str | None = Field(default=None, description="Listed PI or contact name")
|
|
100
|
+
investigator_role: str | None = Field(
|
|
101
|
+
default=None, description="Role: Principal Investigator, Sub-Investigator, etc."
|
|
102
|
+
)
|
|
103
|
+
source_url: str = Field(description="Direct URL to the trial record")
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
class ClinicalTrialSearchResult(BaseModel):
|
|
107
|
+
"""Structured result from search_clinical_trials."""
|
|
108
|
+
|
|
109
|
+
query: str = Field(description="The query that was executed")
|
|
110
|
+
total_count: int = Field(description="Total matching studies")
|
|
111
|
+
trials: list[ClinicalTrial] = Field(description="Retrieved trial records")
|
|
112
|
+
source: Literal["clinicaltrials.gov"] = "clinicaltrials.gov"
|
|
113
|
+
searched_at: str = Field(description="ISO-8601 timestamp of the search")
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class Grant(BaseModel):
|
|
117
|
+
"""A single research grant record (typically from NIH RePORTER)."""
|
|
118
|
+
|
|
119
|
+
grant_number: str | None = Field(default=None, description="Grant number (e.g. R01CA123456)")
|
|
120
|
+
title: str = Field(description="Project title")
|
|
121
|
+
pi_name: str | None = Field(default=None, description="Principal investigator name")
|
|
122
|
+
institution: str | None = Field(default=None, description="Grantee institution")
|
|
123
|
+
award_amount: float | None = Field(default=None, description="Award amount for the fiscal year (USD)")
|
|
124
|
+
start_date: str | None = Field(default=None, description="Project start date")
|
|
125
|
+
end_date: str | None = Field(default=None, description="Project end date")
|
|
126
|
+
agency: str | None = Field(default=None, description="Funding agency (e.g. NCI, NHLBI)")
|
|
127
|
+
is_active: bool | None = Field(default=None, description="Whether the grant is currently active")
|
|
128
|
+
abstract: str | None = Field(default=None, description="Project abstract (truncated)")
|
|
129
|
+
source_url: str = Field(description="Direct URL to the grant record")
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class OrcidProfile(BaseModel):
|
|
133
|
+
"""Structured profile from ORCID public API."""
|
|
134
|
+
|
|
135
|
+
orcid_id: str = Field(description="ORCID identifier (e.g. 0000-0002-1234-5678)")
|
|
136
|
+
given_names: str | None = Field(default=None, description="Given names")
|
|
137
|
+
family_name: str | None = Field(default=None, description="Family name")
|
|
138
|
+
biography: str | None = Field(default=None, description="Self-authored biography (truncated)")
|
|
139
|
+
education: list[dict] = Field(
|
|
140
|
+
default_factory=list,
|
|
141
|
+
description="Education records: institution, degree, department, years",
|
|
142
|
+
)
|
|
143
|
+
employment: list[dict] = Field(
|
|
144
|
+
default_factory=list,
|
|
145
|
+
description="Employment records: institution, role, department, years",
|
|
146
|
+
)
|
|
147
|
+
publication_count: int = Field(default=0, description="Total works in ORCID profile")
|
|
148
|
+
funding_count: int = Field(default=0, description="Total funding entries")
|
|
149
|
+
peer_review_count: int = Field(default=0, description="Total peer review entries")
|
|
150
|
+
profile_url: str = Field(description="Direct URL to the ORCID profile")
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
class CongressAppearance(BaseModel):
|
|
154
|
+
"""A congress/conference appearance found via web search."""
|
|
155
|
+
|
|
156
|
+
congress_name: str = Field(description="Name or abbreviation of the congress (e.g. ASCO 2025)")
|
|
157
|
+
role: str | None = Field(
|
|
158
|
+
default=None,
|
|
159
|
+
description="Presentation role if identifiable: keynote, invited lecture, oral, poster, chair, moderator",
|
|
160
|
+
)
|
|
161
|
+
title: str | None = Field(default=None, description="Title of the presentation or session")
|
|
162
|
+
year: int | None = Field(default=None, description="Year of the congress")
|
|
163
|
+
source_url: str = Field(description="URL where this appearance was found")
|
|
164
|
+
|
|
165
|
+
|
|
166
|
+
class WebResult(BaseModel):
|
|
167
|
+
"""A single web search result."""
|
|
168
|
+
|
|
169
|
+
url: str = Field(description="Page URL")
|
|
170
|
+
title: str = Field(description="Page title")
|
|
171
|
+
snippet: str = Field(description="Relevant excerpt from the page")
|
|
172
|
+
published_date: str | None = Field(default=None, description="Page publication date if known")
|
|
173
|
+
domain: str | None = Field(default=None, description="Source domain (e.g. hospital.org)")
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
class WebSearchResult(BaseModel):
|
|
177
|
+
"""Structured result from search_hcp_web or search_hco_web."""
|
|
178
|
+
|
|
179
|
+
query: str = Field(description="The constructed search query")
|
|
180
|
+
results: list[WebResult] = Field(description="Retrieved web results")
|
|
181
|
+
search_backend: str = Field(description="Backend used: exa, tavily, or serper")
|
|
182
|
+
searched_at: str = Field(description="ISO-8601 timestamp of the search")
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
# ---------------------------------------------------------------------------
|
|
186
|
+
# Profile output models
|
|
187
|
+
# ---------------------------------------------------------------------------
|
|
188
|
+
|
|
189
|
+
|
|
190
|
+
class HcpProfile(BaseModel):
|
|
191
|
+
"""Canonical profile for an individual Healthcare Professional.
|
|
192
|
+
|
|
193
|
+
Every field that makes a factual assertion uses EvidencedClaim so the
|
|
194
|
+
provenance of each data point is individually traceable.
|
|
195
|
+
"""
|
|
196
|
+
|
|
197
|
+
# --- Identity ---
|
|
198
|
+
full_name: str = Field(description="Full name as identified across sources")
|
|
199
|
+
specialty: str = Field(description="Primary medical specialty")
|
|
200
|
+
country: str = Field(description="Primary country of practice")
|
|
201
|
+
|
|
202
|
+
# --- Professional standing ---
|
|
203
|
+
current_title: EvidencedClaim | None = Field(
|
|
204
|
+
default=None, description="Current job title (e.g. Head of Department, Senior Consultant)"
|
|
205
|
+
)
|
|
206
|
+
designations: list[EvidencedClaim] = Field(
|
|
207
|
+
default_factory=list, description="Honorifics or designations (Prof., Dr., FRCP, etc.)"
|
|
208
|
+
)
|
|
209
|
+
affiliations: list[EvidencedClaim] = Field(
|
|
210
|
+
default_factory=list,
|
|
211
|
+
description="Current and recent institutional affiliations",
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
# --- Education & qualifications ---
|
|
215
|
+
education: list[EvidencedClaim] = Field(
|
|
216
|
+
default_factory=list, description="Degrees and institutions (e.g. MBBS, University of X)"
|
|
217
|
+
)
|
|
218
|
+
qualifications: list[EvidencedClaim] = Field(
|
|
219
|
+
default_factory=list,
|
|
220
|
+
description="Board certifications, fellowships, specialist registrations",
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
# --- Professional activities ---
|
|
224
|
+
society_memberships: list[EvidencedClaim] = Field(
|
|
225
|
+
default_factory=list,
|
|
226
|
+
description="Medical society and association memberships",
|
|
227
|
+
)
|
|
228
|
+
professional_roles: list[EvidencedClaim] = Field(
|
|
229
|
+
default_factory=list,
|
|
230
|
+
description="Committee chairs, advisory board seats, guideline panel roles",
|
|
231
|
+
)
|
|
232
|
+
editorial_roles: list[EvidencedClaim] = Field(default_factory=list, description="Journal editorial board positions")
|
|
233
|
+
|
|
234
|
+
# --- Research profile ---
|
|
235
|
+
research_interests: list[EvidencedClaim] = Field(
|
|
236
|
+
default_factory=list,
|
|
237
|
+
description="Key research themes derived from publications and trials",
|
|
238
|
+
)
|
|
239
|
+
publication_summary: EvidencedClaim | None = Field(
|
|
240
|
+
default=None,
|
|
241
|
+
description="Summary: total count, h-index if available, top journals",
|
|
242
|
+
)
|
|
243
|
+
key_publications: list[Publication] = Field(
|
|
244
|
+
default_factory=list,
|
|
245
|
+
description="Most relevant recent publications (up to 10)",
|
|
246
|
+
)
|
|
247
|
+
guideline_publications: list[Publication] = Field(
|
|
248
|
+
default_factory=list,
|
|
249
|
+
description="Practice guidelines, consensus statements authored by this HCP",
|
|
250
|
+
)
|
|
251
|
+
clinical_trial_involvement: list[ClinicalTrial] = Field(
|
|
252
|
+
default_factory=list,
|
|
253
|
+
description="Clinical trials where this HCP is listed as investigator",
|
|
254
|
+
)
|
|
255
|
+
active_grants: list[Grant] = Field(
|
|
256
|
+
default_factory=list,
|
|
257
|
+
description="Current and recent research grants (PI or co-PI)",
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# --- KOL signals ---
|
|
261
|
+
congress_activity: list[CongressAppearance] = Field(
|
|
262
|
+
default_factory=list,
|
|
263
|
+
description="Conference speaking roles, poster presentations, session chairs",
|
|
264
|
+
)
|
|
265
|
+
regulatory_advisory_roles: list[EvidencedClaim] = Field(
|
|
266
|
+
default_factory=list,
|
|
267
|
+
description="FDA advisory committee, EMA CHMP/SAWP, WHO expert panel roles",
|
|
268
|
+
)
|
|
269
|
+
orcid_id: str | None = Field(default=None, description="ORCID identifier if found")
|
|
270
|
+
|
|
271
|
+
# --- Metadata ---
|
|
272
|
+
profile_completeness: float = Field(
|
|
273
|
+
ge=0.0,
|
|
274
|
+
le=1.0,
|
|
275
|
+
description=(
|
|
276
|
+
"Estimated completeness: 1.0 = all sections populated with high-confidence "
|
|
277
|
+
"claims; lower values indicate sections with no data or low confidence"
|
|
278
|
+
),
|
|
279
|
+
)
|
|
280
|
+
disambiguation_notes: str | None = Field(
|
|
281
|
+
default=None,
|
|
282
|
+
description="Notes on how this person was distinguished from others with similar names",
|
|
283
|
+
)
|
|
284
|
+
sources_consulted: list[SourceCitation] = Field(description="All sources accessed during profile construction")
|
|
285
|
+
built_at: str = Field(description="ISO-8601 timestamp when the profile was assembled")
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
class HcoProfile(BaseModel):
|
|
289
|
+
"""Canonical profile for a Healthcare Organization.
|
|
290
|
+
|
|
291
|
+
Covers clinical scope, scale, research activity, and institutional
|
|
292
|
+
history with per-claim provenance.
|
|
293
|
+
"""
|
|
294
|
+
|
|
295
|
+
# --- Identity ---
|
|
296
|
+
name: str = Field(description="Official name of the organization")
|
|
297
|
+
country: str = Field(description="Country")
|
|
298
|
+
organization_type: EvidencedClaim | None = Field(
|
|
299
|
+
default=None,
|
|
300
|
+
description="Type: tertiary hospital, specialty clinic, research institute, etc.",
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
# --- Clinical scope ---
|
|
304
|
+
clinical_focus_areas: list[EvidencedClaim] = Field(
|
|
305
|
+
default_factory=list,
|
|
306
|
+
description="Primary therapeutic areas or disease focus",
|
|
307
|
+
)
|
|
308
|
+
specialist_departments: list[EvidencedClaim] = Field(
|
|
309
|
+
default_factory=list,
|
|
310
|
+
description="Named departments, centres of excellence, or specialist units",
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
# --- Scale & capacity ---
|
|
314
|
+
bed_capacity: EvidencedClaim | None = Field(default=None, description="Total bed count or ICU/ward breakdown")
|
|
315
|
+
annual_patient_volume: EvidencedClaim | None = Field(
|
|
316
|
+
default=None, description="Annual admissions, outpatient visits, or surgical volume"
|
|
317
|
+
)
|
|
318
|
+
staff_count: EvidencedClaim | None = Field(default=None, description="Medical staff, nursing, or total headcount")
|
|
319
|
+
accreditations: list[EvidencedClaim] = Field(
|
|
320
|
+
default_factory=list,
|
|
321
|
+
description="JCI, NABH, Magnet, or national accreditation status",
|
|
322
|
+
)
|
|
323
|
+
|
|
324
|
+
# --- Research & trials ---
|
|
325
|
+
research_focus: list[EvidencedClaim] = Field(
|
|
326
|
+
default_factory=list,
|
|
327
|
+
description="Active research programmes, centres, or partnerships",
|
|
328
|
+
)
|
|
329
|
+
active_clinical_trials: list[ClinicalTrial] = Field(
|
|
330
|
+
default_factory=list,
|
|
331
|
+
description="Clinical trials listing this organization as a site or sponsor",
|
|
332
|
+
)
|
|
333
|
+
institutional_grants: list[Grant] = Field(
|
|
334
|
+
default_factory=list,
|
|
335
|
+
description="Research grants awarded to this organization",
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
# --- History & context ---
|
|
339
|
+
founding_year: EvidencedClaim | None = Field(default=None, description="Year the organization was established")
|
|
340
|
+
key_milestones: list[EvidencedClaim] = Field(
|
|
341
|
+
default_factory=list,
|
|
342
|
+
description="Notable events: mergers, expansions, firsts, rankings",
|
|
343
|
+
)
|
|
344
|
+
notable_affiliations: list[EvidencedClaim] = Field(
|
|
345
|
+
default_factory=list,
|
|
346
|
+
description="University partnerships, network memberships, government designations",
|
|
347
|
+
)
|
|
348
|
+
|
|
349
|
+
# --- Metadata ---
|
|
350
|
+
profile_completeness: float = Field(
|
|
351
|
+
ge=0.0,
|
|
352
|
+
le=1.0,
|
|
353
|
+
description="Estimated completeness (same semantics as HcpProfile)",
|
|
354
|
+
)
|
|
355
|
+
sources_consulted: list[SourceCitation] = Field(description="All sources accessed during profile construction")
|
|
356
|
+
built_at: str = Field(description="ISO-8601 timestamp when the profile was assembled")
|
|
File without changes
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""get_account — simulates a CRM detail view for a single account."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GetAccountArgs(BaseModel):
|
|
11
|
+
account_id: str = Field(description="Account ID (e.g. 'HCP-AU-001', 'HCO-SG-002')")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
TOOL: dict[str, Any] = {
|
|
15
|
+
"name": "get_account",
|
|
16
|
+
"description": (
|
|
17
|
+
"Get detailed information for a single HCP or HCO account by ID. Returns "
|
|
18
|
+
"the account fields (name, specialty, country, institution) plus enrichment "
|
|
19
|
+
"status and the full profile JSON if the account has been enriched. Use this "
|
|
20
|
+
"to check an account before or after profiling."
|
|
21
|
+
),
|
|
22
|
+
"args": GetAccountArgs,
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def handle(arguments: dict[str, Any]) -> list[dict[str, Any]]:
|
|
27
|
+
import json
|
|
28
|
+
|
|
29
|
+
from .._crm_store import get_account_by_id
|
|
30
|
+
|
|
31
|
+
account_id = arguments["account_id"]
|
|
32
|
+
record = get_account_by_id(account_id)
|
|
33
|
+
|
|
34
|
+
if record is None:
|
|
35
|
+
return [{"type": "text", "text": json.dumps({"error": f"Account '{account_id}' not found"})}]
|
|
36
|
+
|
|
37
|
+
output = {
|
|
38
|
+
"id": record["id"],
|
|
39
|
+
"name": record["name"],
|
|
40
|
+
"specialty": record["specialty"],
|
|
41
|
+
"country": record["country"],
|
|
42
|
+
"account_type": record["account_type"],
|
|
43
|
+
"institution": record.get("institution", ""),
|
|
44
|
+
"status": record["status"],
|
|
45
|
+
"last_enriched": record.get("last_enriched"),
|
|
46
|
+
"enrichment": record.get("enrichment"),
|
|
47
|
+
}
|
|
48
|
+
return [{"type": "text", "text": json.dumps(output, indent=2)}]
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""list_accounts — simulates a CRM list view over sample account data."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ListAccountsArgs(BaseModel):
|
|
11
|
+
country: str | None = Field(default=None, description="Filter by country (e.g. 'Australia', 'Singapore')")
|
|
12
|
+
account_type: str | None = Field(default=None, description="Filter by account type: 'HCP' or 'HCO'")
|
|
13
|
+
specialty: str | None = Field(default=None, description="Filter by specialty (substring match, e.g. 'Oncology')")
|
|
14
|
+
status: str | None = Field(
|
|
15
|
+
default=None, description="Filter by enrichment status: 'pending', 'enriched', or 'failed'"
|
|
16
|
+
)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
TOOL: dict[str, Any] = {
|
|
20
|
+
"name": "list_accounts",
|
|
21
|
+
"description": (
|
|
22
|
+
"List HCP and HCO accounts from the sample CRM data. Returns a table of "
|
|
23
|
+
"accounts with id, name, specialty, country, account_type, institution, and "
|
|
24
|
+
"enrichment status. Supports optional filters by country, account_type, "
|
|
25
|
+
"specialty, and status. Use this to see which accounts need profiling."
|
|
26
|
+
),
|
|
27
|
+
"args": ListAccountsArgs,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def handle(arguments: dict[str, Any]) -> list[dict[str, Any]]:
|
|
32
|
+
import json
|
|
33
|
+
|
|
34
|
+
from .._crm_store import load_accounts
|
|
35
|
+
|
|
36
|
+
rows = load_accounts()
|
|
37
|
+
|
|
38
|
+
if arguments.get("country"):
|
|
39
|
+
val = arguments["country"].lower()
|
|
40
|
+
rows = [r for r in rows if r["country"].lower() == val]
|
|
41
|
+
if arguments.get("account_type"):
|
|
42
|
+
val = arguments["account_type"].upper()
|
|
43
|
+
rows = [r for r in rows if r["account_type"].upper() == val]
|
|
44
|
+
if arguments.get("specialty"):
|
|
45
|
+
val = arguments["specialty"].lower()
|
|
46
|
+
rows = [r for r in rows if val in r["specialty"].lower()]
|
|
47
|
+
if arguments.get("status"):
|
|
48
|
+
val = arguments["status"].lower()
|
|
49
|
+
rows = [r for r in rows if r["status"].lower() == val]
|
|
50
|
+
|
|
51
|
+
output = {
|
|
52
|
+
"total": len(rows),
|
|
53
|
+
"accounts": [
|
|
54
|
+
{
|
|
55
|
+
"id": r["id"],
|
|
56
|
+
"name": r["name"],
|
|
57
|
+
"specialty": r["specialty"],
|
|
58
|
+
"country": r["country"],
|
|
59
|
+
"account_type": r["account_type"],
|
|
60
|
+
"institution": r.get("institution", ""),
|
|
61
|
+
"status": r["status"],
|
|
62
|
+
}
|
|
63
|
+
for r in rows
|
|
64
|
+
],
|
|
65
|
+
}
|
|
66
|
+
return [{"type": "text", "text": json.dumps(output, indent=2)}]
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
"""search_clinical_trials — query ClinicalTrials.gov v2 API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from typing import Any
|
|
6
|
+
|
|
7
|
+
from pydantic import BaseModel, Field
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class SearchClinicalTrialsArgs(BaseModel):
|
|
11
|
+
investigator_name: str | None = Field(
|
|
12
|
+
default=None,
|
|
13
|
+
description="Name of the principal investigator or contact",
|
|
14
|
+
)
|
|
15
|
+
organization_name: str | None = Field(
|
|
16
|
+
default=None,
|
|
17
|
+
description="Sponsor or site organization name",
|
|
18
|
+
)
|
|
19
|
+
condition: str | None = Field(
|
|
20
|
+
default=None,
|
|
21
|
+
description="Disease or condition studied (e.g. 'Type 2 Diabetes')",
|
|
22
|
+
)
|
|
23
|
+
intervention: str | None = Field(
|
|
24
|
+
default=None,
|
|
25
|
+
description="Drug, device, or procedure (e.g. 'semaglutide')",
|
|
26
|
+
)
|
|
27
|
+
country: str | None = Field(
|
|
28
|
+
default=None,
|
|
29
|
+
description="Country of the trial site (e.g. 'Singapore')",
|
|
30
|
+
)
|
|
31
|
+
status: str | None = Field(
|
|
32
|
+
default=None,
|
|
33
|
+
description="Recruitment status filter: RECRUITING, COMPLETED, ACTIVE_NOT_RECRUITING, etc.",
|
|
34
|
+
)
|
|
35
|
+
max_results: int = Field(
|
|
36
|
+
default=20,
|
|
37
|
+
ge=1,
|
|
38
|
+
le=50,
|
|
39
|
+
description="Maximum trials to return",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
TOOL: dict[str, Any] = {
|
|
44
|
+
"name": "search_clinical_trials",
|
|
45
|
+
"description": (
|
|
46
|
+
"Search ClinicalTrials.gov for clinical trials by investigator, organization, "
|
|
47
|
+
"condition, or intervention. Returns structured records with NCT ID, title, "
|
|
48
|
+
"status, phase, conditions, interventions, sponsor, investigators, and dates. "
|
|
49
|
+
"Use to identify an HCP's trial involvement or an HCO's trial activity."
|
|
50
|
+
),
|
|
51
|
+
"args": SearchClinicalTrialsArgs,
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def handle(arguments: dict[str, Any]) -> list[dict[str, Any]]:
|
|
56
|
+
import json
|
|
57
|
+
import urllib.parse
|
|
58
|
+
import urllib.request
|
|
59
|
+
from datetime import datetime, timezone
|
|
60
|
+
|
|
61
|
+
base = "https://clinicaltrials.gov/api/v2/studies"
|
|
62
|
+
|
|
63
|
+
query_parts = []
|
|
64
|
+
if arguments.get("investigator_name"):
|
|
65
|
+
query_parts.append(f"SEARCH[Study]{arguments['investigator_name']}")
|
|
66
|
+
if arguments.get("organization_name"):
|
|
67
|
+
query_parts.append(f"SEARCH[Study]{arguments['organization_name']}")
|
|
68
|
+
if arguments.get("condition"):
|
|
69
|
+
query_parts.append(f"COND={arguments['condition']}")
|
|
70
|
+
if arguments.get("intervention"):
|
|
71
|
+
query_parts.append(f"INTR={arguments['intervention']}")
|
|
72
|
+
|
|
73
|
+
if not query_parts:
|
|
74
|
+
return [{"type": "text", "text": json.dumps({"error": "At least one search parameter is required"})}]
|
|
75
|
+
|
|
76
|
+
params: dict[str, str] = {
|
|
77
|
+
"format": "json",
|
|
78
|
+
"pageSize": str(arguments.get("max_results", 20)),
|
|
79
|
+
"query.term": " AND ".join(query_parts),
|
|
80
|
+
}
|
|
81
|
+
if arguments.get("country"):
|
|
82
|
+
params["query.locn"] = f"SEARCH[Location]{arguments['country']}"
|
|
83
|
+
if arguments.get("status"):
|
|
84
|
+
params["filter.overallStatus"] = arguments["status"]
|
|
85
|
+
|
|
86
|
+
fields = [
|
|
87
|
+
"NCTId",
|
|
88
|
+
"BriefTitle",
|
|
89
|
+
"OverallStatus",
|
|
90
|
+
"Phase",
|
|
91
|
+
"Condition",
|
|
92
|
+
"InterventionName",
|
|
93
|
+
"LeadSponsorName",
|
|
94
|
+
"CollaboratorName",
|
|
95
|
+
"StartDate",
|
|
96
|
+
"PrimaryCompletionDate",
|
|
97
|
+
"ResponsiblePartyInvestigatorFullName",
|
|
98
|
+
"OverallOfficialName",
|
|
99
|
+
"OverallOfficialRole",
|
|
100
|
+
]
|
|
101
|
+
params["fields"] = "|".join(fields)
|
|
102
|
+
|
|
103
|
+
url = f"{base}?{urllib.parse.urlencode(params, safe='|=')}"
|
|
104
|
+
req = urllib.request.Request(url, headers={"Accept": "application/json"})
|
|
105
|
+
with urllib.request.urlopen(req, timeout=30) as resp:
|
|
106
|
+
data = json.loads(resp.read())
|
|
107
|
+
|
|
108
|
+
total_count = data.get("totalCount", 0)
|
|
109
|
+
studies = data.get("studies", [])
|
|
110
|
+
|
|
111
|
+
trials = []
|
|
112
|
+
for study in studies:
|
|
113
|
+
proto = study.get("protocolSection", {})
|
|
114
|
+
ident = proto.get("identificationModule", {})
|
|
115
|
+
status_mod = proto.get("statusModule", {})
|
|
116
|
+
design = proto.get("designModule", {})
|
|
117
|
+
cond_mod = proto.get("conditionsModule", {})
|
|
118
|
+
interv_mod = proto.get("armsInterventionsModule", {})
|
|
119
|
+
sponsor_mod = proto.get("sponsorCollaboratorsModule", {})
|
|
120
|
+
contacts_mod = proto.get("contactsLocationsModule", {})
|
|
121
|
+
|
|
122
|
+
nct_id = ident.get("nctId", "")
|
|
123
|
+
title = ident.get("briefTitle", "")
|
|
124
|
+
overall_status = status_mod.get("overallStatus", "")
|
|
125
|
+
|
|
126
|
+
phases = design.get("phases", [])
|
|
127
|
+
phase = ", ".join(phases) if phases else None
|
|
128
|
+
|
|
129
|
+
conditions = cond_mod.get("conditions", [])
|
|
130
|
+
|
|
131
|
+
interventions = []
|
|
132
|
+
for arm in interv_mod.get("interventions", []):
|
|
133
|
+
name = arm.get("name", "")
|
|
134
|
+
itype = arm.get("type", "")
|
|
135
|
+
interventions.append(f"{itype}: {name}" if itype else name)
|
|
136
|
+
|
|
137
|
+
lead_sponsor = sponsor_mod.get("leadSponsor", {}).get("name")
|
|
138
|
+
collabs = [c.get("name", "") for c in sponsor_mod.get("collaborators", [])]
|
|
139
|
+
|
|
140
|
+
start_date = status_mod.get("startDateStruct", {}).get("date")
|
|
141
|
+
completion_date = status_mod.get("primaryCompletionDateStruct", {}).get("date")
|
|
142
|
+
|
|
143
|
+
inv_name = None
|
|
144
|
+
inv_role = None
|
|
145
|
+
officials = contacts_mod.get("overallOfficials", [])
|
|
146
|
+
if officials:
|
|
147
|
+
inv_name = officials[0].get("name")
|
|
148
|
+
inv_role = officials[0].get("role")
|
|
149
|
+
|
|
150
|
+
trials.append(
|
|
151
|
+
{
|
|
152
|
+
"nct_id": nct_id,
|
|
153
|
+
"title": title,
|
|
154
|
+
"status": overall_status,
|
|
155
|
+
"phase": phase,
|
|
156
|
+
"conditions": conditions,
|
|
157
|
+
"interventions": interventions,
|
|
158
|
+
"sponsor": lead_sponsor,
|
|
159
|
+
"collaborators": collabs,
|
|
160
|
+
"start_date": start_date,
|
|
161
|
+
"completion_date": completion_date,
|
|
162
|
+
"investigator_name": inv_name,
|
|
163
|
+
"investigator_role": inv_role,
|
|
164
|
+
"source_url": f"https://clinicaltrials.gov/study/{nct_id}",
|
|
165
|
+
}
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
output = {
|
|
169
|
+
"query": " AND ".join(query_parts),
|
|
170
|
+
"total_count": total_count,
|
|
171
|
+
"trials": trials,
|
|
172
|
+
"source": "clinicaltrials.gov",
|
|
173
|
+
"searched_at": datetime.now(timezone.utc).isoformat(),
|
|
174
|
+
}
|
|
175
|
+
return [{"type": "text", "text": json.dumps(output, indent=2)}]
|