Akira-Kurusu commited on
Commit
f167717
·
verified ·
1 Parent(s): 58f23cd

Upload seed_data.py

Browse files
Files changed (1) hide show
  1. seed_data.py +335 -0
seed_data.py ADDED
@@ -0,0 +1,335 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ seed_data.py — Datos de prueba para el Sistema de Diagnóstico de Retinopatía Diabética
4
+ Uso:
5
+ python seed_data.py # Insertar datos (respeta existentes)
6
+ python seed_data.py --reset # Borrar TODO y reinsertar desde cero
7
+ python seed_data.py --clear # Solo borrar datos de prueba (no el admin)
8
+ python seed_data.py --stats # Ver resumen de lo que hay en la BD
9
+
10
+ Coloca este archivo junto a app.py y database.py antes de ejecutarlo.
11
+ """
12
+
13
+ import os
14
+ import sys
15
+ import random
16
+ import sqlite3
17
+ import hashlib
18
+ from datetime import datetime, timedelta
19
+
20
+ # ── Localizar la base de datos igual que lo hace la app ──────────────────────
21
+ DB_PATH = os.environ.get("DB_PATH", "/data/medical_app.db")
22
+ if not os.path.exists(os.path.dirname(DB_PATH)):
23
+ DB_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), "medical_app.db")
24
+
25
+ print(f"Base de datos: {DB_PATH}")
26
+
27
+ # ── Helpers ──────────────────────────────────────────────────────────────────
28
+ def get_conn():
29
+ conn = sqlite3.connect(DB_PATH)
30
+ conn.row_factory = sqlite3.Row
31
+ conn.execute("PRAGMA foreign_keys = ON")
32
+ return conn
33
+
34
+ def hash_pw(pw: str) -> str:
35
+ return hashlib.sha256(pw.encode()).hexdigest()
36
+
37
+ def rand_date(years_ago_min=1, years_ago_max=80) -> str:
38
+ days = random.randint(years_ago_min * 365, years_ago_max * 365)
39
+ return (datetime.now() - timedelta(days=days)).strftime("%Y-%m-%d")
40
+
41
+ def rand_consultation_date(days_ago_max=180) -> str:
42
+ days = random.randint(0, days_ago_max)
43
+ hours = random.randint(7, 18)
44
+ mins = random.randint(0, 59)
45
+ dt = datetime.now() - timedelta(days=days, hours=hours, minutes=mins)
46
+ return dt.strftime("%Y-%m-%d %H:%M:%S")
47
+
48
+ # ── Datos de prueba ──────────────────────────────────────────────────────────
49
+
50
+ USERS = [
51
+ # (username, password, role)
52
+ ("dr_ramirez", "doctor123", "Doctor"),
53
+ ("dr_perez", "doctor123", "Doctor"),
54
+ ("dr_martinez", "doctor123", "Doctor"),
55
+ ("supervisor", "admin456", "Admin"),
56
+ ]
57
+
58
+ # Nombres dominicanos / caribeños realistas
59
+ PATIENTS = [
60
+ # (nombre, fecha_nacimiento, genero, tipo_diabetes)
61
+ ("Juan Carlos Medina Castillo", "1958-03-14", "M", "Tipo 2"),
62
+ ("María Elena Reyes Sánchez", "1965-07-22", "F", "Tipo 2"),
63
+ ("Roberto Antonio Pérez Núñez", "1972-11-05", "M", "Tipo 1"),
64
+ ("Carmen Altagracia Díaz López", "1949-01-30", "F", "Tipo 2"),
65
+ ("Luis Fernando Vargas Torres", "1980-09-18", "M", "Tipo 2"),
66
+ ("Ana Milagros Santos Herrera", "1955-04-12", "F", "Tipo 2"),
67
+ ("Pedro Emilio Guzmán Rosario", "1963-12-28", "M", "Tipo 1"),
68
+ ("Josefa Alicia Mora Taveras", "1970-06-03", "F", "Gestacional"),
69
+ ("Francisco Javier Marte Féliz", "1945-08-17", "M", "Tipo 2"),
70
+ ("Rosa María Almonte Encarnación", "1968-02-09", "F", "Tipo 2"),
71
+ ("Miguel Ángel Cabrera Paulino", "1976-10-25", "M", "MODY"),
72
+ ("Esperanza del Carmen Pichardo", "1952-05-31", "F", "Tipo 2"),
73
+ ("Carlos Daniel Tejada Ureña", "1988-03-07", "M", "Tipo 1"),
74
+ ("Yolanda Beatriz Novas Acosta", "1961-09-14", "F", "Tipo 2"),
75
+ ("Ramón Ernesto Jiménez Clase", "1957-07-19", "M", "Tipo 2"),
76
+ ("Gloria Inés Santana Bido", "1973-11-22", "F", "Tipo 2"),
77
+ ("Héctor Manuel Ogando Matos", "1966-04-08", "M", "Tipo 2"),
78
+ ("Milagros Concepción Báez Rijo", "1982-01-16", "F", "Tipo 1"),
79
+ ("Víctor Hugo Severino Cepeda", "1948-08-04", "M", "Tipo 2"),
80
+ ("Luz Marina Polanco Espinal", "1959-06-27", "F", "Tipo 2"),
81
+ ("Andrés Felipe Castillo Grullón", "1990-12-11", "M", "Tipo 1"),
82
+ ("Natividad Eugenia Pena Arias", "1944-03-23", "F", "Tipo 2"),
83
+ ("Domingo Rafael Flores Medina", "1967-10-01", "M", "Otro"),
84
+ ("Sandra Margarita Cruz Lizardo", "1978-05-18", "F", "Tipo 2"),
85
+ ("Eugenio Antonio Lora Ferreira", "1953-09-30", "M", "Tipo 2"),
86
+ ]
87
+
88
+ # riskFactorIDs del sistema (se leen de la BD para no asumir IDs)
89
+ # Mapeo nombre -> cuáles pacientes los tienen (por índice 0-based)
90
+ PATIENT_RISK_FACTORS = {
91
+ "Hipertensión": [0, 3, 4, 5, 8, 9, 13, 14, 16, 18, 19, 21, 24],
92
+ "Diabetes Tipo 1": [2, 6, 12, 17, 20],
93
+ "Diabetes Tipo 2": [0, 1, 3, 4, 5, 7, 8, 9, 11, 13, 14, 15, 16, 18, 19, 21, 22, 23, 24],
94
+ "Obesidad": [0, 3, 8, 9, 14, 18, 21, 24],
95
+ "Tabaquismo": [6, 8, 14, 18, 22],
96
+ "Sedentarismo": [1, 3, 5, 9, 13, 19, 21],
97
+ "Antecedentes Familiares":[0, 2, 4, 6, 10, 12, 15, 17, 20],
98
+ "Edad Avanzada": [3, 8, 18, 21],
99
+ "Colesterol Alto": [0, 4, 8, 13, 16, 18, 24],
100
+ "Nefropatía": [3, 8, 18],
101
+ }
102
+
103
+ # ── Distribución de consultas ��────────────────────────────────────────────────
104
+ # Cada tupla: (patient_idx, confidence, has_dr, notes_template)
105
+ # has_dr=None => aleatorio según probabilidad del paciente
106
+ CONSULTATION_TEMPLATES = [
107
+ # Pacientes con DR positivo (casos más graves / mayores)
108
+ (0, 92.3, True, "Lesiones hemorrágicas visibles en polo posterior. Se recomienda evaluación urgente con retinólogo."),
109
+ (0, 87.1, True, "Seguimiento: persisten microaneurismas. Control en 3 meses."),
110
+ (3, 95.8, True, "Retinopatía diabética no proliferativa severa. Derivación inmediata."),
111
+ (3, 88.4, True, "Exudados duros periféricos. Edema macular presente."),
112
+ (5, 79.2, True, "Microaneurismas múltiples en arcada temporal superior."),
113
+ (8, 96.1, True, "Retinopatía proliferativa. Neovascularización de disco. Requiere fotocoagulación."),
114
+ (8, 91.7, True, "Post-fotocoagulación. Reducción de neovascularización. Seguimiento mensual."),
115
+ (13, 84.5, True, "Hemorragias en llama en cuadrante nasal inferior."),
116
+ (14, 88.9, True, "Exudados algodonosos y microaneurismas. RDNP moderada."),
117
+ (18, 93.2, True, "Retinopatía diabética avanzada. Tracción vitreorretiniana incipiente."),
118
+ (21, 90.6, True, "Microaneurismas y hemorragias puntiformes bilaterales."),
119
+ (24, 82.3, True, "RDNP leve. Primera detección. Inicio de seguimiento cada 6 meses."),
120
+ # Pacientes sin DR (negativos)
121
+ (1, 91.5, False, "Retina sin signos de retinopatía. Control anual programado."),
122
+ (1, 94.2, False, "Sin cambios respecto a control anterior. Fondo de ojo normal."),
123
+ (2, 88.7, False, "Retina en buen estado. Se refuerza importancia de control glucémico."),
124
+ (4, 96.3, False, "Sin evidencia de retinopatía. Paciente con buen control metabólico."),
125
+ (6, 85.1, False, "Fondo de ojo sin alteraciones. Seguimiento semestral indicado."),
126
+ (7, 92.8, False, "Retina normal. Paciente en primer trimestre, control mensual."),
127
+ (9, 89.4, False, "Sin signos de RD. Se mejora el control de HbA1c."),
128
+ (10, 93.7, False, "Sin alteraciones retinianas. Paciente joven con buen pronóstico."),
129
+ (11, 87.6, False, "Retina sin lesiones. Control semestral."),
130
+ (12, 91.2, False, "Sin retinopatía. Paciente refiere visión borrosa ocasional, se descarta causa retiniana."),
131
+ (15, 94.8, False, "Fondo de ojo normal. Se recomienda mantener HbA1c < 7%."),
132
+ (16, 86.3, False, "Sin signos de RD. Control en 6 meses."),
133
+ (17, 93.1, False, "Retina sin alteraciones. Control glucémico óptimo."),
134
+ (19, 88.9, False, "Sin retinopatía. Se orienta sobre importancia del ejercicio físico."),
135
+ (20, 95.4, False, "Fondo de ojo normal. Primer control, paciente de reciente diagnóstico."),
136
+ (22, 82.7, False, "Sin evidencia de RD. Paciente con Otro tipo de DM, seguimiento especial."),
137
+ (23, 90.1, False, "Retina sin lesiones. Excelente adherencia al tratamiento."),
138
+ ]
139
+
140
+ # ── Funciones principales ─────────────────────────────────────────────────────
141
+
142
+ def clear_test_data(conn):
143
+ """Elimina solo los datos de prueba (deja intacto el usuario admin)."""
144
+ print("\n🗑 Limpiando datos de prueba...")
145
+ # El cascade ON DELETE maneja Patients -> PatientsRiskFactors y Consultations
146
+ conn.execute("DELETE FROM Users WHERE username != 'admin'")
147
+ conn.commit()
148
+ print(" ✓ Usuarios de prueba eliminados (pacientes y consultas en cascada)")
149
+
150
+ def full_reset(conn):
151
+ """Borra ABSOLUTAMENTE todo y deja solo el admin por defecto."""
152
+ print("\n⚠️ Reset completo de la base de datos...")
153
+ conn.execute("DELETE FROM Consultations")
154
+ conn.execute("DELETE FROM PatientsRiskFactors")
155
+ conn.execute("DELETE FROM Patients")
156
+ conn.execute("DELETE FROM Users WHERE username != 'admin'")
157
+ conn.commit()
158
+ print(" ✓ Base de datos limpia (admin conservado)")
159
+
160
+ def print_stats(conn):
161
+ """Muestra un resumen del estado actual de la BD."""
162
+ print("\n📊 Estado actual de la base de datos:")
163
+ print("─" * 45)
164
+
165
+ users = conn.execute("SELECT role, COUNT(*) as n FROM Users GROUP BY role").fetchall()
166
+ print(" Usuarios:")
167
+ for r in users:
168
+ print(f" {r['role']:10s}: {r['n']}")
169
+
170
+ n_patients = conn.execute("SELECT COUNT(*) FROM Patients").fetchone()[0]
171
+ print(f" Pacientes : {n_patients}")
172
+
173
+ n_consult = conn.execute("SELECT COUNT(*) FROM Consultations").fetchone()[0]
174
+ n_pos = conn.execute("SELECT COUNT(*) FROM Consultations WHERE diabeticRetinopathy=1").fetchone()[0]
175
+ n_neg = n_consult - n_pos
176
+ print(f" Consultas : {n_consult} (✓ {n_neg} negativas / ✗ {n_pos} positivas)")
177
+
178
+ n_rf = conn.execute("SELECT COUNT(*) FROM PatientsRiskFactors").fetchone()[0]
179
+ print(f" Fact.riesgo : {n_rf} asignaciones")
180
+ print("─" * 45)
181
+
182
+ def seed(conn):
183
+ print("\n🌱 Insertando datos de prueba...")
184
+
185
+ # 1. Usuarios
186
+ user_ids = {}
187
+ existing_users = {r['username'] for r in conn.execute("SELECT username FROM Users").fetchall()}
188
+ print("\n 👤 Usuarios:")
189
+ for username, password, role in USERS:
190
+ if username in existing_users:
191
+ uid = conn.execute("SELECT userID FROM Users WHERE username=?", (username,)).fetchone()['userID']
192
+ user_ids[username] = uid
193
+ print(f" - {username} ({role}) → ya existe, omitido")
194
+ else:
195
+ cur = conn.execute(
196
+ "INSERT INTO Users (username, password, role) VALUES (?,?,?)",
197
+ (username, hash_pw(password), role)
198
+ )
199
+ user_ids[username] = cur.lastrowid
200
+ print(f" + {username} ({role}) → creado [pass: {password}]")
201
+ conn.commit()
202
+
203
+ # Lista de doctores para repartir pacientes
204
+ doctor_usernames = [u for u, _, r in USERS if r == "Doctor"]
205
+ doctor_ids = [user_ids[u] for u in doctor_usernames]
206
+
207
+ # 2. Pacientes
208
+ patient_ids = []
209
+ print("\n 🏥 Pacientes:")
210
+ for i, (name, birth, gender, diab) in enumerate(PATIENTS):
211
+ existing = conn.execute(
212
+ "SELECT patientID FROM Patients WHERE name=?", (name,)
213
+ ).fetchone()
214
+ if existing:
215
+ patient_ids.append(existing['patientID'])
216
+ print(f" - {name} → ya existe, omitido")
217
+ continue
218
+
219
+ # Repartir entre doctores de forma cíclica
220
+ doctor_id = doctor_ids[i % len(doctor_ids)]
221
+ cur = conn.execute(
222
+ "INSERT INTO Patients (createdByUserID, name, birthDate, gender, diabetesType) VALUES (?,?,?,?,?)",
223
+ (doctor_id, name, birth, gender, diab)
224
+ )
225
+ patient_ids.append(cur.lastrowid)
226
+ doctor_name = doctor_usernames[i % len(doctor_usernames)]
227
+ print(f" + {name} ({diab}) → Dr. {doctor_name}")
228
+ conn.commit()
229
+
230
+ # 3. Factores de riesgo
231
+ rf_map = {} # nombre -> riskFactorID
232
+ for r in conn.execute("SELECT riskFactorID, name FROM RiskFactors").fetchall():
233
+ rf_map[r['name']] = r['riskFactorID']
234
+
235
+ print("\n ⚠️ Factores de riesgo:")
236
+ rf_count = 0
237
+ for rf_name, patient_indices in PATIENT_RISK_FACTORS.items():
238
+ rf_id = rf_map.get(rf_name)
239
+ if rf_id is None:
240
+ print(f" ⚠ Factor '{rf_name}' no encontrado en BD, omitido")
241
+ continue
242
+ for idx in patient_indices:
243
+ if idx >= len(patient_ids):
244
+ continue
245
+ pid = patient_ids[idx]
246
+ try:
247
+ conn.execute(
248
+ "INSERT OR IGNORE INTO PatientsRiskFactors (patientID, riskFactorID) VALUES (?,?)",
249
+ (pid, rf_id)
250
+ )
251
+ rf_count += 1
252
+ except Exception:
253
+ pass
254
+ conn.commit()
255
+ print(f" + {rf_count} asignaciones insertadas")
256
+
257
+ # 4. Consultas
258
+ print("\n 📋 Consultas:")
259
+ consult_count = 0
260
+ for patient_idx, confidence, has_dr, notes in CONSULTATION_TEMPLATES:
261
+ if patient_idx >= len(patient_ids):
262
+ continue
263
+ pid = patient_ids[patient_idx]
264
+
265
+ # El doctor que creó el paciente hace la consulta
266
+ doctor_row = conn.execute(
267
+ "SELECT createdByUserID FROM Patients WHERE patientID=?", (pid,)
268
+ ).fetchone()
269
+ if not doctor_row:
270
+ continue
271
+ doctor_id = doctor_row['createdByUserID']
272
+
273
+ raw_output = confidence / 100.0
274
+ consult_date = rand_consultation_date(days_ago_max=180)
275
+
276
+ conn.execute(
277
+ """INSERT INTO Consultations
278
+ (patientID, createdByUserID, diabeticRetinopathy, notes,
279
+ confidence, rawOutput, consultationDate)
280
+ VALUES (?,?,?,?,?,?,?)""",
281
+ (pid, doctor_id, 1 if has_dr else 0, notes,
282
+ confidence, raw_output, consult_date)
283
+ )
284
+ consult_count += 1
285
+
286
+ conn.commit()
287
+ print(f" + {consult_count} consultas insertadas")
288
+
289
+ print_stats(conn)
290
+ print("\n✅ Seed completado.\n")
291
+ print(" Credenciales de prueba:")
292
+ print(" ┌─────────────────┬────────────┬─────────┐")
293
+ print(" │ Usuario │ Contraseña │ Rol │")
294
+ print(" ├─────────────────┼────────────┼─────────┤")
295
+ print(" │ admin │ admin123 │ Admin │")
296
+ print(" │ supervisor │ admin456 │ Admin │")
297
+ print(" │ dr_ramirez │ doctor123 │ Doctor │")
298
+ print(" │ dr_perez │ doctor123 │ Doctor │")
299
+ print(" │ dr_martinez │ doctor123 │ Doctor │")
300
+ print(" └─────────────────┴────────────┴─────────┘\n")
301
+
302
+
303
+ # ── Entry point ───────────────────────────────────────────────────────────────
304
+ if __name__ == "__main__":
305
+ args = sys.argv[1:]
306
+
307
+ if not os.path.exists(DB_PATH):
308
+ print(f"❌ Base de datos no encontrada en: {DB_PATH}")
309
+ print(" Asegúrate de ejecutar la app al menos una vez para inicializar la BD.")
310
+ print(" O ajusta DB_PATH con: DB_PATH=/ruta/a/medical_app.db python seed_data.py")
311
+ sys.exit(1)
312
+
313
+ conn = get_conn()
314
+
315
+ try:
316
+ if "--stats" in args:
317
+ print_stats(conn)
318
+
319
+ elif "--clear" in args:
320
+ clear_test_data(conn)
321
+ print_stats(conn)
322
+
323
+ elif "--reset" in args:
324
+ confirm = input("⚠️ Esto borrará TODOS los datos (excepto admin). ¿Continuar? [s/N]: ")
325
+ if confirm.lower() == 's':
326
+ full_reset(conn)
327
+ seed(conn)
328
+ else:
329
+ print("Cancelado.")
330
+
331
+ else:
332
+ seed(conn)
333
+
334
+ finally:
335
+ conn.close()