mannnon commited on
Commit
b0ebdad
·
verified ·
1 Parent(s): ad672a3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +186 -61
app.py CHANGED
@@ -291,50 +291,33 @@ def extract_text_from_pdf(uploaded_pdf) -> str:
291
  text_parts.append(txt)
292
  return "\n".join(text_parts)
293
 
 
 
 
 
 
 
 
 
 
 
294
 
295
- def find_float_after_label(text: str, label: str, max_numbers: int = 2):
296
- idx = text.lower().find(label.lower())
297
  if idx == -1:
298
  return []
299
- snippet = text[idx: idx + 500]
 
 
300
  nums = re.findall(r"(\d+,\d+|\d+\.\d+|\d+)", snippet)
301
  out = []
302
  for n in nums[:max_numbers]:
303
- out.append(float(n.replace(",", ".")))
 
 
 
304
  return out
305
 
306
 
307
- def extract_pdf_name(text: str):
308
- m = re.search(r"Personne:\s*([A-Za-zÀ-ÿ\- ]+),\s*\d{2}/\d{2}/\d{4}", text)
309
- if m:
310
- return m.group(1).strip()
311
- return None
312
-
313
-
314
- def estimate_attack_from_pdf(pdf_data: dict):
315
- heel_peak_t = avg(pdf_data.get("heel_peak_time_pct_g"), pdf_data.get("heel_peak_time_pct_d"))
316
- fore_peak_t = avg(pdf_data.get("fore_peak_time_pct_g"), pdf_data.get("fore_peak_time_pct_d"))
317
- heel_force = avg(pdf_data.get("heel_force_g"), pdf_data.get("heel_force_d"))
318
- fore_force = avg(pdf_data.get("fore_force_g"), pdf_data.get("fore_force_d"))
319
- transition = avg(pdf_data.get("transition_g"), pdf_data.get("transition_d"))
320
-
321
- if pd.notna(heel_peak_t) and pd.notna(fore_peak_t):
322
- if heel_peak_t <= 12 and pd.notna(transition) and transition >= 0.055:
323
- return "attaque talon"
324
- if heel_peak_t > 15 and fore_peak_t < 50:
325
- return "attaque avant-pied"
326
-
327
- if pd.notna(heel_force) and pd.notna(fore_force) and fore_force != 0:
328
- ratio = heel_force / fore_force
329
- if ratio > 1.05 and pd.notna(transition) and transition >= 0.055:
330
- return "attaque talon"
331
- if ratio < 0.90 and pd.notna(transition) and transition <= 0.055:
332
- return "attaque avant-pied"
333
- return "attaque médio-pied"
334
-
335
- return "indéterminée"
336
-
337
-
338
  def parse_zebris_pdf(uploaded_pdf):
339
  text = extract_text_from_pdf(uploaded_pdf)
340
  athlete_name = extract_pdf_name(text)
@@ -364,53 +347,195 @@ def parse_zebris_pdf(uploaded_pdf):
364
  "fore_peak_time_pct_d": np.nan,
365
  }
366
 
367
- vals = find_float_after_label(text, "Instant du passage du talon vers\nl'avant-pied, s", 2)
 
 
 
 
 
 
 
368
  if len(vals) >= 2:
369
  data["transition_g"], data["transition_d"] = vals[0], vals[1]
 
 
 
 
 
 
 
 
370
 
371
- vals = find_float_after_label(text, "Force maximale, N\nForefoot (Three zones)", 2)
 
 
 
372
  if len(vals) >= 2:
373
  data["fore_force_g"], data["fore_force_d"] = vals[0], vals[1]
 
 
 
 
374
 
375
- vals = find_float_after_label(text, "Midfoot (Three zones)", 2)
376
  if len(vals) >= 2:
377
- # attention : la première occurrence de Midfoot peut venir de la force ou d'une autre section
378
- if pd.isna(data["mid_force_g"]):
379
- data["mid_force_g"], data["mid_force_d"] = vals[0], vals[1]
380
 
