Tracy André commited on
Commit
28849b3
·
1 Parent(s): d988d52
Files changed (2) hide show
  1. data_loader.py +348 -103
  2. requirements.txt +1 -1
data_loader.py CHANGED
@@ -4,10 +4,11 @@ Module de chargement des données depuis Hugging Face
4
  import os
5
  import traceback
6
  import pandas as pd
7
- from datasets import load_dataset
8
  import datasets as hf_datasets
9
  from huggingface_hub import HfApi, hf_hub_download
10
  import huggingface_hub as hf_hub
 
11
  from config import HF_TOKEN, DATASET_ID, REQUIRED_COLUMNS, MESSAGES
12
 
13
 
@@ -18,7 +19,7 @@ class DataLoader:
18
  self.df = None
19
 
20
  def load_data(self):
21
- """Charge les données du dataset Hugging Face avec gestion d'erreur robuste"""
22
  try:
23
  print(MESSAGES["loading"])
24
  print(f"📋 Dataset ID: {DATASET_ID}")
@@ -26,96 +27,33 @@ class DataLoader:
26
 
27
  self.df = None
28
 
29
- # 1) Tentative de chargement direct via datasets.load_dataset
30
- try:
31
- print("🔄 Tentative de chargement direct...")
32
- dataset = load_dataset(
33
- DATASET_ID,
34
- split="train",
35
- token=HF_TOKEN,
36
- # trust_remote_code n'est plus supporté; retiré pour éviter le warning
37
- )
38
- print(f"📊 Dataset chargé: {len(dataset)} exemples")
39
-
40
- try:
41
- self.df = dataset.to_pandas()
42
- print("✅ Conversion to_pandas() réussie")
43
- except Exception as pandas_error:
44
- print(f"❌ Erreur to_pandas(): {pandas_error}")
45
- print("🔄 Tentative de conversion manuelle...")
46
-
47
- try:
48
- data_list = []
49
- max_examples = min(len(dataset), 1000) # Limiter pour éviter les problèmes de mémoire
50
-
51
- for i, item in enumerate(dataset):
52
- if i >= max_examples:
53
- break
54
- data_list.append(item)
55
- if i < 5:
56
- print(f"📋 Exemple {i}: {list(item.keys())}")
57
-
58
- self.df = pd.DataFrame(data_list)
59
- print(f"✅ Conversion manuelle réussie: {len(self.df)} lignes")
60
- except Exception as manual_error:
61
- print(f"❌ Erreur lors de la conversion manuelle: {manual_error}")
62
- self.df = None
63
-
64
- except Exception as e:
65
- print("\n===== 🔎 Détails de l'erreur Hugging Face =====")
66
- print(f"❌ Erreur lors du chargement depuis Hugging Face: {str(e)}")
67
- print(f"❌ Type d'erreur: {type(e).__name__}")
68
- # Détails de l'exception
69
- try:
70
- print(f"❖ repr(e): {repr(e)}")
71
- if getattr(e, '__cause__', None) is not None:
72
- print(f"❖ Cause: {repr(e.__cause__)}")
73
- if getattr(e, '__context__', None) is not None:
74
- print(f"❖ Contexte: {repr(e.__context__)}")
75
- if getattr(e, 'args', None):
76
- print(f"❖ Args: {e.args}")
77
- except Exception:
78
- pass
79
- # Versions des libs pour diagnostic
80
- try:
81
- print(f"❖ datasets version: {getattr(hf_datasets, '__version__', 'unknown')}")
82
- print(f"❖ huggingface_hub version: {getattr(hf_hub, '__version__', 'unknown')}")
83
- except Exception:
84
- pass
85
- # Environnement réseau de base
86
- try:
87
- proxies = {k: v for k, v in os.environ.items() if k.lower().endswith('proxy')}
88
- print(f"❖ Proxies détectés: {proxies if proxies else 'aucun'}")
89
- except Exception:
90
- pass
91
- # Trace complète
92
- try:
93
- print("❖ Traceback complet:")
94
- print(traceback.format_exc())
95
- except Exception:
96
- pass
97
- print("===== 🔎 Fin des détails =====\n")
98
-
99
- # 2) Fallback: récupérer directement les fichiers du repo
100
- try:
101
- fallback_msg = self._fallback_load_from_repo_files()
102
- if self.df is None and fallback_msg:
103
- return f"❌ Erreur lors du chargement du dataset : {str(e)} | Fallback: {fallback_msg}"
104
- except Exception as fallback_error:
105
- print(f"❌ Erreur dans le fallback: {fallback_error}")
106
- # Continue vers le chargement local
107
-
108
- # Si on n'a toujours pas de dataframe, arrêter
109
  if self.df is None:
