169 lines
6.1 KiB
Python
169 lines
6.1 KiB
Python
from fastapi import FastAPI, HTTPException
|
|
from fastapi.middleware.cors import CORSMiddleware
|
|
import pandas as pd
|
|
import os
|
|
from typing import Optional
|
|
|
|
app = FastAPI()
|
|
|
|
app.add_middleware(
|
|
CORSMiddleware,
|
|
allow_origins=["*"],
|
|
allow_methods=["*"],
|
|
allow_headers=["*"],
|
|
)
|
|
|
|
DATA_PATH = "/data/prenoms.csv"
|
|
df_cache = None
|
|
autocomplete_cache = None
|
|
alltime_cache = None # total all-time par (prenom, sexe)
|
|
|
|
def get_df():
|
|
global df_cache, autocomplete_cache
|
|
if df_cache is None:
|
|
if not os.path.exists(DATA_PATH):
|
|
raise HTTPException(status_code=503, detail="Fichier prenoms.csv non trouvé dans /data/")
|
|
df_cache = pd.read_csv(DATA_PATH, sep=";", dtype={"sexe": int, "prenom": str, "periode": int, "valeur": int})
|
|
df_cache = df_cache[df_cache["prenom"] != "_PRENOMS_RARES"]
|
|
df_cache["longueur"] = df_cache["prenom"].str.len()
|
|
# Précalcul all-time total (utilisé par /api/top)
|
|
global alltime_cache
|
|
alltime_cache = df_cache.groupby(["prenom", "sexe"])["valeur"].sum().reset_index()
|
|
alltime_cache.columns = ["prenom", "sexe", "total"]
|
|
# Build autocomplete index: prenom -> total all-time, sexe majority
|
|
agg = df_cache.groupby(["prenom", "sexe"])["valeur"].sum().reset_index()
|
|
totals = agg.groupby("prenom")["valeur"].sum().reset_index()
|
|
totals.columns = ["prenom", "total"]
|
|
# dominant sexe
|
|
dom = agg.loc[agg.groupby("prenom")["valeur"].idxmax(), ["prenom","sexe"]]
|
|
merged = totals.merge(dom, on="prenom")
|
|
autocomplete_cache = merged.sort_values("total", ascending=False).reset_index(drop=True)
|
|
return df_cache
|
|
|
|
def get_autocomplete():
|
|
get_df()
|
|
return autocomplete_cache
|
|
|
|
@app.get("/api/status")
|
|
def status():
|
|
if os.path.exists(DATA_PATH):
|
|
try:
|
|
df = get_df()
|
|
return {"ok": True, "rows": len(df), "prenoms": df["prenom"].nunique()}
|
|
except Exception as e:
|
|
return {"ok": False, "error": str(e)}
|
|
return {"ok": False, "error": "Fichier manquant"}
|
|
|
|
@app.get("/api/autocomplete")
|
|
def autocomplete(q: str, limit: int = 8):
|
|
ac = get_autocomplete()
|
|
prefix = q.upper().strip()
|
|
if not prefix:
|
|
return []
|
|
matches = ac[ac["prenom"].str.startswith(prefix)].head(limit)
|
|
return matches.to_dict(orient="records")
|
|
|
|
@app.get("/api/evolution/{prenom}")
|
|
def evolution(prenom: str):
|
|
df = get_df()
|
|
prenom = prenom.upper()
|
|
sub = df[df["prenom"] == prenom].groupby(["periode", "sexe"])["valeur"].sum().reset_index()
|
|
if sub.empty:
|
|
raise HTTPException(status_code=404, detail=f"Prénom '{prenom}' introuvable")
|
|
return sub.to_dict(orient="records")
|
|
|
|
@app.get("/api/compare")
|
|
def compare(a: str, b: str):
|
|
df = get_df()
|
|
result = {}
|
|
for prenom in [a.upper(), b.upper()]:
|
|
sub = df[df["prenom"] == prenom].groupby(["periode", "sexe"])["valeur"].sum().reset_index()
|
|
result[prenom] = sub.to_dict(orient="records")
|
|
return result
|
|
|
|
@app.get("/api/heatmap/{prenom}")
|
|
def heatmap(prenom: str):
|
|
"""Returns total births per decade per sexe"""
|
|
df = get_df()
|
|
prenom = prenom.upper()
|
|
sub = df[df["prenom"] == prenom].copy()
|
|
if sub.empty:
|
|
raise HTTPException(status_code=404, detail=f"Prénom '{prenom}' introuvable")
|
|
sub["decade"] = (sub["periode"] // 10) * 10
|
|
agg = sub.groupby(["decade", "sexe"])["valeur"].sum().reset_index()
|
|
return agg.to_dict(orient="records")
|
|
|
|
@app.get("/api/search")
|
|
def search(
|
|
q: Optional[str] = None,
|
|
sexe: Optional[int] = None,
|
|
longueur_min: int = 1,
|
|
longueur_max: int = 20,
|
|
popularite_min: int = 0,
|
|
popularite_max: int = 10000000,
|
|
peak_min: Optional[int] = None,
|
|
peak_max: Optional[int] = None,
|
|
periode_min: int = 1900,
|
|
periode_max: int = 2024,
|
|
limit: int = 50
|
|
):
|
|
df = get_df()
|
|
|
|
# Total all-time (pour popularité et affichage)
|
|
agg_all = df.groupby(["prenom", "sexe", "longueur"])["valeur"].sum().reset_index()
|
|
agg_all.columns = ["prenom", "sexe", "longueur", "total"]
|
|
|
|
# Total sur la période choisie (pour filtrer les prénoms actifs sur cette période)
|
|
sub = df[(df["periode"] >= periode_min) & (df["periode"] <= periode_max)]
|
|
agg_period = sub.groupby(["prenom", "sexe"])["valeur"].sum().reset_index()
|
|
agg_period.columns = ["prenom", "sexe", "total_periode"]
|
|
|
|
# Garder seulement les prénoms présents sur la période (au moins 1 naissance)
|
|
agg = agg_all.merge(agg_period, on=["prenom", "sexe"], how="inner")
|
|
|
|
if sexe is not None:
|
|
agg = agg[agg["sexe"] == sexe]
|
|
agg = agg[(agg["longueur"] >= longueur_min) & (agg["longueur"] <= longueur_max)]
|
|
if q:
|
|
agg = agg[agg["prenom"].str.startswith(q.upper())]
|
|
|
|
# Popularité filtrée sur total all-time
|
|
agg = agg[(agg["total"] >= popularite_min) & (agg["total"] <= popularite_max)]
|
|
|
|
agg = agg.sort_values("total_periode", ascending=False).head(limit)
|
|
return agg.to_dict(orient="records")
|
|
|
|
@app.get("/api/top")
|
|
def top(
|
|
sexe: Optional[int] = None,
|
|
periode_min: int = 1900,
|
|
periode_max: int = 2024,
|
|
order: str = "desc",
|
|
page: int = 1,
|
|
per_page: int = 50
|
|
):
|
|
df = get_df() # assure que alltime_cache est initialisé
|
|
# Total sur la période (pour tri)
|
|
sub = df[(df["periode"] >= periode_min) & (df["periode"] <= periode_max)]
|
|
agg_period = sub.groupby(["prenom", "sexe"])["valeur"].sum().reset_index()
|
|
agg_period.columns = ["prenom", "sexe", "total_periode"]
|
|
# Joindre avec all-time (précalculé)
|
|
agg = alltime_cache.merge(agg_period, on=["prenom","sexe"], how="inner")
|
|
if sexe is not None:
|
|
agg = agg[agg["sexe"] == sexe]
|
|
agg = agg.sort_values("total_periode", ascending=(order=="asc"))
|
|
total_count = len(agg)
|
|
offset = (page - 1) * per_page
|
|
page_data = agg.iloc[offset:offset+per_page].copy()
|
|
return {"total": total_count, "page": page, "per_page": per_page, "data": page_data.to_dict(orient="records")}
|
|
|
|
@app.get("/api/stats")
|
|
def stats():
|
|
df = get_df()
|
|
return {
|
|
"total_naissances": int(df["valeur"].sum()),
|
|
"nb_prenoms": int(df["prenom"].nunique()),
|
|
"periode_min": int(df["periode"].min()),
|
|
"periode_max": int(df["periode"].max()),
|
|
}
|