Spaces:
Runtime error
Runtime error
Commit ·
1f51659
1
Parent(s): 7a26ec6
Fixed the fallback issue
Browse files- business_continuity.py +312 -155
business_continuity.py
CHANGED
|
@@ -1,50 +1,109 @@
|
|
| 1 |
# business_continuity.py
|
| 2 |
from fastapi import APIRouter, HTTPException
|
| 3 |
-
from pydantic import BaseModel
|
| 4 |
from typing import List, Optional, Dict, Any
|
| 5 |
import os
|
| 6 |
import openai
|
| 7 |
import json
|
| 8 |
import uuid
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 9 |
|
| 10 |
# Environment Variables
|
| 11 |
GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
|
| 12 |
if GROQ_API_KEY:
|
| 13 |
GROQ_API_KEY = GROQ_API_KEY.strip()
|
| 14 |
|
| 15 |
-
|
| 16 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 17 |
if not GROQ_API_KEY:
|
|
|
|
| 18 |
raise Exception("GROQ_API_KEY environment variable is not set")
|
| 19 |
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
|
| 24 |
-
|
| 25 |
-
|
| 26 |
-
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
|
| 30 |
-
|
| 31 |
-
|
| 32 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
|
| 34 |
-
# Request Models
|
| 35 |
class BusinessProcess(BaseModel):
|
| 36 |
-
department: str
|
| 37 |
-
sub_department: Optional[str] = ""
|
| 38 |
-
process_name: str
|
| 39 |
-
process_description: str
|
| 40 |
|
| 41 |
class AnalysisData(BaseModel):
|
| 42 |
-
impact_analysis: Optional[Dict[str, Any]] =
|
| 43 |
-
minimum_operating_requirements: Optional[Dict[str, Any]] =
|
| 44 |
|
| 45 |
class BusinessContinuityRequest(BaseModel):
|
| 46 |
business_process: BusinessProcess
|
| 47 |
-
analysis_data: AnalysisData
|
| 48 |
|
| 49 |
# Response Models
|
| 50 |
class RecoveryStrategiesResponse(BaseModel):
|
|
@@ -58,6 +117,125 @@ class RecoveryStrategiesResponse(BaseModel):
|
|
| 58 |
third_party_vendors_unavailability_strategy: str
|
| 59 |
vendor_reasoning: str
|
| 60 |
message: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 61 |
|
| 62 |
# Business Continuity Router
|
| 63 |
business_continuity_router = APIRouter()
|
|
@@ -67,150 +245,129 @@ def generate_recovery_strategies(request: BusinessContinuityRequest):
|
|
| 67 |
"""
|
| 68 |
Generate comprehensive business continuity recovery strategies for a business process
|
| 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 |
-
- Process-specific vulnerabilities and requirements
|
| 95 |
-
- Resource availability and constraints
|
| 96 |
-
- Regulatory requirements and compliance needs
|
| 97 |
-
- Business impact considerations
|
| 98 |
-
- Technology dependencies and alternatives
|
| 99 |
-
|
| 100 |
-
RESPOND WITH ONLY THIS EXACT JSON FORMAT (no markdown, no code blocks, no additional text):
|
| 101 |
{
|
| 102 |
-
"people_unavailability_strategy": "Detailed strategy for handling personnel unavailability with specific
|
| 103 |
-
"people_reasoning": "Clear explanation of why this strategy
|
| 104 |
-
"technology_data_unavailability_strategy": "
|
| 105 |
-
"technology_reasoning": "
|
| 106 |
-
"site_unavailability_strategy": "Detailed strategy for
|
| 107 |
-
"site_reasoning": "
|
| 108 |
-
"third_party_vendors_unavailability_strategy": "
|
| 109 |
-
"vendor_reasoning": "
|
| 110 |
}"""
|
| 111 |
|
| 112 |
-
|
| 113 |
-
input_json = {
|
| 114 |
-
"business_process": {
|
| 115 |
-
"department": request.business_process.department,
|
| 116 |
-
"sub_department": request.business_process.sub_department,
|
| 117 |
-
"process_name": request.business_process.process_name,
|
| 118 |
-
"process_description": request.business_process.process_description
|
| 119 |
-
},
|
| 120 |
-
"analysis_data": {
|
| 121 |
-
"impact_analysis": request.analysis_data.impact_analysis,
|
| 122 |
-
"minimum_operating_requirements": request.analysis_data.minimum_operating_requirements
|
| 123 |
-
}
|
| 124 |
-
}
|
| 125 |
-
|
| 126 |
-
user_message = f"""You are a business continuity expert. Generate comprehensive recovery strategies AND their reasoning for the business process described in the following JSON data:
|
| 127 |
-
|
| 128 |
-
{json.dumps(input_json, indent=2)}
|
| 129 |
-
|
| 130 |
-
Please analyze the provided business process information and provide specific, actionable recovery strategies for each of the following scenarios. Each strategy should be detailed, practical, and specific to this business process.
|
| 131 |
-
|
| 132 |
-
For EACH strategy, also provide a clear reasoning explanation of WHY this strategy was recommended for this specific process.
|
| 133 |
-
|
| 134 |
-
Strategy Categories Required:
|
| 135 |
-
1. People Unavailability Strategy - What to do when key personnel are unavailable
|
| 136 |
-
2. Technology/Data Unavailability Strategy - How to handle IT system failures or data loss
|
| 137 |
-
3. Site Unavailability Strategy - Plans for when the primary work location is inaccessible
|
| 138 |
-
4. Third Party Vendors Unavailability Strategy - Contingencies for vendor/supplier disruptions
|
| 139 |
-
|
| 140 |
-
Output Requirements:
|
| 141 |
-
- Format your response as a valid JSON object with these exact keys:
|
| 142 |
-
- people_unavailability_strategy
|
| 143 |
-
- people_reasoning
|
| 144 |
-
- technology_data_unavailability_strategy
|
| 145 |
-
- technology_reasoning
|
| 146 |
-
- site_unavailability_strategy
|
| 147 |
-
- site_reasoning
|
| 148 |
-
- third_party_vendors_unavailability_strategy
|
| 149 |
-
- vendor_reasoning
|
| 150 |
-
|
| 151 |
-
- Each strategy should be a detailed string (2-3 sentences minimum) with actionable steps
|
| 152 |
-
- Each reasoning should explain WHY this strategy was chosen for this specific process (1-2 sentences)
|
| 153 |
-
- Return ONLY the JSON object, no additional text or formatting"""
|
| 154 |
|
| 155 |
-
|
| 156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
|
| 158 |
-
#
|
| 159 |
-
|
| 160 |
-
if cleaned_result.startswith('```json'):
|
| 161 |
-
cleaned_result = cleaned_result[7:] # Remove ```json
|
| 162 |
-
elif cleaned_result.startswith('```'):
|
| 163 |
-
cleaned_result = cleaned_result[3:] # Remove ```
|
| 164 |
-
if cleaned_result.endswith('```'):
|
| 165 |
-
cleaned_result = cleaned_result[:-3] # Remove trailing ```
|
| 166 |
-
cleaned_result = cleaned_result.strip()
|
| 167 |
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 175 |
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
people_reasoning=strategies_data.get("people_reasoning", ""),
|
| 180 |
-
technology_data_unavailability_strategy=strategies_data.get("technology_data_unavailability_strategy", ""),
|
| 181 |
-
technology_reasoning=strategies_data.get("technology_reasoning", ""),
|
| 182 |
-
site_unavailability_strategy=strategies_data.get("site_unavailability_strategy", ""),
|
| 183 |
-
site_reasoning=strategies_data.get("site_reasoning", ""),
|
| 184 |
-
third_party_vendors_unavailability_strategy=strategies_data.get("third_party_vendors_unavailability_strategy", ""),
|
| 185 |
-
vendor_reasoning=strategies_data.get("vendor_reasoning", ""),
|
| 186 |
-
message="Successfully generated comprehensive recovery strategies"
|
| 187 |
-
)
|
| 188 |
-
else:
|
| 189 |
-
raise ValueError("No valid JSON found in response")
|
| 190 |
|
| 191 |
-
|
| 192 |
-
|
| 193 |
-
|
| 194 |
-
|
| 195 |
|
| 196 |
-
|
| 197 |
-
|
| 198 |
-
|
| 199 |
-
people_reasoning=f"Cross-training and documentation are essential for {process_name} in {department} to reduce single points of failure and ensure continuity when key personnel are unavailable.",
|
| 200 |
|
| 201 |
-
|
| 202 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 203 |
|
| 204 |
-
|
| 205 |
-
|
| 206 |
|
| 207 |
-
|
| 208 |
-
|
|
|
|
| 209 |
|
| 210 |
-
|
| 211 |
-
|
|
|
|
| 212 |
|
| 213 |
-
return fallback_response
|
| 214 |
-
|
| 215 |
except Exception as e:
|
| 216 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
# business_continuity.py
|
| 2 |
from fastapi import APIRouter, HTTPException
|
| 3 |
+
from pydantic import BaseModel, Field
|
| 4 |
from typing import List, Optional, Dict, Any
|
| 5 |
import os
|
| 6 |
import openai
|
| 7 |
import json
|
| 8 |
import uuid
|
| 9 |
+
import logging
|
| 10 |
+
import traceback
|
| 11 |
+
import re
|
| 12 |
+
from dotenv import load_dotenv
|
| 13 |
+
|
| 14 |
+
# Configure logging
|
| 15 |
+
logging.basicConfig(
|
| 16 |
+
level=logging.INFO,
|
| 17 |
+
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
| 18 |
+
)
|
| 19 |
+
logger = logging.getLogger(__name__)
|
| 20 |
+
|
| 21 |
+
# Load environment variables from .env file
|
| 22 |
+
load_dotenv()
|
| 23 |
|
| 24 |
# Environment Variables
|
| 25 |
GROQ_API_KEY = os.environ.get("GROQ_API_KEY")
|
| 26 |
if GROQ_API_KEY:
|
| 27 |
GROQ_API_KEY = GROQ_API_KEY.strip()
|
| 28 |
|
| 29 |
+
logger.info(f"GROQ_API_KEY loaded: {'Yes' if GROQ_API_KEY else 'No'}")
|
| 30 |
+
if GROQ_API_KEY:
|
| 31 |
+
logger.info(f"GROQ_API_KEY preview: {GROQ_API_KEY[:10]}...")
|
| 32 |
+
|
| 33 |
+
# Model Setup with improved error handling
|
| 34 |
+
def generate_response(system_prompt: str, user_message: str, max_retries: int = 3):
|
| 35 |
+
"""Generate response from GROQ API with retry logic and comprehensive error handling"""
|
| 36 |
+
|
| 37 |
if not GROQ_API_KEY:
|
| 38 |
+
logger.error("GROQ_API_KEY environment variable is not set")
|
| 39 |
raise Exception("GROQ_API_KEY environment variable is not set")
|
| 40 |
|
| 41 |
+
logger.info("Initializing GROQ API client")
|
| 42 |
+
|
| 43 |
+
for attempt in range(max_retries):
|
| 44 |
+
try:
|
| 45 |
+
logger.info(f"Attempt {attempt + 1}/{max_retries} - Making API call to GROQ")
|
| 46 |
+
|
| 47 |
+
client = openai.OpenAI(
|
| 48 |
+
api_key=GROQ_API_KEY,
|
| 49 |
+
base_url="https://api.groq.com/openai/v1",
|
| 50 |
+
timeout=60.0 # Increased timeout
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
response = client.chat.completions.create(
|
| 54 |
+
model="llama3-8b-8192",
|
| 55 |
+
messages=[
|
| 56 |
+
{"role": "system", "content": system_prompt},
|
| 57 |
+
{"role": "user", "content": user_message}
|
| 58 |
+
],
|
| 59 |
+
temperature=0.2, # Even lower for more consistent responses
|
| 60 |
+
max_tokens=3000, # Increased further to avoid truncation
|
| 61 |
+
top_p=0.9,
|
| 62 |
+
frequency_penalty=0.0,
|
| 63 |
+
presence_penalty=0.0
|
| 64 |
+
)
|
| 65 |
+
|
| 66 |
+
content = response.choices[0].message.content
|
| 67 |
+
logger.info(f"API call successful - Response length: {len(content) if content else 0}")
|
| 68 |
+
|
| 69 |
+
if not content or content.strip() == "":
|
| 70 |
+
logger.warning(f"Empty response on attempt {attempt + 1}")
|
| 71 |
+
if attempt == max_retries - 1:
|
| 72 |
+
raise Exception("Received empty response from GROQ API after all retries")
|
| 73 |
+
continue
|
| 74 |
+
|
| 75 |
+
return content
|
| 76 |
+
|
| 77 |
+
except openai.APITimeoutError as e:
|
| 78 |
+
logger.warning(f"Timeout error on attempt {attempt + 1}: {str(e)}")
|
| 79 |
+
if attempt == max_retries - 1:
|
| 80 |
+
raise Exception(f"GROQ API timeout after {max_retries} attempts: {str(e)}")
|
| 81 |
+
|
| 82 |
+
except openai.APIError as e:
|
| 83 |
+
logger.error(f"GROQ API error on attempt {attempt + 1}: {str(e)}")
|
| 84 |
+
if attempt == max_retries - 1:
|
| 85 |
+
raise Exception(f"GROQ API error after {max_retries} attempts: {str(e)}")
|
| 86 |
+
|
| 87 |
+
except Exception as e:
|
| 88 |
+
logger.error(f"Unexpected error on attempt {attempt + 1}: {str(e)}")
|
| 89 |
+
logger.error(f"Traceback: {traceback.format_exc()}")
|
| 90 |
+
if attempt == max_retries - 1:
|
| 91 |
+
raise Exception(f"GROQ API connection failed after {max_retries} attempts: {str(e)}")
|
| 92 |
|
| 93 |
+
# Request Models with validation
|
| 94 |
class BusinessProcess(BaseModel):
|
| 95 |
+
department: str = Field(..., min_length=1, max_length=100)
|
| 96 |
+
sub_department: Optional[str] = Field(default="", max_length=100)
|
| 97 |
+
process_name: str = Field(..., min_length=1, max_length=200)
|
| 98 |
+
process_description: str = Field(..., min_length=1, max_length=1000)
|
| 99 |
|
| 100 |
class AnalysisData(BaseModel):
|
| 101 |
+
impact_analysis: Optional[Dict[str, Any]] = Field(default_factory=dict)
|
| 102 |
+
minimum_operating_requirements: Optional[Dict[str, Any]] = Field(default_factory=dict)
|
| 103 |
|
| 104 |
class BusinessContinuityRequest(BaseModel):
|
| 105 |
business_process: BusinessProcess
|
| 106 |
+
analysis_data: AnalysisData = Field(default_factory=lambda: AnalysisData())
|
| 107 |
|
| 108 |
# Response Models
|
| 109 |
class RecoveryStrategiesResponse(BaseModel):
|
|
|
|
| 117 |
third_party_vendors_unavailability_strategy: str
|
| 118 |
vendor_reasoning: str
|
| 119 |
message: str
|
| 120 |
+
request_id: str = Field(default_factory=lambda: str(uuid.uuid4()))
|
| 121 |
+
|
| 122 |
+
def clean_json_response(response_text: str) -> str:
|
| 123 |
+
"""Clean and extract JSON from API response"""
|
| 124 |
+
logger.info("Cleaning JSON response")
|
| 125 |
+
|
| 126 |
+
if not response_text:
|
| 127 |
+
logger.error("Empty response text provided")
|
| 128 |
+
raise ValueError("Empty response text")
|
| 129 |
+
|
| 130 |
+
# Remove common markdown formatting
|
| 131 |
+
cleaned = response_text.strip()
|
| 132 |
+
|
| 133 |
+
# Remove code blocks more aggressively
|
| 134 |
+
patterns = [
|
| 135 |
+
r'```json\s*', # Matches opening fence with optional spaces after ```json
|
| 136 |
+
r'```', # Matches closing or generic triple backticks
|
| 137 |
+
]
|
| 138 |
+
for pattern in patterns:
|
| 139 |
+
cleaned = re.sub(pattern, '', cleaned, flags=re.MULTILINE | re.DOTALL)
|
| 140 |
+
|
| 141 |
+
cleaned = cleaned.strip()
|
| 142 |
+
|
| 143 |
+
# Try to find complete JSON by counting braces
|
| 144 |
+
json_start = cleaned.find('{')
|
| 145 |
+
if json_start == -1:
|
| 146 |
+
logger.error(f"No opening brace found. Response preview: {cleaned[:300]}...")
|
| 147 |
+
raise ValueError("No JSON opening brace found in response")
|
| 148 |
+
|
| 149 |
+
# Count braces to find the matching closing brace
|
| 150 |
+
brace_count = 0
|
| 151 |
+
json_end = -1
|
| 152 |
+
|
| 153 |
+
for i in range(json_start, len(cleaned)):
|
| 154 |
+
if cleaned[i] == '{':
|
| 155 |
+
brace_count += 1
|
| 156 |
+
elif cleaned[i] == '}':
|
| 157 |
+
brace_count -= 1
|
| 158 |
+
if brace_count == 0:
|
| 159 |
+
json_end = i
|
| 160 |
+
break
|
| 161 |
+
|
| 162 |
+
if json_end == -1:
|
| 163 |
+
logger.error(f"No matching closing brace found. Response preview: {cleaned[:500]}...")
|
| 164 |
+
# Try using the last closing brace as fallback
|
| 165 |
+
json_end = cleaned.rfind('}')
|
| 166 |
+
if json_end == -1 or json_end <= json_start:
|
| 167 |
+
raise ValueError("No valid JSON structure found in response")
|
| 168 |
+
|
| 169 |
+
json_str = cleaned[json_start:json_end + 1]
|
| 170 |
+
logger.info(f"Extracted JSON length: {len(json_str)}")
|
| 171 |
+
logger.info(f"JSON preview: {json_str[:150]}...")
|
| 172 |
+
logger.info(f"JSON ending: ...{json_str[-50:]}")
|
| 173 |
+
|
| 174 |
+
# Validate that it's at least syntactically correct JSON
|
| 175 |
+
try:
|
| 176 |
+
json.loads(json_str)
|
| 177 |
+
logger.info("JSON syntax validation passed")
|
| 178 |
+
except json.JSONDecodeError as e:
|
| 179 |
+
logger.error(f"JSON syntax validation failed: {str(e)}")
|
| 180 |
+
logger.error(f"Problematic JSON: {json_str}")
|
| 181 |
+
raise ValueError(f"Invalid JSON syntax: {str(e)}")
|
| 182 |
+
|
| 183 |
+
return json_str
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
def validate_strategies_data(data: dict) -> dict:
|
| 187 |
+
"""Validate and clean strategies data"""
|
| 188 |
+
logger.info("Validating strategies data")
|
| 189 |
+
|
| 190 |
+
required_fields = [
|
| 191 |
+
"people_unavailability_strategy",
|
| 192 |
+
"people_reasoning",
|
| 193 |
+
"technology_data_unavailability_strategy",
|
| 194 |
+
"technology_reasoning",
|
| 195 |
+
"site_unavailability_strategy",
|
| 196 |
+
"site_reasoning",
|
| 197 |
+
"third_party_vendors_unavailability_strategy",
|
| 198 |
+
"vendor_reasoning"
|
| 199 |
+
]
|
| 200 |
+
|
| 201 |
+
# Ensure all required fields exist and are strings
|
| 202 |
+
for field in required_fields:
|
| 203 |
+
if field not in data:
|
| 204 |
+
logger.warning(f"Missing field: {field}")
|
| 205 |
+
data[field] = "Strategy not generated - please retry request"
|
| 206 |
+
elif not isinstance(data[field], str):
|
| 207 |
+
logger.warning(f"Invalid type for field {field}: {type(data[field])}")
|
| 208 |
+
data[field] = str(data[field]) if data[field] is not None else "Strategy not generated"
|
| 209 |
+
elif len(data[field].strip()) == 0:
|
| 210 |
+
logger.warning(f"Empty field: {field}")
|
| 211 |
+
data[field] = "Strategy not generated - please retry request"
|
| 212 |
+
|
| 213 |
+
logger.info("Data validation completed")
|
| 214 |
+
return data
|
| 215 |
+
|
| 216 |
+
def generate_fallback_response(request: BusinessContinuityRequest, error_msg: str = "") -> RecoveryStrategiesResponse:
|
| 217 |
+
"""Generate fallback response when API fails"""
|
| 218 |
+
logger.info("Generating fallback response")
|
| 219 |
+
|
| 220 |
+
process_name = request.business_process.process_name
|
| 221 |
+
department = request.business_process.department
|
| 222 |
+
|
| 223 |
+
return RecoveryStrategiesResponse(
|
| 224 |
+
success=True,
|
| 225 |
+
people_unavailability_strategy=f"Implement cross-training programs for critical roles in {process_name}, maintain updated emergency contact lists, and establish clear succession planning. Create detailed process documentation and implement job rotation to reduce single points of failure.",
|
| 226 |
+
people_reasoning=f"Cross-training and succession planning are essential for {process_name} in {department} to ensure continuity when key personnel are unavailable.",
|
| 227 |
+
|
| 228 |
+
technology_data_unavailability_strategy=f"Establish automated backup systems for {process_name} data, implement redundant infrastructure with failover capabilities, and maintain disaster recovery procedures. Conduct regular backup testing and ensure secure off-site data replication.",
|
| 229 |
+
technology_reasoning=f"Robust backup and redundancy systems are critical for {process_name} to minimize downtime and ensure rapid recovery from technology failures.",
|
| 230 |
+
|
| 231 |
+
site_unavailability_strategy=f"Identify and prepare alternative work locations for {process_name} operations, enable comprehensive remote work capabilities, and establish emergency communication protocols. Maintain emergency supplies and equipment at backup locations.",
|
| 232 |
+
site_reasoning=f"Alternative work arrangements ensure {process_name} can continue operating when primary {department} facilities are inaccessible.",
|
| 233 |
+
|
| 234 |
+
third_party_vendors_unavailability_strategy=f"Develop relationships with backup vendors for {process_name} critical services, maintain emergency supplier contacts, and establish contingency contracts. Regularly assess vendor reliability and maintain strategic inventory buffers.",
|
| 235 |
+
vendor_reasoning=f"Vendor diversification and contingency planning reduce supply chain risks for {process_name} and ensure continuity of essential external services.",
|
| 236 |
+
|
| 237 |
+
message=f"Fallback strategies generated due to API processing error: {error_msg}" if error_msg else "Fallback strategies generated"
|
| 238 |
+
)
|
| 239 |
|
| 240 |
# Business Continuity Router
|
| 241 |
business_continuity_router = APIRouter()
|
|
|
|
| 245 |
"""
|
| 246 |
Generate comprehensive business continuity recovery strategies for a business process
|
| 247 |
"""
|
| 248 |
+
request_id = str(uuid.uuid4())
|
| 249 |
+
logger.info(f"Request {request_id}: Starting recovery strategies generation")
|
| 250 |
+
logger.info(f"Request {request_id}: Process - {request.business_process.process_name}")
|
| 251 |
+
logger.info(f"Request {request_id}: Department - {request.business_process.department}")
|
| 252 |
+
|
| 253 |
+
try:
|
| 254 |
+
# Validate input
|
| 255 |
+
if not request.business_process.process_name.strip():
|
| 256 |
+
logger.error(f"Request {request_id}: Empty process name")
|
| 257 |
+
raise HTTPException(status_code=400, detail="Process name cannot be empty")
|
| 258 |
+
|
| 259 |
+
if not request.business_process.department.strip():
|
| 260 |
+
logger.error(f"Request {request_id}: Empty department")
|
| 261 |
+
raise HTTPException(status_code=400, detail="Department cannot be empty")
|
| 262 |
+
|
| 263 |
+
system_prompt = """You are an expert business continuity consultant. Your task is to generate specific, actionable recovery strategies for business processes.
|
| 264 |
+
|
| 265 |
+
CRITICAL INSTRUCTIONS:
|
| 266 |
+
1. Respond with ONLY a valid JSON object
|
| 267 |
+
2. Do not include any markdown formatting, code blocks, or additional text
|
| 268 |
+
3. Each strategy must be 2-3 detailed sentences with specific actionable steps
|
| 269 |
+
4. Each reasoning must be 1-2 sentences explaining why the strategy fits the specific process
|
| 270 |
+
|
| 271 |
+
Required JSON format (no deviations allowed):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 272 |
{
|
| 273 |
+
"people_unavailability_strategy": "Detailed strategy for handling personnel unavailability with specific steps",
|
| 274 |
+
"people_reasoning": "Clear explanation of why this strategy suits this specific process",
|
| 275 |
+
"technology_data_unavailability_strategy": "Comprehensive strategy for technology and data failures with actionable steps",
|
| 276 |
+
"technology_reasoning": "Explanation of why this technology strategy is appropriate for this process",
|
| 277 |
+
"site_unavailability_strategy": "Detailed strategy for site/facility unavailability with specific actions",
|
| 278 |
+
"site_reasoning": "Why this site strategy is suitable for this specific process",
|
| 279 |
+
"third_party_vendors_unavailability_strategy": "Comprehensive strategy for vendor/supplier disruptions",
|
| 280 |
+
"vendor_reasoning": "Why this vendor strategy is appropriate for this process"
|
| 281 |
}"""
|
| 282 |
|
| 283 |
+
user_message = f"""Generate business continuity recovery strategies for this specific process:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 284 |
|
| 285 |
+
PROCESS DETAILS:
|
| 286 |
+
- Process Name: {request.business_process.process_name}
|
| 287 |
+
- Department: {request.business_process.department}
|
| 288 |
+
- Sub-Department: {request.business_process.sub_department or 'Not specified'}
|
| 289 |
+
- Description: {request.business_process.process_description}
|
| 290 |
+
|
| 291 |
+
ANALYSIS DATA:
|
| 292 |
+
- Impact Analysis: {json.dumps(request.analysis_data.impact_analysis) if request.analysis_data.impact_analysis else 'Not provided'}
|
| 293 |
+
- Minimum Operating Requirements: {json.dumps(request.analysis_data.minimum_operating_requirements) if request.analysis_data.minimum_operating_requirements else 'Not provided'}
|
| 294 |
+
|
| 295 |
+
Generate comprehensive recovery strategies for these four scenarios:
|
| 296 |
+
1. Key personnel unavailability
|
| 297 |
+
2. Technology/data system failures
|
| 298 |
+
3. Primary site/facility unavailability
|
| 299 |
+
4. Critical third-party vendor/supplier disruptions
|
| 300 |
+
|
| 301 |
+
Focus on actionable, specific strategies tailored to this exact process and department context."""
|
| 302 |
+
|
| 303 |
+
logger.info(f"Request {request_id}: Making API call to GROQ")
|
| 304 |
|
| 305 |
+
# Get response from API
|
| 306 |
+
api_response = generate_response(system_prompt, user_message)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 307 |
|
| 308 |
+
if not api_response:
|
| 309 |
+
logger.error(f"Request {request_id}: Empty API response")
|
| 310 |
+
return generate_fallback_response(request, "Empty API response")
|
| 311 |
|
| 312 |
+
logger.info(f"Request {request_id}: API response received, length: {len(api_response)}")
|
| 313 |
+
|
| 314 |
+
# Clean and parse JSON
|
| 315 |
+
try:
|
| 316 |
+
logger.info(f"Request {request_id}: Raw API response length: {len(api_response)}")
|
| 317 |
+
logger.info(f"Request {request_id}: Raw response first 200 chars: {api_response[:200]}")
|
| 318 |
+
logger.info(f"Request {request_id}: Raw response last 200 chars: {api_response[-200:]}")
|
| 319 |
|
| 320 |
+
json_str = clean_json_response(api_response)
|
| 321 |
+
strategies_data = json.loads(json_str)
|
| 322 |
+
logger.info(f"Request {request_id}: JSON parsing successful")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 323 |
|
| 324 |
+
except (json.JSONDecodeError, ValueError) as e:
|
| 325 |
+
logger.error(f"Request {request_id}: JSON parsing failed - {str(e)}")
|
| 326 |
+
logger.error(f"Request {request_id}: Raw response preview: {api_response[:300]}...")
|
| 327 |
+
return generate_fallback_response(request, f"JSON parsing failed: {str(e)}")
|
| 328 |
|
| 329 |
+
# Validate the parsed data
|
| 330 |
+
try:
|
| 331 |
+
strategies_data = validate_strategies_data(strategies_data)
|
|
|
|
| 332 |
|
| 333 |
+
response = RecoveryStrategiesResponse(
|
| 334 |
+
success=True,
|
| 335 |
+
people_unavailability_strategy=strategies_data["people_unavailability_strategy"],
|
| 336 |
+
people_reasoning=strategies_data["people_reasoning"],
|
| 337 |
+
technology_data_unavailability_strategy=strategies_data["technology_data_unavailability_strategy"],
|
| 338 |
+
technology_reasoning=strategies_data["technology_reasoning"],
|
| 339 |
+
site_unavailability_strategy=strategies_data["site_unavailability_strategy"],
|
| 340 |
+
site_reasoning=strategies_data["site_reasoning"],
|
| 341 |
+
third_party_vendors_unavailability_strategy=strategies_data["third_party_vendors_unavailability_strategy"],
|
| 342 |
+
vendor_reasoning=strategies_data["vendor_reasoning"],
|
| 343 |
+
message="Successfully generated comprehensive recovery strategies",
|
| 344 |
+
request_id=request_id
|
| 345 |
+
)
|
| 346 |
|
| 347 |
+
logger.info(f"Request {request_id}: Successfully generated strategies")
|
| 348 |
+
return response
|
| 349 |
|
| 350 |
+
except Exception as e:
|
| 351 |
+
logger.error(f"Request {request_id}: Data validation failed - {str(e)}")
|
| 352 |
+
return generate_fallback_response(request, f"Data validation failed: {str(e)}")
|
| 353 |
|
| 354 |
+
except HTTPException:
|
| 355 |
+
# Re-raise HTTP exceptions
|
| 356 |
+
raise
|
| 357 |
|
|
|
|
|
|
|
| 358 |
except Exception as e:
|
| 359 |
+
logger.error(f"Request {request_id}: Unexpected error - {str(e)}")
|
| 360 |
+
logger.error(f"Request {request_id}: Traceback: {traceback.format_exc()}")
|
| 361 |
+
|
| 362 |
+
# Return fallback response instead of raising HTTP error
|
| 363 |
+
return generate_fallback_response(request, f"Unexpected error: {str(e)}")
|
| 364 |
+
|
| 365 |
+
# Health check endpoint
|
| 366 |
+
@business_continuity_router.get("/api/business-continuity/health")
|
| 367 |
+
def health_check():
|
| 368 |
+
"""Health check endpoint"""
|
| 369 |
+
return {
|
| 370 |
+
"status": "healthy",
|
| 371 |
+
"groq_api_configured": bool(GROQ_API_KEY),
|
| 372 |
+
"timestamp": uuid.uuid4().hex
|
| 373 |
+
}
|