File size: 5,575 Bytes
773f31c
 
 
d89f106
773f31c
 
ace3652
 
 
 
 
 
 
773f31c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1e76509
d89f106
 
 
 
 
 
 
 
 
573e1e6
d89f106
 
773f31c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
d89f106
 
773f31c
 
d89f106
 
773f31c
1e76509
773f31c
d89f106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
773f31c
d89f106
 
 
 
773f31c
573e1e6
d89f106
 
773f31c
d89f106
 
 
 
 
773f31c
 
d89f106
 
 
 
773f31c
d89f106
 
573e1e6
773f31c
 
d89f106
573e1e6
773f31c
01828f7
773f31c
 
d89f106
773f31c
 
d89f106
 
 
 
773f31c
d89f106
773f31c
 
d89f106
 
 
 
773f31c
 
d89f106
773f31c
d89f106
 
773f31c
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
import subprocess
import sys
import os

# --- 1. AWARYJNA INSTALACJA XGBOOST ---
# Ten fragment musi być na samej górze, zaraz po importach sys i subprocess
try:
    import xgboost
except ImportError:
    print("--- ⚠️ BRAK XGBOOST. Rozpoczynam awaryjną instalację... ---")
    subprocess.check_call([sys.executable, "-m", "pip", "install", "xgboost"])
    print("--- ✅ XGBoost zainstalowany pomyślnie! ---")
    import xgboost
# --------------------------------------

import joblib
import pandas as pd
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, Field
from typing import List
from huggingface_hub import hf_hub_download
from contextlib import asynccontextmanager

# --- Sekcja Konfiguracji Modelu ---
# Upewnij się, że nazwa pliku jest zgodna z tym co masz w Files!
# Wcześniej w logach miałeś 'model_raport.pkl', teraz w kodzie masz 'model.pkl'.
# Zostawiam 'model.pkl', ale sprawdź to!
MODEL_FILE_NAME = 'model.pkl' 
MODEL_REPO_ID = 'zotthytt12/model_hr' 

MODEL_FEATURES_ORDER = [
    'Experience (Years)', 'Education', 'Certifications', 'Job Role', 
    'Salary Expectation ($)', 'Projects Count', 'C++', 'Cybersecurity', 
    'Deep Learning', 'Ethical Hacking', 'Java', 'Linux', 
    'Machine Learning', 'NLP', 'Networking', 'Python', 'Pytorch', 
    'React', 'SQL', 'TensorFlow'
]

# --- Globalna zmienna na model ---
model = None

# --- 2. Definicja cyklu życia aplikacji (Lifespan) ---
@asynccontextmanager
async def lifespan(app: FastAPI):
    # Kod uruchamiany przy starcie
    global model
    print("--- Rozpoczynanie ładowania modelu z Huba... ---")
    try:
        model_path = hf_hub_download(
            repo_id=MODEL_REPO_ID,
            filename=MODEL_FILE_NAME
        )
        # Tutaj joblib użyje zainstalowanego wyżej xgboost
        model = joblib.load(model_path)
        
        print(f"--- Pomyślnie pobrano i wczytano model z Huba: {MODEL_REPO_ID} ---")
        
        # 🧹 Naprawa nazw kolumn – usuwamy spacje z przodu i końca
        if hasattr(model, "feature_names_in_"):
            clean_names = [f.strip() for f in model.feature_names_in_]
            model.feature_names_in_ = clean_names
            print("🧹 Oczyszczone feature_names_in_:", model.feature_names_in_)
            
    except Exception as e:
        print(f"BŁĄD KRYTYCZNY: Nie można wczytać modelu z Huba ({MODEL_REPO_ID}). Błąd: {e}")
        # Nie przerywamy yield, żeby aplikacja wstała i pokazała błąd w HTTP 503
    
    yield
    print("--- Zamykanie aplikacji ---")

# --- 3. Definicja API ---
app = FastAPI(
    title="API Rankingu CV",
    description="API oceniania kandydatów (XGBoost/RandomForest)",
    lifespan=lifespan
)

# --- 4. Modele danych (Pydantic) ---
class CandidateFeatures(BaseModel):
    identifier: str = Field(..., description="ID kandydata")
    Experience_Years: float = Field(..., alias="Experience (Years)")
    Education: float
    Certifications: float
    Job_Role: float = Field(..., alias="Job Role")
    Salary_Expectation: float = Field(..., alias="Salary Expectation ($)")
    Projects_Count: float = Field(..., alias="Projects Count")
    Cpp: float = Field(..., alias="C++")
    Cybersecurity: float
    Deep_Learning: float = Field(..., alias="Deep Learning")
    Ethical_Hacking: float = Field(..., alias="Ethical Hacking")
    Java: float
    Linux: float
    Machine_Learning: float = Field(..., alias="Machine Learning")
    NLP: float
    Networking: float
    Python: float
    Pytorch: float
    React: float
    SQL: float
    TensorFlow: float

    class Config:
        populate_by_name = True

class RankingRequest(BaseModel):
    candidates: List[CandidateFeatures]

class RankedCandidate(BaseModel):
    identifier: str
    score: float

class RankingResponse(BaseModel):
    ranked_candidates: List[RankedCandidate]

# --- 5. Punkty końcowe API ---

@app.get("/")
def read_root():
    return {"status": "OK", "message": "API działa poprawnie"}

@app.post("/rank", response_model=RankingResponse)
def rank_candidates(request: RankingRequest):
    global model
    if model is None:
        raise HTTPException(status_code=503, detail="Model nie jest gotowy. Sprawdź logi aplikacji.")
    
    if not request.candidates:
        return {"ranked_candidates": []}

    try:
        # Konwersja danych
        candidate_data_list = [c.model_dump(by_alias=True) for c in request.candidates]
        identifiers = [c['identifier'] for c in candidate_data_list]
        
        # DataFrame
        df = pd.DataFrame(candidate_data_list)
        features_df = df.drop(columns=['identifier'])
        
        # Dopasowanie kolumn do modelu
        features_df_ordered = features_df.reindex(columns=model.feature_names_in_, fill_value=0)
        
        # Predykcja
        probabilities = model.predict_proba(features_df_ordered)[:, 1]
        
        # Wynik
        ranked_list = []
        for i, identifier in enumerate(identifiers):
            ranked_list.append(RankedCandidate(
                identifier=identifier,
                score=float(probabilities[i])
            ))
            
        # Sortowanie
        sorted_ranked_list = sorted(ranked_list, key=lambda x: x.score, reverse=True)
        return {"ranked_candidates": sorted_ranked_list}

    except Exception as e:
        print(f"Błąd podczas predykcji: {e}")
        raise HTTPException(status_code=500, detail=f"Błąd serwera: {str(e)}")

# Uruchomienie lokalne
if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000)