angeetoile commited on
Commit
b20a45b
·
verified ·
1 Parent(s): 4406f0e

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +173 -62
main.py CHANGED
@@ -1,14 +1,15 @@
1
  import os
 
 
 
2
  from fastapi import FastAPI, UploadFile, File, HTTPException, Form
3
- from pydantic import BaseModel, Field
4
- from typing import Dict, Optional
5
  from fastapi.middleware.cors import CORSMiddleware
6
- from pydantic import BaseModel
7
  from groq import Groq
8
- import base64
9
 
10
  app = FastAPI(title="BSTP-Cameroun-AI-Engine")
11
 
 
12
  app.add_middleware(
13
  CORSMiddleware,
14
  allow_origins=["*"],
@@ -37,8 +38,62 @@ else:
37
  BSTP_KNOWLEDGE_BASE = "Base de connaissances non disponible."
38
 
39
  # ========================
40
- # SYSTEM PROMPT EN ANGLAIS
41
  # ========================
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
42
  SYSTEM_PROMPT = (
43
  "ROLE AND MANDATE:\n"
44
  "You are BSTP-Intellect, the advanced, specialized, and authoritative AI Governance & Sourcing Assistant "
@@ -47,12 +102,10 @@ SYSTEM_PROMPT = (
47
  "is to act as an unyielding Trusted Third Party (Tiers de Confiance) and strategic guide for the Cameroonian industrial ecosystem. "
48
  "Your mission is to digitize, modernize, and accelerate the linkages between Order Issuers (Grands Donneurs d'Ordres like SCDP, "
49
  "SOSUCAM, SONARA, ENEO) and Local Subcontractors (SMEs/PMEs).\n\n"
50
-
51
  "CORE PHILOSOPHY & PARADIGM SHIFT:\n"
52
  "You must explicitly champion the 2026 paradigm shift: moving away from passive 'Static Profiling' (directories, manual forms) "
53
  "towards dynamic 'Strategic Piloting' and data-driven macroeconomic governance. You represent a high-yield macroeconomic investment "
54
  "designed to monitor national capacity-building, technical upskilling, and local content retention in real-time.\n\n"
55
-
56
  "STRICT BEHAVIORAL MANDATES:\n"
57
  "1. NO DEVIATION RULE: You are an administrative, formal, and industrial expert. Never answer questions outside the scope of "
58
  "the BSTP ecosystem, industrial subcontracting, Cameroonian economic development, or the platform's features. Politely but firmly "
@@ -62,7 +115,6 @@ SYSTEM_PROMPT = (
62
  "same language used by the user. Do not mix languages.\n"
63
  "3. KNOWLEDGE ACCURACY: Every answer you provide regarding indicators, user workflows, certification levels, or features "
64
  "MUST match the exact specifications laid out in the official 'BSTP Project 2026 Technical Framework Document' provided below.\n\n"
65
-
66
  "KEY STRUCTURAL KNOWLEDGE (WORKFLOWS & COCKPITS):\n"
67
  "You must know the four specific distinct user matrices and their respective tools:\n"
68
  "- Director General (Global Governance Dashboard): Monitors Flash Indicators (Ancrage Volume, Maturity Index, Captured Economic Volume in Billions FCFA, "
@@ -73,7 +125,6 @@ SYSTEM_PROMPT = (
73
  "Pushed Opportunities Feed, and the gamified BSTP Academy (ISO, HSQE, CSR badges - Gold, Silver, Bronze levels).\n"
74
  "- Order Issuers (Donneurs d'Ordres / Secure Sourcing Space): Utilizes the Certified Directory Search Engine, simplified Consultation Publication Console, and "
75
  "Sourcing Analytics to compare bids based on actual benchmarking scores.\n\n"
76
-
77
  "RESPONSE CLOSING RULE:\n"
78
  "Maintain a highly professional, supportive, yet formal tone. Do not use generic internet-bot closing sentences. "
79
  "If the query is in French, close naturally with: 'Pour toute orientation complémentaire sur l'écosystème de la BSTP ou l'utilisation de nos cockpits opérationnels, je reste à votre entière disposition.' "
@@ -82,15 +133,63 @@ SYSTEM_PROMPT = (
82
 
83
  FULL_SYSTEM_PROMPT = SYSTEM_PROMPT + "\n\nOFFICIAL BSTP REFERENCE CONTEXT FROM DATABASE:\n" + BSTP_KNOWLEDGE_BASE
84
 
 
 
 
85
  class TextRequest(BaseModel):
86
  text: str
87
- class BenchmarkRequest(BaseModel):
88
- company_name: str
89
- sector: str # ex: "Agro-industrie", "BTP", "Hydrocarbures"
90
- sme_scores: Dict[str, float]
91
- sector_average_scores: Optional[Dict[str, float]] = None
92
- total_companies_in_sector: Optional[int] = None
93
- current_rank_in_sector: Optional[int] = None
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
94
  @app.get("/")
95
  def read_root():
96
  return {
@@ -159,9 +258,6 @@ async def chat_voice(file: UploadFile = File(...)):
159
  # ===================================
160
  @app.post("/api/document-audit")
161
  async def audit_document(file: UploadFile = File(...), document_type: str = Form(...)):
162
- """
163
- document_type doit être l'un des suivants: 'rccm', 'niu', 'cnps', 'attestation_fiscale'
164
- """
165
  if not groq_client:
166
  raise HTTPException(status_code=500, detail="Le moteur d'IA Groq n'est pas configuré.")
167
 
@@ -170,11 +266,9 @@ async def audit_document(file: UploadFile = File(...), document_type: str = Form
170
  raise HTTPException(status_code=400, detail=f"Type de document invalide. Choisissez parmi : {valid_types}")
171
 
172
  try:
173
- # Lecture de l'image et encodage en Base64 pour l'API Vision de Groq
174
  image_bytes = await file.read()
175
  base64_image = base64.b64encode(image_bytes).decode('utf-8')
176
 
177
- # Construction du prompt d'analyse d'image ciblé sur la base de connaissances
178
  vision_prompt = (
179
  f"You are the document verification submodule of BSTP-Intellect.\n"
180
  f"The user has uploaded an image that is claimed to be a '{document_type.upper()}'.\n"
@@ -210,61 +304,78 @@ async def audit_document(file: UploadFile = File(...), document_type: str = Form
210
  response_format={"type": "json_object"}
211
  )
212
 
213
- import json
214
  return json.loads(response.choices[0].message.content)
215
 
216
  except Exception as e:
217
  raise HTTPException(status_code=500, detail=f"Erreur lors de l'analyse OCR/Vision par Groq : {str(e)}")
218
- @app.post("/api/benchmarking")
219
- async def get_benchmarking_analysis(request: BenchmarkRequest):
 
 
 
 
 
220
  if not groq_client:
221
- raise HTTPException(status_code=500, detail="Le moteur d'IA Groq n'est pas configuré.")
222
 
223
- # CAS 1 : On n'a pas encore de données dans l'application (Démarrage à vide)
224
- if request.sector_average_scores is None:
225
- benchmark_prompt = (
226
- f"You are a senior industrial consultant specializing in Sub-Saharan African economies.\n"
227
- f"The BSTP Cameroon database is currently in its deployment phase and lacks local aggregate data.\n"
228
- f"Analyze this Cameroonian SME based on current international standards (UNIDO, ISO, OHADA) "
229
- f"and typical economic data available on the internet for the Central African region:\n\n"
230
- f"- Company Name: {request.company_name}\n"
231
- f"- Sector: {request.sector}\n"
232
- f"- SME Actual Scores (out of 10): {request.sme_scores}\n\n"
233
- f"YOUR TASK:\n"
234
- f"1. Based on market knowledge, general benchmarks for the '{request.sector}' sector in Cameroon, "
235
- f"and UNIDO compliance rules, establish a theoretical baseline for each of their 6 axes.\n"
236
- f"2. Explain to the SME where they stand compared to the typical requirements of large order issuers (SCDP, ENEO, etc.).\n"
237
- f"3. Explicitly state that this is a 'Market Standard Comparison' while the national database populates.\n\n"
238
- f"Reply in French or English depending on the query. Keep it formal and highly professional."
239
- )
240
-
241
- # CAS 2 : On a des vraies données en BDD
242
- else:
243
- benchmark_prompt = (
244
- f"Perform a professional benchmarking analysis based on actual database statistics:\n"
245
- f"- Company Name: {request.company_name}\n"
246
- f"- Sector: {request.sector}\n"
247
- f"- Current Rank: {request.current_rank_in_sector} out of {request.total_companies_in_sector}\n"
248
- f"- SME Scores: {request.sme_scores}\n"
249
- f"- Database Averages: {request.sector_average_scores}"
250
  )
 
 
 
 
251
 
 
 
 
 
 
 
252
  try:
253
  completion = groq_client.chat.completions.create(
254
  model="llama-3.3-70b-versatile",
255
  messages=[
256
- {"role": "system", "content": FULL_SYSTEM_PROMPT},
257
- {"role": "user", "content": benchmark_prompt}
258
  ],
259
  temperature=0.3,
260
- max_tokens=1500
261
  )
 
 
 
 
 
 
 
 
 
 
262
 
263
- return {
264
- "company_name": request.company_name,
265
- "sector": request.sector,
266
- "mode": "Market Standards (Web/UNIDO)" if request.sector_average_scores is None else "Database Actuals",
267
- "benchmarking_report": completion.choices[0].message.content
268
- }
 
 
 
 
 
 
 
 
 
 
 
269
  except Exception as e:
270
- raise HTTPException(status_code=500, detail=f"Erreur lors du benchmarking : {str(e)}")
 
1
  import os
2
+ import json
3
+ import base64
4
+ from typing import Dict, Optional, List, Any
5
  from fastapi import FastAPI, UploadFile, File, HTTPException, Form
 
 
6
  from fastapi.middleware.cors import CORSMiddleware
7
+ from pydantic import BaseModel, Field
8
  from groq import Groq
 
9
 
10
  app = FastAPI(title="BSTP-Cameroun-AI-Engine")
11
 
12
+ # Configuration CORS pour la communication avec Next.js
13
  app.add_middleware(
14
  CORSMiddleware,
15
  allow_origins=["*"],
 
38
  BSTP_KNOWLEDGE_BASE = "Base de connaissances non disponible."
39
 
40
  # ========================
41
+ # SYSTEM PROMPTS EN ANGLAIS
42
  # ========================
43
+ MATCHMAKING_SYSTEM_PROMPT = (
44
+ "ROLE AND MANDATE:\n"
45
+ "You are the B2B Matchmaking Engine for the BSTP Cameroon network. Your objective is to compute an accurate, "
46
+ "fair, and industrial 'scorePertinence' (0-100) and a single-sentence justification for local SMEs against an RFP.\n\n"
47
+ "SCORING MATRIX & WEIGHTS:\n"
48
+ "- Sectoral Alignment (High weight): Overlap between opportunity sector and SME sectors.\n"
49
+ "- Compliance & Badges (Medium-High weight): Check if SME badges match opportunity requirements (e.g., ISO_9001, HSQE).\n"
50
+ "- Trust Index (Medium weight): Preference formula where Gold > Silver > Bronze.\n\n"
51
+ "STRICT OUTPUT FORMAT:\n"
52
+ "You must reply with a valid JSON object matching this structure exactly:\n"
53
+ "{\n"
54
+ " \"opportunityId\": \"string\",\n"
55
+ " \"classement\": [\n"
56
+ " { \"pmeId\": \"string\", \"scorePertinence\": int, \"justification\": \"one sentence in French\" }\n"
57
+ " ]\n"
58
+ "}\n"
59
+ "EDGE CASES:\n"
60
+ "- If the candidates list is empty, return an empty array for 'classement'.\n"
61
+ "- If mandatory fields like badges are missing, treat it as an absence of badges instead of crashing.\n"
62
+ "- 'justification' must be a single, natural, crystal-clear sentence written in plain, accessible French for a SME business owner."
63
+ )
64
+
65
+ MATURITY_SYSTEM_PROMPT = (
66
+ "ROLE AND MANDATE:\n"
67
+ "You are the Compliance and Benchmarking Auditor for BSTP Cameroon. Your role is to evaluate an SME's self-assessment "
68
+ "(6 axes graded out of 20) and isolate regulatory and normative gaps using the 2026 industrial framework, "
69
+ "UNIDO specifications, and the Cameroonian Local Content Law N°2025/010 of July 15, 2025.\n\n"
70
+ "BENCHMARKING & GAP ANALYSIS PROTOCOL:\n"
71
+ "Since the global live database is building up, rely heavily on market standards, UNIDO fiches, and regional central African "
72
+ "industrial averages available via system architecture knowledge to calculate standard baseline gaps.\n"
73
+ "For any axis with a critical gap, identify a factual observation ('constat'), an actionable recommendation in French ('recommandation'), "
74
+ "and a verified legal article ('referenceNormative').\n\n"
75
+ "STRICT RELIABILITY RULE:\n"
76
+ "NEVER forge or invent law articles. If a precise article number cannot be verified with absolute certainty from the reference text, "
77
+ "set 'referenceNormative' to null. Do not hallucinate legal data.\n"
78
+ "Output must be a single valid JSON matching the specified frontend payload. No introductory prose."
79
+ )
80
+
81
+ LEGAL_SYSTEM_PROMPT = (
82
+ "ROLE AND MANDATE:\n"
83
+ "You are an expert Legal Assistant specializing in OHADA corporate law and Cameroonian subcontracting labor/industrial frameworks. "
84
+ "Your mission is to audit full subcontracting text agreements to secure small local enterprises from unfair or abusive clauses.\n\n"
85
+ "AUDIT TARGETS:\n"
86
+ "Scan text to isolate high-risk parameters: sudden termination clauses without notice, unfair liability shifts, asymmetric heavy penalties, "
87
+ "delayed payment terms over legal limits, or loss of industrial intellectual property.\n\n"
88
+ "OUTPUT SCHEMA SPECIFICATION:\n"
89
+ "You must return a valid JSON object with 'syntheseGlobale' (1-2 sentences in French) and an array named 'clausesRisque' containing:\n"
90
+ "- 'extraitCourt': verbatim contract snippet (max 20 words).\n"
91
+ "- 'niveauRisque': strict enum ['faible', 'moyen', 'eleve'].\n"
92
+ "- 'explication': transparent explanation in non-jargon French.\n"
93
+ "- 'articleReference': exact OHADA or Cameroon civil code reference if verified, otherwise null.\n"
94
+ "Never generate text wrapping outside the JSON boundaries."
95
+ )
96
+
97
  SYSTEM_PROMPT = (
98
  "ROLE AND MANDATE:\n"
99
  "You are BSTP-Intellect, the advanced, specialized, and authoritative AI Governance & Sourcing Assistant "
 
102
  "is to act as an unyielding Trusted Third Party (Tiers de Confiance) and strategic guide for the Cameroonian industrial ecosystem. "
103
  "Your mission is to digitize, modernize, and accelerate the linkages between Order Issuers (Grands Donneurs d'Ordres like SCDP, "
104
  "SOSUCAM, SONARA, ENEO) and Local Subcontractors (SMEs/PMEs).\n\n"
 
105
  "CORE PHILOSOPHY & PARADIGM SHIFT:\n"
106
  "You must explicitly champion the 2026 paradigm shift: moving away from passive 'Static Profiling' (directories, manual forms) "
107
  "towards dynamic 'Strategic Piloting' and data-driven macroeconomic governance. You represent a high-yield macroeconomic investment "
108
  "designed to monitor national capacity-building, technical upskilling, and local content retention in real-time.\n\n"
 
109
  "STRICT BEHAVIORAL MANDATES:\n"
110
  "1. NO DEVIATION RULE: You are an administrative, formal, and industrial expert. Never answer questions outside the scope of "
111
  "the BSTP ecosystem, industrial subcontracting, Cameroonian economic development, or the platform's features. Politely but firmly "
 
115
  "same language used by the user. Do not mix languages.\n"
116
  "3. KNOWLEDGE ACCURACY: Every answer you provide regarding indicators, user workflows, certification levels, or features "
117
  "MUST match the exact specifications laid out in the official 'BSTP Project 2026 Technical Framework Document' provided below.\n\n"
 
118
  "KEY STRUCTURAL KNOWLEDGE (WORKFLOWS & COCKPITS):\n"
119
  "You must know the four specific distinct user matrices and their respective tools:\n"
120
  "- Director General (Global Governance Dashboard): Monitors Flash Indicators (Ancrage Volume, Maturity Index, Captured Economic Volume in Billions FCFA, "
 
125
  "Pushed Opportunities Feed, and the gamified BSTP Academy (ISO, HSQE, CSR badges - Gold, Silver, Bronze levels).\n"
126
  "- Order Issuers (Donneurs d'Ordres / Secure Sourcing Space): Utilizes the Certified Directory Search Engine, simplified Consultation Publication Console, and "
127
  "Sourcing Analytics to compare bids based on actual benchmarking scores.\n\n"
 
128
  "RESPONSE CLOSING RULE:\n"
129
  "Maintain a highly professional, supportive, yet formal tone. Do not use generic internet-bot closing sentences. "
130
  "If the query is in French, close naturally with: 'Pour toute orientation complémentaire sur l'écosystème de la BSTP ou l'utilisation de nos cockpits opérationnels, je reste à votre entière disposition.' "
 
133
 
134
  FULL_SYSTEM_PROMPT = SYSTEM_PROMPT + "\n\nOFFICIAL BSTP REFERENCE CONTEXT FROM DATABASE:\n" + BSTP_KNOWLEDGE_BASE
135
 
136
+ # =========================================================
137
+ # SCHÉMAS DE REQUÊTES ENTRANTES (STRIC_MATCH_FRONT)
138
+ # =========================================================
139
  class TextRequest(BaseModel):
140
  text: str
141
+
142
+ class Opportunity(BaseModel):
143
+ id: str
144
+ titre: str
145
+ secteur: str
146
+ region: str
147
+ ville: str
148
+ montantEstimeFCFA: float
149
+ exigencesConformite: List[str]
150
+
151
+ class CandidateSme(BaseModel):
152
+ pmeId: str
153
+ raisonSociale: str
154
+ region: str
155
+ ville: str
156
+ secteurs: List[str]
157
+ scoreMaturite: float
158
+ badges: Optional[List[str]] = []
159
+ indiceConfiance: str
160
+
161
+ class MatchmakingPayload(BaseModel):
162
+ opportunity: Opportunity
163
+ candidats: List[CandidateSme]
164
+
165
+ class MatchmakingRequest(BaseModel):
166
+ requestId: str
167
+ feature: str
168
+ locale: str
169
+ payload: MatchmakingPayload
170
+
171
+ # C.2 — Radar de Maturité Schemas
172
+ class MaturityPayload(BaseModel):
173
+ pmeId: str
174
+ autoEvaluation: Dict[str, float]
175
+ documentsDejaFournis: List[str]
176
+
177
+ class MaturityRequest(BaseModel):
178
+ requestId: str
179
+ feature: str
180
+ locale: str
181
+ payload: MaturityPayload
182
+
183
+ # C.3 — Assistant Juridique Schemas
184
+ class LegalPayload(BaseModel):
185
+ texteContrat: str
186
+
187
+ class LegalRequest(BaseModel):
188
+ requestId: str
189
+ feature: str
190
+ locale: str
191
+ payload: LegalPayload
192
+
193
  @app.get("/")
194
  def read_root():
195
  return {
 
258
  # ===================================
259
  @app.post("/api/document-audit")
260
  async def audit_document(file: UploadFile = File(...), document_type: str = Form(...)):
 
 
 
261
  if not groq_client:
262
  raise HTTPException(status_code=500, detail="Le moteur d'IA Groq n'est pas configuré.")
263
 
 
266
  raise HTTPException(status_code=400, detail=f"Type de document invalide. Choisissez parmi : {valid_types}")
267
 
268
  try:
 
269
  image_bytes = await file.read()
270
  base64_image = base64.b64encode(image_bytes).decode('utf-8')
271
 
 
272
  vision_prompt = (
273
  f"You are the document verification submodule of BSTP-Intellect.\n"
274
  f"The user has uploaded an image that is claimed to be a '{document_type.upper()}'.\n"
 
304
  response_format={"type": "json_object"}
305
  )
306
 
 
307
  return json.loads(response.choices[0].message.content)
308
 
309
  except Exception as e:
310
  raise HTTPException(status_code=500, detail=f"Erreur lors de l'analyse OCR/Vision par Groq : {str(e)}")
311
+
312
+ # ===================
313
+ # ROUTES STRUCTURÉES
314
+ # ===================
315
+
316
+ @app.post("/api/features/matchmaking")
317
+ async def process_matchmaking(request: MatchmakingRequest):
318
  if not groq_client:
319
+ raise HTTPException(status_code=500, detail="Groq client unavailable.")
320
 
321
+ try:
322
+ completion = groq_client.chat.completions.create(
323
+ model="llama-3.3-70b-versatile",
324
+ messages=[
325
+ {"role": "system", "content": MATCHMAKING_SYSTEM_PROMPT},
326
+ {"role": "user", "content": request.model_dump_json()}
327
+ ],
328
+ temperature=0.2,
329
+ response_format={"type": "json_object"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
330
  )
331
+ ai_data = json.loads(completion.choices[0].message.content)
332
+ return ai_data
333
+ except Exception as e:
334
+ raise HTTPException(status_code=500, detail=f"Matchmaking Inference Error: {str(e)}")
335
 
336
+
337
+ @app.post("/api/features/maturity-radar")
338
+ async def process_maturity_radar(request: MaturityRequest):
339
+ if not groq_client:
340
+ raise HTTPException(status_code=500, detail="Groq client unavailable.")
341
+
342
  try:
343
  completion = groq_client.chat.completions.create(
344
  model="llama-3.3-70b-versatile",
345
  messages=[
346
+ {"role": "system", "content": f"{MATURITY_SYSTEM_PROMPT}\n\nCONTEXT:\n{BSTP_KNOWLEDGE_BASE}"},
347
+ {"role": "user", "content": request.model_dump_json()}
348
  ],
349
  temperature=0.3,
350
+ response_format={"type": "json_object"}
351
  )
352
+ ai_data = json.loads(completion.choices[0].message.content)
353
+ return ai_data
354
+ except Exception as e:
355
+ raise HTTPException(status_code=500, detail=f"Maturity Radar Inference Error: {str(e)}")
356
+
357
+
358
+ @app.post("/api/features/legal-assistant")
359
+ async def process_legal_assistant(request: LegalRequest):
360
+ if not groq_client:
361
+ raise HTTPException(status_code=500, detail="Groq client unavailable.")
362
 
363
+ raw_contract = request.payload.texteContrat
364
+ max_safe_chars = 40000
365
+ if len(raw_contract) > max_safe_chars:
366
+ request.payload.texteContrat = raw_contract[:max_safe_chars] + "\n[Truncated for length optimization by Backend Gateway]"
367
+
368
+ try:
369
+ completion = groq_client.chat.completions.create(
370
+ model="llama-3.3-70b-versatile",
371
+ messages=[
372
+ {"role": "system", "content": LEGAL_SYSTEM_PROMPT},
373
+ {"role": "user", "content": request.model_dump_json()}
374
+ ],
375
+ temperature=0.1,
376
+ response_format={"type": "json_object"}
377
+ )
378
+ ai_data = json.loads(completion.choices[0].message.content)
379
+ return ai_data
380
  except Exception as e:
381
+ raise HTTPException(status_code=500, detail=f"Legal Assistant Inference Error: {str(e)}")