Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions audit/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
This module provides a thin adapter around `drug_discovery.compliance.audit_trail`
so teams can import `audit` as a separate module.
"""

from .audit_adapter import ComplianceAuditAdapter

__all__ = ["ComplianceAuditAdapter"]
16 changes: 10 additions & 6 deletions audit/audit_adapter.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"""Audit adapter exposing the project's audit trail from a top-level package."""

from __future__ import annotations

from typing import Any, Dict
from typing import Any

from drug_discovery.compliance.audit_trail import (
ComplianceAuditLogger,
AuditTrail,
AuditEventType,
ComplianceAuditEntry,
ComplianceAuditLogger,
)


Expand All @@ -20,11 +20,15 @@ def __init__(self, trail: AuditTrail | None = None):
def log_screen(self, smiles: str, compound_id: str | None = None, user_id: str = "system") -> ComplianceAuditEntry:
return self.logger.log_compound_screened(smiles=smiles, compound_id=compound_id, user_id=user_id)

def log_prediction(self, compound_id: str, smiles: str, predictions: Dict[str, float], user_id: str = "system") -> ComplianceAuditEntry:
return self.logger.log_toxicity_prediction(compound_id=compound_id, smiles=smiles, predictions=predictions, user_id=user_id)
def log_prediction(
self, compound_id: str, smiles: str, predictions: dict[str, float], user_id: str = "system"
) -> ComplianceAuditEntry:
return self.logger.log_toxicity_prediction(
compound_id=compound_id, smiles=smiles, predictions=predictions, user_id=user_id
)

def verify(self) -> bool:
return self.logger.verify_integrity()

def export(self) -> Dict[str, Any]:
def export(self) -> dict[str, Any]:
return self.logger.export_report()
89 changes: 47 additions & 42 deletions backend/api/client_gateway.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
from fastapi import FastAPI, UploadFile, File, Form
from pydantic import BaseModel, EmailStr
from typing import List, Optional
import os
import uuid
import shutil
import asyncio
import random
import shutil
import uuid

from fastapi import FastAPI, File, Form, UploadFile
from pydantic import BaseModel

from drug_discovery.commercial.fda_drug_matcher import CommercialDrugMapper
from zane_apex_entrypoint import execute_zane_pipeline

Expand All @@ -15,24 +15,28 @@
mapper = CommercialDrugMapper()
mapper.load_fda_orange_book("data/fda_orange_book.csv")


class Compound(BaseModel):
smiles: str
dosage: str
timing: str
purpose: str
toxicity_level: str


class CommercialMatch(BaseModel):
closest_drug: str
similarity: float
commercial_dose: str
extra_compounds: List[str]
missing_compounds: List[str]
extra_compounds: list[str]
missing_compounds: list[str]


class TherapeuticBlueprint(BaseModel):
compounds: List[Compound]
compounds: list[Compound]
commercial_match: CommercialMatch


