Source code for pystatsbio.pk._common

"""Shared result types for pharmacokinetic analysis."""

from __future__ import annotations

from dataclasses import dataclass

import numpy as np
from numpy.typing import NDArray


[docs] @dataclass(frozen=True) class NCAResult: """Result of non-compartmental pharmacokinetic analysis.""" # Primary PK parameters auc_last: float # AUC from 0 to last measurable concentration auc_inf: float | None # AUC extrapolated to infinity auc_pct_extrap: float | None # % AUC extrapolated cmax: float # peak concentration tmax: float # time of peak concentration half_life: float | None # terminal elimination half-life lambda_z: float | None # terminal elimination rate constant lambda_z_r_squared: float | None # r-squared of terminal slope regression # Derived parameters (require dose) clearance: float | None # CL = Dose / AUC_inf (or CL/F for oral) vz: float | None # Vz = Dose / (lambda_z * AUC_inf) # Metadata dose: float | None route: str # 'iv' or 'ev' (extravascular) auc_method: str # 'linear', 'log-linear', 'linear-up/log-down' n_points: int n_terminal: int # number of points used for terminal slope
[docs] def summary(self) -> str: """Human-readable PK summary.""" lines = ["Non-Compartmental Analysis", ""] lines.append(f" Route: {self.route.upper()}") lines.append(f" AUC method: {self.auc_method}") if self.dose is not None: lines.append(f" Dose: {self.dose}") lines.append("") lines.append(f" Cmax = {self.cmax:.4g}") lines.append(f" Tmax = {self.tmax:.4g}") lines.append(f" AUC(0-last) = {self.auc_last:.4g}") if self.auc_inf is not None: lines.append(f" AUC(0-inf) = {self.auc_inf:.4g}") lines.append(f" %AUC extrap = {self.auc_pct_extrap:.1f}%") if self.half_life is not None: lines.append(f" t1/2 = {self.half_life:.4g}") lines.append(f" lambda_z = {self.lambda_z:.4g}") lines.append(f" r-squared = {self.lambda_z_r_squared:.4f}") if self.clearance is not None: label = "CL" if self.route == "iv" else "CL/F" lines.append(f" {label:<14s} = {self.clearance:.4g}") if self.vz is not None: label = "Vz" if self.route == "iv" else "Vz/F" lines.append(f" {label:<14s} = {self.vz:.4g}") return "\n".join(lines)