zotthytt12 commited on
Commit
773f31c
·
verified ·
1 Parent(s): ace3652

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +74 -91
app.py CHANGED
@@ -1,10 +1,9 @@
1
- import joblib
2
- import pandas as pd
3
- from fastapi import FastAPI, HTTPException
4
- from pydantic import BaseModel, Field
5
- from typing import List
6
- from huggingface_hub import hf_hub_download
7
 
 
 
8
  try:
9
  import xgboost
10
  except ImportError:
@@ -12,8 +11,21 @@ except ImportError:
12
  subprocess.check_call([sys.executable, "-m", "pip", "install", "xgboost"])
13
  print("--- ✅ XGBoost zainstalowany pomyślnie! ---")
14
  import xgboost
15
-
16
- MODEL_FILE_NAME = 'model.pkl'
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  MODEL_REPO_ID = 'zotthytt12/model_hr'
18
 
19
  MODEL_FEATURES_ORDER = [
@@ -27,17 +39,45 @@ MODEL_FEATURES_ORDER = [
27
  # --- Globalna zmienna na model ---
28
  model = None
29
 
30
- # --- Definicja API (FastAPI) ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
31
  app = FastAPI(
32
  title="API Rankingu CV",
33
- description="API, które przyjmuje listę kandydatów, ocenia ich za pomocą modelu RandomForest i zwraca ranking."
 
34
  )
35
 
36
- # --- 1. Modele danych (Pydantic) ---
37
-
38
  class CandidateFeatures(BaseModel):
39
- """Definiuje cechy JEDNEGO kandydata."""
40
- identifier: str = Field(..., description="Unikalny identyfikator kandydata, np. email lub ID.")
41
  Experience_Years: float = Field(..., alias="Experience (Years)")
42
  Education: float
43
  Certifications: float
@@ -63,119 +103,62 @@ class CandidateFeatures(BaseModel):
63
  populate_by_name = True
64
 
65
  class RankingRequest(BaseModel):
66
- """Definiuje format zapytania - oczekujemy listy kandydatów."""
67
  candidates: List[CandidateFeatures]
68
 
69
  class RankedCandidate(BaseModel):
70
- """Definiuje format odpowiedzi dla jednego kandydata."""
71
  identifier: str
72
- score: float = Field(..., description="Prawdopodobieństwo zaproszenia (0.0 do 1.0)")
73
 
74
  class RankingResponse(BaseModel):
75
- """Definiuje format odpowiedzi - zwracamy listę ocenionych kandydatów."""
76
  ranked_candidates: List[RankedCandidate]
77
 
78
-
79
- # --- 2. Ładowanie modelu ---
80
- # (Używamy nowszego 'lifespan' zamiast 'on_event')
81
- from contextlib import asynccontextmanager
82
-
83
- @asynccontextmanager
84
- async def lifespan(app: FastAPI):
85
- # Kod uruchamiany przy starcie
86
- global model
87
- print("--- Rozpoczynanie ładowania modelu z Huba... ---")
88
- try:
89
- model_path = hf_hub_download(
90
- repo_id=MODEL_REPO_ID,
91
- filename=MODEL_FILE_NAME
92
- )
93
-
94
- model = joblib.load(model_path)
95
- print(f"--- Pomyślnie pobrano i wczytano model z Huba: {MODEL_REPO_ID} ---")
96
-
97
- # 🧹 Naprawa nazw kolumn – usuwamy spacje z przodu i końca
98
- if hasattr(model, "feature_names_in_"):
99
- clean_names = [f.strip() for f in model.feature_names_in_]
100
- model.feature_names_in_ = clean_names
101
- print("🧹 Oczyszczone feature_names_in_:", model.feature_names_in_)
102
- print(f"--- Pomyślnie pobrano i wczytano model z Huba: {MODEL_REPO_ID} ---")
103
- print("Feature names in model:", model.feature_names_in_)
104
-
105
- except Exception as e:
106
- print(f"BŁĄD KRYTYCZNY: Nie można wczytać modelu z Huba ({MODEL_REPO_ID}). Błąd: {e}")
107
-
108
- yield
109
- # Kod uruchamiany przy zamknięciu (jeśli potrzebny)
110
- print("--- Zamykanie aplikacji ---")
111
-
112
- # Przypisz funkcję lifespan do aplikacji
113
- app.router.lifespan_context = lifespan
114
-
115
-
116
- # --- 3. Punkty końcowe API (Endpoints) ---
117
 
118
  @app.get("/")
119
  def read_root():
120
- """Podstawowy endpoint (główna strona) do sprawdzania, czy API działa."""
121
- return {"status": "OK", "message": "Witaj w API do Rankingu CV!"}
122
-
123
 
124
  @app.post("/rank", response_model=RankingResponse)
125
  def rank_candidates(request: RankingRequest):
126
- """
127
- Ten endpoint przyjmuje listę kandydatów, przetwarza ich dane,
128
- przepuszcza przez model i zwraca posortowany ranking.
129
- """
130
  global model
131
  if model is None:
132
- # Jeśli model się nie załadował przy starcie, zwróć błąd
133
- raise HTTPException(status_code=503, detail="Model nie jest jeszcze gotowy. Sprawdź logi serwera.")
134
-
135
  if not request.candidates:
136
  return {"ranked_candidates": []}
137
 
138
  try:
139
- # 1. Konwertuj listę kandydatów
140
  candidate_data_list = [c.model_dump(by_alias=True) for c in request.candidates]
141
  identifiers = [c['identifier'] for c in candidate_data_list]
142
-
143
- # 2. Stwórz DataFrame
144
- df = pd.DataFrame(candidate_data_list)
145
 
146
- # Upewnij się, że brakuje tylko kolumny 'identifier', a reszta pasuje
 
147
  features_df = df.drop(columns=['identifier'])
148
 
 
149
  features_df_ordered = features_df.reindex(columns=model.feature_names_in_, fill_value=0)
150
-
151
-
152
- # 3. Predykcja
153
  probabilities = model.predict_proba(features_df_ordered)[:, 1]
154
-
155
- # 4. Tworzenie odpowiedzi
156
  ranked_list = []
157
  for i, identifier in enumerate(identifiers):
158
  ranked_list.append(RankedCandidate(
159
  identifier=identifier,
160
- score=probabilities[i]
161
  ))
162
-
163
- # 5. Sortowanie
164
  sorted_ranked_list = sorted(ranked_list, key=lambda x: x.score, reverse=True)
165
-
166
  return {"ranked_candidates": sorted_ranked_list}
167
 
168
- except KeyError as e:
169
- raise HTTPException(status_code=400, detail=f"Brakująca lub błędna cecha (KeyError): {e}")
170
  except Exception as e:
171
- raise HTTPException(status_code=500, detail=f"Wystąpił wewnętrzny błąd serwera: {str(e)}")
 
172
 
173
- # Uruchomienie aplikacji (dla testów lokalnych)
174
  if __name__ == "__main__":
175
  import uvicorn
176
- # Uwaga: przy starcie z __main__ lifespan nie zadziała automatycznie
177
- # Trzeba by go wywołać ręcznie lub po prostu polegać na teście z uvicorn
178
- print("Uruchamianie lokalne - model zostanie załadowany przez 'lifespan' po starcie uvicorn.")
179
- uvicorn.run(app, host="0.0.0.0", port=8000)
180
-
181
-
 
1
+ import subprocess
2
+ import sys
3
+ import os
 
 
 
4
 
5
+ # --- 1. AWARYJNA INSTALACJA XGBOOST ---
6
+ # Ten fragment musi być na samej górze, zaraz po importach sys i subprocess
7
  try:
8
  import xgboost
9
  except ImportError:
 
11
  subprocess.check_call([sys.executable, "-m", "pip", "install", "xgboost"])
12
  print("--- ✅ XGBoost zainstalowany pomyślnie! ---")
13
  import xgboost
14
+ # --------------------------------------
15
+
16
+ import joblib
17
+ import pandas as pd
18
+ from fastapi import FastAPI, HTTPException
19
+ from pydantic import BaseModel, Field
20
+ from typing import List
21
+ from huggingface_hub import hf_hub_download
22
+ from contextlib import asynccontextmanager
23
+
24
+ # --- Sekcja Konfiguracji Modelu ---
25
+ # Upewnij się, że nazwa pliku jest zgodna z tym co masz w Files!
26
+ # Wcześniej w logach miałeś 'model_raport.pkl', teraz w kodzie masz 'model.pkl'.
27
+ # Zostawiam 'model.pkl', ale sprawdź to!
28
+ MODEL_FILE_NAME = 'model.pkl'
29
  MODEL_REPO_ID = 'zotthytt12/model_hr'
30
 
31
  MODEL_FEATURES_ORDER = [
 
39
  # --- Globalna zmienna na model ---
40
  model = None
41
 
42
+ # --- 2. Definicja cyklu życia aplikacji (Lifespan) ---
43
+ @asynccontextmanager
44
+ async def lifespan(app: FastAPI):
45
+ # Kod uruchamiany przy starcie
46
+ global model
47
+ print("--- Rozpoczynanie ładowania modelu z Huba... ---")
48
+ try:
49
+ model_path = hf_hub_download(
50
+ repo_id=MODEL_REPO_ID,
51
+ filename=MODEL_FILE_NAME
52
+ )
53
+ # Tutaj joblib użyje zainstalowanego wyżej xgboost
54
+ model = joblib.load(model_path)
55
+
56
+ print(f"--- Pomyślnie pobrano i wczytano model z Huba: {MODEL_REPO_ID} ---")
57
+
58
+ # 🧹 Naprawa nazw kolumn – usuwamy spacje z przodu i końca
59
+ if hasattr(model, "feature_names_in_"):
60
+ clean_names = [f.strip() for f in model.feature_names_in_]
61
+ model.feature_names_in_ = clean_names
62
+ print("🧹 Oczyszczone feature_names_in_:", model.feature_names_in_)
63
+
64
+ except Exception as e:
65
+ print(f"BŁĄD KRYTYCZNY: Nie można wczytać modelu z Huba ({MODEL_REPO_ID}). Błąd: {e}")
66
+ # Nie przerywamy yield, żeby aplikacja wstała i pokazała błąd w HTTP 503
67
+
68
+ yield
69
+ print("--- Zamykanie aplikacji ---")
70
+
71
+ # --- 3. Definicja API ---
72
  app = FastAPI(
73
  title="API Rankingu CV",
74
+ description="API oceniania kandydatów (XGBoost/RandomForest)",
75
+ lifespan=lifespan
76
  )
77
 
78
+ # --- 4. Modele danych (Pydantic) ---
 
79
  class CandidateFeatures(BaseModel):
80
+ identifier: str = Field(..., description="ID kandydata")
 
81
  Experience_Years: float = Field(..., alias="Experience (Years)")
82
  Education: float
83
  Certifications: float
 
103
  populate_by_name = True
104
 
105
  class RankingRequest(BaseModel):
 
106
  candidates: List[CandidateFeatures]
107
 
108
  class RankedCandidate(BaseModel):
 
109
  identifier: str
110
+ score: float
111
 
112
  class RankingResponse(BaseModel):
 
113
  ranked_candidates: List[RankedCandidate]
114
 
115
+ # --- 5. Punkty końcowe API ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
  @app.get("/")
118
  def read_root():
119
+ return {"status": "OK", "message": "API działa poprawnie"}
 
 
120
 
121
  @app.post("/rank", response_model=RankingResponse)
122
  def rank_candidates(request: RankingRequest):
 
 
 
 
123
  global model
124
  if model is None:
125
+ raise HTTPException(status_code=503, detail="Model nie jest gotowy. Sprawdź logi aplikacji.")
126
+
 
127
  if not request.candidates:
128
  return {"ranked_candidates": []}
129
 
130
  try:
131
+ # Konwersja danych
132
  candidate_data_list = [c.model_dump(by_alias=True) for c in request.candidates]
133
  identifiers = [c['identifier'] for c in candidate_data_list]
 
 
 
134
 
135
+ # DataFrame
136
+ df = pd.DataFrame(candidate_data_list)
137
  features_df = df.drop(columns=['identifier'])
138
 
139
+ # Dopasowanie kolumn do modelu
140
  features_df_ordered = features_df.reindex(columns=model.feature_names_in_, fill_value=0)
141
+
142
+ # Predykcja
 
143
  probabilities = model.predict_proba(features_df_ordered)[:, 1]
144
+
145
+ # Wynik
146
  ranked_list = []
147
  for i, identifier in enumerate(identifiers):
148
  ranked_list.append(RankedCandidate(
149
  identifier=identifier,
150
+ score=float(probabilities[i])
151
  ))
152
+
153
+ # Sortowanie
154
  sorted_ranked_list = sorted(ranked_list, key=lambda x: x.score, reverse=True)
 
155
  return {"ranked_candidates": sorted_ranked_list}
156
 
 
 
157
  except Exception as e:
158
+ print(f"Błąd podczas predykcji: {e}")
159
+ raise HTTPException(status_code=500, detail=f"Błąd serwera: {str(e)}")
160
 
161
+ # Uruchomienie lokalne
162
  if __name__ == "__main__":
163
  import uvicorn
164
+ uvicorn.run(app, host="0.0.0.0", port=8000)