import random
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
from datetime import date, timedelta
random.seed(42)
─── DONNÉES DE RÉFÉRENCE ─────────────────────────────────────────────────────
PRENOMS_M = ["Mamadou","Ibrahima","Abdoulaye","Oumar","Cheikh","Modou","Aliou",
"Pape","Serigne","Babacar","Lamine","Moussa","Samba","Alioune",
"Boubacar","Thierno","Daouda","Idrissa","Malick","Souleymane",
"Amadou","Bassirou","Demba","Elhadji","Fatoumata","Gora","Habib",
"Ismaila","Karim","Landing","Matar","Ndiaga","Omar","Pape Ibou"]
PRENOMS_F = ["Fatou","Mariama","Aissatou","Rokhaya","Ndéye","Aminata","Coumba",
"Khady","Seynabou","Yacine","Bineta","Dieynaba","Awa","Binta",
"Fatoumata","Gnagna","Hawa","Ines","Kadiatou","Lamine","Mame",
"Ndeye Fatou","Oumou","Penda","Ramata","Sokhna","Tening","Adja",
"Bousso","Codou","Dado","Edna","Fama"]
NOMS = ["Diallo","Ndiaye","Mbaye","Sow","Fall","Diop","Ba","Ndour","Sy","Thiam",
"Gueye","Sarr","Diouf","Cissé","Koné","Faye","Badji","Camara","Dème",
"Seck","Lô","Toure","Kane","Diagne","Mbodj","Tall","Traoré","Konaté",
"Balde","Bodian","Coly","Dabo","Gomis","Hane","Janko","Keita","Leye",
"Mané","Niass","Ouédraogo","Pouye","Sagna","Tendeng","Wague","Diatta"]
REGIONS = {
"Dakar": 0.42, "Thiès": 0.12, "Diourbel": 0.08, "Saint-Louis": 0.06,
"Kaolack": 0.06, "Ziguinchor": 0.05, "Tambacounda": 0.04, "Louga": 0.04,
"Fatick": 0.04, "Kolda": 0.03, "Matam": 0.02, "Kédougou": 0.02,
"Sédhiou": 0.01, "Kaffrine": 0.01
}
MOTIFS = [
"Dysmorphie faciale", "Retard psychomoteur", "Malformation congénitale",
"Suspicion de trisomie 21", "Retard de croissance", "Hypotonie néonatale",
"Malformation cardiaque", "Petite taille", "Aménorrhée primaire",
"Déficience intellectuelle", "Épilepsie", "Ambiguïté sexuelle",
"Bilan de dépistage", "Retard de langage", "Fissure labio-palatine"
]
SERVICES = [
"Génétique médicale", "Pédiatrie générale", "Néonatologie",
"Cardiologie pédiatrique", "Neurologie pédiatrique", "Endocrinologie",
"ORL pédiatrique", "Chirurgie pédiatrique", "Urgences pédiatriques"
]
PARTICULARITES_GROSSESSE = [
"Aucune", "Grossesse non suivie", "HTA gravidique", "Diabète gestationnel",
"Menace d'accouchement prématuré", "Oligoamnios", "Hydramnios",
"Infections en cours de grossesse", "Gémellité", "Grossesse multiple"
]
Anomalies chromosomiques avec probabilités réalistes
ANOMALIES = [
("Trisomie 21", 0.52),
("Trisomie 18", 0.08),
("Trisomie 13", 0.05),
("Syndrome de Turner (45,X)", 0.09),
("Syndrome de Klinefelter (47,XXY)", 0.07),
("Délétion 5p (Cri du chat)", 0.03),
("Syndrome de Di George (del22q11)", 0.04),
("Trisomie 8 en mosaïque", 0.02),
("Translocation robertsonienne", 0.03),
("Autre anomalie chromosomique", 0.07),
]
DIAGNOSTICS_SUSPECTES = [
"Trisomie 21", "Trisomie 18", "Syndrome de Turner", "Syndrome de Klinefelter",
"Maladie chromosomique non précisée", "Anomalie chromosomique",
"Syndrome de Di George", "Syndrome de Prader-Willi", "Non précisé"
]
RESULTATS_ETF = ["Normal", "Anomalie mineure", "Anomalie majeure", "Non fait", ""]
RESULTATS_RADIO = ["Normal", "Cardiomégalie", "Opacité pulmonaire", "Non fait", ""]
RESULTATS_ECHO_C = ["Normal", "CIV", "CAV complet", "CIA", "Tétralogie de Fallot",
"Coarctation de l'aorte", "Cardiopathie complexe", "Non fait", ""]
RESULTATS_ECHO_A = ["Normal", "Malformation rénale", "Hépatomégalie",
"Anomalie digestive", "Non fait", ""]
RESULTATS_ECHO_P = ["Normal", "Agénésie gonadique", "Gonades en place", "Non fait", "Non applicable", ""]
EVOLUTIONS = [
"Stable", "Amélioration", "Aggravation progressive",
"Stationnaire", "Complications infectieuses", "Décompensation cardiaque",
"Perdu de vue après 1ère consultation"
]
CIRCONSTANCES_DECES = [
"Insuffisance cardiaque", "Pneumopathie sévère", "Sepsis",
"Détresse respiratoire", "Malformations multiples incompatibles",
"Complications post-opératoires", "Paludisme grave",
"Méningite bactérienne", "Causes indéterminées"
]
TARES = [
"", "", "", "HTA", "Diabète", "Cardiopathie", "Drépanocytose",
"Epilepsie", "Hypertension", "Diabète + HTA", "Cancer", "Insuffisance rénale"
]
def weighted_choice(options_weights):
items = list(options_weights.keys())
weights = list(options_weights.values())
return random.choices(items, weights=weights, k=1)[0]
def random_date(start_year, end_year):
start = date(start_year, 1, 1)
end = date(end_year, 12, 31)
delta = (end - start).days
return start + timedelta(days=random.randint(0, delta))
def format_date(d):
return d.strftime("%d/%m/%Y") if d else ""
def phone():
prefixes = ["77","78","70","76","75"]
return f"{random.choice(prefixes)} {random.randint(100,999)} {random.randint(10,99)} {random.randint(10,99)}"
─── GÉNÉRATION DES 500 LIGNES ────────────────────────────────────────────────
rows = []
for i in range(1, 501):
# Sexe déterminé selon l'anomalie si nécessaire
anomalie_pre = random.choices(
[a[0] for a in ANOMALIES],
weights=[a[1] for a in ANOMALIES], k=1
)[0]
if "Turner" in anomalie_pre:
sexe = "Féminin"
elif "Klinefelter" in anomalie_pre:
sexe = "Masculin"
else:
sexe = random.choices(["Masculin", "Féminin"], weights=[0.54, 0.46])[0]
# Prénom selon sexe
prenom = random.choice(PRENOMS_M if sexe == "Masculin" else PRENOMS_F)
nom = random.choice(NOMS)
# Dates
ddn_year = random.randint(2000, 2024)
ddn = random_date(ddn_year, min(ddn_year + 5, 2024))
# Âge en mois
today = date(2025, 12, 31)
age_mois = max(0, (today - ddn).days // 30)
age_ans = age_mois // 12
# Délai de consultation (jours entre naissance et 1ère consultation)
delai_jours = random.randint(1, 2000)
date_1ere_consult = ddn + timedelta(days=delai_jours)
if date_1ere_consult > date(2025, 12, 31):
date_1ere_consult = date(2025, 6, 15)
# Âge mère
age_mere = random.randint(16, 48)
# Origine géographique
origine = weighted_choice(REGIONS)
# Suivi grossesse
suivi_grossesse = random.choices(
["Oui", "Non", "Partiel"],
weights=[0.55, 0.25, 0.20]
)[0]
# Consanguinité
consanguinite = random.choices(
["Non", "1er degré", "2ème degré", "3ème degré ou plus"],
weights=[0.65, 0.08, 0.15, 0.12]
)[0]
# Motif de consultation
motif = random.choice(MOTIFS)
if "Trisomie 21" in anomalie_pre:
motif = random.choices(
["Dysmorphie faciale", "Retard psychomoteur", "Malformation cardiaque",
"Suspicion de trisomie 21", "Hypotonie néonatale"],
weights=[0.35, 0.25, 0.20, 0.15, 0.05]
)[0]
elif "Turner" in anomalie_pre:
motif = random.choices(
["Petite taille", "Aménorrhée primaire", "Malformation cardiaque", "Dysmorphie faciale"],
weights=[0.40, 0.30, 0.20, 0.10]
)[0]
# Service consulté
service = random.choice(SERVICES)
# Examens paracliniques
# ETF surtout chez nourrissons
etf = random.choices(RESULTATS_ETF, weights=[0.30, 0.15, 0.10, 0.35, 0.10])[0]
radio_thorax = random.choices(RESULTATS_RADIO, weights=[0.30, 0.10, 0.08, 0.45, 0.07])[0]
echo_card = random.choices(RESULTATS_ECHO_C,
weights=[0.20, 0.12, 0.08, 0.06, 0.04, 0.04, 0.05, 0.35, 0.06])[0]
echo_abdo = random.choices(RESULTATS_ECHO_A,
weights=[0.30, 0.08, 0.06, 0.05, 0.45, 0.06])[0]
autres_exam = random.choices(
["IRM cérébrale", "EEG", "NFS + bilan bio", "Audiogramme",
"Fond d'œil", "Radiologie squelette", "Non fait", ""],
weights=[0.08, 0.06, 0.15, 0.06, 0.05, 0.05, 0.40, 0.15]
)[0]
echo_pelv = random.choices(RESULTATS_ECHO_P,
weights=[0.20, 0.05, 0.10, 0.30, 0.25, 0.10])[0]
# Diagnostic suspecté
diag_suspecte = random.choices(
DIAGNOSTICS_SUSPECTES,
weights=[0.38, 0.06, 0.08, 0.06, 0.14, 0.12, 0.06, 0.04, 0.06]
)[0]
# Examens génétiques
chromatine = random.choices(["Positive", "Négative", "Non fait"],
weights=[0.20, 0.25, 0.55])[0]
baar = random.choices(["Positif", "Négatif", "Non fait"],
weights=[0.05, 0.15, 0.80])[0]
# Caryotype
if "Trisomie 21" in anomalie_pre:
caryotype = random.choices(
["47,XY,+21", "47,XX,+21", "46,XX/47,XX,+21 (mosaïque)"],
weights=[0.45, 0.45, 0.10]
)[0]
elif "Trisomie 18" in anomalie_pre:
caryotype = "47," + ("XY" if sexe=="Masculin" else "XX") + ",+18"
elif "Trisomie 13" in anomalie_pre:
caryotype = "47," + ("XY" if sexe=="Masculin" else "XX") + ",+13"
elif "Turner" in anomalie_pre:
caryotype = random.choices(
["45,X", "46,XX/45,X (mosaïque)", "46,X,i(Xq)"],
weights=[0.60, 0.25, 0.15]
)[0]
elif "Klinefelter" in anomalie_pre:
caryotype = random.choices(
["47,XXY", "48,XXXY", "47,XXY/46,XY (mosaïque)"],
weights=[0.85, 0.05, 0.10]
)[0]
elif "Cri du chat" in anomalie_pre:
caryotype = "46," + ("XY" if sexe=="Masculin" else "XX") + ",del(5)(p14)"
elif "Di George" in anomalie_pre:
caryotype = "46," + ("XY" if sexe=="Masculin" else "XX") + ",del(22)(q11.2)"
else:
caryotype = random.choices(
["En cours", "Non fait", "Résultat attendu", "Anomalie structurale complexe"],
weights=[0.15, 0.40, 0.20, 0.25]
)[0]
# Autres examens génétiques
pcr = random.choices(["Positive", "Négative", "Non fait"], weights=[0.08, 0.12, 0.80])[0]
sry = random.choices(["Présent", "Absent", "Non fait"],
weights=[0.10 if sexe=="Féminin" else 0.05, 0.08, 0.82])[0]
fish = random.choices(["Positive", "Négative", "Non fait", "En attente"],
weights=[0.12, 0.08, 0.72, 0.08])[0]
cgh = random.choices(["CNV pathogène détecté", "Normal", "VUS", "Non fait"],
weights=[0.06, 0.08, 0.04, 0.82])[0]
# Diagnostic retenu = anomalie générée
diag_retenu = anomalie_pre
# Évolution
if "Trisomie 18" in anomalie_pre or "Trisomie 13" in anomalie_pre:
evolution = random.choices(
["Aggravation progressive", "Complications infectieuses",
"Décompensation cardiaque", "Stable"],
weights=[0.40, 0.25, 0.25, 0.10]
)[0]
else:
evolution = random.choice(EVOLUTIONS)
# Devenir en 2026
if "Trisomie 18" in anomalie_pre or "Trisomie 13" in anomalie_pre:
devenir_weights = [0.08, 0.05, 0.72, 0.15]
elif "Trisomie 21" in anomalie_pre:
devenir_weights = [0.45, 0.20, 0.18, 0.17]
else:
devenir_weights = [0.50, 0.15, 0.20, 0.15]
devenir = random.choices(
["Vivant en suivi", "Vivant perdu de vue", "Décédé", "Pas de nouvelles"],
weights=devenir_weights
)[0]
# Survie
survie = "Oui" if devenir == "Vivant en suivi" else (
"Non" if devenir == "Décédé" else
"Inconnue")
# Pas de nouvelles
pas_nouvelles = "Oui" if devenir in ["Vivant perdu de vue", "Pas de nouvelles"] else "Non"
# Décès
deces = "Oui" if devenir == "Décédé" else "Non"
age_deces = ""
circonstances = ""
if deces == "Oui":
# Âge au décès (en mois, entre 0 et age_mois)
if "Trisomie 18" in anomalie_pre or "Trisomie 13" in anomalie_pre:
age_deces_val = random.randint(0, min(6, age_mois))
else:
age_deces_val = random.randint(0, age_mois)
age_deces = f"{age_deces_val} mois"
circonstances = random.choice(CIRCONSTANCES_DECES)
# Tares parentales
tare_mat = random.choices(TARES, weights=[0.55]*1 + [0.55]*1 + [0.55]*1 +
[0.08, 0.06, 0.05, 0.04, 0.03, 0.04, 0.03, 0.03, 0.02])[0]
# fix for unequal weights
tare_mat = random.choices(
["", "HTA", "Diabète", "Cardiopathie", "Drépanocytose",
"Epilepsie", "Diabète + HTA", "Cancer", "Insuffisance rénale"],
weights=[0.62, 0.10, 0.08, 0.05, 0.04, 0.04, 0.03, 0.02, 0.02]
)[0]
tare_pat = random.choices(
["", "HTA", "Diabète", "Cardiopathie", "Drépanocytose",
"Epilepsie", "Diabète + HTA", "Alcoolisme", "Insuffisance rénale"],
weights=[0.65, 0.09, 0.07, 0.05, 0.04, 0.04, 0.03, 0.02, 0.01]
)[0]
particularites = random.choices(PARTICULARITES_GROSSESSE,
weights=[0.38, 0.12, 0.08, 0.07, 0.06, 0.05, 0.05, 0.08, 0.05, 0.06])[0]
rows.append({
"N°": i,
"Prénoms": prenom,
"Nom": nom,
"Numéro de téléphone": phone(),
"Age (mois)": age_mois,
"Date de naissance": format_date(ddn),
"Délai de consultation (jours)": delai_jours,
"Date de 1ère consultation": format_date(date_1ere_consult),
"Sexe": sexe,
"Origine géographique": origine,
"Age de la mère à la 1ère consultation (ans)": age_mere,
"Suivi de la grossesse": suivi_grossesse,
"Particularités": particularites,
"Consanguinité": consanguinite,
"Motif de consultation": motif,
"Services consultés": service,
"ETF": etf,
"Radiographie du thorax": radio_thorax,
"Echographie cardiaque": echo_card,
"Echographie abdominale": echo_abdo,
"Autres examens": autres_exam,
"Echographie pelvienne": echo_pelv,
"Diagnostic suspecté": diag_suspecte,
"Chromatine": chromatine,
"BAAR": baar,
"Caryotype": caryotype,
"PCR": pcr,
"Recherche de SRY": sry,
"FISH": fish,
"CGH-array": cgh,
"Diagnostic retenu": diag_retenu,
"Evolution": evolution,
"Devenir en 2026": devenir,
"Survie": survie,
"Pas de nouvelles": pas_nouvelles,
"Décès": deces,
"Age au décès": age_deces,
"Circonstances du décès": circonstances,
"Tare maternelle": tare_mat,
"Tare paternelle": tare_pat,
})
─── CRÉATION DU FICHIER EXCEL ────────────────────────────────────────────────
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "Données"
headers = list(rows[0].keys())
── Styles ──
BLEU_FONCE = "1F3864"
BLEU_CLAIR = "2E74B5"
GRIS_CLAIR = "F2F7FB"
GRIS_ALT = "FFFFFF"
header_font = Font(name="Arial", bold=True, color="FFFFFF", size=10)
header_fill = PatternFill("solid", fgColor=BLEU_CLAIR)
header_align = Alignment(horizontal="center", vertical="center", wrap_text=True)
data_font = Font(name="Arial", size=10)
data_font_bold = Font(name="Arial", size=10, bold=True)
alt_fill = PatternFill("solid", fgColor=GRIS_CLAIR)
white_fill = PatternFill("solid", fgColor="FFFFFF")
thin_side = Side(style="thin", color="CCCCCC")
thin_border = Border(left=thin_side, right=thin_side,
top=thin_side, bottom=thin_side)
center_align = Alignment(horizontal="center", vertical="center")
left_align = Alignment(horizontal="left", vertical="center", wrap_text=False)
── En-têtes ──
for col_idx, header in enumerate(headers, start=1):
cell = ws.cell(row=1, column=col_idx, value=header)
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_align
cell.border = thin_border
ws.row_dimensions[1].height = 40
── Données ──
for row_idx, row_data in enumerate(rows, start=2):
fill = alt_fill if row_idx % 2 == 0 else PatternFill("solid", fgColor="FFFFFF")
for col_idx, key in enumerate(headers, start=1):
value = row_data[key]
cell = ws.cell(row=row_idx, column=col_idx, value=value)
cell.font = data_font_bold if key == "N°" else data_font
cell.fill = fill
cell.border = thin_border
cell.alignment = center_align if key in [
"N°", "Age (mois)", "Délai de consultation (jours)",
"Age de la mère à la 1ère consultation (ans)", "Sexe",
"Suivi de la grossesse", "Consanguinité",
"Chromatine", "BAAR", "PCR", "Recherche de SRY", "FISH",
"CGH-array", "Survie", "Pas de nouvelles", "Décès"
] else left_align
ws.row_dimensions[row_idx].height = 18
── Largeurs de colonnes adaptées ──
col_widths = {
"N°": 5,
"Prénoms": 14,
"Nom": 14,
"Numéro de téléphone": 16,
"Age (mois)": 10,
"Date de naissance": 14,
"Délai de consultation (jours)": 16,
"Date de 1ère consultation": 16,
"Sexe": 10,
"Origine géographique": 16,
"Age de la mère à la 1ère consultation (ans)": 16,
"Suivi de la grossesse": 14,
"Particularités": 26,
"Consanguinité": 18,
"Motif de consultation": 26,
"Services consultés": 22,
"ETF": 18,
"Radiographie du thorax": 18,
"Echographie cardiaque": 24,
"Echographie abdominale": 18,
"Autres examens": 22,
"Echographie pelvienne": 18,
"Diagnostic suspecté": 26,
"Chromatine": 12,
"BAAR": 12,
"Caryotype": 26,
"PCR": 12,
"Recherche de SRY": 16,
"FISH": 16,
"CGH-array": 26,
"Diagnostic retenu": 30,
"Evolution": 28,
"Devenir en 2026": 22,
"Survie": 10,
"Pas de nouvelles": 14,
"Décès": 10,
"Age au décès": 14,
"Circonstances du décès": 28,
"Tare maternelle": 18,
"Tare paternelle": 18,
}
for col_idx, header in enumerate(headers, start=1):
col_letter = get_column_letter(col_idx)
ws.column_dimensions[col_letter].width = col_widths.get(header, 16)
── Figer la première ligne ──
ws.freeze_panes = "B2"
── Filtre automatique ──
ws.auto_filter.ref = f"A1:{get_column_letter(len(headers))}1"
─── FEUILLE 2 : DICTIONNAIRE DES VARIABLES ───────────────────────────────────
ws2 = wb.create_sheet("Dictionnaire des variables")
dict_headers = ["Variable", "Type", "Modalités / Format", "Notes"]
for col_idx, h in enumerate(dict_headers, start=1):
cell = ws2.cell(row=1, column=col_idx, value=h)
cell.font = Font(name="Arial", bold=True, color="FFFFFF", size=10)
cell.fill = PatternFill("solid", fgColor=BLEU_FONCE)
cell.alignment = header_align
cell.border = thin_border
ws2.row_dimensions[1].height = 28
dictionnaire = [
("N°", "Numérique", "Entier 1–500", "Identifiant unique du patient"),
("Prénoms", "Texte", "Prénom(s)", "Données anonymisées"),
("Nom", "Texte", "Nom de famille", "Données anonymisées"),
("Numéro de téléphone", "Texte", "Format SN : 7X XXX XX XX", "Contact famille"),
("Age (mois)", "Numérique", "Entier ≥ 0", "Âge au 31/12/2025"),
("Date de naissance", "Date", "JJ/MM/AAAA", ""),
("Délai de consultation", "Numérique", "Jours entre naissance et 1ère consult","Variable pronostique clé"),
("Date de 1ère consult.", "Date", "JJ/MM/AAAA", ""),
("Sexe", "Catégorie", "Masculin / Féminin", ""),
("Origine géographique", "Catégorie", "14 régions du Sénégal", ""),
("Age mère", "Numérique", "Années", "Facteur de risque non-disjonction"),
("Suivi grossesse", "Catégorie", "Oui / Non / Partiel", ""),
("Particularités", "Texte", "Événements gravidiques", ""),
("Consanguinité", "Catégorie", "Non / 1er / 2ème / 3ème degré", ""),
("Motif de consultation", "Texte", "Libre", ""),
("Services consultés", "Catégorie", "Service orienteur", ""),
("ETF", "Catégorie", "Normal / Anomalie / Non fait", "Échographie transfontanellaire"),
("Radiographie thorax", "Catégorie", "Normal / Anomalie / Non fait", ""),
("Echographie cardiaque", "Catégorie", "Résultat ou Non fait", "Systématique si T21/T18/T13"),
("Echographie abdominale","Catégorie", "Résultat ou Non fait", ""),
("Autres examens", "Texte", "IRM, EEG, NFS, etc.", ""),
("Echographie pelvienne", "Catégorie", "Résultat ou Non fait", "Turner, Klinefelter"),
("Diagnostic suspecté", "Texte", "Diagnostic clinique avant caryotype", ""),
("Chromatine", "Catégorie", "Positive / Négative / Non fait", "Corpuscule de Barr"),
("BAAR", "Catégorie", "Positif / Négatif / Non fait", ""),
("Caryotype", "Texte", "Formule ISCN (ex: 47,XY,+21)", "Gold standard diagnostic"),
("PCR", "Catégorie", "Positive / Négative / Non fait", ""),
("Recherche de SRY", "Catégorie", "Présent / Absent / Non fait", "Ambiguïté sexuelle"),
("FISH", "Catégorie", "Positive / Négative / Non fait", ""),
("CGH-array", "Catégorie", "CNV / Normal / VUS / Non fait", ""),
("Diagnostic retenu", "Catégorie", "Anomalie chromosomique confirmée", "Variable principale"),
("Evolution", "Texte", "Description évolution clinique", ""),
("Devenir en 2026", "Catégorie", "Vivant / Décédé / Perdu de vue", ""),
("Survie", "Catégorie", "Oui / Non / Inconnue", "Variable de survie (event)"),
("Pas de nouvelles", "Catégorie", "Oui / Non", "Censure à droite"),
("Décès", "Catégorie", "Oui / Non", "Événement = 1 pour l'analyse de survie"),
("Age au décès", "Texte", "Ex: 24 mois", "Variable de temps"),
("Circonstances décès", "Texte", "Cause du décès", ""),
("Tare maternelle", "Texte", "Pathologie chronique mère", ""),
("Tare paternelle", "Texte", "Pathologie chronique père", ""),
]
for row_idx, (var, typ, modal, note) in enumerate(dictionnaire, start=2):
fill = alt_fill if row_idx % 2 == 0 else PatternFill("solid", fgColor="FFFFFF")
for col_idx, val in enumerate([var, typ, modal, note], start=1):
cell = ws2.cell(row=row_idx, column=col_idx, value=val)
cell.font = Font(name="Arial", size=10, bold=(col_idx == 1))
cell.fill = fill
cell.border = thin_border
cell.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
ws2.row_dimensions[row_idx].height = 20
ws2.column_dimensions["A"].width = 28
ws2.column_dimensions["B"].width = 14
ws2.column_dimensions["C"].width = 34
ws2.column_dimensions["D"].width = 40
─── FEUILLE 3 : STATISTIQUES RAPIDES ────────────────────────────────────────
ws3 = wb.create_sheet("Stats rapides")
stats_title_font = Font(name="Arial", bold=True, size=12, color=BLEU_FONCE)
stats_sub_font = Font(name="Arial", bold=True, size=10, color="FFFFFF")
ws3["A1"] = "STATISTIQUES DESCRIPTIVES RAPIDES — Données simulées (n=500)"
ws3["A1"].font = stats_title_font
ws3["A1"].alignment = Alignment(horizontal="left", vertical="center")
ws3.row_dimensions[1].height = 24
Distribution des anomalies
ws3["A3"] = "Distribution des anomalies chromosomiques"
ws3["A3"].font = Font(name="Arial", bold=True, size=11, color=BLEU_CLAIR)
anomalies_count = {}
sexe_count = {"Masculin": 0, "Féminin": 0}
deces_count = {"Oui": 0, "Non": 0}
survie_count = {}
for r in rows:
diag = r["Diagnostic retenu"]
anomalies_count[diag] = anomalies_count.get(diag, 0) + 1
sexe_count[r["Sexe"]] += 1
deces_count[r["Décès"]] = deces_count.get(r["Décès"], 0) + 1
sv = r["Survie"]
survie_count[sv] = survie_count.get(sv, 0) + 1
row_s = 4
for col_idx, h in enumerate(["Diagnostic retenu", "Effectif (n)", "Proportion (%)"], start=1):
cell = ws3.cell(row=row_s, column=col_idx, value=h)
cell.font = Font(name="Arial", bold=True, color="FFFFFF", size=10)
cell.fill = PatternFill("solid", fgColor=BLEU_CLAIR)
cell.alignment = center_align
cell.border = thin_border
ws3.row_dimensions[row_s].height = 20
row_s += 1
for idx, (diag, cnt) in enumerate(sorted(anomalies_count.items(), key=lambda x: -x[1])):
fill_s = alt_fill if idx % 2 == 0 else PatternFill("solid", fgColor="FFFFFF")
for col_idx, val in enumerate([diag, cnt, f"{round(100*cnt/500,1)}%"], start=1):
cell = ws3.cell(row=row_s, column=col_idx, value=val)
cell.font = Font(name="Arial", size=10)
cell.fill = fill_s
cell.border = thin_border
cell.alignment = left_align if col_idx == 1 else center_align
row_s += 1
Survie
row_s += 1
ws3.cell(row=row_s, column=1, value="Statut de survie").font = Font(name="Arial", bold=True, size=11, color=BLEU_CLAIR)
row_s += 1
for col_idx, h in enumerate(["Survie", "Effectif (n)", "Proportion (%)"], start=1):
cell = ws3.cell(row=row_s, column=col_idx, value=h)
cell.font = Font(name="Arial", bold=True, color="FFFFFF", size=10)
cell.fill = PatternFill("solid", fgColor=BLEU_CLAIR)
cell.alignment = center_align
cell.border = thin_border
row_s += 1
for idx, (sv, cnt) in enumerate(survie_count.items()):
fill_s = alt_fill if idx % 2 == 0 else PatternFill("solid", fgColor="FFFFFF")
for col_idx, val in enumerate([sv, cnt, f"{round(100*cnt/500,1)}%"], start=1):
cell = ws3.cell(row=row_s, column=col_idx, value=val)
cell.font = Font(name="Arial", size=10)
cell.fill = fill_s
cell.border = thin_border
cell.alignment = left_align if col_idx == 1 else center_align
row_s += 1
ws3.column_dimensions["A"].width = 34
ws3.column_dimensions["B"].width = 16
ws3.column_dimensions["C"].width = 16
─── SAUVEGARDE ───────────────────────────────────────────────────────────────
output_path = "/mnt/user-data/outputs/Donnees_Entrainement_500lignes.xlsx"
wb.save(output_path)
print(f"✅ Fichier généré : {output_path}")
print(f" Lignes de données : 500")
print(f" Colonnes : {len(headers)}")
print(f" Feuilles : Données | Dictionnaire des variables | Stats rapides")
import random
import openpyxl
from openpyxl.styles import Font, PatternFill, Alignment, Border, Side
from openpyxl.utils import get_column_letter
from datetime import date, timedelta
random.seed(42)
─── DONNÉES DE RÉFÉRENCE ─────────────────────────────────────────────────────
PRENOMS_M = ["Mamadou","Ibrahima","Abdoulaye","Oumar","Cheikh","Modou","Aliou",
"Pape","Serigne","Babacar","Lamine","Moussa","Samba","Alioune",
"Boubacar","Thierno","Daouda","Idrissa","Malick","Souleymane",
"Amadou","Bassirou","Demba","Elhadji","Fatoumata","Gora","Habib",
"Ismaila","Karim","Landing","Matar","Ndiaga","Omar","Pape Ibou"]
PRENOMS_F = ["Fatou","Mariama","Aissatou","Rokhaya","Ndéye","Aminata","Coumba",
"Khady","Seynabou","Yacine","Bineta","Dieynaba","Awa","Binta",
"Fatoumata","Gnagna","Hawa","Ines","Kadiatou","Lamine","Mame",
"Ndeye Fatou","Oumou","Penda","Ramata","Sokhna","Tening","Adja",
"Bousso","Codou","Dado","Edna","Fama"]
NOMS = ["Diallo","Ndiaye","Mbaye","Sow","Fall","Diop","Ba","Ndour","Sy","Thiam",
"Gueye","Sarr","Diouf","Cissé","Koné","Faye","Badji","Camara","Dème",
"Seck","Lô","Toure","Kane","Diagne","Mbodj","Tall","Traoré","Konaté",
"Balde","Bodian","Coly","Dabo","Gomis","Hane","Janko","Keita","Leye",
"Mané","Niass","Ouédraogo","Pouye","Sagna","Tendeng","Wague","Diatta"]
REGIONS = {
"Dakar": 0.42, "Thiès": 0.12, "Diourbel": 0.08, "Saint-Louis": 0.06,
"Kaolack": 0.06, "Ziguinchor": 0.05, "Tambacounda": 0.04, "Louga": 0.04,
"Fatick": 0.04, "Kolda": 0.03, "Matam": 0.02, "Kédougou": 0.02,
"Sédhiou": 0.01, "Kaffrine": 0.01
}
MOTIFS = [
"Dysmorphie faciale", "Retard psychomoteur", "Malformation congénitale",
"Suspicion de trisomie 21", "Retard de croissance", "Hypotonie néonatale",
"Malformation cardiaque", "Petite taille", "Aménorrhée primaire",
"Déficience intellectuelle", "Épilepsie", "Ambiguïté sexuelle",
"Bilan de dépistage", "Retard de langage", "Fissure labio-palatine"
]
SERVICES = [
"Génétique médicale", "Pédiatrie générale", "Néonatologie",
"Cardiologie pédiatrique", "Neurologie pédiatrique", "Endocrinologie",
"ORL pédiatrique", "Chirurgie pédiatrique", "Urgences pédiatriques"
]
PARTICULARITES_GROSSESSE = [
"Aucune", "Grossesse non suivie", "HTA gravidique", "Diabète gestationnel",
"Menace d'accouchement prématuré", "Oligoamnios", "Hydramnios",
"Infections en cours de grossesse", "Gémellité", "Grossesse multiple"
]
Anomalies chromosomiques avec probabilités réalistes
ANOMALIES = [
("Trisomie 21", 0.52),
("Trisomie 18", 0.08),
("Trisomie 13", 0.05),
("Syndrome de Turner (45,X)", 0.09),
("Syndrome de Klinefelter (47,XXY)", 0.07),
("Délétion 5p (Cri du chat)", 0.03),
("Syndrome de Di George (del22q11)", 0.04),
("Trisomie 8 en mosaïque", 0.02),
("Translocation robertsonienne", 0.03),
("Autre anomalie chromosomique", 0.07),
]
DIAGNOSTICS_SUSPECTES = [
"Trisomie 21", "Trisomie 18", "Syndrome de Turner", "Syndrome de Klinefelter",
"Maladie chromosomique non précisée", "Anomalie chromosomique",
"Syndrome de Di George", "Syndrome de Prader-Willi", "Non précisé"
]
RESULTATS_ETF = ["Normal", "Anomalie mineure", "Anomalie majeure", "Non fait", ""]
RESULTATS_RADIO = ["Normal", "Cardiomégalie", "Opacité pulmonaire", "Non fait", ""]
RESULTATS_ECHO_C = ["Normal", "CIV", "CAV complet", "CIA", "Tétralogie de Fallot",
"Coarctation de l'aorte", "Cardiopathie complexe", "Non fait", ""]
RESULTATS_ECHO_A = ["Normal", "Malformation rénale", "Hépatomégalie",
"Anomalie digestive", "Non fait", ""]
RESULTATS_ECHO_P = ["Normal", "Agénésie gonadique", "Gonades en place", "Non fait", "Non applicable", ""]
EVOLUTIONS = [
"Stable", "Amélioration", "Aggravation progressive",
"Stationnaire", "Complications infectieuses", "Décompensation cardiaque",
"Perdu de vue après 1ère consultation"
]
CIRCONSTANCES_DECES = [
"Insuffisance cardiaque", "Pneumopathie sévère", "Sepsis",
"Détresse respiratoire", "Malformations multiples incompatibles",
"Complications post-opératoires", "Paludisme grave",
"Méningite bactérienne", "Causes indéterminées"
]
TARES = [
"", "", "", "HTA", "Diabète", "Cardiopathie", "Drépanocytose",
"Epilepsie", "Hypertension", "Diabète + HTA", "Cancer", "Insuffisance rénale"
]
def weighted_choice(options_weights):
items = list(options_weights.keys())
weights = list(options_weights.values())
return random.choices(items, weights=weights, k=1)[0]
def random_date(start_year, end_year):
start = date(start_year, 1, 1)
end = date(end_year, 12, 31)
delta = (end - start).days
return start + timedelta(days=random.randint(0, delta))
def format_date(d):
return d.strftime("%d/%m/%Y") if d else ""
def phone():
prefixes = ["77","78","70","76","75"]
return f"{random.choice(prefixes)} {random.randint(100,999)} {random.randint(10,99)} {random.randint(10,99)}"
─── GÉNÉRATION DES 500 LIGNES ────────────────────────────────────────────────
rows = []
for i in range(1, 501):
─── CRÉATION DU FICHIER EXCEL ────────────────────────────────────────────────
wb = openpyxl.Workbook()
ws = wb.active
ws.title = "Données"
headers = list(rows[0].keys())
── Styles ──
BLEU_FONCE = "1F3864"
BLEU_CLAIR = "2E74B5"
GRIS_CLAIR = "F2F7FB"
GRIS_ALT = "FFFFFF"
header_font = Font(name="Arial", bold=True, color="FFFFFF", size=10)
header_fill = PatternFill("solid", fgColor=BLEU_CLAIR)
header_align = Alignment(horizontal="center", vertical="center", wrap_text=True)
data_font = Font(name="Arial", size=10)
data_font_bold = Font(name="Arial", size=10, bold=True)
alt_fill = PatternFill("solid", fgColor=GRIS_CLAIR)
white_fill = PatternFill("solid", fgColor="FFFFFF")
thin_side = Side(style="thin", color="CCCCCC")
thin_border = Border(left=thin_side, right=thin_side,
top=thin_side, bottom=thin_side)
center_align = Alignment(horizontal="center", vertical="center")
left_align = Alignment(horizontal="left", vertical="center", wrap_text=False)
── En-têtes ──
for col_idx, header in enumerate(headers, start=1):
cell = ws.cell(row=1, column=col_idx, value=header)
cell.font = header_font
cell.fill = header_fill
cell.alignment = header_align
cell.border = thin_border
ws.row_dimensions[1].height = 40
── Données ──
for row_idx, row_data in enumerate(rows, start=2):
fill = alt_fill if row_idx % 2 == 0 else PatternFill("solid", fgColor="FFFFFF")
for col_idx, key in enumerate(headers, start=1):
value = row_data[key]
cell = ws.cell(row=row_idx, column=col_idx, value=value)
cell.font = data_font_bold if key == "N°" else data_font
cell.fill = fill
cell.border = thin_border
cell.alignment = center_align if key in [
"N°", "Age (mois)", "Délai de consultation (jours)",
"Age de la mère à la 1ère consultation (ans)", "Sexe",
"Suivi de la grossesse", "Consanguinité",
"Chromatine", "BAAR", "PCR", "Recherche de SRY", "FISH",
"CGH-array", "Survie", "Pas de nouvelles", "Décès"
] else left_align
── Largeurs de colonnes adaptées ──
col_widths = {
"N°": 5,
"Prénoms": 14,
"Nom": 14,
"Numéro de téléphone": 16,
"Age (mois)": 10,
"Date de naissance": 14,
"Délai de consultation (jours)": 16,
"Date de 1ère consultation": 16,
"Sexe": 10,
"Origine géographique": 16,
"Age de la mère à la 1ère consultation (ans)": 16,
"Suivi de la grossesse": 14,
"Particularités": 26,
"Consanguinité": 18,
"Motif de consultation": 26,
"Services consultés": 22,
"ETF": 18,
"Radiographie du thorax": 18,
"Echographie cardiaque": 24,
"Echographie abdominale": 18,
"Autres examens": 22,
"Echographie pelvienne": 18,
"Diagnostic suspecté": 26,
"Chromatine": 12,
"BAAR": 12,
"Caryotype": 26,
"PCR": 12,
"Recherche de SRY": 16,
"FISH": 16,
"CGH-array": 26,
"Diagnostic retenu": 30,
"Evolution": 28,
"Devenir en 2026": 22,
"Survie": 10,
"Pas de nouvelles": 14,
"Décès": 10,
"Age au décès": 14,
"Circonstances du décès": 28,
"Tare maternelle": 18,
"Tare paternelle": 18,
}
for col_idx, header in enumerate(headers, start=1):
col_letter = get_column_letter(col_idx)
ws.column_dimensions[col_letter].width = col_widths.get(header, 16)
── Figer la première ligne ──
ws.freeze_panes = "B2"
── Filtre automatique ──
ws.auto_filter.ref = f"A1:{get_column_letter(len(headers))}1"
─── FEUILLE 2 : DICTIONNAIRE DES VARIABLES ───────────────────────────────────
ws2 = wb.create_sheet("Dictionnaire des variables")
dict_headers = ["Variable", "Type", "Modalités / Format", "Notes"]
for col_idx, h in enumerate(dict_headers, start=1):
cell = ws2.cell(row=1, column=col_idx, value=h)
cell.font = Font(name="Arial", bold=True, color="FFFFFF", size=10)
cell.fill = PatternFill("solid", fgColor=BLEU_FONCE)
cell.alignment = header_align
cell.border = thin_border
ws2.row_dimensions[1].height = 28
dictionnaire = [
("N°", "Numérique", "Entier 1–500", "Identifiant unique du patient"),
("Prénoms", "Texte", "Prénom(s)", "Données anonymisées"),
("Nom", "Texte", "Nom de famille", "Données anonymisées"),
("Numéro de téléphone", "Texte", "Format SN : 7X XXX XX XX", "Contact famille"),
("Age (mois)", "Numérique", "Entier ≥ 0", "Âge au 31/12/2025"),
("Date de naissance", "Date", "JJ/MM/AAAA", ""),
("Délai de consultation", "Numérique", "Jours entre naissance et 1ère consult","Variable pronostique clé"),
("Date de 1ère consult.", "Date", "JJ/MM/AAAA", ""),
("Sexe", "Catégorie", "Masculin / Féminin", ""),
("Origine géographique", "Catégorie", "14 régions du Sénégal", ""),
("Age mère", "Numérique", "Années", "Facteur de risque non-disjonction"),
("Suivi grossesse", "Catégorie", "Oui / Non / Partiel", ""),
("Particularités", "Texte", "Événements gravidiques", ""),
("Consanguinité", "Catégorie", "Non / 1er / 2ème / 3ème degré", ""),
("Motif de consultation", "Texte", "Libre", ""),
("Services consultés", "Catégorie", "Service orienteur", ""),
("ETF", "Catégorie", "Normal / Anomalie / Non fait", "Échographie transfontanellaire"),
("Radiographie thorax", "Catégorie", "Normal / Anomalie / Non fait", ""),
("Echographie cardiaque", "Catégorie", "Résultat ou Non fait", "Systématique si T21/T18/T13"),
("Echographie abdominale","Catégorie", "Résultat ou Non fait", ""),
("Autres examens", "Texte", "IRM, EEG, NFS, etc.", ""),
("Echographie pelvienne", "Catégorie", "Résultat ou Non fait", "Turner, Klinefelter"),
("Diagnostic suspecté", "Texte", "Diagnostic clinique avant caryotype", ""),
("Chromatine", "Catégorie", "Positive / Négative / Non fait", "Corpuscule de Barr"),
("BAAR", "Catégorie", "Positif / Négatif / Non fait", ""),
("Caryotype", "Texte", "Formule ISCN (ex: 47,XY,+21)", "Gold standard diagnostic"),
("PCR", "Catégorie", "Positive / Négative / Non fait", ""),
("Recherche de SRY", "Catégorie", "Présent / Absent / Non fait", "Ambiguïté sexuelle"),
("FISH", "Catégorie", "Positive / Négative / Non fait", ""),
("CGH-array", "Catégorie", "CNV / Normal / VUS / Non fait", ""),
("Diagnostic retenu", "Catégorie", "Anomalie chromosomique confirmée", "Variable principale"),
("Evolution", "Texte", "Description évolution clinique", ""),
("Devenir en 2026", "Catégorie", "Vivant / Décédé / Perdu de vue", ""),
("Survie", "Catégorie", "Oui / Non / Inconnue", "Variable de survie (event)"),
("Pas de nouvelles", "Catégorie", "Oui / Non", "Censure à droite"),
("Décès", "Catégorie", "Oui / Non", "Événement = 1 pour l'analyse de survie"),
("Age au décès", "Texte", "Ex: 24 mois", "Variable de temps"),
("Circonstances décès", "Texte", "Cause du décès", ""),
("Tare maternelle", "Texte", "Pathologie chronique mère", ""),
("Tare paternelle", "Texte", "Pathologie chronique père", ""),
]
for row_idx, (var, typ, modal, note) in enumerate(dictionnaire, start=2):
fill = alt_fill if row_idx % 2 == 0 else PatternFill("solid", fgColor="FFFFFF")
for col_idx, val in enumerate([var, typ, modal, note], start=1):
cell = ws2.cell(row=row_idx, column=col_idx, value=val)
cell.font = Font(name="Arial", size=10, bold=(col_idx == 1))
cell.fill = fill
cell.border = thin_border
cell.alignment = Alignment(horizontal="left", vertical="center", wrap_text=True)
ws2.row_dimensions[row_idx].height = 20
ws2.column_dimensions["A"].width = 28
ws2.column_dimensions["B"].width = 14
ws2.column_dimensions["C"].width = 34
ws2.column_dimensions["D"].width = 40
─── FEUILLE 3 : STATISTIQUES RAPIDES ────────────────────────────────────────
ws3 = wb.create_sheet("Stats rapides")
stats_title_font = Font(name="Arial", bold=True, size=12, color=BLEU_FONCE)
stats_sub_font = Font(name="Arial", bold=True, size=10, color="FFFFFF")
ws3["A1"] = "STATISTIQUES DESCRIPTIVES RAPIDES — Données simulées (n=500)"
ws3["A1"].font = stats_title_font
ws3["A1"].alignment = Alignment(horizontal="left", vertical="center")
ws3.row_dimensions[1].height = 24
Distribution des anomalies
ws3["A3"] = "Distribution des anomalies chromosomiques"
ws3["A3"].font = Font(name="Arial", bold=True, size=11, color=BLEU_CLAIR)
anomalies_count = {}
sexe_count = {"Masculin": 0, "Féminin": 0}
deces_count = {"Oui": 0, "Non": 0}
survie_count = {}
for r in rows:
diag = r["Diagnostic retenu"]
anomalies_count[diag] = anomalies_count.get(diag, 0) + 1
sexe_count[r["Sexe"]] += 1
deces_count[r["Décès"]] = deces_count.get(r["Décès"], 0) + 1
sv = r["Survie"]
survie_count[sv] = survie_count.get(sv, 0) + 1
row_s = 4
for col_idx, h in enumerate(["Diagnostic retenu", "Effectif (n)", "Proportion (%)"], start=1):
cell = ws3.cell(row=row_s, column=col_idx, value=h)
cell.font = Font(name="Arial", bold=True, color="FFFFFF", size=10)
cell.fill = PatternFill("solid", fgColor=BLEU_CLAIR)
cell.alignment = center_align
cell.border = thin_border
ws3.row_dimensions[row_s].height = 20
row_s += 1
for idx, (diag, cnt) in enumerate(sorted(anomalies_count.items(), key=lambda x: -x[1])):
fill_s = alt_fill if idx % 2 == 0 else PatternFill("solid", fgColor="FFFFFF")
for col_idx, val in enumerate([diag, cnt, f"{round(100*cnt/500,1)}%"], start=1):
cell = ws3.cell(row=row_s, column=col_idx, value=val)
cell.font = Font(name="Arial", size=10)
cell.fill = fill_s
cell.border = thin_border
cell.alignment = left_align if col_idx == 1 else center_align
row_s += 1
Survie
row_s += 1
ws3.cell(row=row_s, column=1, value="Statut de survie").font = Font(name="Arial", bold=True, size=11, color=BLEU_CLAIR)
row_s += 1
for col_idx, h in enumerate(["Survie", "Effectif (n)", "Proportion (%)"], start=1):
cell = ws3.cell(row=row_s, column=col_idx, value=h)
cell.font = Font(name="Arial", bold=True, color="FFFFFF", size=10)
cell.fill = PatternFill("solid", fgColor=BLEU_CLAIR)
cell.alignment = center_align
cell.border = thin_border
row_s += 1
for idx, (sv, cnt) in enumerate(survie_count.items()):
fill_s = alt_fill if idx % 2 == 0 else PatternFill("solid", fgColor="FFFFFF")
for col_idx, val in enumerate([sv, cnt, f"{round(100*cnt/500,1)}%"], start=1):
cell = ws3.cell(row=row_s, column=col_idx, value=val)
cell.font = Font(name="Arial", size=10)
cell.fill = fill_s
cell.border = thin_border
cell.alignment = left_align if col_idx == 1 else center_align
row_s += 1
ws3.column_dimensions["A"].width = 34
ws3.column_dimensions["B"].width = 16
ws3.column_dimensions["C"].width = 16
─── SAUVEGARDE ───────────────────────────────────────────────────────────────
output_path = "/mnt/user-data/outputs/Donnees_Entrainement_500lignes.xlsx"
wb.save(output_path)
print(f"✅ Fichier généré : {output_path}")
print(f" Lignes de données : 500")
print(f" Colonnes : {len(headers)}")
print(f" Feuilles : Données | Dictionnaire des variables | Stats rapides")