381
- vals = find_float_after_label(text, "Heel (Three zones)", 2)
382
  if len(vals) >= 2:
383
- if pd.isna(data["heel_force_g"]):
384
- data["heel_force_g"], data["heel_force_d"] = vals[0], vals[1]
385
-
386
- vals = find_float_after_label(text, "Pression maximale, N/cm²\nForefoot (Three zones)", 2)
 
 
 
 
387
  if len(vals) >= 2:
388
  data["fore_pressure_g"], data["fore_pressure_d"] = vals[0], vals[1]
389
-
390
- vals = find_float_after_label(text, "Pression maximale, N/cm²\nMidfoot (Three zones)", 2)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
391
  if len(vals) >= 2:
392
- data["mid_pressure_g"], data["mid_pressure_d"] = vals[0], vals[1]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
393
 
394
- vals = find_float_after_label(text, "Pression maximale, N/cm²\nHeel (Three zones)", 2)
395
- if len(vals) >= 2:
396
- data["heel_pressure_g"], data["heel_pressure_d"] = vals[0], vals[1]
397
 
398
- vals = find_float_after_label(text, "Instant pic de force, % de phase d'appui\nForefoot (Three zones)", 2)
399
- if len(vals) >= 2:
400
- data["fore_peak_time_pct_g"], data["fore_peak_time_pct_d"] = vals[0], vals[1]
401
 
402
- vals = find_float_after_label(text, "Instant pic de force, % de phase d'appui\nMidfoot (Three zones)", 2)
403
- if len(vals) >= 2:
404
- data["mid_peak_time_pct_g"], data["mid_peak_time_pct_d"] = vals[0], vals[1]
405
 
406
- vals = find_float_after_label(text, "Instant pic de force, % de phase d'appui\nHeel (Three zones)", 2)
407
- if len(vals) >= 2:
408
- data["heel_peak_time_pct_g"], data["heel_peak_time_pct_d"] = vals[0], vals[1]
 
 
 
 
409
 
410
  data["attaque_pdf"] = estimate_attack_from_pdf(data)
411
  return data
412
 
413
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
414
  def match_pdf_to_athlete(pdfs_data, athlete_name):
415
  target = normalize_name(athlete_name)
416
  target_parts = set(target.split())
 
291
  text_parts.append(txt)
292
  return "\n".join(text_parts)
293
 
294
+ def find_float_after_label(text: str, label: str, max_numbers: int = 2, window: int = 1200):
295
+ """
296
+ Cherche un label dans le texte extrait puis récupère les premiers nombres après ce label.
297
+ Version tolérante aux retours ligne / espaces / accents du PDF Zebris.
298
+ """
299
+ if not text or not label:
300
+ return []
301
+
302
+ text_norm = text.lower().replace("\xa0", " ")
303
+ label_norm = label.lower().replace("\xa0", " ")
304
 
305
+ idx = text_norm.find(label_norm)
 
306
  if idx == -1:
307
  return []
308
+
309
+ snippet = text[idx: idx + window]
310
+
311
  nums = re.findall(r"(\d+,\d+|\d+\.\d+|\d+)", snippet)
312
  out = []
313
  for n in nums[:max_numbers]:
314
+ try:
315
+ out.append(float(n.replace(",", ".")))
316
+ except Exception:
317
+ pass
318
  return out
319
 
320
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
321
  def parse_zebris_pdf(uploaded_pdf):
322
  text = extract_text_from_pdf(uploaded_pdf)
323
  athlete_name = extract_pdf_name(text)
 
347
  "fore_peak_time_pct_d": np.nan,
348
  }
349
 
350
+ # =========================
351
+ # TRANSITION talon -> avant-pied (s)
352
+ # =========================
353
+ vals = find_float_after_label(
354
+ text,
355
+ "Instant du passage du talon vers\nl'avant-pied, s",
356
+ max_numbers=2
357
+ )
358
  if len(vals) >= 2:
