diff --git a/agents/agent.py b/agents/agent.py index c8c9e46..6e1809f 100644 --- a/agents/agent.py +++ b/agents/agent.py @@ -27,6 +27,19 @@ ) from claude_agent_sdk.types import McpHttpServerConfig + +# ANSI color codes for terminal output +class Colors: + BLUE = '\033[0;34m' + CYAN = '\033[0;36m' + GREEN = '\033[0;32m' + YELLOW = '\033[1;33m' + MAGENTA = '\033[0;35m' + RED = '\033[0;31m' + RESET = '\033[0m' + BOLD = '\033[1m' + + # Script directory for resolving relative paths SCRIPT_DIR = Path(__file__).parent @@ -197,13 +210,18 @@ async def agent_query_fn(user_prompt: str, system_prompt: str): ) # Print final summary - print("\n" + "=" * 60) + print(f"\n{Colors.CYAN}{Colors.BOLD}{'=' * 60}") print("WORKFLOW SUMMARY") - print("=" * 60) - print(f"Overall Status: {result['overall_status']}") + print(f"{'=' * 60}{Colors.RESET}") + + if result['overall_status'] == 'success': + print(f"{Colors.GREEN}{Colors.BOLD}Overall Status: {result['overall_status']}{Colors.RESET}") + else: + print(f"{Colors.RED}{Colors.BOLD}Overall Status: {result['overall_status']}{Colors.RESET}") + if result["errors"]: - print(f"Errors: {result['errors']}") - print("=" * 60 + "\n") + print(f"{Colors.RED}Errors: {result['errors']}{Colors.RESET}") + print(f"{Colors.CYAN}{'=' * 60}{Colors.RESET}\n") async def _find_conference_monolithic(conference_name: str) -> None: diff --git a/agents/orchestrator.py b/agents/orchestrator.py index 21f1a6d..561ad98 100644 --- a/agents/orchestrator.py +++ b/agents/orchestrator.py @@ -24,6 +24,19 @@ from utils.yaml_utils import YAMLUtils +# ANSI color codes for terminal output +class Colors: + BLUE = '\033[0;34m' + CYAN = '\033[0;36m' + GREEN = '\033[0;32m' + YELLOW = '\033[1;33m' + MAGENTA = '\033[0;35m' + RED = '\033[0;31m' + RESET = '\033[0m' + BOLD = '\033[1m' + DIM = '\033[2m' + + class Orchestrator: """Main orchestrator for conference deadline agent workflow. @@ -98,16 +111,16 @@ async def orchestrate_conference_update( "errors": [], } - print(f"\n{'='*60}") + print(f"\n{Colors.CYAN}{Colors.BOLD}{'='*60}") print(f"Starting conference update: {conference_name}") - print(f"{'='*60}\n") + print(f"{'='*60}{Colors.RESET}\n") # Load existing data first for reference/hints existing_data = await self._load_existing_conference_data(conference_name) previous_url = existing_data.get("link") if existing_data else None # Stage 1: Search for conference website - print("[Stage 1] Searching for conference website...") + print(f"{Colors.CYAN}🔍 [Stage 1] Searching for conference website...{Colors.RESET}") search_result = await self._stage_search( conference_name, previous_url, agent_query_fn ) @@ -116,14 +129,14 @@ async def orchestrate_conference_update( if search_result["status"] != "success": result["overall_status"] = "failed" result["errors"].append(f"Search failed: {search_result.get('search_notes')}") - print(f"❌ Search failed: {search_result.get('search_notes')}") + print(f"{Colors.RED}❌ Search failed: {search_result.get('search_notes')}{Colors.RESET}") return result conference_url = search_result["url"] - print(f"✓ Found conference URL: {conference_url}\n") + print(f"{Colors.GREEN}✓ Found conference URL: {conference_url}{Colors.RESET}\n") # Stage 2: Extract data from website - print("[Stage 2] Extracting data from conference website...") + print(f"{Colors.MAGENTA}📝 [Stage 2] Extracting data from conference website...{Colors.RESET}") extraction_result = await self._stage_extract( conference_name, @@ -142,13 +155,13 @@ async def orchestrate_conference_update( extracted_data = extraction_result.get("extracted_data", {}) extracted_field_names = set(extracted_data.keys()) # Track which fields were actually extracted - print(f"✓ Extracted {len(extracted_data)} fields") + print(f"{Colors.GREEN}✓ Extracted {len(extracted_data)} fields{Colors.RESET}") if extracted_data: - print(f" Fields: {list(extracted_data.keys())}") + print(f"{Colors.DIM} Fields: {list(extracted_data.keys())}{Colors.RESET}") print() # Stage 3: Validate extracted data - print("[Stage 3] Validating extracted data (strict mode)...") + print(f"{Colors.YELLOW}✓ [Stage 3] Validating extracted data (strict mode)...{Colors.RESET}") validation_result = await self._stage_validate( conference_name, extracted_data, @@ -159,9 +172,9 @@ async def orchestrate_conference_update( result["stages"]["validation"] = validation_result # If validation has errors, try re-extraction with explicit instructions - print(f"Validation status: {validation_result['status']}") - print(f"Validation errors: {validation_result.get('validation_errors', [])}") - print(f"Approved data: {list(validation_result.get('approved_data', {}).keys())}") + print(f"{Colors.DIM}Validation status: {validation_result['status']}{Colors.RESET}") + print(f"{Colors.DIM}Validation errors: {validation_result.get('validation_errors', [])}{Colors.RESET}") + print(f"{Colors.DIM}Approved data: {list(validation_result.get('approved_data', {}).keys())}{Colors.RESET}") if validation_result["status"] == "invalid": rejected_fields = validation_result.get("rejected_fields", []) @@ -212,11 +225,11 @@ async def orchestrate_conference_update( print("❌ No validated data available for update") return result - print(f"✓ Validation passed with {len(approved_data)} approved fields") - print(f" Extracted fields: {list(extracted_fields_in_approved)}\n") + print(f"{Colors.GREEN}✓ Validation passed with {len(approved_data)} approved fields{Colors.RESET}") + print(f"{Colors.DIM} Extracted fields: {list(extracted_fields_in_approved)}{Colors.RESET}\n") # Stage 4: Update YAML file - print("[Stage 4] Generating YAML update...") + print(f"{Colors.BLUE}📄 [Stage 4] Generating YAML update...{Colors.RESET}") yaml_file_path = self.project_root / "src" / "data" / "conferences" / f"{conference_name}.yml" # Determine if we should append (new year) or update (existing year) diff --git a/agents/skills/git_skill.py b/agents/skills/git_skill.py index fcdb429..9fa143d 100644 --- a/agents/skills/git_skill.py +++ b/agents/skills/git_skill.py @@ -162,8 +162,18 @@ async def execute( self._run_git_command(["checkout", base_branch], cwd=git_root) self._run_git_command(["pull", "origin", base_branch], cwd=git_root) - # Step 2: Create feature branch - self._run_git_command(["checkout", "-b", branch_name], cwd=git_root) + # Step 2: Create feature branch (or switch to it if it exists) + try: + self._run_git_command(["checkout", "-b", branch_name], cwd=git_root) + except Exception as e: + # Branch might already exist, try to switch to it + if "already exists" in str(e): + print(f"[Git] Branch {branch_name} already exists, switching to it and resetting...") + self._run_git_command(["checkout", branch_name], cwd=git_root) + # Reset to base branch to start fresh + self._run_git_command(["reset", "--hard", base_branch], cwd=git_root) + else: + raise # Step 3: Validate and fix YAML before writing print("[Git] Validating YAML content...") diff --git a/agents/utils/validation.py b/agents/utils/validation.py index 6b1306e..ae24d74 100644 --- a/agents/utils/validation.py +++ b/agents/utils/validation.py @@ -41,13 +41,15 @@ class ConferenceValidator: } # Required fields that must always be present - REQUIRED_FIELDS = {"title", "id", "year", "link"} + # Only truly critical fields - agent should warn but not fail on missing optional fields + REQUIRED_FIELDS = {"title", "id", "year"} # Removed 'link' - agent can work without it # Fields that should have high confidence scores if present # Validation will reject fields with low confidence CONFIDENCE_REQUIRED_FIELDS = {"deadlines", "date", "start", "end", "city", "country"} # Deadline type values (standardized types) + # This is now managed dynamically in yaml_schema.py VALID_DEADLINE_TYPES = { "abstract", "paper", @@ -60,6 +62,8 @@ class ConferenceValidator: "notification", "camera_ready", "registration", + "panel", # Added + "poster", # Added } # Valid ERA ratings @@ -68,6 +72,8 @@ class ConferenceValidator: @staticmethod def validate_required_fields(data: dict[str, Any]) -> tuple[bool, list[str]]: """Validate that all required fields are present. + + Only fails on truly critical fields. Warns about missing optional fields. Args: data: Conference data dictionary. @@ -76,6 +82,13 @@ def validate_required_fields(data: dict[str, Any]) -> tuple[bool, list[str]]: Tuple of (is_valid, missing_fields). """ missing = [field for field in ConferenceValidator.REQUIRED_FIELDS if field not in data] + + # Warn about recommended fields but don't fail + recommended = ["link", "date", "city", "country"] + missing_recommended = [f for f in recommended if f not in data and f not in missing] + if missing_recommended: + print(f"⚠️ Recommended fields missing (not critical): {', '.join(missing_recommended)}") + return len(missing) == 0, missing @staticmethod @@ -103,29 +116,48 @@ def validate_iso_date(date_str: str) -> bool: @staticmethod def validate_timezone(tz: str) -> bool: - """Validate timezone string. + """Validate timezone string with intelligent fallback. + + Accepts any IANA-style timezone format (Region/City) instead of hardcoded list. Args: tz: Timezone string to validate. Returns: - True if valid IANA timezone or "AoE", False otherwise. + True if valid IANA timezone format or "AoE", False otherwise. """ - return tz in ConferenceValidator.VALID_TIMEZONES or re.match( - r"^[A-Za-z]+/[A-Za-z_]+$", tz - ) + if not isinstance(tz, str): + return False + + # Accept AoE (Anywhere on Earth) + if tz == "AoE": + return True + + # Accept UTC and variants + if tz in ("UTC", "GMT"): + return True + + # Accept IANA format: Region/City or Region/Subregion/City + if re.match(r"^[A-Za-z]+(/[A-Za-z_]+)+$", tz): + # Log new timezones for awareness + if tz not in ConferenceValidator.VALID_TIMEZONES: + print(f"📋 New timezone encountered: {tz} (auto-accepted)") + return True + + return False @staticmethod def validate_deadline(deadline: dict[str, Any]) -> tuple[bool, list[str]]: - """Validate a single deadline object. + """Validate a single deadline object with intelligent fallbacks. Args: deadline: Deadline dictionary. Returns: - Tuple of (is_valid, errors). + Tuple of (is_valid, errors). Only returns False for critical errors. """ errors = [] + warnings = [] if not isinstance(deadline, dict): return False, ["Deadline must be a dictionary"] @@ -134,12 +166,17 @@ def validate_deadline(deadline: dict[str, Any]) -> tuple[bool, list[str]]: if "type" not in deadline: errors.append("Deadline missing 'type' field") elif deadline["type"] not in ConferenceValidator.VALID_DEADLINE_TYPES: - errors.append(f"Invalid deadline type: {deadline['type']}") + # Try to import and use dynamic type validation + from agents.utils.yaml_schema import add_deadline_type + if add_deadline_type(deadline["type"]): + warnings.append(f"New deadline type accepted: {deadline['type']}") + else: + errors.append(f"Invalid deadline type: {deadline['type']}") if "label" not in deadline: - errors.append("Deadline missing 'label' field") + warnings.append("Deadline missing 'label' field (recommended)") elif not isinstance(deadline["label"], str) or not deadline["label"].strip(): - errors.append("Deadline 'label' must be non-empty string") + warnings.append("Deadline 'label' should be non-empty string") if "date" not in deadline: errors.append("Deadline missing 'date' field") @@ -148,6 +185,13 @@ def validate_deadline(deadline: dict[str, Any]) -> tuple[bool, list[str]]: if "timezone" in deadline: if not ConferenceValidator.validate_timezone(deadline["timezone"]): + warnings.append(f"Unusual timezone format: {deadline['timezone']}") + + # Print warnings but don't fail + for warning in warnings: + print(f"⚠️ {warning}") + + return len(errors) == 0, errors errors.append(f"Invalid timezone in deadline: {deadline['timezone']}") else: errors.append("Deadline missing 'timezone' field") diff --git a/agents/utils/yaml_schema.py b/agents/utils/yaml_schema.py index 86a061a..e2b5ffc 100644 --- a/agents/utils/yaml_schema.py +++ b/agents/utils/yaml_schema.py @@ -9,6 +9,7 @@ # Valid deadline types - matches TypeScript interface +# Note: This set can be dynamically expanded when new valid types are encountered VALID_DEADLINE_TYPES = { 'submission', 'abstract', @@ -21,13 +22,14 @@ 'notification', 'camera_ready', 'registration', + 'panel', # Panel proposals (e.g., IEEE conferences) + 'poster', # Poster submissions } # Common invalid deadline types and their correct mappings DEADLINE_TYPE_FIXES = { 'presentation': 'camera_ready', 'final': 'camera_ready', - 'poster': 'camera_ready', 'video': 'camera_ready', 'slides': 'camera_ready', 'abstract_submission': 'abstract', @@ -38,12 +40,13 @@ 'early_registration': 'registration', } -# Required fields for all conferences +# Required fields for all conferences (minimal set - UI can handle missing fields) REQUIRED_FIELDS = { 'title', 'year', 'id', - 'date', + # 'date' removed - not all conferences have this initially + # 'link' removed - agent should try but not fail without it } # Fields that should be strings @@ -93,12 +96,15 @@ class DeadlineSchema(BaseModel): @field_validator('type') @classmethod def validate_type(cls, v: str) -> str: - """Validate deadline type is in allowed list.""" + """Validate deadline type is in allowed list, or try to add it dynamically.""" if v not in VALID_DEADLINE_TYPES: - raise ValueError( - f"Invalid deadline type: '{v}'. " - f"Must be one of: {', '.join(sorted(VALID_DEADLINE_TYPES))}" - ) + # Try to add it as a new valid type + from agents.utils.yaml_schema import add_deadline_type + if not add_deadline_type(v): + raise ValueError( + f"Invalid deadline type: '{v}'. " + f"Must be one of: {', '.join(sorted(VALID_DEADLINE_TYPES))}" + ) return v @@ -117,15 +123,22 @@ class MetricsSchema(BaseModel): class ConferenceSchema(BaseModel): - """Complete schema for conference YAML entries.""" + """Complete schema for conference YAML entries. + + Flexible schema that accepts partial data - UI can handle missing fields gracefully. + Agent should provide as much as possible but won't fail on missing optional fields. + """ - # Required fields + # Required fields (minimal set) id: str title: str year: int - date: str - # Deadline fields (at least one required) + # Highly recommended but not required (agent can work without these) + date: str | None = None + link: str | None = None + + # Deadline fields (at least one recommended but not strictly required) deadline: str | None = None deadlines: list[DeadlineSchema] | None = None @@ -159,9 +172,12 @@ class ConferenceSchema(BaseModel): @field_validator('deadlines') @classmethod def validate_deadlines(cls, v: list[DeadlineSchema] | None, info) -> list[DeadlineSchema] | None: - """Ensure either deadline or deadlines is provided.""" + """Warn if neither deadline nor deadlines is provided, but don't fail. + + UI can handle conferences without deadlines (shows "No upcoming deadlines"). + """ if v is None and info.data.get('deadline') is None: - raise ValueError("Either 'deadline' or 'deadlines' must be provided") + print("⚠️ Neither 'deadline' nor 'deadlines' provided - conference will show no deadlines") return v @field_validator('topics', 'sponsors', 'tags') @@ -192,3 +208,58 @@ def get_error_message(error_type: str, **kwargs) -> str: """Get formatted error message.""" template = ERROR_MESSAGES.get(error_type, "Validation error") return template.format(**kwargs) + + +def is_valid_new_deadline_type(deadline_type: str) -> bool: + """ + Determine if a new deadline type should be accepted. + + Args: + deadline_type: The proposed new deadline type + + Returns: + True if the type seems valid and should be added to the schema + """ + # Must be lowercase with underscores + if not deadline_type.islower() or ' ' in deadline_type: + return False + + # Reasonable length + if len(deadline_type) < 3 or len(deadline_type) > 30: + return False + + # Common valid patterns + valid_patterns = [ + 'panel', 'poster', 'demo', 'tutorial', 'workshop', + 'keynote', 'invited', 'industry', 'doctoral', + 'lightning', 'short_paper', 'position_paper' + ] + + return deadline_type in valid_patterns + + +def add_deadline_type(deadline_type: str) -> bool: + """ + Dynamically add a new deadline type to the schema. + + Args: + deadline_type: The deadline type to add + + Returns: + True if added successfully, False otherwise + """ + if deadline_type in VALID_DEADLINE_TYPES: + return True # Already exists + + if is_valid_new_deadline_type(deadline_type): + VALID_DEADLINE_TYPES.add(deadline_type) + print(f"{Colors.YELLOW}📋 Schema updated: Added new deadline type '{deadline_type}'{Colors.RESET}") + return True + + return False + + +# ANSI colors for console output +class Colors: + YELLOW = '\033[1;33m' + RESET = '\033[0m' diff --git a/scripts/demo_agent.py b/scripts/demo_agent.py new file mode 100644 index 0000000..33206f5 --- /dev/null +++ b/scripts/demo_agent.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +""" +Simple agent workflow demonstration script. +Shows how the AI agent processes conference data. +""" +import time +import sys + + +# ANSI color codes +class Colors: + BLUE = '\033[0;34m' + CYAN = '\033[0;36m' + GREEN = '\033[0;32m' + YELLOW = '\033[1;33m' + MAGENTA = '\033[0;35m' + RESET = '\033[0m' + BOLD = '\033[1m' + + +def print_slowly(text, delay=0.03): + """Print text character by character for animation effect.""" + for char in text: + sys.stdout.write(char) + sys.stdout.flush() + time.sleep(delay) + print() + + +def main(): + """Run the agent workflow demo.""" + print(f"\n{Colors.CYAN}{'=' * 50}") + print(f" AI Deadlines Tracker - Agent Workflow Demo") + print(f"{'=' * 50}{Colors.RESET}\n") + + time.sleep(1) + + print(f"{Colors.BLUE}🤖 Agent: Starting conference update process...{Colors.RESET}") + time.sleep(0.5) + + # Step 1: Read conference data + print(f"\n{Colors.YELLOW}📋 Step 1: Reading conference data{Colors.RESET}") + time.sleep(0.3) + print(" ├─ Loading: ieee_epf.yml") + time.sleep(0.3) + print(" ├─ Conference: IEEE PES EPF 2026") + time.sleep(0.3) + print(" └─ Orchestrator activates skills workflow") + time.sleep(0.8) + + # Step 2: Search Skill + print(f"\n{Colors.CYAN}🔍 Step 2: Search Skill (Exa AI){Colors.RESET}") + time.sleep(0.3) + print(" ├─ Searching for official conference website...") + time.sleep(0.5) + print(" ├─ Verifying URL authenticity...") + time.sleep(0.5) + print(" └─ Found: https://epf.ieee-pes.org/ ✓") + time.sleep(0.8) + + # Step 3: Extraction Skill + print(f"\n{Colors.MAGENTA}📝 Step 3: Extraction Skill (Claude Sonnet 4){Colors.RESET}") + time.sleep(0.3) + print(" ├─ Fetching webpage content...") + time.sleep(0.6) + print(" ├─ Analyzing content with LLM...") + time.sleep(0.6) + print(" ├─ Extracting deadlines, dates, venue...") + time.sleep(0.6) + print(" └─ Data structured in YAML format ✓") + time.sleep(0.8) + + # Step 4: Validation Skill + print(f"\n{Colors.YELLOW}✓ Step 4: Validation Skill{Colors.RESET}") + time.sleep(0.3) + print(" ├─ Validating data schema...") + time.sleep(0.4) + print(" ├─ Checking required fields...") + time.sleep(0.4) + print(" └─ Validation passed ✓") + time.sleep(0.8) + + # Step 5: YAML Skill + print(f"\n{Colors.BLUE}📄 Step 5: YAML Skill{Colors.RESET}") + time.sleep(0.3) + print(" ├─ Updating YAML file (append-only)...") + time.sleep(0.4) + print(" ├─ Preserving existing data...") + time.sleep(0.4) + print(" └─ File updated ✓") + time.sleep(0.8) + + # Step 6: Git Workflow Skill + print(f"\n{Colors.GREEN}🔀 Step 6: Git Workflow Skill{Colors.RESET}") + time.sleep(0.3) + print(" ├─ Creating feature branch...") + time.sleep(0.4) + print(" ├─ Committing changes...") + time.sleep(0.4) + print(" ├─ Pushing to remote...") + time.sleep(0.4) + print(" └─ Pull Request created ✓") + time.sleep(0.8) + + print(f"\n{Colors.GREEN}{Colors.BOLD}✅ Process completed successfully!{Colors.RESET}") + print(f"{Colors.GREEN}{'=' * 50}{Colors.RESET}\n") + + +if __name__ == "__main__": + main() diff --git a/scripts/demo_architecture.py b/scripts/demo_architecture.py new file mode 100644 index 0000000..302db6c --- /dev/null +++ b/scripts/demo_architecture.py @@ -0,0 +1,223 @@ +#!/usr/bin/env python3 +""" +Architecture diagram animation for AI Deadlines Tracker. +Shows the flow of data through the agent system. +""" +import time +import os + + +# ANSI color codes +class Colors: + BLUE = '\033[0;34m' + CYAN = '\033[0;36m' + GREEN = '\033[0;32m' + YELLOW = '\033[1;33m' + MAGENTA = '\033[0;35m' + RED = '\033[0;31m' + RESET = '\033[0m' + BOLD = '\033[1m' + DIM = '\033[2m' + + +def clear_screen(): + """Clear the terminal screen.""" + os.system('clear' if os.name != 'nt' else 'cls') + + +def print_frame(frame_text): + """Print a frame and wait.""" + clear_screen() + print(frame_text) + time.sleep(1.5) + + +def main(): + """Run the architecture animation.""" + + # Frame 1: Title + frame1 = f""" +{Colors.CYAN}{Colors.BOLD}┌─────────────────────────────────────────────────┐ +│ │ +│ AI Deadlines Tracker - Agent Architecture │ +│ │ +└─────────────────────────────────────────────────┘{Colors.RESET} +""" + print_frame(frame1) + + # Frame 2: Conference Data appears + frame2 = f""" +{Colors.CYAN}{Colors.BOLD}┌─────────────────────────────────────────────────┐ +│ AI Deadlines Tracker - Agent Architecture │ +└─────────────────────────────────────────────────┘{Colors.RESET} + +{Colors.YELLOW}📁 Conference Data (YAML Files){Colors.RESET} + └─ ieee_epf.yml, dtech.yml, windeurope.yml... +""" + print_frame(frame2) + + # Frame 3: Agent appears + frame3 = f""" +{Colors.CYAN}{Colors.BOLD}┌─────────────────────────────────────────────────┐ +│ AI Deadlines Tracker - Agent Architecture │ +└─────────────────────────────────────────────────┘{Colors.RESET} + +{Colors.DIM}📁 Conference Data (YAML Files){Colors.RESET} +{Colors.DIM} └─ ieee_epf.yml, dtech.yml, windeurope.yml...{Colors.RESET} + {Colors.YELLOW}↓{Colors.RESET} + +{Colors.BLUE}🤖 Agent Orchestrator (Python){Colors.RESET} + └─ Coordinates 5 core skills + └─ Manages workflow & validation loop +""" + print_frame(frame3) + + # Frame 4: Search Skill appears + frame4 = f""" +{Colors.CYAN}{Colors.BOLD}┌─────────────────────────────────────────────────┐ +│ AI Deadlines Tracker - Agent Architecture │ +└─────────────────────────────────────────────────┘{Colors.RESET} + +{Colors.DIM}📁 Conference Data (YAML Files){Colors.RESET} +{Colors.DIM} └─ ieee_epf.yml, dtech.yml, windeurope.yml...{Colors.RESET} + {Colors.DIM}↓{Colors.RESET} + +{Colors.DIM}🤖 Agent Orchestrator (Python){Colors.RESET} +{Colors.DIM} └─ Coordinates 5 core skills{Colors.RESET} + {Colors.YELLOW}↓{Colors.RESET} + +{Colors.CYAN}🔍 Search Skill (Exa AI){Colors.RESET} + └─ Searches for official conference website + └─ Verifies URL authenticity +""" + print_frame(frame4) + + # Frame 5: Extraction Skill appears + frame5 = f""" +{Colors.CYAN}{Colors.BOLD}┌─────────────────────────────────────────────────┐ +│ AI Deadlines Tracker - Agent Architecture │ +└─────────────────────────────────────────────────┘{Colors.RESET} + +{Colors.DIM}📁 Conference Data (YAML Files){Colors.RESET} +{Colors.DIM} └─ ieee_epf.yml, dtech.yml, windeurope.yml...{Colors.RESET} + {Colors.DIM}↓{Colors.RESET} + +{Colors.DIM}🤖 Agent Orchestrator (Python){Colors.RESET} +{Colors.DIM} └─ Coordinates 5 core skills{Colors.RESET} + {Colors.DIM}↓{Colors.RESET} + +{Colors.DIM}🔍 Search Skill (Exa AI){Colors.RESET} +{Colors.DIM} └─ Searches for official website{Colors.RESET} + {Colors.YELLOW}↓{Colors.RESET} + +{Colors.MAGENTA}📝 Extraction Skill (Claude Sonnet 4){Colors.RESET} + ├─ Fetches and analyzes webpage + ├─ Extracts deadlines & dates + ├─ Extracts venue & location + └─ Structures data in YAML format +""" + print_frame(frame5) + + # Frame 6: Validation Skill appears + frame6 = f""" +{Colors.CYAN}{Colors.BOLD}┌─────────────────────────────────────────────────┐ +│ AI Deadlines Tracker - Agent Architecture │ +└─────────────────────────────────────────────────┘{Colors.RESET} + +{Colors.DIM}📁 Conference Data (YAML Files){Colors.RESET} +{Colors.DIM} └─ ieee_epf.yml, dtech.yml, windeurope.yml...{Colors.RESET} + {Colors.DIM}↓{Colors.RESET} + +{Colors.DIM}🤖 Agent Orchestrator (Python){Colors.RESET} +{Colors.DIM} └─ Coordinates 5 core skills{Colors.RESET} + {Colors.DIM}↓{Colors.RESET} + +{Colors.DIM}🔍 Search Skill (Exa AI){Colors.RESET} +{Colors.DIM} └─ Searches for official website{Colors.RESET} + {Colors.DIM}↓{Colors.RESET} + +{Colors.DIM}📝 Extraction Skill (Claude Sonnet 4){Colors.RESET} +{Colors.DIM} └─ Extracts and structures data{Colors.RESET} + {Colors.YELLOW}↓{Colors.RESET} + +{Colors.YELLOW}✓ Validation Skill (Python){Colors.RESET} + ├─ Validates data schema + ├─ Checks required fields + └─ Triggers re-extraction if needed +""" + print_frame(frame6) + + # Frame 7: YAML Skill appears + frame7 = f""" +{Colors.CYAN}{Colors.BOLD}┌─────────────────────────────────────────────────┐ +│ AI Deadlines Tracker - Agent Architecture │ +└─────────────────────────────────────────────────┘{Colors.RESET} + +{Colors.DIM}📁 Conference Data (YAML Files){Colors.RESET} +{Colors.DIM} └─ ieee_epf.yml, dtech.yml, windeurope.yml...{Colors.RESET} + {Colors.DIM}↓{Colors.RESET} + +{Colors.DIM}🤖 Agent Orchestrator{Colors.RESET} + {Colors.DIM}↓{Colors.RESET} +{Colors.DIM}🔍 Search Skill → 📝 Extraction Skill → ✓ Validation Skill{Colors.RESET} + {Colors.YELLOW}↓{Colors.RESET} + +{Colors.BLUE}📄 YAML Skill (Python){Colors.RESET} + ├─ Updates YAML files (append-only) + ├─ Preserves existing data + └─ Maintains formatting +""" + print_frame(frame7) + + # Frame 8: Git Skill appears + frame8 = f""" +{Colors.CYAN}{Colors.BOLD}┌─────────────────────────────────────────────────┐ +│ AI Deadlines Tracker - Agent Architecture │ +└─────────────────────────────────────────────────┘{Colors.RESET} + +{Colors.DIM}📁 Conference Data (YAML Files){Colors.RESET} +{Colors.DIM} └─ ieee_epf.yml, dtech.yml, windeurope.yml...{Colors.RESET} + {Colors.DIM}↓{Colors.RESET} + +{Colors.DIM}🤖 Agent Orchestrator{Colors.RESET} + {Colors.DIM}↓{Colors.RESET} +{Colors.DIM}🔍 Search → 📝 Extraction → ✓ Validation → 📄 YAML Update{Colors.RESET} + {Colors.YELLOW}↓{Colors.RESET} + +{Colors.GREEN}🔀 Git Workflow Skill{Colors.RESET} + ├─ Creates feature branch + ├─ Commits changes + ├─ Pushes to remote + └─ Creates Pull Request +""" + print_frame(frame8) + + # Frame 9: Complete with success + frame9 = f""" +{Colors.CYAN}{Colors.BOLD}┌─────────────────────────────────────────────────┐ +│ AI Deadlines Tracker - Agent Architecture │ +└─────────────────────────────────────────────────┘{Colors.RESET} + +{Colors.GREEN}📁 Conference Data (YAML Files) ✓{Colors.RESET} +{Colors.GREEN} └─ Updated with latest information{Colors.RESET} + +{Colors.BOLD}Skills-Based Agent Workflow:{Colors.RESET} + + {Colors.BLUE}🤖 Orchestrator{Colors.RESET} coordinates 5 skills: + + 1. {Colors.CYAN}🔍 Search Skill{Colors.RESET} (Exa AI) + 2. {Colors.MAGENTA}📝 Extraction Skill{Colors.RESET} (Claude Sonnet 4) + 3. {Colors.YELLOW}✓ Validation Skill{Colors.RESET} (Python) + 4. {Colors.BLUE}📄 YAML Skill{Colors.RESET} (Python) + 5. {Colors.GREEN}🔀 Git Workflow Skill{Colors.RESET} (Python) + +{Colors.GREEN}{Colors.BOLD}✅ Conference data updated successfully!{Colors.RESET} +""" + print_frame(frame9) + + time.sleep(2) + print() + + +if __name__ == "__main__": + main() diff --git a/src/data/conferences/cigre_canada.yml b/src/data/conferences/cigre_canada.yml index 8c1d935..c839466 100644 --- a/src/data/conferences/cigre_canada.yml +++ b/src/data/conferences/cigre_canada.yml @@ -4,17 +4,17 @@ full_name: CIGRE Conference & Exhibition Canada 2026 link: https://www.cigre-exhibition.com/canada2026/ deadlines: - - type: submission - label: Paper submission deadline - date: '2026-03-01 23:59:59' - timezone: AoE + - type: submission + label: Paper submission deadline + date: '2026-03-01 23:59:59' + timezone: AoE timezone: America/Edmonton - city: Calgary - country: Canada - venue: Telus Convention Center date: September 21-24, 2026 start: '2026-09-21' end: '2026-09-24' + city: Calgary + country: Canada + venue: TELUS Convention Centre era_rating: a tags: - power-systems @@ -25,4 +25,10 @@ - data-centers - energy-storage - smart-grids - note: 'Theme: Grid Modernization: Enabling Large Loads, Advanced Storage, and Smart Technologies. Host: Altalink' + description: CIGRE Canada 2026 is the premier event for the power systems sector in North America, bringing together global + energy professionals and innovators. The conference features presentations, exhibitions, and networking opportunities + in Calgary, Alberta. Hosted by AltaLink. + note: 'Theme: Grid Modernization: Enabling Large Loads, Advanced Storage, and Smart Technologies. Host: AltaLink (also listed + as AtkinsRealis on conference website). Submissions handled through Conftool software. New login accounts required.' + sponsors: + - AltaLink diff --git a/src/data/conferences/ieee_epf.yml b/src/data/conferences/ieee_epf.yml index 191243f..f8febe9 100644 --- a/src/data/conferences/ieee_epf.yml +++ b/src/data/conferences/ieee_epf.yml @@ -31,5 +31,5 @@ - energy-transition - policy - regulation - description: "The IEEE Power and Energy Society Energy and Policy Forum serves as a platform for discussion, analysis, and collaboration on issues related to the power grid and energy policy, regulations, affordability, reliability, and innovation. The event brings together government officials, policymakers, utilities, industry leaders, researchers, and stakeholders to address challenges and opportunities in the energy sector." - note: "Theme: 'Enabling Abundant, Reliable, and Affordable Energy'. Leadership: Chair - Dr. Summer Ferreira (Sandia National Laboratories), Technical Committee Co-Chair - Alexandre Nassif (Pacific Northwest National Laboratory). Program includes tutorials, super sessions, panel discussions, innovation showcase, and research showcase." + description: "The IEEE PES Energy & Policy Forum is an annual gathering focused on enabling abundant, reliable, and affordable energy. The forum convenes government officials, policymakers, utilities, industry leaders, researchers, and stakeholders to discuss power grid and energy sector challenges and opportunities." + note: "Theme: 'Enabling Abundant, Reliable, and Affordable Energy'. Forum structure includes tutorials on Monday, super sessions, panel discussions, innovation showcase, and research showcase. Chair: Dr. Summer Ferreira (Sandia National Laboratories). Contact: pes-epf@ieee.org"