Skip to content
Open
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
18 changes: 18 additions & 0 deletions patient-care-app/backend/app/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,24 @@ def init_db():
)
""")

cursor.execute("""
CREATE TABLE IF NOT EXISTS treatment_plans (
id INTEGER PRIMARY KEY AUTOINCREMENT,
patient_id INTEGER NOT NULL,
provider_id INTEGER NOT NULL,
name TEXT NOT NULL,
description TEXT,
goals TEXT,
notes TEXT,
status TEXT DEFAULT 'active' CHECK(status IN ('active', 'completed', 'discontinued')),
is_active BOOLEAN DEFAULT 1,
created_at TEXT NOT NULL,
updated_at TEXT NOT NULL,
FOREIGN KEY (patient_id) REFERENCES patients(id),
FOREIGN KEY (provider_id) REFERENCES providers(id)
)
""")

cursor.execute("""
CREATE TABLE IF NOT EXISTS audit_logs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
Expand Down
3 changes: 2 additions & 1 deletion patient-care-app/backend/app/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from app.config import settings
from app.database import init_db
from app.middleware.error_handler import global_exception_handler
from app.routers import auth, providers, patients, visits, treatments, audit
from app.routers import auth, providers, patients, visits, treatments, treatment_plans, audit

# Validate configuration on startup
settings.validate()
Expand All @@ -25,6 +25,7 @@
app.include_router(patients.router)
app.include_router(visits.router)
app.include_router(treatments.router)
app.include_router(treatment_plans.router)
app.include_router(audit.router)


Expand Down
138 changes: 138 additions & 0 deletions patient-care-app/backend/app/routers/treatment_plans.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
from datetime import datetime, timezone
from fastapi import APIRouter, Depends, HTTPException, Request, status
from app.database import get_db_connection
from app.schemas.treatment_plan import TreatmentPlanCreate, TreatmentPlanUpdate, TreatmentPlanResponse
from app.auth.rbac import require_permission

router = APIRouter(prefix="/treatment-plans", tags=["treatment-plans"])


@router.post("/", response_model=TreatmentPlanResponse, status_code=status.HTTP_201_CREATED)
async def create_treatment_plan(
request: Request,
plan_data: TreatmentPlanCreate,
current_user: dict = Depends(require_permission("treatments:create")),
):
now = datetime.now(timezone.utc).isoformat()
conn = get_db_connection()
try:
patient = conn.execute("SELECT id FROM patients WHERE id = ? AND is_active = 1", (plan_data.patient_id,)).fetchone()
if not patient:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Patient not found")

cursor = conn.execute(
"""INSERT INTO treatment_plans (patient_id, provider_id, name, description, goals, notes, status, is_active, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?)""",
(
plan_data.patient_id, current_user["id"], plan_data.name,
plan_data.description, plan_data.goals, plan_data.notes,
plan_data.status or "active", now, now,
),
)
conn.commit()
plan_id = cursor.lastrowid

row = conn.execute("SELECT * FROM treatment_plans WHERE id = ?", (plan_id,)).fetchone()
return _row_to_response(row)
finally:
conn.close()


@router.get("/", response_model=list[TreatmentPlanResponse])
async def list_treatment_plans(
request: Request,
patient_id: int = None,
status_filter: str = None,
current_user: dict = Depends(require_permission("treatments:read")),
):
conn = get_db_connection()
try:
query = "SELECT * FROM treatment_plans WHERE is_active = 1"
params = []
if patient_id:
query += " AND patient_id = ?"
params.append(patient_id)
if status_filter:
query += " AND status = ?"
params.append(status_filter)
query += " ORDER BY id DESC"

rows = conn.execute(query, params).fetchall()
return [_row_to_response(row) for row in rows]
finally:
conn.close()


@router.get("/{plan_id}", response_model=TreatmentPlanResponse)
async def get_treatment_plan(
plan_id: int,
request: Request,
current_user: dict = Depends(require_permission("treatments:read")),
):
conn = get_db_connection()
try:
row = conn.execute("SELECT * FROM treatment_plans WHERE id = ? AND is_active = 1", (plan_id,)).fetchone()
if not row:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Treatment plan not found")

return _row_to_response(row)
finally:
conn.close()


@router.put("/{plan_id}", response_model=TreatmentPlanResponse)
async def update_treatment_plan(
plan_id: int,
request: Request,
plan_data: TreatmentPlanUpdate,
current_user: dict = Depends(require_permission("treatments:update")),
):
conn = get_db_connection()
try:
row = conn.execute("SELECT * FROM treatment_plans WHERE id = ? AND is_active = 1", (plan_id,)).fetchone()
if not row:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Treatment plan not found")

update_data = plan_data.model_dump(exclude_unset=True)
if not update_data:
return _row_to_response(row)

update_data["updated_at"] = datetime.now(timezone.utc).isoformat()

