akshaybabu06 commited on
Commit
e118e1f
·
verified ·
1 Parent(s): ab331fa

Delete api

Browse files
api/.dockerignore DELETED
@@ -1,15 +0,0 @@
1
- .github
2
- .pytest_cache
3
- __pycache__
4
- .vscode
5
- .venv
6
- build
7
- JobSelect.egg-info
8
- test
9
- feat_to_add.txt
10
- pytest.ini
11
- data
12
- dist
13
- frontend
14
- notebooks
15
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
api/Dockerfile DELETED
@@ -1,17 +0,0 @@
1
- FROM python:3.12-slim
2
-
3
- ENV PYTHONDONTWRITEBYTECODE=1
4
- ENV PYTHONUNBUFFERED=1
5
-
6
- WORKDIR /Job-Description-Analysis
7
-
8
- RUN apt-get update && apt-get install -y --no-install-recommends build-essential && rm -rf /var/lib/apt/lists/*
9
-
10
- COPY ./requirements.txt /Job-Description-Analysis/requirements.txt
11
- RUN pip install --no-cache-dir -r requirements.txt
12
-
13
- COPY . /Job-Description-Analysis
14
-
15
- EXPOSE 8080
16
-
17
- CMD ["python", "api/JobAnalyze_API.py"]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
api/JobAnalyze_API.py DELETED
@@ -1,281 +0,0 @@
1
- """
2
- JobAnalyze_API.py - Main API Script
3
- """
4
-
5
- from fastapi import FastAPI, Depends, HTTPException, Security, status
6
- from fastapi.middleware.cors import CORSMiddleware
7
- from fastapi.security import APIKeyHeader
8
- from fastapi.responses import JSONResponse
9
- from fastapi_mcp import FastApiMCP
10
- from pydantic import BaseModel, field_validator, EmailStr
11
- from slowapi import Limiter, _rate_limit_exceeded_handler
12
- from slowapi.util import get_remote_address
13
- from slowapi.errors import RateLimitExceeded
14
- from starlette.requests import Request
15
- from starlette.middleware.base import BaseHTTPMiddleware
16
- from supabase_auth.errors import AuthApiError
17
- import hashlib
18
- import secrets
19
- import traceback
20
- import uvicorn
21
-
22
- from pred import JobAnalyze_6k
23
- from supabase_client import upsert_api_key_db
24
-
25
- ALLOWED_ORIGINS = [
26
- "https://job-analyzer-view.vercel.app",
27
- "http://localhost:5173",
28
- "http://localhost:3000",
29
- ]
30
-
31
- app = FastAPI(title="Unified JobAuto Model API")
32
-
33
- class ForceCORSMiddleware(BaseHTTPMiddleware):
34
- async def dispatch(self, request: Request, call_next):
35
- origin = request.headers.get("origin", "")
36
- try:
37
- response = await call_next(request)
38
- except Exception:
39
- response = JSONResponse(
40
- status_code=500,
41
- content={"detail": "Internal server error"},
42
- )
43
- if origin in ALLOWED_ORIGINS:
44
- response.headers["Access-Control-Allow-Origin"] = origin
45
- response.headers["Access-Control-Allow-Credentials"] = "true"
46
- response.headers["Access-Control-Allow-Methods"] = "GET,POST,OPTIONS"
47
- response.headers["Access-Control-Allow-Headers"] = "*"
48
- return response
49
-
50
- app.add_middleware(ForceCORSMiddleware)
51
- app.add_middleware(
52
- CORSMiddleware,
53
- allow_origins=ALLOWED_ORIGINS,
54
- allow_credentials=True,
55
- allow_methods=["GET", "POST", "OPTIONS"],
56
- allow_headers=["*"],
57
- )
58
-
59
- API_KEY_NAME = "JobAnalyze_6k_Key"
60
- api_key_header = APIKeyHeader(name=API_KEY_NAME, auto_error=False)
61
- API_KEY_DB: dict = {}
62
-
63
-
64
- def key_func(request: Request) -> str:
65
- api_key = request.headers.get(API_KEY_NAME)
66
- return api_key if api_key else get_remote_address(request)
67
-
68
- limiter = Limiter(key_func=key_func)
69
- app.state.limiter = limiter
70
- app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)
71
-
72
-
73
- class SignUpRequest(BaseModel):
74
- name: str
75
- email: EmailStr
76
- password: str
77
-
78
-
79
- class SignInRequest(BaseModel):
80
- email: EmailStr
81
- password: str
82
-
83
-
84
- def generate_api(prefix: str = "ja6k") -> str:
85
- return f"{prefix}_{secrets.token_hex(32)}"
86
-
87
-
88
- def hash_key(api_key: str) -> str:
89
- return hashlib.sha256(api_key.encode("utf-8")).hexdigest()
90
-
91
-
92
- async def verify(api_key: str = Security(api_key_header)):
93
- if not api_key:
94
- raise HTTPException(
95
- status_code=status.HTTP_401_UNAUTHORIZED,
96
- detail="API Key Missing From Header",
97
- )
98
- hash_income = hash_key(api_key)
99
- db_record = API_KEY_DB.get(hash_income)
100
- if not db_record:
101
- try:
102
- from supabase_client import get_api_key_db
103
- db_record = get_api_key_db(api_key=api_key)
104
- if db_record and isinstance(db_record, dict):
105
- API_KEY_DB[hash_income] = db_record
106
- except Exception:
107
- db_record = None
108
- if not db_record:
109
- raise HTTPException(
110
- status_code=status.HTTP_403_FORBIDDEN,
111
- detail="Invalid or Expired API Key, Please get an API Key",
112
- )
113
- return db_record
114
-
115
-
116
- class ModelRequest(BaseModel):
117
- Job_Desc: str
118
- Role: str
119
- Type: str
120
-
121
- @field_validator("Job_Desc")
122
- def jd_not_empty(cls, v):
123
- if not v.strip():
124
- raise ValueError("Job_Desc cannot be empty")
125
- return v.strip()
126
-
127
- @field_validator("Role")
128
- def role_valid(cls, v):
129
- allowed = ["AI Engineer", "AI Developer"]
130
- if v not in allowed:
131
- raise ValueError(f"Role must be one of {allowed}")
132
- return v
133
-
134
- @field_validator("Type")
135
- def type_valid(cls, v):
136
- allowed = ["Internship", "Junior", "Senior"]
137
- if v not in allowed:
138
- raise ValueError(f"Type must be one of {allowed}")
139
- return v
140
-
141
-
142
- @app.get("/")
143
- async def main() -> dict:
144
- return {"message": "JobAnalyze 6k"}
145
-
146
-
147
- @app.get("/cron", operation_id="Cron Job")
148
- async def cron() -> dict:
149
- return {"message": "Cron Task Executed"}
150
-
151
-
152
- @app.post("/auth/create_acc", status_code=status.HTTP_201_CREATED, operation_id="Sign Up")
153
- async def create_acc(data: SignUpRequest) -> dict:
154
- from supabase_client import supabase
155
-
156
- email = str(data.email).strip().lower()
157
- name = data.name.strip()
158
-
159
- try:
160
- res = supabase.auth.sign_up({
161
- "email": email,
162
- "password": data.password,
163
- "options": {"data": {"name": name}},
164
- })
165
-
166
- except AuthApiError as e:
167
- msg = str(e).lower()
168
- if "already registered" in msg or "already exists" in msg:
169
- raise HTTPException(
170
- status_code=status.HTTP_409_CONFLICT,
171
- detail="An account with this email already exists. Please sign in.",
172
- )
173
- if "password" in msg:
174
- raise HTTPException(
175
- status_code=status.HTTP_422_UNPROCESSABLE_ENTITY,
176
- detail="Password must be at least 6 characters.",
177
- )
178
- raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail=str(e))
179
-
180
- except Exception as e:
181
- traceback.print_exc()
182
- raise HTTPException(
183
- status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
184
- detail="Unexpected signup error — please try again",
185
- )
186
-
187
- if res.user is None:
188
- raise HTTPException(
189
- status_code=status.HTTP_400_BAD_REQUEST,
190
- detail="Signup failed — Supabase email provider may not be enabled",
191
- )
192
-
193
- raw = generate_api()
194
- hashed = hash_key(raw)
195
-
196
- try:
197
- upsert_api_key_db(user_id=hashed, owner=email, api_key=raw)
198
- except Exception:
199
- traceback.print_exc()
200
- return {
201
- "message": "Account Created — API key storage failed, contact support",
202
- "api_key": raw,
203
- "name": name,
204
- "email": email,
205
- }
206
-
207
- return {"message": "Account Created", "api_key": raw, "name": name, "email": email}
208
-
209
-
210
- @app.post("/auth/sign_in", operation_id="Sign In")
211
- async def sign_in(data: SignInRequest) -> dict:
212
- from supabase_client import supabase, get_api_key_db
213
-
214
- email = str(data.email).strip().lower()
215
-
216
- try:
217
- res = supabase.auth.sign_in_with_password({
218
- "email": email,
219
- "password": data.password,
220
- })
221
-
222
- except AuthApiError as e:
223
- msg = str(e).lower()
224
- if "invalid" in msg or "credentials" in msg or "password" in msg:
225
- raise HTTPException(status_code=401, detail="Invalid email or password")
226
- raise HTTPException(status_code=400, detail=str(e))
227
-
228
- except Exception:
229
- traceback.print_exc()
230
- raise HTTPException(status_code=500, detail="Unexpected sign-in error")
231
-
232
- if res.user is None:
233
- raise HTTPException(status_code=401, detail="Invalid email or password")
234
-
235
- name = (res.user.user_metadata or {}).get("name", email.split("@")[0])
236
- record = get_api_key_db(owner=email)
237
-
238
- if not record:
239
- raise HTTPException(status_code=404, detail="API Key does not exist")
240
-
241
- return {"email": email, "name": name, "api_key": record["api_key"]}
242
-
243
-
244
- @app.post("/API/Generate", status_code=status.HTTP_201_CREATED, operation_id="API Key Creator")
245
- @limiter.limit("5/hour")
246
- async def create_api(request: Request, email: str) -> dict:
247
- raw = generate_api()
248
- hashed = hash_key(raw)
249
- API_KEY_DB[hashed] = {"owner": email}
250
- upsert_api_key_db(user_id=hashed, owner=email, api_key=raw)
251
- return {
252
- "owner": email,
253
- "api_key": raw,
254
- "warning": "Copy this key, this is a one time displayed key",
255
- }
256
-
257
-
258
- @app.post("/JobAnalyze_6k", operation_id="JobAnalyze 6k Model : Analyze Job Descriptions")
259
- @limiter.limit("10/minute")
260
- async def JobAnalyze_Pred(
261
- request: Request,
262
- data: ModelRequest,
263
- api_client: dict = Depends(verify),
264
- ) -> dict:
265
- resp = JobAnalyze_6k(
266
- job_desc=data.Job_Desc,
267
- role=data.Role,
268
- job_type=data.Type,
269
- )
270
- return {"answer": [(skill, float(score)) for skill, score in resp]}
271
-
272
- mcp = FastApiMCP(
273
- app,
274
- include_operations=["JobAnalyze 6k Model : Analyze Job Descriptions"],
275
- name="JobAnalyze 6k",
276
- description="Predicts Most Probable Skills",
277
- )
278
- mcp.mount_http()
279
-
280
- if __name__ == "__main__":
281
- uvicorn.run("JobAnalyze_API:app", host="0.0.0.0", port=5000)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
api/__init__.py DELETED
File without changes
api/auth/__init__.py DELETED
File without changes
api/auth/auth_layer.py DELETED
File without changes
api/pred.py DELETED
@@ -1,91 +0,0 @@
1
- """
2
- pred.py - Powers JobAnalyze 6k model skeleton
3
- """
4
-
5
- import json
6
- import pickle
7
- from typing import List, Tuple
8
- import numpy as np
9
- import torch
10
- from pathlib import Path
11
- import warnings
12
-
13
- warnings.filterwarnings(
14
- "ignore",
15
- message="InconsistentVersionWarning: Trying to unpickle estimator*",
16
- )
17
- warnings.filterwarnings(
18
- "ignore",
19
- message="Trying to unpickle estimator*",
20
- )
21
-
22
-
23
- ROOT = Path(__file__).resolve().parent.parent
24
-
25
- from torch import nn
26
-
27
- class SkillClassifier(nn.Module):
28
- def __init__(self, input_dim, num_labels, hidden_dim=32, dropout=0.3):
29
- super().__init__()
30
- self.net = nn.Sequential(
31
- nn.Linear(input_dim, hidden_dim),
32
- nn.ReLU(),
33
- nn.Dropout(dropout),
34
- nn.Linear(hidden_dim, num_labels),
35
- )
36
-
37
- def forward(self, x):
38
- return self.net(x)
39
-
40
- def JobAnalyze_6k(job_desc: str = "", role: str = "", job_type: str = "", top_k: int = 50) -> List[Tuple[str, float]]:
41
- """
42
- Predict top-k skills.
43
-
44
- Current top-k = 48
45
- """
46
- prep_dir = ROOT / "model" / "prep"
47
- if not prep_dir.exists():
48
- alt = ROOT / "prep"
49
- if alt.exists():
50
- prep_dir = alt
51
-
52
- label_path = prep_dir / "label_vocab.json"
53
- vector_path = prep_dir / "vectorizer.pkl"
54
- weights_path = ROOT / "model_out" / "skill_classifier.pt"
55
-
56
- if not label_path.exists():
57
- raise FileNotFoundError(
58
- f"Missing {label_path}. Make sure you ran data_prep and model training."
59
- )
60
- if not vector_path.exists():
61
- raise FileNotFoundError(
62
- f"Missing {vector_path}. Make sure you ran data_prep."
63
- )
64
- if not weights_path.exists():
65
- raise FileNotFoundError(
66
- f"Missing {weights_path}. Make sure you ran model/model.py."
67
- )
68
-
69
- with open(label_path, encoding="utf-8") as f:
70
- label_vocab = json.load(f)
71
- with open(vector_path, "rb") as f:
72
- vectorizer = pickle.load(f)
73
-
74
- input_dim = int(getattr(vectorizer, "vocabulary_", {}).__len__()) or vectorizer.transform([""]).shape[1]
75
-
76
- model = SkillClassifier(input_dim, len(label_vocab))
77
- model.load_state_dict(torch.load(weights_path, map_location="cpu"))
78
-
79
- model.eval()
80
-
81
- combined_text = f"{job_desc} {role} {job_type}"
82
- X = vectorizer.transform([combined_text]).toarray().astype(np.float32)
83
-
84
- with torch.no_grad():
85
- logits = model(torch.tensor(X))
86
- probs = torch.sigmoid(logits).numpy()[0]
87
-
88
- ranked = sorted(zip(label_vocab, probs), key=lambda x: -x[1])
89
- return ranked[:top_k]
90
-
91
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
api/supabase_client.py DELETED
@@ -1,58 +0,0 @@
1
- """
2
- supabase_client.py - Main client for the supabase integration for queries to postgres
3
- """
4
-
5
- from pathlib import Path
6
- from dotenv import load_dotenv
7
- import sys
8
- import os
9
- from supabase import create_client, Client
10
-
11
- PROJECT_ROOT = Path(__file__).resolve().parent.parent
12
- if str(PROJECT_ROOT) not in sys.path:
13
- sys.path.insert(0, str(PROJECT_ROOT))
14
-
15
- _env_path = Path(__file__).resolve().parent / ".env"
16
- if not _env_path.exists():
17
- _env_path = PROJECT_ROOT / ".env"
18
- load_dotenv(dotenv_path=_env_path)
19
-
20
- SUPA_URL = os.getenv("SUPA_URL", "")
21
- SUPA_KEY = os.getenv("SUPA_KEY", "")
22
-
23
- supabase: Client = create_client(SUPA_URL, SUPA_KEY)
24
-
25
- def upsert_api_key_db(*, user_id: str, owner: str, api_key: str) -> dict:
26
- payload = {"owner": owner, "api_key": api_key}
27
-
28
- resp = (
29
- supabase.table("api_tok")
30
- .upsert(payload)
31
- .execute()
32
- )
33
- data = getattr(resp, "data", None)
34
- return data[0] if data else {}
35
-
36
-
37
- def get_api_key_db(*, owner: str | None = None, api_key: str | None = None) -> dict | None:
38
- query = (
39
- supabase
40
- .table("api_tok")
41
- .select("user_id", "owner", "api_key")
42
- )
43
-
44
- if owner is not None:
45
- query = query.eq("owner", owner)
46
-
47
- elif api_key is not None:
48
- query = query.eq("api_key", api_key)
49
-
50
- else:
51
- return None
52
-
53
- resp = query.limit(1).execute()
54
-
55
- data = getattr(resp, "data", None) or []
56
- return data[0] if data else None
57
-
58
-