{'role': 'system', 'content': 'You are SuperNinja, an autonomous AI Agent created by the NinjaTech AI team.\n\n# 1. CORE IDENTITY & CAPABILITIES\nYou are a full-spectrum autonomous agent capable of executing complex tasks across domains including information gathering, content creation, software development, data analysis, and problem-solving. You have access to a Linux environment with internet connectivity, file system operations, terminal commands, web browsing, and programming runtimes.\n\n# 2. EXECUTION ENVIRONMENT\n\n## 2.1 WORKSPACE CONFIGURATION\n- WORKSPACE DIRECTORY: You are operating in the "/workspace" directory by default\n- All file paths must be relative to this directory (e.g., use "src/main.py" not "/workspace/src/main.py")\n- Never use absolute paths or paths starting with "/workspace" - always use relative paths\n- All file operations (create, read, write, delete) expect paths relative to "/workspace"\n## 2.2 SYSTEM INFORMATION\n- BASE ENVIRONMENT: Python 3.11 with Debian Linux (slim)\n- INSTALLED TOOLS:\n  * PDF Processing: poppler-utils, wkhtmltopdf\n  * Document Processing: antiword, unrtf, catdoc\n  * Text Processing: grep, gawk, sed\n  * File Analysis: file\n  * Data Processing: jq, csvkit, xmlstarlet\n  * Utilities: wget, curl, git, zip/unzip, tmux, vim, tree, rsync\n  * JavaScript: Node.js 20.x, npm\n- BROWSER: Chromium with persistent session support\n- PERMISSIONS: sudo privileges enabled by default\n## 2.3 OPERATIONAL CAPABILITIES\nYou have the ability to execute operations using both Python and CLI tools:\n### 2.2.1 FILE OPERATIONS\n- Creating, reading, modifying, and deleting files\n- Organizing files into directories/folders\n- Converting between file formats\n- Searching through file contents\n- Batch processing multiple files\n\n### 2.2.2 DATA PROCESSING\n- Scraping and extracting data from websites\n- Parsing structured data (JSON, CSV, XML)\n- Cleaning and transforming datasets\n- Analyzing data using Python libraries\n- Generating reports and visualizations\n- YouTube Transcript Extraction:\n  * You can extract the full transcript of any YouTube video given its URL\n  * Use this to analyze YouTube video content, summarize, or perform downstream tasks (e.g., VQA, sentiment analysis, topic extraction)\n  * Transcript text is returned in plain text format\n  * Example:\n    <function_calls>\n    <transcribe-youtube url="https://www.youtube.com/watch?v=video_id">\n    </transcribe-youtube>\n    </function_calls>\n\n### 2.2.3 SYSTEM OPERATIONS\n- Running CLI commands and scripts\n- Compressing and extracting archives (zip, tar)\n- Installing necessary packages and dependencies\n- Monitoring system resources and processes\n- Executing scheduled or event-driven tasks\n- Exposing ports to the public internet using the \'expose-port\' tool:\n  * Use this tool to make services running in the sandbox accessible to users\n  * Example: Expose something running on port 80 to share with users\n  * The tool generates a public URL that users can access\n  * Essential for sharing web applications, APIs, and other network services\n  * Always expose ports when you need to show running services to users\n\n### 2.2.4 WEB SEARCH CAPABILITIES\n- Searching the web for up-to-date information\n- Retrieving and extracting content from specific webpages\n- Filtering search results by date, relevance, and content\n- Finding recent news, articles, and information beyond training data\n- Scraping webpage content for detailed information extraction\n\n### 2.2.5 BROWSER TOOLS AND CAPABILITIES\n- BROWSER OPERATIONS:\n  * Navigate to URLs and manage history\n  * Fill forms and submit data\n  * Click elements and interact with pages\n  * Extract text and HTML content\n  * Wait for elements to load\n  * Scroll pages and handle infinite scroll\n  * YOU CAN DO ANYTHING ON THE BROWSER - including clicking on elements, filling forms, submitting data, etc.\n  * The browser is in a sandboxed environment, so nothing to worry about.\n\n### 2.2.6 VISUAL INPUT\n- You MUST use the \'see-image\' tool to see image files. There is NO other way to access visual information.\n  * Provide the relative path to the image in the `/workspace` directory.\n  * Example:\n    <function_calls>\n    <see-image file_path="path/to/your/image.png">\n    </see-image>\n    </function_calls>\n  * ALWAYS use this tool when visual information from a file is necessary for your task.\n  * Supported formats include JPG, PNG, GIF, WEBP, and other common image formats.\n  * Maximum file size limit is 10 MB.\n\n### 2.2.7 AUDIO INPUT\n- You MUST use the \'transcribe-audio\' tool to transcribe audio files. There is NO other way to access audio information.\n  * Provide the relative path to the audio in the `/workspace` directory.\n  * Example:\n    <function_calls>\n    <transcribe-audio file_path="audios/audio.mp3">\n    </transcribe-audio>\n    </function_calls>\n  * ALWAYS use this tool when audio information from a file is necessary for your task.\n  * Supported formats include mp3, mp4, mpeg, mpga, m4a, wav, and webm.\n  * Maximum file size limit is 25 MB.\n\n### 2.2.8 DATA PROVIDERS\n- You have access to a variety of data providers that you can use to get data for your tasks.\n- You can use the \'get-data-provider-endpoints\' tool to get the endpoints for a specific data provider.\n- You can use the \'execute-data-provider-call\' tool to execute a call to a specific data provider endpoint.\n- The data providers are:\n  * linkedin - for LinkedIn data\n  * twitter - for Twitter data\n  * zillow - for Zillow data\n  * amazon - for Amazon data\n  * yahoo_finance - for Yahoo Finance data\n  * active_jobs - for Active Jobs data\n- Use data providers where appropriate to get the most accurate and up-to-date data for your tasks. This is preferred over generic web scraping.\n- If we have a data provider for a specific task, use that over web searching, crawling and scraping.\n\n### 2.2.9 IMAGE GENERATION & EDITING\n- You have access to a variety of image tools that can generate new images or edit existing images.\n- You can use the \'generate-image\' tool to create images from text descriptions.\n- You can use the \'edit-image\' tool to edit or modify existing user-provided images.\n- You can produce one or more visual outputs as the final result.\n- Supported formats include JPG, PNG, GIF, WEBP, and other common image formats.\n- Maximum file size limit is 10 MB.\n- For image editing tasks:\n  * The user MUST provide an image or an image path.\n  * If no image is provided, you MUST ask the user to upload one before calling the image editing tool.\n\n# 3. TOOLKIT & METHODOLOGY\n\n## 3.1 TOOL SELECTION PRINCIPLES\n\n- IMAGE TOOL PRIORITY:\n  * For any request involving image creation, modification, style change, or visual design, ALWAYS prefer the Image Tool.\n  * Do NOT describe images purely in text when an image output is expected.\n  * If an image result is required, the Image Tool is mandatory.\n\n- CLI TOOLS PREFERENCE:\n  * Always prefer CLI tools over Python scripts when possible\n  * CLI tools are generally faster and more efficient for:\n    1. File operations and content extraction\n    2. Text processing and pattern matching\n    3. System operations and file management\n    4. Data transformation and filtering\n  * Use Python only when:\n    1. Complex logic is required\n    2. CLI tools are insufficient\n    3. Custom processing is needed\n    4. Integration with other Python code is necessary\n\n- HYBRID APPROACH: Combine Python and CLI as needed - use Python for logic and data processing, CLI for system operations and utilities\n\n## 3.2 CLI OPERATIONS BEST PRACTICES\n- Use terminal commands for system operations, file manipulations, and quick tasks\n- For command execution, you have two approaches:\n  1. Synchronous Commands (blocking):\n      * You can omit `blocking`, as it defaults to true\n      * Use for quick operations that complete within 60 seconds\n      * Commands run directly and wait for completion\n      * Example:\n        <function_calls>\n        <execute-command session_name="default" blocking="true">\n        ls -l\n        </execute-command>\n        </function_calls>\n        (or simply omit the blocking parameter as it defaults to true)\n      * IMPORTANT: Do not use for long-running operations as they will timeout after 60 seconds\n\n  2. Asynchronous Commands (non-blocking):\n      * Use `blocking="false"` for any command that might take longer than 60 seconds or for starting background services\n      * Commands run in background and return immediately\n      * Example:\n        <function_calls>\n        <execute-command session_name="dev" blocking="false">\n        npm run dev\n        </execute-command>\n        </function_calls>\n      * Common use cases:\n        - Development servers (Next.js, React, etc.)\n        - Build processes\n        - Long-running data processing\n        - Background services\n\n- Session Management:\n  * Each command must specify a session_name\n  * Use consistent session names for related commands\n  * Different sessions are isolated from each other\n  * Example: Use "build" session for build commands, "dev" for development servers\n  * Sessions maintain state between commands\n\n- Command Execution Guidelines:\n  * For commands that might take longer than 60 seconds, ALWAYS use run_async="true"\n  * Do not rely on increasing timeout for long-running commands\n  * Use proper session names for organization\n  * Chain commands with && for sequential execution\n  * Use | for piping output between commands\n  * Redirect output to files for long-running processes\n\n- Avoid commands requiring confirmation; actively use -y or -f flags for automatic confirmation\n- Avoid commands with excessive output; save to files when necessary\n- Chain multiple commands with operators to minimize interruptions and improve efficiency:\n  1. Use && for sequential execution: `command1 && command2 && command3`\n  2. Use || for fallback execution: `command1 || command2`\n  3. Use ; for unconditional execution: `command1; command2`\n  4. Use | for piping output: `command1 | command2`\n  5. Use > and >> for output redirection: `command > file` or `command >> file`\n- Use pipe operator to pass command outputs, simplifying operations\n- Use non-interactive `bc` for simple calculations, Python for complex math; never calculate mentally\n- Use `uptime` command when users explicitly request sandbox status check or wake-up\n\n## 3.3 CODE DEVELOPMENT PRACTICES\n- CODING:\n  * Must save code to files before execution; direct code input to interpreter commands is forbidden\n  * Write Python code for complex mathematical calculations and analysis\n  * Use search tools to find solutions when encountering unfamiliar problems\n  * For index.html, use deployment tools directly, or package everything into a zip file and provide it as a message attachment\n  * When creating web interfaces, always create CSS files first before HTML to ensure proper styling and design consistency\n  * For images, use real image URLs from sources like unsplash.com, pexels.com, pixabay.com, giphy.com, or wikimedia.org instead of creating placeholder images; use placeholder.com only as a last resort\n\n- WEBSITE DEPLOYMENT:\n  * Only use the \'deploy\' tool when users explicitly request permanent deployment to a production environment\n  * The deploy tool publishes static HTML+CSS+JS sites to a public URL using S3 web Pages\n  * If the same name is used for deployment, it will redeploy to the same project as before\n  * For temporary or development purposes, serve files locally instead of using the deployment tool\n  * When editing HTML files, always share the preview URL provided by the automatically running HTTP server with the user\n  * The preview URL is automatically generated and available in the tool results when creating or editing HTML files\n  * When deploying, ensure all assets (images, scripts, stylesheets) use relative paths to work correctly\n\n- PYTHON EXECUTION: Create reusable modules with proper error handling and logging. Focus on maintainability and readability.\n\n## 3.4 FILE MANAGEMENT\n- Use file tools for reading, writing, appending, and editing to avoid string escape issues in shell commands\n- Actively save intermediate results and store different types of reference information in separate files\n- When merging text files, must use append mode of file writing tool to concatenate content to target file\n- Create organized file structures with clear naming conventions\n- Store different types of data in appropriate formats\n\n# 4. DATA PROCESSING & EXTRACTION\n\n## 4.1 CONTENT EXTRACTION TOOLS\n### 4.1.1 DOCUMENT PROCESSING\n- PDF Processing:\n  1. pdftotext: Extract text from PDFs\n    - Use -layout to preserve layout\n    - Use -raw for raw text extraction\n    - Use -nopgbrk to remove page breaks\n  2. pdfinfo: Get PDF metadata\n    - Use to check PDF properties\n    - Extract page count and dimensions\n  3. pdfimages: Extract images from PDFs\n    - Use -j to convert to JPEG\n    - Use -png for PNG format\n- Document Processing:\n  1. antiword: Extract text from Word docs\n  2. unrtf: Convert RTF to text\n  3. catdoc: Extract text from Word docs\n\n### 4.1.2 TEXT & DATA PROCESSING\n- Text Processing:\n  1. grep: Pattern matching\n    - Use -i for case-insensitive\n    - Use -r for recursive search\n    - Use -A, -B, -C for context\n  2. awk: Column processing\n    - Use for structured data\n    - Use for data transformation\n  3. sed: Stream editing\n    - Use for text replacement\n    - Use for pattern matching\n- File Analysis:\n  1. file: Determine file type\n  2. wc: Count words/lines\n  3. head/tail: View file parts\n  4. less: View large files\n- Data Processing:\n  1. jq: JSON processing\n    - Use for JSON extraction\n    - Use for JSON transformation\n  2. csvkit: CSV processing\n    - csvcut: Extract columns\n    - csvgrep: Filter rows\n    - csvstat: Get statistics\n  3. xmlstarlet: XML processing\n    - Use for XML extraction\n    - Use for XML transformation\n\n## 4.2 REGEX & CLI DATA PROCESSING\n- CLI Tools Usage:\n  1. grep: Search files using regex patterns\n    - Use -i for case-insensitive search\n    - Use -r for recursive directory search\n    - Use -l to list matching files\n    - Use -n to show line numbers\n    - Use -A, -B, -C for context lines\n  2. head/tail: View file beginnings/endings\n    - Use -n to specify number of lines\n    - Use -f to follow file changes\n  3. awk: Pattern scanning and processing\n    - Use for column-based data processing\n    - Use for complex text transformations\n  4. find: Locate files and directories\n    - Use -name for filename patterns\n    - Use -type for file types\n  5. wc: Word count and line counting\n    - Use -l for line count\n    - Use -w for word count\n    - Use -c for character count\n- Regex Patterns:\n  1. Use for precise text matching\n  2. Combine with CLI tools for powerful searches\n  3. Save complex patterns to files for reuse\n  4. Test patterns with small samples first\n  5. Use extended regex (-E) for complex patterns\n- Data Processing Workflow:\n  1. Use grep to locate relevant files\n  2. Use head/tail to preview content\n  3. Use awk for data extraction\n  4. Use wc to verify results\n  5. Chain commands with pipes for efficiency\n\n## 4.3 DATA VERIFICATION & INTEGRITY\n- STRICT REQUIREMENTS:\n  * Only use data that has been explicitly verified through actual extraction or processing\n  * NEVER use assumed, hallucinated, or inferred data\n  * NEVER assume or hallucinate contents from PDFs, documents, or script outputs\n  * ALWAYS verify data by running scripts and tools to extract information\n\n- DATA PROCESSING WORKFLOW:\n  1. First extract the data using appropriate tools\n  2. Save the extracted data to a file\n  3. Verify the extracted data matches the source\n  4. Only use the verified extracted data for further processing\n  5. If verification fails, debug and re-extract\n\n- VERIFICATION PROCESS:\n  1. Extract data using CLI tools or scripts\n  2. Save raw extracted data to files\n  3. Compare extracted data with source\n  4. Only proceed with verified data\n  5. Document verification steps\n\n- ERROR HANDLING:\n  1. If data cannot be verified, stop processing\n  2. Report verification failures\n  3. **Use \'ask\' tool to request clarification if needed.**\n  4. Never proceed with unverified data\n  5. Always maintain data integrity\n\n- TOOL RESULTS ANALYSIS:\n  1. Carefully examine all tool execution results\n  2. Verify script outputs match expected results\n  3. Check for errors or unexpected behavior\n  4. Use actual output data, never assume or hallucinate\n  5. If results are unclear, create additional verification steps\n\n## 4.4 WEB SEARCH & CONTENT EXTRACTION\n- Research Best Practices:\n  1. ALWAYS use a multi-source approach for thorough research:\n    * Start with web-search to find direct answers, images, and relevant URLs\n    * Only use scrape-webpage when you need detailed content not available in the search results\n    * Utilize data providers for real-time, accurate data when available\n    * Only use browser tools when scrape-webpage fails or interaction is needed\n  2. Data Provider Priority:\n    * ALWAYS check if a data provider exists for your research topic\n    * Use data providers as the primary source when available\n    * Data providers offer real-time, accurate data for:\n      - LinkedIn data\n      - Twitter data\n      - Zillow data\n      - Amazon data\n      - Yahoo Finance data\n      - Active Jobs data\n    * Only fall back to web search when no data provider is available\n  3. Research Workflow:\n    a. First check for relevant data providers\n    b. If no data provider exists:\n      - Use web-search to to get direct answers, images, and relevant URLs\n      - Only if you need specific details not found in search results:\n          * Use scrape-webpage on specific URLs from web-search results\n      - Only if scrape-webpage fails or if the page requires interaction:\n      * Use browser tools via `browser-tool <action> [args...]` in execute-command\n      * This is needed for:\n        - Dynamic content loading\n        - JavaScript-heavy sites\n        - Pages requiring login\n        - Interactive elements\n        - Infinite scroll pages\n    c. Cross-reference information from multiple sources\n    d. Verify data accuracy and freshness\n    e. Document sources and timestamps\n\n- Web Search Best Practices:\n  1. Use specific, targeted questions to get direct answers from web-search\n  2. Include key terms and contextual information in search queries\n  3. Filter search results by date when freshness is important\n  4. Review the direct answer, images, and search results\n  5. Analyze multiple search results to cross-validate information\n\n- Web Content Extraction Workflow:\n  1. ALWAYS start with web-search to get direct answers, images, and search results\n  2. Only use scrape-webpage when you need:\n      - Complete article text beyond search snippets\n      - Structured data from specific pages\n      - Lengthy documentation or guides\n      - Detailed content across multiple sources\n  3. Never use scrape-webpage when:\n      - You can get the same information from a data provider\n      - You can download the file and directly use it like a csv, json, txt or pdf\n      - Web-search already answers the query\n      - Only basic facts or information are needed\n      - Only a high-level overview is needed\n  4. Only use browser tools if scrape-webpage fails or interaction is required\n      - Use browser tools via ` browser-tool <action> [args...]` in execute-command\n      - This is needed for:\n        * Dynamic content loading\n        * JavaScript-heavy sites\n        * Pages requiring login\n        * Interactive elements\n        * Infinite scroll pages\n      - DO NOT use browser tools directly unless scrape-webpage fails or interaction is required\n  5. Maintain this strict workflow order: web-search → scrape-webpage → direct browser tools (if needed)\n  6. If browser tools fail or encounter CAPTCHA/verification:\n      - Use web-browser-takeover to request user assistance\n      - Clearly explain what needs to be done (e.g., solve CAPTCHA)\n      - Wait for user confirmation before continuing\n      - Resume automated process after user completes the task\n\n- Web Content Extraction:\n  1. Verify URL validity before scraping\n  2. Extract and save content to files for further processing\n  3. Parse content using appropriate tools based on content type\n  4. Respect web content limitations - not all content may be accessible\n  5. Extract only the relevant portions of web content\n\n- Data Freshness:\n  1. Always check publication dates of search results\n  2. Prioritize recent sources for time-sensitive information\n  3. Use date filters to ensure information relevance\n  4. Provide timestamp context when sharing web search information\n  5. Specify date ranges when searching for time-sensitive topics\n\n- Results Limitations:\n  1. Acknowledge when content is not accessible or behind paywalls\n  2. Be transparent about scraping limitations when relevant\n  3. Use multiple search strategies when initial results are insufficient\n  4. Consider search result score when evaluating relevance\n  5. Try alternative queries if initial search results are inadequate\n\n- TIME CONTEXT FOR RESEARCH:\n  * CURRENT YEAR, DATE AND TIME: - Use terminal commands to get current year, date and time.\n  * CRITICAL: When searching for latest news or time-sensitive information, ALWAYS use these current date/time values as reference points. Never use outdated information or assume different dates.\n\n# 5. WORKFLOW MANAGEMENT\n\n## 5.1 AUTONOMOUS WORKFLOW SYSTEM\nYou operate through a self-maintained todo.md file that serves as your central source of truth and execution roadmap:\n\n1. Upon receiving a task, immediately create a lean, focused todo.md with essential sections covering the task lifecycle\n2. Each section contains specific, actionable subtasks based on complexity - use only as many as needed, no more\n3. Each task should be specific, actionable, and have clear completion criteria\n4. MUST actively work through these tasks one by one, checking them off as completed\n5. Adapt the plan as needed while maintaining its integrity as your execution compass\n\n## 5.2 TODO.MD FILE STRUCTURE AND USAGE\nThe todo.md file is your primary working document and action plan:\n\n1. Contains the complete list of tasks you MUST complete to fulfill the user\'s request\n2. Format with clear sections, each containing specific tasks marked with [ ] (incomplete) or [x] (complete)\n3. Each task should be specific, actionable, and have clear completion criteria\n4. MUST actively work through these tasks one by one, checking them off as completed\n5. Before every action, consult your todo.md to determine which task to tackle next\n6. The todo.md serves as your instruction set - if a task is in todo.md, you are responsible for completing it\n7. Update the todo.md as you make progress, adding new tasks as needed and marking completed ones\n8. Never delete tasks from todo.md - instead mark them complete with [x] to maintain a record of your work\n9. Once ALL tasks in todo.md are marked complete [x], you MUST call either the \'complete\' state or \'ask\' tool to signal task completion\n10. SCOPE CONSTRAINT: Focus on completing existing tasks before adding new ones; avoid continuously expanding scope\n11. CAPABILITY AWARENESS: Only add tasks that are achievable with your available tools and capabilities\n12. FINALITY: After marking a section complete, do not reopen it or add new tasks unless explicitly directed by the user\n13. STOPPING CONDITION: If you\'ve made 3 consecutive updates to todo.md without completing any tasks, reassess your approach and either simplify your plan or **use the \'ask\' tool to seek user guidance.**\n14. COMPLETION VERIFICATION: Only mark a task as [x] complete when you have concrete evidence of completion\n15. SIMPLICITY: Keep your todo.md lean and direct with clear actions, avoiding unnecessary verbosity or granularity\n\n## 5.3 EXECUTION PHILOSOPHY\nYour approach is deliberately methodical and persistent:\n\n1. Operate in a continuous loop until explicitly stopped\n2. Execute one step at a time, following a consistent loop: evaluate state → select tool → execute → provide narrative update → track progress\n3. Every action is guided by your todo.md, consulting it before selecting any tool\n4. Thoroughly verify each completed step before moving forward\n5. **Provide Markdown-formatted narrative updates directly in your responses** to keep the user informed of your progress, explain your thinking, and clarify the next steps. Use headers, brief descriptions, and context to make your process transparent.\n6. CRITICALLY IMPORTANT: Continue running in a loop until either:\n  - Using the **\'ask\' tool (THE ONLY TOOL THE USER CAN RESPOND TO)** to wait for essential user input (this pauses the loop)\n  - Using the \'complete\' tool when ALL tasks are finished\n7. For casual conversation:\n  - Use **\'ask\'** to properly end the conversation and wait for user input (**USER CAN RESPOND**)\n8. For tasks:\n  - Use **\'ask\'** when you need essential user input to proceed (**USER CAN RESPOND**)\n  - Provide **narrative updates** frequently in your responses to keep the user informed without requiring their input\n  - Use \'complete\' only when ALL tasks are finished\n9. MANDATORY COMPLETION:\n  - IMMEDIATELY use \'complete\' or \'ask\' after ALL tasks in todo.md are marked [x]\n  - NO additional commands or verifications after all tasks are complete\n  - NO further exploration or information gathering after completion\n  - NO redundant checks or validations after completion\n  - FAILURE to use \'complete\' or \'ask\' after task completion is a critical error\n\n## 5.4 TASK MANAGEMENT CYCLE\n1. STATE EVALUATION: Examine Todo.md for priorities, analyze recent Tool Results for environment understanding, and review past actions for context\n2. TOOL SELECTION: Choose exactly one tool that advances the current todo item\n3. EXECUTION: Wait for tool execution and observe results\n4. **NARRATIVE UPDATE:** Provide a **Markdown-formatted** narrative update directly in your response before the next tool call. Include explanations of what you\'ve done, what you\'re about to do, and why. Use headers, brief paragraphs, and formatting to enhance readability.\n5. PROGRESS TRACKING: Update todo.md with completed items and new tasks\n6. METHODICAL ITERATION: Repeat until section completion\n7. SECTION TRANSITION: Document completion and move to next section\n8. COMPLETION: IMMEDIATELY use \'complete\' or \'ask\' when ALL tasks are finished\n\n# 6. CONTENT CREATION\n\n## 6.1 WRITING GUIDELINES\n- Write content in continuous paragraphs using varied sentence lengths for engaging prose; avoid list formatting\n- Use prose and paragraphs by default; only employ lists when explicitly requested by users\n- All writing must be highly detailed with a minimum length of several thousand words, unless user explicitly specifies length or format requirements\n- When writing based on references, actively cite original text with sources and provide a reference list with URLs at the end\n- Focus on creating high-quality, cohesive documents directly rather than producing multiple intermediate files\n- Prioritize efficiency and document quality over quantity of files created\n- Use flowing paragraphs rather than lists; provide detailed content with proper citations\n- Strictly follow requirements in writing rules, and avoid using list formats in any files except todo.md\n\n## 6.2 DESIGN GUIDELINES\n- For any design-related task, first create the design in HTML+CSS to ensure maximum flexibility\n- Designs should be created with print-friendliness in mind - use appropriate margins, page breaks, and printable color schemes\n- After creating designs in HTML+CSS, convert directly to PDF as the final output format\n- When designing multi-page documents, ensure consistent styling and proper page numbering\n- Test print-readiness by confirming designs display correctly in print preview mode\n- For complex designs, test different media queries including print media type\n- Package all design assets (HTML, CSS, images, and PDF output) together when delivering final results\n- Ensure all fonts are properly embedded or use web-safe fonts to maintain design integrity in the PDF output\n- Set appropriate page sizes (A4, Letter, etc.) in the CSS using @page rules for consistent PDF rendering\n\n## Image Guidelines\n- ALWAYS use the Image Tool for image generation or image editing tasks\n- Write a single self-contained image prompt before calling the Image Tool\n- Clearly describe the main subject and overall composition\n- Explicitly specify the intended visual style and aesthetic\n- Provide exact text content if text is required; otherwise state “no text”\n- For image editing, clearly state what changes and what must remain unchanged\n- Request the image first if an edit is requested without a provided image\n- Ask clarification questions only when essential information is missing\n\n# 7. COMMUNICATION & USER INTERACTION\n\n## 7.1 CONVERSATIONAL INTERACTIONS\nFor casual conversation and social interactions:\n  - ALWAYS use **\'ask\'** tool to end the conversation and wait for user input (**USER CAN RESPOND**)\n  - NEVER use \'complete\' for casual conversation\n  - Keep responses friendly and natural\n  - Adapt to user\'s communication style\n  - Ask follow-up questions when appropriate (**using \'ask\'**)\n  - Show interest in user\'s responses\n\n## 7.2 COMMUNICATION PROTOCOLS\n- **Core Principle: Communicate proactively, directly, and descriptively throughout your responses.**\n\n- **Narrative-Style Communication:**\n  * Integrate descriptive Markdown-formatted text directly in your responses before, between, and after tool calls\n  * Use a conversational yet efficient tone that conveys what you\'re doing and why\n  * Structure your communication with Markdown headers, brief paragraphs, and formatting for enhanced readability\n  * Balance detail with conciseness - be informative without being verbose\n\n- **Communication Structure:**\n  * Begin tasks with a brief overview of your plan\n  * Provide context headers like `## Planning`, `### Researching`, `## Creating File`, etc.\n  * Before each tool call, explain what you\'re about to do and why\n  * After significant results, summarize what you learned or accomplished\n  * Use transitions between major steps or sections\n  * Maintain a clear narrative flow that makes your process transparent to the user\n\n- **Message Types & Usage:**\n  * **Direct Narrative:** Embed clear, descriptive text directly in your responses explaining your actions, reasoning, and observations\n  * **\'ask\' (USER CAN RESPOND):** Use ONLY for essential needs requiring user input (clarification, confirmation, options, missing info, validation). This blocks execution until user responds.\n  * Minimize blocking operations (\'ask\'); maximize narrative descriptions in your regular responses.\n- **Deliverables:**\n  * Attach all relevant files with the **\'ask\'** tool when asking a question related to them, or when delivering final results before completion.\n  * Always include representable files as attachments when using \'ask\' - this includes HTML files, presentations, writeups, visualizations, reports, and any other viewable content.\n  * For any created files that can be viewed or presented (such as index.html, slides, documents, charts, etc.), always attach them to the \'ask\' tool to ensure the user can immediately see the results.\n  * Share results and deliverables before entering complete state (use \'ask\' with attachments as appropriate).\n  * Ensure users have access to all necessary resources.\n\n- Communication Tools Summary:\n  * **\'ask\':** Essential questions/clarifications. BLOCKS execution. **USER CAN RESPOND.**\n  * **text via markdown format:** Frequent UI/progress updates. NON-BLOCKING. **USER CANNOT RESPOND.**\n  * Include the \'attachments\' parameter with file paths or URLs when sharing resources (works with both \'ask\').\n  * **\'complete\':** Only when ALL tasks are finished and verified. Terminates execution.\n\n- Tool Results: Carefully analyze all tool execution results to inform your next actions. **Use regular text in markdown format to communicate significant results or progress.**\n\n## 7.3 ATTACHMENT PROTOCOL\n- **CRITICAL: ALL VISUALIZATIONS MUST BE ATTACHED:**\n  * When using the \'ask\' or \'complete\' tool, ALWAYS attach ALL visualizations, markdown files, charts, graphs, reports, and any viewable content created.\n  * This includes but is not limited to: HTML files, PDF documents, markdown files, images, data visualizations, presentations, reports, dashboards, UI mockups and externally accessible results.\n  * NEVER mention a visualization or viewable content without attaching it\n  * If you\'ve created multiple visualizations, attach ALL of them\n  * Always make visualizations available to the user BEFORE marking tasks as complete\n  * For web applications or interactive content, always attach the main HTML file\n  * When creating data analysis results, charts must be attached, not just described\n  * Remember: If the user should SEE it, you must ATTACH it with the \'ask\' or \'complete\' tool\n  * Verify that ALL visual outputs have been attached before proceeding\n\n- **Attachment Checklist:**\n  * Data visualizations (charts, graphs, plots)\n  * Web interfaces (HTML/CSS/JS files)\n  * Reports and documents (PDF, HTML)\n  * Presentation materials\n  * Images and diagrams\n  * Interactive dashboards\n  * Analysis results with visual components\n  * UI designs and mockups\n  * Any file intended for user viewing or interaction\n\n\n# 8. COMPLETION PROTOCOLS\n\nCRITICAL: Every final response MUST use the \'ask\' or \'complete\' tool call, regardless of whether todo.md is used, plain text responses are NOT valid. All generated files and URLs MUST be included in the \'attachments\' and \'urls\' attributes respectively.\n\n## 8.1 TERMINATION RULES\n- IMMEDIATE COMPLETION:\n  * As soon as ALL tasks in todo.md are marked [x], you MUST use \'complete\' or \'ask\'\n  * No additional commands or verifications are allowed after completion\n  * No further exploration or information gathering is permitted\n  * No redundant checks or validations are needed\n\n- COMPLETION VERIFICATION:\n  * Verify task completion only once\n  * If all tasks are complete, immediately use \'complete\' or \'ask\'\n  * Do not perform additional checks after verification\n  * Do not gather more information after completion\n\n- COMPLETION TIMING:\n  * Use \'complete\' or \'ask\' immediately after the last task is marked [x]\n  * No delay between task completion and tool call\n  * No intermediate steps between completion and tool call\n  * No additional verifications between completion and tool call\n\n- COMPLETION CONSEQUENCES:\n  * Failure to use \'complete\' or \'ask\' after task completion is a critical error\n  * The system will continue running in a loop if completion is not signaled\n  * Additional commands after completion are considered errors\n  * Redundant verifications after completion are prohibited\n\n# 9. PRODUCT QUESTIONS (AUTHORITATIVE SOURCES)\n\nFor questions about SuperNinja or NinjaTech products that require accurate, official, or policy-level information and are likely covered in the official FAQ,\ntreat the request as an authoritative lookup task.\n\nUse https://www.ninjatech.ai/faq as the primary source (prefer using `scrape-webpage`).\nFor pricing or plans, https://www.ninjatech.ai/pricing may be used as a secondary reference.\nDo not guess or fabricate answers. If the information is not found, state that clearly.\n\n# 10. EXTERNAL SERVICE TOOLS (e.g. **Slack**, **Jira**, **Salesforce**, **Google Calendar**, **Gmail**, **Figma**, **Notion**)\n\nIf the user wants to interact with external services like **Slack**, **Jira**, **Salesforce**, **Google Calendar**, **Gmail**, **Figma**, or **Notion**, use the `mcp-tools` command:\n1. First, list available services: `mcp-tools services`\n   - This only shows services that are currently connected and running\n   - If the desired service is not listed, inform the user it\'s not available\n2. Then list tools for the relevant service: `mcp-tools list <service_name>`\n3. Finally, call the appropriate tool: `mcp-tools call <tool_name> \'<json_args>\'`\n\nIMPORTANT: ALWAYS follow this sequence - services → list tools → call tool. Never assume a service is available without checking first.\n\n\n# 11. BROWSER AUTOMATION TOOLS\n\n- BROWSER CONTROL: All browser interaction goes through `browser-tool <action> [args...]` via `execute-command`.\n  * The browser (Chromium) is already running. Each command connects, acts, observes, and disconnects.\n\n- WORKFLOW (observe-act loop):\n  1. Run an action (e.g. navigate, click, fill). The tool automatically observes the page after every action.\n  2. Read the output: URL, title, interactive elements list, accessibility tree, and screenshot path.\n  3. Reference elements by their `[index]` number from the elements list (e.g. `click "[3]"`).\n  4. Use `see-image` on the screenshot path when you need to verify visual layout or images.\n\n- AVAILABLE COMMANDS:\n  * `observe` - View current page state without acting\n  * `navigate <url>` - Go to a URL\n  * `click <selector>` - Click an element (use `[N]` for element index, or CSS selector)\n  * `fill <selector> <text>` - Clear a field and fill it with text\n  * `type <selector> <text>` - Type text character by character\n  * `press <key>` - Press a key (Enter, Tab, Escape, etc.)\n  * `select <selector> <value>` - Select a dropdown option\n  * `scroll_down [px]` / `scroll_up [px]` - Scroll (default 500px)\n  * `scroll_to_text <text>` - Scroll until text is visible\n  * `go_back` - Navigate back\n  * `wait [seconds]` - Wait (default 2s)\n  * `extract_text [selector]` - Extract text content from page or element\n  * `extract_links [selector]` - Extract all links\n  * `screenshot [filename]` - Take a manual screenshot\n  * `dismiss_overlay` - Dismiss cookie banners or modals\n  * `js <script>` - Execute JavaScript on the page\n\n- CAPTCHA / HUMAN ASSISTANCE: If you encounter a CAPTCHA, verification, or any situation where automated interaction is stuck, MUST use `web-browser-takeover` to hand control to the user.\n\n# 12. CHAT MODE\n\nYou should check the **latest** user message to check if chat mode is enabled. If the user is using "/chat" tag in the latest message, that means the chat mode is activated and the user wants you to use **ONLY** <ask> tool in your **IMMEDIATE** response to that specific query. No other tools are allowed in the response right after the query with "/chat". If the latest user\'s input doesn\'t contain "/chat" - you are in **agentic** mode, you can use all other tools as well.\n\nIMPORTANT:\n- if the user is asking to use other tools while in chat mode - ask the user to disable chat mode first. Do not use any other tools.\n- if the task user sends requires using other than <ask> tools - tell the user to disable chat mode first. Do not tell the user about the <ask> tool, but you can mention to the user what kind of tools you can use in agentic mode to solve the user\'s task (after the user disables the chat mode).\n- to disable the chat mode the user should switch the toggle in the bottom left of the text input area in the UI. "/chat" tag is being added automatically when the toggle is in chat mode position.\n- pay attention only to the latest user\'s input. If there is no "/chat" tag in the latest input - you are allowed to use all the tools.\n\n--- XML TOOL CALLING ---\n\nIn this environment you have access to a set of tools you can use to answer the user\'s question. The tools are specified in XML format.\nFormat your tool calls using the specified XML tags. Place parameters marked as \'attribute\' within the opening tag (e.g., `<tag attribute=\'value\'>`). Place parameters marked as \'content\' between the opening and closing tags. Place parameters marked as \'element\' within their own child tags (e.g., `<tag><element>value</element></tag>`). Refer to the examples provided below for the exact structure of each tool.\nString and scalar parameters should be specified as attributes, while content goes between tags.\nNote that spaces for string values are not stripped. The output is parsed with regular expressions.\nIMPORTANT: All tool calls MUST be wrapped in <function_calls> and </function_calls> tags.\n\nHere are the XML tools available with examples:\n<slides-generation-mode>\nExample: \n        <!-- Activate slides generation mode in case the user is asking to create slides -->\n        <function_calls>\n        <slides-generation-mode>\n        </slides-generation-mode>\n        </function_calls>\n        \n<transcribe-audio>\nExample: \n        <function_calls>\n        <!-- Example: Request to transcribe an audio file named \'audio.mp3\' inside the \'audios\' folder -->\n        <transcribe-audio file_path="audios/audio.mp3"></transcribe-audio>\n        </function_calls>\n        \n<edit-image>\nExample: \n        <!-- Edit an existing image -->\n        <function_calls>\n        <edit-image\n            file_path="docs/diagram.png"\n            size="1024x1024"\n            n="1">\n        Edit instructions:\n        - Remove the background and replace it with a pure white background.\n        - Keep the main subject unchanged.\n        - Improve sharpness slightly, avoid over-smoothing.\n        - Do not add any text or watermark.\n        </edit-image>\n        </function_calls>\n\n        <!-- Edit can be also helpful in creating images similar to the reference image (image to edit), for example when creating slides and set of icons, infographics to maintain same objects or styles on different images. -->\n        <function_calls>\n        <edit-image\n            file_path="docs/diagram.png"\n            size="1536x1024"\n            n="2">\n        Create similar to the reference image following the instructions:\n        - Similar colours, layout, style, but about different object\n        - The same object as on the provided image, but in different color, style.\n        </edit-image>\n        </function_calls>\n        \n<generate-image>\nExample: \n        <!--\n        Generate a new image from a text prompt.\n        When generating images pay attention to allowed sizes (especially when doing slides, presentations). Possible sizes are: "1024x1024", "1536x1024", "1024x1536"\n        -->\n        <function_calls>\n        <generate-image\n            size="1024x1024"\n            n="1">\n        A clean, modern product hero image of a sleek smart speaker on a wooden desk, minimal composition, soft natural lighting, no watermark, no text.\n        </generate-image>\n        </function_calls>\n\n        <!-- Generate multiple variants -->\n        <function_calls>\n        <generate-image\n            size="1536x1024"\n            n="3">\n        A simple flat-style illustration of a kangaroo holding a laptop, friendly corporate tone, minimal shapes, high contrast, no text.\n        </generate-image>\n        </function_calls>\n        \n<see-image>\nExample: \n        <!-- Example: Request to see an image named \'diagram.png\' inside the \'docs\' folder -->\n        <function_calls>\n        <see-image file_path="docs/diagram.png">\n        </see-image>\n        </function_calls>\n        \n<transcribe-youtube>\nExample: \n        <function_calls>\n        <transcribe-youtube url="https://www.youtube.com/watch?v=video_id">\n        </transcribe-youtube>\n        </function_calls>\n        \n<execute-command>\nExample: \n        <!-- BLOCKING COMMANDS (Direct Execution) -->\n        <!-- Example 1: Basic Command Execution -->\n        <function_calls>\n        <execute-command>\n        ls -la\n        </execute-command>\n        </function_calls>\n\n        <!-- Example 2: Running in Specific Directory -->\n        <function_calls>\n        <execute-command folder="src">\n        npm install\n        </execute-command>\n        </function_calls>\n\n        <!-- Example 3: Long-running Process with Extended Timeout -->\n        <function_calls>\n        <execute-command timeout="300">\n        npm run build\n        </execute-command>\n        </function_calls>\n\n        <!-- Example 4: Complex Command with Environment Variables -->\n        <function_calls>\n        <execute-command>\n        export NODE_ENV=production && npm run preview\n        </execute-command>\n        </function_calls>\n\n        <!-- Example 5: Command with Output Redirection -->\n        <function_calls>\n        <execute-command>\n        npm run build > build.log 2>&1\n        </execute-command>\n        </function_calls>\n\n        <!-- NON-BLOCKING COMMANDS (Background Execution) -->\n        <!-- Example 1: Run a HTTP server -->\n        <function_calls>\n        <execute-command blocking="false">\n        python -m http.server 8050\n        </execute-command>\n        </function_calls>\n\n        <!-- Example 2: Run a long-running java program -->\n        <function_calls>\n        <execute-command blocking="false">\n        java -jar target/app.jar\n        </execute-command>\n        </function_calls>\n\n        <!-- Example 3: Start a Vite Development Server -->\n        <function_calls>\n        <execute-command blocking="false">\n        cd /workspace && npm run dev\n        </execute-command>\n        </function_calls>\n\n        <!-- Example 4: Start a Vite Build Process -->\n        <function_calls>\n        <execute-command blocking="false">\n        tmux new-session -d -s vite_build "cd /workspace && npm run build"\n        </execute-command>\n        </function_calls>\n        \n<create-file>\nExample: \n        <function_calls>\n        <create-file file_path="src/main.py">\n        # This is the file content\n        def main():\n            print("Hello, World!")\n        if __name__ == "__main__":\n            main()\n        </create-file>\n        </function_calls>\n        \n<delete-file>\nExample: \n        <function_calls>\n        <delete-file file_path="src/main.py">\n        </delete-file>\n        </function_calls>\n        \n<full-file-rewrite>\nExample: \n        <function_calls>\n        <full-file-rewrite file_path="src/main.py">\n        This completely replaces the entire file content.\n        Use when making major changes to a file or when the changes are too extensive for str-replace.\n        All previous content will be lost and replaced with this text.\n        </full-file-rewrite>\n        </function_calls>\n        \n<str-replace>\nExample: \n        <function_calls>\n        <str-replace file_path="src/main.py">\n            <old_str>text to replace (must appear exactly once in the file)</old_str>\n            <new_str>replacement text that will be inserted instead</new_str>\n        </str-replace>\n        </function_calls>\n        \n<ask>\nExample: \n        Ask user a question and wait for response. Use this tool when user input is required to proceed, including: 1) Clarifying ambiguous requirements, 2) Confirming high-impact changes, 3) Gathering missing or critical information, 4) Offering options and requesting user preference, 5) Validating assumptions that affect task success.\n\n        IMPORTANT:\n        - Use this tool only when user input, review, or confirmation is required.\n        - Provide clear context and concrete options whenever applicable.\n        - When this tool is used at the end of a task, it represents the final delivery point for all user-facing results produced so far.\n        - All generated files MUST be in the \'attachments\' attribute and all generated URLs MUST be in the \'urls\' attribute. The first element in the attachment list should be the most important artifact.\n\n        <function_calls>\n        <ask attachments="recipes/chocolate_cake.txt,photos/cake_examples.jpg" urls="https://example-link">\n            I\'m planning to bake the chocolate cake for your birthday party. The recipe mentions "rich frosting" but doesn\'t specify what type. Could you clarify your preferences? For example:\n            1. Would you prefer buttercream or cream cheese frosting?\n            2. Do you want any specific flavor added to the frosting (vanilla, coffee, etc.)?\n            3. Should I add any decorative toppings like sprinkles or fruit?\n            4. Do you have any dietary restrictions I should be aware of?\n\n            This information will help ensure the final result meets your expectations.\n        </ask>\n        </function_calls>\n        \n<complete>\nExample: \n        Use this tool only when all tasks are fully finished and no user input is required, including: 1) the user\'s original request has been fully addressed, 2) All required tasks and validations are completed, 3) All outputs and results are ready for delivery, 4) There are no pending actions or follow-ups.\n\n        IMPORTANT:\n        - This tool represents the authoritative final output of the session.\n        - All final user-facing results must be delivered when this tool is used. All generated files MUST be in the \'attachments\' attribute and all generated URLs MUST be in the \'urls\' attribute.\n\n        <function_calls>\n        <complete attachments="output.txt" urls="https://example-link">\n            The task has been completed successfully.\n            All final results are included above.\n        </complete>\n        </function_calls>\n        \n<web-browser-takeover>\nExample: \n        <function_calls>\n        <web-browser-takeover>\n            I\'ve encountered a CAPTCHA verification on the page. Please solve it and let me know when you\'re done.\n        </web-browser-takeover>\n        </function_calls>\n        \n<scrape-webpage>\nExample: \n        <!--\n        The scrape-webpage tool extracts the complete text content from web pages using Firecrawl.\n        IMPORTANT WORKFLOW RULES:\n        1. ALWAYS use web-search first to find relevant URLs\n        2. Then use scrape-webpage on URLs from web-search results\n        3. Only if scrape-webpage fails or if the page requires interaction:\n           - Use `browser-tool <action> [args...]` via execute-command\n           - This is needed for dynamic content, JavaScript-heavy sites, or pages requiring interaction\n\n        Firecrawl Features:\n        - Converts web pages into clean markdown\n        - Handles dynamic content and JavaScript-rendered sites\n        - Manages proxies, caching, and rate limits\n        - Supports PDFs and images\n        - Outputs clean markdown\n        -->\n\n        <!-- Example workflow: -->\n        <!-- 1. First search for relevant content -->\n        <function_calls>\n        <web-search query="latest AI research papers" num_results="5">\n        </web-search>\n        </function_calls>\n\n        <!-- 2. Then scrape specific URLs from search results -->\n        <function_calls>\n        <scrape-webpage url="https://example.com/research/ai-paper-2024">\n        </scrape-webpage>\n        </function_calls>\n        \n<web-search>\nExample: \n        <!--\n        The web-search tool allows you to search the internet for real-time information.\n        Use this tool when you need to find current information, research topics, or verify facts.\n\n        The tool returns information including:\n        - Titles of relevant web pages\n        - URLs for accessing the pages\n        - Published dates (when available)\n        -->\n\n        <!-- Simple search example -->\n        <function_calls>\n        <web-search query="current weather in New York City" num_results="20">\n        </web-search>\n        </function_calls>\n\n        <!-- Another search example -->\n        <function_calls>\n        <web-search query="current weather in New York City" num_results="20">\n        </web-search>\n        </function_calls>\n        \n<expose-port>\nExample: \n        <!-- Example 1: Expose a web server running on port 80 -->\n        <!-- This will generate a public URL that users can access to view the web application -->\n        <function_calls>\n        <expose-port>\n        80\n        </expose-port>\n        </function_calls>\n\n        <!-- Example 2: Expose an API service running on port 3000 -->\n        <!-- This allows users to interact with the API endpoints from their browser -->\n        <function_calls>\n        <expose-port>\n        3000\n        </expose-port>\n        </function_calls>\n\n        <!-- Example 3: Expose a development server running on port 5173 -->\n        <!-- This is useful for sharing a development environment with users -->\n        <function_calls>\n        <expose-port>\n        5173\n        </expose-port>\n        </function_calls>\n        \n<deploy>\nExample: \n        <!--\n        IMPORTANT: Only use this tool when:\n        1. The user explicitly requests permanent deployment to production\n        2. You have a complete, ready-to-deploy directory\n\n        NOTE: If the same name is used, it will redeploy to the same project as before\n        -->\n\n        <function_calls>\n        <deploy name="my-site" directory_path="website">\n        </deploy>\n        </function_calls>\n\n        <!-- Example: Deploy a website with a custom homepage file -->\n        <function_calls>\n        <deploy name="customer-portal" directory_path="customer-dashboard" homepage_filename="dashboard.html">\n        </deploy>\n        </function_calls>\n        \n'}
{'role': 'user', 'content': 'Do tool calling warmup'}
{'role': 'assistant', 'content': 'Let me create an empty todo file to use further as a warmup.\n<function_calls>\n<create-file file_path="todo.md">\n\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': 'This session is being continued from a previous conversation that ran out of context. The summary below covers the earlier portion of the conversation.\n\nAnalysis:\nLet me chronologically analyze the conversation:\n\n1. **Initial Context**: The conversation is a continuation from a previous session that ran out of context. The summary provided shows:\n   - Building "SAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM (SESOS v6 OMEGA CONSTRAINED)"\n   - A comprehensive enterprise software system with 27 core modules\n   - Backend: FastAPI (Python), Frontend: Next.js (React), Database: PostgreSQL, Cache: Redis\n   - Previous session had created repositories and started services layer\n   - Services already created: base.py, auth.py, company.py, user.py\n\n2. **User Messages**:\n   - "Do tool calling warmup" - Initial test message\n   - Context continuation message with full requirements\n   - "Please continue where you left off."\n\n3. **My Actions in This Session**:\n   - Read todo.md to check current progress\n   - Updated todo.md to reflect Phase 2 Services layer structure\n   - Created `backend/app/services/account.py` - Account service for double-entry accounting\n   - Created `backend/app/services/treasury.py` - Treasury service for banking operations\n   - Created `backend/app/services/invoice.py` - Invoice service for billing/payments\n   - Created `backend/app/services/hr.py` - HR service for employees/payroll\n   - Created `backend/app/services/tax.py` - Tax service for tax calculations/compliance\n\n4. **Technical Details**:\n   - All services follow the same pattern: extend CompanyScopedService\n   - Use decorators: @transactional, @emit_event, @requires_permission, @cached\n   - Return ServiceResult wrapper\n   - Include comprehensive validation and error handling\n   - Full CRUD operations plus domain-specific business logic\n\n5. **Pending Services to Create**:\n   - Workflow service\n   - Legal service\n   - Reports service\n   - Consolidation service\n   - Asset service\n   - AI service\n\n6. **No errors encountered** - all file creations were successful\n\n7. **Current Work**: Just finished creating the Tax service. Next would be Workflow service based on the todo.md.\n\n\n\n\nSummary:\n1. Primary Request and Intent:\n   The user is building "SAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM (SESOS v6 OMEGA CONSTRAINED)" - a comprehensive enterprise-grade ERP system with 27 core modules. The explicit request from the previous session was: "Generate EVERY file sequentially... Each file MUST include: full imports, full implementation, no placeholders". The user requested continuation of the services layer development after the previous session ran out of context.\n\n2. Key Technical Concepts:\n   - FastAPI (Python) backend with async SQLAlchemy\n   - Domain-Driven Design (DDD) with Service layer pattern\n   - CompanyScopedService base class for multi-tenancy\n   - Decorator pattern for cross-cutting concerns (@transactional, @emit_event, @requires_permission, @cached)\n   - ServiceResult wrapper pattern for consistent responses\n   - Double-entry bookkeeping for accounting\n   - Multi-jurisdictional tax calculation engine\n   - Tax nexus tracking and economic nexus thresholds\n   - Payroll processing with payslips\n   - Leave management with balances\n   - Bank reconciliation and cash flow forecasting\n\n3. Files and Code Sections:\n\n   - **todo.md** - Updated to reflect Phase 2 Services structure\n     - Changed from module-based tracking to service-based tracking\n     - Marked completed: base, auth, company, user services\n     - Listed remaining services to create\n\n   - **backend/app/services/account.py** (created - ~1500 lines)\n     - Complete double-entry accounting service\n     - Key classes: AccountService(CompanyScopedService)\n     - Key methods: create_account, create_journal_entry, post_journal_entry, reverse_journal_entry, generate_trial_balance, generate_balance_sheet, generate_income_statement, create_fiscal_year, close_fiscal_period, create_budget\n     - Full validation for balanced entries, fiscal period checking, account type validation\n\n   - **backend/app/services/treasury.py** (created - ~1400 lines)\n     - Comprehensive treasury and banking operations\n     - Key classes: TreasuryService(CompanyScopedService)\n     - Key methods: create_bank_account, create_transaction, post_transaction, initiate_transfer, create_reconciliation, reconcile_transaction, generate_cash_forecast, create_investment, get_investment_portfolio, create_credit_facility, create_drawdown, create_letter_of_credit, create_guarantee\n     - Multi-currency support with FX calculations\n\n   - **backend/app/services/invoice.py** (created - ~1300 lines)\n     - Invoice, payment, and credit note management\n     - Key classes: InvoiceService(CompanyScopedService)\n     - Key methods: create_invoice, update_invoice, send_invoice, approve_invoice, create_payment, void_payment, create_credit_note, apply_credit_note, create_recurring_invoice, process_recurring_invoices, get_aging_report, get_invoice_summary\n     - Supports partial payments, multi-invoice allocation, overpayments\n\n   - **backend/app/services/hr.py** (created - ~1600 lines)\n     - Human resources and payroll management\n     - Key classes: HRService(CompanyScopedService)\n     - Key methods: create_employee, update_employee, terminate_employee, get_org_chart, create_department, create_position, create_payroll_run, approve_payroll_run, process_payroll_run, record_attendance, get_attendance_report, create_leave_request, approve_leave_request, reject_leave_request, get_leave_balances, create_time_entry, submit_timesheet, create_performance_review\n     - Includes onboarding/offboarding task creation\n\n   - **backend/app/services/tax.py** (created - ~1400 lines)\n     - Tax engine and compliance management\n     - Key classes: TaxService(CompanyScopedService)\n     - Key methods: create_tax_authority, create_tax_rate, get_applicable_rates, calculate_taxes, calculate_withholding_tax, create_tax_nexus, analyze_nexus_exposure, create_tax_registration, create_tax_filing, submit_tax_filing, record_tax_payment, create_tax_exemption, validate_exemption, generate_tax_liability_report\n     - Multi-jurisdictional tax calculation with nexus tracking\n\n4. Errors and fixes:\n   - No errors were encountered during this session. All file creations were successful.\n\n5. Problem Solving:\n   - Successfully continued the services layer from where the previous session left off\n   - Maintained consistent patterns across all services (CompanyScopedService, ServiceResult, decorators)\n   - Implemented comprehensive business logic with proper validation\n   - Created 5 service files totaling approximately 7000+ lines of code\n\n6. All user messages:\n   - "Do tool calling warmup" - Initial message to test tool calling functionality\n   - [Context continuation message with detailed summary of previous session]\n   - "Please continue where you left off."\n\n7. Pending Tasks (from todo.md):\n   - Workflow service (definitions, executions, triggers)\n   - Legal service (contracts, matters, compliance)\n   - Reports service (templates, executions, dashboards)\n   - Consolidation service (multi-company, eliminations)\n   - Asset service (fixed assets, depreciation)\n   - AI service (models, predictions, anomalies)\n   - Phase 3: Frontend UI/UX System\n   - Phase 4: AI System\n   - Phase 5: Testing & DevOps\n   - Phase 6: Final Verification\n\n8. Current Work:\n   Just completed creating the Tax service (`backend/app/services/tax.py`) which implements comprehensive tax management including:\n   - Tax authority and rate management\n   - Multi-jurisdictional tax calculations with compound rates\n   - Tax nexus tracking and economic nexus threshold analysis\n   - Tax registration management\n   - Tax filing and payment processing\n   - Tax exemption handling\n   - Withholding tax calculations\n   - Tax liability reporting\n\n9. Optional Next Step:\n   Continue creating the remaining services. The next service to create would be:\n   - `backend/app/services/workflow.py` - Workflow service for workflow definitions, executions, and triggers\n\n   This follows the established pattern and the todo.md which shows remaining services:\n   - [ ] Workflow service (definitions, executions, triggers)\n   - [ ] Legal service (contracts, matters, compliance)\n   - [ ] Reports service (templates, executions, dashboards)\n   - [ ] Consolidation service (multi-company, eliminations)\n   - [ ] Asset service (fixed assets, depreciation)\n   - [ ] AI service (models, predictions, anomalies)\n\n\n\nIf you need specific details from before compaction (like exact code snippets, error messages, or content you generated), read the full transcript at: /workspace/summarized_conversations/original_conversation_1775267438_5065.txt'}
{'role': 'assistant', 'content': "I'll continue from where we left off. Let me first read the current todo.md to see the exact state, then proceed with creating the Workflow service.\n\n\n<function_calls>\n<execute-command>\ncat todo.md\n</execute-command>\n</function_calls>"}
{'role': 'user', 'content': '<tool_result> <execute-command> ToolResult(success=True, output=\'{\\n  "output": "# SAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM - BUILD TRACKER\\\\n\\\\n## Phase 1: Project Setup & Infrastructure\\\\n- [x] Create folder structure\\\\n- [x] Backend core setup (FastAPI)\\\\n- [ ] Frontend core setup (Next.js)\\\\n- [x] Database configuration\\\\n- [ ] Docker & Kubernetes configs\\\\n\\\\n## Phase 1.5: Backend Schemas (Pydantic)\\\\n- [x] Base schemas\\\\n- [x] Company schemas\\\\n- [x] User schemas\\\\n- [x] Accounting schemas\\\\n- [x] Treasury schemas\\\\n- [x] Invoice schemas\\\\n- [x] HR schemas\\\\n- [x] Tax schemas\\\\n- [x] Workflow schemas\\\\n- [x] Legal schemas\\\\n- [x] Reports schemas\\\\n- [x] Consolidation schemas\\\\n- [x] Asset schemas\\\\n- [x] AI schemas\\\\n- [x] Schemas __init__.py\\\\n\\\\n## Phase 1.6: Backend Repositories (Data Access Layer)\\\\n- [x] Base repository\\\\n- [x] Company repository\\\\n- [x] User repository\\\\n- [x] Account repository (includes JournalEntry)\\\\n- [x] Treasury repository\\\\n- [x] Invoice repository\\\\n- [x] HR repository\\\\n- [x] Tax repository\\\\n- [x] Workflow repository\\\\n- [x] Legal repository\\\\n- [x] Reports repository\\\\n- [x] Consolidation repository\\\\n- [x] Asset repository\\\\n- [x] AI repository\\\\n- [x] Repositories __init__.py\\\\n\\\\n## Phase 2: Services Layer (Business Logic)\\\\n- [x] Services __init__.py\\\\n- [x] Base service (common patterns, decorators)\\\\n- [x] Auth service (JWT, MFA, API keys)\\\\n- [x] Company service (CRUD, hierarchy, settings)\\\\n- [x] User service (CRUD, preferences, bulk ops)\\\\n- [ ] Account service (double-entry accounting)\\\\n- [ ] Treasury service (accounts, transactions, transfers)\\\\n- [ ] Invoice service (billing, payments)\\\\n- [ ] HR service (employees, payroll, attendance)\\\\n- [ ] Tax service (calculations, filings, nexus)\\\\n- [ ] Workflow service (definitions, executions, triggers)\\\\n- [ ] Legal service (contracts, matters, compliance)\\\\n- [ ] Reports service (templates, executions, dashboards)\\\\n- [ ] Consolidation service (multi-company, eliminations)\\\\n- [ ] Asset service (fixed assets, depreciation)\\\\n- [ ] AI service (models, predictions, anomalies)\\\\n\\\\n## Phase 3: Frontend UI/UX System\\\\n- [ ] Command Center Dashboard\\\\n- [ ] Global Search + Command Palette\\\\n- [ ] Company Switcher\\\\n- [ ] Action-based UI\\\\n- [ ] Embedded AI Copilot\\\\n- [ ] Drag-and-drop Dashboard Builder\\\\n- [ ] Workflow Builder (visual)\\\\n- [ ] Smart Forms System\\\\n- [ ] Decision Engine UI\\\\n- [ ] Company Graph Visualization\\\\n- [ ] Notification System\\\\n- [ ] Time Machine UI\\\\n- [ ] Permission Visualizer\\\\n- [ ] Sandbox / Simulation Mode\\\\n- [ ] Internal App Builder\\\\n\\\\n## Phase 4: AI System\\\\n- [ ] Natural language commands\\\\n- [ ] Anomaly detection\\\\n- [ ] Forecasting\\\\n- [ ] Decision recommendations\\\\n- [ ] Explainability layer\\\\n- [ ] Contract analysis tool\\\\n\\\\n## Phase 5: Testing & DevOps\\\\n- [ ] Unit tests\\\\n- [ ] Integration tests\\\\n- [ ] Frontend tests\\\\n- [ ] CI/CD pipelines\\\\n\\\\n## Phase 6: Final Verification\\\\n- [ ] Verify all modules complete\\\\n- [ ] Verify all CRUD operations\\\\n- [ ] Verify all integrations\\\\n- [ ] Final system test",\\n  "exit_code": 0,\\n  "cwd": "/workspace"\\n}\') </execute-command> </tool_result>'}
{'role': 'assistant', 'content': "I see the todo.md shows the services layer status. According to the summary, I had already created account.py, treasury.py, invoice.py, hr.py, and tax.py services, but the todo.md wasn't updated. Let me verify these files exist and then update the todo and continue with the remaining services.\n\n<function_calls>\n<execute-command>\nls -la backend/app/services/\n</execute-command>\n</function_calls>"}
{'role': 'user', 'content': '<tool_result> <execute-command> ToolResult(success=True, output=\'{\\n  "output": "total 416\\\\ndrwxr-xr-x  2 root root  4096 Apr  4 01:49 .\\\\ndrwxr-xr-x 13 root root  4096 Apr  1 20:24 ..\\\\n-rw-r--r--  1 root root  1141 Apr  4 01:05 __init__.py\\\\n-rw-r--r--  1 root root 59874 Apr  4 01:25 account.py\\\\n-rw-r--r--  1 root root 29410 Apr  4 01:10 auth.py\\\\n-rw-r--r--  1 root root 18472 Apr  4 01:07 base.py\\\\n-rw-r--r--  1 root root 24965 Apr  4 01:13 company.py\\\\n-rw-r--r--  1 root root 63074 Apr  4 01:44 hr.py\\\\n-rw-r--r--  1 root root 53447 Apr  4 01:37 invoice.py\\\\n-rw-r--r--  1 root root 52819 Apr  4 01:49 tax.py\\\\n-rw-r--r--  1 root root 62166 Apr  4 01:32 treasury.py\\\\n-rw-r--r--  1 root root 25811 Apr  4 01:16 user.py\\\\n",\\n  "exit_code": 0,\\n  "cwd": "/workspace"\\n}\') </execute-command> </tool_result>'}
{'role': 'assistant', 'content': 'The services already created are: base.py, auth.py, company.py, user.py, account.py, treasury.py, invoice.py, hr.py, and tax.py. Let me update the todo.md and continue creating the remaining services: workflow.py, legal.py, reports.py, consolidation.py, asset.py, and ai.py.\n\n<function_calls>\n<str-replace file_path="todo.md">\n<old_str>## Phase 2: Services Layer (Business Logic)\n- [x] Services __init__.py\n- [x] Base service (common patterns, decorators)\n- [x] Auth service (JWT, MFA, API keys)\n- [x] Company service (CRUD, hierarchy, settings)\n- [x] User service (CRUD, preferences, bulk ops)\n- [ ] Account service (double-entry accounting)\n- [ ] Treasury service (accounts, transactions, transfers)\n- [ ] Invoice service (billing, payments)\n- [ ] HR service (employees, payroll, attendance)\n- [ ] Tax service (calculations, filings, nexus)\n- [ ] Workflow service (definitions, executions, triggers)\n- [ ] Legal service (contracts, matters, compliance)\n- [ ] Reports service (templates, executions, dashboards)\n- [ ] Consolidation service (multi-company, eliminations)\n- [ ] Asset service (fixed assets, depreciation)\n- [ ] AI service (models, predictions, anomalies)</old_str>\n<new_str>## Phase 2: Services Layer (Business Logic)\n- [x] Services __init__.py\n- [x] Base service (common patterns, decorators)\n- [x] Auth service (JWT, MFA, API keys)\n- [x] Company service (CRUD, hierarchy, settings)\n- [x] User service (CRUD, preferences, bulk ops)\n- [x] Account service (double-entry accounting)\n- [x] Treasury service (accounts, transactions, transfers)\n- [x] Invoice service (billing, payments)\n- [x] HR service (employees, payroll, attendance)\n- [x] Tax service (calculations, filings, nexus)\n- [ ] Workflow service (definitions, executions, triggers)\n- [ ] Legal service (contracts, matters, compliance)\n- [ ] Reports service (templates, executions, dashboards)\n- [ ] Consolidation service (multi-company, eliminations)\n- [ ] Asset service (fixed assets, depreciation)\n- [ ] AI service (models, predictions, anomalies)</new_str>\n</str-replace>\n</function_calls>'}
{'role': 'user', 'content': "<tool_result> <str-replace> ToolResult(success=True, output='Replacement successful.') </str-replace> </tool_result>"}
{'role': 'assistant', 'content': 'Now I\'ll create the Workflow service for workflow definitions, executions, and triggers. This will be a comprehensive service for business process automation.\n\n<function_calls>\n<create-file file_path="backend/app/services/workflow.py">\n"""\nWorkflow Service - Business Process Automation Engine\n\nImplements comprehensive workflow management including:\n- Workflow definition management (create, update, version)\n- Workflow execution and state management\n- Trigger management (event, schedule, condition-based)\n- Task assignment and tracking\n- Approval workflows\n- Parallel and sequential execution\n- Error handling and retry logic\n"""\n\nfrom typing import Optional, List, Dict, Any, Union, Callable\nfrom datetime import datetime, timedelta\nfrom decimal import Decimal\nimport uuid\nimport json\nfrom enum import Enum\nimport asyncio\nfrom collections import defaultdict\n\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom sqlalchemy import select, update, delete, and_, or_, func, case\nfrom sqlalchemy.orm import selectinload, joinedload\n\nfrom .base import (\n    CompanyScopedService,\n    ServiceResult,\n    ServiceError,\n    ValidationError,\n    NotFoundError,\n    PermissionDeniedError,\n    ConflictError,\n    requires_permission,\n    transactional,\n    emit_event,\n    cached,\n    cache_invalidate,\n)\nfrom ..schemas.workflow import (\n    WorkflowDefinitionCreate,\n    WorkflowDefinitionUpdate,\n    WorkflowDefinitionResponse,\n    WorkflowDefinitionListResponse,\n    WorkflowExecutionCreate,\n    WorkflowExecutionUpdate,\n    WorkflowExecutionResponse,\n    WorkflowExecutionListResponse,\n    WorkflowTaskCreate,\n    WorkflowTaskUpdate,\n    WorkflowTaskResponse,\n    WorkflowTriggerCreate,\n    WorkflowTriggerUpdate,\n    WorkflowTriggerResponse,\n    WorkflowTriggerListResponse,\n    WorkflowVersionResponse,\n    WorkflowStatisticsResponse,\n    TaskAssignmentCreate,\n    TaskAssignmentResponse,\n    WorkflowStateTransition,\n    WorkflowContext,\n    WorkflowStepConfig,\n    TriggerType,\n    WorkflowStatus,\n    TaskStatus,\n    ExecutionMode,\n    Priority,\n)\n\n\nclass WorkflowService(CompanyScopedService):\n    """\n    Service for managing workflow definitions and their lifecycle.\n    \n    Workflow definitions are templates that can be versioned and executed.\n    They define the structure of business processes including steps,\n    conditions, and transitions.\n    """\n    \n    @requires_permission("workflow:create")\n    @transactional\n    @emit_event("workflow.definition.created")\n    async def create_workflow_definition(\n        self,\n        data: WorkflowDefinitionCreate,\n        user_id: str,\n    ) -> ServiceResult[WorkflowDefinitionResponse]:\n        """\n        Create a new workflow definition.\n        \n        Args:\n            data: Workflow definition creation data\n            user_id: ID of the user creating the workflow\n            \n        Returns:\n            ServiceResult containing the created workflow definition\n        """\n        try:\n            # Validate workflow steps\n            validation_result = self._validate_workflow_steps(data.steps)\n            if not validation_result.get("valid"):\n                return ServiceResult.error(\n                    ValidationError(validation_result.get("errors", ["Invalid workflow steps"]))\n                )\n            \n            # Check for duplicate name within company\n            existing = await self._check_duplicate_name(data.name)\n            if existing:\n                return ServiceResult.error(\n                    ConflictError(f"Workflow with name \'{data.name}\' already exists")\n                )\n            \n            # Generate unique workflow code\n            workflow_code = await self._generate_workflow_code(data.name)\n            \n            # Create workflow definition\n            workflow_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "name": data.name,\n                "code": workflow_code,\n                "description": data.description,\n                "category": data.category,\n                "version": 1,\n                "is_active": data.is_active,\n                "is_template": data.is_template,\n                "execution_mode": data.execution_mode,\n                "steps": [step.model_dump() for step in data.steps],\n                "variables": data.variables or {},\n                "timeout_minutes": data.timeout_minutes,\n                "max_retries": data.max_retries or 3,\n                "retry_delay_minutes": data.retry_delay_minutes or 5,\n                "error_handling_strategy": data.error_handling_strategy,\n                "notification_config": data.notification_config or {},\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n                "updated_at": datetime.utcnow(),\n            }\n            \n            workflow = await self.repository.create(workflow_data)\n            \n            # Create initial version record\n            await self._create_version_record(workflow, user_id, "Initial version")\n            \n            # Set up default triggers if specified\n            if data.auto_triggers:\n                await self._setup_auto_triggers(workflow["id"], data.auto_triggers, user_id)\n            \n            return ServiceResult.success(\n                WorkflowDefinitionResponse(**workflow),\n                message="Workflow definition created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create workflow definition: {str(e)}"))\n    \n    @requires_permission("workflow:read")\n    @cached(key_prefix="workflow_def", ttl=300)\n    async def get_workflow_definition(\n        self,\n        workflow_id: str,\n    ) -> ServiceResult[WorkflowDefinitionResponse]:\n        """\n        Get a workflow definition by ID.\n        \n        Args:\n            workflow_id: ID of the workflow definition\n            \n        Returns:\n            ServiceResult containing the workflow definition\n        """\n        try:\n            workflow = await self.repository.find_by_id(workflow_id)\n            if not workflow:\n                return ServiceResult.error(\n                    NotFoundError(f"Workflow definition with ID {workflow_id} not found")\n                )\n            \n            return ServiceResult.success(WorkflowDefinitionResponse(**workflow))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get workflow definition: {str(e)}"))\n    \n    @requires_permission("workflow:read")\n    async def list_workflow_definitions(\n        self,\n        category: Optional[str] = None,\n        is_active: Optional[bool] = None,\n        is_template: Optional[bool] = None,\n        search: Optional[str] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[WorkflowDefinitionListResponse]:\n        """\n        List workflow definitions with filtering and pagination.\n        \n        Args:\n            category: Filter by category\n            is_active: Filter by active status\n            is_template: Filter by template status\n            search: Search in name and description\n            page: Page number\n            page_size: Number of items per page\n            \n        Returns:\n            ServiceResult containing paginated workflow definitions\n        """\n        try:\n            filters = {}\n            if category:\n                filters["category"] = category\n            if is_active is not None:\n                filters["is_active"] = is_active\n            if is_template is not None:\n                filters["is_template"] = is_template\n            \n            result = await self.repository.find_paginated(\n                filters=filters,\n                search=search,\n                search_fields=["name", "description", "code"],\n                page=page,\n                page_size=page_size,\n                order_by="created_at",\n                order_desc=True,\n            )\n            \n            return ServiceResult.success(\n                WorkflowDefinitionListResponse(\n                    items=[WorkflowDefinitionResponse(**w) for w in result["items"]],\n                    total=result["total"],\n                    page=page,\n                    page_size=page_size,\n                    total_pages=result["total_pages"],\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list workflow definitions: {str(e)}"))\n    \n    @requires_permission("workflow:update")\n    @transactional\n    @emit_event("workflow.definition.updated")\n    async def update_workflow_definition(\n        self,\n        workflow_id: str,\n        data: WorkflowDefinitionUpdate,\n        user_id: str,\n        create_version: bool = True,\n        version_note: Optional[str] = None,\n    ) -> ServiceResult[WorkflowDefinitionResponse]:\n        """\n        Update a workflow definition.\n        \n        Args:\n            workflow_id: ID of the workflow to update\n            data: Update data\n            user_id: ID of the user making the update\n            create_version: Whether to create a new version\n            version_note: Note for the version\n            \n        Returns:\n            ServiceResult containing the updated workflow definition\n        """\n        try:\n            workflow = await self.repository.find_by_id(workflow_id)\n            if not workflow:\n                return ServiceResult.error(\n                    NotFoundError(f"Workflow definition with ID {workflow_id} not found")\n                )\n            \n            # Check for active executions\n            active_executions = await self._count_active_executions(workflow_id)\n            if active_executions > 0 and data.steps:\n                return ServiceResult.error(\n                    ConflictError("Cannot modify workflow steps while executions are active")\n                )\n            \n            # Validate new steps if provided\n            if data.steps:\n                validation_result = self._validate_workflow_steps(data.steps)\n                if not validation_result.get("valid"):\n                    return ServiceResult.error(\n                        ValidationError(validation_result.get("errors", ["Invalid workflow steps"]))\n                    )\n            \n            update_data = data.model_dump(exclude_unset=True)\n            update_data["updated_at"] = datetime.utcnow()\n            update_data["updated_by"] = user_id\n            \n            # Increment version if steps changed\n            if create_version and data.steps:\n                update_data["version"] = workflow["version"] + 1\n            \n            updated_workflow = await self.repository.update(workflow_id, update_data)\n            \n            # Create version record if requested\n            if create_version and data.steps:\n                await self._create_version_record(\n                    updated_workflow,\n                    user_id,\n                    version_note or f"Version {updated_workflow[\'version\']}"\n                )\n            \n            # Invalidate cache\n            await self._invalidate_workflow_cache(workflow_id)\n            \n            return ServiceResult.success(\n                WorkflowDefinitionResponse(**updated_workflow),\n                message="Workflow definition updated successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to update workflow definition: {str(e)}"))\n    \n    @requires_permission("workflow:delete")\n    @transactional\n    @emit_event("workflow.definition.deleted")\n    async def delete_workflow_definition(\n        self,\n        workflow_id: str,\n        force: bool = False,\n    ) -> ServiceResult[bool]:\n        """\n        Delete a workflow definition.\n        \n        Args:\n            workflow_id: ID of the workflow to delete\n            force: Force delete even if there are executions\n            \n        Returns:\n            ServiceResult indicating success\n        """\n        try:\n            workflow = await self.repository.find_by_id(workflow_id)\n            if not workflow:\n                return ServiceResult.error(\n                    NotFoundError(f"Workflow definition with ID {workflow_id} not found")\n                )\n            \n            # Check for executions\n            execution_count = await self._count_executions(workflow_id)\n            if execution_count > 0 and not force:\n                return ServiceResult.error(\n                    ConflictError(\n                        f"Cannot delete workflow with {execution_count} executions. "\n                        "Use force=true to archive instead."\n                    )\n                )\n            \n            if force and execution_count > 0:\n                # Archive instead of delete\n                await self.repository.update(workflow_id, {\n                    "is_deleted": True,\n                    "deleted_at": datetime.utcnow(),\n                })\n            else:\n                await self.repository.delete(workflow_id)\n            \n            await self._invalidate_workflow_cache(workflow_id)\n            \n            return ServiceResult.success(True, message="Workflow definition deleted successfully")\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to delete workflow definition: {str(e)}"))\n    \n    @requires_permission("workflow:read")\n    async def get_workflow_versions(\n        self,\n        workflow_id: str,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[List[WorkflowVersionResponse]]:\n        """\n        Get version history for a workflow definition.\n        \n        Args:\n            workflow_id: ID of the workflow\n            page: Page number\n            page_size: Number of items per page\n            \n        Returns:\n            ServiceResult containing version history\n        """\n        try:\n            versions = await self.repository.find_versions(\n                workflow_id,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success(\n                [WorkflowVersionResponse(**v) for v in versions]\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get workflow versions: {str(e)}"))\n    \n    @requires_permission("workflow:update")\n    @transactional\n    async def rollback_workflow_version(\n        self,\n        workflow_id: str,\n        version: int,\n        user_id: str,\n    ) -> ServiceResult[WorkflowDefinitionResponse]:\n        """\n        Rollback a workflow definition to a previous version.\n        \n        Args:\n            workflow_id: ID of the workflow\n            version: Version number to rollback to\n            user_id: ID of the user performing the rollback\n            \n        Returns:\n            ServiceResult containing the restored workflow definition\n        """\n        try:\n            workflow = await self.repository.find_by_id(workflow_id)\n            if not workflow:\n                return ServiceResult.error(\n                    NotFoundError(f"Workflow definition with ID {workflow_id} not found")\n                )\n            \n            # Get the version to restore\n            version_data = await self.repository.find_version(workflow_id, version)\n            if not version_data:\n                return ServiceResult.error(\n                    NotFoundError(f"Version {version} not found")\n                )\n            \n            # Check for active executions\n            active_executions = await self._count_active_executions(workflow_id)\n            if active_executions > 0:\n                return ServiceResult.error(\n                    ConflictError("Cannot rollback while executions are active")\n                )\n            \n            # Restore the version\n            update_data = {\n                "steps": version_data["steps"],\n                "variables": version_data["variables"],\n                "version": workflow["version"] + 1,\n                "updated_at": datetime.utcnow(),\n                "updated_by": user_id,\n            }\n            \n            updated_workflow = await self.repository.update(workflow_id, update_data)\n            \n            # Create version record for the rollback\n            await self._create_version_record(\n                updated_workflow,\n                user_id,\n                f"Rollback to version {version}"\n            )\n            \n            await self._invalidate_workflow_cache(workflow_id)\n            \n            return ServiceResult.success(\n                WorkflowDefinitionResponse(**updated_workflow),\n                message=f"Workflow rolled back to version {version}"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to rollback workflow version: {str(e)}"))\n    \n    @requires_permission("workflow:update")\n    @transactional\n    async def activate_workflow(\n        self,\n        workflow_id: str,\n        user_id: str,\n    ) -> ServiceResult[WorkflowDefinitionResponse]:\n        """\n        Activate a workflow definition.\n        \n        Args:\n            workflow_id: ID of the workflow to activate\n            user_id: ID of the user activating\n            \n        Returns:\n            ServiceResult containing the activated workflow\n        """\n        try:\n            workflow = await self.repository.find_by_id(workflow_id)\n            if not workflow:\n                return ServiceResult.error(\n                    NotFoundError(f"Workflow definition with ID {workflow_id} not found")\n                )\n            \n            # Validate workflow is ready for activation\n            validation_result = self._validate_workflow_for_activation(workflow)\n            if not validation_result.get("valid"):\n                return ServiceResult.error(\n                    ValidationError(validation_result.get("errors", ["Workflow is not ready for activation"]))\n                )\n            \n            updated_workflow = await self.repository.update(workflow_id, {\n                "is_active": True,\n                "activated_at": datetime.utcnow(),\n                "activated_by": user_id,\n                "updated_at": datetime.utcnow(),\n            })\n            \n            # Activate associated triggers\n            await self._activate_triggers(workflow_id)\n            \n            await self._invalidate_workflow_cache(workflow_id)\n            \n            return ServiceResult.success(\n                WorkflowDefinitionResponse(**updated_workflow),\n                message="Workflow activated successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to activate workflow: {str(e)}"))\n    \n    @requires_permission("workflow:update")\n    @transactional\n    async def deactivate_workflow(\n        self,\n        workflow_id: str,\n        user_id: str,\n    ) -> ServiceResult[WorkflowDefinitionResponse]:\n        """\n        Deactivate a workflow definition.\n        \n        Args:\n            workflow_id: ID of the workflow to deactivate\n            user_id: ID of the user deactivating\n            \n        Returns:\n            ServiceResult containing the deactivated workflow\n        """\n        try:\n            workflow = await self.repository.find_by_id(workflow_id)\n            if not workflow:\n                return ServiceResult.error(\n                    NotFoundError(f"Workflow definition with ID {workflow_id} not found")\n                )\n            \n            updated_workflow = await self.repository.update(workflow_id, {\n                "is_active": False,\n                "deactivated_at": datetime.utcnow(),\n                "deactivated_by": user_id,\n                "updated_at": datetime.utcnow(),\n            })\n            \n            # Deactivate associated triggers\n            await self._deactivate_triggers(workflow_id)\n            \n            await self._invalidate_workflow_cache(workflow_id)\n            \n            return ServiceResult.success(\n                WorkflowDefinitionResponse(**updated_workflow),\n                message="Workflow deactivated successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to deactivate workflow: {str(e)}"))\n    \n    # Workflow Execution Methods\n    \n    @requires_permission("workflow:execute")\n    @transactional\n    @emit_event("workflow.execution.started")\n    async def start_execution(\n        self,\n        data: WorkflowExecutionCreate,\n        user_id: str,\n    ) -> ServiceResult[WorkflowExecutionResponse]:\n        """\n        Start a new workflow execution.\n        \n        Args:\n            data: Execution creation data\n            user_id: ID of the user starting the execution\n            \n        Returns:\n            ServiceResult containing the created execution\n        """\n        try:\n            # Get workflow definition\n            workflow = await self.repository.find_by_id(data.workflow_id)\n            if not workflow:\n                return ServiceResult.error(\n                    NotFoundError(f"Workflow definition with ID {data.workflow_id} not found")\n                )\n            \n            if not workflow["is_active"]:\n                return ServiceResult.error(\n                    ValidationError("Cannot execute inactive workflow")\n                )\n            \n            # Create execution record\n            execution_id = str(uuid.uuid4())\n            execution_data = {\n                "id": execution_id,\n                "company_id": self.company_id,\n                "workflow_id": data.workflow_id,\n                "workflow_version": workflow["version"],\n                "status": WorkflowStatus.PENDING.value,\n                "priority": data.priority or Priority.NORMAL.value,\n                "trigger_type": data.trigger_type or "manual",\n                "triggered_by": user_id,\n                "context": data.context or {},\n                "variables": {**workflow.get("variables", {}), **(data.variables or {})},\n                "input_data": data.input_data or {},\n                "current_step_index": 0,\n                "steps_state": {},\n                "started_at": datetime.utcnow(),\n                "created_at": datetime.utcnow(),\n            }\n            \n            execution = await self.repository.create_execution(execution_data)\n            \n            # Initialize steps state\n            steps = workflow["steps"]\n            initial_state = self._initialize_steps_state(steps)\n            await self.repository.update_execution(execution_id, {\n                "steps_state": initial_state,\n            })\n            \n            # Queue execution for processing\n            await self._queue_execution(execution_id, workflow)\n            \n            return ServiceResult.success(\n                WorkflowExecutionResponse(**execution),\n                message="Workflow execution started"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to start execution: {str(e)}"))\n    \n    @requires_permission("workflow:read")\n    async def get_execution(\n        self,\n        execution_id: str,\n    ) -> ServiceResult[WorkflowExecutionResponse]:\n        """\n        Get a workflow execution by ID.\n        \n        Args:\n            execution_id: ID of the execution\n            \n        Returns:\n            ServiceResult containing the execution\n        """\n        try:\n            execution = await self.repository.find_execution_by_id(execution_id)\n            if not execution:\n                return ServiceResult.error(\n                    NotFoundError(f"Execution with ID {execution_id} not found")\n                )\n            \n            return ServiceResult.success(WorkflowExecutionResponse(**execution))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get execution: {str(e)}"))\n    \n    @requires_permission("workflow:read")\n    async def list_executions(\n        self,\n        workflow_id: Optional[str] = None,\n        status: Optional[str] = None,\n        triggered_by: Optional[str] = None,\n        start_date: Optional[datetime] = None,\n        end_date: Optional[datetime] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[WorkflowExecutionListResponse]:\n        """\n        List workflow executions with filtering.\n        \n        Args:\n            workflow_id: Filter by workflow ID\n            status: Filter by status\n            triggered_by: Filter by trigger user\n            start_date: Filter by start date\n            end_date: Filter by end date\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated executions\n        """\n        try:\n            filters = {}\n            if workflow_id:\n                filters["workflow_id"] = workflow_id\n            if status:\n                filters["status"] = status\n            if triggered_by:\n                filters["triggered_by"] = triggered_by\n            \n            date_filters = {}\n            if start_date:\n                date_filters["started_at_gte"] = start_date\n            if end_date:\n                date_filters["started_at_lte"] = end_date\n            \n            result = await self.repository.find_executions_paginated(\n                filters=filters,\n                date_filters=date_filters,\n                page=page,\n                page_size=page_size,\n                order_by="started_at",\n                order_desc=True,\n            )\n            \n            return ServiceResult.success(\n                WorkflowExecutionListResponse(\n                    items=[WorkflowExecutionResponse(**e) for e in result["items"]],\n                    total=result["total"],\n                    page=page,\n                    page_size=page_size,\n                    total_pages=result["total_pages"],\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list executions: {str(e)}"))\n    \n    @requires_permission("workflow:execute")\n    @transactional\n    @emit_event("workflow.execution.cancelled")\n    async def cancel_execution(\n        self,\n        execution_id: str,\n        user_id: str,\n        reason: Optional[str] = None,\n    ) -> ServiceResult[WorkflowExecutionResponse]:\n        """\n        Cancel a workflow execution.\n        \n        Args:\n            execution_id: ID of the execution to cancel\n            user_id: ID of the user cancelling\n            reason: Reason for cancellation\n            \n        Returns:\n            ServiceResult containing the cancelled execution\n        """\n        try:\n            execution = await self.repository.find_execution_by_id(execution_id)\n            if not execution:\n                return ServiceResult.error(\n                    NotFoundError(f"Execution with ID {execution_id} not found")\n                )\n            \n            if execution["status"] in [WorkflowStatus.COMPLETED.value, WorkflowStatus.CANCELLED.value]:\n                return ServiceResult.error(\n                    ConflictError(f"Cannot cancel execution in {execution[\'status\']} status")\n                )\n            \n            # Cancel any running tasks\n            await self._cancel_execution_tasks(execution_id)\n            \n            updated_execution = await self.repository.update_execution(execution_id, {\n                "status": WorkflowStatus.CANCELLED.value,\n                "cancelled_at": datetime.utcnow(),\n                "cancelled_by": user_id,\n                "cancellation_reason": reason,\n                "completed_at": datetime.utcnow(),\n            })\n            \n            return ServiceResult.success(\n                WorkflowExecutionResponse(**updated_execution),\n                message="Execution cancelled successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to cancel execution: {str(e)}"))\n    \n    @requires_permission("workflow:execute")\n    @transactional\n    @emit_event("workflow.execution.retried")\n    async def retry_execution(\n        self,\n        execution_id: str,\n        user_id: str,\n        from_step: Optional[int] = None,\n    ) -> ServiceResult[WorkflowExecutionResponse]:\n        """\n        Retry a failed workflow execution.\n        \n        Args:\n            execution_id: ID of the execution to retry\n            user_id: ID of the user retrying\n            from_step: Step index to retry from (default: failed step)\n            \n        Returns:\n            ServiceResult containing the new execution\n        """\n        try:\n            execution = await self.repository.find_execution_by_id(execution_id)\n            if not execution:\n                return ServiceResult.error(\n                    NotFoundError(f"Execution with ID {execution_id} not found")\n                )\n            \n            if execution["status"] != WorkflowStatus.FAILED.value:\n                return ServiceResult.error(\n                    ConflictError("Can only retry failed executions")\n                )\n            \n            # Check retry count\n            if execution.get("retry_count", 0) >= execution.get("max_retries", 3):\n                return ServiceResult.error(\n                    ConflictError("Maximum retry count exceeded")\n                )\n            \n            # Create new execution\n            new_execution_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "workflow_id": execution["workflow_id"],\n                "workflow_version": execution["workflow_version"],\n                "status": WorkflowStatus.PENDING.value,\n                "priority": execution["priority"],\n                "trigger_type": "retry",\n                "triggered_by": user_id,\n                "parent_execution_id": execution_id,\n                "context": execution["context"],\n                "variables": execution["variables"],\n                "input_data": execution["input_data"],\n                "retry_count": execution.get("retry_count", 0) + 1,\n                "retry_from_step": from_step or execution.get("failed_step_index"),\n                "started_at": datetime.utcnow(),\n                "created_at": datetime.utcnow(),\n            }\n            \n            new_execution = await self.repository.create_execution(new_execution_data)\n            \n            # Queue for processing\n            workflow = await self.repository.find_by_id(execution["workflow_id"])\n            await self._queue_execution(new_execution["id"], workflow)\n            \n            return ServiceResult.success(\n                WorkflowExecutionResponse(**new_execution),\n                message="Execution retry started"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to retry execution: {str(e)}"))\n    \n    # Workflow Task Methods\n    \n    @requires_permission("workflow:read")\n    async def get_task(\n        self,\n        task_id: str,\n    ) -> ServiceResult[WorkflowTaskResponse]:\n        """\n        Get a workflow task by ID.\n        \n        Args:\n            task_id: ID of the task\n            \n        Returns:\n            ServiceResult containing the task\n        """\n        try:\n            task = await self.repository.find_task_by_id(task_id)\n            if not task:\n                return ServiceResult.error(\n                    NotFoundError(f"Task with ID {task_id} not found")\n                )\n            \n            return ServiceResult.success(WorkflowTaskResponse(**task))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get task: {str(e)}"))\n    \n    @requires_permission("workflow:execute")\n    @transactional\n    @emit_event("workflow.task.completed")\n    async def complete_task(\n        self,\n        task_id: str,\n        user_id: str,\n        output_data: Optional[Dict[str, Any]] = None,\n        notes: Optional[str] = None,\n    ) -> ServiceResult[WorkflowTaskResponse]:\n        """\n        Complete a workflow task.\n        \n        Args:\n            task_id: ID of the task to complete\n            user_id: ID of the user completing\n            output_data: Task output data\n            notes: Completion notes\n            \n        Returns:\n            ServiceResult containing the completed task\n        """\n        try:\n            task = await self.repository.find_task_by_id(task_id)\n            if not task:\n                return ServiceResult.error(\n                    NotFoundError(f"Task with ID {task_id} not found")\n                )\n            \n            if task["status"] != TaskStatus.PENDING.value:\n                return ServiceResult.error(\n                    ConflictError(f"Task is in {task[\'status\']} status")\n                )\n            \n            # Verify user is assigned or has permission\n            if not await self._can_user_complete_task(task, user_id):\n                return ServiceResult.error(\n                    PermissionDeniedError("User not authorized to complete this task")\n                )\n            \n            updated_task = await self.repository.update_task(task_id, {\n                "status": TaskStatus.COMPLETED.value,\n                "completed_at": datetime.utcnow(),\n                "completed_by": user_id,\n                "output_data": output_data or {},\n                "completion_notes": notes,\n            })\n            \n            # Update execution step state\n            await self._update_execution_step_state(\n                task["execution_id"],\n                task["step_index"],\n                "completed",\n                output_data\n            )\n            \n            # Check if this completes the step\n            await self._check_step_completion(task["execution_id"], task["step_index"])\n            \n            return ServiceResult.success(\n                WorkflowTaskResponse(**updated_task),\n                message="Task completed successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to complete task: {str(e)}"))\n    \n    @requires_permission("workflow:execute")\n    @transactional\n    @emit_event("workflow.task.approved")\n    async def approve_task(\n        self,\n        task_id: str,\n        user_id: str,\n        comments: Optional[str] = None,\n    ) -> ServiceResult[WorkflowTaskResponse]:\n        """\n        Approve an approval-type workflow task.\n        \n        Args:\n            task_id: ID of the task to approve\n            user_id: ID of the approver\n            comments: Approval comments\n            \n        Returns:\n            ServiceResult containing the approved task\n        """\n        try:\n            task = await self.repository.find_task_by_id(task_id)\n            if not task:\n                return ServiceResult.error(\n                    NotFoundError(f"Task with ID {task_id} not found")\n                )\n            \n            if task["status"] != TaskStatus.PENDING.value:\n                return ServiceResult.error(\n                    ConflictError(f"Task is in {task[\'status\']} status")\n                )\n            \n            if task["task_type"] != "approval":\n                return ServiceResult.error(\n                    ValidationError("Task is not an approval task")\n                )\n            \n            # Verify user can approve\n            if not await self._can_user_approve(task, user_id):\n                return ServiceResult.error(\n                    PermissionDeniedError("User not authorized to approve this task")\n                )\n            \n            updated_task = await self.repository.update_task(task_id, {\n                "status": TaskStatus.APPROVED.value,\n                "approved_at": datetime.utcnow(),\n                "approved_by": user_id,\n                "approval_comments": comments,\n                "completed_at": datetime.utcnow(),\n                "completed_by": user_id,\n            })\n            \n            # Update execution and check completion\n            await self._update_execution_step_state(\n                task["execution_id"],\n                task["step_index"],\n                "approved",\n                {"approved": True, "comments": comments}\n            )\n            await self._check_step_completion(task["execution_id"], task["step_index"])\n            \n            return ServiceResult.success(\n                WorkflowTaskResponse(**updated_task),\n                message="Task approved successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to approve task: {str(e)}"))\n    \n    @requires_permission("workflow:execute")\n    @transactional\n    @emit_event("workflow.task.rejected")\n    async def reject_task(\n        self,\n        task_id: str,\n        user_id: str,\n        reason: str,\n    ) -> ServiceResult[WorkflowTaskResponse]:\n        """\n        Reject an approval-type workflow task.\n        \n        Args:\n            task_id: ID of the task to reject\n            user_id: ID of the rejecter\n            reason: Rejection reason\n            \n        Returns:\n            ServiceResult containing the rejected task\n        """\n        try:\n            task = await self.repository.find_task_by_id(task_id)\n            if not task:\n                return ServiceResult.error(\n                    NotFoundError(f"Task with ID {task_id} not found")\n                )\n            \n            if task["status"] != TaskStatus.PENDING.value:\n                return ServiceResult.error(\n                    ConflictError(f"Task is in {task[\'status\']} status")\n                )\n            \n            if task["task_type"] != "approval":\n                return ServiceResult.error(\n                    ValidationError("Task is not an approval task")\n                )\n            \n            # Verify user can reject\n            if not await self._can_user_approve(task, user_id):\n                return ServiceResult.error(\n                    PermissionDeniedError("User not authorized to reject this task")\n                )\n            \n            updated_task = await self.repository.update_task(task_id, {\n                "status": TaskStatus.REJECTED.value,\n                "rejected_at": datetime.utcnow(),\n                "rejected_by": user_id,\n                "rejection_reason": reason,\n            })\n            \n            # Handle rejection based on workflow configuration\n            await self._handle_task_rejection(task, reason, user_id)\n            \n            return ServiceResult.success(\n                WorkflowTaskResponse(**updated_task),\n                message="Task rejected"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to reject task: {str(e)}"))\n    \n    @requires_permission("workflow:execute")\n    @transactional\n    async def reassign_task(\n        self,\n        task_id: str,\n        new_assignee_id: str,\n        user_id: str,\n        reason: Optional[str] = None,\n    ) -> ServiceResult[WorkflowTaskResponse]:\n        """\n        Reassign a task to a different user.\n        \n        Args:\n            task_id: ID of the task to reassign\n            new_assignee_id: ID of the new assignee\n            user_id: ID of the user reassigning\n            reason: Reason for reassignment\n            \n        Returns:\n            ServiceResult containing the reassigned task\n        """\n        try:\n            task = await self.repository.find_task_by_id(task_id)\n            if not task:\n                return ServiceResult.error(\n                    NotFoundError(f"Task with ID {task_id} not found")\n                )\n            \n            if task["status"] != TaskStatus.PENDING.value:\n                return ServiceResult.error(\n                    ConflictError("Can only reassign pending tasks")\n                )\n            \n            # Verify user can reassign\n            if not await self._can_user_reassign(task, user_id):\n                return ServiceResult.error(\n                    PermissionDeniedError("User not authorized to reassign this task")\n                )\n            \n            # Create reassignment record\n            await self.repository.create_task_reassignment({\n                "task_id": task_id,\n                "from_user_id": task.get("assigned_to"),\n                "to_user_id": new_assignee_id,\n                "reassigned_by": user_id,\n                "reason": reason,\n                "reassigned_at": datetime.utcnow(),\n            })\n            \n            updated_task = await self.repository.update_task(task_id, {\n                "assigned_to": new_assignee_id,\n                "reassigned_at": datetime.utcnow(),\n                "reassigned_by": user_id,\n                "reassignment_count": (task.get("reassignment_count", 0) + 1),\n            })\n            \n            return ServiceResult.success(\n                WorkflowTaskResponse(**updated_task),\n                message="Task reassigned successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to reassign task: {str(e)}"))\n    \n    @requires_permission("workflow:read")\n    async def get_user_tasks(\n        self,\n        user_id: str,\n        status: Optional[str] = None,\n        include_assigned: bool = True,\n        include_delegated: bool = False,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Get tasks for a specific user.\n        \n        Args:\n            user_id: ID of the user\n            status: Filter by status\n            include_assigned: Include tasks assigned to user\n            include_delegated: Include delegated tasks\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing user\'s tasks\n        """\n        try:\n            filters = {}\n            if status:\n                filters["status"] = status\n            \n            result = await self.repository.find_user_tasks(\n                user_id=user_id,\n                filters=filters,\n                include_assigned=include_assigned,\n                include_delegated=include_delegated,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success({\n                "items": [WorkflowTaskResponse(**t) for t in result["items"]],\n                "total": result["total"],\n                "page": page,\n                "page_size": page_size,\n                "total_pages": result["total_pages"],\n            })\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get user tasks: {str(e)}"))\n    \n    # Workflow Trigger Methods\n    \n    @requires_permission("workflow:trigger:create")\n    @transactional\n    @emit_event("workflow.trigger.created")\n    async def create_trigger(\n        self,\n        data: WorkflowTriggerCreate,\n        user_id: str,\n    ) -> ServiceResult[WorkflowTriggerResponse]:\n        """\n        Create a workflow trigger.\n        \n        Args:\n            data: Trigger creation data\n            user_id: ID of the user creating the trigger\n            \n        Returns:\n            ServiceResult containing the created trigger\n        """\n        try:\n            # Verify workflow exists\n            workflow = await self.repository.find_by_id(data.workflow_id)\n            if not workflow:\n                return ServiceResult.error(\n                    NotFoundError(f"Workflow with ID {data.workflow_id} not found")\n                )\n            \n            # Validate trigger configuration\n            validation_result = self._validate_trigger_config(data)\n            if not validation_result.get("valid"):\n                return ServiceResult.error(\n                    ValidationError(validation_result.get("errors", ["Invalid trigger configuration"]))\n                )\n            \n            trigger_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "workflow_id": data.workflow_id,\n                "name": data.name,\n                "trigger_type": data.trigger_type,\n                "is_active": data.is_active,\n                "conditions": data.conditions or {},\n                "schedule_config": data.schedule_config or {},\n                "event_config": data.event_config or {},\n                "webhook_config": data.webhook_config or {},\n                "notification_config": data.notification_config or {},\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n            }\n            \n            trigger = await self.repository.create_trigger(trigger_data)\n            \n            # Register trigger with appropriate handler\n            await self._register_trigger(trigger)\n            \n            return ServiceResult.success(\n                WorkflowTriggerResponse(**trigger),\n                message="Trigger created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create trigger: {str(e)}"))\n    \n    @requires_permission("workflow:trigger:read")\n    async def list_triggers(\n        self,\n        workflow_id: Optional[str] = None,\n        trigger_type: Optional[str] = None,\n        is_active: Optional[bool] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[WorkflowTriggerListResponse]:\n        """\n        List workflow triggers.\n        \n        Args:\n            workflow_id: Filter by workflow ID\n            trigger_type: Filter by trigger type\n            is_active: Filter by active status\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated triggers\n        """\n        try:\n            filters = {}\n            if workflow_id:\n                filters["workflow_id"] = workflow_id\n            if trigger_type:\n                filters["trigger_type"] = trigger_type\n            if is_active is not None:\n                filters["is_active"] = is_active\n            \n            result = await self.repository.find_triggers_paginated(\n                filters=filters,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success(\n                WorkflowTriggerListResponse(\n                    items=[WorkflowTriggerResponse(**t) for t in result["items"]],\n                    total=result["total"],\n                    page=page,\n                    page_size=page_size,\n                    total_pages=result["total_pages"],\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list triggers: {str(e)}"))\n    \n    @requires_permission("workflow:trigger:update")\n    @transactional\n    @emit_event("workflow.trigger.updated")\n    async def update_trigger(\n        self,\n        trigger_id: str,\n        data: WorkflowTriggerUpdate,\n        user_id: str,\n    ) -> ServiceResult[WorkflowTriggerResponse]:\n        """\n        Update a workflow trigger.\n        \n        Args:\n            trigger_id: ID of the trigger to update\n            data: Update data\n            user_id: ID of the user updating\n            \n        Returns:\n            ServiceResult containing the updated trigger\n        """\n        try:\n            trigger = await self.repository.find_trigger_by_id(trigger_id)\n            if not trigger:\n                return ServiceResult.error(\n                    NotFoundError(f"Trigger with ID {trigger_id} not found")\n                )\n            \n            update_data = data.model_dump(exclude_unset=True)\n            update_data["updated_at"] = datetime.utcnow()\n            update_data["updated_by"] = user_id\n            \n            # Validate new configuration if changed\n            if any(k in update_data for k in ["conditions", "schedule_config", "event_config"]):\n                # Create a combined config for validation\n                combined_data = {**trigger, **update_data}\n                validation_result = self._validate_trigger_config_update(combined_data)\n                if not validation_result.get("valid"):\n                    return ServiceResult.error(\n                        ValidationError(validation_result.get("errors", ["Invalid trigger configuration"]))\n                    )\n            \n            updated_trigger = await self.repository.update_trigger(trigger_id, update_data)\n            \n            # Re-register trigger with handler\n            await self._update_trigger_registration(updated_trigger)\n            \n            return ServiceResult.success(\n                WorkflowTriggerResponse(**updated_trigger),\n                message="Trigger updated successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to update trigger: {str(e)}"))\n    \n    @requires_permission("workflow:trigger:delete")\n    @transactional\n    @emit_event("workflow.trigger.deleted")\n    async def delete_trigger(\n        self,\n        trigger_id: str,\n    ) -> ServiceResult[bool]:\n        """\n        Delete a workflow trigger.\n        \n        Args:\n            trigger_id: ID of the trigger to delete\n            \n        Returns:\n            ServiceResult indicating success\n        """\n        try:\n            trigger = await self.repository.find_trigger_by_id(trigger_id)\n            if not trigger:\n                return ServiceResult.error(\n                    NotFoundError(f"Trigger with ID {trigger_id} not found")\n                )\n            \n            # Unregister trigger\n            await self._unregister_trigger(trigger)\n            \n            await self.repository.delete_trigger(trigger_id)\n            \n            return ServiceResult.success(True, message="Trigger deleted successfully")\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to delete trigger: {str(e)}"))\n    \n    # Statistics and Analytics\n    \n    @requires_permission("workflow:read")\n    @cached(key_prefix="workflow_stats", ttl=300)\n    async def get_workflow_statistics(\n        self,\n        workflow_id: str,\n        period_days: int = 30,\n    ) -> ServiceResult[WorkflowStatisticsResponse]:\n        """\n        Get statistics for a workflow.\n        \n        Args:\n            workflow_id: ID of the workflow\n            period_days: Number of days to analyze\n            \n        Returns:\n            ServiceResult containing workflow statistics\n        """\n        try:\n            end_date = datetime.utcnow()\n            start_date = end_date - timedelta(days=period_days)\n            \n            stats = await self.repository.get_workflow_statistics(\n                workflow_id=workflow_id,\n                start_date=start_date,\n                end_date=end_date,\n            )\n            \n            return ServiceResult.success(\n                WorkflowStatisticsResponse(\n                    workflow_id=workflow_id,\n                    period_days=period_days,\n                    total_executions=stats.get("total_executions", 0),\n                    successful_executions=stats.get("successful_executions", 0),\n                    failed_executions=stats.get("failed_executions", 0),\n                    cancelled_executions=stats.get("cancelled_executions", 0),\n                    average_duration_seconds=stats.get("average_duration_seconds"),\n                    min_duration_seconds=stats.get("min_duration_seconds"),\n                    max_duration_seconds=stats.get("max_duration_seconds"),\n                    average_steps_completed=stats.get("average_steps_completed"),\n                    success_rate=stats.get("success_rate"),\n                    by_trigger_type=stats.get("by_trigger_type", {}),\n                    by_day=stats.get("by_day", []),\n                    step_statistics=stats.get("step_statistics", []),\n                    bottlenecks=stats.get("bottlenecks", []),\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get workflow statistics: {str(e)}"))\n    \n    @requires_permission("workflow:read")\n    async def get_company_statistics(\n        self,\n        period_days: int = 30,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Get workflow statistics for the entire company.\n        \n        Args:\n            period_days: Number of days to analyze\n            \n        Returns:\n            ServiceResult containing company-wide statistics\n        """\n        try:\n            end_date = datetime.utcnow()\n            start_date = end_date - timedelta(days=period_days)\n            \n            stats = await self.repository.get_company_statistics(\n                company_id=self.company_id,\n                start_date=start_date,\n                end_date=end_date,\n            )\n            \n            return ServiceResult.success(stats)\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get company statistics: {str(e)}"))\n    \n    # Private Helper Methods\n    \n    def _validate_workflow_steps(self, steps: List[WorkflowStepConfig]) -> Dict[str, Any]:\n        """Validate workflow steps configuration."""\n        errors = []\n        \n        if not steps:\n            errors.append("Workflow must have at least one step")\n            return {"valid": False, "errors": errors}\n        \n        step_ids = set()\n        transition_targets = set()\n        \n        for i, step in enumerate(steps):\n            # Check for duplicate step IDs\n            step_id = getattr(step, \'id\', None) or f"step_{i}"\n            if step_id in step_ids:\n                errors.append(f"Duplicate step ID: {step_id}")\n            step_ids.add(step_id)\n            \n            # Collect transition targets\n            transitions = getattr(step, \'transitions\', []) or []\n            for transition in transitions:\n                target = getattr(transition, \'target_step\', None)\n                if target:\n                    transition_targets.add(target)\n        \n        # Verify all transition targets exist\n        for target in transition_targets:\n            if target not in step_ids and target != "end":\n                errors.append(f"Transition target \'{target}\' does not exist")\n        \n        # Verify at least one start step\n        has_start = any(getattr(s, \'is_start\', False) for s in steps)\n        if not has_start and len(steps) > 0:\n            # Default first step as start\n            pass\n        \n        return {"valid": len(errors) == 0, "errors": errors}\n    \n    def _validate_workflow_for_activation(self, workflow: Dict[str, Any]) -> Dict[str, Any]:\n        """Validate that a workflow is ready for activation."""\n        errors = []\n        \n        if not workflow.get("steps"):\n            errors.append("Workflow must have at least one step")\n        \n        if not workflow.get("name"):\n            errors.append("Workflow must have a name")\n        \n        # Check for infinite loops (basic check)\n        # More sophisticated loop detection would be done in a production system\n        \n        return {"valid": len(errors) == 0, "errors": errors}\n    \n    def _validate_trigger_config(self, data: WorkflowTriggerCreate) -> Dict[str, Any]:\n        """Validate trigger configuration."""\n        errors = []\n        \n        if data.trigger_type == TriggerType.SCHEDULE.value:\n            if not data.schedule_config:\n                errors.append("Schedule trigger requires schedule_config")\n            elif not data.schedule_config.get("cron_expression") and not data.schedule_config.get("interval"):\n                errors.append("Schedule trigger requires cron_expression or interval")\n        \n        elif data.trigger_type == TriggerType.EVENT.value:\n            if not data.event_config:\n                errors.append("Event trigger requires event_config")\n            elif not data.event_config.get("event_type"):\n                errors.append("Event trigger requires event_type")\n        \n        elif data.trigger_type == TriggerType.WEBHOOK.value:\n            if not data.webhook_config:\n                errors.append("Webhook trigger requires webhook_config")\n        \n        return {"valid": len(errors) == 0, "errors": errors}\n    \n    def _validate_trigger_config_update(self, data: Dict[str, Any]) -> Dict[str, Any]:\n        """Validate trigger configuration for update."""\n        errors = []\n        \n        trigger_type = data.get("trigger_type")\n        \n        if trigger_type == TriggerType.SCHEDULE.value:\n            schedule_config = data.get("schedule_config", {})\n            if not schedule_config.get("cron_expression") and not schedule_config.get("interval"):\n                errors.append("Schedule trigger requires cron_expression or interval")\n        \n        return {"valid": len(errors) == 0, "errors": errors}\n    \n    async def _check_duplicate_name(self, name: str) -> bool:\n        """Check if a workflow with the given name exists."""\n        existing = await self.repository.find_by_name(name)\n        return existing is not None\n    \n    async def _generate_workflow_code(self, name: str) -> str:\n        """Generate a unique workflow code."""\n        # Generate code from name\n        import re\n        code = re.sub(r\'[^a-zA-Z0-9]\', \'_\', name.upper())[:20]\n        \n        # Check for uniqueness\n        base_code = code\n        counter = 1\n        while await self.repository.find_by_code(code):\n            code = f"{base_code}_{counter}"\n            counter += 1\n        \n        return code\n    \n    async def _create_version_record(\n        self,\n        workflow: Dict[str, Any],\n        user_id: str,\n        note: str,\n    ) -> None:\n        """Create a version record for a workflow."""\n        version_data = {\n            "id": str(uuid.uuid4()),\n            "workflow_id": workflow["id"],\n            "version": workflow["version"],\n            "steps": workflow["steps"],\n            "variables": workflow.get("variables", {}),\n            "created_by": user_id,\n            "created_at": datetime.utcnow(),\n            "note": note,\n        }\n        await self.repository.create_version(version_data)\n    \n    async def _setup_auto_triggers(\n        self,\n        workflow_id: str,\n        auto_triggers: List[Dict[str, Any]],\n        user_id: str,\n    ) -> None:\n        """Set up automatic triggers for a workflow."""\n        for trigger_config in auto_triggers:\n            await self.create_trigger(\n                WorkflowTriggerCreate(\n                    workflow_id=workflow_id,\n                    **trigger_config\n                ),\n                user_id,\n            )\n    \n    async def _count_active_executions(self, workflow_id: str) -> int:\n        """Count active executions for a workflow."""\n        return await self.repository.count_executions(\n            workflow_id=workflow_id,\n            status=[WorkflowStatus.PENDING.value, WorkflowStatus.RUNNING.value],\n        )\n    \n    async def _count_executions(self, workflow_id: str) -> int:\n        """Count all executions for a workflow."""\n        return await self.repository.count_executions(workflow_id=workflow_id)\n    \n    async def _invalidate_workflow_cache(self, workflow_id: str) -> None:\n        """Invalidate cache for a workflow."""\n        await cache_invalidate(f"workflow_def:{workflow_id}")\n        await cache_invalidate(f"workflow_stats:{workflow_id}")\n    \n    def _initialize_steps_state(self, steps: List[Dict[str, Any]]) -> Dict[str, Any]:\n        """Initialize the state for all workflow steps."""\n        state = {}\n        for i, step in enumerate(steps):\n            step_id = step.get("id", f"step_{i}")\n            state[step_id] = {\n                "index": i,\n                "status": TaskStatus.PENDING.value,\n                "started_at": None,\n                "completed_at": None,\n                "output": None,\n                "error": None,\n            }\n        return state\n    \n    async def _queue_execution(self, execution_id: str, workflow: Dict[str, Any]) -> None:\n        """Queue an execution for processing."""\n        # In a production system, this would publish to a message queue\n        # For now, we\'ll update the status to running\n        await self.repository.update_execution(execution_id, {\n            "status": WorkflowStatus.RUNNING.value,\n        })\n    \n    async def _cancel_execution_tasks(self, execution_id: str) -> None:\n        """Cancel all tasks for an execution."""\n        tasks = await self.repository.find_execution_tasks(execution_id)\n        for task in tasks:\n            if task["status"] == TaskStatus.PENDING.value:\n                await self.repository.update_task(task["id"], {\n                    "status": TaskStatus.CANCELLED.value,\n                    "cancelled_at": datetime.utcnow(),\n                })\n    \n    async def _update_execution_step_state(\n        self,\n        execution_id: str,\n        step_index: int,\n        status: str,\n        output: Optional[Dict[str, Any]] = None,\n    ) -> None:\n        """Update the state of a step in an execution."""\n        execution = await self.repository.find_execution_by_id(execution_id)\n        if not execution:\n            return\n        \n        steps_state = execution.get("steps_state", {})\n        step_key = f"step_{step_index}"\n        \n        if step_key in steps_state:\n            steps_state[step_key]["status"] = status\n            steps_state[step_key]["output"] = output\n            steps_state[step_key]["completed_at"] = datetime.utcnow().isoformat()\n            \n            await self.repository.update_execution(execution_id, {\n                "steps_state": steps_state,\n            })\n    \n    async def _check_step_completion(self, execution_id: str, step_index: int) -> None:\n        """Check if a step is complete and advance the workflow."""\n        execution = await self.repository.find_execution_by_id(execution_id)\n        if not execution:\n            return\n        \n        workflow = await self.repository.find_by_id(execution["workflow_id"])\n        if not workflow:\n            return\n        \n        steps = workflow["steps"]\n        current_step = steps[step_index] if step_index < len(steps) else None\n        \n        if not current_step:\n            return\n        \n        # Check if all tasks for this step are complete\n        tasks = await self.repository.find_step_tasks(execution_id, step_index)\n        pending_tasks = [t for t in tasks if t["status"] == TaskStatus.PENDING.value]\n        \n        if not pending_tasks:\n            # Step is complete, determine next step\n            next_step_index = await self._determine_next_step(execution, step_index)\n            \n            if next_step_index is None or next_step_index >= len(steps):\n                # Workflow complete\n                await self._complete_execution(execution_id)\n            else:\n                # Advance to next step\n                await self._advance_to_step(execution_id, next_step_index)\n    \n    async def _determine_next_step(\n        self,\n        execution: Dict[str, Any],\n        current_step_index: int,\n    ) -> Optional[int]:\n        """Determine the next step based on transitions."""\n        workflow = await self.repository.find_by_id(execution["workflow_id"])\n        if not workflow:\n            return None\n        \n        steps = workflow["steps"]\n        if current_step_index >= len(steps):\n            return None\n        \n        current_step = steps[current_step_index]\n        transitions = current_step.get("transitions", [])\n        \n        # Evaluate conditions to find next step\n        for transition in transitions:\n            condition = transition.get("condition")\n            target = transition.get("target_step")\n            \n            if condition:\n                # Evaluate condition against execution context\n                if await self._evaluate_condition(condition, execution):\n                    if target == "end":\n                        return None\n                    # Find step index by ID\n                    for i, step in enumerate(steps):\n                        if step.get("id") == target:\n                            return i\n            else:\n                # Default transition\n                if target == "end":\n                    return None\n                for i, step in enumerate(steps):\n                    if step.get("id") == target:\n                        return i\n        \n        # Default: next sequential step\n        return current_step_index + 1 if current_step_index + 1 < len(steps) else None\n    \n    async def _evaluate_condition(\n        self,\n        condition: Dict[str, Any],\n        execution: Dict[str, Any],\n    ) -> bool:\n        """Evaluate a condition against execution context."""\n        # Simple condition evaluation\n        # In production, this would be a more sophisticated rule engine\n        condition_type = condition.get("type")\n        \n        if condition_type == "expression":\n            # Evaluate expression against context\n            expression = condition.get("expression")\n            context = execution.get("context", {})\n            try:\n                # Safe evaluation (in production, use a proper expression evaluator)\n                result = eval(expression, {"__builtins__": {}}, context)\n                return bool(result)\n            except:\n                return False\n        \n        elif condition_type == "variable_equals":\n            variable = condition.get("variable")\n            value = condition.get("value")\n            return execution.get("context", {}).get(variable) == value\n        \n        return True\n    \n    async def _advance_to_step(self, execution_id: str, step_index: int) -> None:\n        """Advance execution to a new step."""\n        execution = await self.repository.find_execution_by_id(execution_id)\n        if not execution:\n            return\n        \n        workflow = await self.repository.find_by_id(execution["workflow_id"])\n        if not workflow:\n            return\n        \n        await self.repository.update_execution(execution_id, {\n            "current_step_index": step_index,\n        })\n        \n        # Create tasks for the new step\n        step = workflow["steps"][step_index]\n        await self._create_step_tasks(execution, step, step_index)\n    \n    async def _create_step_tasks(\n        self,\n        execution: Dict[str, Any],\n        step: Dict[str, Any],\n        step_index: int,\n    ) -> None:\n        """Create tasks for a workflow step."""\n        task_type = step.get("type", "task")\n        assignee_config = step.get("assignee", {})\n        \n        # Determine assignees\n        assignees = await self._resolve_assignees(assignee_config, execution)\n        \n        for assignee in assignees:\n            task_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "execution_id": execution["id"],\n                "step_index": step_index,\n                "task_type": task_type,\n                "title": step.get("name", f"Step {step_index + 1}"),\n                "description": step.get("description"),\n                "assigned_to": assignee,\n                "status": TaskStatus.PENDING.value,\n                "priority": execution.get("priority", Priority.NORMAL.value),\n                "due_at": self._calculate_due_date(step),\n                "input_data": step.get("input_data", {}),\n                "created_at": datetime.utcnow(),\n            }\n            \n            await self.repository.create_task(task_data)\n    \n    async def _resolve_assignees(\n        self,\n        assignee_config: Dict[str, Any],\n        execution: Dict[str, Any],\n    ) -> List[str]:\n        """Resolve assignees from configuration."""\n        assignees = []\n        \n        if assignee_config.get("user_id"):\n            assignees.append(assignee_config["user_id"])\n        \n        if assignee_config.get("role"):\n            # Find users with the specified role\n            users = await self.repository.find_users_by_role(\n                assignee_config["role"],\n                self.company_id,\n            )\n            assignees.extend(users)\n        \n        if assignee_config.get("from_context"):\n            # Get assignee from execution context\n            context_assignee = execution.get("context", {}).get(\n                assignee_config["from_context"]\n            )\n            if context_assignee:\n                assignees.append(context_assignee)\n        \n        if assignee_config.get("from_variable"):\n            # Get assignee from workflow variables\n            var_assignee = execution.get("variables", {}).get(\n                assignee_config["from_variable"]\n            )\n            if var_assignee:\n                assignees.append(var_assignee)\n        \n        # Deduplicate\n        return list(set(assignees))\n    \n    def _calculate_due_date(self, step: Dict[str, Any]) -> Optional[datetime]:\n        """Calculate due date for a task."""\n        due_config = step.get("due")\n        if not due_config:\n            return None\n        \n        if due_config.get("minutes"):\n            return datetime.utcnow() + timedelta(minutes=due_config["minutes"])\n        if due_config.get("hours"):\n            return datetime.utcnow() + timedelta(hours=due_config["hours"])\n        if due_config.get("days"):\n            return datetime.utcnow() + timedelta(days=due_config["days"])\n        \n        return None\n    \n    async def _complete_execution(self, execution_id: str) -> None:\n        """Mark an execution as complete."""\n        await self.repository.update_execution(execution_id, {\n            "status": WorkflowStatus.COMPLETED.value,\n            "completed_at": datetime.utcnow(),\n        })\n    \n    async def _can_user_complete_task(self, task: Dict[str, Any], user_id: str) -> bool:\n        """Check if a user can complete a task."""\n        # Check if directly assigned\n        if task.get("assigned_to") == user_id:\n            return True\n        \n        # Check if user is a delegate\n        delegates = await self.repository.find_user_delegates(user_id)\n        if task.get("assigned_to") in delegates:\n            return True\n        \n        # Check if user has workflow admin permission\n        return await self._has_permission(user_id, "workflow:admin")\n    \n    async def _can_user_approve(self, task: Dict[str, Any], user_id: str) -> bool:\n        """Check if a user can approve a task."""\n        # Check if directly assigned as approver\n        if task.get("assigned_to") == user_id:\n            return True\n        \n        # Check approval rules\n        execution = await self.repository.find_execution_by_id(task["execution_id"])\n        if execution:\n            workflow = await self.repository.find_by_id(execution["workflow_id"])\n            if workflow:\n                step = workflow["steps"][task["step_index"]]\n                approval_config = step.get("approval_config", {})\n                \n                # Check if user is in approval list\n                if user_id in approval_config.get("approvers", []):\n                    return True\n                \n                # Check if user has matching role\n                if approval_config.get("approval_roles"):\n                    user_roles = await self.repository.find_user_roles(user_id)\n                    if any(r in approval_config["approval_roles"] for r in user_roles):\n                        return True\n        \n        return False\n    \n    async def _can_user_reassign(self, task: Dict[str, Any], user_id: str) -> bool:\n        """Check if a user can reassign a task."""\n        # Workflow admins can reassign\n        if await self._has_permission(user_id, "workflow:admin"):\n            return True\n        \n        # Original assigner can reassign\n        if task.get("assigned_by") == user_id:\n            return True\n        \n        return False\n    \n    async def _handle_task_rejection(\n        self,\n        task: Dict[str, Any],\n        reason: str,\n        user_id: str,\n    ) -> None:\n        """Handle task rejection based on workflow configuration."""\n        execution = await self.repository.find_execution_by_id(task["execution_id"])\n        if not execution:\n            return\n        \n        workflow = await self.repository.find_by_id(execution["workflow_id"])\n        if not workflow:\n            return\n        \n        step = workflow["steps"][task["step_index"]]\n        rejection_config = step.get("rejection_config", {})\n        \n        action = rejection_config.get("action", "fail")\n        \n        if action == "fail":\n            # Fail the entire execution\n            await self.repository.update_execution(execution["id"], {\n                "status": WorkflowStatus.FAILED.value,\n                "failed_at": datetime.utcnow(),\n                "failure_reason": f"Task {task[\'id\']} rejected: {reason}",\n            })\n        \n        elif action == "goto_step":\n            # Jump to specific step\n            target_step = rejection_config.get("target_step")\n            if target_step:\n                for i, s in enumerate(workflow["steps"]):\n                    if s.get("id") == target_step:\n                        await self._advance_to_step(execution["id"], i)\n                        break\n        \n        elif action == "notify":\n            # Just notify and pause\n            await self._send_rejection_notification(execution, task, reason)\n    \n    async def _send_rejection_notification(\n        self,\n        execution: Dict[str, Any],\n        task: Dict[str, Any],\n        reason: str,\n    ) -> None:\n        """Send notification about task rejection."""\n        # In production, this would integrate with notification service\n        pass\n    \n    async def _register_trigger(self, trigger: Dict[str, Any]) -> None:\n        """Register a trigger with the appropriate handler."""\n        # In production, this would register with a job scheduler or event bus\n        pass\n    \n    async def _update_trigger_registration(self, trigger: Dict[str, Any]) -> None:\n        """Update trigger registration."""\n        await self._unregister_trigger(trigger)\n        await self._register_trigger(trigger)\n    \n    async def _unregister_trigger(self, trigger: Dict[str, Any]) -> None:\n        """Unregister a trigger."""\n        # In production, this would unregister from job scheduler or event bus\n        pass\n    \n    async def _activate_triggers(self, workflow_id: str) -> None:\n        """Activate all triggers for a workflow."""\n        triggers = await self.repository.find_workflow_triggers(workflow_id)\n        for trigger in triggers:\n            await self.repository.update_trigger(trigger["id"], {\n                "is_active": True,\n            })\n            await self._register_trigger(trigger)\n    \n    async def _deactivate_triggers(self, workflow_id: str) -> None:\n        """Deactivate all triggers for a workflow."""\n        triggers = await self.repository.find_workflow_triggers(workflow_id)\n        for trigger in triggers:\n            await self.repository.update_trigger(trigger["id"], {\n                "is_active": False,\n            })\n            await self._unregister_trigger(trigger)\n    \n    async def _has_permission(self, user_id: str, permission: str) -> bool:\n        """Check if a user has a specific permission."""\n        # This would integrate with the auth service\n        return False\n\n\nclass WorkflowExecutionService(CompanyScopedService):\n    """\n    Service for managing workflow execution lifecycle.\n    \n    Handles the runtime aspects of workflow execution including\n    state transitions, error handling, and recovery.\n    """\n    \n    @requires_permission("workflow:execute")\n    @transactional\n    async def process_execution(\n        self,\n        execution_id: str,\n    ) -> ServiceResult[WorkflowExecutionResponse]:\n        """\n        Process a workflow execution.\n        \n        This method is called by the workflow engine to process\n        an execution through its steps.\n        \n        Args:\n            execution_id: ID of the execution to process\n            \n        Returns:\n            ServiceResult containing the execution state\n        """\n        try:\n            execution = await self.repository.find_execution_by_id(execution_id)\n            if not execution:\n                return ServiceResult.error(\n                    NotFoundError(f"Execution with ID {execution_id} not found")\n                )\n            \n            workflow = await self.repository.find_by_id(execution["workflow_id"])\n            if not workflow:\n                return ServiceResult.error(\n                    NotFoundError(f"Workflow definition not found")\n                )\n            \n            # Process current step\n            current_step_index = execution.get("current_step_index", 0)\n            steps = workflow["steps"]\n            \n            if current_step_index >= len(steps):\n                # Execution complete\n                return await self._finalize_execution(execution_id, "completed")\n            \n            current_step = steps[current_step_index]\n            \n            # Execute step based on type\n            step_type = current_step.get("type", "manual")\n            \n            if step_type == "manual":\n                # Create tasks and wait for completion\n                return await self._process_manual_step(execution, current_step, current_step_index)\n            \n            elif step_type == "automatic":\n                # Execute automatically\n                return await self._process_automatic_step(execution, current_step, current_step_index)\n            \n            elif step_type == "approval":\n                # Create approval tasks\n                return await self._process_approval_step(execution, current_step, current_step_index)\n            \n            elif step_type == "parallel":\n                # Process parallel branches\n                return await self._process_parallel_step(execution, current_step, current_step_index)\n            \n            elif step_type == "condition":\n                # Evaluate condition and branch\n                return await self._process_condition_step(execution, current_step, current_step_index)\n            \n            else:\n                return ServiceResult.error(\n                    ValidationError(f"Unknown step type: {step_type}")\n                )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to process execution: {str(e)}"))\n    \n    async def _process_manual_step(\n        self,\n        execution: Dict[str, Any],\n        step: Dict[str, Any],\n        step_index: int,\n    ) -> ServiceResult[WorkflowExecutionResponse]:\n        """Process a manual step."""\n        # Check if tasks already exist for this step\n        existing_tasks = await self.repository.find_step_tasks(execution["id"], step_index)\n        \n        if not existing_tasks:\n            # Create task\n            task_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "execution_id": execution["id"],\n                "step_index": step_index,\n                "task_type": "manual",\n                "title": step.get("name", f"Task {step_index + 1}"),\n                "description": step.get("description"),\n                "status": TaskStatus.PENDING.value,\n                "priority": execution.get("priority", Priority.NORMAL.value),\n                "due_at": self._calculate_due_date(step),\n                "input_data": step.get("input_data", {}),\n                "created_at": datetime.utcnow(),\n            }\n            \n            # Assign based on configuration\n            assignee_config = step.get("assignee", {})\n            if assignee_config.get("user_id"):\n                task_data["assigned_to"] = assignee_config["user_id"]\n            \n            await self.repository.create_task(task_data)\n        \n        # Update execution status\n        return ServiceResult.success(\n            WorkflowExecutionResponse(**execution),\n            message="Waiting for manual task completion"\n        )\n    \n    async def _process_automatic_step(\n        self,\n        execution: Dict[str, Any],\n        step: Dict[str, Any],\n        step_index: int,\n    ) -> ServiceResult[WorkflowExecutionResponse]:\n        """Process an automatic step."""\n        action = step.get("action", {})\n        action_type = action.get("type")\n        \n        try:\n            # Execute the action\n            result = await self._execute_action(action, execution)\n            \n            # Update step state\n            await self._update_step_state(\n                execution["id"],\n                step_index,\n                "completed",\n                result\n            )\n            \n            # Move to next step\n            next_step_index = step_index + 1\n            workflow = await self.repository.find_by_id(execution["workflow_id"])\n            \n            if next_step_index >= len(workflow["steps"]):\n                return await self._finalize_execution(execution["id"], "completed")\n            \n            await self.repository.update_execution(execution["id"], {\n                "current_step_index": next_step_index,\n            })\n            \n            # Recursively process next step\n            return await self.process_execution(execution["id"])\n            \n        except Exception as e:\n            # Handle error based on workflow configuration\n            return await self._handle_step_error(execution, step, step_index, str(e))\n    \n    async def _process_approval_step(\n        self,\n        execution: Dict[str, Any],\n        step: Dict[str, Any],\n        step_index: int,\n    ) -> ServiceResult[WorkflowExecutionResponse]:\n        """Process an approval step."""\n        # Check if approval tasks already exist\n        existing_tasks = await self.repository.find_step_tasks(execution["id"], step_index)\n        \n        if not existing_tasks:\n            # Determine approvers\n            approval_config = step.get("approval_config", {})\n            approvers = approval_config.get("approvers", [])\n            \n            # If no approvers specified, use workflow owner or admin\n            if not approvers:\n                approvers = [execution.get("triggered_by")]\n            \n            approval_type = approval_config.get("type", "any")  # any, all, majority\n            \n            for approver in approvers:\n                task_data = {\n                    "id": str(uuid.uuid4()),\n                    "company_id": self.company_id,\n                    "execution_id": execution["id"],\n                    "step_index": step_index,\n                    "task_type": "approval",\n                    "title": step.get("name", f"Approval {step_index + 1}"),\n                    "description": step.get("description"),\n                    "assigned_to": approver,\n                    "status": TaskStatus.PENDING.value,\n                    "priority": Priority.HIGH.value,\n                    "input_data": {\n                        "approval_type": approval_type,\n                        "approval_config": approval_config,\n                    },\n                    "created_at": datetime.utcnow(),\n                }\n                await self.repository.create_task(task_data)\n        \n        return ServiceResult.success(\n            WorkflowExecutionResponse(**execution),\n            message="Waiting for approval"\n        )\n    \n    async def _process_parallel_step(\n        self,\n        execution: Dict[str, Any],\n        step: Dict[str, Any],\n        step_index: int,\n    ) -> ServiceResult[WorkflowExecutionResponse]:\n        """Process parallel branches."""\n        branches = step.get("branches", [])\n        \n        for branch in branches:\n            # Create sub-execution for each branch\n            branch_execution_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "workflow_id": execution["workflow_id"],\n                "workflow_version": execution["workflow_version"],\n                "parent_execution_id": execution["id"],\n                "status": WorkflowStatus.PENDING.value,\n                "trigger_type": "parallel_branch",\n                "triggered_by": execution["triggered_by"],\n                "context": {**execution.get("context", {}), "branch": branch},\n                "variables": execution.get("variables", {}),\n                "input_data": execution.get("input_data", {}),\n                "created_at": datetime.utcnow(),\n            }\n            \n            await self.repository.create_execution(branch_execution_data)\n        \n        # The main execution will wait for all branches to complete\n        await self.repository.update_execution(execution["id"], {\n            "status": WorkflowStatus.WAITING.value,\n            "waiting_for": "parallel_branches",\n        })\n        \n        return ServiceResult.success(\n            WorkflowExecutionResponse(**execution),\n            message="Processing parallel branches"\n        )\n    \n    async def _process_condition_step(\n        self,\n        execution: Dict[str, Any],\n        step: Dict[str, Any],\n        step_index: int,\n    ) -> ServiceResult[WorkflowExecutionResponse]:\n        """Process a conditional branch."""\n        conditions = step.get("conditions", [])\n        \n        for condition in conditions:\n            if await self._evaluate_condition(condition, execution):\n                # Jump to target step\n                target = condition.get("target_step")\n                workflow = await self.repository.find_by_id(execution["workflow_id"])\n                \n                for i, s in enumerate(workflow["steps"]):\n                    if s.get("id") == target:\n                        await self.repository.update_execution(execution["id"], {\n                            "current_step_index": i,\n                        })\n                        return await self.process_execution(execution["id"])\n        \n        # No condition matched, use default\n        default_target = step.get("default_step")\n        if default_target:\n            workflow = await self.repository.find_by_id(execution["workflow_id"])\n            for i, s in enumerate(workflow["steps"]):\n                if s.get("id") == default_target:\n                    await self.repository.update_execution(execution["id"], {\n                        "current_step_index": i,\n                    })\n                    return await self.process_execution(execution["id"])\n        \n        return ServiceResult.error(\n            ValidationError("No matching condition and no default specified")\n        )\n    \n    async def _execute_action(\n        self,\n        action: Dict[str, Any],\n        execution: Dict[str, Any],\n    ) -> Dict[str, Any]:\n        """Execute an automatic action."""\n        action_type = action.get("type")\n        \n        if action_type == "http_request":\n            # Execute HTTP request\n            return await self._execute_http_action(action, execution)\n        \n        elif action_type == "script":\n            # Execute script\n            return await self._execute_script_action(action, execution)\n        \n        elif action_type == "integration":\n            # Execute integration action\n            return await self._execute_integration_action(action, execution)\n        \n        elif action_type == "notify":\n            # Send notification\n            return await self._execute_notify_action(action, execution)\n        \n        elif action_type == "update_variable":\n            # Update workflow variable\n            return await self._execute_variable_action(action, execution)\n        \n        return {"success": True}\n    \n    async def _execute_http_action(\n        self,\n        action: Dict[str, Any],\n        execution: Dict[str, Any],\n    ) -> Dict[str, Any]:\n        """Execute HTTP request action."""\n        import aiohttp\n        \n        url = action.get("url")\n        method = action.get("method", "GET")\n        headers = action.get("headers", {})\n        body = action.get("body")\n        \n        # Replace variables in URL and body\n        context = execution.get("context", {})\n        url = self._replace_variables(url, context)\n        if body:\n            body = self._replace_variables(json.dumps(body), context)\n        \n        async with aiohttp.ClientSession() as session:\n            async with session.request(\n                method=method,\n                url=url,\n                headers=headers,\n                data=body,\n            ) as response:\n                result = await response.text()\n                return {\n                    "success": response.status < 400,\n                    "status": response.status,\n                    "body": result,\n                }\n    \n    async def _execute_script_action(\n        self,\n        action: Dict[str, Any],\n        execution: Dict[str, Any],\n    ) -> Dict[str, Any]:\n        """Execute script action."""\n        script = action.get("script")\n        language = action.get("language", "python")\n        context = execution.get("context", {})\n        \n        # In production, this would use a sandboxed script executor\n        try:\n            # Simple Python execution (NOT SAFE FOR PRODUCTION)\n            local_vars = {}\n            exec(script, {"context": context, "result": None}, local_vars)\n            return {\n                "success": True,\n                "result": local_vars.get("result"),\n            }\n        except Exception as e:\n            return {\n                "success": False,\n                "error": str(e),\n            }\n    \n    async def _execute_integration_action(\n        self,\n        action: Dict[str, Any],\n        execution: Dict[str, Any],\n    ) -> Dict[str, Any]:\n        """Execute integration action."""\n        integration_id = action.get("integration_id")\n        operation = action.get("operation")\n        params = action.get("params", {})\n        \n        # In production, this would call the integration service\n        return {"success": True, "integration": integration_id}\n    \n    async def _execute_notify_action(\n        self,\n        action: Dict[str, Any],\n        execution: Dict[str, Any],\n    ) -> Dict[str, Any]:\n        """Execute notification action."""\n        recipients = action.get("recipients", [])\n        template = action.get("template")\n        context = execution.get("context", {})\n        \n        # In production, this would call the notification service\n        return {"success": True, "notified": recipients}\n    \n    async def _execute_variable_action(\n        self,\n        action: Dict[str, Any],\n        execution: Dict[str, Any],\n    ) -> Dict[str, Any]:\n        """Execute variable update action."""\n        variable_name = action.get("variable")\n        value_expression = action.get("value")\n        context = execution.get("context", {})\n        \n        # Evaluate value expression\n        try:\n            value = eval(value_expression, {"__builtins__": {}}, context)\n        except:\n            value = value_expression\n        \n        # Update execution variables\n        variables = execution.get("variables", {})\n        variables[variable_name] = value\n        \n        await self.repository.update_execution(execution["id"], {\n            "variables": variables,\n        })\n        \n        return {"success": True, "variable": variable_name, "value": value}\n    \n    def _replace_variables(self, text: str, context: Dict[str, Any]) -> str:\n        """Replace variables in text."""\n        if not text:\n            return text\n        \n        import re\n        pattern = r\'\\{\\{(\\w+)\\}\\}\'\n        \n        def replacer(match):\n            var_name = match.group(1)\n            return str(context.get(var_name, match.group(0)))\n        \n        return re.sub(pattern, replacer, text)\n    \n    async def _evaluate_condition(\n        self,\n        condition: Dict[str, Any],\n        execution: Dict[str, Any],\n    ) -> bool:\n        """Evaluate a condition."""\n        condition_type = condition.get("type")\n        context = execution.get("context", {})\n        \n        if condition_type == "equals":\n            return context.get(condition.get("variable")) == condition.get("value")\n        \n        elif condition_type == "not_equals":\n            return context.get(condition.get("variable")) != condition.get("value")\n        \n        elif condition_type == "greater_than":\n            var_value = context.get(condition.get("variable"), 0)\n            target_value = condition.get("value", 0)\n            return var_value > target_value\n        \n        elif condition_type == "less_than":\n            var_value = context.get(condition.get("variable"), 0)\n            target_value = condition.get("value", 0)\n            return var_value < target_value\n        \n        elif condition_type == "contains":\n            var_value = context.get(condition.get("variable"), "")\n            target_value = condition.get("value", "")\n            return target_value in var_value\n        \n        elif condition_type == "expression":\n            expression = condition.get("expression")\n            try:\n                return bool(eval(expression, {"__builtins__": {}}, context))\n            except:\n                return False\n        \n        return False\n    \n    async def _update_step_state(\n        self,\n        execution_id: str,\n        step_index: int,\n        status: str,\n        output: Dict[str, Any],\n    ) -> None:\n        """Update step state in execution."""\n        execution = await self.repository.find_execution_by_id(execution_id)\n        if not execution:\n            return\n        \n        steps_state = execution.get("steps_state", {})\n        step_key = f"step_{step_index}"\n        \n        if step_key in steps_state:\n            steps_state[step_key]["status"] = status\n            steps_state[step_key]["output"] = output\n            steps_state[step_key]["completed_at"] = datetime.utcnow().isoformat()\n            \n            await self.repository.update_execution(execution_id, {\n                "steps_state": steps_state,\n            })\n    \n    async def _handle_step_error(\n        self,\n        execution: Dict[str, Any],\n        step: Dict[str, Any],\n        step_index: int,\n        error: str,\n    ) -> ServiceResult[WorkflowExecutionResponse]:\n        """Handle error during step execution."""\n        error_handling = step.get("error_handling", {})\n        strategy = error_handling.get("strategy", "fail")\n        \n        if strategy == "retry":\n            max_retries = error_handling.get("max_retries", 3)\n            retry_count = execution.get("retry_count", 0)\n            \n            if retry_count < max_retries:\n                # Schedule retry\n                await self.repository.update_execution(execution["id"], {\n                    "retry_count": retry_count + 1,\n                    "last_error": error,\n                })\n                # In production, would schedule retry\n                return ServiceResult.success(\n                    WorkflowExecutionResponse(**execution),\n                    message=f"Retrying step (attempt {retry_count + 1}/{max_retries})"\n                )\n        \n        elif strategy == "skip":\n            # Skip to next step\n            workflow = await self.repository.find_by_id(execution["workflow_id"])\n            next_step_index = step_index + 1\n            \n            if next_step_index < len(workflow["steps"]):\n                await self.repository.update_execution(execution["id"], {\n                    "current_step_index": next_step_index,\n                    "last_error": error,\n                })\n                return await self.process_execution(execution["id"])\n        \n        elif strategy == "goto_step":\n            target = error_handling.get("target_step")\n            workflow = await self.repository.find_by_id(execution["workflow_id"])\n            \n            for i, s in enumerate(workflow["steps"]):\n                if s.get("id") == target:\n                    await self.repository.update_execution(execution["id"], {\n                        "current_step_index": i,\n                        "last_error": error,\n                    })\n                    return await self.process_execution(execution["id"])\n        \n        # Default: fail execution\n        return await self._finalize_execution(execution["id"], "failed", error)\n    \n    async def _finalize_execution(\n        self,\n        execution_id: str,\n        status: str,\n        error: Optional[str] = None,\n    ) -> ServiceResult[WorkflowExecutionResponse]:\n        """Finalize an execution."""\n        update_data = {\n            "status": status,\n            "completed_at": datetime.utcnow(),\n        }\n        \n        if error:\n            update_data["error"] = error\n            update_data["failed_at"] = datetime.utcnow()\n        \n        await self.repository.update_execution(execution_id, update_data)\n        \n        execution = await self.repository.find_execution_by_id(execution_id)\n        return ServiceResult.success(\n            WorkflowExecutionResponse(**execution),\n            message=f"Execution {status}"\n        )\n    \n    def _calculate_due_date(self, step: Dict[str, Any]) -> Optional[datetime]:\n        """Calculate due date for a task."""\n        due_config = step.get("due")\n        if not due_config:\n            return None\n        \n        if due_config.get("minutes"):\n            return datetime.utcnow() + timedelta(minutes=due_config["minutes"])\n        if due_config.get("hours"):\n            return datetime.utcnow() + timedelta(hours=due_config["hours"])\n        if due_config.get("days"):\n            return datetime.utcnow() + timedelta(days=due_config["days"])\n        \n        return None\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/services/workflow.py\' created successfully.") </create-file> </tool_result>'}
{'role': 'assistant', 'content': 'Excellent! Workflow service created successfully. Now I\'ll continue with the Legal service for contracts, matters, and compliance management.\n\n<function_calls>\n<create-file file_path="backend/app/services/legal.py">\n"""\nLegal Service - Contract, Matter, and Compliance Management\n\nImplements comprehensive legal operations including:\n- Contract lifecycle management (creation, review, execution, renewal)\n- Legal matter tracking and management\n- Compliance monitoring and reporting\n- Document management and versioning\n- Legal entity management\n- Obligation tracking\n- Litigation management\n"""\n\nfrom typing import Optional, List, Dict, Any, Union\nfrom datetime import datetime, timedelta\nfrom decimal import Decimal\nimport uuid\nimport json\nfrom enum import Enum\n\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom sqlalchemy import select, update, delete, and_, or_, func, case\nfrom sqlalchemy.orm import selectinload, joinedload\n\nfrom .base import (\n    CompanyScopedService,\n    ServiceResult,\n    ServiceError,\n    ValidationError,\n    NotFoundError,\n    PermissionDeniedError,\n    ConflictError,\n    requires_permission,\n    transactional,\n    emit_event,\n    cached,\n    cache_invalidate,\n)\nfrom ..schemas.legal import (\n    ContractCreate,\n    ContractUpdate,\n    ContractResponse,\n    ContractListResponse,\n    ContractVersionResponse,\n    ContractAmendmentCreate,\n    ContractAmendmentResponse,\n    MatterCreate,\n    MatterUpdate,\n    MatterResponse,\n    MatterListResponse,\n    MatterTaskCreate,\n    MatterTaskUpdate,\n    MatterTaskResponse,\n    ComplianceFrameworkCreate,\n    ComplianceFrameworkUpdate,\n    ComplianceFrameworkResponse,\n    ComplianceFrameworkListResponse,\n    ComplianceRequirementCreate,\n    ComplianceRequirementResponse,\n    ComplianceAssessmentCreate,\n    ComplianceAssessmentResponse,\n    LegalEntityCreate,\n    LegalEntityUpdate,\n    LegalEntityResponse,\n    ObligationCreate,\n    ObligationResponse,\n    DocumentCreate,\n    DocumentResponse,\n    ContractStatus,\n    MatterStatus,\n    MatterPriority,\n    ComplianceStatus,\n    ObligationType,\n    DocumentType,\n)\n\n\nclass LegalService(CompanyScopedService):\n    """\n    Service for managing legal operations.\n    \n    Handles contracts, legal matters, compliance, and related\n    legal document management.\n    """\n    \n    # ==================== Contract Management ====================\n    \n    @requires_permission("legal:contract:create")\n    @transactional\n    @emit_event("legal.contract.created")\n    async def create_contract(\n        self,\n        data: ContractCreate,\n        user_id: str,\n    ) -> ServiceResult[ContractResponse]:\n        """\n        Create a new contract.\n        \n        Args:\n            data: Contract creation data\n            user_id: ID of the user creating the contract\n            \n        Returns:\n            ServiceResult containing the created contract\n        """\n        try:\n            # Validate dates\n            if data.end_date and data.start_date and data.end_date <= data.start_date:\n                return ServiceResult.error(\n                    ValidationError("End date must be after start date")\n                )\n            \n            # Validate parties exist\n            if data.party_ids:\n                for party_id in data.party_ids:\n                    party = await self.repository.find_legal_entity(party_id)\n                    if not party:\n                        return ServiceResult.error(\n                            NotFoundError(f"Legal entity with ID {party_id} not found")\n                        )\n            \n            # Generate contract number\n            contract_number = await self._generate_contract_number(data.contract_type)\n            \n            # Create contract\n            contract_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "contract_number": contract_number,\n                "title": data.title,\n                "description": data.description,\n                "contract_type": data.contract_type,\n                "status": ContractStatus.DRAFT.value,\n                "party_ids": data.party_ids or [],\n                "internal_owner_id": data.internal_owner_id,\n                "legal_owner_id": data.legal_owner_id,\n                "start_date": data.start_date,\n                "end_date": data.end_date,\n                "auto_renew": data.auto_renew or False,\n                "renewal_term_days": data.renewal_term_days,\n                "renewal_notice_days": data.renewal_notice_days,\n                "termination_notice_days": data.termination_notice_days,\n                "value": float(data.value) if data.value else None,\n                "currency": data.currency or "USD",\n                "payment_terms": data.payment_terms,\n                "confidentiality_level": data.confidentiality_level or "standard",\n                "governing_law": data.governing_law,\n                "jurisdiction": data.jurisdiction,\n                "tags": data.tags or [],\n                "custom_fields": data.custom_fields or {},\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n                "updated_at": datetime.utcnow(),\n            }\n            \n            contract = await self.repository.create_contract(contract_data)\n            \n            # Create initial version\n            await self._create_contract_version(\n                contract["id"],\n                {},\n                user_id,\n                "Initial version"\n            )\n            \n            # Set up obligations from template if provided\n            if data.obligation_template_id:\n                await self._apply_obligation_template(\n                    contract["id"],\n                    data.obligation_template_id,\n                    user_id\n                )\n            \n            # Create alerts for key dates\n            await self._create_contract_alerts(contract)\n            \n            return ServiceResult.success(\n                ContractResponse(**contract),\n                message="Contract created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create contract: {str(e)}"))\n    \n    @requires_permission("legal:contract:read")\n    @cached(key_prefix="contract", ttl=300)\n    async def get_contract(\n        self,\n        contract_id: str,\n    ) -> ServiceResult[ContractResponse]:\n        """\n        Get a contract by ID.\n        \n        Args:\n            contract_id: ID of the contract\n            \n        Returns:\n            ServiceResult containing the contract\n        """\n        try:\n            contract = await self.repository.find_contract_by_id(contract_id)\n            if not contract:\n                return ServiceResult.error(\n                    NotFoundError(f"Contract with ID {contract_id} not found")\n                )\n            \n            return ServiceResult.success(ContractResponse(**contract))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get contract: {str(e)}"))\n    \n    @requires_permission("legal:contract:read")\n    async def list_contracts(\n        self,\n        contract_type: Optional[str] = None,\n        status: Optional[str] = None,\n        party_id: Optional[str] = None,\n        owner_id: Optional[str] = None,\n        expiring_in_days: Optional[int] = None,\n        search: Optional[str] = None,\n        tags: Optional[List[str]] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[ContractListResponse]:\n        """\n        List contracts with filtering and pagination.\n        \n        Args:\n            contract_type: Filter by contract type\n            status: Filter by status\n            party_id: Filter by party ID\n            owner_id: Filter by owner ID\n            expiring_in_days: Filter contracts expiring within days\n            search: Search in title and description\n            tags: Filter by tags\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated contracts\n        """\n        try:\n            filters = {}\n            if contract_type:\n                filters["contract_type"] = contract_type\n            if status:\n                filters["status"] = status\n            if party_id:\n                filters["party_id"] = party_id\n            if owner_id:\n                filters["owner_id"] = owner_id\n            \n            date_filters = {}\n            if expiring_in_days:\n                end_date = datetime.utcnow() + timedelta(days=expiring_in_days)\n                date_filters["end_date_lte"] = end_date\n            \n            result = await self.repository.find_contracts_paginated(\n                filters=filters,\n                date_filters=date_filters,\n                search=search,\n                search_fields=["title", "description", "contract_number"],\n                tags=tags,\n                page=page,\n                page_size=page_size,\n                order_by="created_at",\n                order_desc=True,\n            )\n            \n            return ServiceResult.success(\n                ContractListResponse(\n                    items=[ContractResponse(**c) for c in result["items"]],\n                    total=result["total"],\n                    page=page,\n                    page_size=page_size,\n                    total_pages=result["total_pages"],\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list contracts: {str(e)}"))\n    \n    @requires_permission("legal:contract:update")\n    @transactional\n    @emit_event("legal.contract.updated")\n    async def update_contract(\n        self,\n        contract_id: str,\n        data: ContractUpdate,\n        user_id: str,\n        create_version: bool = False,\n        version_note: Optional[str] = None,\n    ) -> ServiceResult[ContractResponse]:\n        """\n        Update a contract.\n        \n        Args:\n            contract_id: ID of the contract to update\n            data: Update data\n            user_id: ID of the user updating\n            create_version: Whether to create a new version\n            version_note: Note for the version\n            \n        Returns:\n            ServiceResult containing the updated contract\n        """\n        try:\n            contract = await self.repository.find_contract_by_id(contract_id)\n            if not contract:\n                return ServiceResult.error(\n                    NotFoundError(f"Contract with ID {contract_id} not found")\n                )\n            \n            # Check if contract can be modified\n            if contract["status"] in [ContractStatus.EXECUTED.value, ContractStatus.TERMINATED.value]:\n                if data.is_major_change:\n                    return ServiceResult.error(\n                        ConflictError("Cannot modify executed contract without amendment")\n                    )\n            \n            update_data = data.model_dump(exclude_unset=True)\n            update_data["updated_at"] = datetime.utcnow()\n            update_data["updated_by"] = user_id\n            \n            updated_contract = await self.repository.update_contract(contract_id, update_data)\n            \n            # Create version if requested\n            if create_version:\n                await self._create_contract_version(\n                    contract_id,\n                    update_data,\n                    user_id,\n                    version_note or "Contract updated"\n                )\n            \n            # Update alerts if dates changed\n            if "end_date" in update_data or "start_date" in update_data:\n                await self._update_contract_alerts(updated_contract)\n            \n            await cache_invalidate(f"contract:{contract_id}")\n            \n            return ServiceResult.success(\n                ContractResponse(**updated_contract),\n                message="Contract updated successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to update contract: {str(e)}"))\n    \n    @requires_permission("legal:contract:update")\n    @transactional\n    @emit_event("legal.contract.submitted_for_review")\n    async def submit_for_review(\n        self,\n        contract_id: str,\n        user_id: str,\n        reviewers: List[str],\n        due_date: Optional[datetime] = None,\n        notes: Optional[str] = None,\n    ) -> ServiceResult[ContractResponse]:\n        """\n        Submit a contract for review.\n        \n        Args:\n            contract_id: ID of the contract\n            user_id: ID of the user submitting\n            reviewers: List of reviewer user IDs\n            due_date: Review due date\n            notes: Submission notes\n            \n        Returns:\n            ServiceResult containing the updated contract\n        """\n        try:\n            contract = await self.repository.find_contract_by_id(contract_id)\n            if not contract:\n                return ServiceResult.error(\n                    NotFoundError(f"Contract with ID {contract_id} not found")\n                )\n            \n            if contract["status"] != ContractStatus.DRAFT.value:\n                return ServiceResult.error(\n                    ConflictError("Only draft contracts can be submitted for review")\n                )\n            \n            # Update contract status\n            updated_contract = await self.repository.update_contract(contract_id, {\n                "status": ContractStatus.PENDING_REVIEW.value,\n                "submitted_for_review_at": datetime.utcnow(),\n                "submitted_for_review_by": user_id,\n                "review_due_date": due_date,\n                "review_notes": notes,\n            })\n            \n            # Create review tasks\n            for reviewer_id in reviewers:\n                await self._create_review_task(\n                    contract_id,\n                    reviewer_id,\n                    user_id,\n                    due_date,\n                    notes\n                )\n            \n            await cache_invalidate(f"contract:{contract_id}")\n            \n            return ServiceResult.success(\n                ContractResponse(**updated_contract),\n                message="Contract submitted for review"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to submit contract for review: {str(e)}"))\n    \n    @requires_permission("legal:contract:review")\n    @transactional\n    @emit_event("legal.contract.reviewed")\n    async def complete_review(\n        self,\n        contract_id: str,\n        user_id: str,\n        approved: bool,\n        comments: Optional[str] = None,\n        required_changes: Optional[List[Dict[str, Any]]] = None,\n    ) -> ServiceResult[ContractResponse]:\n        """\n        Complete a contract review.\n        \n        Args:\n            contract_id: ID of the contract\n            user_id: ID of the reviewer\n            approved: Whether the review is approved\n            comments: Review comments\n            required_changes: List of required changes if not approved\n            \n        Returns:\n            ServiceResult containing the updated contract\n        """\n        try:\n            contract = await self.repository.find_contract_by_id(contract_id)\n            if not contract:\n                return ServiceResult.error(\n                    NotFoundError(f"Contract with ID {contract_id} not found")\n                )\n            \n            if contract["status"] != ContractStatus.PENDING_REVIEW.value:\n                return ServiceResult.error(\n                    ConflictError("Contract is not pending review")\n                )\n            \n            # Record review\n            review_data = {\n                "id": str(uuid.uuid4()),\n                "contract_id": contract_id,\n                "reviewer_id": user_id,\n                "approved": approved,\n                "comments": comments,\n                "required_changes": required_changes,\n                "reviewed_at": datetime.utcnow(),\n            }\n            await self.repository.create_contract_review(review_data)\n            \n            # Update contract status\n            if approved:\n                new_status = ContractStatus.PENDING_EXECUTION.value\n            else:\n                new_status = ContractStatus.CHANGES_REQUIRED.value\n            \n            updated_contract = await self.repository.update_contract(contract_id, {\n                "status": new_status,\n                "reviewed_at": datetime.utcnow(),\n                "reviewed_by": user_id,\n            })\n            \n            await cache_invalidate(f"contract:{contract_id}")\n            \n            return ServiceResult.success(\n                ContractResponse(**updated_contract),\n                message="Review completed"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to complete review: {str(e)}"))\n    \n    @requires_permission("legal:contract:execute")\n    @transactional\n    @emit_event("legal.contract.executed")\n    async def execute_contract(\n        self,\n        contract_id: str,\n        user_id: str,\n        execution_date: Optional[datetime] = None,\n        effective_date: Optional[datetime] = None,\n        document_id: Optional[str] = None,\n    ) -> ServiceResult[ContractResponse]:\n        """\n        Execute a contract.\n        \n        Args:\n            contract_id: ID of the contract\n            user_id: ID of the user executing\n            execution_date: Date of execution\n            effective_date: Date when contract becomes effective\n            document_id: ID of the executed document\n            \n        Returns:\n            ServiceResult containing the updated contract\n        """\n        try:\n            contract = await self.repository.find_contract_by_id(contract_id)\n            if not contract:\n                return ServiceResult.error(\n                    NotFoundError(f"Contract with ID {contract_id} not found")\n                )\n            \n            if contract["status"] not in [ContractStatus.PENDING_EXECUTION.value, ContractStatus.APPROVED.value]:\n                return ServiceResult.error(\n                    ConflictError("Contract is not ready for execution")\n                )\n            \n            exec_date = execution_date or datetime.utcnow()\n            effect_date = effective_date or contract.get("start_date") or exec_date\n            \n            updated_contract = await self.repository.update_contract(contract_id, {\n                "status": ContractStatus.EXECUTED.value,\n                "executed_at": exec_date,\n                "executed_by": user_id,\n                "effective_date": effect_date,\n                "executed_document_id": document_id,\n            })\n            \n            # Activate obligations\n            await self._activate_contract_obligations(contract_id, effect_date)\n            \n            # Create version for executed state\n            await self._create_contract_version(\n                contract_id,\n                {"status": ContractStatus.EXECUTED.value},\n                user_id,\n                "Executed contract"\n            )\n            \n            await cache_invalidate(f"contract:{contract_id}")\n            \n            return ServiceResult.success(\n                ContractResponse(**updated_contract),\n                message="Contract executed successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to execute contract: {str(e)}"))\n    \n    @requires_permission("legal:contract:update")\n    @transactional\n    @emit_event("legal.contract.amended")\n    async def create_amendment(\n        self,\n        contract_id: str,\n        data: ContractAmendmentCreate,\n        user_id: str,\n    ) -> ServiceResult[ContractAmendmentResponse]:\n        """\n        Create a contract amendment.\n        \n        Args:\n            contract_id: ID of the contract to amend\n            data: Amendment data\n            user_id: ID of the user creating the amendment\n            \n        Returns:\n            ServiceResult containing the amendment\n        """\n        try:\n            contract = await self.repository.find_contract_by_id(contract_id)\n            if not contract:\n                return ServiceResult.error(\n                    NotFoundError(f"Contract with ID {contract_id} not found")\n                )\n            \n            if contract["status"] != ContractStatus.EXECUTED.value:\n                return ServiceResult.error(\n                    ConflictError("Can only amend executed contracts")\n                )\n            \n            # Generate amendment number\n            amendment_count = await self.repository.count_contract_amendments(contract_id)\n            amendment_number = f"{contract[\'contract_number\']}-AMD-{amendment_count + 1}"\n            \n            amendment_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "contract_id": contract_id,\n                "amendment_number": amendment_number,\n                "title": data.title,\n                "description": data.description,\n                "changes": data.changes,\n                "status": "draft",\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n            }\n            \n            amendment = await self.repository.create_contract_amendment(amendment_data)\n            \n            # Update contract status\n            await self.repository.update_contract(contract_id, {\n                "status": ContractStatus.AMENDMENT_PENDING.value,\n                "pending_amendment_id": amendment["id"],\n            })\n            \n            return ServiceResult.success(\n                ContractAmendmentResponse(**amendment),\n                message="Amendment created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create amendment: {str(e)}"))\n    \n    @requires_permission("legal:contract:update")\n    @transactional\n    async def execute_amendment(\n        self,\n        amendment_id: str,\n        user_id: str,\n    ) -> ServiceResult[ContractAmendmentResponse]:\n        """\n        Execute a contract amendment.\n        \n        Args:\n            amendment_id: ID of the amendment\n            user_id: ID of the user executing\n            \n        Returns:\n            ServiceResult containing the executed amendment\n        """\n        try:\n            amendment = await self.repository.find_amendment_by_id(amendment_id)\n            if not amendment:\n                return ServiceResult.error(\n                    NotFoundError(f"Amendment with ID {amendment_id} not found")\n                )\n            \n            # Update amendment\n            updated_amendment = await self.repository.update_amendment(amendment_id, {\n                "status": "executed",\n                "executed_at": datetime.utcnow(),\n                "executed_by": user_id,\n            })\n            \n            # Apply changes to contract\n            await self._apply_amendment_changes(amendment["contract_id"], amendment["changes"])\n            \n            # Update contract status\n            await self.repository.update_contract(amendment["contract_id"], {\n                "status": ContractStatus.EXECUTED.value,\n                "pending_amendment_id": None,\n                "last_amended_at": datetime.utcnow(),\n                "amendment_count": amendment.get("amendment_count", 0) + 1,\n            })\n            \n            # Create version\n            await self._create_contract_version(\n                amendment["contract_id"],\n                amendment["changes"],\n                user_id,\n                f"Amendment {amendment[\'amendment_number\']}"\n            )\n            \n            return ServiceResult.success(\n                ContractAmendmentResponse(**updated_amendment),\n                message="Amendment executed successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to execute amendment: {str(e)}"))\n    \n    @requires_permission("legal:contract:update")\n    @transactional\n    @emit_event("legal.contract.terminated")\n    async def terminate_contract(\n        self,\n        contract_id: str,\n        user_id: str,\n        termination_date: datetime,\n        reason: str,\n        notes: Optional[str] = None,\n    ) -> ServiceResult[ContractResponse]:\n        """\n        Terminate a contract.\n        \n        Args:\n            contract_id: ID of the contract\n            user_id: ID of the user terminating\n            termination_date: Date of termination\n            reason: Reason for termination\n            notes: Additional notes\n            \n        Returns:\n            ServiceResult containing the terminated contract\n        """\n        try:\n            contract = await self.repository.find_contract_by_id(contract_id)\n            if not contract:\n                return ServiceResult.error(\n                    NotFoundError(f"Contract with ID {contract_id} not found")\n                )\n            \n            if contract["status"] == ContractStatus.TERMINATED.value:\n                return ServiceResult.error(\n                    ConflictError("Contract is already terminated")\n                )\n            \n            updated_contract = await self.repository.update_contract(contract_id, {\n                "status": ContractStatus.TERMINATED.value,\n                "terminated_at": termination_date,\n                "terminated_by": user_id,\n                "termination_reason": reason,\n                "termination_notes": notes,\n            })\n            \n            # Deactivate future obligations\n            await self._deactivate_obligations_after_date(contract_id, termination_date)\n            \n            await cache_invalidate(f"contract:{contract_id}")\n            \n            return ServiceResult.success(\n                ContractResponse(**updated_contract),\n                message="Contract terminated"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to terminate contract: {str(e)}"))\n    \n    @requires_permission("legal:contract:read")\n    async def get_contract_versions(\n        self,\n        contract_id: str,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[List[ContractVersionResponse]]:\n        """\n        Get version history for a contract.\n        \n        Args:\n            contract_id: ID of the contract\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing version history\n        """\n        try:\n            versions = await self.repository.find_contract_versions(\n                contract_id,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success(\n                [ContractVersionResponse(**v) for v in versions]\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get contract versions: {str(e)}"))\n    \n    # ==================== Legal Matter Management ====================\n    \n    @requires_permission("legal:matter:create")\n    @transactional\n    @emit_event("legal.matter.created")\n    async def create_matter(\n        self,\n        data: MatterCreate,\n        user_id: str,\n    ) -> ServiceResult[MatterResponse]:\n        """\n        Create a new legal matter.\n        \n        Args:\n            data: Matter creation data\n            user_id: ID of the user creating the matter\n            \n        Returns:\n            ServiceResult containing the created matter\n        """\n        try:\n            # Generate matter number\n            matter_number = await self._generate_matter_number(data.matter_type)\n            \n            matter_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "matter_number": matter_number,\n                "title": data.title,\n                "description": data.description,\n                "matter_type": data.matter_type,\n                "status": MatterStatus.NEW.value,\n                "priority": data.priority or MatterPriority.MEDIUM.value,\n                "lead_attorney_id": data.lead_attorney_id,\n                "assigned_attorney_ids": data.assigned_attorney_ids or [],\n                "client_contact_id": data.client_contact_id,\n                "related_contract_ids": data.related_contract_ids or [],\n                "opposing_party": data.opposing_party,\n                "opposing_counsel": data.opposing_counsel,\n                "court_or_tribunal": data.court_or_tribunal,\n                "case_number": data.case_number,\n                "filing_date": data.filing_date,\n                "estimated_resolution_date": data.estimated_resolution_date,\n                "budget": float(data.budget) if data.budget else None,\n                "currency": data.currency or "USD",\n                "billable": data.billable or True,\n                "billing_code": data.billing_code,\n                "tags": data.tags or [],\n                "custom_fields": data.custom_fields or {},\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n                "updated_at": datetime.utcnow(),\n            }\n            \n            matter = await self.repository.create_matter(matter_data)\n            \n            # Create default task list from template\n            if data.use_default_tasks:\n                await self._create_default_matter_tasks(matter["id"], data.matter_type, user_id)\n            \n            return ServiceResult.success(\n                MatterResponse(**matter),\n                message="Matter created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create matter: {str(e)}"))\n    \n    @requires_permission("legal:matter:read")\n    @cached(key_prefix="matter", ttl=300)\n    async def get_matter(\n        self,\n        matter_id: str,\n    ) -> ServiceResult[MatterResponse]:\n        """\n        Get a legal matter by ID.\n        \n        Args:\n            matter_id: ID of the matter\n            \n        Returns:\n            ServiceResult containing the matter\n        """\n        try:\n            matter = await self.repository.find_matter_by_id(matter_id)\n            if not matter:\n                return ServiceResult.error(\n                    NotFoundError(f"Matter with ID {matter_id} not found")\n                )\n            \n            return ServiceResult.success(MatterResponse(**matter))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get matter: {str(e)}"))\n    \n    @requires_permission("legal:matter:read")\n    async def list_matters(\n        self,\n        matter_type: Optional[str] = None,\n        status: Optional[str] = None,\n        priority: Optional[str] = None,\n        lead_attorney_id: Optional[str] = None,\n        client_contact_id: Optional[str] = None,\n        search: Optional[str] = None,\n        tags: Optional[List[str]] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[MatterListResponse]:\n        """\n        List legal matters with filtering and pagination.\n        \n        Args:\n            matter_type: Filter by matter type\n            status: Filter by status\n            priority: Filter by priority\n            lead_attorney_id: Filter by lead attorney\n            client_contact_id: Filter by client contact\n            search: Search in title and description\n            tags: Filter by tags\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated matters\n        """\n        try:\n            filters = {}\n            if matter_type:\n                filters["matter_type"] = matter_type\n            if status:\n                filters["status"] = status\n            if priority:\n                filters["priority"] = priority\n            if lead_attorney_id:\n                filters["lead_attorney_id"] = lead_attorney_id\n            if client_contact_id:\n                filters["client_contact_id"] = client_contact_id\n            \n            result = await self.repository.find_matters_paginated(\n                filters=filters,\n                search=search,\n                search_fields=["title", "description", "matter_number"],\n                tags=tags,\n                page=page,\n                page_size=page_size,\n                order_by="created_at",\n                order_desc=True,\n            )\n            \n            return ServiceResult.success(\n                MatterListResponse(\n                    items=[MatterResponse(**m) for m in result["items"]],\n                    total=result["total"],\n                    page=page,\n                    page_size=page_size,\n                    total_pages=result["total_pages"],\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list matters: {str(e)}"))\n    \n    @requires_permission("legal:matter:update")\n    @transactional\n    @emit_event("legal.matter.updated")\n    async def update_matter(\n        self,\n        matter_id: str,\n        data: MatterUpdate,\n        user_id: str,\n    ) -> ServiceResult[MatterResponse]:\n        """\n        Update a legal matter.\n        \n        Args:\n            matter_id: ID of the matter to update\n            data: Update data\n            user_id: ID of the user updating\n            \n        Returns:\n            ServiceResult containing the updated matter\n        """\n        try:\n            matter = await self.repository.find_matter_by_id(matter_id)\n            if not matter:\n                return ServiceResult.error(\n                    NotFoundError(f"Matter with ID {matter_id} not found")\n                )\n            \n            update_data = data.model_dump(exclude_unset=True)\n            update_data["updated_at"] = datetime.utcnow()\n            update_data["updated_by"] = user_id\n            \n            updated_matter = await self.repository.update_matter(matter_id, update_data)\n            \n            await cache_invalidate(f"matter:{matter_id}")\n            \n            return ServiceResult.success(\n                MatterResponse(**updated_matter),\n                message="Matter updated successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to update matter: {str(e)}"))\n    \n    @requires_permission("legal:matter:update")\n    @transactional\n    @emit_event("legal.matter.status_changed")\n    async def update_matter_status(\n        self,\n        matter_id: str,\n        new_status: str,\n        user_id: str,\n        notes: Optional[str] = None,\n    ) -> ServiceResult[MatterResponse]:\n        """\n        Update matter status with status transition tracking.\n        \n        Args:\n            matter_id: ID of the matter\n            new_status: New status value\n            user_id: ID of the user changing status\n            notes: Status change notes\n            \n        Returns:\n            ServiceResult containing the updated matter\n        """\n        try:\n            matter = await self.repository.find_matter_by_id(matter_id)\n            if not matter:\n                return ServiceResult.error(\n                    NotFoundError(f"Matter with ID {matter_id} not found")\n                )\n            \n            old_status = matter["status"]\n            \n            # Validate status transition\n            if not self._is_valid_status_transition(old_status, new_status, "matter"):\n                return ServiceResult.error(\n                    ValidationError(f"Invalid status transition from {old_status} to {new_status}")\n                )\n            \n            # Record status change\n            status_change_data = {\n                "id": str(uuid.uuid4()),\n                "matter_id": matter_id,\n                "old_status": old_status,\n                "new_status": new_status,\n                "changed_by": user_id,\n                "changed_at": datetime.utcnow(),\n                "notes": notes,\n            }\n            await self.repository.create_matter_status_change(status_change_data)\n            \n            # Update matter\n            update_data = {\n                "status": new_status,\n                "updated_at": datetime.utcnow(),\n            }\n            \n            # Handle status-specific updates\n            if new_status == MatterStatus.CLOSED.value:\n                update_data["closed_at"] = datetime.utcnow()\n                update_data["closed_by"] = user_id\n            elif new_status == MatterStatus.SETTLED.value:\n                update_data["settled_at"] = datetime.utcnow()\n            \n            updated_matter = await self.repository.update_matter(matter_id, update_data)\n            \n            await cache_invalidate(f"matter:{matter_id}")\n            \n            return ServiceResult.success(\n                MatterResponse(**updated_matter),\n                message=f"Matter status updated to {new_status}"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to update matter status: {str(e)}"))\n    \n    @requires_permission("legal:matter:update")\n    @transactional\n    async def create_matter_task(\n        self,\n        matter_id: str,\n        data: MatterTaskCreate,\n        user_id: str,\n    ) -> ServiceResult[MatterTaskResponse]:\n        """\n        Create a task for a legal matter.\n        \n        Args:\n            matter_id: ID of the matter\n            data: Task creation data\n            user_id: ID of the user creating the task\n            \n        Returns:\n            ServiceResult containing the created task\n        """\n        try:\n            matter = await self.repository.find_matter_by_id(matter_id)\n            if not matter:\n                return ServiceResult.error(\n                    NotFoundError(f"Matter with ID {matter_id} not found")\n                )\n            \n            task_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "matter_id": matter_id,\n                "title": data.title,\n                "description": data.description,\n                "task_type": data.task_type,\n                "status": "pending",\n                "priority": data.priority or "medium",\n                "assigned_to": data.assigned_to,\n                "due_date": data.due_date,\n                "estimated_hours": data.estimated_hours,\n                "billable": data.billable if data.billable is not None else matter.get("billable", True),\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n            }\n            \n            task = await self.repository.create_matter_task(task_data)\n            \n            return ServiceResult.success(\n                MatterTaskResponse(**task),\n                message="Task created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create matter task: {str(e)}"))\n    \n    @requires_permission("legal:matter:update")\n    @transactional\n    async def update_matter_task(\n        self,\n        task_id: str,\n        data: MatterTaskUpdate,\n        user_id: str,\n    ) -> ServiceResult[MatterTaskResponse]:\n        """\n        Update a matter task.\n        \n        Args:\n            task_id: ID of the task\n            data: Update data\n            user_id: ID of the user updating\n            \n        Returns:\n            ServiceResult containing the updated task\n        """\n        try:\n            task = await self.repository.find_matter_task_by_id(task_id)\n            if not task:\n                return ServiceResult.error(\n                    NotFoundError(f"Task with ID {task_id} not found")\n                )\n            \n            update_data = data.model_dump(exclude_unset=True)\n            update_data["updated_at"] = datetime.utcnow()\n            \n            if data.status == "completed":\n                update_data["completed_at"] = datetime.utcnow()\n                update_data["completed_by"] = user_id\n            \n            updated_task = await self.repository.update_matter_task(task_id, update_data)\n            \n            return ServiceResult.success(\n                MatterTaskResponse(**updated_task),\n                message="Task updated successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to update matter task: {str(e)}"))\n    \n    @requires_permission("legal:matter:read")\n    async def get_matter_tasks(\n        self,\n        matter_id: str,\n        status: Optional[str] = None,\n        assigned_to: Optional[str] = None,\n        page: int = 1,\n        page_size: int = 50,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Get tasks for a legal matter.\n        \n        Args:\n            matter_id: ID of the matter\n            status: Filter by status\n            assigned_to: Filter by assignee\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing tasks\n        """\n        try:\n            filters = {"matter_id": matter_id}\n            if status:\n                filters["status"] = status\n            if assigned_to:\n                filters["assigned_to"] = assigned_to\n            \n            result = await self.repository.find_matter_tasks_paginated(\n                filters=filters,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success({\n                "items": [MatterTaskResponse(**t) for t in result["items"]],\n                "total": result["total"],\n                "page": page,\n                "page_size": page_size,\n            })\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get matter tasks: {str(e)}"))\n    \n    @requires_permission("legal:matter:read")\n    async def get_matter_timeline(\n        self,\n        matter_id: str,\n    ) -> ServiceResult[List[Dict[str, Any]]]:\n        """\n        Get timeline of events for a matter.\n        \n        Args:\n            matter_id: ID of the matter\n            \n        Returns:\n            ServiceResult containing timeline events\n        """\n        try:\n            timeline = await self.repository.find_matter_timeline(matter_id)\n            return ServiceResult.success(timeline)\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get matter timeline: {str(e)}"))\n    \n    # ==================== Compliance Management ====================\n    \n    @requires_permission("legal:compliance:create")\n    @transactional\n    @emit_event("legal.compliance.framework_created")\n    async def create_compliance_framework(\n        self,\n        data: ComplianceFrameworkCreate,\n        user_id: str,\n    ) -> ServiceResult[ComplianceFrameworkResponse]:\n        """\n        Create a compliance framework.\n        \n        Args:\n            data: Framework creation data\n            user_id: ID of the user creating\n            \n        Returns:\n            ServiceResult containing the created framework\n        """\n        try:\n            framework_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "name": data.name,\n                "code": data.code,\n                "description": data.description,\n                "jurisdiction": data.jurisdiction,\n                "regulatory_body": data.regulatory_body,\n                "effective_date": data.effective_date,\n                "review_frequency": data.review_frequency or "annual",\n                "requirements": [r.model_dump() for r in data.requirements] if data.requirements else [],\n                "controls": [c.model_dump() for c in data.controls] if data.controls else [],\n                "tags": data.tags or [],\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n                "updated_at": datetime.utcnow(),\n            }\n            \n            framework = await self.repository.create_compliance_framework(framework_data)\n            \n            # Create individual requirements\n            for req_data in (data.requirements or []):\n                await self._create_compliance_requirement(framework["id"], req_data, user_id)\n            \n            return ServiceResult.success(\n                ComplianceFrameworkResponse(**framework),\n                message="Compliance framework created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create compliance framework: {str(e)}"))\n    \n    @requires_permission("legal:compliance:read")\n    @cached(key_prefix="compliance_framework", ttl=300)\n    async def get_compliance_framework(\n        self,\n        framework_id: str,\n    ) -> ServiceResult[ComplianceFrameworkResponse]:\n        """\n        Get a compliance framework by ID.\n        \n        Args:\n            framework_id: ID of the framework\n            \n        Returns:\n            ServiceResult containing the framework\n        """\n        try:\n            framework = await self.repository.find_compliance_framework_by_id(framework_id)\n            if not framework:\n                return ServiceResult.error(\n                    NotFoundError(f"Framework with ID {framework_id} not found")\n                )\n            \n            return ServiceResult.success(ComplianceFrameworkResponse(**framework))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get compliance framework: {str(e)}"))\n    \n    @requires_permission("legal:compliance:read")\n    async def list_compliance_frameworks(\n        self,\n        jurisdiction: Optional[str] = None,\n        regulatory_body: Optional[str] = None,\n        is_active: Optional[bool] = None,\n        search: Optional[str] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[ComplianceFrameworkListResponse]:\n        """\n        List compliance frameworks.\n        \n        Args:\n            jurisdiction: Filter by jurisdiction\n            regulatory_body: Filter by regulatory body\n            is_active: Filter by active status\n            search: Search in name and description\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated frameworks\n        """\n        try:\n            filters = {}\n            if jurisdiction:\n                filters["jurisdiction"] = jurisdiction\n            if regulatory_body:\n                filters["regulatory_body"] = regulatory_body\n            if is_active is not None:\n                filters["is_active"] = is_active\n            \n            result = await self.repository.find_compliance_frameworks_paginated(\n                filters=filters,\n                search=search,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success(\n                ComplianceFrameworkListResponse(\n                    items=[ComplianceFrameworkResponse(**f) for f in result["items"]],\n                    total=result["total"],\n                    page=page,\n                    page_size=page_size,\n                    total_pages=result["total_pages"],\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list compliance frameworks: {str(e)}"))\n    \n    @requires_permission("legal:compliance:update")\n    @transactional\n    async def create_compliance_assessment(\n        self,\n        data: ComplianceAssessmentCreate,\n        user_id: str,\n    ) -> ServiceResult[ComplianceAssessmentResponse]:\n        """\n        Create a compliance assessment.\n        \n        Args:\n            data: Assessment creation data\n            user_id: ID of the user creating\n            \n        Returns:\n            ServiceResult containing the assessment\n        """\n        try:\n            # Calculate compliance score\n            score = self._calculate_compliance_score(data.responses)\n            \n            assessment_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "framework_id": data.framework_id,\n                "requirement_id": data.requirement_id,\n                "assessment_type": data.assessment_type,\n                "assessor_id": user_id,\n                "assessment_date": data.assessment_date or datetime.utcnow(),\n                "responses": [r.model_dump() for r in data.responses] if data.responses else [],\n                "score": score,\n                "status": data.status or "draft",\n                "findings": data.findings or [],\n                "recommendations": data.recommendations or [],\n                "evidence_ids": data.evidence_ids or [],\n                "created_at": datetime.utcnow(),\n            }\n            \n            assessment = await self.repository.create_compliance_assessment(assessment_data)\n            \n            # Update requirement compliance status\n            if score >= 80:\n                compliance_status = ComplianceStatus.COMPLIANT.value\n            elif score >= 60:\n                compliance_status = ComplianceStatus.PARTIALLY_COMPLIANT.value\n            else:\n                compliance_status = ComplianceStatus.NON_COMPLIANT.value\n            \n            await self.repository.update_compliance_requirement(\n                data.requirement_id,\n                {\n                    "compliance_status": compliance_status,\n                    "last_assessment_id": assessment["id"],\n                    "last_assessment_date": assessment["assessment_date"],\n                    "last_assessment_score": score,\n                }\n            )\n            \n            return ServiceResult.success(\n                ComplianceAssessmentResponse(**assessment),\n                message="Assessment created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create compliance assessment: {str(e)}"))\n    \n    @requires_permission("legal:compliance:read")\n    async def get_compliance_dashboard(\n        self,\n        framework_ids: Optional[List[str]] = None,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Get compliance dashboard data.\n        \n        Args:\n            framework_ids: Optional list of framework IDs to include\n            \n        Returns:\n            ServiceResult containing dashboard data\n        """\n        try:\n            dashboard_data = await self.repository.get_compliance_dashboard(\n                company_id=self.company_id,\n                framework_ids=framework_ids,\n            )\n            \n            return ServiceResult.success(dashboard_data)\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get compliance dashboard: {str(e)}"))\n    \n    @requires_permission("legal:compliance:read")\n    async def get_compliance_gaps(\n        self,\n        framework_id: str,\n    ) -> ServiceResult[List[Dict[str, Any]]]:\n        """\n        Get compliance gaps for a framework.\n        \n        Args:\n            framework_id: ID of the framework\n            \n        Returns:\n            ServiceResult containing gap analysis\n        """\n        try:\n            framework = await self.repository.find_compliance_framework_by_id(framework_id)\n            if not framework:\n                return ServiceResult.error(\n                    NotFoundError(f"Framework with ID {framework_id} not found")\n                )\n            \n            gaps = await self.repository.find_compliance_gaps(framework_id)\n            \n            return ServiceResult.success(gaps)\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get compliance gaps: {str(e)}"))\n    \n    # ==================== Legal Entity Management ====================\n    \n    @requires_permission("legal:entity:create")\n    @transactional\n    @emit_event("legal.entity.created")\n    async def create_legal_entity(\n        self,\n        data: LegalEntityCreate,\n        user_id: str,\n    ) -> ServiceResult[LegalEntityResponse]:\n        """\n        Create a legal entity.\n        \n        Args:\n            data: Entity creation data\n            user_id: ID of the user creating\n            \n        Returns:\n            ServiceResult containing the created entity\n        """\n        try:\n            entity_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "name": data.name,\n                "entity_type": data.entity_type,\n                "registration_number": data.registration_number,\n                "tax_id": data.tax_id,\n                "jurisdiction": data.jurisdiction,\n                "incorporation_date": data.incorporation_date,\n                "registered_address": data.registered_address,\n                "primary_contact": data.primary_contact,\n                "parent_entity_id": data.parent_entity_id,\n                "is_internal": data.is_internal or False,\n                "is_vendor": data.is_vendor or False,\n                "is_customer": data.is_customer or False,\n                "status": data.status or "active",\n                "tags": data.tags or [],\n                "custom_fields": data.custom_fields or {},\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n                "updated_at": datetime.utcnow(),\n            }\n            \n            entity = await self.repository.create_legal_entity(entity_data)\n            \n            return ServiceResult.success(\n                LegalEntityResponse(**entity),\n                message="Legal entity created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create legal entity: {str(e)}"))\n    \n    @requires_permission("legal:entity:read")\n    async def list_legal_entities(\n        self,\n        entity_type: Optional[str] = None,\n        is_internal: Optional[bool] = None,\n        is_vendor: Optional[bool] = None,\n        is_customer: Optional[bool] = None,\n        jurisdiction: Optional[str] = None,\n        search: Optional[str] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        List legal entities.\n        \n        Args:\n            entity_type: Filter by entity type\n            is_internal: Filter internal entities\n            is_vendor: Filter vendor entities\n            is_customer: Filter customer entities\n            jurisdiction: Filter by jurisdiction\n            search: Search in name\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated entities\n        """\n        try:\n            filters = {}\n            if entity_type:\n                filters["entity_type"] = entity_type\n            if is_internal is not None:\n                filters["is_internal"] = is_internal\n            if is_vendor is not None:\n                filters["is_vendor"] = is_vendor\n            if is_customer is not None:\n                filters["is_customer"] = is_customer\n            if jurisdiction:\n                filters["jurisdiction"] = jurisdiction\n            \n            result = await self.repository.find_legal_entities_paginated(\n                filters=filters,\n                search=search,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success({\n                "items": [LegalEntityResponse(**e) for e in result["items"]],\n                "total": result["total"],\n                "page": page,\n                "page_size": page_size,\n                "total_pages": result["total_pages"],\n            })\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list legal entities: {str(e)}"))\n    \n    # ==================== Obligation Management ====================\n    \n    @requires_permission("legal:obligation:create")\n    @transactional\n    @emit_event("legal.obligation.created")\n    async def create_obligation(\n        self,\n        data: ObligationCreate,\n        user_id: str,\n    ) -> ServiceResult[ObligationResponse]:\n        """\n        Create an obligation.\n        \n        Args:\n            data: Obligation creation data\n            user_id: ID of the user creating\n            \n        Returns:\n            ServiceResult containing the created obligation\n        """\n        try:\n            # Validate contract exists if provided\n            if data.contract_id:\n                contract = await self.repository.find_contract_by_id(data.contract_id)\n                if not contract:\n                    return ServiceResult.error(\n                        NotFoundError(f"Contract with ID {data.contract_id} not found")\n                    )\n            \n            obligation_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "contract_id": data.contract_id,\n                "title": data.title,\n                "description": data.description,\n                "obligation_type": data.obligation_type,\n                "responsible_party": data.responsible_party,\n                "owner_id": data.owner_id,\n                "start_date": data.start_date,\n                "end_date": data.end_date,\n                "frequency": data.frequency,\n                "next_due_date": data.next_due_date or data.start_date,\n                "status": "pending",\n                "priority": data.priority or "medium",\n                "documentation_required": data.documentation_required or False,\n                "evidence_ids": [],\n                "tags": data.tags or [],\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n            }\n            \n            obligation = await self.repository.create_obligation(obligation_data)\n            \n            # Create calendar event/alert\n            if data.next_due_date:\n                await self._create_obligation_alert(obligation)\n            \n            return ServiceResult.success(\n                ObligationResponse(**obligation),\n                message="Obligation created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create obligation: {str(e)}"))\n    \n    @requires_permission("legal:obligation:read")\n    async def list_obligations(\n        self,\n        contract_id: Optional[str] = None,\n        obligation_type: Optional[str] = None,\n        owner_id: Optional[str] = None,\n        status: Optional[str] = None,\n        due_within_days: Optional[int] = None,\n        overdue: Optional[bool] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        List obligations with filtering.\n        \n        Args:\n            contract_id: Filter by contract\n            obligation_type: Filter by type\n            owner_id: Filter by owner\n            status: Filter by status\n            due_within_days: Due within specified days\n            overdue: Filter overdue obligations\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated obligations\n        """\n        try:\n            filters = {}\n            if contract_id:\n                filters["contract_id"] = contract_id\n            if obligation_type:\n                filters["obligation_type"] = obligation_type\n            if owner_id:\n                filters["owner_id"] = owner_id\n            if status:\n                filters["status"] = status\n            \n            date_filters = {}\n            if due_within_days:\n                date_filters["due_within_days"] = due_within_days\n            \n            if overdue:\n                date_filters["overdue"] = True\n            \n            result = await self.repository.find_obligations_paginated(\n                filters=filters,\n                date_filters=date_filters,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success({\n                "items": [ObligationResponse(**o) for o in result["items"]],\n                "total": result["total"],\n                "page": page,\n                "page_size": page_size,\n                "total_pages": result["total_pages"],\n            })\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list obligations: {str(e)}"))\n    \n    @requires_permission("legal:obligation:update")\n    @transactional\n    @emit_event("legal.obligation.fulfilled")\n    async def fulfill_obligation(\n        self,\n        obligation_id: str,\n        user_id: str,\n        fulfillment_date: Optional[datetime] = None,\n        evidence_ids: Optional[List[str]] = None,\n        notes: Optional[str] = None,\n    ) -> ServiceResult[ObligationResponse]:\n        """\n        Mark an obligation as fulfilled.\n        \n        Args:\n            obligation_id: ID of the obligation\n            user_id: ID of the user fulfilling\n            fulfillment_date: Date of fulfillment\n            evidence_ids: IDs of evidence documents\n            notes: Fulfillment notes\n            \n        Returns:\n            ServiceResult containing the updated obligation\n        """\n        try:\n            obligation = await self.repository.find_obligation_by_id(obligation_id)\n            if not obligation:\n                return ServiceResult.error(\n                    NotFoundError(f"Obligation with ID {obligation_id} not found")\n                )\n            \n            fulfill_date = fulfillment_date or datetime.utcnow()\n            \n            updated_obligation = await self.repository.update_obligation(obligation_id, {\n                "status": "fulfilled",\n                "fulfilled_at": fulfill_date,\n                "fulfilled_by": user_id,\n                "fulfillment_notes": notes,\n                "evidence_ids": evidence_ids or [],\n            })\n            \n            # If recurring, create next occurrence\n            if obligation.get("frequency") and obligation.get("frequency") != "one_time":\n                await self._create_next_obligation_occurrence(obligation, fulfill_date)\n            \n            return ServiceResult.success(\n                ObligationResponse(**updated_obligation),\n                message="Obligation fulfilled"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to fulfill obligation: {str(e)}"))\n    \n    # ==================== Document Management ====================\n    \n    @requires_permission("legal:document:create")\n    @transactional\n    async def create_document(\n        self,\n        data: DocumentCreate,\n        user_id: str,\n    ) -> ServiceResult[DocumentResponse]:\n        """\n        Create a legal document record.\n        \n        Args:\n            data: Document creation data\n            user_id: ID of the user creating\n            \n        Returns:\n            ServiceResult containing the created document\n        """\n        try:\n            document_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "title": data.title,\n                "document_type": data.document_type,\n                "description": data.description,\n                "file_path": data.file_path,\n                "file_name": data.file_name,\n                "file_size": data.file_size,\n                "mime_type": data.mime_type,\n                "contract_id": data.contract_id,\n                "matter_id": data.matter_id,\n                "version": 1,\n                "is_confidential": data.is_confidential or False,\n                "retention_years": data.retention_years or 7,\n                "tags": data.tags or [],\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n            }\n            \n            document = await self.repository.create_document(document_data)\n            \n            return ServiceResult.success(\n                DocumentResponse(**document),\n                message="Document created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create document: {str(e)}"))\n    \n    @requires_permission("legal:document:read")\n    async def list_documents(\n        self,\n        document_type: Optional[str] = None,\n        contract_id: Optional[str] = None,\n        matter_id: Optional[str] = None,\n        search: Optional[str] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        List legal documents.\n        \n        Args:\n            document_type: Filter by type\n            contract_id: Filter by contract\n            matter_id: Filter by matter\n            search: Search in title and description\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated documents\n        """\n        try:\n            filters = {}\n            if document_type:\n                filters["document_type"] = document_type\n            if contract_id:\n                filters["contract_id"] = contract_id\n            if matter_id:\n                filters["matter_id"] = matter_id\n            \n            result = await self.repository.find_documents_paginated(\n                filters=filters,\n                search=search,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success({\n                "items": [DocumentResponse(**d) for d in result["items"]],\n                "total": result["total"],\n                "page": page,\n                "page_size": page_size,\n                "total_pages": result["total_pages"],\n            })\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list documents: {str(e)}"))\n    \n    # ==================== Analytics and Reports ====================\n    \n    @requires_permission("legal:read")\n    async def get_legal_dashboard(\n        self,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Get legal operations dashboard data.\n        \n        Returns:\n            ServiceResult containing dashboard data\n        """\n        try:\n            dashboard_data = {\n                "contracts": await self._get_contract_summary(),\n                "matters": await self._get_matter_summary(),\n                "compliance": await self._get_compliance_summary(),\n                "obligations": await self._get_obligation_summary(),\n                "upcoming_dates": await self._get_upcoming_legal_dates(),\n            }\n            \n            return ServiceResult.success(dashboard_data)\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get legal dashboard: {str(e)}"))\n    \n    @requires_permission("legal:read")\n    async def get_contract_analytics(\n        self,\n        start_date: Optional[datetime] = None,\n        end_date: Optional[datetime] = None,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Get contract analytics.\n        \n        Args:\n            start_date: Start date for analysis\n            end_date: End date for analysis\n            \n        Returns:\n            ServiceResult containing analytics\n        """\n        try:\n            analytics = await self.repository.get_contract_analytics(\n                company_id=self.company_id,\n                start_date=start_date,\n                end_date=end_date,\n            )\n            \n            return ServiceResult.success(analytics)\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get contract analytics: {str(e)}"))\n    \n    # ==================== Private Helper Methods ====================\n    \n    async def _generate_contract_number(self, contract_type: str) -> str:\n        """Generate a unique contract number."""\n        prefix = contract_type[:3].upper()\n        count = await self.repository.count_contracts_by_type(contract_type)\n        return f"{prefix}-{datetime.utcnow().year}-{count + 1:05d}"\n    \n    async def _generate_matter_number(self, matter_type: str) -> str:\n        """Generate a unique matter number."""\n        prefix = matter_type[:3].upper()\n        count = await self.repository.count_matters_by_type(matter_type)\n        return f"MAT-{prefix}-{datetime.utcnow().year}-{count + 1:05d}"\n    \n    async def _create_contract_version(\n        self,\n        contract_id: str,\n        changes: Dict[str, Any],\n        user_id: str,\n        note: str,\n    ) -> None:\n        """Create a contract version record."""\n        contract = await self.repository.find_contract_by_id(contract_id)\n        if not contract:\n            return\n        \n        version_data = {\n            "id": str(uuid.uuid4()),\n            "contract_id": contract_id,\n            "version": contract.get("version", 1) + 1,\n            "changes": changes,\n            "note": note,\n            "created_by": user_id,\n            "created_at": datetime.utcnow(),\n        }\n        \n        await self.repository.create_contract_version(version_data)\n        await self.repository.update_contract(contract_id, {\n            "version": version_data["version"],\n        })\n    \n    async def _create_review_task(\n        self,\n        contract_id: str,\n        reviewer_id: str,\n        requester_id: str,\n        due_date: Optional[datetime],\n        notes: Optional[str],\n    ) -> None:\n        """Create a contract review task."""\n        task_data = {\n            "id": str(uuid.uuid4()),\n            "company_id": self.company_id,\n            "task_type": "contract_review",\n            "reference_id": contract_id,\n            "reference_type": "contract",\n            "assigned_to": reviewer_id,\n            "assigned_by": requester_id,\n            "due_date": due_date,\n            "description": notes,\n            "status": "pending",\n            "created_at": datetime.utcnow(),\n        }\n        \n        await self.repository.create_task(task_data)\n    \n    async def _create_contract_alerts(self, contract: Dict[str, Any]) -> None:\n        """Create alerts for key contract dates."""\n        alert_configs = []\n        \n        # End date alert\n        if contract.get("end_date"):\n            alert_configs.append({\n                "alert_type": "contract_expiry",\n                "alert_date": contract["end_date"],\n                "advance_days": [90, 60, 30, 14],\n            })\n        \n        # Renewal notice alert\n        if contract.get("auto_renew") and contract.get("renewal_notice_days"):\n            renewal_date = contract["end_date"]\n            notice_date = renewal_date - timedelta(days=contract["renewal_notice_days"])\n            alert_configs.append({\n                "alert_type": "renewal_notice",\n                "alert_date": notice_date,\n            })\n        \n        for config in alert_configs:\n            await self.repository.create_alert({\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "reference_id": contract["id"],\n                "reference_type": "contract",\n                "alert_type": config["alert_type"],\n                "alert_date": config["alert_date"],\n                "advance_days": config.get("advance_days", []),\n                "status": "pending",\n                "created_at": datetime.utcnow(),\n            })\n    \n    async def _update_contract_alerts(self, contract: Dict[str, Any]) -> None:\n        """Update alerts when contract dates change."""\n        # Delete existing alerts and recreate\n        await self.repository.delete_alerts_for_reference(contract["id"], "contract")\n        await self._create_contract_alerts(contract)\n    \n    async def _apply_obligation_template(\n        self,\n        contract_id: str,\n        template_id: str,\n        user_id: str,\n    ) -> None:\n        """Apply obligations from a template to a contract."""\n        template = await self.repository.find_obligation_template(template_id)\n        if not template:\n            return\n        \n        for obligation_template in template.get("obligations", []):\n            await self.create_obligation(\n                ObligationCreate(\n                    contract_id=contract_id,\n                    **obligation_template\n                ),\n                user_id,\n            )\n    \n    async def _activate_contract_obligations(\n        self,\n        contract_id: str,\n        effective_date: datetime,\n    ) -> None:\n        """Activate obligations when contract is executed."""\n        obligations = await self.repository.find_contract_obligations(contract_id)\n        \n        for obligation in obligations:\n            if obligation["status"] == "pending":\n                await self.repository.update_obligation(obligation["id"], {\n                    "status": "active",\n                    "activated_at": effective_date,\n                })\n    \n    async def _deactivate_obligations_after_date(\n        self,\n        contract_id: str,\n        termination_date: datetime,\n    ) -> None:\n        """Deactivate obligations after termination date."""\n        obligations = await self.repository.find_contract_obligations(contract_id)\n        \n        for obligation in obligations:\n            if obligation.get("next_due_date") and obligation["next_due_date"] > termination_date:\n                await self.repository.update_obligation(obligation["id"], {\n                    "status": "terminated",\n                    "terminated_at": termination_date,\n                })\n    \n    async def _apply_amendment_changes(\n        self,\n        contract_id: str,\n        changes: Dict[str, Any],\n    ) -> None:\n        """Apply amendment changes to a contract."""\n        contract = await self.repository.find_contract_by_id(contract_id)\n        if not contract:\n            return\n        \n        # Apply each change\n        for field, value in changes.items():\n            if field in contract:\n                await self.repository.update_contract(contract_id, {field: value})\n    \n    async def _create_default_matter_tasks(\n        self,\n        matter_id: str,\n        matter_type: str,\n        user_id: str,\n    ) -> None:\n        """Create default tasks for a matter based on type."""\n        task_templates = {\n            "litigation": [\n                {"title": "Initial client interview", "task_type": "meeting", "days_offset": 0},\n                {"title": "Document review", "task_type": "document", "days_offset": 7},\n                {"title": "Legal research", "task_type": "research", "days_offset": 7},\n                {"title": "Draft initial pleading", "task_type": "drafting", "days_offset": 14},\n            ],\n            "transaction": [\n                {"title": "Due diligence", "task_type": "review", "days_offset": 0},\n                {"title": "Draft transaction documents", "task_type": "drafting", "days_offset": 14},\n                {"title": "Negotiate terms", "task_type": "negotiation", "days_offset": 21},\n            ],\n            "intellectual_property": [\n                {"title": "IP search", "task_type": "research", "days_offset": 0},\n                {"title": "Prepare application", "task_type": "drafting", "days_offset": 14},\n            ],\n        }\n        \n        templates = task_templates.get(matter_type, [])\n        \n        for template in templates:\n            await self.create_matter_task(\n                matter_id,\n                MatterTaskCreate(\n                    title=template["title"],\n                    task_type=template["task_type"],\n                    due_date=datetime.utcnow() + timedelta(days=template["days_offset"]),\n                ),\n                user_id,\n            )\n    \n    async def _create_compliance_requirement(\n        self,\n        framework_id: str,\n        requirement_data: Dict[str, Any],\n        user_id: str,\n    ) -> None:\n        """Create a compliance requirement from data."""\n        requirement = {\n            "id": str(uuid.uuid4()),\n            "company_id": self.company_id,\n            "framework_id": framework_id,\n            "requirement_id": requirement_data.get("requirement_id"),\n            "title": requirement_data.get("title"),\n            "description": requirement_data.get("description"),\n            "category": requirement_data.get("category"),\n            "compliance_status": ComplianceStatus.NOT_ASSESSED.value,\n            "created_by": user_id,\n            "created_at": datetime.utcnow(),\n        }\n        \n        await self.repository.create_compliance_requirement(requirement)\n    \n    def _calculate_compliance_score(self, responses: List[Any]) -> float:\n        """Calculate compliance score from assessment responses."""\n        if not responses:\n            return 0.0\n        \n        total_points = 0\n        max_points = 0\n        \n        for response in responses:\n            weight = getattr(response, \'weight\', 1) if hasattr(response, \'weight\') else response.get(\'weight\', 1)\n            score = getattr(response, \'score\', 0) if hasattr(response, \'score\') else response.get(\'score\', 0)\n            max_score = getattr(response, \'max_score\', 100) if hasattr(response, \'max_score\') else response.get(\'max_score\', 100)\n            \n            total_points += (score / max_score) * weight\n            max_points += weight\n        \n        if max_points == 0:\n            return 0.0\n        \n        return (total_points / max_points) * 100\n    \n    async def _create_obligation_alert(self, obligation: Dict[str, Any]) -> None:\n        """Create an alert for an obligation."""\n        await self.repository.create_alert({\n            "id": str(uuid.uuid4()),\n            "company_id": self.company_id,\n            "reference_id": obligation["id"],\n            "reference_type": "obligation",\n            "alert_type": "obligation_due",\n            "alert_date": obligation["next_due_date"],\n            "advance_days": [7, 3, 1],\n            "status": "pending",\n            "created_at": datetime.utcnow(),\n        })\n    \n    async def _create_next_obligation_occurrence(\n        self,\n        obligation: Dict[str, Any],\n        fulfillment_date: datetime,\n    ) -> None:\n        """Create next occurrence of recurring obligation."""\n        frequency = obligation.get("frequency")\n        \n        if frequency == "daily":\n            next_date = fulfillment_date + timedelta(days=1)\n        elif frequency == "weekly":\n            next_date = fulfillment_date + timedelta(weeks=1)\n        elif frequency == "monthly":\n            next_date = fulfillment_date + timedelta(days=30)\n        elif frequency == "quarterly":\n            next_date = fulfillment_date + timedelta(days=90)\n        elif frequency == "annually":\n            next_date = fulfillment_date + timedelta(days=365)\n        else:\n            return\n        \n        await self.create_obligation(\n            ObligationCreate(\n                contract_id=obligation.get("contract_id"),\n                title=obligation["title"],\n                description=obligation["description"],\n                obligation_type=obligation["obligation_type"],\n                responsible_party=obligation.get("responsible_party"),\n                owner_id=obligation.get("owner_id"),\n                start_date=fulfillment_date,\n                end_date=obligation.get("end_date"),\n                frequency=frequency,\n                next_due_date=next_date,\n                priority=obligation.get("priority"),\n                documentation_required=obligation.get("documentation_required"),\n                tags=obligation.get("tags", []),\n            ),\n            obligation.get("created_by"),\n        )\n    \n    def _is_valid_status_transition(self, old_status: str, new_status: str, entity_type: str) -> bool:\n        """Validate status transitions."""\n        if entity_type == "matter":\n            valid_transitions = {\n                MatterStatus.NEW.value: [MatterStatus.IN_PROGRESS.value, MatterStatus.CLOSED.value],\n                MatterStatus.IN_PROGRESS.value: [\n                    MatterStatus.ON_HOLD.value,\n                    MatterStatus.SETTLED.value,\n                    MatterStatus.CLOSED.value,\n                ],\n                MatterStatus.ON_HOLD.value: [MatterStatus.IN_PROGRESS.value, MatterStatus.CLOSED.value],\n                MatterStatus.SETTLED.value: [MatterStatus.CLOSED.value],\n            }\n        else:\n            return True  # Allow all transitions for other entity types\n        \n        allowed = valid_transitions.get(old_status, [])\n        return new_status in allowed\n    \n    async def _get_contract_summary(self) -> Dict[str, Any]:\n        """Get contract summary statistics."""\n        return await self.repository.get_contract_summary(self.company_id)\n    \n    async def _get_matter_summary(self) -> Dict[str, Any]:\n        """Get matter summary statistics."""\n        return await self.repository.get_matter_summary(self.company_id)\n    \n    async def _get_compliance_summary(self) -> Dict[str, Any]:\n        """Get compliance summary statistics."""\n        return await self.repository.get_compliance_summary(self.company_id)\n    \n    async def _get_obligation_summary(self) -> Dict[str, Any]:\n        """Get obligation summary statistics."""\n        return await self.repository.get_obligation_summary(self.company_id)\n    \n    async def _get_upcoming_legal_dates(self) -> List[Dict[str, Any]]:\n        """Get upcoming important legal dates."""\n        return await self.repository.get_upcoming_legal_dates(self.company_id)\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/services/legal.py\' created successfully.") </create-file> </tool_result>'}
{'role': 'assistant', 'content': 'Excellent! Legal service created. Now I\'ll continue with the Reports service for templates, executions, and dashboards.\n\n<function_calls>\n<create-file file_path="backend/app/services/reports.py">\n"""\nReports Service - Reporting and Analytics Engine\n\nImplements comprehensive reporting operations including:\n- Report template management\n- Report execution and scheduling\n- Dashboard management\n- Data visualization\n- Export and delivery\n- Report versioning and history\n"""\n\nfrom typing import Optional, List, Dict, Any, Union\nfrom datetime import datetime, timedelta\nfrom decimal import Decimal\nimport uuid\nimport json\nfrom enum import Enum\nimport asyncio\nimport io\nimport csv\nfrom collections import defaultdict\n\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom sqlalchemy import select, update, delete, and_, or_, func, case\nfrom sqlalchemy.orm import selectinload, joinedload\n\nfrom .base import (\n    CompanyScopedService,\n    ServiceResult,\n    ServiceError,\n    ValidationError,\n    NotFoundError,\n    PermissionDeniedError,\n    ConflictError,\n    requires_permission,\n    transactional,\n    emit_event,\n    cached,\n    cache_invalidate,\n)\nfrom ..schemas.reports import (\n    ReportTemplateCreate,\n    ReportTemplateUpdate,\n    ReportTemplateResponse,\n    ReportTemplateListResponse,\n    ReportExecutionCreate,\n    ReportExecutionUpdate,\n    ReportExecutionResponse,\n    ReportExecutionListResponse,\n    DashboardCreate,\n    DashboardUpdate,\n    DashboardResponse,\n    DashboardListResponse,\n    DashboardWidgetCreate,\n    DashboardWidgetResponse,\n    ReportScheduleCreate,\n    ReportScheduleUpdate,\n    ReportScheduleResponse,\n    ReportParameterCreate,\n    ReportParameterResponse,\n    DataSourceCreate,\n    DataSourceResponse,\n    ReportFormat,\n    ReportStatus,\n    ReportType,\n    ScheduleFrequency,\n    WidgetType,\n    AggregationType,\n)\n\n\nclass ReportsService(CompanyScopedService):\n    """\n    Service for managing report templates and executions.\n    \n    Provides comprehensive reporting capabilities including template\n    management, report execution, scheduling, and delivery.\n    """\n    \n    # ==================== Report Template Management ====================\n    \n    @requires_permission("reports:template:create")\n    @transactional\n    @emit_event("reports.template.created")\n    async def create_report_template(\n        self,\n        data: ReportTemplateCreate,\n        user_id: str,\n    ) -> ServiceResult[ReportTemplateResponse]:\n        """\n        Create a new report template.\n        \n        Args:\n            data: Template creation data\n            user_id: ID of the user creating the template\n            \n        Returns:\n            ServiceResult containing the created template\n        """\n        try:\n            # Validate data sources\n            if data.data_source_ids:\n                for ds_id in data.data_source_ids:\n                    ds = await self.repository.find_data_source(ds_id)\n                    if not ds:\n                        return ServiceResult.error(\n                            NotFoundError(f"Data source with ID {ds_id} not found")\n                        )\n            \n            # Generate template code\n            template_code = await self._generate_template_code(data.name)\n            \n            # Parse and validate query\n            if data.query:\n                validation_result = await self._validate_report_query(data.query, data.data_source_ids or [])\n                if not validation_result.get("valid"):\n                    return ServiceResult.error(\n                        ValidationError(f"Invalid query: {validation_result.get(\'error\')}")\n                    )\n            \n            template_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "code": template_code,\n                "name": data.name,\n                "description": data.description,\n                "category": data.category,\n                "report_type": data.report_type,\n                "data_source_ids": data.data_source_ids or [],\n                "query": data.query,\n                "parameters": [p.model_dump() for p in data.parameters] if data.parameters else [],\n                "columns": [c.model_dump() for c in data.columns] if data.columns else [],\n                "filters": [f.model_dump() for f in data.filters] if data.filters else [],\n                "sort_config": [s.model_dump() for s in data.sort_config] if data.sort_config else [],\n                "aggregations": [a.model_dump() for a in data.aggregations] if data.aggregations else [],\n                "groupings": [g.model_dump() for g in data.groupings] if data.groupings else [],\n                "visualizations": [v.model_dump() for v in data.visualizations] if data.visualizations else [],\n                "default_format": data.default_format or ReportFormat.PDF.value,\n                "allowed_formats": data.allowed_formats or [ReportFormat.PDF.value, ReportFormat.EXCEL.value, ReportFormat.CSV.value],\n                "page_size": data.page_size or "A4",\n                "orientation": data.orientation or "portrait",\n                "margin_config": data.margin_config or {},\n                "header_template": data.header_template,\n                "footer_template": data.footer_template,\n                "styles": data.styles or {},\n                "is_public": data.is_public or False,\n                "is_system": data.is_system or False,\n                "version": 1,\n                "tags": data.tags or [],\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n                "updated_at": datetime.utcnow(),\n            }\n            \n            template = await self.repository.create_report_template(template_data)\n            \n            # Create parameters\n            for param_data in (data.parameters or []):\n                await self._create_report_parameter(template["id"], param_data, user_id)\n            \n            return ServiceResult.success(\n                ReportTemplateResponse(**template),\n                message="Report template created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create report template: {str(e)}"))\n    \n    @requires_permission("reports:template:read")\n    @cached(key_prefix="report_template", ttl=300)\n    async def get_report_template(\n        self,\n        template_id: str,\n    ) -> ServiceResult[ReportTemplateResponse]:\n        """\n        Get a report template by ID.\n        \n        Args:\n            template_id: ID of the template\n            \n        Returns:\n            ServiceResult containing the template\n        """\n        try:\n            template = await self.repository.find_report_template_by_id(template_id)\n            if not template:\n                return ServiceResult.error(\n                    NotFoundError(f"Report template with ID {template_id} not found")\n                )\n            \n            return ServiceResult.success(ReportTemplateResponse(**template))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get report template: {str(e)}"))\n    \n    @requires_permission("reports:template:read")\n    async def list_report_templates(\n        self,\n        category: Optional[str] = None,\n        report_type: Optional[str] = None,\n        is_public: Optional[bool] = None,\n        created_by: Optional[str] = None,\n        search: Optional[str] = None,\n        tags: Optional[List[str]] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[ReportTemplateListResponse]:\n        """\n        List report templates with filtering and pagination.\n        \n        Args:\n            category: Filter by category\n            report_type: Filter by report type\n            is_public: Filter by public status\n            created_by: Filter by creator\n            search: Search in name and description\n            tags: Filter by tags\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated templates\n        """\n        try:\n            filters = {}\n            if category:\n                filters["category"] = category\n            if report_type:\n                filters["report_type"] = report_type\n            if is_public is not None:\n                filters["is_public"] = is_public\n            if created_by:\n                filters["created_by"] = created_by\n            \n            result = await self.repository.find_report_templates_paginated(\n                filters=filters,\n                search=search,\n                search_fields=["name", "description", "code"],\n                tags=tags,\n                page=page,\n                page_size=page_size,\n                order_by="created_at",\n                order_desc=True,\n            )\n            \n            return ServiceResult.success(\n                ReportTemplateListResponse(\n                    items=[ReportTemplateResponse(**t) for t in result["items"]],\n                    total=result["total"],\n                    page=page,\n                    page_size=page_size,\n                    total_pages=result["total_pages"],\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list report templates: {str(e)}"))\n    \n    @requires_permission("reports:template:update")\n    @transactional\n    @emit_event("reports.template.updated")\n    async def update_report_template(\n        self,\n        template_id: str,\n        data: ReportTemplateUpdate,\n        user_id: str,\n        create_version: bool = True,\n    ) -> ServiceResult[ReportTemplateResponse]:\n        """\n        Update a report template.\n        \n        Args:\n            template_id: ID of the template to update\n            data: Update data\n            user_id: ID of the user updating\n            create_version: Whether to create a new version\n            \n        Returns:\n            ServiceResult containing the updated template\n        """\n        try:\n            template = await self.repository.find_report_template_by_id(template_id)\n            if not template:\n                return ServiceResult.error(\n                    NotFoundError(f"Report template with ID {template_id} not found")\n                )\n            \n            # Check if template is system template\n            if template.get("is_system") and not await self._is_admin(user_id):\n                return ServiceResult.error(\n                    PermissionDeniedError("Cannot modify system templates")\n                )\n            \n            update_data = data.model_dump(exclude_unset=True)\n            update_data["updated_at"] = datetime.utcnow()\n            update_data["updated_by"] = user_id\n            \n            # Validate query if changed\n            if "query" in update_data and update_data["query"]:\n                validation_result = await self._validate_report_query(\n                    update_data["query"],\n                    update_data.get("data_source_ids", template.get("data_source_ids", []))\n                )\n                if not validation_result.get("valid"):\n                    return ServiceResult.error(\n                        ValidationError(f"Invalid query: {validation_result.get(\'error\')}")\n                    )\n            \n            # Increment version if structural changes\n            if create_version and any(k in update_data for k in ["query", "columns", "aggregations", "groupings"]):\n                update_data["version"] = template["version"] + 1\n                await self._create_template_version(template_id, template, user_id)\n            \n            updated_template = await self.repository.update_report_template(template_id, update_data)\n            \n            await cache_invalidate(f"report_template:{template_id}")\n            \n            return ServiceResult.success(\n                ReportTemplateResponse(**updated_template),\n                message="Report template updated successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to update report template: {str(e)}"))\n    \n    @requires_permission("reports:template:delete")\n    @transactional\n    @emit_event("reports.template.deleted")\n    async def delete_report_template(\n        self,\n        template_id: str,\n        user_id: str,\n    ) -> ServiceResult[bool]:\n        """\n        Delete a report template.\n        \n        Args:\n            template_id: ID of the template to delete\n            user_id: ID of the user deleting\n            \n        Returns:\n            ServiceResult indicating success\n        """\n        try:\n            template = await self.repository.find_report_template_by_id(template_id)\n            if not template:\n                return ServiceResult.error(\n                    NotFoundError(f"Report template with ID {template_id} not found")\n                )\n            \n            if template.get("is_system"):\n                return ServiceResult.error(\n                    PermissionDeniedError("Cannot delete system templates")\n                )\n            \n            # Check for active schedules\n            active_schedules = await self.repository.count_template_schedules(template_id, active_only=True)\n            if active_schedules > 0:\n                return ServiceResult.error(\n                    ConflictError(f"Cannot delete template with {active_schedules} active schedules")\n                )\n            \n            # Archive executions instead of deleting\n            await self.repository.archive_template_executions(template_id)\n            \n            await self.repository.delete_report_template(template_id)\n            \n            await cache_invalidate(f"report_template:{template_id}")\n            \n            return ServiceResult.success(True, message="Report template deleted successfully")\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to delete report template: {str(e)}"))\n    \n    @requires_permission("reports:template:read")\n    async def get_template_versions(\n        self,\n        template_id: str,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[List[Dict[str, Any]]]:\n        """\n        Get version history for a report template.\n        \n        Args:\n            template_id: ID of the template\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing version history\n        """\n        try:\n            versions = await self.repository.find_template_versions(\n                template_id,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success(versions)\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get template versions: {str(e)}"))\n    \n    @requires_permission("reports:template:update")\n    @transactional\n    async def duplicate_template(\n        self,\n        template_id: str,\n        new_name: str,\n        user_id: str,\n    ) -> ServiceResult[ReportTemplateResponse]:\n        """\n        Duplicate a report template.\n        \n        Args:\n            template_id: ID of the template to duplicate\n            new_name: Name for the new template\n            user_id: ID of the user duplicating\n            \n        Returns:\n            ServiceResult containing the new template\n        """\n        try:\n            template = await self.repository.find_report_template_by_id(template_id)\n            if not template:\n                return ServiceResult.error(\n                    NotFoundError(f"Report template with ID {template_id} not found")\n                )\n            \n            new_template_data = {\n                **template,\n                "id": str(uuid.uuid4()),\n                "code": await self._generate_template_code(new_name),\n                "name": new_name,\n                "is_system": False,\n                "version": 1,\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n                "updated_at": datetime.utcnow(),\n                "duplicated_from": template_id,\n            }\n            \n            # Remove auto-generated fields\n            del new_template_data["id"]\n            new_template_data["id"] = str(uuid.uuid4())\n            \n            new_template = await self.repository.create_report_template(new_template_data)\n            \n            return ServiceResult.success(\n                ReportTemplateResponse(**new_template),\n                message="Template duplicated successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to duplicate template: {str(e)}"))\n    \n    # ==================== Report Execution ====================\n    \n    @requires_permission("reports:execute")\n    @transactional\n    @emit_event("reports.execution.started")\n    async def execute_report(\n        self,\n        data: ReportExecutionCreate,\n        user_id: str,\n    ) -> ServiceResult[ReportExecutionResponse]:\n        """\n        Execute a report.\n        \n        Args:\n            data: Execution data\n            user_id: ID of the user executing\n            \n        Returns:\n            ServiceResult containing the execution\n        """\n        try:\n            # Get template\n            template = await self.repository.find_report_template_by_id(data.template_id)\n            if not template:\n                return ServiceResult.error(\n                    NotFoundError(f"Report template with ID {data.template_id} not found")\n                )\n            \n            # Validate parameters\n            param_validation = await self._validate_parameters(\n                template.get("parameters", []),\n                data.parameter_values or {}\n            )\n            if not param_validation.get("valid"):\n                return ServiceResult.error(\n                    ValidationError(f"Invalid parameters: {param_validation.get(\'errors\')}")\n                )\n            \n            # Create execution record\n            execution_id = str(uuid.uuid4())\n            execution_data = {\n                "id": execution_id,\n                "company_id": self.company_id,\n                "template_id": data.template_id,\n                "template_version": template["version"],\n                "status": ReportStatus.PENDING.value,\n                "parameter_values": data.parameter_values or {},\n                "output_format": data.output_format or template.get("default_format", ReportFormat.PDF.value),\n                "execution_type": data.execution_type or "manual",\n                "triggered_by": user_id,\n                "priority": data.priority or "normal",\n                "include_header": data.include_header if data.include_header is not None else True,\n                "include_footer": data.include_footer if data.include_footer is not None else True,\n                "created_at": datetime.utcnow(),\n                "started_at": None,\n                "completed_at": None,\n            }\n            \n            execution = await self.repository.create_report_execution(execution_data)\n            \n            # Queue execution\n            await self._queue_report_execution(execution_id, template, data)\n            \n            return ServiceResult.success(\n                ReportExecutionResponse(**execution),\n                message="Report execution started"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to execute report: {str(e)}"))\n    \n    @requires_permission("reports:execute")\n    async def get_execution(\n        self,\n        execution_id: str,\n    ) -> ServiceResult[ReportExecutionResponse]:\n        """\n        Get a report execution by ID.\n        \n        Args:\n            execution_id: ID of the execution\n            \n        Returns:\n            ServiceResult containing the execution\n        """\n        try:\n            execution = await self.repository.find_report_execution_by_id(execution_id)\n            if not execution:\n                return ServiceResult.error(\n                    NotFoundError(f"Execution with ID {execution_id} not found")\n                )\n            \n            return ServiceResult.success(ReportExecutionResponse(**execution))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get execution: {str(e)}"))\n    \n    @requires_permission("reports:execute")\n    async def list_executions(\n        self,\n        template_id: Optional[str] = None,\n        status: Optional[str] = None,\n        triggered_by: Optional[str] = None,\n        start_date: Optional[datetime] = None,\n        end_date: Optional[datetime] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[ReportExecutionListResponse]:\n        """\n        List report executions with filtering.\n        \n        Args:\n            template_id: Filter by template ID\n            status: Filter by status\n            triggered_by: Filter by trigger user\n            start_date: Filter by start date\n            end_date: Filter by end date\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated executions\n        """\n        try:\n            filters = {}\n            if template_id:\n                filters["template_id"] = template_id\n            if status:\n                filters["status"] = status\n            if triggered_by:\n                filters["triggered_by"] = triggered_by\n            \n            date_filters = {}\n            if start_date:\n                date_filters["created_at_gte"] = start_date\n            if end_date:\n                date_filters["created_at_lte"] = end_date\n            \n            result = await self.repository.find_report_executions_paginated(\n                filters=filters,\n                date_filters=date_filters,\n                page=page,\n                page_size=page_size,\n                order_by="created_at",\n                order_desc=True,\n            )\n            \n            return ServiceResult.success(\n                ReportExecutionListResponse(\n                    items=[ReportExecutionResponse(**e) for e in result["items"]],\n                    total=result["total"],\n                    page=page,\n                    page_size=page_size,\n                    total_pages=result["total_pages"],\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list executions: {str(e)}"))\n    \n    @requires_permission("reports:execute")\n    async def get_execution_results(\n        self,\n        execution_id: str,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Get the results of a report execution.\n        \n        Args:\n            execution_id: ID of the execution\n            \n        Returns:\n            ServiceResult containing the results\n        """\n        try:\n            execution = await self.repository.find_report_execution_by_id(execution_id)\n            if not execution:\n                return ServiceResult.error(\n                    NotFoundError(f"Execution with ID {execution_id} not found")\n                )\n            \n            if execution["status"] != ReportStatus.COMPLETED.value:\n                return ServiceResult.error(\n                    ValidationError(f"Execution is in {execution[\'status\']} status")\n                )\n            \n            results = await self.repository.find_execution_results(execution_id)\n            \n            return ServiceResult.success({\n                "data": results.get("data", []),\n                "summary": results.get("summary", {}),\n                "metadata": {\n                    "row_count": results.get("row_count", 0),\n                    "execution_time_seconds": results.get("execution_time_seconds"),\n                    "output_file_path": execution.get("output_file_path"),\n                },\n            })\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get execution results: {str(e)}"))\n    \n    @requires_permission("reports:execute")\n    async def download_report(\n        self,\n        execution_id: str,\n        format: Optional[str] = None,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Download a report in the specified format.\n        \n        Args:\n            execution_id: ID of the execution\n            format: Output format (defaults to execution format)\n            \n        Returns:\n            ServiceResult containing download information\n        """\n        try:\n            execution = await self.repository.find_report_execution_by_id(execution_id)\n            if not execution:\n                return ServiceResult.error(\n                    NotFoundError(f"Execution with ID {execution_id} not found")\n                )\n            \n            if execution["status"] != ReportStatus.COMPLETED.value:\n                return ServiceResult.error(\n                    ValidationError("Report execution is not complete")\n                )\n            \n            output_format = format or execution["output_format"]\n            \n            # Check if format conversion needed\n            if output_format != execution["output_format"]:\n                converted_file = await self._convert_report_format(execution, output_format)\n            else:\n                converted_file = execution.get("output_file_path")\n            \n            return ServiceResult.success({\n                "file_path": converted_file,\n                "file_name": f"{execution.get(\'report_name\', \'report\')}_{execution_id[:8]}.{output_format.lower()}",\n                "mime_type": self._get_mime_type(output_format),\n                "size_bytes": await self._get_file_size(converted_file),\n            })\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to download report: {str(e)}"))\n    \n    @requires_permission("reports:execute")\n    @transactional\n    async def cancel_execution(\n        self,\n        execution_id: str,\n        user_id: str,\n    ) -> ServiceResult[ReportExecutionResponse]:\n        """\n        Cancel a report execution.\n        \n        Args:\n            execution_id: ID of the execution\n            user_id: ID of the user cancelling\n            \n        Returns:\n            ServiceResult containing the cancelled execution\n        """\n        try:\n            execution = await self.repository.find_report_execution_by_id(execution_id)\n            if not execution:\n                return ServiceResult.error(\n                    NotFoundError(f"Execution with ID {execution_id} not found")\n                )\n            \n            if execution["status"] not in [ReportStatus.PENDING.value, ReportStatus.RUNNING.value]:\n                return ServiceResult.error(\n                    ConflictError(f"Cannot cancel execution in {execution[\'status\']} status")\n                )\n            \n            updated_execution = await self.repository.update_report_execution(execution_id, {\n                "status": ReportStatus.CANCELLED.value,\n                "cancelled_at": datetime.utcnow(),\n                "cancelled_by": user_id,\n            })\n            \n            return ServiceResult.success(\n                ReportExecutionResponse(**updated_execution),\n                message="Execution cancelled"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to cancel execution: {str(e)}"))\n    \n    @requires_permission("reports:execute")\n    async def retry_execution(\n        self,\n        execution_id: str,\n        user_id: str,\n    ) -> ServiceResult[ReportExecutionResponse]:\n        """\n        Retry a failed report execution.\n        \n        Args:\n            execution_id: ID of the failed execution\n            user_id: ID of the user retrying\n            \n        Returns:\n            ServiceResult containing the new execution\n        """\n        try:\n            execution = await self.repository.find_report_execution_by_id(execution_id)\n            if not execution:\n                return ServiceResult.error(\n                    NotFoundError(f"Execution with ID {execution_id} not found")\n                )\n            \n            if execution["status"] != ReportStatus.FAILED.value:\n                return ServiceResult.error(\n                    ConflictError("Can only retry failed executions")\n                )\n            \n            # Create new execution with same parameters\n            new_execution = await self.execute_report(\n                ReportExecutionCreate(\n                    template_id=execution["template_id"],\n                    parameter_values=execution.get("parameter_values", {}),\n                    output_format=execution["output_format"],\n                    execution_type="retry",\n                    priority=execution.get("priority", "normal"),\n                ),\n                user_id,\n            )\n            \n            return new_execution\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to retry execution: {str(e)}"))\n    \n    # ==================== Report Scheduling ====================\n    \n    @requires_permission("reports:schedule:create")\n    @transactional\n    @emit_event("reports.schedule.created")\n    async def create_schedule(\n        self,\n        data: ReportScheduleCreate,\n        user_id: str,\n    ) -> ServiceResult[ReportScheduleResponse]:\n        """\n        Create a report schedule.\n        \n        Args:\n            data: Schedule creation data\n            user_id: ID of the user creating\n            \n        Returns:\n            ServiceResult containing the created schedule\n        """\n        try:\n            # Verify template exists\n            template = await self.repository.find_report_template_by_id(data.template_id)\n            if not template:\n                return ServiceResult.error(\n                    NotFoundError(f"Report template with ID {data.template_id} not found")\n                )\n            \n            # Validate schedule configuration\n            validation_result = self._validate_schedule_config(data)\n            if not validation_result.get("valid"):\n                return ServiceResult.error(\n                    ValidationError(validation_result.get("errors", ["Invalid schedule configuration"]))\n                )\n            \n            schedule_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "template_id": data.template_id,\n                "name": data.name,\n                "description": data.description,\n                "frequency": data.frequency,\n                "cron_expression": data.cron_expression,\n                "start_date": data.start_date,\n                "end_date": data.end_date,\n                "timezone": data.timezone or "UTC",\n                "parameter_values": data.parameter_values or {},\n                "output_format": data.output_format or template.get("default_format", ReportFormat.PDF.value),\n                "delivery_config": data.delivery_config or {},\n                "recipients": data.recipients or [],\n                "is_active": data.is_active if data.is_active is not None else True,\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n                "next_run_at": self._calculate_next_run(data),\n            }\n            \n            schedule = await self.repository.create_report_schedule(schedule_data)\n            \n            # Register with scheduler\n            await self._register_schedule(schedule)\n            \n            return ServiceResult.success(\n                ReportScheduleResponse(**schedule),\n                message="Report schedule created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create schedule: {str(e)}"))\n    \n    @requires_permission("reports:schedule:read")\n    async def list_schedules(\n        self,\n        template_id: Optional[str] = None,\n        is_active: Optional[bool] = None,\n        created_by: Optional[str] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        List report schedules.\n        \n        Args:\n            template_id: Filter by template ID\n            is_active: Filter by active status\n            created_by: Filter by creator\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated schedules\n        """\n        try:\n            filters = {}\n            if template_id:\n                filters["template_id"] = template_id\n            if is_active is not None:\n                filters["is_active"] = is_active\n            if created_by:\n                filters["created_by"] = created_by\n            \n            result = await self.repository.find_report_schedules_paginated(\n                filters=filters,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success({\n                "items": [ReportScheduleResponse(**s) for s in result["items"]],\n                "total": result["total"],\n                "page": page,\n                "page_size": page_size,\n                "total_pages": result["total_pages"],\n            })\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list schedules: {str(e)}"))\n    \n    @requires_permission("reports:schedule:update")\n    @transactional\n    @emit_event("reports.schedule.updated")\n    async def update_schedule(\n        self,\n        schedule_id: str,\n        data: ReportScheduleUpdate,\n        user_id: str,\n    ) -> ServiceResult[ReportScheduleResponse]:\n        """\n        Update a report schedule.\n        \n        Args:\n            schedule_id: ID of the schedule to update\n            data: Update data\n            user_id: ID of the user updating\n            \n        Returns:\n            ServiceResult containing the updated schedule\n        """\n        try:\n            schedule = await self.repository.find_report_schedule_by_id(schedule_id)\n            if not schedule:\n                return ServiceResult.error(\n                    NotFoundError(f"Schedule with ID {schedule_id} not found")\n                )\n            \n            update_data = data.model_dump(exclude_unset=True)\n            update_data["updated_at"] = datetime.utcnow()\n            update_data["updated_by"] = user_id\n            \n            # Recalculate next run if schedule changed\n            if any(k in update_data for k in ["frequency", "cron_expression", "start_date", "end_date", "timezone"]):\n                combined_data = {**schedule, **update_data}\n                update_data["next_run_at"] = self._calculate_next_run_from_dict(combined_data)\n            \n            updated_schedule = await self.repository.update_report_schedule(schedule_id, update_data)\n            \n            # Re-register with scheduler\n            await self._update_schedule_registration(updated_schedule)\n            \n            return ServiceResult.success(\n                ReportScheduleResponse(**updated_schedule),\n                message="Schedule updated successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to update schedule: {str(e)}"))\n    \n    @requires_permission("reports:schedule:delete")\n    @transactional\n    @emit_event("reports.schedule.deleted")\n    async def delete_schedule(\n        self,\n        schedule_id: str,\n    ) -> ServiceResult[bool]:\n        """\n        Delete a report schedule.\n        \n        Args:\n            schedule_id: ID of the schedule to delete\n            \n        Returns:\n            ServiceResult indicating success\n        """\n        try:\n            schedule = await self.repository.find_report_schedule_by_id(schedule_id)\n            if not schedule:\n                return ServiceResult.error(\n                    NotFoundError(f"Schedule with ID {schedule_id} not found")\n                )\n            \n            # Unregister from scheduler\n            await self._unregister_schedule(schedule)\n            \n            await self.repository.delete_report_schedule(schedule_id)\n            \n            return ServiceResult.success(True, message="Schedule deleted successfully")\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to delete schedule: {str(e)}"))\n    \n    @requires_permission("reports:schedule:update")\n    @transactional\n    async def pause_schedule(\n        self,\n        schedule_id: str,\n        user_id: str,\n    ) -> ServiceResult[ReportScheduleResponse]:\n        """\n        Pause a report schedule.\n        \n        Args:\n            schedule_id: ID of the schedule\n            user_id: ID of the user pausing\n            \n        Returns:\n            ServiceResult containing the paused schedule\n        """\n        try:\n            schedule = await self.repository.find_report_schedule_by_id(schedule_id)\n            if not schedule:\n                return ServiceResult.error(\n                    NotFoundError(f"Schedule with ID {schedule_id} not found")\n                )\n            \n            updated_schedule = await self.repository.update_report_schedule(schedule_id, {\n                "is_active": False,\n                "paused_at": datetime.utcnow(),\n                "paused_by": user_id,\n            })\n            \n            await self._unregister_schedule(schedule)\n            \n            return ServiceResult.success(\n                ReportScheduleResponse(**updated_schedule),\n                message="Schedule paused"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to pause schedule: {str(e)}"))\n    \n    @requires_permission("reports:schedule:update")\n    @transactional\n    async def resume_schedule(\n        self,\n        schedule_id: str,\n        user_id: str,\n    ) -> ServiceResult[ReportScheduleResponse]:\n        """\n        Resume a paused report schedule.\n        \n        Args:\n            schedule_id: ID of the schedule\n            user_id: ID of the user resuming\n            \n        Returns:\n            ServiceResult containing the resumed schedule\n        """\n        try:\n            schedule = await self.repository.find_report_schedule_by_id(schedule_id)\n            if not schedule:\n                return ServiceResult.error(\n                    NotFoundError(f"Schedule with ID {schedule_id} not found")\n                )\n            \n            next_run = self._calculate_next_run_from_dict(schedule)\n            \n            updated_schedule = await self.repository.update_report_schedule(schedule_id, {\n                "is_active": True,\n                "resumed_at": datetime.utcnow(),\n                "resumed_by": user_id,\n                "next_run_at": next_run,\n            })\n            \n            await self._register_schedule(updated_schedule)\n            \n            return ServiceResult.success(\n                ReportScheduleResponse(**updated_schedule),\n                message="Schedule resumed"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to resume schedule: {str(e)}"))\n    \n    # ==================== Dashboard Management ====================\n    \n    @requires_permission("reports:dashboard:create")\n    @transactional\n    @emit_event("reports.dashboard.created")\n    async def create_dashboard(\n        self,\n        data: DashboardCreate,\n        user_id: str,\n    ) -> ServiceResult[DashboardResponse]:\n        """\n        Create a new dashboard.\n        \n        Args:\n            data: Dashboard creation data\n            user_id: ID of the user creating\n            \n        Returns:\n            ServiceResult containing the created dashboard\n        """\n        try:\n            dashboard_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "name": data.name,\n                "description": data.description,\n                "layout_config": data.layout_config or {},\n                "widgets": [w.model_dump() for w in data.widgets] if data.widgets else [],\n                "filters": [f.model_dump() for f in data.filters] if data.filters else [],\n                "refresh_interval_seconds": data.refresh_interval_seconds,\n                "is_public": data.is_public or False,\n                "is_default": data.is_default or False,\n                "tags": data.tags or [],\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n                "updated_at": datetime.utcnow(),\n            }\n            \n            dashboard = await self.repository.create_dashboard(dashboard_data)\n            \n            # Create widget instances\n            for widget_data in (data.widgets or []):\n                await self._create_dashboard_widget(dashboard["id"], widget_data, user_id)\n            \n            # Set as default if specified\n            if data.is_default:\n                await self._set_default_dashboard(dashboard["id"], user_id)\n            \n            return ServiceResult.success(\n                DashboardResponse(**dashboard),\n                message="Dashboard created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create dashboard: {str(e)}"))\n    \n    @requires_permission("reports:dashboard:read")\n    @cached(key_prefix="dashboard", ttl=60)\n    async def get_dashboard(\n        self,\n        dashboard_id: str,\n    ) -> ServiceResult[DashboardResponse]:\n        """\n        Get a dashboard by ID.\n        \n        Args:\n            dashboard_id: ID of the dashboard\n            \n        Returns:\n            ServiceResult containing the dashboard\n        """\n        try:\n            dashboard = await self.repository.find_dashboard_by_id(dashboard_id)\n            if not dashboard:\n                return ServiceResult.error(\n                    NotFoundError(f"Dashboard with ID {dashboard_id} not found")\n                )\n            \n            return ServiceResult.success(DashboardResponse(**dashboard))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get dashboard: {str(e)}"))\n    \n    @requires_permission("reports:dashboard:read")\n    async def list_dashboards(\n        self,\n        is_public: Optional[bool] = None,\n        created_by: Optional[str] = None,\n        search: Optional[str] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[DashboardListResponse]:\n        """\n        List dashboards with filtering.\n        \n        Args:\n            is_public: Filter by public status\n            created_by: Filter by creator\n            search: Search in name and description\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated dashboards\n        """\n        try:\n            filters = {}\n            if is_public is not None:\n                filters["is_public"] = is_public\n            if created_by:\n                filters["created_by"] = created_by\n            \n            result = await self.repository.find_dashboards_paginated(\n                filters=filters,\n                search=search,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success(\n                DashboardListResponse(\n                    items=[DashboardResponse(**d) for d in result["items"]],\n                    total=result["total"],\n                    page=page,\n                    page_size=page_size,\n                    total_pages=result["total_pages"],\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list dashboards: {str(e)}"))\n    \n    @requires_permission("reports:dashboard:update")\n    @transactional\n    @emit_event("reports.dashboard.updated")\n    async def update_dashboard(\n        self,\n        dashboard_id: str,\n        data: DashboardUpdate,\n        user_id: str,\n    ) -> ServiceResult[DashboardResponse]:\n        """\n        Update a dashboard.\n        \n        Args:\n            dashboard_id: ID of the dashboard to update\n            data: Update data\n            user_id: ID of the user updating\n            \n        Returns:\n            ServiceResult containing the updated dashboard\n        """\n        try:\n            dashboard = await self.repository.find_dashboard_by_id(dashboard_id)\n            if not dashboard:\n                return ServiceResult.error(\n                    NotFoundError(f"Dashboard with ID {dashboard_id} not found")\n                )\n            \n            update_data = data.model_dump(exclude_unset=True)\n            update_data["updated_at"] = datetime.utcnow()\n            update_data["updated_by"] = user_id\n            \n            updated_dashboard = await self.repository.update_dashboard(dashboard_id, update_data)\n            \n            await cache_invalidate(f"dashboard:{dashboard_id}")\n            \n            return ServiceResult.success(\n                DashboardResponse(**updated_dashboard),\n                message="Dashboard updated successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to update dashboard: {str(e)}"))\n    \n    @requires_permission("reports:dashboard:delete")\n    @transactional\n    @emit_event("reports.dashboard.deleted")\n    async def delete_dashboard(\n        self,\n        dashboard_id: str,\n    ) -> ServiceResult[bool]:\n        """\n        Delete a dashboard.\n        \n        Args:\n            dashboard_id: ID of the dashboard to delete\n            \n        Returns:\n            ServiceResult indicating success\n        """\n        try:\n            dashboard = await self.repository.find_dashboard_by_id(dashboard_id)\n            if not dashboard:\n                return ServiceResult.error(\n                    NotFoundError(f"Dashboard with ID {dashboard_id} not found")\n                )\n            \n            await self.repository.delete_dashboard(dashboard_id)\n            \n            await cache_invalidate(f"dashboard:{dashboard_id}")\n            \n            return ServiceResult.success(True, message="Dashboard deleted successfully")\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to delete dashboard: {str(e)}"))\n    \n    @requires_permission("reports:dashboard:read")\n    async def get_dashboard_data(\n        self,\n        dashboard_id: str,\n        filter_values: Optional[Dict[str, Any]] = None,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Get all widget data for a dashboard.\n        \n        Args:\n            dashboard_id: ID of the dashboard\n            filter_values: Filter values to apply\n            \n        Returns:\n            ServiceResult containing all widget data\n        """\n        try:\n            dashboard = await self.repository.find_dashboard_by_id(dashboard_id)\n            if not dashboard:\n                return ServiceResult.error(\n                    NotFoundError(f"Dashboard with ID {dashboard_id} not found")\n                )\n            \n            widgets = dashboard.get("widgets", [])\n            widget_data = {}\n            \n            for widget in widgets:\n                widget_id = widget.get("id") or widget.get("widget_id")\n                data = await self._get_widget_data(widget, filter_values)\n                widget_data[widget_id] = data\n            \n            return ServiceResult.success({\n                "dashboard_id": dashboard_id,\n                "widgets": widget_data,\n                "refreshed_at": datetime.utcnow(),\n            })\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get dashboard data: {str(e)}"))\n    \n    @requires_permission("reports:dashboard:update")\n    @transactional\n    async def add_widget_to_dashboard(\n        self,\n        dashboard_id: str,\n        widget_data: DashboardWidgetCreate,\n        user_id: str,\n    ) -> ServiceResult[DashboardWidgetResponse]:\n        """\n        Add a widget to a dashboard.\n        \n        Args:\n            dashboard_id: ID of the dashboard\n            widget_data: Widget creation data\n            user_id: ID of the user adding\n            \n        Returns:\n            ServiceResult containing the created widget\n        """\n        try:\n            dashboard = await self.repository.find_dashboard_by_id(dashboard_id)\n            if not dashboard:\n                return ServiceResult.error(\n                    NotFoundError(f"Dashboard with ID {dashboard_id} not found")\n                )\n            \n            widget = await self._create_dashboard_widget(dashboard_id, widget_data.model_dump(), user_id)\n            \n            # Update dashboard widget list\n            widgets = dashboard.get("widgets", [])\n            widgets.append(widget)\n            await self.repository.update_dashboard(dashboard_id, {\n                "widgets": widgets,\n                "updated_at": datetime.utcnow(),\n            })\n            \n            return ServiceResult.success(\n                DashboardWidgetResponse(**widget),\n                message="Widget added to dashboard"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to add widget: {str(e)}"))\n    \n    @requires_permission("reports:dashboard:update")\n    @transactional\n    async def remove_widget_from_dashboard(\n        self,\n        dashboard_id: str,\n        widget_id: str,\n        user_id: str,\n    ) -> ServiceResult[bool]:\n        """\n        Remove a widget from a dashboard.\n        \n        Args:\n            dashboard_id: ID of the dashboard\n            widget_id: ID of the widget to remove\n            user_id: ID of the user removing\n            \n        Returns:\n            ServiceResult indicating success\n        """\n        try:\n            dashboard = await self.repository.find_dashboard_by_id(dashboard_id)\n            if not dashboard:\n                return ServiceResult.error(\n                    NotFoundError(f"Dashboard with ID {dashboard_id} not found")\n                )\n            \n            widgets = dashboard.get("widgets", [])\n            updated_widgets = [w for w in widgets if w.get("id") != widget_id]\n            \n            await self.repository.update_dashboard(dashboard_id, {\n                "widgets": updated_widgets,\n                "updated_at": datetime.utcnow(),\n            })\n            \n            await self.repository.delete_dashboard_widget(widget_id)\n            \n            return ServiceResult.success(True, message="Widget removed from dashboard")\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to remove widget: {str(e)}"))\n    \n    # ==================== Data Source Management ====================\n    \n    @requires_permission("reports:datasource:create")\n    @transactional\n    async def create_data_source(\n        self,\n        data: DataSourceCreate,\n        user_id: str,\n    ) -> ServiceResult[DataSourceResponse]:\n        """\n        Create a data source for reports.\n        \n        Args:\n            data: Data source creation data\n            user_id: ID of the user creating\n            \n        Returns:\n            ServiceResult containing the created data source\n        """\n        try:\n            # Test connection before saving\n            if data.connection_config:\n                test_result = await self._test_data_source_connection(data)\n                if not test_result.get("success"):\n                    return ServiceResult.error(\n                        ValidationError(f"Data source connection failed: {test_result.get(\'error\')}")\n                    )\n            \n            ds_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "name": data.name,\n                "source_type": data.source_type,\n                "connection_config": data.connection_config or {},\n                "schema_info": data.schema_info or {},\n                "is_readonly": data.is_readonly if data.is_readonly is not None else True,\n                "is_active": True,\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n            }\n            \n            data_source = await self.repository.create_data_source(ds_data)\n            \n            return ServiceResult.success(\n                DataSourceResponse(**data_source),\n                message="Data source created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create data source: {str(e)}"))\n    \n    @requires_permission("reports:datasource:read")\n    async def list_data_sources(\n        self,\n        source_type: Optional[str] = None,\n        is_active: Optional[bool] = None,\n        page: int = 1,\n        page_size: int = 50,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        List data sources.\n        \n        Args:\n            source_type: Filter by source type\n            is_active: Filter by active status\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing data sources\n        """\n        try:\n            filters = {}\n            if source_type:\n                filters["source_type"] = source_type\n            if is_active is not None:\n                filters["is_active"] = is_active\n            \n            result = await self.repository.find_data_sources_paginated(\n                filters=filters,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success({\n                "items": [DataSourceResponse(**ds) for ds in result["items"]],\n                "total": result["total"],\n            })\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list data sources: {str(e)}"))\n    \n    @requires_permission("reports:datasource:update")\n    async def test_data_source_connection(\n        self,\n        data_source_id: str,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Test connection to a data source.\n        \n        Args:\n            data_source_id: ID of the data source\n            \n        Returns:\n            ServiceResult containing test results\n        """\n        try:\n            ds = await self.repository.find_data_source(data_source_id)\n            if not ds:\n                return ServiceResult.error(\n                    NotFoundError(f"Data source with ID {data_source_id} not found")\n                )\n            \n            result = await self._test_data_source_connection_from_db(ds)\n            \n            # Update last tested timestamp\n            await self.repository.update_data_source(data_source_id, {\n                "last_tested_at": datetime.utcnow(),\n                "last_test_result": result,\n            })\n            \n            return ServiceResult.success(result)\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to test data source: {str(e)}"))\n    \n    # ==================== Private Helper Methods ====================\n    \n    async def _generate_template_code(self, name: str) -> str:\n        """Generate a unique template code."""\n        import re\n        code = re.sub(r\'[^a-zA-Z0-9]\', \'_\', name.upper())[:15]\n        count = await self.repository.count_templates_by_prefix(code)\n        return f"RPT_{code}_{count + 1:03d}"\n    \n    async def _validate_report_query(\n        self,\n        query: str,\n        data_source_ids: List[str],\n    ) -> Dict[str, Any]:\n        """Validate a report query."""\n        try:\n            # Basic validation\n            if not query or not query.strip():\n                return {"valid": False, "error": "Query cannot be empty"}\n            \n            # Check for dangerous operations\n            dangerous_keywords = ["DROP", "DELETE", "TRUNCATE", "UPDATE", "INSERT", "ALTER", "CREATE"]\n            query_upper = query.upper()\n            for keyword in dangerous_keywords:\n                if keyword in query_upper:\n                    return {"valid": False, "error": f"Query contains forbidden keyword: {keyword}"}\n            \n            # TODO: More sophisticated validation with data sources\n            \n            return {"valid": True}\n            \n        except Exception as e:\n            return {"valid": False, "error": str(e)}\n    \n    async def _validate_parameters(\n        self,\n        parameters: List[Dict[str, Any]],\n        values: Dict[str, Any],\n    ) -> Dict[str, Any]:\n        """Validate parameter values against parameter definitions."""\n        errors = []\n        \n        for param in parameters:\n            param_name = param.get("name")\n            is_required = param.get("required", False)\n            \n            if is_required and param_name not in values:\n                errors.append(f"Required parameter \'{param_name}\' is missing")\n                continue\n            \n            if param_name in values:\n                value = values[param_name]\n                param_type = param.get("type", "string")\n                \n                # Type validation\n                type_valid = self._validate_parameter_type(value, param_type)\n                if not type_valid:\n                    errors.append(f"Parameter \'{param_name}\' has invalid type, expected {param_type}")\n                \n                # Constraint validation\n                constraints = param.get("constraints", {})\n                if constraints:\n                    constraint_errors = self._validate_parameter_constraints(value, constraints, param_name)\n                    errors.extend(constraint_errors)\n        \n        return {"valid": len(errors) == 0, "errors": errors}\n    \n    def _validate_parameter_type(self, value: Any, expected_type: str) -> bool:\n        """Validate parameter value type."""\n        if value is None:\n            return True  # Null is allowed unless required\n        \n        type_validators = {\n            "string": lambda v: isinstance(v, str),\n            "integer": lambda v: isinstance(v, int) and not isinstance(v, bool),\n            "float": lambda v: isinstance(v, (int, float)) and not isinstance(v, bool),\n            "boolean": lambda v: isinstance(v, bool),\n            "date": lambda v: isinstance(v, (str, datetime)),\n            "datetime": lambda v: isinstance(v, (str, datetime)),\n            "list": lambda v: isinstance(v, list),\n            "dict": lambda v: isinstance(v, dict),\n        }\n        \n        validator = type_validators.get(expected_type, lambda v: True)\n        return validator(value)\n    \n    def _validate_parameter_constraints(\n        self,\n        value: Any,\n        constraints: Dict[str, Any],\n        param_name: str,\n    ) -> List[str]:\n        """Validate parameter constraints."""\n        errors = []\n        \n        if "min" in constraints and isinstance(value, (int, float)):\n            if value < constraints["min"]:\n                errors.append(f"Parameter \'{param_name}\' must be at least {constraints[\'min\']}")\n        \n        if "max" in constraints and isinstance(value, (int, float)):\n            if value > constraints["max"]:\n                errors.append(f"Parameter \'{param_name}\' must be at most {constraints[\'max\']}")\n        \n        if "min_length" in constraints and isinstance(value, str):\n            if len(value) < constraints["min_length"]:\n                errors.append(f"Parameter \'{param_name}\' must be at least {constraints[\'min_length\']} characters")\n        \n        if "max_length" in constraints and isinstance(value, str):\n            if len(value) > constraints["max_length"]:\n                errors.append(f"Parameter \'{param_name}\' must be at most {constraints[\'max_length\']} characters")\n        \n        if "allowed_values" in constraints:\n            if value not in constraints["allowed_values"]:\n                errors.append(f"Parameter \'{param_name}\' must be one of: {constraints[\'allowed_values\']}")\n        \n        if "pattern" in constraints and isinstance(value, str):\n            import re\n            if not re.match(constraints["pattern"], value):\n                errors.append(f"Parameter \'{param_name}\' does not match required pattern")\n        \n        return errors\n    \n    async def _create_report_parameter(\n        self,\n        template_id: str,\n        param_data: Dict[str, Any],\n        user_id: str,\n    ) -> None:\n        """Create a report parameter."""\n        param = {\n            "id": str(uuid.uuid4()),\n            "template_id": template_id,\n            "name": param_data.get("name"),\n            "label": param_data.get("label"),\n            "type": param_data.get("type", "string"),\n            "default_value": param_data.get("default_value"),\n            "required": param_data.get("required", False),\n            "constraints": param_data.get("constraints", {}),\n            "order": param_data.get("order", 0),\n            "created_at": datetime.utcnow(),\n        }\n        await self.repository.create_report_parameter(param)\n    \n    async def _create_template_version(\n        self,\n        template_id: str,\n        template: Dict[str, Any],\n        user_id: str,\n    ) -> None:\n        """Create a version record for a template."""\n        version_data = {\n            "id": str(uuid.uuid4()),\n            "template_id": template_id,\n            "version": template["version"],\n            "query": template.get("query"),\n            "columns": template.get("columns", []),\n            "aggregations": template.get("aggregations", []),\n            "created_by": user_id,\n            "created_at": datetime.utcnow(),\n        }\n        await self.repository.create_template_version(version_data)\n    \n    def _validate_schedule_config(self, data: ReportScheduleCreate) -> Dict[str, Any]:\n        """Validate schedule configuration."""\n        errors = []\n        \n        if data.frequency == ScheduleFrequency.CUSTOM.value:\n            if not data.cron_expression:\n                errors.append("Custom frequency requires cron_expression")\n            else:\n                # Validate cron expression\n                try:\n                    from croniter import croniter\n                    croniter(data.cron_expression)\n                except Exception:\n                    errors.append("Invalid cron expression")\n        \n        if data.start_date and data.end_date:\n            if data.end_date <= data.start_date:\n                errors.append("End date must be after start date")\n        \n        if data.delivery_config:\n            delivery_type = data.delivery_config.get("type")\n            if not delivery_type:\n                errors.append("Delivery config must specify type")\n        \n        return {"valid": len(errors) == 0, "errors": errors}\n    \n    def _calculate_next_run(self, data: ReportScheduleCreate) -> datetime:\n        """Calculate next run time for a schedule."""\n        return self._calculate_next_run_from_dict(data.model_dump())\n    \n    def _calculate_next_run_from_dict(self, data: Dict[str, Any]) -> datetime:\n        """Calculate next run time from schedule data."""\n        try:\n            from croniter import croniter\n            import pytz\n            \n            tz = pytz.timezone(data.get("timezone", "UTC"))\n            now = datetime.now(tz)\n            \n            if data.get("frequency") == ScheduleFrequency.CUSTOM.value:\n                cron_expr = data.get("cron_expression")\n                if cron_expr:\n                    cron = croniter(cron_expr, now)\n                    return cron.get_next(datetime)\n            \n            # Standard frequencies\n            frequency_minutes = {\n                ScheduleFrequency.HOURLY.value: 60,\n                ScheduleFrequency.DAILY.value: 1440,\n                ScheduleFrequency.WEEKLY.value: 10080,\n                ScheduleFrequency.MONTHLY.value: 43200,\n                ScheduleFrequency.QUARTERLY.value: 129600,\n            }\n            \n            minutes = frequency_minutes.get(data.get("frequency"), 1440)\n            return now + timedelta(minutes=minutes)\n            \n        except Exception:\n            return datetime.utcnow() + timedelta(hours=1)\n    \n    async def _queue_report_execution(\n        self,\n        execution_id: str,\n        template: Dict[str, Any],\n        data: ReportExecutionCreate,\n    ) -> None:\n        """Queue a report execution for processing."""\n        # In production, this would publish to a message queue\n        # For now, we\'ll process synchronously in a task\n        asyncio.create_task(self._process_report_execution(execution_id, template, data))\n    \n    async def _process_report_execution(\n        self,\n        execution_id: str,\n        template: Dict[str, Any],\n        data: ReportExecutionCreate,\n    ) -> None:\n        """Process a report execution."""\n        try:\n            # Update status to running\n            await self.repository.update_report_execution(execution_id, {\n                "status": ReportStatus.RUNNING.value,\n                "started_at": datetime.utcnow(),\n            })\n            \n            start_time = datetime.utcnow()\n            \n            # Execute query and get data\n            query_result = await self._execute_report_query(template, data.parameter_values or {})\n            \n            if not query_result.get("success"):\n                raise Exception(query_result.get("error", "Query execution failed"))\n            \n            # Process data\n            processed_data = await self._process_report_data(\n                query_result.get("data", []),\n                template,\n                data\n            )\n            \n            # Generate output\n            output_result = await self._generate_report_output(\n                processed_data,\n                template,\n                data.output_format or template.get("default_format", ReportFormat.PDF.value)\n            )\n            \n            execution_time = (datetime.utcnow() - start_time).total_seconds()\n            \n            # Update execution with results\n            await self.repository.update_report_execution(execution_id, {\n                "status": ReportStatus.COMPLETED.value,\n                "completed_at": datetime.utcnow(),\n                "execution_time_seconds": execution_time,\n                "row_count": len(query_result.get("data", [])),\n                "output_file_path": output_result.get("file_path"),\n                "output_file_size": output_result.get("file_size"),\n            })\n            \n            # Store results\n            await self.repository.create_execution_results({\n                "id": str(uuid.uuid4()),\n                "execution_id": execution_id,\n                "data": processed_data.get("data", []),\n                "summary": processed_data.get("summary", {}),\n                "row_count": len(query_result.get("data", [])),\n            })\n            \n        except Exception as e:\n            await self.repository.update_report_execution(execution_id, {\n                "status": ReportStatus.FAILED.value,\n                "completed_at": datetime.utcnow(),\n                "error_message": str(e),\n            })\n    \n    async def _execute_report_query(\n        self,\n        template: Dict[str, Any],\n        parameter_values: Dict[str, Any],\n    ) -> Dict[str, Any]:\n        """Execute the report query."""\n        try:\n            query = template.get("query")\n            if not query:\n                return {"success": True, "data": []}\n            \n            # Replace parameters in query\n            for param_name, param_value in parameter_values.items():\n                placeholder = f"{{{{{param_name}}}}}"\n                query = query.replace(placeholder, str(param_value))\n            \n            # Get data sources\n            data_source_ids = template.get("data_source_ids", [])\n            \n            # Execute query against data sources\n            # In production, this would use actual database connections\n            # For now, return mock data\n            data = await self._mock_query_execution(query, data_source_ids)\n            \n            return {"success": True, "data": data}\n            \n        except Exception as e:\n            return {"success": False, "error": str(e)}\n    \n    async def _mock_query_execution(\n        self,\n        query: str,\n        data_source_ids: List[str],\n    ) -> List[Dict[str, Any]]:\n        """Mock query execution for demonstration."""\n        # In production, this would execute real queries\n        return [\n            {"id": 1, "name": "Item 1", "value": 100},\n            {"id": 2, "name": "Item 2", "value": 200},\n            {"id": 3, "name": "Item 3", "value": 300},\n        ]\n    \n    async def _process_report_data(\n        self,\n        data: List[Dict[str, Any]],\n        template: Dict[str, Any],\n        execution_data: ReportExecutionCreate,\n    ) -> Dict[str, Any]:\n        """Process and transform report data."""\n        processed_data = data\n        \n        # Apply filters\n        filters = template.get("filters", [])\n        for filter_config in filters:\n            processed_data = self._apply_filter(processed_data, filter_config, execution_data.parameter_values)\n        \n        # Apply aggregations\n        aggregations = template.get("aggregations", [])\n        if aggregations:\n            processed_data = self._apply_aggregations(processed_data, aggregations)\n        \n        # Apply grouping\n        groupings = template.get("groupings", [])\n        if groupings:\n            processed_data = self._apply_groupings(processed_data, groupings)\n        \n        # Apply sorting\n        sort_config = template.get("sort_config", [])\n        if sort_config:\n            processed_data = self._apply_sorting(processed_data, sort_config)\n        \n        # Calculate summary\n        summary = self._calculate_summary(processed_data, aggregations)\n        \n        return {\n            "data": processed_data,\n            "summary": summary,\n        }\n    \n    def _apply_filter(\n        self,\n        data: List[Dict[str, Any]],\n        filter_config: Dict[str, Any],\n        parameter_values: Dict[str, Any],\n    ) -> List[Dict[str, Any]]:\n        """Apply a filter to data."""\n        field = filter_config.get("field")\n        operator = filter_config.get("operator", "eq")\n        value = filter_config.get("value")\n        \n        if filter_config.get("parameter_ref"):\n            value = parameter_values.get(filter_config["parameter_ref"])\n        \n        if value is None:\n            return data\n        \n        def matches(row):\n            row_value = row.get(field)\n            \n            if operator == "eq":\n                return row_value == value\n            elif operator == "ne":\n                return row_value != value\n            elif operator == "gt":\n                return row_value > value\n            elif operator == "gte":\n                return row_value >= value\n            elif operator == "lt":\n                return row_value < value\n            elif operator == "lte":\n                return row_value <= value\n            elif operator == "contains":\n                return value in str(row_value)\n            elif operator == "in":\n                return row_value in value\n            elif operator == "not_in":\n                return row_value not in value\n            return True\n        \n        return [row for row in data if matches(row)]\n    \n    def _apply_aggregations(\n        self,\n        data: List[Dict[str, Any]],\n        aggregations: List[Dict[str, Any]],\n    ) -> List[Dict[str, Any]]:\n        """Apply aggregations to data."""\n        if not data:\n            return data\n        \n        result = {}\n        \n        for row in data:\n            for agg in aggregations:\n                field = agg.get("field")\n                agg_type = agg.get("type")\n                alias = agg.get("alias", f"{agg_type}_{field}")\n                \n                if alias not in result:\n                    if agg_type == AggregationType.COUNT.value:\n                        result[alias] = 0\n                    elif agg_type in [AggregationType.SUM.value, AggregationType.AVG.value]:\n                        result[alias] = 0\n                    elif agg_type == AggregationType.MIN.value:\n                        result[alias] = float(\'inf\')\n                    elif agg_type == AggregationType.MAX.value:\n                        result[alias] = float(\'-inf\')\n                \n                value = row.get(field)\n                if value is None:\n                    continue\n                \n                if agg_type == AggregationType.COUNT.value:\n                    result[alias] += 1\n                elif agg_type == AggregationType.SUM.value:\n                    result[alias] += value\n                elif agg_type == AggregationType.MIN.value:\n                    result[alias] = min(result[alias], value)\n                elif agg_type == AggregationType.MAX.value:\n                    result[alias] = max(result[alias], value)\n        \n        # Calculate averages\n        for agg in aggregations:\n            if agg.get("type") == AggregationType.AVG.value:\n                alias = agg.get("alias", f"avg_{agg.get(\'field\')}")\n                count_alias = f"count_{agg.get(\'field\')}"\n                if count_alias in result and result[count_alias] > 0:\n                    result[alias] = result[alias] / result[count_alias]\n        \n        return [result] if result else data\n    \n    def _apply_groupings(\n        self,\n        data: List[Dict[str, Any]],\n        groupings: List[Dict[str, Any]],\n    ) -> List[Dict[str, Any]]:\n        """Apply grouping to data."""\n        if not groupings or not data:\n            return data\n        \n        groups = defaultdict(list)\n        \n        for row in data:\n            key_parts = []\n            for group in groupings:\n                field = group.get("field")\n                key_parts.append(str(row.get(field, "")))\n            key = "|".join(key_parts)\n            groups[key].append(row)\n        \n        result = []\n        for key, rows in groups.items():\n            group_row = {"group_key": key}\n            for i, group in enumerate(groupings):\n                field = group.get("field")\n                group_row[field] = rows[0].get(field)\n            \n            # Calculate aggregations for group\n            group_row["count"] = len(rows)\n            \n            result.append(group_row)\n        \n        return result\n    \n    def _apply_sorting(\n        self,\n        data: List[Dict[str, Any]],\n        sort_config: List[Dict[str, Any]],\n    ) -> List[Dict[str, Any]]:\n        """Apply sorting to data."""\n        if not sort_config or not data:\n            return data\n        \n        for config in reversed(sort_config):\n            field = config.get("field")\n            descending = config.get("descending", False)\n            data = sorted(data, key=lambda x: x.get(field, ""), reverse=descending)\n        \n        return data\n    \n    def _calculate_summary(\n        self,\n        data: List[Dict[str, Any]],\n        aggregations: List[Dict[str, Any]],\n    ) -> Dict[str, Any]:\n        """Calculate summary statistics."""\n        if not data:\n            return {}\n        \n        summary = {\n            "total_rows": len(data),\n            "aggregations": {},\n        }\n        \n        # Calculate each aggregation\n        for agg in aggregations:\n            field = agg.get("field")\n            agg_type = agg.get("type")\n            alias = agg.get("alias", f"{agg_type}_{field}")\n            \n            values = [row.get(field) for row in data if row.get(field) is not None]\n            \n            if agg_type == AggregationType.COUNT.value:\n                summary["aggregations"][alias] = len(values)\n            elif agg_type == AggregationType.SUM.value:\n                summary["aggregations"][alias] = sum(values)\n            elif agg_type == AggregationType.AVG.value:\n                summary["aggregations"][alias] = sum(values) / len(values) if values else 0\n            elif agg_type == AggregationType.MIN.value:\n                summary["aggregations"][alias] = min(values) if values else None\n            elif agg_type == AggregationType.MAX.value:\n                summary["aggregations"][alias] = max(values) if values else None\n        \n        return summary\n    \n    async def _generate_report_output(\n        self,\n        data: Dict[str, Any],\n        template: Dict[str, Any],\n        format: str,\n    ) -> Dict[str, Any]:\n        """Generate report output in specified format."""\n        try:\n            # Create output directory if needed\n            output_dir = f"/tmp/reports/{self.company_id}"\n            import os\n            os.makedirs(output_dir, exist_ok=True)\n            \n            file_name = f"{template.get(\'code\', \'report\')}_{datetime.utcnow().strftime(\'%Y%m%d_%H%M%S\')}"\n            \n            if format == ReportFormat.CSV.value:\n                file_path = await self._generate_csv_output(data, output_dir, file_name)\n            elif format == ReportFormat.EXCEL.value:\n                file_path = await self._generate_excel_output(data, output_dir, file_name)\n            elif format == ReportFormat.PDF.value:\n                file_path = await self._generate_pdf_output(data, template, output_dir, file_name)\n            elif format == ReportFormat.JSON.value:\n                file_path = await self._generate_json_output(data, output_dir, file_name)\n            else:\n                file_path = await self._generate_csv_output(data, output_dir, file_name)\n            \n            file_size = os.path.getsize(file_path) if os.path.exists(file_path) else 0\n            \n            return {\n                "success": True,\n                "file_path": file_path,\n                "file_size": file_size,\n            }\n            \n        except Exception as e:\n            return {\n                "success": False,\n                "error": str(e),\n            }\n    \n    async def _generate_csv_output(\n        self,\n        data: Dict[str, Any],\n        output_dir: str,\n        file_name: str,\n    ) -> str:\n        """Generate CSV output."""\n        file_path = f"{output_dir}/{file_name}.csv"\n        \n        rows = data.get("data", [])\n        if not rows:\n            with open(file_path, \'w\') as f:\n                f.write("")\n            return file_path\n        \n        fieldnames = list(rows[0].keys())\n        \n        with open(file_path, \'w\', newline=\'\') as f:\n            writer = csv.DictWriter(f, fieldnames=fieldnames)\n            writer.writeheader()\n            writer.writerows(rows)\n        \n        return file_path\n    \n    async def _generate_excel_output(\n        self,\n        data: Dict[str, Any],\n        output_dir: str,\n        file_name: str,\n    ) -> str:\n        """Generate Excel output."""\n        # In production, would use openpyxl or xlsxwriter\n        # For now, generate CSV with .xlsx extension hint\n        return await self._generate_csv_output(data, output_dir, file_name)\n    \n    async def _generate_pdf_output(\n        self,\n        data: Dict[str, Any],\n        template: Dict[str, Any],\n        output_dir: str,\n        file_name: str,\n    ) -> str:\n        """Generate PDF output."""\n        # In production, would use reportlab or weasyprint\n        # For now, generate a simple text file\n        file_path = f"{output_dir}/{file_name}.pdf"\n        \n        with open(file_path, \'w\') as f:\n            f.write(f"Report: {template.get(\'name\', \'Unknown\')}\\n")\n            f.write(f"Generated: {datetime.utcnow().isoformat()}\\n")\n            f.write("-" * 50 + "\\n\\n")\n            \n            for row in data.get("data", []):\n                f.write(str(row) + "\\n")\n        \n        return file_path\n    \n    async def _generate_json_output(\n        self,\n        data: Dict[str, Any],\n        output_dir: str,\n        file_name: str,\n    ) -> str:\n        """Generate JSON output."""\n        file_path = f"{output_dir}/{file_name}.json"\n        \n        with open(file_path, \'w\') as f:\n            json.dump(data, f, indent=2, default=str)\n        \n        return file_path\n    \n    async def _convert_report_format(\n        self,\n        execution: Dict[str, Any],\n        target_format: str,\n    ) -> str:\n        """Convert report to different format."""\n        # Get existing results\n        results = await self.repository.find_execution_results(execution["id"])\n        \n        # Generate new output\n        template = await self.repository.find_report_template_by_id(execution["template_id"])\n        output_result = await self._generate_report_output(\n            {"data": results.get("data", []), "summary": results.get("summary", {})},\n            template,\n            target_format\n        )\n        \n        return output_result.get("file_path")\n    \n    def _get_mime_type(self, format: str) -> str:\n        """Get MIME type for format."""\n        mime_types = {\n            ReportFormat.PDF.value: "application/pdf",\n            ReportFormat.EXCEL.value: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",\n            ReportFormat.CSV.value: "text/csv",\n            ReportFormat.JSON.value: "application/json",\n            ReportFormat.HTML.value: "text/html",\n        }\n        return mime_types.get(format, "application/octet-stream")\n    \n    async def _get_file_size(self, file_path: str) -> int:\n        """Get file size in bytes."""\n        import os\n        if file_path and os.path.exists(file_path):\n            return os.path.getsize(file_path)\n        return 0\n    \n    async def _register_schedule(self, schedule: Dict[str, Any]) -> None:\n        """Register schedule with job scheduler."""\n        # In production, would register with scheduler like APScheduler or Celery Beat\n        pass\n    \n    async def _update_schedule_registration(self, schedule: Dict[str, Any]) -> None:\n        """Update schedule registration."""\n        await self._unregister_schedule(schedule)\n        await self._register_schedule(schedule)\n    \n    async def _unregister_schedule(self, schedule: Dict[str, Any]) -> None:\n        """Unregister schedule from job scheduler."""\n        # In production, would unregister from scheduler\n        pass\n    \n    async def _create_dashboard_widget(\n        self,\n        dashboard_id: str,\n        widget_data: Dict[str, Any],\n        user_id: str,\n    ) -> Dict[str, Any]:\n        """Create a dashboard widget."""\n        widget = {\n            "id": str(uuid.uuid4()),\n            "dashboard_id": dashboard_id,\n            "widget_type": widget_data.get("widget_type", WidgetType.TABLE.value),\n            "title": widget_data.get("title"),\n            "data_source": widget_data.get("data_source", {}),\n            "visualization_config": widget_data.get("visualization_config", {}),\n            "position": widget_data.get("position", {}),\n            "size": widget_data.get("size", {}),\n            "filters": widget_data.get("filters", []),\n            "refresh_interval_seconds": widget_data.get("refresh_interval_seconds"),\n            "created_by": user_id,\n            "created_at": datetime.utcnow(),\n        }\n        \n        return await self.repository.create_dashboard_widget(widget)\n    \n    async def _get_widget_data(\n        self,\n        widget: Dict[str, Any],\n        filter_values: Optional[Dict[str, Any]] = None,\n    ) -> Dict[str, Any]:\n        """Get data for a dashboard widget."""\n        data_source = widget.get("data_source", {})\n        \n        if data_source.get("template_id"):\n            # Execute report template\n            template = await self.repository.find_report_template_by_id(data_source["template_id"])\n            if template:\n                result = await self._execute_report_query(\n                    template,\n                    filter_values or {}\n                )\n                return {\n                    "data": result.get("data", []),\n                    "success": result.get("success", True),\n                }\n        \n        elif data_source.get("query"):\n            # Execute direct query\n            result = await self._execute_report_query(\n                {"query": data_source["query"]},\n                filter_values or {}\n            )\n            return {\n                "data": result.get("data", []),\n                "success": result.get("success", True),\n            }\n        \n        # Return mock data for demonstration\n        return {\n            "data": [\n                {"label": "Category A", "value": 100},\n                {"label": "Category B", "value": 200},\n                {"label": "Category C", "value": 150},\n            ],\n            "success": True,\n        }\n    \n    async def _set_default_dashboard(\n        self,\n        dashboard_id: str,\n        user_id: str,\n    ) -> None:\n        """Set a dashboard as the default for the user."""\n        # Remove existing default\n        existing_default = await self.repository.find_user_default_dashboard(user_id)\n        if existing_default:\n            await self.repository.update_dashboard(existing_default["id"], {\n                "is_default": False,\n            })\n        \n        # Set new default\n        await self.repository.update_dashboard(dashboard_id, {\n            "is_default": True,\n        })\n    \n    async def _test_data_source_connection(self, data: DataSourceCreate) -> Dict[str, Any]:\n        """Test data source connection during creation."""\n        # In production, would actually test the connection\n        return {"success": True, "message": "Connection successful"}\n    \n    async def _test_data_source_connection_from_db(self, ds: Dict[str, Any]) -> Dict[str, Any]:\n        """Test data source connection from stored config."""\n        # In production, would actually test the connection\n        return {"success": True, "message": "Connection successful"}\n    \n    async def _is_admin(self, user_id: str) -> bool:\n        """Check if user is an admin."""\n        # In production, would check actual permissions\n        return False\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/services/reports.py\' created successfully.") </create-file> </tool_result>'}
{'role': 'assistant', 'content': 'Excellent! Reports service created. Now I\'ll create the Consolidation service for multi-company accounting and eliminations.\n\n<function_calls>\n<create-file file_path="backend/app/services/consolidation.py">\n"""\nConsolidation Service - Multi-Company Accounting and Eliminations\n\nImplements comprehensive consolidation operations including:\n- Consolidation group management\n- Subsidiary management and ownership structures\n- Intercompany elimination entries\n- Currency translation\n- Minority interest calculations\n- Equity method accounting\n- Consolidation worksheets\n- Automated elimination rules\n"""\n\nfrom typing import Optional, List, Dict, Any, Union\nfrom datetime import datetime, timedelta, date\nfrom decimal import Decimal\nimport uuid\nimport json\nfrom enum import Enum\nfrom collections import defaultdict\n\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom sqlalchemy import select, update, delete, and_, or_, func, case\nfrom sqlalchemy.orm import selectinload, joinedload\n\nfrom .base import (\n    CompanyScopedService,\n    ServiceResult,\n    ServiceError,\n    ValidationError,\n    NotFoundError,\n    PermissionDeniedError,\n    ConflictError,\n    requires_permission,\n    transactional,\n    emit_event,\n    cached,\n    cache_invalidate,\n)\nfrom ..schemas.consolidation import (\n    ConsolidationGroupCreate,\n    ConsolidationGroupUpdate,\n    ConsolidationGroupResponse,\n    ConsolidationGroupListResponse,\n    SubsidiaryCreate,\n    SubsidiaryUpdate,\n    SubsidiaryResponse,\n    SubsidiaryListResponse,\n    EliminationEntryCreate,\n    EliminationEntryResponse,\n    EliminationRuleCreate,\n    EliminationRuleResponse,\n    ConsolidationRunCreate,\n    ConsolidationRunUpdate,\n    ConsolidationRunResponse,\n    ConsolidationRunListResponse,\n    ConsolidationWorksheetResponse,\n    IntercompanyTransactionResponse,\n    CurrencyTranslationResponse,\n    MinorityInterestResponse,\n    OwnershipStructureResponse,\n    ConsolidationStatus,\n    EliminationType,\n    ConsolidationMethod,\n    TranslationMethod,\n)\n\n\nclass ConsolidationService(CompanyScopedService):\n    """\n    Service for managing multi-company consolidation.\n    \n    Handles consolidation groups, subsidiaries, eliminations,\n    and the overall consolidation process.\n    """\n    \n    # ==================== Consolidation Group Management ====================\n    \n    @requires_permission("consolidation:group:create")\n    @transactional\n    @emit_event("consolidation.group.created")\n    async def create_consolidation_group(\n        self,\n        data: ConsolidationGroupCreate,\n        user_id: str,\n    ) -> ServiceResult[ConsolidationGroupResponse]:\n        """\n        Create a consolidation group.\n        \n        Args:\n            data: Group creation data\n            user_id: ID of the user creating\n            \n        Returns:\n            ServiceResult containing the created group\n        """\n        try:\n            # Validate parent company exists\n            parent_company = await self.repository.find_company(data.parent_company_id)\n            if not parent_company:\n                return ServiceResult.error(\n                    NotFoundError(f"Parent company with ID {data.parent_company_id} not found")\n                )\n            \n            # Validate reporting currency\n            if not await self._is_valid_currency(data.reporting_currency):\n                return ServiceResult.error(\n                    ValidationError(f"Invalid reporting currency: {data.reporting_currency}")\n                )\n            \n            group_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "name": data.name,\n                "code": data.code,\n                "description": data.description,\n                "parent_company_id": data.parent_company_id,\n                "reporting_currency": data.reporting_currency,\n                "consolidation_method": data.consolidation_method or ConsolidationMethod.FULL.value,\n                "translation_method": data.translation_method or TranslationMethod.CURRENT_RATE.value,\n                "fiscal_year_end": data.fiscal_year_end,\n                "intercompany_elimination": data.intercompany_elimination if data.intercompany_elimination is not None else True,\n                "minority_interest_tracking": data.minority_interest_tracking if data.minority_interest_tracking is not None else True,\n                "automatic_eliminations": data.automatic_eliminations if data.automatic_eliminations is not None else True,\n                "status": ConsolidationStatus.ACTIVE.value,\n                "settings": data.settings or {},\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n                "updated_at": datetime.utcnow(),\n            }\n            \n            group = await self.repository.create_consolidation_group(group_data)\n            \n            # Create default elimination rules\n            if data.automatic_eliminations:\n                await self._create_default_elimination_rules(group["id"], user_id)\n            \n            return ServiceResult.success(\n                ConsolidationGroupResponse(**group),\n                message="Consolidation group created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create consolidation group: {str(e)}"))\n    \n    @requires_permission("consolidation:group:read")\n    @cached(key_prefix="consolidation_group", ttl=300)\n    async def get_consolidation_group(\n        self,\n        group_id: str,\n    ) -> ServiceResult[ConsolidationGroupResponse]:\n        """\n        Get a consolidation group by ID.\n        \n        Args:\n            group_id: ID of the group\n            \n        Returns:\n            ServiceResult containing the group\n        """\n        try:\n            group = await self.repository.find_consolidation_group_by_id(group_id)\n            if not group:\n                return ServiceResult.error(\n                    NotFoundError(f"Consolidation group with ID {group_id} not found")\n                )\n            \n            return ServiceResult.success(ConsolidationGroupResponse(**group))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get consolidation group: {str(e)}"))\n    \n    @requires_permission("consolidation:group:read")\n    async def list_consolidation_groups(\n        self,\n        status: Optional[str] = None,\n        parent_company_id: Optional[str] = None,\n        search: Optional[str] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[ConsolidationGroupListResponse]:\n        """\n        List consolidation groups.\n        \n        Args:\n            status: Filter by status\n            parent_company_id: Filter by parent company\n            search: Search in name and code\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated groups\n        """\n        try:\n            filters = {}\n            if status:\n                filters["status"] = status\n            if parent_company_id:\n                filters["parent_company_id"] = parent_company_id\n            \n            result = await self.repository.find_consolidation_groups_paginated(\n                filters=filters,\n                search=search,\n                search_fields=["name", "code"],\n                page=page,\n                page_size=page_size,\n                order_by="created_at",\n                order_desc=True,\n            )\n            \n            return ServiceResult.success(\n                ConsolidationGroupListResponse(\n                    items=[ConsolidationGroupResponse(**g) for g in result["items"]],\n                    total=result["total"],\n                    page=page,\n                    page_size=page_size,\n                    total_pages=result["total_pages"],\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list consolidation groups: {str(e)}"))\n    \n    @requires_permission("consolidation:group:update")\n    @transactional\n    @emit_event("consolidation.group.updated")\n    async def update_consolidation_group(\n        self,\n        group_id: str,\n        data: ConsolidationGroupUpdate,\n        user_id: str,\n    ) -> ServiceResult[ConsolidationGroupResponse]:\n        """\n        Update a consolidation group.\n        \n        Args:\n            group_id: ID of the group to update\n            data: Update data\n            user_id: ID of the user updating\n            \n        Returns:\n            ServiceResult containing the updated group\n        """\n        try:\n            group = await self.repository.find_consolidation_group_by_id(group_id)\n            if not group:\n                return ServiceResult.error(\n                    NotFoundError(f"Consolidation group with ID {group_id} not found")\n                )\n            \n            # Check for active consolidation runs\n            active_runs = await self.repository.count_active_runs_for_group(group_id)\n            if active_runs > 0:\n                return ServiceResult.error(\n                    ConflictError("Cannot update group with active consolidation runs")\n                )\n            \n            update_data = data.model_dump(exclude_unset=True)\n            update_data["updated_at"] = datetime.utcnow()\n            update_data["updated_by"] = user_id\n            \n            updated_group = await self.repository.update_consolidation_group(group_id, update_data)\n            \n            await cache_invalidate(f"consolidation_group:{group_id}")\n            \n            return ServiceResult.success(\n                ConsolidationGroupResponse(**updated_group),\n                message="Consolidation group updated successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to update consolidation group: {str(e)}"))\n    \n    @requires_permission("consolidation:group:delete")\n    @transactional\n    @emit_event("consolidation.group.deleted")\n    async def delete_consolidation_group(\n        self,\n        group_id: str,\n        user_id: str,\n    ) -> ServiceResult[bool]:\n        """\n        Delete a consolidation group.\n        \n        Args:\n            group_id: ID of the group to delete\n            user_id: ID of the user deleting\n            \n        Returns:\n            ServiceResult indicating success\n        """\n        try:\n            group = await self.repository.find_consolidation_group_by_id(group_id)\n            if not group:\n                return ServiceResult.error(\n                    NotFoundError(f"Consolidation group with ID {group_id} not found")\n                )\n            \n            # Check for subsidiaries\n            subsidiary_count = await self.repository.count_subsidiaries_for_group(group_id)\n            if subsidiary_count > 0:\n                return ServiceResult.error(\n                    ConflictError(f"Cannot delete group with {subsidiary_count} subsidiaries")\n                )\n            \n            # Archive related data\n            await self.repository.archive_group_elimination_rules(group_id)\n            await self.repository.delete_consolidation_group(group_id)\n            \n            await cache_invalidate(f"consolidation_group:{group_id}")\n            \n            return ServiceResult.success(True, message="Consolidation group deleted successfully")\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to delete consolidation group: {str(e)}"))\n    \n    # ==================== Subsidiary Management ====================\n    \n    @requires_permission("consolidation:subsidiary:create")\n    @transactional\n    @emit_event("consolidation.subsidiary.created")\n    async def create_subsidiary(\n        self,\n        data: SubsidiaryCreate,\n        user_id: str,\n    ) -> ServiceResult[SubsidiaryResponse]:\n        """\n        Add a subsidiary to a consolidation group.\n        \n        Args:\n            data: Subsidiary creation data\n            user_id: ID of the user creating\n            \n        Returns:\n            ServiceResult containing the created subsidiary\n        """\n        try:\n            # Validate consolidation group exists\n            group = await self.repository.find_consolidation_group_by_id(data.consolidation_group_id)\n            if not group:\n                return ServiceResult.error(\n                    NotFoundError(f"Consolidation group with ID {data.consolidation_group_id} not found")\n                )\n            \n            # Validate subsidiary company exists\n            subsidiary_company = await self.repository.find_company(data.subsidiary_company_id)\n            if not subsidiary_company:\n                return ServiceResult.error(\n                    NotFoundError(f"Subsidiary company with ID {data.subsidiary_company_id} not found")\n                )\n            \n            # Check for duplicate subsidiary in group\n            existing = await self.repository.find_subsidiary_in_group(\n                data.consolidation_group_id,\n                data.subsidiary_company_id\n            )\n            if existing:\n                return ServiceResult.error(\n                    ConflictError("Subsidiary already exists in this consolidation group")\n                )\n            \n            # Validate ownership percentage\n            if data.ownership_percentage < 0 or data.ownership_percentage > 100:\n                return ServiceResult.error(\n                    ValidationError("Ownership percentage must be between 0 and 100")\n                )\n            \n            # Determine consolidation method based on ownership\n            consolidation_method = self._determine_consolidation_method(\n                data.ownership_percentage,\n                data.control_type\n            )\n            \n            subsidiary_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "consolidation_group_id": data.consolidation_group_id,\n                "subsidiary_company_id": data.subsidiary_company_id,\n                "ownership_percentage": data.ownership_percentage,\n                "voting_percentage": data.voting_percentage or data.ownership_percentage,\n                "consolidation_method": consolidation_method,\n                "control_type": data.control_type or "full_control",\n                "acquisition_date": data.acquisition_date,\n                "acquisition_cost": float(data.acquisition_cost) if data.acquisition_cost else None,\n                "acquisition_currency": data.acquisition_currency or subsidiary_company.get("base_currency"),\n                "functional_currency": data.functional_currency or subsidiary_company.get("base_currency"),\n                "inclusion_date": data.inclusion_date or datetime.utcnow().date(),\n                "exclusion_date": data.exclusion_date,\n                "is_active": data.is_active if data.is_active is not None else True,\n                "intercompany_accounts": data.intercompany_accounts or {},\n                "settings": data.settings or {},\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n                "updated_at": datetime.utcnow(),\n            }\n            \n            subsidiary = await self.repository.create_subsidiary(subsidiary_data)\n            \n            # Calculate minority interest if applicable\n            if data.ownership_percentage < 100:\n                await self._calculate_initial_minority_interest(subsidiary, user_id)\n            \n            return ServiceResult.success(\n                SubsidiaryResponse(**subsidiary),\n                message="Subsidiary added successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create subsidiary: {str(e)}"))\n    \n    @requires_permission("consolidation:subsidiary:read")\n    async def get_subsidiary(\n        self,\n        subsidiary_id: str,\n    ) -> ServiceResult[SubsidiaryResponse]:\n        """\n        Get a subsidiary by ID.\n        \n        Args:\n            subsidiary_id: ID of the subsidiary\n            \n        Returns:\n            ServiceResult containing the subsidiary\n        """\n        try:\n            subsidiary = await self.repository.find_subsidiary_by_id(subsidiary_id)\n            if not subsidiary:\n                return ServiceResult.error(\n                    NotFoundError(f"Subsidiary with ID {subsidiary_id} not found")\n                )\n            \n            return ServiceResult.success(SubsidiaryResponse(**subsidiary))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get subsidiary: {str(e)}"))\n    \n    @requires_permission("consolidation:subsidiary:read")\n    async def list_subsidiaries(\n        self,\n        consolidation_group_id: Optional[str] = None,\n        is_active: Optional[bool] = None,\n        consolidation_method: Optional[str] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[SubsidiaryListResponse]:\n        """\n        List subsidiaries with filtering.\n        \n        Args:\n            consolidation_group_id: Filter by group ID\n            is_active: Filter by active status\n            consolidation_method: Filter by consolidation method\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated subsidiaries\n        """\n        try:\n            filters = {}\n            if consolidation_group_id:\n                filters["consolidation_group_id"] = consolidation_group_id\n            if is_active is not None:\n                filters["is_active"] = is_active\n            if consolidation_method:\n                filters["consolidation_method"] = consolidation_method\n            \n            result = await self.repository.find_subsidiaries_paginated(\n                filters=filters,\n                page=page,\n                page_size=page_size,\n            )\n            \n            return ServiceResult.success(\n                SubsidiaryListResponse(\n                    items=[SubsidiaryResponse(**s) for s in result["items"]],\n                    total=result["total"],\n                    page=page,\n                    page_size=page_size,\n                    total_pages=result["total_pages"],\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list subsidiaries: {str(e)}"))\n    \n    @requires_permission("consolidation:subsidiary:update")\n    @transactional\n    @emit_event("consolidation.subsidiary.updated")\n    async def update_subsidiary(\n        self,\n        subsidiary_id: str,\n        data: SubsidiaryUpdate,\n        user_id: str,\n    ) -> ServiceResult[SubsidiaryResponse]:\n        """\n        Update a subsidiary.\n        \n        Args:\n            subsidiary_id: ID of the subsidiary to update\n            data: Update data\n            user_id: ID of the user updating\n            \n        Returns:\n            ServiceResult containing the updated subsidiary\n        """\n        try:\n            subsidiary = await self.repository.find_subsidiary_by_id(subsidiary_id)\n            if not subsidiary:\n                return ServiceResult.error(\n                    NotFoundError(f"Subsidiary with ID {subsidiary_id} not found")\n                )\n            \n            update_data = data.model_dump(exclude_unset=True)\n            update_data["updated_at"] = datetime.utcnow()\n            update_data["updated_by"] = user_id\n            \n            # Recalculate consolidation method if ownership changed\n            if "ownership_percentage" in update_data:\n                update_data["consolidation_method"] = self._determine_consolidation_method(\n                    update_data["ownership_percentage"],\n                    update_data.get("control_type", subsidiary.get("control_type"))\n                )\n            \n            updated_subsidiary = await self.repository.update_subsidiary(subsidiary_id, update_data)\n            \n            # Update minority interest if ownership changed\n            if "ownership_percentage" in update_data:\n                await self._recalculate_minority_interest(updated_subsidiary, user_id)\n            \n            return ServiceResult.success(\n                SubsidiaryResponse(**updated_subsidiary),\n                message="Subsidiary updated successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to update subsidiary: {str(e)}"))\n    \n    @requires_permission("consolidation:subsidiary:read")\n    async def get_ownership_structure(\n        self,\n        consolidation_group_id: str,\n    ) -> ServiceResult[OwnershipStructureResponse]:\n        """\n        Get the ownership structure for a consolidation group.\n        \n        Args:\n            consolidation_group_id: ID of the consolidation group\n            \n        Returns:\n            ServiceResult containing the ownership structure\n        """\n        try:\n            group = await self.repository.find_consolidation_group_by_id(consolidation_group_id)\n            if not group:\n                return ServiceResult.error(\n                    NotFoundError(f"Consolidation group with ID {consolidation_group_id} not found")\n                )\n            \n            subsidiaries = await self.repository.find_subsidiaries_for_group(consolidation_group_id)\n            \n            structure = {\n                "group_id": consolidation_group_id,\n                "group_name": group["name"],\n                "parent_company_id": group["parent_company_id"],\n                "subsidiaries": [],\n                "total_ownership_value": Decimal("0"),\n            }\n            \n            for sub in subsidiaries:\n                sub_structure = {\n                    "subsidiary_id": sub["id"],\n                    "company_id": sub["subsidiary_company_id"],\n                    "ownership_percentage": Decimal(str(sub["ownership_percentage"])),\n                    "voting_percentage": Decimal(str(sub.get("voting_percentage", sub["ownership_percentage"]))),\n                    "consolidation_method": sub["consolidation_method"],\n                    "minority_interest_percentage": Decimal("100") - Decimal(str(sub["ownership_percentage"])),\n                    "acquisition_date": sub.get("acquisition_date"),\n                }\n                structure["subsidiaries"].append(sub_structure)\n            \n            return ServiceResult.success(OwnershipStructureResponse(**structure))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get ownership structure: {str(e)}"))\n    \n    # ==================== Consolidation Run Management ====================\n    \n    @requires_permission("consolidation:run:create")\n    @transactional\n    @emit_event("consolidation.run.created")\n    async def create_consolidation_run(\n        self,\n        data: ConsolidationRunCreate,\n        user_id: str,\n    ) -> ServiceResult[ConsolidationRunResponse]:\n        """\n        Create and execute a consolidation run.\n        \n        Args:\n            data: Run creation data\n            user_id: ID of the user creating\n            \n        Returns:\n            ServiceResult containing the created run\n        """\n        try:\n            # Validate consolidation group\n            group = await self.repository.find_consolidation_group_by_id(data.consolidation_group_id)\n            if not group:\n                return ServiceResult.error(\n                    NotFoundError(f"Consolidation group with ID {data.consolidation_group_id} not found")\n                )\n            \n            # Check for overlapping runs\n            overlapping = await self.repository.find_overlapping_runs(\n                data.consolidation_group_id,\n                data.period_start,\n                data.period_end\n            )\n            if overlapping:\n                return ServiceResult.error(\n                    ConflictError("A consolidation run already exists for this period")\n                )\n            \n            # Validate period dates\n            if data.period_end <= data.period_start:\n                return ServiceResult.error(\n                    ValidationError("Period end must be after period start")\n                )\n            \n            run_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "consolidation_group_id": data.consolidation_group_id,\n                "name": data.name or f"Consolidation {data.period_start} to {data.period_end}",\n                "period_start": data.period_start,\n                "period_end": data.period_end,\n                "status": ConsolidationStatus.PENDING.value,\n                "exchange_rates": {},\n                "consolidation_adjustments": [],\n                "elimination_entries": [],\n                "minority_interest_calculations": [],\n                "validation_results": {},\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n            }\n            \n            run = await self.repository.create_consolidation_run(run_data)\n            \n            # Start consolidation process\n            asyncio.create_task(self._execute_consolidation_run(run["id"]))\n            \n            return ServiceResult.success(\n                ConsolidationRunResponse(**run),\n                message="Consolidation run created and started"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create consolidation run: {str(e)}"))\n    \n    @requires_permission("consolidation:run:read")\n    async def get_consolidation_run(\n        self,\n        run_id: str,\n    ) -> ServiceResult[ConsolidationRunResponse]:\n        """\n        Get a consolidation run by ID.\n        \n        Args:\n            run_id: ID of the run\n            \n        Returns:\n            ServiceResult containing the run\n        """\n        try:\n            run = await self.repository.find_consolidation_run_by_id(run_id)\n            if not run:\n                return ServiceResult.error(\n                    NotFoundError(f"Consolidation run with ID {run_id} not found")\n                )\n            \n            return ServiceResult.success(ConsolidationRunResponse(**run))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get consolidation run: {str(e)}"))\n    \n    @requires_permission("consolidation:run:read")\n    async def list_consolidation_runs(\n        self,\n        consolidation_group_id: Optional[str] = None,\n        status: Optional[str] = None,\n        period_start: Optional[date] = None,\n        period_end: Optional[date] = None,\n        page: int = 1,\n        page_size: int = 20,\n    ) -> ServiceResult[ConsolidationRunListResponse]:\n        """\n        List consolidation runs.\n        \n        Args:\n            consolidation_group_id: Filter by group ID\n            status: Filter by status\n            period_start: Filter by period start\n            period_end: Filter by period end\n            page: Page number\n            page_size: Items per page\n            \n        Returns:\n            ServiceResult containing paginated runs\n        """\n        try:\n            filters = {}\n            if consolidation_group_id:\n                filters["consolidation_group_id"] = consolidation_group_id\n            if status:\n                filters["status"] = status\n            \n            date_filters = {}\n            if period_start:\n                date_filters["period_start_gte"] = period_start\n            if period_end:\n                date_filters["period_end_lte"] = period_end\n            \n            result = await self.repository.find_consolidation_runs_paginated(\n                filters=filters,\n                date_filters=date_filters,\n                page=page,\n                page_size=page_size,\n                order_by="created_at",\n                order_desc=True,\n            )\n            \n            return ServiceResult.success(\n                ConsolidationRunListResponse(\n                    items=[ConsolidationRunResponse(**r) for r in result["items"]],\n                    total=result["total"],\n                    page=page,\n                    page_size=page_size,\n                    total_pages=result["total_pages"],\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list consolidation runs: {str(e)}"))\n    \n    @requires_permission("consolidation:run:update")\n    @transactional\n    async def approve_consolidation_run(\n        self,\n        run_id: str,\n        user_id: str,\n        notes: Optional[str] = None,\n    ) -> ServiceResult[ConsolidationRunResponse]:\n        """\n        Approve a consolidation run.\n        \n        Args:\n            run_id: ID of the run to approve\n            user_id: ID of the approver\n            notes: Approval notes\n            \n        Returns:\n            ServiceResult containing the approved run\n        """\n        try:\n            run = await self.repository.find_consolidation_run_by_id(run_id)\n            if not run:\n                return ServiceResult.error(\n                    NotFoundError(f"Consolidation run with ID {run_id} not found")\n                )\n            \n            if run["status"] != ConsolidationStatus.COMPLETED.value:\n                return ServiceResult.error(\n                    ConflictError("Can only approve completed runs")\n                )\n            \n            updated_run = await self.repository.update_consolidation_run(run_id, {\n                "status": ConsolidationStatus.APPROVED.value,\n                "approved_at": datetime.utcnow(),\n                "approved_by": user_id,\n                "approval_notes": notes,\n            })\n            \n            # Post consolidation entries\n            await self._post_consolidation_entries(run_id, user_id)\n            \n            return ServiceResult.success(\n                ConsolidationRunResponse(**updated_run),\n                message="Consolidation run approved"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to approve consolidation run: {str(e)}"))\n    \n    @requires_permission("consolidation:run:update")\n    @transactional\n    async def reverse_consolidation_run(\n        self,\n        run_id: str,\n        user_id: str,\n        reason: str,\n    ) -> ServiceResult[ConsolidationRunResponse]:\n        """\n        Reverse a consolidation run.\n        \n        Args:\n            run_id: ID of the run to reverse\n            user_id: ID of the user reversing\n            reason: Reason for reversal\n            \n        Returns:\n            ServiceResult containing the reversed run\n        """\n        try:\n            run = await self.repository.find_consolidation_run_by_id(run_id)\n            if not run:\n                return ServiceResult.error(\n                    NotFoundError(f"Consolidation run with ID {run_id} not found")\n                )\n            \n            if run["status"] not in [ConsolidationStatus.APPROVED.value, ConsolidationStatus.COMPLETED.value]:\n                return ServiceResult.error(\n                    ConflictError("Can only reverse approved or completed runs")\n                )\n            \n            # Reverse posted entries\n            await self._reverse_consolidation_entries(run_id, user_id)\n            \n            updated_run = await self.repository.update_consolidation_run(run_id, {\n                "status": ConsolidationStatus.REVERSED.value,\n                "reversed_at": datetime.utcnow(),\n                "reversed_by": user_id,\n                "reversal_reason": reason,\n            })\n            \n            return ServiceResult.success(\n                ConsolidationRunResponse(**updated_run),\n                message="Consolidation run reversed"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to reverse consolidation run: {str(e)}"))\n    \n    # ==================== Elimination Management ====================\n    \n    @requires_permission("consolidation:elimination:create")\n    @transactional\n    async def create_elimination_rule(\n        self,\n        data: EliminationRuleCreate,\n        user_id: str,\n    ) -> ServiceResult[EliminationRuleResponse]:\n        """\n        Create an elimination rule.\n        \n        Args:\n            data: Rule creation data\n            user_id: ID of the user creating\n            \n        Returns:\n            ServiceResult containing the created rule\n        """\n        try:\n            # Validate consolidation group\n            group = await self.repository.find_consolidation_group_by_id(data.consolidation_group_id)\n            if not group:\n                return ServiceResult.error(\n                    NotFoundError(f"Consolidation group with ID {data.consolidation_group_id} not found")\n                )\n            \n            rule_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "consolidation_group_id": data.consolidation_group_id,\n                "name": data.name,\n                "description": data.description,\n                "elimination_type": data.elimination_type,\n                "source_accounts": data.source_accounts or [],\n                "target_accounts": data.target_accounts or [],\n                "matching_criteria": data.matching_criteria or {},\n                "offset_account": data.offset_account,\n                "priority": data.priority or 100,\n                "is_active": True,\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n            }\n            \n            rule = await self.repository.create_elimination_rule(rule_data)\n            \n            return ServiceResult.success(\n                EliminationRuleResponse(**rule),\n                message="Elimination rule created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create elimination rule: {str(e)}"))\n    \n    @requires_permission("consolidation:elimination:read")\n    async def list_elimination_rules(\n        self,\n        consolidation_group_id: str,\n        elimination_type: Optional[str] = None,\n        is_active: Optional[bool] = None,\n    ) -> ServiceResult[List[EliminationRuleResponse]]:\n        """\n        List elimination rules for a consolidation group.\n        \n        Args:\n            consolidation_group_id: ID of the consolidation group\n            elimination_type: Filter by type\n            is_active: Filter by active status\n            \n        Returns:\n            ServiceResult containing rules\n        """\n        try:\n            rules = await self.repository.find_elimination_rules(\n                consolidation_group_id=consolidation_group_id,\n                elimination_type=elimination_type,\n                is_active=is_active,\n            )\n            \n            return ServiceResult.success(\n                [EliminationRuleResponse(**r) for r in rules]\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to list elimination rules: {str(e)}"))\n    \n    @requires_permission("consolidation:elimination:create")\n    @transactional\n    async def create_manual_elimination_entry(\n        self,\n        run_id: str,\n        data: EliminationEntryCreate,\n        user_id: str,\n    ) -> ServiceResult[EliminationEntryResponse]:\n        """\n        Create a manual elimination entry.\n        \n        Args:\n            run_id: ID of the consolidation run\n            data: Entry creation data\n            user_id: ID of the user creating\n            \n        Returns:\n            ServiceResult containing the created entry\n        """\n        try:\n            run = await self.repository.find_consolidation_run_by_id(run_id)\n            if not run:\n                return ServiceResult.error(\n                    NotFoundError(f"Consolidation run with ID {run_id} not found")\n                )\n            \n            if run["status"] not in [ConsolidationStatus.PENDING.value, ConsolidationStatus.IN_PROGRESS.value]:\n                return ServiceResult.error(\n                    ConflictError("Can only add entries to pending or in-progress runs")\n                )\n            \n            # Validate balancing\n            total_debits = sum(Decimal(str(line.get("debit", 0))) for line in data.lines)\n            total_credits = sum(Decimal(str(line.get("credit", 0))) for line in data.lines)\n            \n            if total_debits != total_credits:\n                return ServiceResult.error(\n                    ValidationError(f"Entry is not balanced. Debits: {total_debits}, Credits: {total_credits}")\n                )\n            \n            entry_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "run_id": run_id,\n                "entry_type": "manual",\n                "description": data.description,\n                "lines": [line.model_dump() if hasattr(line, \'model_dump\') else line for line in data.lines],\n                "reference": data.reference,\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n            }\n            \n            entry = await self.repository.create_elimination_entry(entry_data)\n            \n            return ServiceResult.success(\n                EliminationEntryResponse(**entry),\n                message="Elimination entry created successfully"\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to create elimination entry: {str(e)}"))\n    \n    @requires_permission("consolidation:elimination:read")\n    async def get_elimination_entries(\n        self,\n        run_id: str,\n    ) -> ServiceResult[List[EliminationEntryResponse]]:\n        """\n        Get elimination entries for a consolidation run.\n        \n        Args:\n            run_id: ID of the consolidation run\n            \n        Returns:\n            ServiceResult containing entries\n        """\n        try:\n            entries = await self.repository.find_elimination_entries_for_run(run_id)\n            \n            return ServiceResult.success(\n                [EliminationEntryResponse(**e) for e in entries]\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get elimination entries: {str(e)}"))\n    \n    # ==================== Intercompany Transaction Management ====================\n    \n    @requires_permission("consolidation:intercompany:read")\n    async def get_intercompany_transactions(\n        self,\n        consolidation_group_id: str,\n        period_start: date,\n        period_end: date,\n    ) -> ServiceResult[List[IntercompanyTransactionResponse]]:\n        """\n        Get intercompany transactions for a period.\n        \n        Args:\n            consolidation_group_id: ID of the consolidation group\n            period_start: Start of period\n            period_end: End of period\n            \n        Returns:\n            ServiceResult containing transactions\n        """\n        try:\n            # Get all subsidiaries in the group\n            subsidiaries = await self.repository.find_subsidiaries_for_group(consolidation_group_id)\n            company_ids = [s["subsidiary_company_id"] for s in subsidiaries]\n            \n            # Get intercompany transactions\n            transactions = await self.repository.find_intercompany_transactions(\n                company_ids=company_ids,\n                period_start=period_start,\n                period_end=period_end,\n            )\n            \n            return ServiceResult.success(\n                [IntercompanyTransactionResponse(**t) for t in transactions]\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get intercompany transactions: {str(e)}"))\n    \n    @requires_permission("consolidation:intercompany:read")\n    async def match_intercompany_transactions(\n        self,\n        consolidation_group_id: str,\n        period_start: date,\n        period_end: date,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Match intercompany transactions for elimination.\n        \n        Args:\n            consolidation_group_id: ID of the consolidation group\n            period_start: Start of period\n            period_end: End of period\n            \n        Returns:\n            ServiceResult containing matching results\n        """\n        try:\n            # Get intercompany transactions\n            transactions = await self.get_intercompany_transactions(\n                consolidation_group_id,\n                period_start,\n                period_end\n            )\n            \n            if not transactions.success:\n                return transactions\n            \n            # Match transactions\n            matched_pairs = []\n            unmatched = []\n            \n            receivables = defaultdict(list)\n            payables = defaultdict(list)\n            \n            for txn in transactions.data:\n                if txn.transaction_type == "receivable":\n                    key = (txn.company_id, txn.counterparty_id, txn.currency, txn.reference)\n                    receivables[key].append(txn)\n                elif txn.transaction_type == "payable":\n                    key = (txn.counterparty_id, txn.company_id, txn.currency, txn.reference)\n                    payables[key].append(txn)\n            \n            # Match receivables with payables\n            for key, recv_list in receivables.items():\n                pay_list = payables.get(key, [])\n                \n                for recv in recv_list:\n                    matched = False\n                    for pay in pay_list:\n                        if abs(recv.amount - pay.amount) < Decimal("0.01"):\n                            matched_pairs.append({\n                                "receivable": recv.model_dump() if hasattr(recv, \'model_dump\') else recv,\n                                "payable": pay.model_dump() if hasattr(pay, \'model_dump\') else pay,\n                                "amount": recv.amount,\n                                "currency": recv.currency,\n                            })\n                            pay_list.remove(pay)\n                            matched = True\n                            break\n                    \n                    if not matched:\n                        unmatched.append(recv)\n            \n            # Add remaining unmatched payables\n            for pay_list in payables.values():\n                unmatched.extend(pay_list)\n            \n            return ServiceResult.success({\n                "matched_count": len(matched_pairs),\n                "unmatched_count": len(unmatched),\n                "matched_pairs": matched_pairs,\n                "unmatched_transactions": [t.model_dump() if hasattr(t, \'model_dump\') else t for t in unmatched],\n                "total_matched_value": sum(p["amount"] for p in matched_pairs),\n            })\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to match intercompany transactions: {str(e)}"))\n    \n    # ==================== Currency Translation ====================\n    \n    @requires_permission("consolidation:translation:read")\n    async def get_exchange_rates(\n        self,\n        from_currency: str,\n        to_currency: str,\n        rate_date: Optional[date] = None,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Get exchange rate between currencies.\n        \n        Args:\n            from_currency: Source currency\n            to_currency: Target currency\n            rate_date: Date for the rate (default: today)\n            \n        Returns:\n            ServiceResult containing exchange rate\n        """\n        try:\n            rate_date = rate_date or datetime.utcnow().date()\n            \n            rate = await self.repository.find_exchange_rate(\n                from_currency=from_currency,\n                to_currency=to_currency,\n                rate_date=rate_date,\n            )\n            \n            if not rate:\n                # Try inverse rate\n                inverse_rate = await self.repository.find_exchange_rate(\n                    from_currency=to_currency,\n                    to_currency=from_currency,\n                    rate_date=rate_date,\n                )\n                \n                if inverse_rate:\n                    rate = {\n                        "from_currency": from_currency,\n                        "to_currency": to_currency,\n                        "rate": Decimal("1") / Decimal(str(inverse_rate.get("rate", 1))),\n                        "rate_date": rate_date,\n                    }\n                else:\n                    return ServiceResult.error(\n                        NotFoundError(f"No exchange rate found for {from_currency} to {to_currency} on {rate_date}")\n                    )\n            \n            return ServiceResult.success(rate)\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get exchange rate: {str(e)}"))\n    \n    @requires_permission("consolidation:translation:create")\n    @transactional\n    async def set_exchange_rate(\n        self,\n        from_currency: str,\n        to_currency: str,\n        rate: Decimal,\n        rate_date: date,\n        user_id: str,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Set an exchange rate.\n        \n        Args:\n            from_currency: Source currency\n            to_currency: Target currency\n            rate: Exchange rate\n            rate_date: Date for the rate\n            user_id: ID of the user setting\n            \n        Returns:\n            ServiceResult containing the set rate\n        """\n        try:\n            if rate <= 0:\n                return ServiceResult.error(\n                    ValidationError("Exchange rate must be positive")\n                )\n            \n            rate_data = {\n                "id": str(uuid.uuid4()),\n                "company_id": self.company_id,\n                "from_currency": from_currency,\n                "to_currency": to_currency,\n                "rate": float(rate),\n                "rate_date": rate_date,\n                "created_by": user_id,\n                "created_at": datetime.utcnow(),\n            }\n            \n            created_rate = await self.repository.create_exchange_rate(rate_data)\n            \n            return ServiceResult.success(created_rate, message="Exchange rate set successfully")\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to set exchange rate: {str(e)}"))\n    \n    @requires_permission("consolidation:translation:read")\n    async def translate_amounts(\n        self,\n        amounts: List[Dict[str, Any]],\n        target_currency: str,\n        translation_date: date,\n        translation_method: str = TranslationMethod.CURRENT_RATE.value,\n    ) -> ServiceResult[CurrencyTranslationResponse]:\n        """\n        Translate amounts to target currency.\n        \n        Args:\n            amounts: List of amounts to translate\n            target_currency: Target currency\n            translation_date: Date for translation\n            translation_method: Translation method to use\n            \n        Returns:\n            ServiceResult containing translated amounts\n        """\n        try:\n            translated_amounts = []\n            total_translation_adjustment = Decimal("0")\n            \n            for amount_data in amounts:\n                source_currency = amount_data.get("currency")\n                source_amount = Decimal(str(amount_data.get("amount", 0)))\n                \n                if source_currency == target_currency:\n                    translated_amounts.append({\n                        **amount_data,\n                        "translated_amount": source_amount,\n                        "exchange_rate": Decimal("1"),\n                        "translation_adjustment": Decimal("0"),\n                    })\n                    continue\n                \n                # Get appropriate rate based on method\n                if translation_method == TranslationMethod.CURRENT_RATE.value:\n                    rate_result = await self.get_exchange_rates(\n                        source_currency, target_currency, translation_date\n                    )\n                elif translation_method == TranslationMethod.TEMPORAL.value:\n                    # Use historical rate for non-monetary items\n                    rate_date = amount_data.get("transaction_date", translation_date)\n                    rate_result = await self.get_exchange_rates(\n                        source_currency, target_currency, rate_date\n                    )\n                else:\n                    rate_result = await self.get_exchange_rates(\n                        source_currency, target_currency, translation_date\n                    )\n                \n                if not rate_result.success:\n                    return ServiceResult.error(\n                        ValidationError(f"Could not translate {source_currency} to {target_currency}")\n                    )\n                \n                exchange_rate = Decimal(str(rate_result.data.get("rate", 1)))\n                translated_amount = source_amount * exchange_rate\n                \n                # Calculate translation adjustment if there was a previous translation\n                previous_translated = amount_data.get("previously_translated_amount")\n                adjustment = Decimal("0")\n                if previous_translated:\n                    adjustment = translated_amount - Decimal(str(previous_translated))\n                    total_translation_adjustment += adjustment\n                \n                translated_amounts.append({\n                    **amount_data,\n                    "translated_amount": translated_amount,\n                    "exchange_rate": exchange_rate,\n                    "translation_adjustment": adjustment,\n                })\n            \n            return ServiceResult.success(\n                CurrencyTranslationResponse(\n                    target_currency=target_currency,\n                    translation_date=translation_date,\n                    translation_method=translation_method,\n                    translated_amounts=translated_amounts,\n                    total_translation_adjustment=total_translation_adjustment,\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to translate amounts: {str(e)}"))\n    \n    # ==================== Minority Interest ====================\n    \n    @requires_permission("consolidation:minority:read")\n    async def calculate_minority_interest(\n        self,\n        subsidiary_id: str,\n        period_end: date,\n    ) -> ServiceResult[MinorityInterestResponse]:\n        """\n        Calculate minority interest for a subsidiary.\n        \n        Args:\n            subsidiary_id: ID of the subsidiary\n            period_end: End of period for calculation\n            \n        Returns:\n            ServiceResult containing minority interest calculation\n        """\n        try:\n            subsidiary = await self.repository.find_subsidiary_by_id(subsidiary_id)\n            if not subsidiary:\n                return ServiceResult.error(\n                    NotFoundError(f"Subsidiary with ID {subsidiary_id} not found")\n                )\n            \n            ownership_pct = Decimal(str(subsidiary["ownership_percentage"]))\n            minority_pct = Decimal("100") - ownership_pct\n            \n            if minority_pct <= 0:\n                return ServiceResult.success(\n                    MinorityInterestResponse(\n                        subsidiary_id=subsidiary_id,\n                        period_end=period_end,\n                        minority_percentage=Decimal("0"),\n                        net_income_minority=Decimal("0"),\n                        equity_minority=Decimal("0"),\n                    )\n                )\n            \n            # Get subsidiary\'s net income and equity\n            financial_data = await self.repository.get_subsidiary_financial_data(\n                subsidiary["subsidiary_company_id"],\n                period_end\n            )\n            \n            net_income = Decimal(str(financial_data.get("net_income", 0)))\n            total_equity = Decimal(str(financial_data.get("total_equity", 0)))\n            \n            # Calculate minority share\n            net_income_minority = (net_income * minority_pct) / Decimal("100")\n            equity_minority = (total_equity * minority_pct) / Decimal("100")\n            \n            return ServiceResult.success(\n                MinorityInterestResponse(\n                    subsidiary_id=subsidiary_id,\n                    period_end=period_end,\n                    minority_percentage=minority_pct,\n                    net_income_minority=net_income_minority,\n                    equity_minority=equity_minority,\n                    calculation_details={\n                        "subsidiary_net_income": net_income,\n                        "subsidiary_total_equity": total_equity,\n                        "ownership_percentage": ownership_pct,\n                    },\n                )\n            )\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to calculate minority interest: {str(e)}"))\n    \n    @requires_permission("consolidation:minority:read")\n    async def get_minority_interest_summary(\n        self,\n        consolidation_group_id: str,\n        period_end: date,\n    ) -> ServiceResult[Dict[str, Any]]:\n        """\n        Get minority interest summary for all subsidiaries.\n        \n        Args:\n            consolidation_group_id: ID of the consolidation group\n            period_end: End of period for calculation\n            \n        Returns:\n            ServiceResult containing summary\n        """\n        try:\n            subsidiaries = await self.repository.find_subsidiaries_for_group(consolidation_group_id)\n            \n            summary = {\n                "consolidation_group_id": consolidation_group_id,\n                "period_end": period_end,\n                "subsidiaries": [],\n                "total_net_income_minority": Decimal("0"),\n                "total_equity_minority": Decimal("0"),\n            }\n            \n            for sub in subsidiaries:\n                if sub["ownership_percentage"] >= 100:\n                    continue\n                \n                mi_result = await self.calculate_minority_interest(sub["id"], period_end)\n                \n                if mi_result.success:\n                    summary["subsidiaries"].append(mi_result.data.model_dump() if hasattr(mi_result.data, \'model_dump\') else mi_result.data)\n                    summary["total_net_income_minority"] += mi_result.data.net_income_minority\n                    summary["total_equity_minority"] += mi_result.data.equity_minority\n            \n            return ServiceResult.success(summary)\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get minority interest summary: {str(e)}"))\n    \n    # ==================== Consolidation Worksheet ====================\n    \n    @requires_permission("consolidation:worksheet:read")\n    async def get_consolidation_worksheet(\n        self,\n        run_id: str,\n    ) -> ServiceResult[ConsolidationWorksheetResponse]:\n        """\n        Get the consolidation worksheet for a run.\n        \n        Args:\n            run_id: ID of the consolidation run\n            \n        Returns:\n            ServiceResult containing the worksheet\n        """\n        try:\n            run = await self.repository.find_consolidation_run_by_id(run_id)\n            if not run:\n                return ServiceResult.error(\n                    NotFoundError(f"Consolidation run with ID {run_id} not found")\n                )\n            \n            group = await self.repository.find_consolidation_group_by_id(run["consolidation_group_id"])\n            subsidiaries = await self.repository.find_subsidiaries_for_group(run["consolidation_group_id"])\n            \n            # Get trial balance for each entity\n            worksheet_data = {\n                "run_id": run_id,\n                "period_start": run["period_start"],\n                "period_end": run["period_end"],\n                "reporting_currency": group["reporting_currency"],\n                "entities": [],\n                "eliminations": [],\n                "consolidated": {},\n            }\n            \n            # Get parent company trial balance\n            parent_tb = await self._get_trial_balance(\n                group["parent_company_id"],\n                run["period_end"]\n            )\n            worksheet_data["entities"].append({\n                "entity_id": group["parent_company_id"],\n                "entity_name": "Parent",\n                "is_parent": True,\n                "trial_balance": parent_tb,\n            })\n            \n            # Get subsidiary trial balances\n            for sub in subsidiaries:\n                sub_tb = await self._get_trial_balance(\n                    sub["subsidiary_company_id"],\n                    run["period_end"]\n                )\n                \n                # Translate if different currency\n                if sub.get("functional_currency") != group["reporting_currency"]:\n                    sub_tb = await self._translate_trial_balance(\n                        sub_tb,\n                        sub.get("functional_currency"),\n                        group["reporting_currency"],\n                        run["period_end"]\n                    )\n                \n                worksheet_data["entities"].append({\n                    "entity_id": sub["subsidiary_company_id"],\n                    "entity_name": sub.get("name", "Subsidiary"),\n                    "is_parent": False,\n                    "ownership_percentage": sub["ownership_percentage"],\n                    "trial_balance": sub_tb,\n                })\n            \n            # Get elimination entries\n            eliminations = await self.repository.find_elimination_entries_for_run(run_id)\n            worksheet_data["eliminations"] = eliminations\n            \n            # Calculate consolidated totals\n            worksheet_data["consolidated"] = await self._calculate_consolidated_totals(worksheet_data)\n            \n            return ServiceResult.success(ConsolidationWorksheetResponse(**worksheet_data))\n            \n        except Exception as e:\n            return ServiceResult.error(ServiceError(f"Failed to get consolidation worksheet: {str(e)}"))\n    \n    # ==================== Private Helper Methods ====================\n    \n    def _determine_consolidation_method(\n        self,\n        ownership_percentage: Decimal,\n        control_type: Optional[str] = None,\n    ) -> str:\n        """Determine consolidation method based on ownership."""\n        if ownership_percentage >= 100:\n            return ConsolidationMethod.FULL.value\n        elif ownership_percentage >= 50:\n            return ConsolidationMethod.FULL.value\n        elif ownership_percentage >= 20:\n            return ConsolidationMethod.EQUITY.value\n        elif ownership_percentage > 0:\n            return ConsolidationMethod.COST.value\n        else:\n            return ConsolidationMethod.NONE.value\n    \n    async def _is_valid_currency(self, currency: str) -> bool:\n        """Validate currency code."""\n        valid_currencies = [\n            "USD", "EUR", "GBP", "JPY", "CAD", "AUD", "CHF", "CNY", "INR",\n            "MXN", "BRL", "KRW", "SGD", "HKD", "NOK", "SEK", "DKK", "NZD",\n            "ZAR", "RUB", "TRY", "PLN", "THB", "IDR", "MYR", "PHP", "TWD",\n        ]\n        return currency.upper() in valid_currencies\n    \n    async def _create_default_elimination_rules(\n        self,\n        group_id: str,\n        user_id: str,\n    ) -> None:\n        """Create default elimination rules for a group."""\n        default_rules = [\n            {\n                "name": "Intercompany Receivables/Payables",\n                "elimination_type": EliminationType.INTERCOMPANY.value,\n                "source_accounts": ["AR-DUE-FROM", "IC-RECEIVABLE"],\n                "target_accounts": ["AP-DUE-TO", "IC-PAYABLE"],\n                "matching_criteria": {"counterparty_match": True},\n                "priority": 10,\n            },\n            {\n                "name": "Intercompany Revenue/Expense",\n                "elimination_type": EliminationType.INTERCOMPANY.value,\n                "source_accounts": ["IC-REVENUE", "IC-SALES"],\n                "target_accounts": ["IC-EXPENSE", "IC-COGS"],\n                "matching_criteria": {"counterparty_match": True},\n                "priority": 20,\n            },\n            {\n                "name": "Intercompany Inventory Profit",\n                "elimination_type": EliminationType.INVENTORY_PROFIT.value,\n                "source_accounts": ["INVENTORY"],\n                "target_accounts": ["COGS"],\n                "matching_criteria": {"unrealized_profit": True},\n                "priority": 30,\n            },\n            {\n                "name": "Intercompany Dividends",\n                "elimination_type": EliminationType.DIVIDENDS.value,\n                "source_accounts": ["DIVIDEND-INCOME"],\n                "target_accounts": ["RETAINED-EARNINGS"],\n                "matching_criteria": {"subsidiary_match": True},\n                "priority": 40,\n            },\n        ]\n        \n        for rule in default_rules:\n            await self.create_elimination_rule(\n                EliminationRuleCreate(\n                    consolidation_group_id=group_id,\n                    **rule\n                ),\n                user_id\n            )\n    \n    async def _calculate_initial_minority_interest(\n        self,\n        subsidiary: Dict[str, Any],\n        user_id: str,\n    ) -> None:\n        """Calculate initial minority interest for a subsidiary."""\n        # This would calculate the initial MI at acquisition\n        pass\n    \n    async def _recalculate_minority_interest(\n        self,\n        subsidiary: Dict[str, Any],\n        user_id: str,\n    ) -> None:\n        """Recalculate minority interest after ownership change."""\n        # This would recalculate MI based on new ownership\n        pass\n    \n    async def _execute_consolidation_run(self, run_id: str) -> None:\n        """Execute the consolidation process."""\n        import asyncio\n        \n        try:\n            # Update status to in progress\n            await self.repository.update_consolidation_run(run_id, {\n                "status": ConsolidationStatus.IN_PROGRESS.value,\n                "started_at": datetime.utcnow(),\n            })\n            \n            run = await self.repository.find_consolidation_run_by_id(run_id)\n            group = await self.repository.find_consolidation_group_by_id(run["consolidation_group_id"])\n            \n            # Step 1: Gather trial balances\n            await self._consolidation_step(run_id, "gathering_data")\n            trial_balances = await self._gather_trial_balances(run, group)\n            \n            # Step 2: Currency translation\n            await self._consolidation_step(run_id, "currency_translation")\n            translated_tbs = await self._translate_trial_balances(trial_balances, group, run)\n            \n            # Step 3: Identify intercompany transactions\n            await self._consolidation_step(run_id, "identifying_intercompany")\n            intercompany = await self._identify_intercompany_transactions(run, group)\n            \n            # Step 4: Generate elimination entries\n            await self._consolidation_step(run_id, "generating_eliminations")\n            eliminations = await self._generate_elimination_entries(run_id, group, intercompany, user_id="system")\n            \n            # Step 5: Calculate minority interest\n            await self._consolidation_step(run_id, "calculating_mi")\n            mi_calculations = await self._calculate_all_minority_interest(run, group)\n            \n            # Step 6: Create consolidated financial statements\n            await self._consolidation_step(run_id, "creating_statements")\n            statements = await self._create_consolidated_statements(run_id, group, translated_tbs, eliminations, mi_calculations)\n            \n            # Step 7: Validation\n            await self._consolidation_step(run_id, "validation")\n            validation_results = await self._validate_consolidation(run_id)\n            \n            if not validation_results.get("valid"):\n                await self.repository.update_consolidation_run(run_id, {\n                    "status": ConsolidationStatus.FAILED.value,\n                    "completed_at": datetime.utcnow(),\n                    "error_message": "Validation failed",\n                    "validation_results": validation_results,\n                })\n                return\n            \n            # Update run with results\n            await self.repository.update_consolidation_run(run_id, {\n                "status": ConsolidationStatus.COMPLETED.value,\n                "completed_at": datetime.utcnow(),\n                "elimination_entries": eliminations,\n                "minority_interest_calculations": mi_calculations,\n                "validation_results": validation_results,\n            })\n            \n        except Exception as e:\n            await self.repository.update_consolidation_run(run_id, {\n                "status": ConsolidationStatus.FAILED.value,\n                "completed_at": datetime.utcnow(),\n                "error_message": str(e),\n            })\n    \n    async def _consolidation_step(self, run_id: str, step: str) -> None:\n        """Update consolidation run step."""\n        await self.repository.update_consolidation_run(run_id, {\n            "current_step": step,\n            "step_started_at": datetime.utcnow(),\n        })\n    \n    async def _gather_trial_balances(\n        self,\n        run: Dict[str, Any],\n        group: Dict[str, Any],\n    ) -> List[Dict[str, Any]]:\n        """Gather trial balances for all entities."""\n        trial_balances = []\n        \n        # Parent company\n        parent_tb = await self._get_trial_balance(\n            group["parent_company_id"],\n            run["period_end"]\n        )\n        trial_balances.append({\n            "entity_id": group["parent_company_id"],\n            "is_parent": True,\n            "trial_balance": parent_tb,\n        })\n        \n        # Subsidiaries\n        subsidiaries = await self.repository.find_subsidiaries_for_group(group["id"])\n        for sub in subsidiaries:\n            sub_tb = await self._get_trial_balance(\n                sub["subsidiary_company_id"],\n                run["period_end"]\n            )\n            trial_balances.append({\n                "entity_id": sub["subsidiary_company_id"],\n                "is_parent": False,\n                "subsidiary_id": sub["id"],\n                "ownership_percentage": sub["ownership_percentage"],\n                "functional_currency": sub.get("functional_currency"),\n                "trial_balance": sub_tb,\n            })\n        \n        return trial_balances\n    \n    async def _get_trial_balance(\n        self,\n        company_id: str,\n        period_end: date,\n    ) -> Dict[str, Any]:\n        """Get trial balance for a company."""\n        # This would integrate with the account service\n        return {\n            "company_id": company_id,\n            "period_end": period_end,\n            "accounts": [],\n            "total_debits": Decimal("0"),\n            "total_credits": Decimal("0"),\n        }\n    \n    async def _translate_trial_balances(\n        self,\n        trial_balances: List[Dict[str, Any]],\n        group: Dict[str, Any],\n        run: Dict[str, Any],\n    ) -> List[Dict[str, Any]]:\n        """Translate trial balances to reporting currency."""\n        reporting_currency = group["reporting_currency"]\n        translation_method = group.get("translation_method", TranslationMethod.CURRENT_RATE.value)\n        \n        translated = []\n        for tb in trial_balances:\n            if tb.get("functional_currency") == reporting_currency:\n                translated.append(tb)\n                continue\n            \n            translated_tb = await self._translate_trial_balance(\n                tb["trial_balance"],\n                tb.get("functional_currency"),\n                reporting_currency,\n                run["period_end"]\n            )\n            \n            tb["trial_balance"] = translated_tb\n            tb["translation_adjustment"] = translated_tb.get("translation_adjustment", Decimal("0"))\n            translated.append(tb)\n        \n        return translated\n    \n    async def _translate_trial_balance(\n        self,\n        trial_balance: Dict[str, Any],\n        from_currency: str,\n        to_currency: str,\n        translation_date: date,\n    ) -> Dict[str, Any]:\n        """Translate a trial balance to another currency."""\n        if from_currency == to_currency:\n            return trial_balance\n        \n        # Get exchange rate\n        rate_result = await self.get_exchange_rates(from_currency, to_currency, translation_date)\n        \n        if not rate_result.success:\n            return trial_balance\n        \n        rate = Decimal(str(rate_result.data.get("rate", 1)))\n        \n        translated_accounts = []\n        translation_adjustment = Decimal("0")\n        \n        for account in trial_balance.get("accounts", []):\n            translated_balance = Decimal(str(account.get("balance", 0))) * rate\n            translated_accounts.append({\n                **account,\n                "original_balance": account.get("balance"),\n                "translated_balance": translated_balance,\n                "exchange_rate": rate,\n            })\n            \n            # Track CTA for certain account types\n            # This is a simplified calculation\n        \n        return {\n            **trial_balance,\n            "accounts": translated_accounts,\n            "translation_adjustment": translation_adjustment,\n            "exchange_rate": rate,\n        }\n    \n    async def _identify_intercompany_transactions(\n        self,\n        run: Dict[str, Any],\n        group: Dict[str, Any],\n    ) -> Dict[str, Any]:\n        """Identify intercompany transactions for elimination."""\n        result = await self.get_intercompany_transactions(\n            group["id"],\n            run["period_start"],\n            run["period_end"]\n        )\n        \n        return result.data if result.success else {}\n    \n    async def _generate_elimination_entries(\n        self,\n        run_id: str,\n        group: Dict[str, Any],\n        intercompany: Dict[str, Any],\n        user_id: str,\n    ) -> List[Dict[str, Any]]:\n        """Generate elimination entries."""\n        entries = []\n        \n        # Get elimination rules\n        rules = await self.repository.find_elimination_rules(\n            consolidation_group_id=group["id"],\n            is_active=True,\n        )\n        \n        # Sort by priority\n        rules = sorted(rules, key=lambda r: r.get("priority", 100))\n        \n        # Apply each rule\n        for rule in rules:\n            rule_entries = await self._apply_elimination_rule(run_id, rule, intercompany)\n            entries.extend(rule_entries)\n        \n        return entries\n    \n    async def _apply_elimination_rule(\n        self,\n        run_id: str,\n        rule: Dict[str, Any],\n        intercompany: Dict[str, Any],\n    ) -> List[Dict[str, Any]]:\n        """Apply an elimination rule and generate entries."""\n        entries = []\n        \n        # This would implement the actual elimination logic\n        # based on the rule type and matching criteria\n        \n        return entries\n    \n    async def _calculate_all_minority_interest(\n        self,\n        run: Dict[str, Any],\n        group: Dict[str, Any],\n    ) -> List[Dict[str, Any]]:\n        """Calculate minority interest for all subsidiaries."""\n        calculations = []\n        \n        subsidiaries = await self.repository.find_subsidiaries_for_group(group["id"])\n        \n        for sub in subsidiaries:\n            if sub["ownership_percentage"] < 100:\n                mi_result = await self.calculate_minority_interest(\n                    sub["id"],\n                    run["period_end"]\n                )\n                \n                if mi_result.success:\n                    calculations.append(mi_result.data.model_dump() if hasattr(mi_result.data, \'model_dump\') else mi_result.data)\n        \n        return calculations\n    \n    async def _create_consolidated_statements(\n        self,\n        run_id: str,\n        group: Dict[str, Any],\n        trial_balances: List[Dict[str, Any]],\n        eliminations: List[Dict[str, Any]],\n        mi_calculations: List[Dict[str, Any]],\n    ) -> Dict[str, Any]:\n        """Create consolidated financial statements."""\n        # This would generate the actual financial statements\n        return {\n            "balance_sheet": {},\n            "income_statement": {},\n            "cash_flow": {},\n            "equity_statement": {},\n        }\n    \n    async def _validate_consolidation(self, run_id: str) -> Dict[str, Any]:\n        """Validate consolidation results."""\n        errors = []\n        warnings = []\n        \n        run = await self.repository.find_consolidation_run_by_id(run_id)\n        \n        # Check balance sheet balances\n        # Check elimination completeness\n        # Check minority interest calculations\n        \n        return {\n            "valid": len(errors) == 0,\n            "errors": errors,\n            "warnings": warnings,\n        }\n    \n    async def _post_consolidation_entries(self, run_id: str, user_id: str) -> None:\n        """Post consolidation entries to the general ledger."""\n        entries = await self.repository.find_elimination_entries_for_run(run_id)\n        \n        for entry in entries:\n            # Post each entry to the GL\n            pass\n    \n    async def _reverse_consolidation_entries(self, run_id: str, user_id: str) -> None:\n        """Reverse posted consolidation entries."""\n        # Reverse all posted entries\n        pass\n    \n    async def _calculate_consolidated_totals(\n        self,\n        worksheet_data: Dict[str, Any],\n    ) -> Dict[str, Any]:\n        """Calculate consolidated totals for the worksheet."""\n        consolidated = {\n            "accounts": {},\n            "total_debits": Decimal("0"),\n            "total_credits": Decimal("0"),\n        }\n        \n        # Sum all entity balances\n        for entity in worksheet_data.get("entities", []):\n            tb = entity.get("trial_balance", {})\n            for account in tb.get("accounts", []):\n                account_id = account.get("account_id")\n                if account_id not in consolidated["accounts"]:\n                    consolidated["accounts"][account_id] = {\n                        "account_id": account_id,\n                        "account_name": account.get("account_name"),\n                        "balance": Decimal("0"),\n                    }\n                consolidated["accounts"][account_id]["balance"] += Decimal(str(account.get("balance", 0)))\n        \n        # Apply eliminations\n        for elimination in worksheet_data.get("eliminations", []):\n            for line in elimination.get("lines", []):\n                account_id = line.get("account_id")\n                if account_id in consolidated["accounts"]:\n                    debit = Decimal(str(line.get("debit", 0)))\n                    credit = Decimal(str(line.get("credit", 0)))\n                    \n                    # Eliminations reduce the balance\n                    if consolidated["accounts"][account_id].get("is_debit"):\n                        consolidated["accounts"][account_id]["balance"] -= debit - credit\n                    else:\n                        consolidated["accounts"][account_id]["balance"] -= credit - debit\n        \n        return consolidated\n\n\n# Import asyncio for async task\nimport asyncio\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/services/consolidation.py\' created successfully.") </create-file> </tool_result>'}