Update main.py
Browse files
main.py
CHANGED
|
@@ -230,6 +230,205 @@ Example format for "Recommended Interventions":
|
|
| 230 |
except Exception as e:
|
| 231 |
return {"error": f"Unexpected error: {str(e)}", "raw_response": response_text}
|
| 232 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 233 |
|
| 234 |
|
| 235 |
|
|
@@ -697,6 +896,87 @@ def get_shortlist():
|
|
| 697 |
}), 500
|
| 698 |
|
| 699 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 700 |
|
| 701 |
|
| 702 |
# --------- Run the App ---------
|
|
|
|
| 230 |
except Exception as e:
|
| 231 |
return {"error": f"Unexpected error: {str(e)}", "raw_response": response_text}
|
| 232 |
|
| 233 |
+
# Lepharo interventions structure
|
| 234 |
+
lepharo_interventions_offered = {
|
| 235 |
+
"ROM (Recruitment, Onboarding, and Maintenance)": [
|
| 236 |
+
"Gap Analysis",
|
| 237 |
+
"SMME Onboarding Induction",
|
| 238 |
+
"Compliance Document Verification",
|
| 239 |
+
"Developmental Plan"
|
| 240 |
+
],
|
| 241 |
+
"HSE (Health, Safety & Environment) and Labour Compliance": [
|
| 242 |
+
"UIF Compliance Training",
|
| 243 |
+
"UIF Registration",
|
| 244 |
+
"COID Compliance Training",
|
| 245 |
+
"COID Registration",
|
| 246 |
+
"COID Annual Renewal",
|
| 247 |
+
"Employment Contract Collection",
|
| 248 |
+
"ID Copy Collection",
|
| 249 |
+
"Health & Safety File",
|
| 250 |
+
"HSE & Labour Newsletter",
|
| 251 |
+
"Risk Management Information Session",
|
| 252 |
+
"HSE/Labour Compliance Workshop"
|
| 253 |
+
],
|
| 254 |
+
"Financial Compliance": [
|
| 255 |
+
"Business Planning",
|
| 256 |
+
"Budgeting & Financial Planning",
|
| 257 |
+
"Bookkeeping & Accounting",
|
| 258 |
+
"Taxation & Compliance Advisory",
|
| 259 |
+
"Financial Analysis & Reporting",
|
| 260 |
+
"Funding Linkage"
|
| 261 |
+
],
|
| 262 |
+
"PDS (Personal Development Services)": [
|
| 263 |
+
"Personal Insight Assessment",
|
| 264 |
+
"Psychometric Assessment",
|
| 265 |
+
"Personal Recommendation Report",
|
| 266 |
+
"Leadership Fundamentals Module",
|
| 267 |
+
"Communication Skills Module",
|
| 268 |
+
"Emotional Intelligence Module",
|
| 269 |
+
"Leadership Project",
|
| 270 |
+
"Mentorship Session"
|
| 271 |
+
],
|
| 272 |
+
"Market Linkages": [
|
| 273 |
+
"Stakeholder Company Sourcing",
|
| 274 |
+
"RFP/RFQ Response Support",
|
| 275 |
+
"Procurement Opportunity Identification",
|
| 276 |
+
"SMME Engagement Support",
|
| 277 |
+
"Open Day/Exhibition Participation",
|
| 278 |
+
"Aftercare Support"
|
| 279 |
+
],
|
| 280 |
+
"Legal Advisory Services": [
|
| 281 |
+
"Commercial Law Advisory",
|
| 282 |
+
"Labour Law Advisory",
|
| 283 |
+
"Business Law Advisory",
|
| 284 |
+
"Intellectual Property Advisory",
|
| 285 |
+
"BBBEE Compliance Support",
|
| 286 |
+
"Debt Collection Advisory",
|
| 287 |
+
"Company Tax Compliance Advisory",
|
| 288 |
+
"Digital Economy Legal Advisory",
|
| 289 |
+
"Cross-Border Transaction Advisory"
|
| 290 |
+
],
|
| 291 |
+
"Wellness Services": [
|
| 292 |
+
"Soft Skills Training",
|
| 293 |
+
"Counselling Session",
|
| 294 |
+
"Grief Support",
|
| 295 |
+
"Health Risk Assessment",
|
| 296 |
+
"Employee Wellness Newsletter"
|
| 297 |
+
],
|
| 298 |
+
"Training Academy – NVC (New Venture Creation)": [
|
| 299 |
+
"Maths in Business Module",
|
| 300 |
+
"Business Communication Module (1st Language)",
|
| 301 |
+
"Business Communication Module (2nd Language)",
|
| 302 |
+
"New Venture Creation Module",
|
| 303 |
+
"Leadership Skills Module",
|
| 304 |
+
"Business Ethics Module",
|
| 305 |
+
"Business Finance Management Module",
|
| 306 |
+
"Marketing Skills Module"
|
| 307 |
+
],
|
| 308 |
+
"Training Academy – QMS": [
|
| 309 |
+
"QMS Certification Training",
|
| 310 |
+
"ISO Standards Workshop"
|
| 311 |
+
],
|
| 312 |
+
"Marketing and Communication": [
|
| 313 |
+
"Logo Design",
|
| 314 |
+
"Website Development",
|
| 315 |
+
"Domain Hosting",
|
| 316 |
+
"Company Profile Design",
|
| 317 |
+
"Business Cards",
|
| 318 |
+
"Branded Golf Shirts",
|
| 319 |
+
"Pull-Up Banner",
|
| 320 |
+
"Marketing Collateral",
|
| 321 |
+
"Event Planning"
|
| 322 |
+
]
|
| 323 |
+
}
|
| 324 |
+
|
| 325 |
+
class LepharoEvaluator:
|
| 326 |
+
def __init__(self, available_interventions=None):
|
| 327 |
+
self.available_interventions = available_interventions or lepharo_interventions_offered
|
| 328 |
+
|
| 329 |
+
def generate_prompt(self, participant_info: dict) -> str:
|
| 330 |
+
# Create a simplified version of interventions for the prompt
|
| 331 |
+
interventions_json = json.dumps(self.available_interventions, indent=2)
|
| 332 |
+
|
| 333 |
+
prompt = f"""
|
| 334 |
+
You are an expert evaluator for Lepharo, a business development and compliance support organization in South Africa, reviewing candidate applications. Use your expertise, critical thinking, and judgment to assess the following applicant based on their business needs and development stage. There are no predefined criteria or weights — your evaluation should be holistic and based on the information provided.
|
| 335 |
+
|
| 336 |
+
Participant Info:
|
| 337 |
+
{json.dumps(participant_info, indent=2)}
|
| 338 |
+
|
| 339 |
+
Based on your assessment, provide:
|
| 340 |
+
1. "AI Recommendation": either "Accept" or "Reject"
|
| 341 |
+
2. "AI Score": a score out of 100 reflecting overall business quality or readiness
|
| 342 |
+
3. "Justification": a brief explanation for your decision (3-5 sentences)
|
| 343 |
+
4. "Recommended Interventions": Select 3-5 appropriate intervention categories and specific areas of support that would most benefit this business.
|
| 344 |
+
|
| 345 |
+
Available interventions:
|
| 346 |
+
{interventions_json}
|
| 347 |
+
|
| 348 |
+
Return your output strictly as a JSON dictionary with these keys:
|
| 349 |
+
- "AI Recommendation" (string: "Accept" or "Reject")
|
| 350 |
+
- "AI Score" (integer between 0-100)
|
| 351 |
+
- "Justification" (string)
|
| 352 |
+
- "Recommended Interventions" (object with intervention names as keys and arrays of specific areas of support as values)
|
| 353 |
+
- "intervention" (string: the primary intervention category recommended)
|
| 354 |
+
- "areaOfSupport" (string: the primary area of support recommended)
|
| 355 |
+
|
| 356 |
+
Example format for "Recommended Interventions":
|
| 357 |
+
{{
|
| 358 |
+
"HSE (Health, Safety & Environment) and Labour Compliance": [
|
| 359 |
+
"UIF Registration",
|
| 360 |
+
"Health & Safety File"
|
| 361 |
+
],
|
| 362 |
+
"Financial Compliance": [
|
| 363 |
+
"Business Planning",
|
| 364 |
+
"Taxation & Compliance Advisory"
|
| 365 |
+
]
|
| 366 |
+
}}
|
| 367 |
+
|
| 368 |
+
For "intervention" and "areaOfSupport", select the single most important intervention category and area of support for this participant.
|
| 369 |
+
"""
|
| 370 |
+
return prompt
|
| 371 |
+
|
| 372 |
+
def parse_gemini_response(self, response_text: str) -> dict:
|
| 373 |
+
try:
|
| 374 |
+
# Try to find and extract JSON from the response
|
| 375 |
+
response_text = response_text.strip()
|
| 376 |
+
|
| 377 |
+
# Look for JSON content between curly braces
|
| 378 |
+
start_idx = response_text.find('{')
|
| 379 |
+
end_idx = response_text.rfind('}')
|
| 380 |
+
|
| 381 |
+
if start_idx >= 0 and end_idx > start_idx:
|
| 382 |
+
json_str = response_text[start_idx:end_idx+1]
|
| 383 |
+
result = json.loads(json_str)
|
| 384 |
+
|
| 385 |
+
# Validate required fields
|
| 386 |
+
required_fields = ["AI Recommendation", "AI Score", "Justification", "Recommended Interventions", "intervention", "areaOfSupport"]
|
| 387 |
+
missing_fields = [field for field in required_fields if field not in result]
|
| 388 |
+
|
| 389 |
+
if missing_fields:
|
| 390 |
+
return {
|
| 391 |
+
"error": f"Missing required fields: {', '.join(missing_fields)}",
|
| 392 |
+
"parsed_data": result
|
| 393 |
+
}
|
| 394 |
+
|
| 395 |
+
# Validate AI Recommendation format
|
| 396 |
+
if result["AI Recommendation"] not in ["Accept", "Reject"]:
|
| 397 |
+
return {
|
| 398 |
+
"error": "AI Recommendation must be either 'Accept' or 'Reject'",
|
| 399 |
+
"parsed_data": result
|
| 400 |
+
}
|
| 401 |
+
|
| 402 |
+
# Validate AI Score format
|
| 403 |
+
try:
|
| 404 |
+
score = int(result["AI Score"])
|
| 405 |
+
if not 0 <= score <= 100:
|
| 406 |
+
return {
|
| 407 |
+
"error": "AI Score must be between 0 and 100",
|
| 408 |
+
"parsed_data": result
|
| 409 |
+
}
|
| 410 |
+
except (ValueError, TypeError):
|
| 411 |
+
return {
|
| 412 |
+
"error": "AI Score must be a valid integer",
|
| 413 |
+
"parsed_data": result
|
| 414 |
+
}
|
| 415 |
+
|
| 416 |
+
# Validate Recommended Interventions format
|
| 417 |
+
interventions = result.get("Recommended Interventions", {})
|
| 418 |
+
if not isinstance(interventions, dict):
|
| 419 |
+
return {
|
| 420 |
+
"error": "Recommended Interventions must be an object/dictionary",
|
| 421 |
+
"parsed_data": result
|
| 422 |
+
}
|
| 423 |
+
|
| 424 |
+
# All validations passed
|
| 425 |
+
return result
|
| 426 |
+
else:
|
| 427 |
+
return {"error": "No valid JSON found in response", "raw_response": response_text}
|
| 428 |
+
except json.JSONDecodeError as e:
|
| 429 |
+
return {"error": f"JSON parsing error: {str(e)}", "raw_response": response_text}
|
| 430 |
+
except Exception as e:
|
| 431 |
+
return {"error": f"Unexpected error: {str(e)}", "raw_response": response_text}
|
| 432 |
|
| 433 |
|
| 434 |
|
|
|
|
| 896 |
}), 500
|
| 897 |
|
| 898 |
|
| 899 |
+
# Lepharo AI Screening endpoint
|
| 900 |
+
@app.route('/api/lepharo_evaluate', methods=['POST'])
|
| 901 |
+
def lepharo_evaluate_participant():
|
| 902 |
+
try:
|
| 903 |
+
data = request.json
|
| 904 |
+
participant_id = data.get("participantId")
|
| 905 |
+
participant_info = data.get("participantInfo", {})
|
| 906 |
+
|
| 907 |
+
evaluator = LepharoEvaluator()
|
| 908 |
+
prompt = evaluator.generate_prompt(participant_info)
|
| 909 |
+
|
| 910 |
+
response = client.models.generate_content(
|
| 911 |
+
model=model_name,
|
| 912 |
+
contents=prompt
|
| 913 |
+
)
|
| 914 |
+
|
| 915 |
+
evaluation = evaluator.parse_gemini_response(response.text)
|
| 916 |
+
|
| 917 |
+
return jsonify({
|
| 918 |
+
"status": "success",
|
| 919 |
+
"participantId": participant_id,
|
| 920 |
+
"evaluation": evaluation
|
| 921 |
+
})
|
| 922 |
+
|
| 923 |
+
except Exception as e:
|
| 924 |
+
return jsonify({
|
| 925 |
+
"status": "error",
|
| 926 |
+
"message": str(e)
|
| 927 |
+
}), 500
|
| 928 |
+
|
| 929 |
+
|
| 930 |
+
@app.route('/api/lepharo_batch-evaluate', methods=['POST'])
|
| 931 |
+
def lepharo_batch_evaluate():
|
| 932 |
+
try:
|
| 933 |
+
participants = request.json.get('participants', [])
|
| 934 |
+
results = []
|
| 935 |
+
|
| 936 |
+
evaluator = LepharoEvaluator()
|
| 937 |
+
|
| 938 |
+
for item in participants:
|
| 939 |
+
participant_id = item.get("participantId")
|
| 940 |
+
participant_info = item.get("participantInfo", {})
|
| 941 |
+
prompt = evaluator.generate_prompt(participant_info)
|
| 942 |
+
|
| 943 |
+
response = client.models.generate_content(
|
| 944 |
+
model=model_name,
|
| 945 |
+
contents=prompt
|
| 946 |
+
)
|
| 947 |
+
|
| 948 |
+
evaluation = evaluator.parse_gemini_response(response.text)
|
| 949 |
+
|
| 950 |
+
results.append({
|
| 951 |
+
"participantId": participant_id,
|
| 952 |
+
"evaluation": evaluation
|
| 953 |
+
})
|
| 954 |
+
|
| 955 |
+
return jsonify({
|
| 956 |
+
"status": "success",
|
| 957 |
+
"evaluations": results
|
| 958 |
+
})
|
| 959 |
+
|
| 960 |
+
except Exception as e:
|
| 961 |
+
return jsonify({
|
| 962 |
+
"status": "error",
|
| 963 |
+
"message": str(e)
|
| 964 |
+
}), 500
|
| 965 |
+
|
| 966 |
+
|
| 967 |
+
@app.route('/api/lepharo_shortlist', methods=['GET'])
|
| 968 |
+
def lepharo_get_shortlist():
|
| 969 |
+
try:
|
| 970 |
+
# Placeholder logic - you can implement your shortlisting logic here
|
| 971 |
+
return jsonify({
|
| 972 |
+
"status": "success",
|
| 973 |
+
"shortlist": []
|
| 974 |
+
})
|
| 975 |
+
except Exception as e:
|
| 976 |
+
return jsonify({
|
| 977 |
+
"status": "error",
|
| 978 |
+
"message": str(e)
|
| 979 |
+
}), 500
|
| 980 |
|
| 981 |
|
| 982 |
# --------- Run the App ---------
|