Data-Flow / web_research.py
transformer03's picture
added the toggle for fallback
5875a4c
Raw
History Blame Contribute Delete
11.3 kB
# web_research.py
import os
import re
import csv
import json
import logging
from datetime import datetime
from urllib.parse import urljoin
from config import (
MISTRAL_API_KEY, SEARCH_API_KEY, CSE_ID,
OUTPUT_DIR, KNOWLEDGE_CACHE_DIR, APP_VERSION
)
from schemas import (
TRIATHLON_SCHEMA, RUNNING_SCHEMA, SWIMMING_SCHEMA, DUATHLON_SCHEMA,
AQUATHLON_SCHEMA, AQUABIKE_SCHEMA, CYCLING_SCHEMA, FITNESS_RACING_SCHEMA
)
from agent import MistralAnalystAgent, AgentInitializationError
from utils import (
serialize_knowledge_base, deserialize_knowledge_base, format_final_row, SafeCSVWriter
)
from supabase_client import get_supabase_client
logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
logger = logging.getLogger(__name__)
def save_output_to_supabase(filepath: str, agent_mode: str, event_type: str = None):
supabase = get_supabase_client()
if not supabase: return
try:
if not os.path.exists(filepath):
logger.warning(f"File not found for upload: {filepath}")
return
with open(filepath, 'r', encoding='utf-8') as f:
reader = csv.DictReader(f)
run_data = list(reader)
if not run_data:
logger.info(f"File {filepath} is empty. Skipping upload.")
return
payload = {
"agent_mode": agent_mode,
"event_type": event_type,
"filename": os.path.basename(filepath),
"run_data": run_data,
"event_count": len(run_data)
}
supabase.table('run_outputs').insert(payload).execute()
logger.info(f"Successfully saved {os.path.basename(filepath)} to Supabase.")
except Exception as e:
logger.error(f"Failed to save output file to Supabase: {e}")
def run_web_analyst_mode(input_file_path, output_dir_override=None, max_events=None, output_filename=None, enable_fallback=False):
print("=" * 60)
print(f"MODE: Web Research Analyst (Crawl4AI) | Version: {APP_VERSION}")
print("=" * 60)
effective_output_dir = output_dir_override or OUTPUT_DIR
run_timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
try:
with open(input_file_path, 'r', encoding='utf-8') as f:
races = json.load(f)
races.sort(key=lambda x: x.get('Priority', 99))
print(f"[SUCCESS] Found {len(races)} events to process from '{input_file_path}'.")
except (FileNotFoundError, json.JSONDecodeError) as e:
print(f"[ERROR] CONFIGURATION ERROR: Could not read '{input_file_path}'. Error: {e}")
return
if max_events is not None and max_events > 0:
races = races[:max_events]
print(f"[INFO] Limiting run to a maximum of {max_events} events.")
grouped_races, failed_missions = {},[]
for race in races:
raw_type = race.get("Type")
race_type = str(raw_type if raw_type else "Unknown").lower()
if race_type not in grouped_races: grouped_races[race_type] = []
grouped_races[race_type].append(race)
agent = None
type_to_filepath = {}
try:
for race_type, race_list in grouped_races.items():
schema_map = {
"triathlon": TRIATHLON_SCHEMA, "run": RUNNING_SCHEMA, "running": RUNNING_SCHEMA,
"trail running": RUNNING_SCHEMA, "swimming": SWIMMING_SCHEMA, "swimathon": SWIMMING_SCHEMA,
"duathlon": DUATHLON_SCHEMA, "aquathlon": AQUATHLON_SCHEMA, "aquabike": AQUABIKE_SCHEMA,
"cycling": CYCLING_SCHEMA, "fitness racing": FITNESS_RACING_SCHEMA
}
schema = schema_map.get(race_type)
if not schema:
print(f"[WARNING] Skipping unknown race type '{race_type}'.")
continue
# Pass the fallback flag to the agent
agent = MistralAnalystAgent(
mistral_key=MISTRAL_API_KEY,
search_key=SEARCH_API_KEY, cse_id=CSE_ID, schema=schema,
enable_fallback=enable_fallback
)
base_filename = f"{output_filename}_{race_type}" if output_filename else f"Crawl4AI_WebAnalyst_{race_type}_{run_timestamp}"
final_filename = f"{base_filename}.csv"
output_filepath = os.path.join(effective_output_dir, final_filename)
type_to_filepath[race_type] = output_filepath
print(f"\n[INFO] Processing {len(race_list)} '{race_type}' events. Target File: {final_filename}")
with SafeCSVWriter(output_filepath, schema) as writer:
for i, race_info in enumerate(race_list):
event_name = race_info.get("Festival")
if not event_name:
continue
print("\n" + "=" * 60)
print(f"[STARTING MISSION] {i + 1}/{len(race_list)} for '{race_type.upper()}': {event_name}")
print("=" * 60)
caching_key = agent.get_caching_key(event_name)
cache_file_path = os.path.join(KNOWLEDGE_CACHE_DIR, f"{caching_key}.json")
knowledge_base = agent.run(race_info)
if knowledge_base:
with open(cache_file_path, 'w', encoding='utf-8') as f_cache:
json.dump(serialize_knowledge_base(knowledge_base), f_cache, indent=4)
for variant_name, data in knowledge_base.items():
if row := format_final_row(event_name, variant_name, data, schema):
writer.writerow(row)
print(f"[SUCCESS] Saved data for: {event_name}")
else:
print(f"[ERROR] No data extracted for: {event_name}")
failed_missions.append(event_name)
except AgentInitializationError as e:
print(f"[FATAL] Agent setup failed: {e}")
# Not re-raising here to let the script exit gracefully instead of crashing streamlit
return
finally:
if agent: agent.shutdown()
print("\n[INFO] Syncing output files to database...")
for r_type, filepath in type_to_filepath.items():
if os.path.exists(filepath):
save_output_to_supabase(filepath, agent_mode="web_analyst", event_type=r_type)
print("\n" + "=" * 60)
print("Web Research Analyst Run Complete")
if failed_missions:
print("\n[SUMMARY] Failed Missions:")
for event in failed_missions: print(f" - {event}")
else:
print("\n[SUCCESS] All missions completed successfully.")
print("=" * 60)
def run_calendar_crawl_mode(output_dir_override=None, max_events=None, output_filename=None, enable_fallback=False):
print("=" * 60)
print(f"MODE: Calendar Crawl | Version: {APP_VERSION}")
print("=" * 60)
effective_output_dir = output_dir_override or OUTPUT_DIR
schema = CYCLING_SCHEMA
run_timestamp = datetime.now().strftime("%Y%m%d-%H%M%S")
agent = None
failed_missions =[]
try:
agent = MistralAnalystAgent(
mistral_key=MISTRAL_API_KEY,
search_key=SEARCH_API_KEY, cse_id=CSE_ID, schema=schema,
enable_fallback=enable_fallback
)
CALENDAR_URL = "https://www.audaxindia.in/events.php"
main_page_content = agent._get_content_from_url(CALENDAR_URL, is_web_research=False)
if not main_page_content:
print(f"[FATAL] Could not retrieve content from the calendar URL. Aborting.")
return
event_list =[]
organizer_pattern = re.compile(r"^\s*\*\*(?P<city>[^*]+)\*\*\s*(?P<name>[^\[\n\r]+)")
link_pattern = re.compile(r'\[(?P<details>.*?)\]\((?P<url>.*?)\)')
for line in main_page_content.splitlines():
organizer_match = organizer_pattern.match(line)
if organizer_match:
city, name = organizer_match.group('city').strip(), organizer_match.group('name').strip()
current_organizer = f"{city} {name}"
for link_match in link_pattern.finditer(line):
event_list.append({
"organizer": current_organizer,
"event_details": link_match.group('details').strip(),
"url": urljoin(CALENDAR_URL, link_match.group('url'))
})
if not event_list:
print("[INFO] No events found on calendar.")
return
print(f"[SUCCESS] Found {len(event_list)} total events.")
if max_events is not None and max_events > 0:
events_to_process = event_list[:max_events]
print(f"[INFO] Limiting run to {max_events} events.")
else:
events_to_process = event_list
base_filename = output_filename or f"CalendarCrawl_Cycling_{run_timestamp}"
final_filename = f"{base_filename}.csv"
output_filepath = os.path.join(effective_output_dir, final_filename)
print(f"[INFO] Writing all output to: {output_filepath}")
with SafeCSVWriter(output_filepath, schema) as writer:
for i, event_info in enumerate(events_to_process):
event_name = f"{event_info['organizer']} {event_info['event_details']}"
event_url = event_info['url']
print("\n" + "=" * 60)
print(f"[STARTING MISSION] {i + 1}/{len(events_to_process)} FOR: {event_name}")
print("=" * 60)
race_info = {"Festival": event_name, "Type": "Cycling"}
url_part = hashlib.md5(event_url.encode()).hexdigest()[:8]
caching_key = agent.get_caching_key(f"{event_name}-{url_part}")
cache_file_path = os.path.join(KNOWLEDGE_CACHE_DIR, f"{caching_key}.json")
knowledge_base = agent.run_direct(race_info, direct_urls=[event_url])
if knowledge_base:
with open(cache_file_path, 'w', encoding='utf-8') as f:
json.dump(serialize_knowledge_base(knowledge_base), f, indent=4)
main_festival_name = event_info['organizer']
for variant_name, data in knowledge_base.items():
if row := format_final_row(main_festival_name, variant_name, data, schema):
writer.writerow(row)
print(f"[SUCCESS] Saved data for: {event_name}")
else:
print(f"[ERROR] No data found for: {event_name}")
failed_missions.append(event_name)
save_output_to_supabase(output_filepath, agent_mode="calendar_crawl", event_type="Cycling")
except AgentInitializationError as e:
print(f"[FATAL] Error: {e}")
return
finally:
if agent: agent.shutdown()
print("\n" + "=" * 60)
print("Calendar Crawl Complete")
if failed_missions:
print("\n[SUMMARY] Failed Missions:")
for event in failed_missions: print(f" - {event}")
else:
print("\n[SUCCESS] All missions completed successfully.")
print("=" * 60)