Refactor - API lazy loading

This commit is contained in:
2026-04-20 14:12:11 -04:00
parent 6be571a48c
commit 36634dc2bf
2 changed files with 325 additions and 90 deletions
+168 -14
View File
@@ -1,6 +1,7 @@
from fastapi import FastAPI, Depends, HTTPException, APIRouter, status, Response
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy.orm import Session
from sqlalchemy import func
from typing import List, Optional, Dict, Any
from datetime import datetime, timedelta, date
import math
@@ -263,6 +264,34 @@ class DrugWithVariantsResponse(BaseModel):
class Config:
from_attributes = True
class VariantSummaryResponse(BaseModel):
"""Lightweight variant summary returned by GET /drugs list — no packs or batches."""
id: int
drug_id: int
strength: str
quantity: float
unit: str
low_stock_threshold: float
has_inventory_history: bool = False
expired_quantity: float = 0.0
class Config:
from_attributes = True
class DrugSummaryResponse(BaseModel):
"""Lightweight drug summary for the main list — variants without packs or batches."""
id: int
name: str
description: Optional[str] = None
is_controlled: bool = False
locations: List[str] = []
variants: List[VariantSummaryResponse] = []
class Config:
from_attributes = True
class DispensingAllocationCreate(BaseModel):
batch_id: int
quantity: float
@@ -399,6 +428,40 @@ def enrich_variant_with_batches(db: Session, variant: DrugVariant) -> Dict[str,
return variant_dict
def serialize_variant_with_packs(db: Session, variant: DrugVariant) -> Dict[str, Any]:
"""Return variant data with packs but without batch details (level-2 detail)."""
has_batch_history = (
db.query(Batch.id)
.filter(Batch.drug_variant_id == variant.id)
.first()
is not None
)
has_dispense_history = (
db.query(Dispensing.id)
.filter(Dispensing.drug_variant_id == variant.id)
.first()
is not None
)
packs = (
db.query(VariantPack)
.filter(VariantPack.drug_variant_id == variant.id)
.order_by(VariantPack.is_active.desc(), VariantPack.id.asc())
.all()
)
return {
"id": variant.id,
"drug_id": variant.drug_id,
"strength": variant.strength,
"quantity": variant.quantity,
"unit": variant.unit,
"base_unit": variant.unit,
"low_stock_threshold": variant.low_stock_threshold,
"has_inventory_history": has_batch_history or has_dispense_history,
"packs": [serialize_variant_pack(pack) for pack in packs],
"batches": [],
}
def serialize_batch_response(db: Session, batch: Batch) -> Dict[str, Any]:
location = db.query(Location).filter(Location.id == batch.location_id).first()
pack = None
@@ -866,21 +929,112 @@ def admin_change_password(user_id: int, password_data: AdminPasswordChange, db:
def read_root():
return {"message": "Drug Inventory API"}
@router.get("/drugs", response_model=List[DrugWithVariantsResponse])
@router.get("/drugs", response_model=List[DrugSummaryResponse])
def list_drugs(db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
"""Get all drugs with their variants"""
"""Get all drugs with lightweight variant summaries (no packs or batches)."""
drugs = db.query(Drug).all()
if not drugs:
return []
drug_ids = [d.id for d in drugs]
all_variants = (
db.query(DrugVariant)
.filter(DrugVariant.drug_id.in_(drug_ids))
.all()
)
variants_by_drug: Dict[int, list] = {}
for v in all_variants:
variants_by_drug.setdefault(v.drug_id, []).append(v)
variant_ids = [v.id for v in all_variants]
if not variant_ids:
return [
{
"id": d.id,
"name": d.name,
"description": d.description,
"is_controlled": bool(d.is_controlled),
"locations": [],
"variants": [],
}
for d in drugs
]
# Variant IDs that have ever had a batch received
batch_history_ids = set(
row[0]
for row in db.query(Batch.drug_variant_id)
.filter(Batch.drug_variant_id.in_(variant_ids))
.distinct()
.all()
)
# Variant IDs that have ever had a dispense
dispense_history_ids = set(
row[0]
for row in db.query(Dispensing.drug_variant_id)
.filter(Dispensing.drug_variant_id.in_(variant_ids))
.distinct()
.all()
)
# Sum of expired active batch stock per variant
today = date.today()
expired_rows = (
db.query(Batch.drug_variant_id, func.sum(Batch.quantity))
.filter(
Batch.drug_variant_id.in_(variant_ids),
Batch.quantity > 0,
Batch.expiry_date < today,
)
.group_by(Batch.drug_variant_id)
.all()
)
expired_by_variant: Dict[int, float] = {row[0]: float(row[1]) for row in expired_rows}
# Distinct location names per drug (from active batch stock)
location_rows = (
db.query(DrugVariant.drug_id, Location.name)
.join(Batch, Batch.drug_variant_id == DrugVariant.id)
.join(Location, Location.id == Batch.location_id)
.filter(
DrugVariant.drug_id.in_(drug_ids),
Batch.quantity > 0,
)
.distinct()
.all()
)
locations_by_drug: Dict[int, list] = {}
for drug_id, loc_name in location_rows:
locations_by_drug.setdefault(drug_id, []).append(loc_name)
result = []
for drug in drugs:
variants = db.query(DrugVariant).filter(DrugVariant.drug_id == drug.id).all()
drug_dict = {
"id": drug.id,
"name": drug.name,
"description": drug.description,
"is_controlled": bool(drug.is_controlled),
"variants": [enrich_variant_with_batches(db, v) for v in variants],
}
result.append(drug_dict)
drug_variants = variants_by_drug.get(drug.id, [])
variant_summaries = [
{
"id": v.id,
"drug_id": v.drug_id,
"strength": v.strength,
"quantity": v.quantity,
"unit": v.unit,
"low_stock_threshold": v.low_stock_threshold,
"has_inventory_history": v.id in batch_history_ids or v.id in dispense_history_ids,
"expired_quantity": expired_by_variant.get(v.id, 0.0),
}
for v in drug_variants
]
result.append(
{
"id": drug.id,
"name": drug.name,
"description": drug.description,
"is_controlled": bool(drug.is_controlled),
"locations": locations_by_drug.get(drug.id, []),
"variants": variant_summaries,
}
)
return result
@router.get("/drugs/low-stock", response_model=List[DrugWithVariantsResponse])
@@ -910,18 +1064,18 @@ def low_stock_drugs(db: Session = Depends(get_db), current_user: User = Depends(
@router.get("/drugs/{drug_id}", response_model=DrugWithVariantsResponse)
def get_drug(drug_id: int, db: Session = Depends(get_db), current_user: User = Depends(get_current_user)):
"""Get a specific drug with its variants"""
"""Get a specific drug with its variants and packs (no batch detail — level-2 detail)."""
drug = db.query(Drug).filter(Drug.id == drug_id).first()
if not drug:
raise HTTPException(status_code=404, detail="Drug not found")
variants = db.query(DrugVariant).filter(DrugVariant.drug_id == drug.id).all()
drug_dict = {
"id": drug.id,
"name": drug.name,
"description": drug.description,
"is_controlled": bool(drug.is_controlled),
"variants": [enrich_variant_with_batches(db, v) for v in variants],
"variants": [serialize_variant_with_packs(db, v) for v in variants],
}
return drug_dict