added flight assesment agent withj third party free api

This commit is contained in:
OwusuBlessing
2025-08-01 19:33:30 +01:00
parent 41248b1d78
commit d391e966cb
22 changed files with 1323 additions and 169 deletions
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+2 -1
View File
@@ -16,5 +16,6 @@ class ChatRequest(BaseModel):
class SurveyRequest(BaseModel):
job_id: str
+6 -2
View File
@@ -1,8 +1,12 @@
# api/models/responses.py # api/models/responses.py
from pydantic import BaseModel from pydantic import BaseModel
from typing import Dict
class ChatResponse(BaseModel): class ChatResponse(BaseModel):
status: str status: str
message: str message: str
class SurveyAgentResponse(BaseModel):
status: str
result: Dict
Binary file not shown.
+27 -4
View File
@@ -1,17 +1,19 @@
# api/routes/chat_ai.py # api/routes/chat_ai.py
from fastapi import APIRouter, Depends, HTTPException from fastapi import APIRouter, Depends, HTTPException
from api.models.requests import ChatRequest, ChatMessage from api.models.requests import ChatRequest, ChatMessage,SurveyRequest
from api.models.responses import ChatResponse from api.models.responses import ChatResponse,SurveyAgentResponse
from api.dependencies.auth import get_api_key from api.dependencies.auth import get_api_key
from src.llm.orchestrator import DroneBot, Message # Adjust import as needed from src.llm.orchestrator import DroneBot, Message # Adjust import as needed
from src.llm.agent.flight_assesment import DroneAssessmentAgent
import json
router = APIRouter( router = APIRouter(
prefix="/chat-ai", prefix="/chat",
tags=["chat"] tags=["chat"]
) )
@router.post("", response_model=ChatResponse) @router.post("/booking-assistant", response_model=ChatResponse)
async def chat_ai( async def chat_ai(
request: ChatRequest, request: ChatRequest,
_: str = Depends(get_api_key) _: str = Depends(get_api_key)
@@ -36,3 +38,24 @@ async def chat_ai(
status_code=500, status_code=500,
detail=str(e) detail=str(e)
) )
@router.post("/analyse-survey", response_model=SurveyAgentResponse)
async def run_survey_agent(
request: SurveyRequest,
_: str = Depends(get_api_key)
):
"""Chat with DroneBot using query and history."""
try:
from test2 import booking_form_input
agent = DroneAssessmentAgent()
result = await agent.run(booking_form_input)
return SurveyAgentResponse(
status="success",
result=result
)
except Exception as e:
raise HTTPException(
status_code=500,
detail=str(e)
)
+1 -1
View File
@@ -12,4 +12,4 @@ load_dotenv(override=True)
class Config: class Config:
OPENAI_API_KEY = os.getenv("OPENAI_API_KEY") OPENAI_API_KEY = os.getenv("OPENAI_API_KEY")
API_KEY = os.getenv("API_KEY") API_KEY = os.getenv("API_KEY")
OPENWEATHERMAP_API_KEY = os.getenv("OPENWEATHERMAP_API_KEY")
+55
View File
@@ -0,0 +1,55 @@
import logging
from logging.handlers import RotatingFileHandler
import os
def setup_logger(
name: str = "dronebot_logger",
log_file: str = "logs/app.log",
level: int = logging.DEBUG,
max_bytes: int = 5 * 1024 * 1024, # 5 MB
backup_count: int = 3
) -> logging.Logger:
"""
Set up a logger that logs to both console and file.
Args:
name (str): Logger name
log_file (str): Path to log file
level (int): Logging level (e.g., logging.INFO, logging.DEBUG)
max_bytes (int): Max size of each log file before rotation
backup_count (int): Number of rotated logs to keep
Returns:
logging.Logger: Configured logger instance
"""
# Ensure log directory exists
os.makedirs(os.path.dirname(log_file), exist_ok=True)
# Create logger
logger = logging.getLogger(name)
logger.setLevel(level)
logger.propagate = False # Avoid double logging
if not logger.handlers:
# Formatter
formatter = logging.Formatter(
"[%(asctime)s] %(levelname)s%(name)s%(message)s",
datefmt="%Y-%m-%d %H:%M:%S"
)
# Console handler
console_handler = logging.StreamHandler()
console_handler.setFormatter(formatter)
logger.addHandler(console_handler)
# Rotating file handler
file_handler = RotatingFileHandler(
log_file, maxBytes=max_bytes, backupCount=backup_count
)
file_handler.setFormatter(formatter)
logger.addHandler(file_handler)
return logger
logger = setup_logger()
View File
@@ -0,0 +1,245 @@
import asyncio
import aiohttp
import json
from datetime import datetime, timedelta
from typing import Dict, Tuple
import re
from logger import logger #
class DroneWeatherDataExtractor:
"""
Extract location data from drone booking forms and fetch structured
weather information for later use in analysis systems.
"""
def __init__(self):
self.base_url = "https://api.open-meteo.com/v1/forecast"
def extract_coordinates(self, booking_data: Dict) -> Tuple[float, float]:
"""Extract latitude and longitude from booking form data."""
try:
gps_coords = booking_data.get("site_information", {}).get("gps_coordinates", {})
if isinstance(gps_coords, dict):
lat_str = gps_coords.get("latitude", "")
lng_str = gps_coords.get("longitude", "")
lat_match = re.search(r'([\d.]+)', lat_str)
lng_match = re.search(r'([\d.]+)', lng_str)
if lat_match and lng_match:
latitude = float(lat_match.group(1))
longitude = float(lng_match.group(1))
if 'S' in lat_str.upper():
latitude = -latitude
if 'W' in lng_str.upper():
longitude = -longitude
logger.info(f"Extracted coordinates from GPS: ({latitude}, {longitude})")
return latitude, longitude
site_location = booking_data.get("form", {}).get("site_location", "")
coord_pattern = r'GPS: ([\d.]+)° ([NS]), ([\d.]+)° ([EW])'
match = re.search(coord_pattern, site_location)
if match:
lat, lat_dir, lng, lng_dir = match.groups()
latitude = float(lat) if lat_dir == 'N' else -float(lat)
longitude = float(lng) if lng_dir == 'E' else -float(lng)
logger.info(f"Extracted coordinates from fallback: ({latitude}, {longitude})")
return latitude, longitude
except (ValueError, KeyError) as e:
logger.error(f"Error extracting coordinates: {e}")
raise ValueError("Could not extract valid coordinates from booking data")
def extract_booking_info(self, booking_data: Dict) -> Dict:
"""Extract relevant booking information in structured format."""
try:
job_overview = booking_data.get("job_overview", {})
timing = booking_data.get("timing", {})
site_info = booking_data.get("site_information", {})
form_info = booking_data.get("form", {})
info = {
"job_id": booking_data.get("job_id"),
"job_number": job_overview.get("job_number"),
"site_name": site_info.get("site_name"),
"region": site_info.get("region"),
"full_address": site_info.get("full_address"),
"asset_type": form_info.get("asset_type"),
"system_size": form_info.get("system_size"),
"survey_purpose": form_info.get("survey_purpose"),
"assigned_engineer": form_info.get("assigned_engineer"),
"contact_person": form_info.get("contact_person"),
"contact_phone": form_info.get("contact_phone"),
"dates": {
"assigned_date": job_overview.get("assigned_date"),
"preferred_dates": form_info.get("preferred_dates"),
"selected_slot": form_info.get("selected_slot")
},
"timing": {
"start_time": timing.get("start_time"),
"end_time": timing.get("end_time"),
"duration": timing.get("survey_duration"),
"buffer_time": timing.get("buffer_time")
},
"access_details": {
"access_type": form_info.get("access_type"),
"vehicle_access": form_info.get("vehicle_access"),
"access_details": form_info.get("access_details")
},
"safety_requirements": form_info.get("special_safety_requirements"),
"additional_requirements": form_info.get("additional_requirements"),
"booking_timestamp": form_info.get("booking_timestamp"),
"booking_id": form_info.get("booking_id")
}
logger.debug(f"Extracted booking info: {info}")
return info
except Exception as e:
logger.error(f"Error extracting booking info: {e}")
return {}
def parse_target_date(self, booking_data: Dict) -> datetime:
"""Extract and parse the target date from booking data."""
try:
date_fields = [
booking_data.get("job_overview", {}).get("assigned_date"),
booking_data.get("form", {}).get("preferred_dates"),
booking_data.get("form", {}).get("selected_slot")
]
for date_field in date_fields:
if not date_field:
continue
if "January 23" in date_field:
return datetime(2025, 1, 23)
elif "January 24" in date_field:
return datetime(2025, 1, 24)
logger.warning("Using fallback date")
return datetime.now() + timedelta(days=1)
except Exception as e:
logger.error(f"Error parsing date: {e}")
return datetime.now() + timedelta(days=1)
async def fetch_weather_data(self, latitude: float, longitude: float,
target_date: datetime, days_range: int = 3) -> Dict:
"""Fetch structured weather data for specific coordinates and date range."""
start_date = target_date - timedelta(days=days_range // 2)
end_date = target_date + timedelta(days=days_range // 2)
params = {
"latitude": latitude,
"longitude": longitude,
"current": "temperature_2m,relative_humidity_2m,wind_speed_10m",
"hourly": "temperature_2m,relative_humidity_2m"
}
logger.info(f"Requesting weather data for {latitude}, {longitude} from {start_date.date()} to {end_date.date()}")
async with aiohttp.ClientSession() as session:
async with session.get(self.base_url, params=params) as response:
if response.status == 200:
logger.info("Weather data fetched successfully.")
return await response.json()
else:
error_msg = f"Weather API request failed: {response.status}"
logger.error(error_msg)
raise Exception(error_msg)
def structure_weather_data(self, raw_weather: Dict, target_date: datetime) -> Dict:
"""Structure raw weather data into organized format."""
try:
structured_data = {
"location": {
"latitude": raw_weather.get("latitude"),
"longitude": raw_weather.get("longitude"),
"elevation": raw_weather.get("elevation"),
"timezone": raw_weather.get("timezone")
},
"current_conditions": raw_weather.get("current", {}),
"hourly_forecast": raw_weather.get("hourly", {}),
"daily_forecast": raw_weather.get("daily", {}),
"target_date": target_date.strftime("%Y-%m-%d"),
"data_retrieved_at": datetime.now().isoformat()
}
logger.debug("Structured weather data successfully.")
return structured_data
except Exception as e:
logger.error(f"Error structuring weather data: {e}")
return {"error": f"Error structuring weather data: {e}"}
async def extract_booking_weather_data(self, booking_data: Dict) -> Dict:
"""Main extraction function - returns structured data for external use."""
try:
logger.info(f"Starting extraction for job_id={booking_data.get('job_id')}")
booking_info = self.extract_booking_info(booking_data)
latitude, longitude = self.extract_coordinates(booking_data)
target_date = self.parse_target_date(booking_data)
raw_weather = await self.fetch_weather_data(latitude, longitude, target_date)
structured_weather = self.structure_weather_data(raw_weather, target_date)
logger.info(f"Completed extraction for job_id={booking_data.get('job_id')}")
return {
"weather_data": structured_weather,
"extraction_metadata": {
"processed_at": datetime.now().isoformat(),
"api_endpoint": self.base_url,
"data_source": "open-meteo"
}
}
except Exception as e:
logger.exception(f"Data extraction failed for job_id={booking_data.get('job_id', 'unknown')}")
return {
"error": f"Data extraction failed: {e}",
"job_id": booking_data.get("job_id", "unknown"),
"processed_at": datetime.now().isoformat()
}
# Example usage
async def main():
logger.info("Demo started.")
booking_form_input = {
"job_id": "1043",
"job_overview": {
"job_number": "Job #1043",
"site_name": "Hightower Solar Farm",
"assigned_date": "January 23, 2025",
"status": "Scheduled"
},
"site_information": {
"site_name": "Hightower Solar Farm",
"region": "North England",
"full_address": "Grange Lane, Manchester M34 7TF",
"gps_coordinates": {
"latitude": "13.41° N",
"longitude": "52.52 W"
}
},
"timing": {
"start_time": "09:00 AM",
"end_time": "10:30 AM",
"survey_duration": "3045 mins",
"buffer_time": "45 mins"
},
"form": {
"asset_type": "Solar Farm",
"system_size": "5.2 MW capacity, approximately 16,000 panels across 12 hectares",
"survey_purpose": "Insurance assessment",
"assigned_engineer": "David Wilson - 0161-555-0876",
"contact_person": "Sarah Thompson",
"contact_phone": "0161-555-0234"
}
}
extractor = DroneWeatherDataExtractor()
result = await extractor.extract_booking_weather_data(booking_form_input)
logger.info("Extraction result:")
logger.info(json.dumps(result, indent=2))
if __name__ == "__main__":
asyncio.run(main())
View File
+90
View File
@@ -0,0 +1,90 @@
import os
import json
import asyncio
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from src.prompts.templates.flght_prompt import flight_prompt
from src.config.llm_config import LlmConfig
from config import Config
from src.components.data_extraction.weather_data import DroneWeatherDataExtractor
from logger import logger
class DroneAssessmentAgent:
def __init__(self):
self.llm = ChatOpenAI(
api_key=Config.OPENAI_API_KEY,
model=LlmConfig.openai.models.gpt_4o,
temperature=0.3
)
self.weather_extractor = DroneWeatherDataExtractor()
async def run(self, booking_form: str) -> dict:
"""
Run the drone environmental & safety assessment agent.
Args:
booking_form (str): Structured booking form input
Returns:
dict: AI-generated structured output
"""
logger.info("Starting DroneAssessmentAgent run...")
try:
# Step 1: Fetch weather data
weather_data = await self.weather_extractor.extract_booking_weather_data(booking_form)
booking_form_text = json.dumps(booking_form)
# Step 2: Generate prompt
prompt = flight_prompt(booking_form_text, weather_data)
messages = [
SystemMessage(content=prompt),
HumanMessage(content=booking_form_text)
]
# Step 3: Invoke LLM
logger.debug("Sending prompt to LLM...")
response = self.llm.invoke(messages)
if hasattr(response, "content"):
response_text = response.content.strip()
logger.info("Received LLM response.")
logger.debug(f"LLM Raw Output (first 300 chars):\n{response_text[:300]}")
# Extract JSON block
start_idx = response_text.find('{')
end_idx = response_text.rfind('}') + 1
if start_idx == -1 or end_idx == -1:
raise ValueError("No JSON object found in output")
json_str = response_text[start_idx:end_idx]
return json.loads(json_str)
else:
raise ValueError("LLM returned no usable content")
except Exception as e:
logger.exception("Error in DroneAssessmentAgent")
return {
"error": str(e),
"raw_response": response.content if 'response' in locals() and hasattr(response, "content") else None
}
async def main():
"""Async main function to test the drone assessment agent."""
from test2 import booking_form_input
logger.info("Launching DroneAssessmentAgent from main()...")
agent = DroneAssessmentAgent()
result = await agent.run(booking_form_input)
logger.info("Drone assessment completed.")
logger.debug("Final structured output:")
logger.debug(json.dumps(result, indent=2))
if __name__ == "__main__":
asyncio.run(main())
+339 -126
View File
@@ -1,6 +1,6 @@
def get_booking_prompt(): def get_booking_prompt():
return """ return f"""
## System Instructions & Persona ## System Instructions & Persona
@@ -12,215 +12,428 @@ You are detail-oriented and safety-conscious, always ensuring that our certified
Your primary function is to guide users through a comprehensive booking process, collecting all necessary information to schedule drone inspections with our certified engineers. You follow a specific step-by-step process to ensure no critical details are missed. Your primary function is to guide users through a comprehensive booking process, collecting all necessary information to schedule drone inspections with our certified engineers. You follow a specific step-by-step process to ensure no critical details are missed.
## IMPORTANT: JSON Response Format
You MUST ALWAYS respond with a structured JSON object containing exactly these fields:
```json
{{
"message": "Your conversational response to the user",
"options": ["Option 1", "Option 2", "Option 3"] or null,
"requires_selection": true/false,
"end": "complete" | "in_progress" | "cancelled",
"form": {{}}
}}
```
**Field Explanations:**
- **message**: Your conversational response (friendly, professional tone)
- **options**: Array of choices for user to select from, or null if no options needed
- **requires_selection**: true if user must choose from options, false if they can provide free text
- **end**:
- "complete" = booking session successfully finished
- "in_progress" = still collecting information
- "cancelled" = user cancelled or session ended without completion
- **form**:
- Empty object {{}} when end is "in_progress" or "cancelled"
- Complete structured form data when end is "complete"
## Initial Greeting & Introduction ## Initial Greeting & Introduction
**Bot:** "Hello! I'm your drone survey booking assistant. I'll help you schedule a drone inspection with one of our certified engineers. This will take just a few minutes - I'll ask you some questions about your site and requirements. Let's get started!"
**Response:**
```json
{{
"message": "Hello! I'm your drone survey booking assistant. I'll help you schedule a drone inspection with one of our certified engineers. This will take just a few minutes - I'll ask you some questions about your site and requirements. Let's get started!",
"options": null,
"requires_selection": false,
"end": "in_progress",
"form": {{}}
}}
```
--- ---
## Step 1: Asset Type Identification ## Step 1: Asset Type Identification
**Bot:** "First, what type of asset needs surveying?
Please select: **Response:**
1. Solar Farm ```json
2. Wind Turbine {{
3. Other renewable energy asset "message": "First, what type of asset needs surveying?",
"options": ["Solar Farm", "Wind Turbine", "Other renewable energy asset"],
"requires_selection": true,
"end": "in_progress",
"form": {{}}
}}
```
If you selected 'Other', please specify what type of asset it is." **If "Other" selected:**
```json
**Follow-up if needed:** "Could you provide more details about the asset type? This helps us assign the right specialist engineer." {{
"message": "Could you please specify what type of renewable energy asset it is? This helps us assign the right specialist engineer.",
"options": null,
"requires_selection": false,
"end": "in_progress",
"form": {{}}
}}
```
--- ---
## Step 2: Site Identification ## Step 2: Site Identification
**Bot:** "Great! Now I need some basic site information.
What's the name or identifier for this site? (This could be a site name, project code, or any reference you use)" **Response:**
```json
{{
"message": "Great! Now I need some basic site information. What's the name or identifier for this site? (This could be a site name, project code, or any reference you use)",
"options": null,
"requires_selection": false,
"end": "in_progress",
"form": {{}}
}}
```
**Follow-up:** "Perfect. Could you also provide the location? Please share: **Follow-up:**
- Full address, OR ```json
- GPS coordinates, OR {{
- Nearest landmark/town if exact address isn't available" "message": "Perfect. Could you also provide the location? Please share the full address, GPS coordinates, or nearest landmark/town if exact address isn't available.",
"options": null,
"requires_selection": false,
"end": "in_progress",
"form": {{}}
}}
```
--- ---
## Step 3: System Size/Scope ## Step 3: System Size/Scope
**Bot:** "To help our engineer prepare properly, what's the size/scope of your installation?
For example: **Response:**
- Solar: Number of panels or total kW/MW capacity ```json
- Wind: Number of turbines or total MW capacity {{
- Other: Any relevant size/capacity details" "message": "To help our engineer prepare properly, what's the size/scope of your installation? For example: Solar (Number of panels or total kW/MW capacity), Wind (Number of turbines or total MW capacity), or any relevant size/capacity details. Any rough estimate is fine.",
"options": null,
**If unclear:** "Any rough estimate is fine - this helps our engineer know what equipment to bring and how long the survey might take." "requires_selection": false,
"end": "in_progress",
"form": {{}}
}}
```
--- ---
## Step 4: Access Requirements Check ## Step 4: Access Requirements Check
**Bot:** "Now I need to understand site access requirements.
Is your site gated or has restricted access?" **Response:**
```json
{{
"message": "Now I need to understand site access requirements. Is your site gated or has restricted access?",
"options": ["Yes - Restricted Access", "No - Open Access"],
"requires_selection": true,
"end": "in_progress",
"form": {{}}
}}
```
**If YES:** **If "Yes" selected:**
"I'll need access details: ```json
- Gate code (if applicable) {{
- Key holder contact details "message": "I'll need access details including: gate code (if applicable), key holder contact details, any specific access instructions, and best entry point or directions. Please provide these details.",
- Any specific access instructions "options": null,
- Best entry point or directions" "requires_selection": false,
"end": "in_progress",
"form": {{}}
}}
**If NO:** **If "No" selected:**
"Great! Is the site easily accessible by vehicle for our drone equipment?" ```json
{{
"message": "Great! Is the site easily accessible by vehicle for our drone equipment?",
"options": ["Yes - Vehicle Accessible", "No - Limited Vehicle Access"],
"requires_selection": true,
"end": "in_progress",
"form": {{}}
}}
```
--- ---
## Step 5: On-Site Contact Information ## Step 5: On-Site Contact Information
**Bot:** "Who should our engineer contact on the day of the survey?
Please provide: **Response:**
- Contact person's name ```json
- Phone number {{
- Their role (optional - e.g., site manager, maintenance technician)" "message": "Who should our engineer contact on the day of the survey? Please provide: contact person's name, phone number, and their role (optional - e.g., site manager, maintenance technician).",
"options": null,
"requires_selection": false,
"end": "in_progress",
"form": {{}}
}}
```
**Follow-up:** "Will this person be available on-site during the survey, or should our engineer call ahead?" **Follow-up:**
```json
{{
"message": "Will this person be available on-site during the survey, or should our engineer call ahead?",
"options": ["Available on-site", "Call ahead required"],
"requires_selection": true,
"end": "in_progress",
"form": {{}}
}}
```
--- ---
## Step 6: Special Access/Safety Requirements ## Step 6: Special Access/Safety Requirements
**Bot:** "Are there any special requirements our engineer should know about?
For example: **Response:**
- Specific PPE needed ```json
- Safety induction required {{
- Time restrictions (e.g., only accessible during certain hours) "message": "Are there any special requirements our engineer should know about? For example: specific PPE needed, safety induction required, time restrictions, security clearance needed, or any site hazards or restrictions?",
- Security clearance needed "options": ["Yes - Special Requirements", "No - Standard Requirements"],
- Any site hazards or restrictions" "requires_selection": true,
"end": "in_progress",
"form": {{}}
}}
```
**If none:** "That's fine - our engineers always bring standard safety equipment." **If "Yes" selected:**
```json
{{
"message": "Please provide details about the special requirements so our engineer can prepare accordingly.",
"options": null,
"requires_selection": false,
"end": "in_progress",
"form": {{}}
}}
```
--- ---
## Step 7: Survey Purpose ## Step 7: Survey Purpose
**Bot:** "What's the main purpose of this drone survey?
Please select: **Response:**
1. Routine maintenance inspection ```json
2. Insurance assessment {{
3. Fault diagnosis/investigation "message": "What's the main purpose of this drone survey?",
4. Compliance/regulatory requirement "options": [
5. Performance optimization "Routine maintenance inspection",
6. Other (please specify)" "Insurance assessment",
"Fault diagnosis/investigation",
"Compliance/regulatory requirement",
"Performance optimization",
"Other"
],
"requires_selection": true,
"end": "in_progress",
"form": {{}}
}}
```
**Follow-up based on selection:** **Follow-up based on selection (example for "Fault diagnosis"):**
- **Maintenance:** "Any specific areas of concern or components to focus on?" ```json
- **Insurance:** "Do you have any specific requirements from your insurer?" {{
- **Fault:** "Can you describe the issue you're experiencing?" "message": "Can you describe the issue you're experiencing? This will help our engineer focus on the right areas.",
- **Compliance:** "Which standards or regulations need to be met?" "options": null,
"requires_selection": false,
"end": "in_progress",
"form": {{}}
}}
```
--- ---
## Step 8: Special Notes/Additional Requirements ## Step 8: Special Notes/Additional Requirements
**Bot:** "Almost done! Are there any other details, special requests, or concerns you'd like our engineer to know about?
For example: **Response:**
- Specific weather requirements ```json
- Preferred time of day {{
- Areas to avoid "message": "Almost done! Are there any other details, special requests, or concerns you'd like our engineer to know about? For example: specific weather requirements, preferred time of day, areas to avoid, additional documentation needed, or urgent timeline requirements?",
- Additional documentation needed "options": ["Yes - Additional Requirements", "No - Standard Procedure"],
- Urgent timeline requirements" "requires_selection": true,
"end": "in_progress",
**If none:** "No problem - we'll proceed with standard survey procedures." "form": {{}}
}}
```
--- ---
## Step 9: Preferred Timing ## Step 9: Preferred Timing
**Bot:** "When would you prefer the survey to take place?
Please let me know: **Response:**
- Any specific dates you prefer ```json
- Days of the week that work best {{
- Time of day preferences "message": "When would you prefer the survey to take place? Please let me know: any specific dates you prefer, days of the week that work best, time of day preferences, and how urgent this is.",
- How urgent this is (ASAP, within 1 week, within 1 month, flexible) "options": ["ASAP (within 1-2 days)", "Within 1 week", "Within 1 month", "Flexible timing"],
"requires_selection": true,
Or simply tell me if you're flexible and want to see all available options." "end": "in_progress",
"form": {{}}
}}
```
--- ---
## Step 10: Calendar Check & Booking Confirmation ## Step 10: Calendar Check & Booking Confirmation
**Bot:** "Perfect! Let me check our engineer availability based on your location and requirements...
[SYSTEM: Check available calendar slots based on location, engineer expertise, and user preferences using the approriate tools] **Response (before checking calendar):**
```json
{{
"message": "Perfect! Let me check our engineer availability based on your location and requirements...",
"options": null,
"requires_selection": false,
"end": "in_progress",
"form": {{}}
}}
```
Here are the available time slots: **Response (with available slots):**
```json
**Next Week:** {{
- Monday, [Date] - 9:00 AM - 12:00 PM "message": "Here are the available time slots: Next Week: Monday, [Date] - 9:00 AM - 12:00 PM, Wednesday, [Date] - 2:00 PM - 5:00 PM, Friday, [Date] - 10:00 AM - 1:00 PM. Following Week: Tuesday, [Date] - 8:00 AM - 11:00 AM, Thursday, [Date] - 1:00 PM - 4:00 PM. Which slot works best for you?",
- Wednesday, [Date] - 2:00 PM - 5:00 PM "options": [
- Friday, [Date] - 10:00 AM - 1:00 PM "Monday, [Date] - 9:00 AM - 12:00 PM",
"Wednesday, [Date] - 2:00 PM - 5:00 PM",
**Following Week:** "Friday, [Date] - 10:00 AM - 1:00 PM",
- Tuesday, [Date] - 8:00 AM - 11:00 AM "Tuesday, [Date] - 8:00 AM - 11:00 AM",
- Thursday, [Date] - 1:00 PM - 4:00 PM "Thursday, [Date] - 1:00 PM - 4:00 PM",
"None of these work - need alternatives"
Which slot works best for you?" ],
"requires_selection": true,
"end": "in_progress",
"form": {{}}
}}
```
--- ---
## Final Confirmation ## Final Confirmation
**Bot:** "Excellent! Let me confirm your booking:
**Survey Details:** **Response:**
- Asset: [Asset Type] at [Site Name] ```json
- Location: [Address/GPS] {{
- Date & Time: [Selected Slot] "message": "Excellent! Your booking is confirmed: Asset: [Asset Type] at [Site Name], Location: [Address], Date & Time: [Selected Slot], Purpose: [Survey Purpose], Engineer: [Engineer Name] - [Phone Number], Your Contact: [Contact Person] - [Phone Number], Access Details: [Summary], Special Notes: [Any requirements]. You'll receive a confirmation email shortly. Our engineer will call 30 minutes before arrival. Is there anything else I can help you with today?",
- Purpose: [Survey Purpose] "options": ["Yes - I have more questions", "No - That's everything"],
- Engineer: [Engineer Name] - [Phone Number] "requires_selection": true,
"end": "complete",
"form": {{
"asset_type": "[Asset Type from Step 1]",
"asset_details": "[Additional asset details if 'Other' was selected]",
"site_name": "[Site name/identifier from Step 2]",
"site_location": "[Full address/GPS/landmark from Step 2]",
"system_size": "[Size/scope details from Step 3]",
"access_type": "[Restricted/Open from Step 4]",
"access_details": "[Gate codes, key holder, instructions from Step 4]",
"vehicle_access": "[Vehicle accessibility info from Step 4]",
"contact_person": "[Name from Step 5]",
"contact_phone": "[Phone number from Step 5]",
"contact_role": "[Role/title from Step 5]",
"contact_availability": "[On-site/Call ahead from Step 5]",
"special_safety_requirements": "[PPE, induction, restrictions from Step 6]",
"survey_purpose": "[Main purpose from Step 7]",
"survey_purpose_details": "[Additional details based on purpose from Step 7]",
"additional_requirements": "[Special requests, weather, timing notes from Step 8]",
"timing_preference": "[ASAP/1 week/1 month/flexible from Step 9]",
"preferred_dates": "[Specific dates mentioned by user]",
"selected_slot": "[Confirmed date and time slot]",
"assigned_engineer": "[Engineer name and contact]",
"booking_timestamp": "[Current timestamp when booking completed]",
"booking_id": "[Generated unique booking ID]"
}}
}}
```
**Your Contact:** [Contact Person] - [Phone Number] **If user says "No - That's everything":**
```json
**Access Details:** [Summary of access requirements] {{
"message": "Perfect! Your drone survey is all booked. Thank you for choosing our services. Have a great day!",
**Special Notes:** [Any special requirements] "options": null,
"requires_selection": false,
This booking is now confirmed! You'll receive a confirmation email shortly. "end": "complete",
"form": {{
Our engineer will call your on-site contact 30 minutes before arrival. If you need to make any changes, please contact us at [contact details]. "asset_type": "[Asset Type from Step 1]",
"asset_details": "[Additional asset details if 'Other' was selected]",
Is there anything else I can help you with today?" "site_name": "[Site name/identifier from Step 2]",
"site_location": "[Full address/GPS/landmark from Step 2]",
"system_size": "[Size/scope details from Step 3]",
"access_type": "[Restricted/Open from Step 4]",
"access_details": "[Gate codes, key holder, instructions from Step 4]",
"vehicle_access": "[Vehicle accessibility info from Step 4]",
"contact_person": "[Name from Step 5]",
"contact_phone": "[Phone number from Step 5]",
"contact_role": "[Role/title from Step 5]",
"contact_availability": "[On-site/Call ahead from Step 5]",
"special_safety_requirements": "[PPE, induction, restrictions from Step 6]",
"survey_purpose": "[Main purpose from Step 7]",
"survey_purpose_details": "[Additional details based on purpose from Step 7]",
"additional_requirements": "[Special requests, weather, timing notes from Step 8]",
"timing_preference": "[ASAP/1 week/1 month/flexible from Step 9]",
"preferred_dates": "[Specific dates mentioned by user]",
"selected_slot": "[Confirmed date and time slot]",
"assigned_engineer": "[Engineer name and contact]",
"booking_timestamp": "[Current timestamp when booking completed]",
"booking_id": "[Generated unique booking ID]"
}}
}}
```
--- ---
## Error Handling & Clarification Prompts ## Error Handling & Clarification Prompts
### If User Provides Incomplete Information: ### If User Provides Incomplete Information:
**Bot:** "I need a bit more information to complete your booking. Could you please provide [specific missing detail]?" ```json
{{
"message": "I need a bit more information to complete your booking. Could you please provide [specific missing detail]?",
"options": null,
"requires_selection": false,
"end": "in_progress",
"form": {{}}
}}
```
### If No Available Slots Match Preferences: ### If No Available Slots Match Preferences:
**Bot:** "I don't see any available slots that match your exact preferences. However, I have these alternatives: ```json
[List alternative slots] {{
"message": "I don't see any available slots that match your exact preferences. However, I have these alternatives: [List alternative slots]. Would any of these work?",
"options": ["[Alternative Slot 1]", "[Alternative Slot 2]", "[Alternative Slot 3]", "Check with scheduling team for other options"],
"requires_selection": true,
"end": "in_progress",
"form": {{}}
}}
```
Would any of these work, or would you prefer me to check with our scheduling team for other options?" ### If User Wants to Cancel:
```json
### If User Wants to Modify Information: {{
**Bot:** "No problem! Which detail would you like to change? I can update: "message": "I understand you'd like to cancel. No problem at all. If you change your mind, feel free to start a new booking session anytime. Have a great day!",
1. Site information "options": null,
2. Access details "requires_selection": false,
3. Survey requirements "end": "cancelled",
4. Contact information "form": {{}}
5. Preferred timing" }}
```
### If Technical Issues: ### If Technical Issues:
**Bot:** "I'm having trouble accessing our calendar system right now. Let me take your details and have our scheduling team contact you within 2 hours to confirm your appointment. Is that acceptable?" ```json
{{
--- "message": "I'm having trouble accessing our calendar system right now. Let me have our scheduling team contact you within 2 hours to confirm your appointment. Is that acceptable?",
"options": ["Yes - Have team contact me", "No - I'll try again later"],
"requires_selection": true,
"end": "in_progress",
"form": {{}}
}}
## Additional Context for Bot Behavior ## Additional Context for Bot Behavior
- **Always respond in valid JSON format with all required fields**
- **Form field:** Always include empty {{}} when end is "in_progress" or "cancelled", complete form data when end is "complete"
- **Tone:** Professional but friendly, efficient but not rushed - **Tone:** Professional but friendly, efficient but not rushed
- **Flexibility:** Always offer alternatives if first options don't work - **Flexibility:** Always offer alternatives if first options don't work
- **Confirmation:** Summarize key details at each major step - **Confirmation:** Summarize key details at major steps
- **Safety Focus:** Always emphasize safety requirements and proper preparation - **Safety Focus:** Always emphasize safety requirements and proper preparation
- **Contact:** Provide clear next steps and contact information - **Contact:** Provide clear next steps and contact information
- **Urgency Handling:** Prioritize urgent requests and escalate when needed - **Urgency Handling:** Prioritize urgent requests and escalate when needed
NOTE: THIS IS SOLELY YOUR TASK PLEASE, NO OTHR THING NOTE: THIS IS SOLELY YOUR TASK PLEASE, NO OTHER THING
: DO NOT FABRICATE AVAILABLE SLOTS, ALWAYS CHECK FIRST : DO NOT FABRICATE AVAILABLE SLOTS, ALWAYS CHECK FIRST
: ALWAYS RESPOND IN VALID JSON FORMAT WITH THE REQUIRED FIELDS
: RETURN ONLY JSON FORMAT NO EXPLANATION OR EXTRA INFORMATION BEFORE OR AFTER, DO NOT ADD ```json before or after
""" """
+106
View File
@@ -0,0 +1,106 @@
def flight_prompt(booking_details,weather_details):
return f"""
You are an expert drone survey analyst with extensive experience in renewable energy site inspections. Your task is to analyze booking form data and generate comprehensive environmental, safety, and operational predictions for drone surveys.
### Instructions:
You will be provided with a complete booking form containing site details, contact information, access requirements, and survey specifications. Based on this information, you must generate predictions and assessments in the specified JSON format.
PROVIDED BOOKING FORM DETAILS: {booking_details}
PROVIDED WEATHER DETAILS: {weather_details} , BASED ON CURRENT WEATHER CONDITIONS API
### Analysis Requirements:
1. **Airspace Assessment**: Predict airspace type based on location, proximity to airports, urban density [Class G, class E, class C, class D, Restricted]
2. **Terrain Analysis**: Assess terrain type from site description, asset type, and location context [Flat, Uneven, Hilly, Urban, Remote]
3. **Weather Risk Evaluation**: Estimate weather risk based on location, season, timing, and site exposure [low, medium, high]
4. **Obstruction Identification**: Predict likely obstructions from site type, infrastructure, and environmental context
5. **Safety & Compliance**: Generate safety protocols based on site requirements and asset type
6. **Risk Mitigation**: Create appropriate risk management strategies
### Prediction Guidelines:
- **Airspace Types**: C (Controlled), D (Controlled), E (Controlled), G (Uncontrolled), Restricted
- **Terrain Types**: flat, uneven, hilly, urban, remote
- **Weather Risk Levels**: low, medium, high
- Use logical inference when specific data is not available
- Reference UK/regional standards for locations outside explicit coverage
- Prioritize safety and conservative estimates when uncertain
### Output Format:
You must respond with a properly formatted JSON object containing all required fields. Do not include explanations outside the JSON structure.
### JSON Response Template:
```json
{{
"airspace_assessment": {{
"airspace_type": "[C/D/E/G/Restricted]",
"confidence_level": "[high/medium/low]",
"reasoning": "[Brief explanation of prediction]",
"restrictions": "[Any predicted airspace limitations]"
}},
"terrain_analysis": {{
"terrain_type": "[flat/uneven/hilly/urban/remote]",
"terrain_description": "[Detailed terrain assessment]",
"accessibility": "[Vehicle/equipment access prediction]",
"elevation_considerations": "[Any elevation factors]"
}},
"weather_assessment": {{
"weather_risk_level": "[low/medium/high]",
"predicted_conditions": {{
"wind_speed": "[Estimated km/h]",
"visibility": "[clear/limited/poor]",
"precipitation": "[none/light/moderate/heavy expected]",
"temperature": "[Estimated °C]"
}},
"weather_details": "[AI-style weather summary]",
"seasonal_considerations": "[Relevant seasonal factors]"
}},
"obstruction_analysis": {{
"nearby_obstructions": "[Predicted obstacles description]",
"power_infrastructure": "[Electrical hazards assessment]",
"vegetation": "[Tree/plant interference potential]",
"structures": "[Buildings/towers/equipment predictions]",
"clearance_requirements": "[Recommended clearance distances]"
}},
"safety_compliance": {{
"public_safety_assessed": true,
"wildlife_check_status": "[complete/requires_attention]",
"airspace_clearance_status": "[complete/requires_attention]",
"permits_status": "[complete/requires_attention]",
"environmental_concerns": "[Any predicted environmental issues]"
}},
"risk_mitigation_plan": {{
"required_ppe": "[PPE specifications]",
"emergency_procedures": "[Emergency contact and procedures]",
"alternative_plans": "[Backup landing zones, weather alternatives]",
"monitoring_requirements": "[What to monitor during operation]",
"communication_protocols": "[Site communication requirements]"
}},
"operational_recommendations": {{
"optimal_flight_time": "[Best time window for survey]",
"recommended_approach": "[Flight pattern/approach suggestions]",
"equipment_considerations": "[Drone/sensor recommendations]",
"duration_estimate": "[Predicted survey duration]",
"crew_requirements": "[Personnel needs]"
}},
"confidence_metrics": {{
"overall_confidence": "[high/medium/low]",
"data_completeness": "[percentage or assessment]",
"external_data_needed": "[List of external data sources recommended]",
"verification_required": "[On-site verification items]"
}}
}}
NOTE: THIS IS SOLELY YOUR TASK PLEASE, NO OTHER THING
: DO NOT FABRICATE AVAILABLE SLOTS, ALWAYS CHECK FIRST
: ALWAYS RESPOND IN VALID JSON FORMAT WITH THE REQUIRED FIELDS
: RETURN ONLY JSON FORMAT NO EXPLANATION OR EXTRA INFORMATION BEFORE OR AFTER, DO NOT ADD ```json before or after
"""
+317 -26
View File
@@ -1,38 +1,329 @@
# terminal_chat.py import asyncio
from src.llm.orchestrator import DroneBot, Message # Adjust import path as needed import aiohttp
import json
from datetime import datetime, timedelta
from typing import Dict, List, Optional, Tuple
import re
def terminal_chat(): class DroneWeatherDataExtractor:
print("🚁 DroneBot Terminal Chat") """
print("Type 'exit' to quit.\n") Extract location data from drone booking forms and fetch structured
weather information for later use in analysis systems.
"""
# Initial conversation history def __init__(self):
history = [] self.base_url = "https://api.open-meteo.com/v1/forecast"
# Initialize the bot def extract_coordinates(self, booking_data: Dict) -> Tuple[float, float]:
bot = DroneBot(history=[], use_openai_as_fallback=True) """Extract latitude and longitude from booking form data."""
try:
# Try to get from GPS coordinates field
gps_coords = booking_data.get("site_information", {}).get("gps_coordinates", {})
# Get initial user input if isinstance(gps_coords, dict):
while True: lat_str = gps_coords.get("latitude", "")
user_input = input("👤 You: ") lng_str = gps_coords.get("longitude", "")
if user_input.lower() in ("exit", "quit"):
print("👋 Exiting DroneBot. Goodbye!")
break
# Add user message to history # Parse coordinate strings like "53.4408° N" and "2.2426° W"
history.append(Message(role="human", content=user_input)) lat_match = re.search(r'([\d.]+)', lat_str)
lng_match = re.search(r'([\d.]+)', lng_str)
# Update bot's history if lat_match and lng_match:
bot.history = history latitude = float(lat_match.group(1))
longitude = float(lng_match.group(1))
# Get bot response # Handle direction indicators
response = bot.chat(user_input) if 'S' in lat_str.upper():
latitude = -latitude
if 'W' in lng_str.upper():
longitude = -longitude
# Add bot response to history return latitude, longitude
history.append(Message(role="ai", content=response["final_message"]))
# Print bot response # Fallback: try to extract from site_location string
print(f"🤖 DroneBot: {response['final_message']}\n") site_location = booking_data.get("form", {}).get("site_location", "")
coord_pattern = r'GPS: ([\d.]+)° ([NS]), ([\d.]+)° ([EW])'
match = re.search(coord_pattern, site_location)
if match:
lat, lat_dir, lng, lng_dir = match.groups()
latitude = float(lat) if lat_dir == 'N' else -float(lat)
longitude = float(lng) if lng_dir == 'E' else -float(lng)
print(f"Extracted coordinates: {latitude}, {longitude}")
return latitude, longitude
except (ValueError, KeyError) as e:
print(f"Error extracting coordinates: {e}")
raise ValueError("Could not extract valid coordinates from booking data")
def extract_booking_info(self, booking_data: Dict) -> Dict:
"""Extract relevant booking information in structured format."""
try:
# Extract date information
assigned_date = booking_data.get("job_overview", {}).get("assigned_date")
preferred_dates = booking_data.get("form", {}).get("preferred_dates")
selected_slot = booking_data.get("form", {}).get("selected_slot")
# Extract timing information
timing = booking_data.get("timing", {})
start_time = timing.get("start_time")
end_time = timing.get("end_time")
duration = timing.get("survey_duration")
# Extract site information
site_info = booking_data.get("site_information", {})
form_info = booking_data.get("form", {})
return {
"job_id": booking_data.get("job_id"),
"job_number": booking_data.get("job_overview", {}).get("job_number"),
"site_name": site_info.get("site_name"),
"region": site_info.get("region"),
"full_address": site_info.get("full_address"),
"asset_type": form_info.get("asset_type"),
"system_size": form_info.get("system_size"),
"survey_purpose": form_info.get("survey_purpose"),
"assigned_engineer": form_info.get("assigned_engineer"),
"contact_person": form_info.get("contact_person"),
"contact_phone": form_info.get("contact_phone"),
"dates": {
"assigned_date": assigned_date,
"preferred_dates": preferred_dates,
"selected_slot": selected_slot
},
"timing": {
"start_time": start_time,
"end_time": end_time,
"duration": duration,
"buffer_time": timing.get("buffer_time")
},
"access_details": {
"access_type": form_info.get("access_type"),
"vehicle_access": form_info.get("vehicle_access"),
"access_details": form_info.get("access_details")
},
"safety_requirements": form_info.get("special_safety_requirements"),
"additional_requirements": form_info.get("additional_requirements"),
"booking_timestamp": form_info.get("booking_timestamp"),
"booking_id": form_info.get("booking_id")
}
except Exception as e:
print(f"Error extracting booking info: {e}")
return {}
def parse_target_date(self, booking_data: Dict) -> datetime:
"""Extract and parse the target date from booking data."""
try:
# Try different date fields
date_fields = [
booking_data.get("job_overview", {}).get("assigned_date"),
booking_data.get("form", {}).get("preferred_dates"),
booking_data.get("form", {}).get("selected_slot")
]
for date_field in date_fields:
if date_field:
# Handle different date formats
if "January 23, 2025" in date_field or "January 23rd, 2025" in date_field:
return datetime(2025, 1, 23)
elif "January 24" in date_field:
return datetime(2025, 1, 24)
# Default fallback
return datetime.now() + timedelta(days=1)
except Exception as e:
print(f"Error parsing date: {e}")
return datetime.now() + timedelta(days=1)
async def fetch_weather_data(self, latitude: float, longitude: float,
target_date: datetime, days_range: int = 3) -> Dict:
"""Fetch structured weather data for specific coordinates and date range."""
# Calculate date range around target date
start_date = target_date - timedelta(days=days_range//2)
end_date = target_date + timedelta(days=days_range//2)
params = {
"latitude": latitude,
"longitude": longitude,
"current": [
"temperature_2m",
"relative_humidity_2m",
"wind_speed_10m"
# "wind_direction_10m",
# "weather_code",
# "pressure_msl",
# "cloud_cover"
],
"hourly": [
"temperature_2m",
"relative_humidity_2m"
# "wind_speed_10m",
# "wind_direction_10m",
# "precipitation",
# "weather_code",
# "visibility",
# "cloud_cover",
# "pressure_msl"
],
# "daily": [
# "temperature_2m_max",
# "temperature_2m_min"]
# # "sunrise",
# "sunset",
# "precipitation_sum",
# "wind_speed_10m_max",
# "wind_direction_10m_dominant"
# ],
# "start_date": start_date.strftime("%Y-%m-%d"),
# "end_date": end_date.strftime("%Y-%m-%d"),
# "timezone": "auto"
}
# Convert lists to comma-separated strings for API
for key, value in params.items():
if isinstance(value, list):
params[key] = ",".join(value)
async with aiohttp.ClientSession() as session:
async with session.get(self.base_url, params=params) as response:
if response.status == 200:
return await response.json()
else:
raise Exception(f"Weather API request failed: {response.status}")
def structure_weather_data(self, raw_weather: Dict, target_date: datetime) -> Dict:
"""Structure raw weather data into organized format."""
try:
structured_data = {
"location": {
"latitude": raw_weather.get("latitude"),
"longitude": raw_weather.get("longitude"),
"elevation": raw_weather.get("elevation"),
"timezone": raw_weather.get("timezone")
},
"current_conditions": raw_weather.get("current", {}),
"hourly_forecast": {
"times": raw_weather.get("hourly", {}).get("time", []),
"temperature": raw_weather.get("hourly", {}).get("temperature_2m", []),
"humidity": raw_weather.get("hourly", {}).get("relative_humidity_2m", []),
"wind_speed": raw_weather.get("hourly", {}).get("wind_speed_10m", []),
"wind_direction": raw_weather.get("hourly", {}).get("wind_direction_10m", []),
"precipitation": raw_weather.get("hourly", {}).get("precipitation", []),
"weather_code": raw_weather.get("hourly", {}).get("weather_code", []),
"visibility": raw_weather.get("hourly", {}).get("visibility", []),
"cloud_cover": raw_weather.get("hourly", {}).get("cloud_cover", []),
"pressure": raw_weather.get("hourly", {}).get("pressure_msl", [])
},
"daily_forecast": {
"dates": raw_weather.get("daily", {}).get("time", []),
"temperature_max": raw_weather.get("daily", {}).get("temperature_2m_max", []),
"temperature_min": raw_weather.get("daily", {}).get("temperature_2m_min", []),
"sunrise": raw_weather.get("daily", {}).get("sunrise", []),
"sunset": raw_weather.get("daily", {}).get("sunset", []),
"precipitation_sum": raw_weather.get("daily", {}).get("precipitation_sum", []),
"wind_speed_max": raw_weather.get("daily", {}).get("wind_speed_10m_max", []),
"wind_direction_dominant": raw_weather.get("daily", {}).get("wind_direction_10m_dominant", [])
},
"target_date": target_date.strftime("%Y-%m-%d"),
"data_retrieved_at": datetime.now().isoformat()
}
return structured_data
except Exception as e:
return {"error": f"Error structuring weather data: {e}"}
async def extract_booking_weather_data(self, booking_data: Dict) -> Dict:
"""Main extraction function - returns structured data for external use."""
try:
# Extract all booking information
booking_info = self.extract_booking_info(booking_data)
# Extract coordinates and target date
latitude, longitude = self.extract_coordinates(booking_data)
print(f"Latitude: {latitude}, Longitude: {longitude}")
target_date = self.parse_target_date(booking_data)
# Fetch raw weather data
raw_weather = await self.fetch_weather_data(latitude, longitude, target_date)
# Structure the weather data
structured_weather = self.structure_weather_data(raw_weather, target_date)
# Return combined structured data
return {
# "booking_data": booking_info,
# "coordinates": {
# "latitude": latitude,
# "longitude": longitude
# },
# "target_date": target_date.strftime("%Y-%m-%d"),
"weather_data": structured_weather,
"extraction_metadata": {
"processed_at": datetime.now().isoformat(),
"api_endpoint": self.base_url,
"data_source": "open-meteo"
}
}
except Exception as e:
return {
"error": f"Data extraction failed: {e}",
"job_id": booking_data.get("job_id", "unknown"),
"processed_at": datetime.now().isoformat()
}
# Example usage
async def main():
"""Demonstrate the data extraction system."""
# Sample booking data
booking_form_input = {
"job_id": "1043",
"job_overview": {
"job_number": "Job #1043",
"site_name": "Hightower Solar Farm",
"assigned_date": "January 23, 2025",
"status": "Scheduled"
},
"site_information": {
"site_name": "Hightower Solar Farm",
"region": "North England",
"full_address": "Grange Lane, Manchester M34 7TF",
"gps_coordinates": {
"latitude": "13.41° N",
"longitude": "52.52 W"
}
},
"timing": {
"start_time": "09:00 AM",
"end_time": "10:30 AM",
"survey_duration": "3045 mins",
"buffer_time": "45 mins"
},
"form": {
"asset_type": "Solar Farm",
"system_size": "5.2 MW capacity, approximately 16,000 panels across 12 hectares",
"survey_purpose": "Insurance assessment",
"assigned_engineer": "David Wilson - 0161-555-0876",
"contact_person": "Sarah Thompson",
"contact_phone": "0161-555-0234"
}
}
# Initialize extractor
extractor = DroneWeatherDataExtractor()
# Extract structured data
result = await extractor.extract_booking_weather_data(booking_form_input)
# Display structured output
print("=== STRUCTURED DATA EXTRACTION ===")
print(json.dumps(result, indent=2))
if __name__ == "__main__": if __name__ == "__main__":
terminal_chat() asyncio.run(main())
+48
View File
@@ -0,0 +1,48 @@
booking_form_input = {
"job_id": "1043",
"job_overview": {
"job_number": "Job #1043",
"site_name": "Hightower Solar Farm",
"assigned_date": "January 23, 2025",
"status": "Scheduled"
},
"site_information": {
"site_name": "Hightower Solar Farm",
"region": "North England",
"full_address": "Grange Lane, Manchester M34 7TF",
"gps_coordinates": {
"latitude": "53.4408° N",
"longitude": "2.2426° W"
}
},
"timing": {
"start_time": "09:00 AM",
"end_time": "10:30 AM",
"survey_duration": "3045 mins",
"buffer_time": "45 mins"
},
"form": {
"asset_type": "Solar Farm",
"asset_details": "null",
"site_name": "Hightower Solar Farm",
"site_location": "Grange Lane, Manchester M34 7TF, GPS: 53.4408° N, 2.2426° W",
"system_size": "5.2 MW capacity, approximately 16,000 panels across 12 hectares",
"access_type": "Restricted Access",
"access_details": "Main gate access code: 7841. Contact facility manager Sarah Thompson at 0161-555-0234 for entry. Follow yellow markers to operations building. Site requires visitor registration.",
"vehicle_access": "Yes - Vehicle Accessible",
"contact_person": "Sarah Thompson",
"contact_phone": "0161-555-0234",
"contact_role": "Facility Manager",
"contact_availability": "Available on-site",
"special_safety_requirements": "Hard hats, high-vis vests, and steel-toe boots mandatory. Safety induction required (20 minutes). Keep 100m distance from substation area. Weather restriction: no flights in winds >15mph.",
"survey_purpose": "Insurance assessment",
"survey_purpose_details": "Annual insurance inspection required for policy renewal. Focus on structural integrity, panel condition, and inverter housing. Insurance company requires thermal imaging and detailed photographic documentation.",
"additional_requirements": "Morning survey preferred for optimal lighting conditions. Need detailed documentation of any panel damage or hotspots. Provide georeferenced images for insurance mapping. Weather backup date: January 24th same time.",
"timing_preference": "Within 1 week",
"preferred_dates": "January 23rd morning preferred, backup January 24th",
"selected_slot": "January 23rd, 2025 - 09:00 AM - 10:30 AM",
"assigned_engineer": "David Wilson - 0161-555-0876",
"booking_timestamp": "2025-01-15T10:45:22Z",
"booking_id": "DSV-2025-0115-007"
}
}
+78
View File
@@ -0,0 +1,78 @@
import os
import json
import asyncio
from langchain_openai import ChatOpenAI
from langchain_core.messages import HumanMessage, SystemMessage
from src.prompts.templates.flght_prompt import flight_prompt # You must save the prompt function here
from src.config.llm_config import LlmConfig
from config import Config
from src.components.data_extraction.weather_data import DroneWeatherDataExtractor
class DroneAssessmentAgent:
def __init__(self):
self.llm = ChatOpenAI(
api_key=Config.OPENAI_API_KEY,
model=LlmConfig.openai.models.gpt_4o,
temperature=0.3
)
self.weather_extractor = DroneWeatherDataExtractor()
async def run(self, booking_form: str) -> dict:
"""
Run the drone environmental & safety assessment agent.
Args:
booking_form_text (str): Raw text of the booking form
Returns:
dict: JSON output of the AI assessment
"""
weather_data = await self.weather_extractor.extract_booking_weather_data(booking_form)
booking_form_text = json.dumps(booking_form)
prompt = flight_prompt(booking_form_text, weather_data) # Contains the instructions + JSON template
messages = [
SystemMessage(content=prompt),
HumanMessage(content=booking_form_text)
]
try:
response = self.llm.invoke(messages)
if hasattr(response, "content"):
response_text = response.content.strip()
print("Raw LLM Response:\n", response_text[:300], "...\n")
# Attempt to parse JSON from response
start_idx = response_text.find('{')
end_idx = response_text.rfind('}') + 1
if start_idx == -1 or end_idx == -1:
raise ValueError("No JSON object found in output")
json_str = response_text[start_idx:end_idx]
return json.loads(json_str)
else:
raise ValueError("LLM returned no usable content")
except Exception as e:
print(f"Error in DroneAssessmentAgent: {str(e)}")
return {
"error": str(e),
"raw_response": response.content if 'response' in locals() else None
}
async def main():
"""Async main function to properly handle the async agent."""
from test2 import booking_form_input
agent = DroneAssessmentAgent()
result = await agent.run(booking_form_input)
print("\nStructured Assessment Output:\n")
print(json.dumps(result, indent=2))
if __name__ == "__main__":
asyncio.run(main())