QuentinL52 commited on
Commit
2d06d01
·
verified ·
1 Parent(s): a57a4e1

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +59 -155
main.py CHANGED
@@ -1,7 +1,5 @@
1
  import tempfile
2
- import requests
3
  import os
4
- import logging
5
  import json
6
  from fastapi import FastAPI, UploadFile, File, HTTPException, BackgroundTasks
7
  from fastapi.concurrency import run_in_threadpool
@@ -9,58 +7,14 @@ from fastapi.middleware.cors import CORSMiddleware
9
  from pydantic import BaseModel, Field
10
  from typing import List, Dict, Any, Optional
11
 
 
 
 
 
 
12
  os.environ['HOME'] = '/tmp'
13
  os.makedirs('/tmp/feedbacks', exist_ok=True)
14
 
15
- logging.basicConfig(level=logging.INFO)
16
- logger = logging.getLogger(__name__)
17
-
18
-
19
- try:
20
- from src.deep_learning_analyzer import MultiModelInterviewAnalyzer
21
- from src.rag_handler import get_rag_handler
22
- from src.crew.crew_pool import run_interview_analysis
23
-
24
- analyzer_model = MultiModelInterviewAnalyzer()
25
- rag_handler_instance = get_rag_handler()
26
- MODELS_AVAILABLE = True
27
- logger.info("✅ Modèles d'analyse et RAG pré-chargés avec succès")
28
- except Exception as e:
29
- logger.error(f"❌ Erreur lors du pré-chargement des modèles: {e}")
30
- MODELS_AVAILABLE = False
31
- analyzer_model = None
32
- rag_handler_instance = None
33
- run_interview_analysis = None
34
-
35
-
36
- try:
37
- from src.cv_parsing_agents import OptimizedCvParserAgent, create_fallback_cv_data
38
- CV_PARSING_AVAILABLE = True
39
- logger.info("✅ CV Parsing disponible")
40
- except Exception as e:
41
- logger.error(f"❌ CV Parsing indisponible: {e}")
42
- CV_PARSING_AVAILABLE = False
43
- CvParserAgent = None
44
- create_fallback_cv_data = None
45
-
46
- try:
47
- from src.interview_simulator.entretient_version_prod import InterviewProcessor
48
- INTERVIEW_AVAILABLE = True
49
- logger.info("✅ Interview Simulator disponible")
50
- except Exception as e:
51
- logger.error(f"❌ Interview Simulator indisponible: {e}")
52
- INTERVIEW_AVAILABLE = False
53
- InterviewProcessor = None
54
-
55
- try:
56
- from src.scoring_engine import ContextualScoringEngine
57
- SCORING_AVAILABLE = True
58
- logger.info("✅ Scoring Engine disponible")
59
- except Exception as e:
60
- logger.error(f"❌ Scoring Engine indisponible: {e}")
61
- SCORING_AVAILABLE = False
62
- ContextualScoringEngine = None
63
-
64
  app = FastAPI(
65
  title="AIrh Interview Assistant",
66
  description="API pour l'analyse de CV et la simulation d'entretiens d'embauche avec analyse asynchrone.",
@@ -77,6 +31,12 @@ app.add_middleware(
77
  allow_headers=["*"],
78
  )
79
 
 
 
 
 
 
 
80
  class InterviewRequest(BaseModel):
81
  user_id: str = Field(..., example="user_12345")
82
  job_offer_id: str = Field(..., example="job_offer_abcde")
@@ -94,135 +54,79 @@ class HealthCheck(BaseModel):
94
  services: Dict[str, bool] = Field(default_factory=dict)
95
  message: str = "API AIrh fonctionnelle"
96
 
97
- def analysis_in_background(user_id: str, conversation_history: list, job_description_text: str):
98
- """
99
- Fonction exécutée en arrière-plan pour analyser l'entretien
100
- et sauvegarder le résultat.
101
- """
102
- logger.info(f"Démarrage de l'analyse en arrière-plan pour l'utilisateur: {user_id}")
103
- try:
104
- if not MODELS_AVAILABLE:
105
- raise RuntimeError("Les modèles d'analyse ne sont pas disponibles.")
106
-
107
- report = run_interview_analysis(
108
- conversation_history,
109
- job_description_text,
110
- analyzer_model,
111
- rag_handler_instance
112
- )
113
-
114
- feedback_path = f"/tmp/feedbacks/{user_id}.json"
115
- with open(feedback_path, "w", encoding="utf-8") as f:
116
- json.dump({"status": "completed", "feedback_data": report}, f, ensure_ascii=False, indent=4)
117
-
118
- logger.info(f"✅ Analyse terminée et sauvegardée pour l'utilisateur: {user_id}")
119
- except Exception as e:
120
- logger.error(f"❌ Erreur durant l'analyse en arrière-plan pour {user_id}: {e}")
121
- feedback_path = f"/tmp/feedbacks/{user_id}.json"
122
- with open(feedback_path, "w", encoding="utf-8") as f:
123
- json.dump({"status": "error", "feedback_data": str(e)}, f, ensure_ascii=False, indent=4)
124
 
125
  @app.get("/", response_model=HealthCheck, tags=["Status"])
126
  async def health_check():
127
- """Health check de l'API."""
128
  services = {
129
- "models_loaded": MODELS_AVAILABLE,
130
- "cv_parsing": CV_PARSING_AVAILABLE,
131
- "interview_simulation": INTERVIEW_AVAILABLE,
132
- "scoring_engine": SCORING_AVAILABLE
133
  }
134
  return HealthCheck(services=services)
135
 
136
  @app.post("/parse-cv/", tags=["CV Parsing"])
137
  async def parse_cv(file: UploadFile = File(...)):
138
- """Analyse un CV PDF et extrait les informations structurées."""
139
  if file.content_type != "application/pdf":
140
  raise HTTPException(status_code=400, detail="Fichier PDF requis")
141
 
142
- tmp_path = None
143
- try:
144
- contents = await file.read()
145
- with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
146
- tmp.write(contents)
147
- tmp_path = tmp.name
148
-
149
- cv_agent = CvParserAgent(pdf_path=tmp_path)
150
- parsed_data = await run_in_threadpool(cv_agent.process)
151
- if SCORING_AVAILABLE and ContextualScoringEngine and parsed_data:
152
- try:
153
- scoring_engine = ContextualScoringEngine(parsed_data)
154
- scored_data = await run_in_threadpool(scoring_engine.calculate_scores)
155
- if parsed_data.get("candidat"):
156
- parsed_data["candidat"].update(scored_data)
157
- except Exception as e:
158
- logger.warning(f"Scoring échoué: {e}")
159
-
160
- return parsed_data
161
-
162
- except Exception as e:
163
- logger.error(f"Erreur parsing CV: {e}")
164
- raise HTTPException(status_code=500, detail=str(e))
165
 
166
- finally:
167
- if tmp_path and os.path.exists(tmp_path):
168
- os.remove(tmp_path)
169
 
170
  @app.post("/simulate-interview/", tags=["Interview"])
171
  async def simulate_interview(request: InterviewRequest, background_tasks: BackgroundTasks):
172
- """
173
- Gère une conversation d'entretien. Si la conversation se termine,
174
- lance une analyse en arrière-plan.
175
- """
176
- if not INTERVIEW_AVAILABLE or not MODELS_AVAILABLE:
177
- raise HTTPException(status_code=503, detail="Service de simulation ou modèles indisponibles")
 
178
 
179
- try:
180
- processor = InterviewProcessor(
181
- cv_document=request.cv_document,
182
- job_offer=request.job_offer,
183
- conversation_history=request.conversation_history
 
 
 
 
184
  )
185
-
186
- result = await run_in_threadpool(processor.run, messages=request.messages)
187
-
188
- response_content = result["messages"][-1].content
189
-
190
- # Déclencher l'analyse si l'entretien est terminé
191
- if "nous allons maintenant passer a l'analyse" in response_content.lower():
192
- logger.info(f"Fin d'entretien détectée pour {request.user_id}. Lancement de l'analyse en arrière-plan.")
193
-
194
- # Sauvegarder un statut initial
195
- feedback_path = f"/tmp/feedbacks/{request.user_id}.json"
196
- with open(feedback_path, "w", encoding="utf-8") as f:
197
- json.dump({"status": "processing"}, f, ensure_ascii=False, indent=4)
198
-
199
- job_description = request.job_offer.get('description', '')
200
- background_tasks.add_task(
201
- analysis_in_background,
202
- request.user_id,
203
- request.conversation_history + request.messages,
204
- job_description
205
- )
206
-
207
- return {"response": response_content}
208
-
209
- except Exception as e:
210
- logger.error(f"Erreur simulation entretien: {e}")
211
- raise HTTPException(status_code=500, detail=str(e))
212
 
213
  @app.get("/get-feedback/{user_id}", response_model=Feedback, tags=["Analysis"])
214
  async def get_feedback(user_id: str):
215
- """Récupère le résultat de l'analyse post-entretien."""
216
  feedback_path = f"/tmp/feedbacks/{user_id}.json"
 
217
  if not os.path.exists(feedback_path):
218
  raise HTTPException(status_code=404, detail="Feedback non trouvé ou non encore traité.")
219
 
220
- try:
221
- with open(feedback_path, "r", encoding="utf-8") as f:
222
- data = json.load(f)
223
- return Feedback(**data)
224
- except Exception as e:
225
- raise HTTPException(status_code=500, detail=f"Erreur à la lecture du feedback: {e}")
226
 
227
  if __name__ == "__main__":
228
  import uvicorn
 
1
  import tempfile
 
2
  import os
 
3
  import json
4
  from fastapi import FastAPI, UploadFile, File, HTTPException, BackgroundTasks
5
  from fastapi.concurrency import run_in_threadpool
 
7
  from pydantic import BaseModel, Field
8
  from typing import List, Dict, Any, Optional
9
 
10
+ from src.models import load_all_models
11
+ from src.services.cv_service import CVParsingService
12
+ from src.services.interview_service import InterviewService
13
+ from src.services.analysis_service import AnalysisService
14
+
15
  os.environ['HOME'] = '/tmp'
16
  os.makedirs('/tmp/feedbacks', exist_ok=True)
17
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
18
  app = FastAPI(
19
  title="AIrh Interview Assistant",
20
  description="API pour l'analyse de CV et la simulation d'entretiens d'embauche avec analyse asynchrone.",
 
31
  allow_headers=["*"],
32
  )
33
 
34
+ models = load_all_models()
35
+
36
+ cv_service = CVParsingService(models)
37
+ interview_service = InterviewService(models)
38
+ analysis_service = AnalysisService(models)
39
+
40
  class InterviewRequest(BaseModel):
41
  user_id: str = Field(..., example="user_12345")
42
  job_offer_id: str = Field(..., example="job_offer_abcde")
 
54
  services: Dict[str, bool] = Field(default_factory=dict)
55
  message: str = "API AIrh fonctionnelle"
56
 
57
+ def background_analysis_task(user_id: str, conversation_history: list, job_description: str):
58
+ feedback_path = f"/tmp/feedbacks/{user_id}.json"
59
+
60
+ with open(feedback_path, "w", encoding="utf-8") as f:
61
+ json.dump({"status": "processing"}, f, ensure_ascii=False, indent=4)
62
+
63
+ result = analysis_service.run_analysis(conversation_history, job_description)
64
+
65
+ with open(feedback_path, "w", encoding="utf-8") as f:
66
+ json.dump({"status": "completed", "feedback_data": result}, f, ensure_ascii=False, indent=4)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
  @app.get("/", response_model=HealthCheck, tags=["Status"])
69
  async def health_check():
 
70
  services = {
71
+ "models_loaded": models.get("status", False),
72
+ "cv_parsing": True,
73
+ "interview_simulation": True,
74
+ "scoring_engine": True
75
  }
76
  return HealthCheck(services=services)
77
 
78
  @app.post("/parse-cv/", tags=["CV Parsing"])
79
  async def parse_cv(file: UploadFile = File(...)):
 
80
  if file.content_type != "application/pdf":
81
  raise HTTPException(status_code=400, detail="Fichier PDF requis")
82
 
83
+ contents = await file.read()
84
+
85
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".pdf") as tmp:
86
+ tmp.write(contents)
87
+ tmp_path = tmp.name
88
+
89
+ result = await run_in_threadpool(cv_service.parse_cv, tmp_path)
90
+
91
+ if os.path.exists(tmp_path):
92
+ os.remove(tmp_path)
 
 
 
 
 
 
 
 
 
 
 
 
 