110
- print("⚠️ Aucune méthode de chargement n'a fonctionné")
111
- return MESSAGES["no_data"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
112
 
113
  print(f"📊 Données chargées: {len(self.df)} lignes")
114
  print(f"📊 Colonnes disponibles: {list(self.df.columns)}")
115
 
116
  # Nettoyage et validation
117
  return self._clean_and_validate_data()
118
-
119
  except Exception as e:
120
  print(f"❌ Erreur critique dans load_data: {e}")
121
  import traceback
@@ -123,23 +61,265 @@ class DataLoader:
123
  return f"❌ Erreur critique lors du chargement: {str(e)}"
124
 
125
  def _clean_and_validate_data(self):
126
- """Nettoie et valide les données chargées"""
 
 
 
 
 
 
 
127
  missing_cols = [col for col in REQUIRED_COLUMNS if col not in self.df.columns]
128
-
129
  if missing_cols:
130
  print(f"❌ Colonnes manquantes: {missing_cols}")
 
131
  self.df = None
132
  return f"❌ Colonnes manquantes: {missing_cols}"
133
 
134
- # Nettoyage
135
  initial_len = len(self.df)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
136
  self.df = self.df.dropna(subset=REQUIRED_COLUMNS)
137
 
138
- print(f"📊 Avant nettoyage: {initial_len} lignes")
139
- print(f"📊 Après nettoyage: {len(self.df)} lignes")
 
 
 
 
 
 
 
 
 
140
 
141
  return MESSAGES["success"]
142
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
143
  def _fallback_load_from_repo_files(self):
144
  """Fallback pour charger les données en téléchargeant directement les fichiers du repo HF."""
145
  try:
@@ -180,38 +360,68 @@ class DataLoader:
180
  frames = []
181
  if chosen_ext == ".parquet":
182
  for p in local_paths:
183
- frames.append(pd.read_parquet(p))
 
 
 
 
184
  elif chosen_ext == ".csv":
185
  for p in local_paths:
186
- frames.append(pd.read_csv(p))
 
 
 
 
 
187
  elif chosen_ext == ".tsv":
188
  for p in local_paths:
189
- frames.append(pd.read_csv(p, sep="\t"))
 
 
 
 
 
190
  elif chosen_ext == ".json":
191
  for p in local_paths:
192
  try:
193
- frames.append(pd.read_json(p, lines=True))
194
- except Exception:
195
- frames.append(pd.read_json(p))
 
 
 
 
196
 
197
- self.df = pd.concat(frames, ignore_index=True) if len(frames) > 1 else frames[0]
198
- print(f"✅ Fallback réussi: {len(self.df)} lignes chargées depuis les fichiers du dépôt")
199
- return None
 
 
 
 
 
200
  except Exception as e:
201
  print(f"❌ Fallback échoué: {e}")
202
  # Dernier recours: fichier local d'exemple
203
  return self._load_local_sample()
204
 
205
  def _load_local_sample(self):
206
- """Charge un fichier local de secours"""
207
  sample_path = os.path.join(os.path.dirname(__file__), "sample_data.csv")
208
  if os.path.exists(sample_path):
209
  try:
210
- self.df = pd.read_csv(sample_path)
211
- print(f"✅ Chargement du fichier local 'sample_data.csv' ({len(self.df)} lignes)")
212
- return "Chargement via fichier local de secours."
 
 
 
 
 
 
213
  except Exception as e2:
214
  print(f"❌ Échec du chargement du fichier local: {e2}")
 
215
  return "Aucune source de données disponible."
216
 
217
  def get_data(self):
@@ -221,3 +431,38 @@ class DataLoader:
221
  def has_data(self):
222
  """Vérifie si des données sont disponibles"""
223
  return self.df is not None and len(self.df) > 0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  import os
5
  import traceback
6
  import pandas as pd
7
+ from datasets import load_dataset, Dataset, DatasetDict
8
  import datasets as hf_datasets
9
  from huggingface_hub import HfApi, hf_hub_download
10
  import huggingface_hub as hf_hub
11
+ import pyarrow as pa
12
  from config import HF_TOKEN, DATASET_ID, REQUIRED_COLUMNS, MESSAGES
13
 
14
 
 
19
  self.df = None
20
 
21
  def load_data(self):
22
+ """Charge les données du dataset avec gestion robuste des erreurs Arrow."""
23
  try:
24
  print(MESSAGES["loading"])
25
  print(f"📋 Dataset ID: {DATASET_ID}")
 
27
 
28
  self.df = None
29
 
30
+ # Stratégie 1: Chargement direct Hugging Face avec gestion des erreurs Arrow
31
+ print("🔄 Stratégie 1: chargement via datasets HF avec protection Arrow")
32
+ hf_msg = self._safe_load_from_hf()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  if self.df is None:
34
+ if hf_msg:
35
+ print(f"❌ Chargement HF échoué: {hf_msg}")
36
+
37
+ # Stratégie 2: Charger directement les fichiers du repo
38
+ print("🔄 Stratégie 2: chargement via fichiers du dépôt Hugging Face")
39
+ fallback_msg = self._fallback_load_from_repo_files()
40
+ if self.df is None:
41
+ if fallback_msg:
42
+ print(f"❌ Chargement via fichiers du dépôt échoué: {fallback_msg}")
43
+
44
+ # Stratégie 3: Dernier recours avec échantillon local
45
+ print("🔄 Stratégie 3: chargement du fichier local de secours")
46
+ local_msg = self._load_local_sample()
47
+ if self.df is None:
48
+ print(f"❌ Chargement local échoué: {local_msg}")
49
+ return MESSAGES["no_data"]
50
 
51
  print(f"📊 Données chargées: {len(self.df)} lignes")
52
  print(f"📊 Colonnes disponibles: {list(self.df.columns)}")
53
 
54
  # Nettoyage et validation
55
  return self._clean_and_validate_data()
56
+
57
  except Exception as e:
58
  print(f"❌ Erreur critique dans load_data: {e}")
59
  import traceback
 
61
  return f"❌ Erreur critique lors du chargement: {str(e)}"
62
 
63
  def _clean_and_validate_data(self):
64
+ """Nettoie et valide les données chargées avec validation robuste"""
65
+ if self.df is None or self.df.empty:
66
+ print("❌ DataFrame vide ou None")
67
+ return "❌ Aucune donnée à valider"
68
+
69
+ print(f"📊 Validation des données: {len(self.df)} lignes, {len(self.df.columns)} colonnes")
70
+
71
+ # Vérification des colonnes requises
72
  missing_cols = [col for col in REQUIRED_COLUMNS if col not in self.df.columns]
 
73
  if missing_cols:
74
  print(f"❌ Colonnes manquantes: {missing_cols}")
75
+ print(f"📊 Colonnes disponibles: {list(self.df.columns)}")
76
  self.df = None
77
  return f"❌ Colonnes manquantes: {missing_cols}"
78
 
79
+ # Validation et nettoyage des colonnes requises
80
  initial_len = len(self.df)
81
+
82
+ # Nettoyer chaque colonne requise spécifiquement
83
+ for col in REQUIRED_COLUMNS:
84
+ if col in self.df.columns:
85
+ original_count = self.df[col].notna().sum()
86
+
87
+ if col == 'millesime':
88
+ # Validation spéciale pour millesime (doit être une année valide)
89
+ self.df[col] = self._validate_year_column(self.df[col])
90
+ elif col == 'surfparc':
91
+ # Validation spéciale pour surfparc (doit être un nombre positif)
92
+ self.df[col] = self._validate_numeric_positive(self.df[col])
93
+ elif col == 'numparcell':
94
+ # Validation pour numéro de parcelle (string non vide)
95
+ self.df[col] = self._validate_string_column(self.df[col])
96
+
97
+ valid_count = self.df[col].notna().sum()
98
+ if valid_count < original_count:
99
+ print(f"📊 Colonne {col}: {original_count} → {valid_count} valeurs valides")
100
+
101
+ # Supprimer les lignes avec des valeurs manquantes dans les colonnes requises
102
  self.df = self.df.dropna(subset=REQUIRED_COLUMNS)
103
 
104
+ # Validation supplémentaire
105
+ self.df = self._additional_data_validation(self.df)
106
+
107
+ final_len = len(self.df)
108
+ print(f"📊 Avant validation: {initial_len} lignes")
109
+ print(f"📊 Après validation: {final_len} lignes")
110
+
111
+ if final_len == 0:
112
+ print("❌ Aucune ligne valide après nettoyage")
113
+ self.df = None
114
+ return "❌ Aucune ligne valide après nettoyage"
115
 
116
  return MESSAGES["success"]
117
 
118
+ def _validate_year_column(self, series):
119
+ """Valide une colonne d'année (entre 1990 et 2030)"""
120
+ try:
121
+ numeric_series = pd.to_numeric(series, errors='coerce')
122
+ # Filtrer les années valides
123
+ valid_mask = (numeric_series >= 1990) & (numeric_series <= 2030)
124
+ result = numeric_series.where(valid_mask)
125
+ return result
126
+ except Exception:
127
+ return series
128
+
129
+ def _validate_numeric_positive(self, series):
130
+ """Valide une colonne numérique positive"""
131
+ try:
132
+ numeric_series = pd.to_numeric(series, errors='coerce')
133
+ # Filtrer les valeurs positives
134
+ valid_mask = numeric_series > 0
135
+ result = numeric_series.where(valid_mask)
136
+ return result
137
+ except Exception:
138
+ return series
139
+
140
+ def _validate_string_column(self, series):
141
+ """Valide une colonne string (non vide, non null)"""
142
+ try:
143
+ # Convertir en string et nettoyer
144
+ string_series = series.astype(str).str.strip()
145
+ # Remplacer les valeurs vides par NaN
146
+ string_series = string_series.replace(['', 'nan', 'null', 'NULL', 'None'], None)
147
+ return string_series
148
+ except Exception:
149
+ return series
150
+
151
+ def _additional_data_validation(self, df):
152
+ """Validations supplémentaires sur le DataFrame"""
153
+ if df is None or df.empty:
154
+ return df
155
+
156
+ try:
157
+ # Supprimer les doublons complets
158
+ initial_len = len(df)
159
+ df = df.drop_duplicates()
160
+ if len(df) < initial_len:
161
+ print(f"📊 {initial_len - len(df)} doublons supprimés")
162
+
163
+ # Nettoyer les colonnes texte problématiques
164
+ for col in df.columns:
165
+ if df[col].dtype == 'object':
166
+ # Supprimer les lignes avec des valeurs contenant uniquement des caractères spéciaux
167
+ problematic_mask = df[col].astype(str).str.match(r'^[^\w\s]*$', na=False)
168
+ if problematic_mask.any():
169
+ print(f"📊 {problematic_mask.sum()} lignes avec caractères problématiques dans {col}")
170
+ df.loc[problematic_mask, col] = None
171
+
172
+ return df
173
+
174
+ except Exception as e:
175
+ print(f"❌ Erreur validation supplémentaire: {e}")
176
+ return df
177
+
178
+ def _safe_load_from_hf(self):
179
+ """Charge les données depuis Hugging Face avec gestion des erreurs Arrow."""
180
+ try:
181
+ # Tentative de chargement standard
182
+ print("🔄 Tentative de chargement standard...")
183
+ dataset = load_dataset(DATASET_ID, token=HF_TOKEN, trust_remote_code=True)
184
+
185
+ # Conversion en DataFrame avec gestion des types
186
+ if isinstance(dataset, DatasetDict):
187
+ # Prendre le premier split disponible
188
+ split_name = list(dataset.keys())[0]
189
+ hf_dataset = dataset[split_name]
190
+ else:
191
+ hf_dataset = dataset
192
+
193
+ self.df = self._safe_convert_to_pandas(hf_dataset)
194
+ if self.df is not None:
195
+ print(f"✅ Chargement standard réussi: {len(self.df)} lignes")
196
+ return None
197
+ else:
198
+ return "Conversion en DataFrame échouée"
199
+
200
+ except Exception as e:
201
+ error_msg = str(e)
202
+ print(f"❌ Erreur lors du chargement depuis Hugging Face: {error_msg}")
203
+
204
+ # Logging détaillé pour les erreurs Arrow
205
+ if "ArrowInvalid" in error_msg or "Failed to parse string" in error_msg:
206
+ print(f"❌ Type d'erreur: {type(e).__name__}")
207
+ print(f"❖ repr(e): {repr(e)}")
208
+ if hasattr(e, '__cause__') and e.__cause__:
209
+ print(f"❖ Cause: {e.__cause__}")
210
+ if hasattr(e, '__context__') and e.__context__:
211
+ print(f"❖ Contexte: {e.__context__}")
212
+ print(f"❖ Args: {e.args}")
213
+ print(f"❖ datasets version: {hf_datasets.__version__}")
214
+ print(f"❖ huggingface_hub version: {hf_hub.__version__}")
215
+ print(f"❖ Proxies détectés: {os.environ.get('HTTP_PROXY', 'aucun')}")
216
+ print("❖ Traceback complet:")
217
+ traceback.print_exc()
218
+
219
+ return f"Erreur Arrow/parsing: {error_msg}"
220
+
221
+ def _safe_convert_to_pandas(self, hf_dataset):
222
+ """Convertit un dataset HF en DataFrame pandas avec gestion sécurisée des types."""
223
+ try:
224
+ # Méthode 1: Conversion directe
225
+ print("🔄 Tentative de conversion directe...")
226
+ df = hf_dataset.to_pandas()
227
+ return self._clean_data_types(df)
228
+
229
+ except Exception as e1:
230
+ print(f"❌ Conversion directe échouée: {e1}")
231
+
232
+ try:
233
+ # Méthode 2: Via Arrow Table avec schéma modifié
234
+ print("🔄 Tentative via Arrow Table avec schéma string...")
235
+ arrow_table = hf_dataset.data.table
236
+
237
+ # Créer un nouveau schéma avec tous les champs en string
238
+ string_schema = self._create_string_schema(arrow_table.schema)
239
+
240
+ # Convertir les données en utilisant le schéma string
241
+ string_table = arrow_table.cast(string_schema)
242
+ df = string_table.to_pandas()
243
+
244
+ return self._clean_data_types(df)
245
+
246
+ except Exception as e2:
247
+ print(f"❌ Conversion via Arrow échouée: {e2}")
248
+
249
+ try:
250
+ # Méthode 3: Chargement ligne par ligne
251
+ print("🔄 Tentative de chargement ligne par ligne...")
252
+ rows = []
253
+ for i, row in enumerate(hf_dataset):
254
+ if i >= 10000: # Limite pour éviter les timeouts
255
+ break
256
+ # Convertir toutes les valeurs en string pour éviter les erreurs de type
257
+ safe_row = {k: str(v) if v is not None else None for k, v in row.items()}
258
+ rows.append(safe_row)
259
+
260
+ if rows:
261
+ df = pd.DataFrame(rows)
262
+ return self._clean_data_types(df)
263
+
264
+ except Exception as e3:
265
+ print(f"❌ Chargement ligne par ligne échoué: {e3}")
266
+
267
+ return None
268
+
269
+ def _create_string_schema(self, original_schema):
270
+ """Crée un schéma Arrow où tous les types sont convertis en string."""
271
+ fields = []
272
+ for field in original_schema:
273
+ # Convertir tous les types en string pour éviter les erreurs de parsing
274
+ string_field = pa.field(field.name, pa.string(), nullable=True)
275
+ fields.append(string_field)
276
+ return pa.schema(fields)
277
+
278
+ def _clean_data_types(self, df):
279
+ """Nettoie et convertit les types de données du DataFrame."""
280
+ if df is None or df.empty:
281
+ return None
282
+
283
+ print("🔄 Nettoyage et conversion des types...")
284
+
285
+ # Colonnes numériques connues à convertir
286
+ numeric_columns = ['surfparc', 'millesime', 'quantitetot', 'neffqte', 'peffqte',
287
+ 'kqte', 'teneurn', 'teneurp', 'teneurk', 'keq', 'volumebo']
288
+
289
+ for col in numeric_columns:
290
+ if col in df.columns:
291
+ df[col] = self._safe_numeric_conversion(df[col])
292
+
293
+ # Nettoyer les valeurs problématiques
294
+ for col in df.columns:
295
+ if df[col].dtype == 'object':
296
+ # Remplacer les valeurs vides problématiques
297
+ df[col] = df[col].replace(['', 'null', 'NULL', 'None', 'nan'], None)
298
+ # Nettoyer les chaînes avec des caractères problématiques
299
+ df[col] = df[col].astype(str).replace(r'^[^\w\s-]+$', '', regex=True)
300
+ df[col] = df[col].replace('nan', None)
301
+
302
+ return df
303
+
304
+ def _safe_numeric_conversion(self, series):
305
+ """Convertit une série en numérique de manière sécurisée."""
306
+ try:
307
+ # Nettoyer d'abord les valeurs non-numériques
308
+ cleaned = series.astype(str).str.strip()
309
+ cleaned = cleaned.replace(['', 'null', 'NULL', 'None', 'nan', '-'], None)
310
+
311
+ # Supprimer les caractères non-numériques (sauf . et -)
312
+ cleaned = cleaned.str.replace(r'[^\d.-]', '', regex=True)
313
+
314
+ # Convertir en numérique
315
+ numeric_series = pd.to_numeric(cleaned, errors='coerce')
316
+
317
+ return numeric_series
318
+
319
+ except Exception as e:
320
+ print(f"❌ Erreur conversion numérique pour {series.name}: {e}")
321
+ return series
322
+
323
  def _fallback_load_from_repo_files(self):
324
  """Fallback pour charger les données en téléchargeant directement les fichiers du repo HF."""
325
  try:
 
360
  frames = []
361
  if chosen_ext == ".parquet":
362
  for p in local_paths:
363
+ try:
364
+ df = pd.read_parquet(p)
365
+ frames.append(self._clean_data_types(df))
366
+ except Exception as e:
367
+ print(f"❌ Erreur lecture parquet {p}: {e}")
368
  elif chosen_ext == ".csv":
369
  for p in local_paths:
370
+ try:
371
+ # Lecture avec tous les types en string pour éviter les erreurs de parsing
372
+ df = pd.read_csv(p, dtype=str, na_values=['', 'NULL', 'null', 'None'])
373
+ frames.append(self._clean_data_types(df))
374
+ except Exception as e:
375
+ print(f"❌ Erreur lecture CSV {p}: {e}")
376
  elif chosen_ext == ".tsv":
377
  for p in local_paths:
378
+ try:
379
+ # Lecture avec tous les types en string pour éviter les erreurs de parsing
380
+ df = pd.read_csv(p, sep="\t", dtype=str, na_values=['', 'NULL', 'null', 'None'])
381
+ frames.append(self._clean_data_types(df))
382
+ except Exception as e:
383
+ print(f"❌ Erreur lecture TSV {p}: {e}")
384
  elif chosen_ext == ".json":
385
  for p in local_paths:
386
  try:
387
+ try:
388
+ df = pd.read_json(p, lines=True, dtype=str)
389
+ except Exception:
390
+ df = pd.read_json(p, dtype=str)
391
+ frames.append(self._clean_data_types(df))
392
+ except Exception as e:
393
+ print(f"❌ Erreur lecture JSON {p}: {e}")
394
 
395
+ # Filtrer les frames None et concaténer
396
+ valid_frames = [f for f in frames if f is not None and not f.empty]
397
+ if valid_frames:
398
+ self.df = pd.concat(valid_frames, ignore_index=True) if len(valid_frames) > 1 else valid_frames[0]
399
+ print(f"✅ Fallback réussi: {len(self.df)} lignes chargées depuis les fichiers du dépôt")
400
+ return None
401
+ else:
402
+ return "Aucun fichier valide trouvé"
403
  except Exception as e:
404
  print(f"❌ Fallback échoué: {e}")
405
  # Dernier recours: fichier local d'exemple
406
  return self._load_local_sample()
407
 
408
  def _load_local_sample(self):
409
+ """Charge un fichier local de secours avec conversion sécurisée"""
410
  sample_path = os.path.join(os.path.dirname(__file__), "sample_data.csv")
411
  if os.path.exists(sample_path):
412
  try:
413
+ # Lecture avec tous les types en string pour éviter les erreurs
414
+ df = pd.read_csv(sample_path, dtype=str, na_values=['', 'NULL', 'null', 'None'])
415
+ self.df = self._clean_data_types(df)
416
+ if self.df is not None and not self.df.empty:
417
+ print(f"✅ Chargement du fichier local 'sample_data.csv' ({len(self.df)} lignes)")
418
+ return "Chargement via fichier local de secours."
419
+ else:
420
+ print("❌ Fichier local vide après nettoyage")
421
+ return "Fichier local vide après nettoyage"
422
  except Exception as e2:
423
  print(f"❌ Échec du chargement du fichier local: {e2}")
424
+ return f"Erreur fichier local: {str(e2)}"
425
  return "Aucune source de données disponible."
426
 
427
  def get_data(self):
 
431
  def has_data(self):
432
  """Vérifie si des données sont disponibles"""
433
  return self.df is not None and len(self.df) > 0
434
+
435
+ def test_arrow_resilience(self):
436
+ """Teste la résilience aux erreurs Arrow avec des données problématiques"""
437
+ print("🧪 Test de résilience aux erreurs Arrow...")
438
+
439
+ # Créer un DataFrame de test avec des valeurs problématiques
440
+ test_data = {
441
+ 'numparcell': ['P001', 'P002', 'Coué - ', ''],
442
+ 'surfparc': [1.5, 2.0, 'Coué - ', '0'],
443
+ 'millesime': [2014, 2015, 'Coué - ', 'invalid'],
444
+ 'problematic_col': ['Coué - ', 'Normal', '', 'null']
445
+ }
446
+
447
+ original_df = pd.DataFrame(test_data)
448
+ print(f"📊 Données de test créées: {len(original_df)} lignes")
449
+ print("📊 Données problématiques incluses: 'Coué - ', chaînes vides, valeurs invalides")
450
+
451
+ # Appliquer le nettoyage
452
+ cleaned_df = self._clean_data_types(original_df.copy())
453
+
454
+ if cleaned_df is not None:
455
+ print(f"✅ Nettoyage réussi: {len(cleaned_df)} lignes")
456
+ print("📊 Types après nettoyage:")
457
+ for col in cleaned_df.columns:
458
+ print(f" - {col}: {cleaned_df[col].dtype}")
459
+
460
+ # Vérifier les colonnes numériques
461
+ if 'surfparc' in cleaned_df.columns:
462
+ valid_numeric = cleaned_df['surfparc'].notna().sum()
463
+ print(f"📊 Valeurs numériques valides dans surfparc: {valid_numeric}")
464
+
465
+ return True
466
+ else:
467
+ print("❌ Échec du test de nettoyage")
468
+ return False
requirements.txt CHANGED
@@ -9,4 +9,4 @@ scikit-learn>=1.0.0
9
  datasets>=2.0.0
10
  huggingface_hub>=0.16.0
11
  pyarrow>=14.0.0
12
- audioop-lts>=0.2.1; python_version >= "3.13"
 
9
  datasets>=2.0.0
10
  huggingface_hub>=0.16.0
11
  pyarrow>=14.0.0
12
+ audioop-lts>=0.2.1