omgy commited on
Commit
754821f
·
verified ·
1 Parent(s): 730b421

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +168 -0
app.py ADDED
@@ -0,0 +1,168 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import uvicorn
3
+ from fastapi import FastAPI, HTTPException, Path, Query
4
+ from pydantic import BaseModel
5
+ from typing import Optional, Any
6
+ from utils.supabase_client import supabase, get_pending_tasks, lock_task, unlock_task, submit_task_decision, fetch_context_for_task
7
+ import datetime
8
+
9
+ app = FastAPI(title="CareFlow Nexus Backend API")
10
+
11
+ # --- Pydantic models
12
+ class PatientIn(BaseModel):
13
+ name: str
14
+ age: Optional[int] = None
15
+ issue: Optional[str] = None
16
+ priority: Optional[str] = "normal"
17
+
18
+ class TaskDecision(BaseModel):
19
+ action: str
20
+ bed_id: Optional[int] = None
21
+ nurse_id: Optional[int] = None
22
+ cleaner_id: Optional[int] = None
23
+ reason: Optional[str] = None
24
+ details: Optional[Any] = None
25
+
26
+ # --- Frontend endpoints
27
+ @app.post("/api/patients")
28
+ def create_patient(inp: PatientIn):
29
+ data = {
30
+ "name": inp.name,
31
+ "age": inp.age,
32
+ "issue": inp.issue,
33
+ "status": "pending_bed",
34
+ "created_at": datetime.datetime.utcnow().isoformat()
35
+ }
36
+ res = supabase.table("patients").insert([data]).execute()
37
+ if res.status_code != 201 and res.status_code != 200:
38
+ raise HTTPException(status_code=500, detail="Failed to insert patient")
39
+ patient = res.data[0]
40
+ return {"patient": patient}
41
+
42
+ @app.post("/api/patients/{patient_id}/admit")
43
+ def admit_patient(patient_id: int = Path(...)):
44
+ # create initial bed_assignment task assigned to MASTER agent per spec
45
+ task = {
46
+ "type": "bed_assignment",
47
+ "target_role": "AGENT",
48
+ "agent_role": "MASTER",
49
+ "assigned_to": None,
50
+ "patient_id": patient_id,
51
+ "bed_id": None,
52
+ "status": "pending",
53
+ "created_at": datetime.datetime.utcnow().isoformat()
54
+ }
55
+ res = supabase.table("tasks").insert([task]).execute()
56
+ if res.error:
57
+ raise HTTPException(status_code=500, detail="Failed to create bed_assignment task")
58
+ return {"ok": True, "task_created": res.data[0]}
59
+
60
+ @app.get("/api/beds")
61
+ def get_beds():
62
+ res = supabase.table("beds").select("*").execute()
63
+ return {"beds": res.data}
64
+
65
+ @app.get("/api/logs")
66
+ def get_logs(limit: int = Query(100)):
67
+ res = supabase.table("logs").select("*").order("timestamp", {"ascending": False}).limit(limit).execute()
68
+ return {"logs": res.data}
69
+
70
+ # --- Agent-facing endpoints
71
+ @app.get("/api/tasks/pending")
72
+ def tasks_pending(role: str = Query(...), include: Optional[str] = Query(None)):
73
+ """
74
+ Agents call this to fetch pending tasks for their role.
75
+ Backend locks task immediately to prevent double-processing.
76
+ """
77
+ tasks = get_pending_tasks(role)
78
+ # convert and lock first N tasks (be conservative - return one by default)
79
+ if not tasks:
80
+ return []
81
+ out = []
82
+ for t in tasks:
83
+ task_id = t.get("id")
84
+ # lock
85
+ lock_task(task_id)
86
+ # attach context if requested
87
+ if include and "context" in include:
88
+ context = fetch_context_for_task(t)
89
+ t["context"] = context
90
+ out.append(t)
91
+ return out
92
+
93
+ @app.post("/api/agent/tasks/{task_id}/decision")
94
+ def agent_decision(task_id: int, decision: TaskDecision):
95
+ """
96
+ Validate the agent decision, apply DB updates, and generate follow-up tasks.
97
+ This follows your workflow rules from the spec. Agents return simple JSON actions.
98
+ """
99
+ # fetch task
100
+ t = supabase.table("tasks").select("*").eq("id", task_id).single().execute()
101
+ if t.error or not t.data:
102
+ raise HTTPException(status_code=404, detail="Task not found")
103
+ task = t.data
104
+ # basic validation
105
+ if task["status"] not in ("locked", "pending"):
106
+ raise HTTPException(status_code=409, detail="Task not in processable state")
107
+
108
+ # handle actions
109
+ action = decision.action
110
+ if action == "assign_bed":
111
+ if not decision.bed_id:
112
+ return {"error": "bed_id required"}
113
+ # update bed and admission/patient state
114
+ supabase.table("beds").update({"status": "pending_cleaning", "patient_id": task.get("patient_id")}).eq("id", decision.bed_id).execute()
115
+ # update patient status
116
+ supabase.table("patients").update({"status": "awaiting_cleaning"}).eq("id", task.get("patient_id")).execute()
117
+ # create cleaning task for CLEANER
118
+ new_task = {
119
+ "type": "cleaning",
120
+ "target_role": "AGENT",
121
+ "agent_role": "CLEANER",
122
+ "patient_id": task.get("patient_id"),
123
+ "bed_id": decision.bed_id,
124
+ "status": "pending",
125
+ "created_at": datetime.datetime.utcnow().isoformat()
126
+ }
127
+ supabase.table("tasks").insert([new_task]).execute()
128
+ # write decision log
129
+ supabase.table("logs").insert([{"message": f"MASTER assigned bed {decision.bed_id} to patient {task.get('patient_id')}", "timestamp": datetime.datetime.utcnow().isoformat()}]).execute()
130
+ elif action == "assign_cleaner":
131
+ if not decision.cleaner_id or not task.get("bed_id"):
132
+ return {"error": "cleaner_id and bed context required"}
133
+ # mark cleaner busy and bed cleaning_in_progress
134
+ supabase.table("staff").update({"available": False}).eq("id", decision.cleaner_id).execute()
135
+ supabase.table("beds").update({"status": "cleaning_in_progress", "cleaner_id": decision.cleaner_id}).eq("id", task.get("bed_id")).execute()
136
+ supabase.table("logs").insert([{"message": f"CLEANER {decision.cleaner_id} cleaning bed {task.get('bed_id')}", "timestamp": datetime.datetime.utcnow().isoformat()}]).execute()
137
+ elif action == "assign_nurse":
138
+ if not decision.nurse_id:
139
+ return {"error": "nurse_id required"}
140
+ # assign nurse and schedule tasks
141
+ supabase.table("staff").update({"available": False}).eq("id", decision.nurse_id).execute()
142
+ # set bed's nurse_id
143
+ supabase.table("beds").update({"nurse_id": decision.nurse_id}).eq("id", task.get("bed_id")).execute()
144
+ # schedule a nurse_checkup task immediately (example)
145
+ nurse_task = {
146
+ "type": "nurse_checkup",
147
+ "target_role": "HUMAN",
148
+ "agent_role": None,
149
+ "assigned_to": decision.nurse_id,
150
+ "patient_id": task.get("patient_id"),
151
+ "bed_id": task.get("bed_id"),
152
+ "status": "pending",
153
+ "scheduled_at": datetime.datetime.utcnow().isoformat(),
154
+ "created_at": datetime.datetime.utcnow().isoformat()
155
+ }
156
+ supabase.table("tasks").insert([nurse_task]).execute()
157
+ supabase.table("logs").insert([{"message": f"NURSE {decision.nurse_id} assigned to bed {task.get('bed_id')}", "timestamp": datetime.datetime.utcnow().isoformat()}]).execute()
158
+ elif action == "defer":
159
+ # put task back to pending and set a retry timestamp or note
160
+ supabase.table("tasks").update({"status": "pending", "retry_after": (datetime.datetime.utcnow() + datetime.timedelta(minutes=5)).isoformat()}).eq("id", task_id).execute()
161
+ supabase.table("logs").insert([{"message": f"TASK {task_id} deferred by agent: {decision.reason}", "timestamp": datetime.datetime.utcnow().isoformat()}]).execute()
162
+ return {"ok": True}
163
+ else:
164
+ return {"error": "unknown action"}
165
+
166
+ # mark current task completed
167
+ supabase.table("tasks").update({"status": "completed"}).eq("id", task_id).execute()
168
+ return {"ok": True}