Initial commit
This commit is contained in:
@@ -0,0 +1,168 @@
|
||||
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()),
|
||||
}
|
||||
Reference in New Issue
Block a user