rinogeek commited on
Commit
ac451a9
·
1 Parent(s): 31ce146
Files changed (7) hide show
  1. Procfile +1 -0
  2. README.md +0 -12
  3. api/__pycache__/main.cpython-312.pyc +0 -0
  4. api/main.py +32 -59
  5. app.py +5 -0
  6. debug.py +9 -0
  7. vercel.json +0 -7
Procfile ADDED
@@ -0,0 +1 @@
 
 
1
+ web: uvicorn api.main:app --host 0.0.0.0 --port ${PORT:-8000}
README.md DELETED
@@ -1,12 +0,0 @@
1
- ---
2
- title: Test R
3
- emoji: 🏢
4
- colorFrom: yellow
5
- colorTo: blue
6
- sdk: gradio
7
- sdk_version: 5.49.1
8
- app_file: app.py
9
- pinned: false
10
- ---
11
-
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
api/__pycache__/main.cpython-312.pyc CHANGED
Binary files a/api/__pycache__/main.cpython-312.pyc and b/api/__pycache__/main.cpython-312.pyc differ
 
api/main.py CHANGED
@@ -436,10 +436,18 @@ def match_offer_sync(offer_text: str, top_k: int = 7, with_explanation: bool = T
436
 
437
  # Calculer un score d'expérience (toujours, utilisé pour le score final)
438
  if required_exp is not None:
439
- exp_diff = abs(profile_exp - required_exp)
440
- exp_score = max(0, 1 - (exp_diff / 10))
 
 
 
 
 
 
 
 
441
  else:
442
- # Normaliser l'expérience en score 0-1 en supposant une borne haute raisonnable (20 ans)
443
  exp_score = min(1.0, profile_exp / 20)
444
 
445
  # Générer l'explication (optionnel)
@@ -447,11 +455,26 @@ def match_offer_sync(offer_text: str, top_k: int = 7, with_explanation: bool = T
447
  if with_explanation:
448
  explanation = generate_explanation(offer_text, row, skills_score, exp_score)
449
 
 
 
 
 
450
  # Calculer un score de pertinence combiné (compétences + expérience)
451
  try:
452
- final_score = calculate_weighted_score(skills_score, exp_score)
453
  except Exception:
454
- final_score = 0.0
 
 
 
 
 
 
 
 
 
 
 
455
 
456
  candidates.append({
457
  'profile': ProfileResult(
@@ -476,61 +499,11 @@ def match_offer_sync(offer_text: str, top_k: int = 7, with_explanation: bool = T
476
  # 4) Ensuite trier par expérience décroissante. Si required_exp est présent dans l'offre, on place d'abord les profils
477
  # avec profile_exp >= required_exp (triés desc), puis compléter avec les autres (triés desc)
478
 
479
- # Filtrer candidats ayant des correspondances de compétences
480
- with_skills = [c for c in candidates if c['skills_match_count'] > 0]
481
- without_skills = [c for c in candidates if c['skills_match_count'] == 0]
482
-
483
- # Trier ceux avec compétences : on applique les priorités successives
484
- def sort_key(c):
485
- # role_match True -> come first (-1), location_match True -> come first (-1)
486
- return (
487
- -c['skills_match_count'],
488
- -int(c['role_match']),
489
- -int(c['location_match']),
490
- -c['profile_exp']
491
- )
492
 
493
- with_skills.sort(key=sort_key)
494
-
495
- # S'assurer du comportement demandé par le recruteur : si l'offre précise un seuil d'expérience,
496
- # prioriser profils >= seuil (triés par expérience décroissante), puis compléter par les autres.
497
- ordered = []
498
- if required_exp is not None:
499
- meets_exp = [c for c in with_skills if c['profile_exp'] >= required_exp]
500
- not_meets_exp = [c for c in with_skills if c['profile_exp'] < required_exp]
501
- # Trier par : skills_match_count, role_match, location_match, puis expérience décroissante
502
- meets_exp.sort(key=lambda c: (
503
- -c['skills_match_count'],
504
- -int(c['role_match']),
505
- -int(c['location_match']),
506
- -c['profile_exp']
507
- ))
508
- not_meets_exp.sort(key=lambda c: (
509
- -c['skills_match_count'],
510
- -int(c['role_match']),
511
- -int(c['location_match']),
512
- -c['profile_exp']
513
- ))
514
- ordered.extend(meets_exp)
515
- ordered.extend(not_meets_exp)
516
- else:
517
- # Pas de seuil : on veut principalement trier par expérience décroissante, mais en privilégiant
518
- # d'abord ceux qui matchent les compétences / rôle / localisation.
519
- with_skills.sort(key=lambda c: (
520
- -int(c['role_match']),
521
- -int(c['location_match']),
522
- -c['skills_match_count'],
523
- -c['profile_exp']
524
- ))
525
- ordered.extend(with_skills)
526
-
527
- # Si on manque de profils pour top_k, on complète avec candidats sans skills (triés par role/location/exp)
528
- if len(ordered) < top_k:
529
- without_skills.sort(key=sort_key)
530
- ordered.extend(without_skills)
531
-
532
- # Enfin retourner les top_k profils (convertis en ProfileResult)
533
- return [c['profile'] for c in ordered[:top_k]]
534
 
535
  # --- Endpoints de l'API ---
536
  @app.get("/")
 
436
 
437
  # Calculer un score d'expérience (toujours, utilisé pour le score final)
438
  if required_exp is not None:
439
+ if profile_exp >= required_exp:
440
+ # L'expérience est suffisante ou supérieure, le score est élevé
441
+ # Bonus pour l'expérience supplémentaire, plafonné pour ne pas surpondérer
442
+ exp_score = min(1.0, 0.8 + (profile_exp - required_exp) * 0.05)
443
+ else:
444
+ # L'expérience est inférieure, le score est proportionnel
445
+ if required_exp > 0:
446
+ exp_score = max(0, (profile_exp / required_exp) * 0.7)
447
+ else:
448
+ exp_score = 0
449
  else:
450
+ # Pas d'exigence, on normalise sur une échelle de 20 ans
451
  exp_score = min(1.0, profile_exp / 20)
452
 
453
  # Générer l'explication (optionnel)
 
455
  if with_explanation:
456
  explanation = generate_explanation(offer_text, row, skills_score, exp_score)
457
 
458
+ # Pondération fixe 50% compétences / 50% expérience, comme demandé
459
+ skills_weight = 0.5
460
+ exp_weight = 0.5
461
+
462
  # Calculer un score de pertinence combiné (compétences + expérience)
463
  try:
464
+ base_score = calculate_weighted_score(skills_score, exp_score, skills_weight=skills_weight, exp_weight=exp_weight)
465
  except Exception:
466
+ base_score = 0.0
467
+
468
+ # Petites primes pour role_match / location_match / nombre de skills matchés
469
+ bonus = 0.0
470
+ if role_match:
471
+ bonus += 0.08
472
+ if location_match:
473
+ bonus += 0.04
474
+ # bonus croissant mais plafonné pour skills_match_count
475
+ bonus += min(0.03 * skills_match_count, 0.12)
476
+
477
+ final_score = min(1.0, base_score + bonus)
478
 
479
  candidates.append({
480
  'profile': ProfileResult(
 
499
  # 4) Ensuite trier par expérience décroissante. Si required_exp est présent dans l'offre, on place d'abord les profils
500
  # avec profile_exp >= required_exp (triés desc), puis compléter avec les autres (triés desc)
501
 
502
+ # Trier tous les candidats par final_score décroissant (affichage demandé : ordre par pourcentage)
503
+ candidates.sort(key=lambda c: -c.get('profile').score)
 
 
 
 
 
 
 
 
 
 
 
504
 
505
+ # Retourner les top_k profils
506
+ return [c['profile'] for c in candidates[:top_k]]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
507
 
508
  # --- Endpoints de l'API ---
509
  @app.get("/")
app.py ADDED
@@ -0,0 +1,5 @@
 
 
 
 
 
 
1
+ from api.main import app
2
+
3
+ if __name__ == "__main__":
4
+ import uvicorn
5
+ uvicorn.run(app, host="0.0.0.0", port=7860)
debug.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import traceback
2
+
3
+ try:
4
+ print("Attempting to import main.py...")
5
+ from api import main
6
+ print("Successfully imported main.py")
7
+ except Exception as e:
8
+ print("Failed to import main.py. See traceback below:")
9
+ traceback.print_exc()
vercel.json DELETED
@@ -1,7 +0,0 @@
1
- {
2
- "functions": {
3
- "api/index.py": {
4
- "runtime": "python3.12"
5
- }
6
- }
7
- }