@app.post("/api/v1/generate_blueprint", response_model=TherapeuticBlueprint)
async def generate_blueprint(
name: str = Form(...),
Expand All @@ -44,7 +48,7 @@ async def generate_blueprint(
current_treatments: str = Form(""),
lifestyle: str = Form(...),
hereditary_problems: str = Form(""),
health_report: UploadFile = File(...)
health_report: UploadFile = File(...),
):
"""
Triggers the ZANE Zero-Mortality engine and returns a detailed therapeutic blueprint.
Expand All @@ -54,7 +58,7 @@ async def generate_blueprint(
temp_id = str(uuid.uuid4())
upload_dir = f"temp_uploads/{temp_id}"
os.makedirs(upload_dir, exist_ok=True)

report_path = os.path.join(upload_dir, health_report.filename)
with open(report_path, "wb") as buffer:
shutil.copyfileobj(health_report.file, buffer)
Expand All @@ -68,50 +72,51 @@ async def generate_blueprint(
"location": location,
"treatments": current_treatments,
"lifestyle": lifestyle,
"hereditary": hereditary_problems
"hereditary": hereditary_problems,
}
await execute_zane_pipeline(report_path, target_purpose, metadata=metadata)

# 3. Generate Multi-Compound Blueprint (10-20 compounds)
num_compounds = random.randint(10, 20)
mock_compounds = []

# Primary compound
primary_smiles = "CC1=C(C=C(C=C1)NC(=O)C2=CC=C(C=C2)CN3CCN(CC3)C)NC4=NC=CC(=N4)C5=CN=CC=C5"
mock_compounds.append(Compound(
smiles=primary_smiles,
dosage="14.5mg",
timing="08:30 AM",
purpose=f"Primary inhibitor for {target_purpose}",
toxicity_level="Ultra-Low (0.02 LD50)"
))

mock_compounds.append(
Compound(
smiles=primary_smiles,
dosage="14.5mg",
timing="08:30 AM",
purpose=f"Primary inhibitor for {target_purpose}",
toxicity_level="Ultra-Low (0.02 LD50)",
)
)

# Adjuvant compounds
for i in range(num_compounds - 1):
mock_compounds.append(Compound(
smiles=f"SMILES_ADJ_{i}_{uuid.uuid4().hex[:6]}",
dosage=f"{random.uniform(1, 10):.1f}mg",
timing=f"{random.randint(8, 22):02d}:00",
purpose="Metabolic synergy / Adjuvant",
toxicity_level="Non-toxic"
))

mock_compounds.append(
Compound(
smiles=f"SMILES_ADJ_{i}_{uuid.uuid4().hex[:6]}",
dosage=f"{random.uniform(1, 10):.1f}mg",
timing=f"{random.randint(8, 22):02d}:00",
purpose="Metabolic synergy / Adjuvant",
toxicity_level="Non-toxic",
)
)

# 4. Find Commercial Match for the primary compound
comm_match_data = mapper.find_closest_commercial_match(primary_smiles)

# 5. Compare with multi-compound ZANE drug
comp_analysis = mapper.compare_compounds([c.dict() for c in mock_compounds], comm_match_data)

comm_match = CommercialMatch(
closest_drug=comm_match_data['closest_drug'],
similarity=comm_match_data['similarity'],
commercial_dose=comm_match_data['commercial_dose'],
extra_compounds=comp_analysis['extra_compounds'],
missing_compounds=comp_analysis['missing_compounds']
closest_drug=comm_match_data["closest_drug"],
similarity=comm_match_data["similarity"],
commercial_dose=comm_match_data["commercial_dose"],
extra_compounds=comp_analysis["extra_compounds"],
missing_compounds=comp_analysis["missing_compounds"],
)

# 6. Final Blueprint
return TherapeuticBlueprint(
compounds=mock_compounds,
commercial_match=comm_match
)
return TherapeuticBlueprint(compounds=mock_compounds, commercial_match=comm_match)
56 changes: 30 additions & 26 deletions clinical/chronobiology/circadian_dosing.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
import logging

import numpy as np
import pandas as pd
from scipy.optimize import least_squares
from typing import Dict, Any, Optional
import logging

logger = logging.getLogger(__name__)


class CircadianDosingOptimizer:
"""
Optimizes drug dosing schedules based on patient-specific circadian rhythms
Optimizes drug dosing schedules based on patient-specific circadian rhythms
derived from wearable telemetry.
"""

def __init__(self):
self.telemetry_data: Optional[pd.DataFrame] = None
self.circadian_params: Dict[str, float] = {}
self.telemetry_data: pd.DataFrame | None = None
self.circadian_params: dict[str, float] = {}

def ingest_wearable_telemetry(self, timeseries_csv: str) -> None:
"""
Expand All @@ -22,62 +24,64 @@ def ingest_wearable_telemetry(self, timeseries_csv: str) -> None:
"""
try:
df = pd.read_csv(timeseries_csv)
df['timestamp'] = pd.to_datetime(df['timestamp'])
df['hour'] = df['timestamp'].dt.hour + df['timestamp'].dt.minute / 60.0
df["timestamp"] = pd.to_datetime(df["timestamp"])
df["hour"] = df["timestamp"].dt.hour + df["timestamp"].dt.minute / 60.0
self.telemetry_data = df

# Fit a cosinor model to body temperature to find the acrophase (peak)
# T(t) = M + A * cos(2*pi*t/24 - phi)
# Dim Light Melatonin Onset (DLMO) is typically ~7 hours before the temperature nadir
self._fit_circadian_model(df)

except Exception as e:
logger.error(f"Error ingesting telemetry: {str(e)}")
logger.error(f"Error ingesting telemetry: {e!s}")
# Default fallback for healthy adult
self.circadian_params = {"acrophase": 16.0, "mesor": 37.0, "amplitude": 0.5}

def _fit_circadian_model(self, df: pd.DataFrame):
"""Fits a cosinor model to the telemetry data."""
t = df['hour'].values
y = df['body_temperature'].values if 'body_temperature' in df else df['heart_rate'].values
t = df["hour"].values
y = df["body_temperature"].values if "body_temperature" in df else df["heart_rate"].values

def model(params, t):
mesor, amp, phi = params
return mesor + amp * np.cos(2 * np.pi * t / 24 - phi)

def residuals(params, t, y):
return model(params, t) - y

# Initial guess
x0 = [np.mean(y), np.std(y), 0]
res = least_squares(residuals, x0, args=(t, y))

self.circadian_params = {
"mesor": res.x[0],
"amplitude": res.x[1],
"acrophase": res.x[2] % (2 * np.pi) * 24 / (2 * np.pi)
"acrophase": res.x[2] % (2 * np.pi) * 24 / (2 * np.pi),
}
logger.info(f"Circadian phase detected: Acrophase at {self.circadian_params['acrophase']:.2f}h")

def calculate_optimal_tmax(self, target_receptor_peak_hour: float = 8.0) -> float:
"""
Calculates the optimal hour to administer the drug.
peak_plasma_concentration (Cmax) should align with the peak circadian expression
peak_plasma_concentration (Cmax) should align with the peak circadian expression
of the target disease receptor.
"""
# Adjust target peak based on patient's specific phase shift
# Standard healthy acrophase is approx 16:00 (4 PM)
phase_shift = self.circadian_params.get("acrophase", 16.0) - 16.0

# Shift the target receptor peak by the patient's individual clock shift
individualized_target_hour = (target_receptor_peak_hour + phase_shift) % 24

# If the drug takes 'absorption_delay' hours to reach Tmax
absorption_delay = 2.0 # Assume 2 hours for standard oral delivery
absorption_delay = 2.0 # Assume 2 hours for standard oral delivery

optimal_dosing_time = (individualized_target_hour - absorption_delay) % 24

logger.info(f"Optimal dosing time calculated: {optimal_dosing_time:.2f}h "
f"to reach peak at {individualized_target_hour:.2f}h")


logger.info(
f"Optimal dosing time calculated: {optimal_dosing_time:.2f}h "
f"to reach peak at {individualized_target_hour:.2f}h"
)

return optimal_dosing_time
Loading
Loading