set_clause = ", ".join(f"{k} = ?" for k in update_data)
values = list(update_data.values()) + [plan_id]
conn.execute(f"UPDATE treatment_plans SET {set_clause} WHERE id = ?", values)
conn.commit()

row = conn.execute("SELECT * FROM treatment_plans WHERE id = ?", (plan_id,)).fetchone()
return _row_to_response(row)
finally:
conn.close()


@router.delete("/{plan_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_treatment_plan(
plan_id: int,
request: Request,
current_user: dict = Depends(require_permission("treatments:delete")),
):
conn = get_db_connection()
try:
row = conn.execute("SELECT * FROM treatment_plans WHERE id = ? AND is_active = 1", (plan_id,)).fetchone()
if not row:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Treatment plan not found")

now = datetime.now(timezone.utc).isoformat()
conn.execute("UPDATE treatment_plans SET is_active = 0, updated_at = ? WHERE id = ?", (now, plan_id))
conn.commit()
finally:
conn.close()


def _row_to_response(row) -> TreatmentPlanResponse:
return TreatmentPlanResponse(
id=row["id"], patient_id=row["patient_id"], provider_id=row["provider_id"],
name=row["name"], description=row["description"], goals=row["goals"],
notes=row["notes"], status=row["status"], is_active=row["is_active"],
created_at=row["created_at"], updated_at=row["updated_at"],
)
33 changes: 33 additions & 0 deletions patient-care-app/backend/app/schemas/treatment_plan.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from pydantic import BaseModel, Field
from typing import Optional


class TreatmentPlanCreate(BaseModel):
patient_id: int = Field(..., gt=0)
name: str = Field(..., min_length=1, max_length=200)
description: Optional[str] = Field(None, max_length=2000)
goals: Optional[str] = Field(None, max_length=2000)
notes: Optional[str] = Field(None, max_length=2000)
status: Optional[str] = Field("active", pattern=r"^(active|completed|discontinued)$")


class TreatmentPlanUpdate(BaseModel):
name: Optional[str] = Field(None, min_length=1, max_length=200)
description: Optional[str] = Field(None, max_length=2000)
goals: Optional[str] = Field(None, max_length=2000)
notes: Optional[str] = Field(None, max_length=2000)
status: Optional[str] = Field(None, pattern=r"^(active|completed|discontinued)$")


class TreatmentPlanResponse(BaseModel):
id: int
patient_id: int
provider_id: int
name: str
description: Optional[str] = None
goals: Optional[str] = None
notes: Optional[str] = None
status: str
is_active: bool
created_at: str
updated_at: str
17 changes: 16 additions & 1 deletion patient-care-app/backend/seed_data.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def seed():
now = datetime.now(timezone.utc).isoformat()

# Clear existing data
for table in ["treatments", "visits", "patients", "audit_logs", "providers"]:
for table in ["treatment_plans", "treatments", "visits", "patients", "audit_logs", "providers"]:
conn.execute(f"DELETE FROM {table}")

# --- Providers ---
Expand Down Expand Up @@ -126,6 +126,21 @@ def seed():
tstatus, now, now),
)

# --- Treatment Plans (PHI stored as plaintext — no encrypt_phi) ---
treatment_plans_data = [
(patient_ids[0], provider_ids[1], "Cardiac Care Plan", "Comprehensive management of stable angina with medication therapy and lifestyle modifications", "Reduce chest pain frequency to less than once per week; improve exercise tolerance to 30 minutes daily", "Patient is motivated and compliant. Consider cardiac rehab referral if symptoms persist after 3 months.", "active"),
(patient_ids[2], provider_ids[1], "COPD Management Plan", "Long-term management of COPD with bronchodilator therapy and pulmonary rehabilitation", "Maintain FEV1 above 70% predicted; reduce exacerbation frequency to less than 2 per year", "Patient has history of smoking (quit 2019). Monitor for depression secondary to chronic illness.", "active"),
(patient_ids[4], provider_ids[1], "Diabetes Type 2 Management", "Integrated diabetes management including glycemic control, cardiovascular risk reduction, and renal protection", "Maintain A1C below 7.0%; blood pressure below 130/80; annual eye and foot exams", "Patient managing well on current regimen. Wife assists with meal planning. Review insulin initiation if A1C rises above 7.5%.", "active"),
(patient_ids[3], provider_ids[1], "Migraine Prevention Protocol", "Preventive and abortive migraine management with lifestyle trigger identification", "Reduce migraine frequency from 3/month to less than 1/month within 8 weeks", "Patient keeps a headache diary. Oral contraceptive use may be contributing factor — coordinate with OB/GYN.", "active"),
]

for pid, prov_id, name, description, goals, notes, plan_status in treatment_plans_data:
conn.execute(
"""INSERT INTO treatment_plans (patient_id, provider_id, name, description, goals, notes, status, is_active, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, 1, ?, ?)""",
(pid, prov_id, name, description, goals, notes, plan_status, now, now),
)

conn.commit()
conn.close()

Expand Down