celltype-cli 0.1.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.
Files changed (89) hide show
  1. celltype_cli-0.1.0.dist-info/METADATA +267 -0
  2. celltype_cli-0.1.0.dist-info/RECORD +89 -0
  3. celltype_cli-0.1.0.dist-info/WHEEL +4 -0
  4. celltype_cli-0.1.0.dist-info/entry_points.txt +2 -0
  5. celltype_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
  6. ct/__init__.py +3 -0
  7. ct/agent/__init__.py +0 -0
  8. ct/agent/case_studies.py +426 -0
  9. ct/agent/config.py +523 -0
  10. ct/agent/doctor.py +544 -0
  11. ct/agent/knowledge.py +523 -0
  12. ct/agent/loop.py +99 -0
  13. ct/agent/mcp_server.py +478 -0
  14. ct/agent/orchestrator.py +733 -0
  15. ct/agent/runner.py +656 -0
  16. ct/agent/sandbox.py +481 -0
  17. ct/agent/session.py +145 -0
  18. ct/agent/system_prompt.py +186 -0
  19. ct/agent/trace_store.py +228 -0
  20. ct/agent/trajectory.py +169 -0
  21. ct/agent/types.py +182 -0
  22. ct/agent/workflows.py +462 -0
  23. ct/api/__init__.py +1 -0
  24. ct/api/app.py +211 -0
  25. ct/api/config.py +120 -0
  26. ct/api/engine.py +124 -0
  27. ct/cli.py +1448 -0
  28. ct/data/__init__.py +0 -0
  29. ct/data/compute_providers.json +59 -0
  30. ct/data/cro_database.json +395 -0
  31. ct/data/downloader.py +238 -0
  32. ct/data/loaders.py +252 -0
  33. ct/kb/__init__.py +5 -0
  34. ct/kb/benchmarks.py +147 -0
  35. ct/kb/governance.py +106 -0
  36. ct/kb/ingest.py +415 -0
  37. ct/kb/reasoning.py +129 -0
  38. ct/kb/schema_monitor.py +162 -0
  39. ct/kb/substrate.py +387 -0
  40. ct/models/__init__.py +0 -0
  41. ct/models/llm.py +370 -0
  42. ct/tools/__init__.py +195 -0
  43. ct/tools/_compound_resolver.py +297 -0
  44. ct/tools/biomarker.py +368 -0
  45. ct/tools/cellxgene.py +282 -0
  46. ct/tools/chemistry.py +1371 -0
  47. ct/tools/claude.py +390 -0
  48. ct/tools/clinical.py +1153 -0
  49. ct/tools/clue.py +249 -0
  50. ct/tools/code.py +1069 -0
  51. ct/tools/combination.py +397 -0
  52. ct/tools/compute.py +402 -0
  53. ct/tools/cro.py +413 -0
  54. ct/tools/data_api.py +2114 -0
  55. ct/tools/design.py +295 -0
  56. ct/tools/dna.py +575 -0
  57. ct/tools/experiment.py +604 -0
  58. ct/tools/expression.py +655 -0
  59. ct/tools/files.py +957 -0
  60. ct/tools/genomics.py +1387 -0
  61. ct/tools/http_client.py +146 -0
  62. ct/tools/imaging.py +319 -0
  63. ct/tools/intel.py +223 -0
  64. ct/tools/literature.py +743 -0
  65. ct/tools/network.py +422 -0
  66. ct/tools/notification.py +111 -0
  67. ct/tools/omics.py +3330 -0
  68. ct/tools/ops.py +1230 -0
  69. ct/tools/parity.py +649 -0
  70. ct/tools/pk.py +245 -0
  71. ct/tools/protein.py +678 -0
  72. ct/tools/regulatory.py +643 -0
  73. ct/tools/remote_data.py +179 -0
  74. ct/tools/report.py +181 -0
  75. ct/tools/repurposing.py +376 -0
  76. ct/tools/safety.py +1280 -0
  77. ct/tools/shell.py +178 -0
  78. ct/tools/singlecell.py +533 -0
  79. ct/tools/statistics.py +552 -0
  80. ct/tools/structure.py +882 -0
  81. ct/tools/target.py +901 -0
  82. ct/tools/translational.py +123 -0
  83. ct/tools/viability.py +218 -0
  84. ct/ui/__init__.py +0 -0
  85. ct/ui/markdown.py +31 -0
  86. ct/ui/status.py +258 -0
  87. ct/ui/suggestions.py +567 -0
  88. ct/ui/terminal.py +1456 -0
  89. ct/ui/traces.py +112 -0
