Initial commit with Flask API and Docker setup
Browse files- .dockerignore +10 -0
- Dockerfile +18 -0
- app.py +168 -0
- requirements.txt +3 -0
.dockerignore
ADDED
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
__pycache__
|
2 |
+
*.pyc
|
3 |
+
*.pyo
|
4 |
+
*.pyd
|
5 |
+
.Python
|
6 |
+
env/
|
7 |
+
venv/
|
8 |
+
*.log
|
9 |
+
.git
|
10 |
+
.gitignore
|
Dockerfile
ADDED
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Utiliser une image Python officielle comme base
|
2 |
+
FROM python:3.9-slim
|
3 |
+
|
4 |
+
# Définir le répertoire de travail
|
5 |
+
WORKDIR /app
|
6 |
+
|
7 |
+
# Copier les fichiers requirements.txt (à créer) et installer les dépendances
|
8 |
+
COPY requirements.txt .
|
9 |
+
RUN pip install --no-cache-dir -r requirements.txt
|
10 |
+
|
11 |
+
# Copier tout le code de l'application dans le conteneur
|
12 |
+
COPY . .
|
13 |
+
|
14 |
+
# Exposer le port sur lequel l'application va tourner (5000 dans votre cas)
|
15 |
+
EXPOSE 5000
|
16 |
+
|
17 |
+
# Commande pour lancer l'application
|
18 |
+
CMD ["python", "app.py"]
|
app.py
ADDED
@@ -0,0 +1,168 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# app.py (modifié)
|
2 |
+
from flask import Flask, request, jsonify
|
3 |
+
from flask_cors import CORS
|
4 |
+
import numpy as np
|
5 |
+
from random import randint, choice
|
6 |
+
|
7 |
+
app = Flask(__name__)
|
8 |
+
CORS(app)
|
9 |
+
|
10 |
+
class Patient:
|
11 |
+
def __init__(self, id, esi_level, needs):
|
12 |
+
self.id = id
|
13 |
+
self.esi_level = esi_level
|
14 |
+
self.needs = needs
|
15 |
+
|
16 |
+
class CHU:
|
17 |
+
def __init__(self, id, resources, distance):
|
18 |
+
self.id = id
|
19 |
+
self.resources = resources.copy()
|
20 |
+
self.assigned_patients = {}
|
21 |
+
self.available_resources = resources.copy()
|
22 |
+
self.distance = distance # Nouvelle propriété pour la distance
|
23 |
+
|
24 |
+
def update_resources(chu, patient, action="remove"):
|
25 |
+
for r, qty in patient.needs.items():
|
26 |
+
if action == "remove":
|
27 |
+
chu.available_resources[r] = chu.available_resources.get(r, 0) - qty
|
28 |
+
print(f"CHU {chu.id} - {r} décrémenté: {chu.available_resources[r]}")
|
29 |
+
elif action == "add":
|
30 |
+
chu.available_resources[r] = chu.available_resources.get(r, 0) + qty
|
31 |
+
print(f"CHU {chu.id} - {r} incrémenté: {chu.available_resources[r]}")
|
32 |
+
|
33 |
+
def has_sufficient_resources(chu, needs):
|
34 |
+
for r, qty in needs.items():
|
35 |
+
available = chu.available_resources.get(r, 0)
|
36 |
+
if available < qty:
|
37 |
+
print(f"Échec pour {r}: besoin {qty}, disponible {available} dans CHU {chu.id}")
|
38 |
+
return False
|
39 |
+
print(f"Ressources suffisantes dans CHU {chu.id}")
|
40 |
+
return True
|
41 |
+
|
42 |
+
def assign_to_chu(chu, patient):
|
43 |
+
chu.assigned_patients[patient.id] = patient
|
44 |
+
update_resources(chu, patient, "remove")
|
45 |
+
|
46 |
+
def release_from_chu(chu, patient_id):
|
47 |
+
if patient_id in chu.assigned_patients:
|
48 |
+
patient = chu.assigned_patients[patient_id]
|
49 |
+
update_resources(chu, patient, "add")
|
50 |
+
del chu.assigned_patients[patient_id]
|
51 |
+
|
52 |
+
def generate_random_resources(nb_chus):
|
53 |
+
chus = []
|
54 |
+
for i in range(nb_chus):
|
55 |
+
resources = {
|
56 |
+
"lit": randint(35, 45),
|
57 |
+
"specialiste": randint(15, 25),
|
58 |
+
"generaliste": randint(15, 25),
|
59 |
+
"defibrillateur": randint(3, 8),
|
60 |
+
"scanner": randint(2, 5),
|
61 |
+
"respirateur": randint(5, 10),
|
62 |
+
"poche_sang": randint(50, 100)
|
63 |
+
}
|
64 |
+
# Générer une distance fictive entre 1 et 50 km
|
65 |
+
distance = randint(1, 50)
|
66 |
+
chus.append(CHU(i, resources, distance))
|
67 |
+
return chus
|
68 |
+
|
69 |
+
def generate_patient_needs(esi_level, requested_resources):
|
70 |
+
needs = {}
|
71 |
+
if esi_level == 1:
|
72 |
+
needs = {"lit": 1, "specialiste": 1, "defibrillateur": randint(0, 1), "respirateur": randint(0, 1), "poche_sang": randint(0, 3)}
|
73 |
+
elif esi_level == 2:
|
74 |
+
needs = {"lit": 1, "specialiste": 1, "scanner": randint(0, 1), "respirateur": randint(0, 1)}
|
75 |
+
elif esi_level == 3:
|
76 |
+
needs = {"lit": 1, "scanner": randint(0, 1), "specialiste": randint(0, 1), "generaliste": randint(0, 1)}
|
77 |
+
elif esi_level == 4:
|
78 |
+
needs = {"lit": randint(0, 1), "scanner": randint(0, 1), "generaliste": randint(0, 1)}
|
79 |
+
elif esi_level == 5:
|
80 |
+
needs = {"lit": randint(0, 1)} if randint(0, 1) else {}
|
81 |
+
|
82 |
+
resource_mapping = {
|
83 |
+
"Lit": "lit",
|
84 |
+
"Respirateur": "respirateur",
|
85 |
+
"Oxygène": "poche_sang",
|
86 |
+
"Sang": "poche_sang",
|
87 |
+
"Spécialiste": "specialiste",
|
88 |
+
"Généraliste": "generaliste",
|
89 |
+
"Défibrillateur": "defibrillateur"
|
90 |
+
}
|
91 |
+
for res in requested_resources:
|
92 |
+
key = resource_mapping.get(res)
|
93 |
+
if key:
|
94 |
+
needs[key] = needs.get(key, 0) + 1
|
95 |
+
else:
|
96 |
+
print(f"Ressource non reconnue: {res}")
|
97 |
+
|
98 |
+
print(f"Besoins générés pour ESI {esi_level}: {needs}")
|
99 |
+
return needs
|
100 |
+
|
101 |
+
def assign_patients_with_reallocation(patients, chus):
|
102 |
+
allocation = {p.id: chu.id for chu in chus for p in chu.assigned_patients.values()}
|
103 |
+
reallocations = []
|
104 |
+
|
105 |
+
patients_sorted = sorted(patients, key=lambda p: p.esi_level)
|
106 |
+
for patient in patients_sorted:
|
107 |
+
# Trouver tous les CHU avec assez de ressources
|
108 |
+
eligible_chus = [chu for chu in chus if has_sufficient_resources(chu, patient.needs)]
|
109 |
+
|
110 |
+
if eligible_chus:
|
111 |
+
# Choisir le CHU le plus proche parmi ceux qui ont assez de ressources
|
112 |
+
best_chu = min(eligible_chus, key=lambda chu: chu.distance)
|
113 |
+
print(f"CHU {best_chu.id} sélectionné pour patient {patient.id} (distance: {best_chu.distance} km)")
|
114 |
+
allocation[patient.id] = best_chu.id
|
115 |
+
assign_to_chu(best_chu, patient)
|
116 |
+
else:
|
117 |
+
# Si aucun CHU n'a assez de ressources et ESI <= 3, tenter une réallocation
|
118 |
+
if patient.esi_level <= 3:
|
119 |
+
for chu in chus:
|
120 |
+
to_release = [p for p in chu.assigned_patients.values() if p.esi_level > patient.esi_level]
|
121 |
+
if to_release:
|
122 |
+
candidate = max(to_release, key=lambda p: p.esi_level)
|
123 |
+
release_from_chu(chu, candidate.id)
|
124 |
+
reallocations.append((candidate.id, chu.id, patient.id, candidate.needs))
|
125 |
+
if has_sufficient_resources(chu, patient.needs):
|
126 |
+
allocation[patient.id] = chu.id
|
127 |
+
assign_to_chu(chu, patient)
|
128 |
+
break
|
129 |
+
|
130 |
+
unassigned = [p for p in patients if p.id not in allocation]
|
131 |
+
return allocation, reallocations, unassigned
|
132 |
+
|
133 |
+
# Initialiser 10 CHU au lieu de 3
|
134 |
+
chus = generate_random_resources(10)
|
135 |
+
|
136 |
+
@app.route('/assign_patient', methods=['POST'])
|
137 |
+
def assign_patient():
|
138 |
+
data = request.json
|
139 |
+
patient_id = data.get('id', f"PAT-{randint(1000, 9999)}")
|
140 |
+
esi_level = int(data['esi'])
|
141 |
+
requested_resources = data.get('ressources', [])
|
142 |
+
|
143 |
+
print(f"Données reçues: id={patient_id}, esi={esi_level}, ressources={requested_resources}")
|
144 |
+
|
145 |
+
patient_needs = generate_patient_needs(esi_level, requested_resources)
|
146 |
+
patient = Patient(patient_id, esi_level, patient_needs)
|
147 |
+
|
148 |
+
allocation, reallocations, unassigned = assign_patients_with_reallocation([patient], chus)
|
149 |
+
|
150 |
+
response = {
|
151 |
+
"patient_id": patient_id,
|
152 |
+
"assigned_chu": allocation.get(patient_id, None),
|
153 |
+
"chus": [
|
154 |
+
{
|
155 |
+
"id": chu.id,
|
156 |
+
"available_resources": chu.available_resources,
|
157 |
+
"assigned_patients": len(chu.assigned_patients),
|
158 |
+
"distance": chu.distance # Ajouter la distance dans la réponse
|
159 |
+
}
|
160 |
+
for chu in chus
|
161 |
+
],
|
162 |
+
"reallocations": reallocations,
|
163 |
+
"unassigned": [p.id for p in unassigned]
|
164 |
+
}
|
165 |
+
return jsonify(response)
|
166 |
+
|
167 |
+
if __name__ == "__main__":
|
168 |
+
app.run(debug=True, port=5000)
|
requirements.txt
ADDED
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
1 |
+
Flask==2.0.1
|
2 |
+
flask-cors==3.0.10
|
3 |
+
numpy==1.21.0
|