Spaces:
Running
Running
File size: 8,644 Bytes
bad6218 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 | """Pydantic models for the Indicateurs Territoriaux API responses."""
from typing import Any
from pydantic import BaseModel, Field
class IndicatorMetadata(BaseModel):
"""Metadata for a territorial indicator."""
id: int = Field(..., description="Unique identifier of the indicator")
libelle: str = Field(..., description="Human-readable name of the indicator")
unite: str | None = Field(None, description="Unit of measurement")
description: str | None = Field(None, description="Detailed description")
methode_calcul: str | None = Field(None, description="Calculation method")
fonction_calcul: str | None = Field(None, description="Calculation function")
date_debut: int | None = Field(None, description="First available year")
date_fin: int | None = Field(None, description="Last available year")
annees_disponibles: str | None = Field(
None, description="Available years (comma-separated)"
)
annees_manquantes: str | None = Field(
None, description="Missing years (comma-separated)"
)
mailles_disponibles: str | None = Field(
None, description="Available geographic levels (e.g., 'region,departement,epci')"
)
maille_mini_disponible: str | None = Field(
None, description="Finest available geographic level"
)
couverture_geographique: str | None = Field(
None, description="Geographic coverage (France métro, DOM, etc.)"
)
liste_drom: str | None = Field(None, description="Covered DROM territories")
completion_region: float | None = Field(
None, description="Completion percentage at region level"
)
completion_departement: float | None = Field(
None, description="Completion percentage at department level"
)
completion_epci: float | None = Field(
None, description="Completion percentage at EPCI level"
)
completion_commune: float | None = Field(
None, description="Completion percentage at commune level"
)
compte_region: int | None = Field(
None, description="Number of regions with data"
)
compte_departement: int | None = Field(
None, description="Number of departments with data"
)
compte_epci: int | None = Field(None, description="Number of EPCIs with data")
compte_commune: int | None = Field(
None, description="Number of communes with data"
)
thematique_fnv: str | None = Field(
None, description="France Nation Verte thematic"
)
secteur_fnv: str | None = Field(None, description="FNV sector")
enjeux_fnv: str | None = Field(None, description="FNV challenges")
levier_fnv: str | None = Field(None, description="FNV lever")
projets_associes: str | None = Field(None, description="Associated projects")
valeur_axes: str | None = Field(
None, description="Breakdown axes (JSON stringified)"
)
@classmethod
def from_api_response(cls, data: dict[str, Any]) -> "IndicatorMetadata":
"""Create an IndicatorMetadata from a Cube.js API response row.
The API returns dimension names prefixed with the cube name.
This method strips the prefix.
"""
# Strip the cube name prefix from keys
prefix = "indicateur_metadata."
cleaned = {}
for key, value in data.items():
clean_key = key.replace(prefix, "")
cleaned[clean_key] = value
return cls(**cleaned)
def has_geographic_level(self, level: str) -> bool:
"""Check if the indicator has data at the specified geographic level."""
if not self.mailles_disponibles:
return False
return level.lower() in self.mailles_disponibles.lower()
def get_completion_for_level(self, level: str) -> float | None:
"""Get the completion percentage for a geographic level."""
level_map = {
"region": self.completion_region,
"departement": self.completion_departement,
"epci": self.completion_epci,
"commune": self.completion_commune,
}
return level_map.get(level.lower())
class SourceMetadata(BaseModel):
"""Metadata for a data source associated with an indicator."""
id_indicateur: int = Field(..., description="ID of the related indicator")
nom_source: str | None = Field(None, description="Source identifier")
libelle: str | None = Field(None, description="Human-readable source name")
description: str | None = Field(None, description="Source description")
producteur_source: str | None = Field(None, description="Data producer")
distributeur_source: str | None = Field(None, description="Data distributor")
license_source: str | None = Field(None, description="Data license")
lien_page: str | None = Field(None, description="Source URL")
annees_disponibles_source: str | None = Field(
None, description="Available years from this source"
)
annees_manquantes_source: str | None = Field(
None, description="Missing years from this source"
)
maille_mini_disponible: str | None = Field(
None, description="Finest geographic level"
)
couverture_geographique: str | None = Field(
None, description="Geographic coverage"
)
date_derniere_extraction: str | None = Field(
None, description="Last extraction date"
)
@classmethod
def from_api_response(cls, data: dict[str, Any]) -> "SourceMetadata":
"""Create a SourceMetadata from a Cube.js API response row."""
prefix = "indicateur_x_source_metadata."
cleaned = {}
for key, value in data.items():
clean_key = key.replace(prefix, "")
cleaned[clean_key] = value
return cls(**cleaned)
class IndicatorListItem(BaseModel):
"""Simplified indicator info for list responses."""
id: int
libelle: str
unite: str | None = None
mailles_disponibles: str | None = None
thematique_fnv: str | None = None
class IndicatorDetails(BaseModel):
"""Complete indicator details with sources."""
metadata: IndicatorMetadata
sources: list[SourceMetadata] = Field(default_factory=list)
class GeographicDataPoint(BaseModel):
"""A single data point with geographic information."""
geocode: str = Field(..., description="INSEE code of the territory")
libelle: str | None = Field(None, description="Name of the territory")
valeur: float | str | None = Field(None, description="Indicator value")
annee: str | None = Field(None, description="Year of the data")
unite: str | None = Field(None, description="Unit of measurement")
class QueryResult(BaseModel):
"""Result of a data query."""
indicator_id: int
indicator_name: str
geographic_level: str
data: list[GeographicDataPoint]
total_count: int = 0
query_info: dict[str, Any] = Field(default_factory=dict)
class SearchResult(BaseModel):
"""Result of an indicator search."""
indicators: list[IndicatorListItem]
query: str
total_count: int
class CubeInfo(BaseModel):
"""Information about a data cube."""
name: str = Field(..., description="Cube name (e.g., 'conso_enaf_com')")
maille: str = Field(..., description="Geographic level (commune, epci, departement, region)")
indicator_ids: list[int] = Field(default_factory=list, description="Indicator IDs in this cube")
# Geographic level constants
GEOGRAPHIC_LEVELS = ["region", "departement", "epci", "commune"]
# Maille suffix mapping for cube names
MAILLE_SUFFIX_MAP = {
"commune": "_com",
"epci": "_epci",
"departement": "_dpt",
"region": "_reg",
}
# Dimension patterns for each geographic level (validated by API tests)
# Format: geocode_{maille} and libelle_{maille}
GEO_DIMENSION_PATTERNS = {
"region": {
"geocode": "geocode_region",
"libelle": "libelle_region",
},
"departement": {
"geocode": "geocode_departement",
"libelle": "libelle_departement",
},
"epci": {
"geocode": "geocode_epci",
"libelle": "libelle_epci",
},
"commune": {
"geocode": "geocode_commune",
"libelle": "libelle_commune",
},
}
# Region code reference
REGION_CODES = {
"11": "Île-de-France",
"24": "Centre-Val de Loire",
"27": "Bourgogne-Franche-Comté",
"28": "Normandie",
"32": "Hauts-de-France",
"44": "Grand Est",
"52": "Pays de la Loire",
"53": "Bretagne",
"75": "Nouvelle-Aquitaine",
"76": "Occitanie",
"84": "Auvergne-Rhône-Alpes",
"93": "Provence-Alpes-Côte d'Azur",
"94": "Corse",
}
|