ct/tools/experiment.py ADDED
@@ -0,0 +1,604 @@
1
+ """
2
+ Experimental assay design tools: protocol templates, timeline estimation, and assay listing.
3
+
4
+ Provides structured experimental protocols for TPD/molecular glue validation workflows,
5
+ covering degradation, binding, viability, expression, structural, and screening assays.
6
+ """
7
+
8
+ import math
9
+ from ct.tools import registry
10
+
11
+
12
+ # ---------------------------------------------------------------------------
13
+ # Assay protocol templates (TPD / molecular-glue relevant)
14
+ # ---------------------------------------------------------------------------
15
+
16
+ ASSAY_TEMPLATES: dict[str, dict] = {
17
+ "hibit": {
18
+ "name": "HiBiT Degradation Assay",
19
+ "description": "Promega HiBiT lytic/kinetic assay measuring target protein degradation via split-NanoLuc luminescence",
20
+ "category": "degradation",
21
+ "protocol_steps": [
22
+ "Plate HiBiT-tagged cells in 384-well white plates (2000 cells/well)",
23
+ "Allow cells to adhere overnight (16-20 h)",
24
+ "Prepare compound serial dilutions in DMSO (typically 10-point, 3-fold)",
25
+ "Add compounds to plates using acoustic dispenser or pin tool",
26
+ "Incubate at 37C / 5% CO2 for desired timepoints (2h, 6h, 24h)",
27
+ "Add Nano-Glo HiBiT Lytic Reagent (1:1 v/v)",
28
+ "Incubate 10 min at room temperature on orbital shaker",
29
+ "Read luminescence on plate reader (integration time 0.5s)",
30
+ "Normalize to DMSO controls and calculate DC50/Dmax",
31
+ ],
32
+ "reagents": [
33
+ "HiBiT-tagged cell line (endogenous CRISPR knock-in preferred)",
34
+ "Nano-Glo HiBiT Lytic Detection System (Promega N3030)",
35
+ "384-well white plates (Corning 3570)",
36
+ "DMSO (cell-culture grade)",
37
+ "Complete growth medium",
38
+ ],
39
+ "controls": {
40
+ "positive": "Known degrader of target (or proteasome inhibitor MG132 as rescue)",
41
+ "negative": "DMSO vehicle control",
42
+ },
43
+ "readout": "Luminescence (RLU) proportional to target protein level",
44
+ "hands_on_hours": 3.0,
45
+ "calendar_days": 3,
46
+ "cost_per_plate": 800,
47
+ },
48
+ "nanobret": {
49
+ "name": "NanoBRET Ternary Complex Assay",
50
+ "description": "Measures proximity between E3 ligase and target protein via BRET energy transfer in live cells",
51
+ "category": "binding",
52
+ "protocol_steps": [
53
+ "Co-transfect HEK293 cells with NanoLuc-E3 and HaloTag-target constructs",
54
+ "Plate transfected cells into 384-well plates (8000 cells/well)",
55
+ "Allow 24 h for expression",
56
+ "Add HaloTag NanoBRET 618 Ligand (200 nM final)",
57
+ "Add test compounds in dose-response",
58
+ "Incubate 2-4 h at 37C",
59
+ "Add NanoBRET Nano-Glo Substrate",
60
+ "Read donor (460 nm) and acceptor (618 nm) emissions",
61
+ "Calculate milliBRET ratio: (618/460) x 1000",
62
+ ],
63
+ "reagents": [
64
+ "NanoLuc-E3 ligase fusion construct",
65
+ "HaloTag-target fusion construct",
66
+ "HaloTag NanoBRET 618 Ligand (Promega G9801)",
67
+ "NanoBRET Nano-Glo Substrate (Promega N1571)",
68
+ "Transfection reagent (FuGENE HD or Lipofectamine 3000)",
69
+ ],
70
+ "controls": {
71
+ "positive": "Known molecular glue or PROTAC forming ternary complex",
72
+ "negative": "DMSO + no-acceptor control (donor-only)",
73
+ },
74
+ "readout": "milliBRET ratio (higher = stronger ternary complex)",
75
+ "hands_on_hours": 5.0,
76
+ "calendar_days": 4,
77
+ "cost_per_plate": 1200,
78
+ },
79
+ "western_blot": {
80
+ "name": "Western Blot Degradation Confirmation",
81
+ "description": "Orthogonal confirmation of target protein degradation by immunoblotting",
82
+ "category": "degradation",
83
+ "protocol_steps": [
84
+ "Seed cells in 6-well plates (500K cells/well)",
85
+ "Treat with compound at multiple doses and timepoints",
86
+ "Include MG132 co-treatment to confirm proteasome dependence",
87
+ "Lyse cells in RIPA buffer with protease/phosphatase inhibitors",
88
+ "Quantify protein by BCA assay and normalize loading (20-30 ug)",
89
+ "Run SDS-PAGE (4-12% Bis-Tris gel)",
90
+ "Transfer to PVDF membrane (wet transfer, 100V 1h or semi-dry)",
91
+ "Block in 5% BSA/TBST 1h at RT",
92
+ "Probe with primary antibody overnight at 4C",
93
+ "Wash, secondary HRP antibody 1h at RT",
94
+ "Develop with ECL substrate and image",
95
+ "Quantify band intensity and normalize to loading control (vinculin/GAPDH)",
96
+ ],
97
+ "reagents": [
98
+ "Primary antibody against target protein",
99
+ "Anti-vinculin or anti-GAPDH loading control antibody",
100
+ "HRP-conjugated secondary antibodies",
101
+ "RIPA lysis buffer",
102
+ "Protease inhibitor cocktail",
103
+ "4-12% Bis-Tris SDS-PAGE gels (NuPAGE)",
104
+ "PVDF membrane",
105
+ "ECL substrate (SuperSignal West Pico/Femto)",
106
+ ],
107
+ "controls": {
108
+ "positive": "MG132 rescue (5 uM, 2h pre-treatment) to confirm UPS dependence",
109
+ "negative": "DMSO vehicle and untreated cells",
110
+ },
111
+ "readout": "Band intensity (densitometry) normalized to loading control",
112
+ "hands_on_hours": 8.0,
113
+ "calendar_days": 3,
114
+ "cost_per_plate": 200,
115
+ },
116
+ "qpcr": {
117
+ "name": "qPCR Transcript-Level Analysis",
118
+ "description": "RT-qPCR to confirm degradation is post-transcriptional (mRNA unchanged) or detect transcriptional effects",
119
+ "category": "expression",
120
+ "protocol_steps": [
121
+ "Treat cells with compound (same conditions as degradation assay)",
122
+ "Extract total RNA using RNeasy or TRIzol",
123
+ "Quantify RNA (NanoDrop) and check quality (A260/280 > 1.8)",
124
+ "Reverse-transcribe 1 ug RNA with oligo-dT primers",
125
+ "Design qPCR primers spanning exon-exon junctions",
126
+ "Set up qPCR reactions in 384-well plates (10 uL volume)",
127
+ "Run qPCR: 95C 10min, 40x(95C 15s, 60C 1min), melt curve",
128
+ "Analyze by delta-delta-Ct method normalized to housekeeping genes",
129
+ ],
130
+ "reagents": [
131
+ "RNA extraction kit (Qiagen RNeasy or TRIzol)",
132
+ "Reverse transcription kit (SuperScript IV or High-Capacity cDNA)",
133
+ "SYBR Green or TaqMan master mix",
134
+ "Target-specific primers (2 sets for redundancy)",
135
+ "Housekeeping gene primers (GAPDH, ACTB, HPRT1)",
136
+ "384-well qPCR plates",
137
+ ],
138
+ "controls": {
139
+ "positive": "Known transcriptional modulator (e.g., actinomycin D for mRNA stability)",
140
+ "negative": "DMSO vehicle; no-RT control for genomic DNA contamination",
141
+ },
142
+ "readout": "Relative mRNA expression (fold change vs DMSO)",
143
+ "hands_on_hours": 6.0,
144
+ "calendar_days": 2,
145
+ "cost_per_plate": 300,
146
+ },
147
+ "ctg_viability": {
148
+ "name": "CellTiter-Glo Cell Viability",
149
+ "description": "ATP-based luminescent cell viability assay for dose-response curves and IC50 determination",
150
+ "category": "viability",
151
+ "protocol_steps": [
152
+ "Plate cells in 384-well white plates (500-2000 cells/well depending on growth rate)",
153
+ "Allow overnight adherence",
154
+ "Add compounds in dose-response (10-point, 3-fold dilution, 10 uM top dose)",
155
+ "Incubate 72 h at 37C / 5% CO2",
156
+ "Equilibrate plates to room temperature (30 min)",
157
+ "Add CellTiter-Glo reagent (1:1 v/v)",
158
+ "Shake 2 min, incubate 10 min at RT",
159
+ "Read luminescence",
160
+ "Fit 4-parameter logistic curve to calculate IC50",
161
+ ],
162
+ "reagents": [
163
+ "CellTiter-Glo 2.0 (Promega G9241)",
164
+ "384-well white plates",
165
+ "Complete growth medium",
166
+ "DMSO (cell-culture grade)",
167
+ ],
168
+ "controls": {
169
+ "positive": "Staurosporine (1 uM) or bortezomib as cytotoxic control",
170
+ "negative": "DMSO vehicle control",
171
+ },
172
+ "readout": "Luminescence (RLU) proportional to ATP/viable cells; IC50 from curve fit",
173
+ "hands_on_hours": 2.0,
174
+ "calendar_days": 4,
175
+ "cost_per_plate": 500,
176
+ },
177
+ "flow_cytometry": {
178
+ "name": "Flow Cytometry",
179
+ "description": "Multi-parameter flow cytometry for surface marker expression, cell death (Annexin V/PI), and cell cycle analysis",
180
+ "category": "viability",
181
+ "protocol_steps": [
182
+ "Treat cells with compound at chosen doses/timepoints",
183
+ "Harvest cells (trypsinize adherent or collect suspension)",
184
+ "Wash 2x with cold PBS",
185
+ "For surface markers: stain with fluorochrome-conjugated antibodies (30 min, 4C, dark)",
186
+ "For apoptosis: stain with Annexin V-FITC and PI per kit protocol",
187
+ "For cell cycle: fix in 70% ethanol, stain with PI/RNase A",
188
+ "Acquire on flow cytometer (minimum 10,000 events/sample)",
189
+ "Analyze with FlowJo or similar software",
190
+ "Gate on singlets, then live cells, then markers of interest",
191
+ ],
192
+ "reagents": [
193
+ "Fluorochrome-conjugated antibodies for markers of interest",
194
+ "Annexin V-FITC/PI Apoptosis Kit",
195
+ "Propidium iodide + RNase A (for cell cycle)",
196
+ "FACS buffer (PBS + 2% FBS + 0.1% NaN3)",
197
+ "70% ethanol (cell-cycle fixation)",
198
+ "CompBeads for compensation",
199
+ ],
200
+ "controls": {
201
+ "positive": "Staurosporine (apoptosis) or nocodazole (G2/M arrest)",
202
+ "negative": "DMSO vehicle; unstained and single-stain compensation controls",
203
+ },
204
+ "readout": "Percentage of cells in each population (live, apoptotic, necrotic; cell cycle phase)",
205
+ "hands_on_hours": 6.0,
206
+ "calendar_days": 2,
207
+ "cost_per_plate": 400,
208
+ },
209
+ "tr_fret": {
210
+ "name": "TR-FRET Binding Assay",
211
+ "description": "Time-resolved FRET assay for measuring binary binding (compound-protein) or ternary complex formation",
212
+ "category": "binding",
213
+ "protocol_steps": [
214
+ "Prepare assay buffer (50 mM HEPES pH 7.5, 150 mM NaCl, 0.01% Tween-20, 0.1% BSA)",
215
+ "Dispense DMSO/compound into 384-well low-volume plates",
216
+ "Add Eu-labeled donor protein (e.g., Eu-anti-His for His-tagged E3)",
217
+ "Add AF647-labeled acceptor protein (e.g., AF647-target)",
218
+ "For ternary complex: add both proteins + compound simultaneously",
219
+ "Incubate 1-2 h at room temperature",
220
+ "Read TR-FRET: excite 320 nm, read 665/620 nm ratio",
221
+ "Calculate FRET ratio and fit dose-response",
222
+ ],
223
+ "reagents": [
224
+ "Europium-labeled donor (anti-tag antibody or direct protein label)",
225
+ "AlexaFluor647-labeled acceptor protein",
226
+ "Purified recombinant proteins (E3 ligase, target)",
227
+ "384-well low-volume black plates (Corning 4514)",
228
+ "Assay buffer components",
229
+ ],
230
+ "controls": {
231
+ "positive": "Known binder at saturating concentration",
232
+ "negative": "DMSO vehicle; protein-only (no compound) for baseline FRET",
233
+ },
234
+ "readout": "FRET ratio (665 nm / 620 nm); EC50 from dose-response",
235
+ "hands_on_hours": 4.0,
236
+ "calendar_days": 1,
237
+ "cost_per_plate": 600,
238
+ },
239
+ "alphalisa": {
240
+ "name": "AlphaLISA Protein-Protein Interaction",
241
+ "description": "Bead-based proximity assay for detecting protein-protein interactions and ternary complex formation",
242
+ "category": "binding",
243
+ "protocol_steps": [
244
+ "Prepare assay buffer (25 mM HEPES pH 7.4, 100 mM NaCl, 0.1% BSA, 0.01% Tween-20)",
245
+ "Dispense compounds into 384-well AlphaPlates",
246
+ "Add biotinylated protein 1 and His-tagged protein 2",
247
+ "Incubate 1 h at room temperature",
248
+ "Add Anti-His AlphaLISA Acceptor beads (10 ug/mL final)",
249
+ "Incubate 1 h at room temperature",
250
+ "Add Streptavidin Donor beads (40 ug/mL final) in subdued light",
251
+ "Incubate 1 h at room temperature in dark",
252
+ "Read on Alpha-compatible reader (EnVision or CLARIOstar)",
253
+ ],
254
+ "reagents": [
255
+ "Biotinylated protein 1",
256
+ "His-tagged protein 2",
257
+ "Anti-His AlphaLISA Acceptor beads (PerkinElmer AL128)",
258
+ "Streptavidin Alpha Donor beads (PerkinElmer 6760002)",
259
+ "384-well AlphaPlates (PerkinElmer 6005350)",
260
+ "Assay buffer components",
261
+ ],
262
+ "controls": {
263
+ "positive": "Known PPI stabilizer or molecular glue at saturating concentration",
264
+ "negative": "DMSO vehicle; beads-only (no protein) for background",
265
+ },
266
+ "readout": "Alpha signal (counts); EC50 from dose-response curve",
267
+ "hands_on_hours": 4.0,
268
+ "calendar_days": 1,
269
+ "cost_per_plate": 900,
270
+ },
271
+ "dsf": {
272
+ "name": "Differential Scanning Fluorimetry (Thermal Shift)",
273
+ "description": "Measures compound-induced thermal stabilization of target protein as evidence of direct binding",
274
+ "category": "structural",
275
+ "protocol_steps": [
276
+ "Prepare protein at 2-5 uM in assay buffer (PBS or HEPES-based, low detergent)",
277
+ "Add SYPRO Orange dye (5x final concentration)",
278
+ "Dispense 18 uL protein/dye mix into 384-well PCR plates",
279
+ "Add 2 uL compound (10x stock) or DMSO control",
280
+ "Seal plates with optical adhesive film",
281
+ "Run thermal ramp on qPCR instrument: 25C to 95C at 1C/min",
282
+ "Monitor SYPRO Orange fluorescence (Ex 470, Em 570)",
283
+ "Determine Tm by fitting Boltzmann sigmoid to melt curves",
284
+ "Calculate delta-Tm = Tm(compound) - Tm(DMSO)",
285
+ ],
286
+ "reagents": [
287
+ "Purified recombinant target protein (>90% purity)",
288
+ "SYPRO Orange Protein Gel Stain (Invitrogen S6650)",
289
+ "384-well PCR plates (optically clear)",
290
+ "Optical adhesive film",
291
+ "Assay buffer (PBS or 50 mM HEPES, 150 mM NaCl, pH 7.5)",
292
+ ],
293
+ "controls": {
294
+ "positive": "Known ligand that shifts Tm by >2C",
295
+ "negative": "DMSO vehicle (matched % DMSO); protein-only for intrinsic Tm",
296
+ },
297
+ "readout": "Melting temperature (Tm) shift in degrees C; delta-Tm > 2C suggests binding",
298
+ "hands_on_hours": 3.0,
299
+ "calendar_days": 1,
300
+ "cost_per_plate": 150,
301
+ },
302
+ "spr": {
303
+ "name": "Surface Plasmon Resonance (SPR)",
304
+ "description": "Label-free real-time binding kinetics: measures ka, kd, and KD for compound-protein interactions",
305
+ "category": "structural",
306
+ "protocol_steps": [
307
+ "Immobilize target protein on CM5 sensor chip via amine coupling",
308
+ "Inject EDC/NHS to activate surface, then protein (10-50 ug/mL in acetate pH 4.0-5.5)",
309
+ "Block remaining sites with ethanolamine",
310
+ "Prepare compound dilution series in running buffer (+ matched DMSO%)",
311
+ "Inject compound at multiple concentrations (single-cycle or multi-cycle kinetics)",
312
+ "Monitor association (120-180 s) and dissociation (300-600 s)",
313
+ "Regenerate surface between cycles if needed (10 mM glycine pH 2.0)",
314
+ "Fit sensorgrams to 1:1 Langmuir model to extract ka, kd, KD",
315
+ "Perform solvent correction for DMSO bulk effects",
316
+ ],
317
+ "reagents": [
318
+ "CM5 sensor chip (Cytiva BR100530)",
319
+ "Amine Coupling Kit (Cytiva BR100050)",
320
+ "Purified target protein (>95% purity, activity confirmed)",
321
+ "Running buffer (HBS-EP+: 10 mM HEPES, 150 mM NaCl, 3 mM EDTA, 0.05% P20)",
322
+ "Regeneration buffer (10 mM glycine-HCl pH 2.0)",
323
+ ],
324
+ "controls": {
325
+ "positive": "Known binder with published KD for assay validation",
326
+ "negative": "Reference channel (no protein) for bulk refractive index subtraction",
327
+ },
328
+ "readout": "Binding kinetics: ka (1/Ms), kd (1/s), KD (M); steady-state affinity if kinetics too fast",
329
+ "hands_on_hours": 8.0,
330
+ "calendar_days": 2,
331
+ "cost_per_plate": 2000,
332
+ },
333
+ "tmt_proteomics": {
334
+ "name": "TMT Multiplexed Proteomics",
335
+ "description": "Global proteomics by TMT labeling and LC-MS/MS to profile degradation selectivity across the proteome",
336
+ "category": "screening",
337
+ "protocol_steps": [
338
+ "Treat cells (1-5 million per condition) with compound vs DMSO (3+ replicates)",
339
+ "Lyse in 8M urea / 50 mM TEAB buffer",
340
+ "Reduce (TCEP 10 mM, 30 min 37C) and alkylate (IAA 20 mM, 30 min RT dark)",
341
+ "Digest with trypsin (1:50 enzyme:protein, overnight 37C)",
342
+ "Desalt on C18 Sep-Pak cartridges",
343
+ "Label peptides with TMT reagents (TMT-16plex or TMT-18plex)",
344
+ "Combine labeled samples, desalt, and fractionate by high-pH reversed-phase (8-12 fractions)",
345
+ "Analyze each fraction by nanoLC-MS/MS (2h gradient, Orbitrap or timsTOF)",
346
+ "Search with MaxQuant or Proteome Discoverer",
347
+ "Filter: 1% FDR at peptide and protein level",
348
+ "Statistical analysis: limma or MSstats for differential abundance",
349
+ ],
350
+ "reagents": [
351
+ "TMT-16plex or TMT-18plex reagent kit (Thermo A44520 / A52045)",
352
+ "Trypsin (sequencing grade, Promega V5111)",
353
+ "TCEP and iodoacetamide",
354
+ "C18 Sep-Pak cartridges (Waters WAT054955)",
355
+ "High-pH reversed-phase fractionation kit",
356
+ "nanoLC-MS/MS instrument access (Orbitrap Exploris/Eclipse or timsTOF)",
357
+ ],
358
+ "controls": {
359
+ "positive": "Include known degrader condition as positive control channel",
360
+ "negative": "DMSO vehicle (3+ replicates for statistical power)",
361
+ },
362
+ "readout": "Log2 fold-change per protein (compound vs DMSO); volcano plot of selectivity",
363
+ "hands_on_hours": 20.0,
364
+ "calendar_days": 14,
365
+ "cost_per_plate": 5000,
366
+ },
367
+ "crispr_screen": {
368
+ "name": "Genome-Wide CRISPR Screen",
369
+ "description": "Pooled CRISPR knockout screen to identify genetic dependencies and resistance/sensitization mechanisms",
370
+ "category": "screening",
371
+ "protocol_steps": [
372
+ "Expand cells to sufficient scale (500-1000x library coverage, e.g., 100M cells for 100K library)",
373
+ "Transduce with lentiviral sgRNA library at MOI 0.3",
374
+ "Select with puromycin (2-4 ug/mL) for 48-72 h",
375
+ "Confirm >30% transduction by flow cytometry (if GFP reporter)",
376
+ "Split into treatment arms: compound vs DMSO (maintain 500x coverage)",
377
+ "Treat for 14-21 days (passage every 3 days, re-dose compound)",
378
+ "Harvest cells, extract genomic DNA",
379
+ "PCR-amplify sgRNA cassettes with indexed primers",
380
+ "Sequence on NextSeq/NovaSeq (aim for 500 reads per sgRNA)",
381
+ "Analyze with MAGeCK or CRISPR-CASA to identify depleted/enriched genes",
382
+ ],
383
+ "reagents": [
384
+ "Lentiviral sgRNA library (Brunello, TKOv3, or custom focused library)",
385
+ "Puromycin selection antibiotic",
386
+ "Genomic DNA extraction kit (Qiagen Blood & Cell Culture DNA Midi)",
387
+ "PCR primers for sgRNA amplification (library-specific)",
388
+ "NextSeq/NovaSeq sequencing access",
389
+ "MAGeCK software for analysis",
390
+ ],
391
+ "controls": {
392
+ "positive": "Essential gene sgRNAs should deplete (e.g., core essential gene list from Hart et al.)",
393
+ "negative": "Non-targeting sgRNAs and DMSO-treated arm for baseline",
394
+ },
395
+ "readout": "Gene-level enrichment/depletion scores; beta scores from MAGeCK-MLE",
396
+ "hands_on_hours": 40.0,
397
+ "calendar_days": 35,
398
+ "cost_per_plate": 15000,
399
+ },
400
+ }
401
+
402
+
403
+ # ---------------------------------------------------------------------------
404
+ # Tools
405
+ # ---------------------------------------------------------------------------
406
+
407
+
408
+ @registry.register(
409
+ name="experiment.list_assays",
410
+ description="List all available experimental assay templates with name, description, and category",
411
+ category="experiment",
412
+ parameters={},
413
+ usage_guide="You want to see all available experimental assay types before designing a specific protocol.",
414
+ )
415
+ def list_assays(**kwargs) -> dict:
416
+ """Return a catalogue of all available assay templates."""
417
+ assays = []
418
+ for key, tmpl in ASSAY_TEMPLATES.items():
419
+ assays.append({
420
+ "assay_type": key,
421
+ "name": tmpl["name"],
422
+ "description": tmpl["description"],
423
+ "category": tmpl["category"],
424
+ })
425
+
426
+ by_category: dict[str, list[str]] = {}
427
+ for a in assays:
428
+ by_category.setdefault(a["category"], []).append(a["assay_type"])
429
+
430
+ cat_summary = "; ".join(f"{cat}: {', '.join(types)}" for cat, types in sorted(by_category.items()))
431
+
432
+ return {
433
+ "summary": f"{len(assays)} assay templates available across {len(by_category)} categories. {cat_summary}",
434
+ "assays": assays,
435
+ "categories": by_category,
436
+ }
437
+
438
+
439
+ @registry.register(
440
+ name="experiment.design_assay",
441
+ description="Design a detailed experimental protocol for a specific assay type, customized with target/compound/cell line information",
442
+ category="experiment",
443
+ parameters={
444
+ "assay_type": "Assay template key (e.g. 'hibit', 'nanobret', 'tmt_proteomics')",
445
+ "target": "Target protein or gene name",
446
+ "compound": "Compound name or identifier",
447
+ "cell_line": "Cell line to use",
448
+ "goal": "Specific experimental goal or question",
449
+ },
450
+ usage_guide="You need to design an experimental protocol for validating a computational finding. Use before experiment.estimate_timeline and cro.match_experiment.",
451
+ )
452
+ def design_assay(
453
+ assay_type: str,
454
+ target: str = None,
455
+ compound: str = None,
456
+ cell_line: str = None,
457
+ goal: str = None,
458
+ **kwargs,
459
+ ) -> dict:
460
+ """Design a customized experimental protocol from a template."""
461
+ if assay_type not in ASSAY_TEMPLATES:
462
+ available = ", ".join(sorted(ASSAY_TEMPLATES.keys()))
463
+ return {"error": f"Unknown assay type '{assay_type}'. Available: {available}", "summary": f"Unknown assay type '{assay_type}'. Available: {available}"}
464
+ tmpl = ASSAY_TEMPLATES[assay_type]
465
+
466
+ # Customize protocol steps with target/compound/cell_line info
467
+ customized_steps = []
468
+ for step in tmpl["protocol_steps"]:
469
+ s = step
470
+ if target:
471
+ s = s.replace("target protein", f"{target} protein")
472
+ s = s.replace("target gene", f"{target} gene")
473
+ if compound:
474
+ s = s.replace("compound", compound).replace("test compounds", compound)
475
+ if cell_line:
476
+ s = s.replace("cells", f"{cell_line} cells").replace("Plate cells", f"Plate {cell_line} cells")
477
+ customized_steps.append(s)
478
+
479
+ # Build customized controls
480
+ controls = dict(tmpl["controls"])
481
+ if target:
482
+ controls["positive"] = controls["positive"].replace("target", target)
483
+ if compound:
484
+ controls["negative"] = controls["negative"].replace("compound", compound)
485
+
486
+ # Build context string
487
+ context_parts = []
488
+ if target:
489
+ context_parts.append(f"target={target}")
490
+ if compound:
491
+ context_parts.append(f"compound={compound}")
492
+ if cell_line:
493
+ context_parts.append(f"cell_line={cell_line}")
494
+ if goal:
495
+ context_parts.append(f"goal={goal}")
496
+ context_str = ", ".join(context_parts) if context_parts else "generic protocol"
497
+
498
+ # Assemble protocol
499
+ protocol = {
500
+ "assay_type": assay_type,
501
+ "name": tmpl["name"],
502
+ "description": tmpl["description"],
503
+ "category": tmpl["category"],
504
+ "context": context_str,
505
+ "protocol_steps": customized_steps,
506
+ "reagents": list(tmpl["reagents"]),
507
+ "controls": controls,
508
+ "readout": tmpl["readout"],
509
+ "estimated_hands_on_hours": tmpl["hands_on_hours"],
510
+ "estimated_calendar_days": tmpl["calendar_days"],
511
+ "estimated_cost_per_plate": tmpl["cost_per_plate"],
512
+ }
513
+
514
+ if goal:
515
+ protocol["experimental_goal"] = goal
516
+
517
+ summary_parts = [f"Designed {tmpl['name']} protocol"]
518
+ if target:
519
+ summary_parts.append(f"for {target}")
520
+ if compound:
521
+ summary_parts.append(f"with {compound}")
522
+ if cell_line:
523
+ summary_parts.append(f"in {cell_line}")
524
+ summary_parts.append(
525
+ f"({len(customized_steps)} steps, ~{tmpl['hands_on_hours']}h hands-on, "
526
+ f"{tmpl['calendar_days']} calendar days, ~${tmpl['cost_per_plate']}/plate)"
527
+ )
528
+
529
+ return {
530
+ "summary": ". ".join(summary_parts),
531
+ "protocol": protocol,
532
+ }
533
+
534
+
535
+ @registry.register(
536
+ name="experiment.estimate_timeline",
537
+ description="Estimate hands-on time, calendar time, and cost for an experiment scaled by number of compounds, replicates, and doses",
538
+ category="experiment",
539
+ parameters={
540
+ "assay_type": "Assay template key (e.g. 'hibit', 'ctg_viability')",
541
+ "n_compounds": "Number of compounds to test",
542
+ "n_replicates": "Number of biological replicates",
543
+ "n_doses": "Number of dose points per compound",
544
+ },
545
+ usage_guide="You need to estimate how long an experiment will take and how much it will cost. Use after experiment.design_assay.",
546
+ )
547
+ def estimate_timeline(
548
+ assay_type: str,
549
+ n_compounds: int = 1,
550
+ n_replicates: int = 3,
551
+ n_doses: int = 8,
552
+ **kwargs,
553
+ ) -> dict:
554
+ """Estimate timeline and cost scaled by experimental parameters."""
555
+ if assay_type not in ASSAY_TEMPLATES:
556
+ available = ", ".join(sorted(ASSAY_TEMPLATES.keys()))
557
+ return {"error": f"Unknown assay type '{assay_type}'. Available: {available}", "summary": f"Unknown assay type '{assay_type}'. Available: {available}"}
558
+ tmpl = ASSAY_TEMPLATES[assay_type]
559
+
560
+ # Calculate number of wells needed
561
+ # Each compound x dose x replicate = 1 well, plus controls (~10% overhead)
562
+ wells_per_compound = n_doses * n_replicates
563
+ total_wells = n_compounds * wells_per_compound
564
+ control_wells = max(16, int(total_wells * 0.1)) # at least 16 control wells
565
+ total_wells_with_controls = total_wells + control_wells
566
+
567
+ # Number of 384-well plates needed
568
+ wells_per_plate = 384
569
+ n_plates = math.ceil(total_wells_with_controls / wells_per_plate)
570
+
571
+ # Scale hands-on time: base time per plate, with efficiency gains for batching
572
+ base_hours = tmpl["hands_on_hours"]
573
+ # First plate takes full time, each additional plate adds ~60% of base
574
+ hands_on_hours = base_hours + max(0, n_plates - 1) * base_hours * 0.6
575
+ hands_on_days = round(hands_on_hours / 8.0, 1)
576
+
577
+ # Calendar days: base + extra days for additional plates (batching helps)
578
+ base_cal_days = tmpl["calendar_days"]
579
+ extra_plate_days = max(0, math.ceil((n_plates - 1) / 4)) # ~4 plates per batch
580
+ calendar_days = base_cal_days + extra_plate_days
581
+
582
+ # Cost
583
+ total_cost = n_plates * tmpl["cost_per_plate"]
584
+
585
+ return {
586
+ "summary": (
587
+ f"{tmpl['name']}: {n_compounds} compounds x {n_doses} doses x {n_replicates} replicates = "
588
+ f"{total_wells_with_controls} wells ({n_plates} plates). "
589
+ f"Estimated {hands_on_hours:.1f}h hands-on ({hands_on_days} days), "
590
+ f"{calendar_days} calendar days, ~${total_cost:,}"
591
+ ),
592
+ "assay_type": assay_type,
593
+ "assay_name": tmpl["name"],
594
+ "n_compounds": n_compounds,
595
+ "n_doses": n_doses,
596
+ "n_replicates": n_replicates,
597
+ "total_wells": total_wells_with_controls,
598
+ "n_plates": n_plates,
599
+ "hands_on_hours": round(hands_on_hours, 1),
600
+ "hands_on_days": hands_on_days,
601
+ "calendar_days": calendar_days,
602
+ "estimated_cost": total_cost,
603
+ "cost_per_plate": tmpl["cost_per_plate"],
604
+ }