1#![forbid(unsafe_code)]
57#![warn(missing_docs)]
58#![cfg_attr(
59 not(test),
60 deny(clippy::unwrap_used, clippy::expect_used, clippy::panic)
61)]
62
63pub mod builder;
64pub mod graph;
65pub mod queries;
66pub mod ranking;
67
68pub use builder::GraphBuilder;
69pub use graph::{CompanyNode, DiseaseNode, DrugNode, Edge, IntelligenceGraph};
70pub use ranking::{
71 ClassEffectResult, CompanyRanking, HeadToHead, PipelineOverlap, SafetyGap, SharedSignal,
72 TherapeuticLandscape,
73};
74
75pub fn build_top10_graph() -> IntelligenceGraph {
91 GraphBuilder::new()
92 .add_company(
94 "lilly",
95 "Eli Lilly",
96 &["tirzepatide", "donanemab", "dulaglutide"],
97 )
98 .add_company(
99 "novo-nordisk",
100 "Novo Nordisk",
101 &["semaglutide", "liraglutide"],
102 )
103 .add_company("pfizer", "Pfizer", &["paxlovid", "eliquis"])
104 .add_company("bms", "Bristol Myers Squibb", &["nivolumab", "apixaban"])
105 .add_company("merck", "Merck", &["pembrolizumab", "sitagliptin"])
106 .add_company("abbvie", "AbbVie", &["adalimumab", "upadacitinib"])
107 .add_company(
108 "astrazeneca",
109 "AstraZeneca",
110 &["dapagliflozin", "osimertinib"],
111 )
112 .add_company(
113 "roche",
114 "Roche",
115 &["pembrolizumab-biosimilar", "ocrelizumab"],
116 )
117 .add_company("jnj", "Johnson & Johnson", &["ibrutinib", "ustekinumab"])
118 .add_company("novartis", "Novartis", &["secukinumab", "ribociclib"])
119 .add_drug(
121 "tirzepatide",
122 "tirzepatide",
123 "GLP-1/GIP Dual Agonist",
124 Some("lilly"),
125 )
126 .add_drug(
127 "donanemab",
128 "donanemab",
129 "Anti-Amyloid Antibody",
130 Some("lilly"),
131 )
132 .add_drug(
133 "dulaglutide",
134 "dulaglutide",
135 "GLP-1 Receptor Agonist",
136 Some("lilly"),
137 )
138 .add_drug(
139 "semaglutide",
140 "semaglutide",
141 "GLP-1 Receptor Agonist",
142 Some("novo-nordisk"),
143 )
144 .add_drug(
145 "liraglutide",
146 "liraglutide",
147 "GLP-1 Receptor Agonist",
148 Some("novo-nordisk"),
149 )
150 .add_drug(
151 "paxlovid",
152 "nirmatrelvir/ritonavir",
153 "Protease Inhibitor",
154 Some("pfizer"),
155 )
156 .add_drug("eliquis", "apixaban", "Anticoagulant", Some("pfizer"))
157 .add_drug(
158 "nivolumab",
159 "nivolumab",
160 "PD-1 Checkpoint Inhibitor",
161 Some("bms"),
162 )
163 .add_drug("apixaban", "apixaban", "Anticoagulant", Some("bms"))
164 .add_drug(
165 "pembrolizumab",
166 "pembrolizumab",
167 "PD-1 Checkpoint Inhibitor",
168 Some("merck"),
169 )
170 .add_drug(
171 "sitagliptin",
172 "sitagliptin",
173 "DPP-4 Inhibitor",
174 Some("merck"),
175 )
176 .add_drug("adalimumab", "adalimumab", "Anti-TNF", Some("abbvie"))
177 .add_drug(
178 "upadacitinib",
179 "upadacitinib",
180 "JAK Inhibitor",
181 Some("abbvie"),
182 )
183 .add_drug(
184 "dapagliflozin",
185 "dapagliflozin",
186 "SGLT2 Inhibitor",
187 Some("astrazeneca"),
188 )
189 .add_drug(
190 "osimertinib",
191 "osimertinib",
192 "EGFR TKI",
193 Some("astrazeneca"),
194 )
195 .add_drug(
196 "pembrolizumab-biosimilar",
197 "pembrolizumab biosimilar",
198 "PD-1 Checkpoint Inhibitor",
199 Some("roche"),
200 )
201 .add_drug("ocrelizumab", "ocrelizumab", "Anti-CD20", Some("roche"))
202 .add_drug("ibrutinib", "ibrutinib", "BTK Inhibitor", Some("jnj"))
203 .add_drug("ustekinumab", "ustekinumab", "Anti-IL-12/23", Some("jnj"))
204 .add_drug(
205 "secukinumab",
206 "secukinumab",
207 "Anti-IL-17A",
208 Some("novartis"),
209 )
210 .add_drug(
211 "ribociclib",
212 "ribociclib",
213 "CDK4/6 Inhibitor",
214 Some("novartis"),
215 )
216 .add_disease("t2dm", "Type 2 Diabetes Mellitus", "Metabolic")
218 .add_disease("obesity", "Obesity", "Metabolic")
219 .add_disease("alzheimers", "Alzheimer's Disease", "Neuroscience")
220 .add_disease("nsclc", "Non-Small Cell Lung Cancer", "Oncology")
221 .add_disease("ra", "Rheumatoid Arthritis", "Immunology")
222 .add_disease("breast-cancer", "Breast Cancer (HR+/HER2-)", "Oncology")
223 .add_disease("ms", "Multiple Sclerosis", "Neuroscience")
224 .add_disease("cll", "Chronic Lymphocytic Leukaemia", "Hematology")
225 .add_disease("psoriasis", "Psoriasis", "Dermatology")
226 .add_disease("covid19", "COVID-19", "Infectious")
227 .add_disease("afib", "Atrial Fibrillation", "Cardiovascular")
228 .add_treats("tirzepatide", "t2dm")
230 .add_treats("tirzepatide", "obesity")
231 .add_treats("donanemab", "alzheimers")
232 .add_treats("dulaglutide", "t2dm")
233 .add_treats("semaglutide", "t2dm")
234 .add_treats("semaglutide", "obesity")
235 .add_treats("liraglutide", "t2dm")
236 .add_treats("liraglutide", "obesity")
237 .add_treats("paxlovid", "covid19")
238 .add_treats("eliquis", "afib")
239 .add_treats("nivolumab", "nsclc")
240 .add_treats("apixaban", "afib")
241 .add_treats("pembrolizumab", "nsclc")
242 .add_treats("pembrolizumab", "breast-cancer")
243 .add_treats("sitagliptin", "t2dm")
244 .add_treats("adalimumab", "ra")
245 .add_treats("adalimumab", "psoriasis")
246 .add_treats("upadacitinib", "ra")
247 .add_treats("dapagliflozin", "t2dm")
248 .add_treats("osimertinib", "nsclc")
249 .add_treats("pembrolizumab-biosimilar", "nsclc")
250 .add_treats("ocrelizumab", "ms")
251 .add_treats("ibrutinib", "cll")
252 .add_treats("ustekinumab", "psoriasis")
253 .add_treats("secukinumab", "psoriasis")
254 .add_treats("secukinumab", "ra")
255 .add_treats("ribociclib", "breast-cancer")
256 .add_competes("semaglutide", "tirzepatide", "t2dm")
258 .add_competes("semaglutide", "tirzepatide", "obesity")
259 .add_competes("pembrolizumab", "nivolumab", "nsclc")
260 .add_competes("adalimumab", "upadacitinib", "ra")
261 .add_competes("eliquis", "apixaban", "afib")
262 .build()
263}
264
265#[cfg(test)]
266mod tests {
267 use super::*;
268
269 #[test]
270 fn build_top10_graph_has_expected_counts() {
271 let graph = build_top10_graph();
272 assert!(graph.companies.len() >= 10, "expected >= 10 companies");
273 assert!(graph.drugs.len() >= 10, "expected >= 10 drugs");
274 assert!(graph.diseases.len() >= 10, "expected >= 10 diseases");
275 assert!(!graph.edges.is_empty(), "expected edges");
276 }
277
278 #[test]
279 fn build_top10_graph_owns_edges_present() {
280 let graph = build_top10_graph();
281 let owns_count = graph
282 .edges
283 .iter()
284 .filter(|e| matches!(e, Edge::Owns { .. }))
285 .count();
286 assert!(
287 owns_count >= 10,
288 "expected >= 10 Owns edges, got {owns_count}"
289 );
290 }
291
292 #[test]
293 fn build_top10_graph_treats_edges_present() {
294 let graph = build_top10_graph();
295 let treats_count = graph
296 .edges
297 .iter()
298 .filter(|e| matches!(e, Edge::Treats { .. }))
299 .count();
300 assert!(
301 treats_count >= 10,
302 "expected >= 10 Treats edges, got {treats_count}"
303 );
304 }
305
306 #[test]
307 fn build_top10_graph_lilly_owns_tirzepatide() {
308 let graph = build_top10_graph();
309 let has_edge = graph.edges.iter().any(|e| {
310 matches!(e, Edge::Owns { company, drug }
311 if company == "lilly" && drug == "tirzepatide")
312 });
313 assert!(has_edge, "Lilly should own tirzepatide");
314 }
315
316 #[test]
317 fn build_top10_graph_t2dm_rankings_non_empty() {
318 let graph = build_top10_graph();
319 let rankings = graph.safest_company_for_disease("t2dm");
320 assert!(
321 !rankings.is_empty(),
322 "safest_company_for_disease(t2dm) should return results"
323 );
324 }
325
326 #[test]
327 fn build_top10_graph_serializes() {
328 let graph = build_top10_graph();
329 let json = serde_json::to_string(&graph).expect("graph must serialize");
330 assert!(json.contains("tirzepatide"));
331 assert!(json.contains("t2dm"));
332 }
333}