359
  data["transition_g"], data["transition_d"] = vals[0], vals[1]
360
+ else:
361
+ vals = find_float_after_label(
362
+ text,
363
+ "Instant du passage du talon vers l'avant-pied, s",
364
+ max_numbers=2
365
+ )
366
+ if len(vals) >= 2:
367
+ data["transition_g"], data["transition_d"] = vals[0], vals[1]
368
 
369
+ # =========================
370
+ # FORCE maximale, N
371
+ # =========================
372
+ vals = find_float_after_label(text, "Force maximale, N\nForefoot (Three zones)", max_numbers=2)
373
  if len(vals) >= 2:
374
  data["fore_force_g"], data["fore_force_d"] = vals[0], vals[1]
375
+ else:
376
+ vals = find_float_after_label(text, "Forefoot (Three zones)", max_numbers=2)
377
+ if len(vals) >= 2:
378
+ data["fore_force_g"], data["fore_force_d"] = vals[0], vals[1]
379
 
380
+ vals = find_float_after_label(text, "Midfoot (Three zones)", max_numbers=4)
381
  if len(vals) >= 2:
382
+ # On prend les 2 premières valeurs trouvées après Midfoot
383
+ data["mid_force_g"], data["mid_force_d"] = vals[0], vals[1]
 
384
 
385
+ vals = find_float_after_label(text, "Heel (Three zones)", max_numbers=6)
386
  if len(vals) >= 2:
387
+ # Sur cette zone, le mot Heel revient plusieurs fois dans la page.
388
+ # Les 2 premières valeurs après le premier bloc correspondent bien ici aux forces.
389
+ data["heel_force_g"], data["heel_force_d"] = vals[0], vals[1]
390
+
391
+ # =========================
392
+ # PRESSION maximale, N/cm²
393
+ # =========================
394
+ vals = find_float_after_label(text, "Pression maximale, N/cm²\nForefoot (Three zones)", max_numbers=2)
395
  if len(vals) >= 2:
396
  data["fore_pressure_g"], data["fore_pressure_d"] = vals[0], vals[1]
397
+ else:
398
+ # fallback plus tolérant : on repart de "Pression maximale"
399
+ vals = find_float_after_label(text, "Pression maximale, N/cm²", max_numbers=8)
400
+ # ordre attendu dans ce PDF :
401
+ # fore G, fore D, mid G, mid D, heel G, heel D
402
+ if len(vals) >= 6:
403
+ data["fore_pressure_g"], data["fore_pressure_d"] = vals[0], vals[1]
404
+ data["mid_pressure_g"], data["mid_pressure_d"] = vals[2], vals[3]
405
+ data["heel_pressure_g"], data["heel_pressure_d"] = vals[4], vals[5]
406
+
407
+ if pd.isna(data["mid_pressure_g"]):
408
+ vals = find_float_after_label(text, "Pression maximale, N/cm²\nMidfoot (Three zones)", max_numbers=2)
409
+ if len(vals) >= 2:
410
+ data["mid_pressure_g"], data["mid_pressure_d"] = vals[0], vals[1]
411
+
412
+ if pd.isna(data["heel_pressure_g"]):
413
+ vals = find_float_after_label(text, "Pression maximale, N/cm²\nHeel (Three zones)", max_numbers=2)
414
+ if len(vals) >= 2:
415
+ data["heel_pressure_g"], data["heel_pressure_d"] = vals[0], vals[1]
416
+
417
+ # =========================
418
+ # TIMING pic de force, % phase d'appui
419
+ # =========================
420
+ vals = find_float_after_label(
421
+ text,
422
+ "Instant pic de force, % de phase d'appui\nForefoot (Three zones)",
423
+ max_numbers=2
424
+ )
425
  if len(vals) >= 2:
426
+ data["fore_peak_time_pct_g"], data["fore_peak_time_pct_d"] = vals[0], vals[1]
427
+ else:
428
+ vals = find_float_after_label(text, "Instant pic de force, % de phase d'appui", max_numbers=8)
429
+ # ordre attendu : fore G/D, mid G/D, heel G/D
430
+ if len(vals) >= 6:
431
+ data["fore_peak_time_pct_g"], data["fore_peak_time_pct_d"] = vals[0], vals[1]
432
+ data["mid_peak_time_pct_g"], data["mid_peak_time_pct_d"] = vals[2], vals[3]
433
+ data["heel_peak_time_pct_g"], data["heel_peak_time_pct_d"] = vals[4], vals[5]
434
+
435
+ if pd.isna(data["mid_peak_time_pct_g"]):
436
+ vals = find_float_after_label(
437
+ text,
438
+ "Instant pic de force, % de phase d'appui\nMidfoot (Three zones)",
439
+ max_numbers=2
440
+ )
441
+ if len(vals) >= 2:
442
+ data["mid_peak_time_pct_g"], data["mid_peak_time_pct_d"] = vals[0], vals[1]
443
+
444
+ if pd.isna(data["heel_peak_time_pct_g"]):
445
+ vals = find_float_after_label(
446
+ text,
447
+ "Instant pic de force, % de phase d'appui\nHeel (Three zones)",
448
+ max_numbers=2
449
+ )
450
+ if len(vals) >= 2:
451
+ data["heel_peak_time_pct_g"], data["heel_peak_time_pct_d"] = vals[0], vals[1]
452
+
453
+ # =========================
454
+ # FALLBACK spécial adapté à ton PDF page 8
455
+ # =========================
456
+ if (
457
+ pd.isna(data["transition_g"]) or
458
+ pd.isna(data["fore_force_g"]) or
459
+ pd.isna(data["fore_pressure_g"]) or
460
+ pd.isna(data["fore_peak_time_pct_g"])
461
+ ):
462
+ block_match = re.search(
463
+ r"Instant du passage du talon vers.*?Heel \(Three zones\)\s+Gauche\s+Droite\s+(\d+,\d+|\d+\.\d+)\±.*?(\d+,\d+|\d+\.\d+)\±.*?"
464
+ r"Force maximale, N.*?"
465
+ r"Forefoot \(Three zones\)\s+Gauche\s+Droite\s+(\d+,\d+|\d+\.\d+)\±.*?(\d+,\d+|\d+\.\d+)\±.*?"
466
+ r"Midfoot \(Three zones\)\s+Gauche\s+Droite\s+(\d+,\d+|\d+\.\d+)\±.*?(\d+,\d+|\d+\.\d+)\±.*?"
467
+ r"Heel \(Three zones\)\s+Gauche\s+Droite\s+(\d+,\d+|\d+\.\d+)\±.*?(\d+,\d+|\d+\.\d+)\±.*?"
468
+ r"Pression maximale, N/cm².*?"
469
+ r"Forefoot \(Three zones\)\s+Gauche\s+Droite\s+(\d+,\d+|\d+\.\d+)\±.*?(\d+,\d+|\d+\.\d+)\±.*?"
470
+ r"Midfoot \(Three zones\)\s+Gauche\s+Droite\s+(\d+,\d+|\d+\.\d+)\±.*?(\d+,\d+|\d+\.\d+)\±.*?"
471
+ r"Heel \(Three zones\)\s+Gauche\s+Droite\s+(\d+,\d+|\d+\.\d+)\±.*?(\d+,\d+|\d+\.\d+)\±.*?"
472
+ r"Instant pic de force, % de phase d'appui.*?"
473
+ r"Forefoot \(Three zones\)\s+Gauche\s+Droite\s+(\d+,\d+|\d+\.\d+)\±.*?(\d+,\d+|\d+\.\d+)\±.*?"
474
+ r"Midfoot \(Three zones\)\s+Gauche\s+Droite\s+(\d+,\d+|\d+\.\d+)\±.*?(\d+,\d+|\d+\.\d+)\±.*?"
475
+ r"Heel \(Three zones\)\s+Gauche\s+Droite\s+(\d+,\d+|\d+\.\d+)\±.*?(\d+,\d+|\d+\.\d+)\±",
476
+ text,
477
+ flags=re.S
478
+ )
479
 