93
 
94
+ return result
 
 
95
 
96
  @app.post("/simulate-interview/", tags=["Interview"])
97
  async def simulate_interview(request: InterviewRequest, background_tasks: BackgroundTasks):
98
+ result = await run_in_threadpool(
99
+ interview_service.process_conversation,
100
+ request.cv_document,
101
+ request.job_offer,
102
+ request.conversation_history,
103
+ request.messages
104
+ )
105
 
106
+ response_content = result["response"]
107
+
108
+ if "nous allons maintenant passer a l'analyse" in response_content.lower():
109
+ job_description = request.job_offer.get('description', '')
110
+ background_tasks.add_task(
111
+ background_analysis_task,
112
+ request.user_id,
113
+ request.conversation_history + request.messages,
114
+ job_description
115
  )
116
+
117
+ return {"response": response_content}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
118
 
119
  @app.get("/get-feedback/{user_id}", response_model=Feedback, tags=["Analysis"])
120
  async def get_feedback(user_id: str):
 
121
  feedback_path = f"/tmp/feedbacks/{user_id}.json"
122
+
123
  if not os.path.exists(feedback_path):
124
  raise HTTPException(status_code=404, detail="Feedback non trouvé ou non encore traité.")
125
 
126
+ with open(feedback_path, "r", encoding="utf-8") as f:
127
+ data = json.load(f)
128
+
129
+ return Feedback(**data)
 
 
130
 
131
  if __name__ == "__main__":
132
  import uvicorn