480
+ if block_match:
481
+ vals = [float(x.replace(",", ".")) for x in block_match.groups()]
 
482
 
483
+ data["transition_g"], data["transition_d"] = vals[0], vals[1]
 
 
484
 
485
+ data["fore_force_g"], data["fore_force_d"] = vals[2], vals[3]
486
+ data["mid_force_g"], data["mid_force_d"] = vals[4], vals[5]
487
+ data["heel_force_g"], data["heel_force_d"] = vals[6], vals[7]
488
 
489
+ data["fore_pressure_g"], data["fore_pressure_d"] = vals[8], vals[9]
490
+ data["mid_pressure_g"], data["mid_pressure_d"] = vals[10], vals[11]
491
+ data["heel_pressure_g"], data["heel_pressure_d"] = vals[12], vals[13]
492
+
493
+ data["fore_peak_time_pct_g"], data["fore_peak_time_pct_d"] = vals[14], vals[15]
494
+ data["mid_peak_time_pct_g"], data["mid_peak_time_pct_d"] = vals[16], vals[17]
495
+ data["heel_peak_time_pct_g"], data["heel_peak_time_pct_d"] = vals[18], vals[19]
496
 
497
  data["attaque_pdf"] = estimate_attack_from_pdf(data)
498
  return data
499
 
500
 
501
+ def extract_pdf_name(text: str):
502
+ patterns = [
503
+ r"Personne:\s*([A-Za-zÀ-ÿ\- ]+),\s*\d{2}/\d{2}/\d{4}",
504
+ r"Personne:\s*([A-Za-zÀ-ÿ\- ]+)",
505
+ ]
506
+
507
+ for pattern in patterns:
508
+ m = re.search(pattern, text)
509
+ if m:
510
+ return m.group(1).strip()
511
+
512
+ return None
513
+
514
+
515
+ def estimate_attack_from_pdf(pdf_data: dict):
516
+ heel_peak_t = avg(pdf_data.get("heel_peak_time_pct_g"), pdf_data.get("heel_peak_time_pct_d"))
517
+ fore_peak_t = avg(pdf_data.get("fore_peak_time_pct_g"), pdf_data.get("fore_peak_time_pct_d"))
518
+ heel_force = avg(pdf_data.get("heel_force_g"), pdf_data.get("heel_force_d"))
519
+ fore_force = avg(pdf_data.get("fore_force_g"), pdf_data.get("fore_force_d"))
520
+ transition = avg(pdf_data.get("transition_g"), pdf_data.get("transition_d"))
521
+
522
+ if pd.notna(heel_peak_t) and pd.notna(fore_peak_t):
523
+ if heel_peak_t <= 12 and pd.notna(transition) and transition >= 0.055:
524
+ return "attaque talon"
525
+ if heel_peak_t > 15 and fore_peak_t < 50:
526
+ return "attaque avant-pied"
527
+
528
+ if pd.notna(heel_force) and pd.notna(fore_force) and fore_force != 0:
529
+ ratio = heel_force / fore_force
530
+ if ratio > 1.05 and pd.notna(transition) and transition >= 0.055:
531
+ return "attaque talon"
532
+ if ratio < 0.90 and pd.notna(transition) and transition <= 0.055:
533
+ return "attaque avant-pied"
534
+ return "attaque médio-pied"
535
+
536
+ return "indéterminée"
537
+
538
+
539
  def match_pdf_to_athlete(pdfs_data, athlete_name):
540
  target = normalize_name(athlete_name)
541
  target_parts = set(target.split())