{'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 this conversation:\n\n1. The conversation started with a context continuation from a previous session that ran out of context.\n\n2. The summary provided details about building "SAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM (SESOS v6 OMEGA CONSTRAINED)" - a comprehensive enterprise software system with 27+ core modules.\n\n3. Technical stack established:\n   - Backend: FastAPI (Python)\n   - Frontend: Next.js (React)\n   - Database: PostgreSQL\n   - Cache/Event Bus: Redis\n   - APIs: REST + GraphQL\n   - Infrastructure: Docker + Kubernetes\n   - Architecture: DDD, Modular monolith, Event-driven, CQRS-compatible\n\n4. Previous session had created:\n   - Backend core files (config.py, database.py, redis_client.py, security.py, events.py, exceptions.py)\n   - Models (base.py, company.py, user.py, accounting.py, treasury.py, invoice.py, hr.py, tax.py, workflow.py, legal.py, reports.py, consolidation.py, asset.py, ai.py)\n   - Schemas (all modules)\n   - Base repository, Company repository, User repository\n\n5. My actions in this session:\n   - Checked todo.md to see current progress\n   - Updated todo.md to mark completed repositories\n   - Created backend/app/repositories/account.py - Account repository with comprehensive accounting operations\n   - Created backend/app/repositories/treasury.py - Treasury repository for banking operations\n   - Created backend/app/repositories/invoice.py - Invoice repository for billing operations\n   - Created backend/app/repositories/hr.py - HR repository for human resources and payroll\n   - Created backend/app/repositories/tax.py - Tax repository for tax engine operations\n   - Created backend/app/repositories/workflow.py - Workflow repository for automation engine\n   - Created backend/app/repositories/legal.py - Legal repository for contract lifecycle management\n\n6. User messages:\n   - Initial context continuation message with detailed requirements\n   - "Do tool calling warmup" at start\n   - "Please continue where you left off."\n\n7. Pending tasks from todo.md:\n   - Reports repository\n   - Consolidation repository\n   - Asset repository\n   - AI repository\n   - Repositories __init__.py\n   - Then Phase 2: Services layer, Phase 3: Frontend, etc.\n\n8. No errors were encountered - all files created successfully.\n\n9. The most recent work was creating the legal.py repository file with contract lifecycle management, litigation, IP assets, and compliance operations.\n\n\n\n\nSummary:\n1. Primary Request and Intent:\n   The user requested building a complete enterprise-grade software system called "SAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM (SESOS v6 OMEGA CONSTRAINED)" with:\n   - 27 core modules including Company Management, Accounting (double-entry), Treasury, HR/Payroll, Tax Engine, Legal/Contracts, Workflow Engine, Consolidation, AI/Analytics, etc.\n   - Full ERP functionality with CRUD operations on all pages\n   - Multi-company hierarchy with holding/subsidiary support\n   - AI-driven features: anomaly detection, cash flow forecasting, contract analysis, recommendations\n   - Visual workflow builder with triggers\n   - Fixed Asset Registry with depreciation\n   - Tax nexus engine with cross-jurisdictional calculations\n   - Intercompany reconciliation\n   - PDF report generation\n   - World-class UI/UX with Command Center, Dashboard Builder, AI Copilot\n   - Full security: JWT, RBAC+ABAC, field-level permissions, audit logs\n   - Event-driven architecture with versioning/time machine\n   \n   User explicitly stated: "Generate EVERY file sequentially... Each file MUST include: full imports, full implementation, no placeholders"\n\n2. Key Technical Concepts:\n   - FastAPI (Python) backend with async SQLAlchemy\n   - Next.js (React) frontend\n   - PostgreSQL database with SQLAlchemy async ORM\n   - Redis for caching and event bus\n   - Domain-Driven Design (DDD)\n   - Event-driven architecture with event sourcing\n   - CQRS-compatible design\n   - JWT authentication with RBAC/ABAC\n   - Double-entry bookkeeping\n   - Multi-tenancy with company scoping\n   - Generic repository pattern with type hints\n   - Pydantic v2 for schema validation\n   - Soft delete and versioning patterns\n\n3. Files and Code Sections:\n\n   - **todo.md** - Task tracking file\n     - Updated to mark Base, Company, User repositories as complete\n     - Tracks all phases of development\n\n   - **backend/app/repositories/account.py** - Account Repository (created)\n     - Chart of Accounts management with hierarchical tree structure\n     - Fiscal year and period management\n     - Journal entry operations with posting, validation, reversal\n     - Account balance tracking and calculations\n     - Cost center management\n     - Budget vs actual comparisons\n     - Bank reconciliation\n     - Key classes: AccountTypeRepository, AccountRepository, FiscalYearRepository, JournalEntryRepository, etc.\n\n   - **backend/app/repositories/treasury.py** - Treasury Repository (created)\n     - Bank and bank account management\n     - Cash pool operations with concentration\n     - Treasury transactions and cash flow\n     - Bank reconciliation\n     - Investment portfolio management\n     - Credit facilities and drawdowns\n     - FX rates and hedging\n     - Treasury forecasting\n     - Payment batch processing\n     - Key classes: BankRepository, BankAccountRepository, CashPoolRepository, TreasuryTransactionRepository, etc.\n\n   - **backend/app/repositories/invoice.py** - Invoice Repository (created)\n     - Invoice templates and invoice CRUD\n     - Invoice lines and taxes\n     - Recurring invoices with automatic generation\n     - Payment recording and tracking\n     - Credit memos and applications\n     - Dunning letters generation\n     - Invoice aging calculations\n     - AR/AP summaries\n     - Approval workflow\n     - Key classes: InvoiceRepository, RecurringInvoiceRepository, InvoicePaymentRepository, DunningLetterRepository, etc.\n\n   - **backend/app/repositories/hr.py** - HR Repository (created)\n     - Department hierarchy management\n     - Job positions\n     - Employee lifecycle (hire, terminate, birthdays, anniversaries)\n     - Payroll periods, runs, and payslips\n     - Leave types, balances, and requests\n     - Attendance tracking\n     - Performance reviews\n     - Expense reports\n     - Training and benefits\n     - Key classes: DepartmentRepository, EmployeeRepository, PayrollRunRepository, LeaveRequestRepository, AttendanceRepository, etc.\n\n   - **backend/app/repositories/tax.py** - Tax Repository (created)\n     - Tax jurisdictions and types\n     - Tax rates with effective dates\n     - Tax registrations\n     - Tax transactions and calculations\n     - Tax filings and payments\n     - Tax exemptions\n     - Nexus rules and determinations\n     - Tax audits\n     - Key classes: TaxJurisdictionRepository, TaxRateRepository, TaxFilingRepository, NexusDeterminationRepository, etc.\n\n   - **backend/app/repositories/workflow.py** - Workflow Repository (created)\n     - Workflow definitions with versions\n     - Workflow nodes and transitions\n     - Workflow instances with state management\n     - Task assignments and completion\n     - Triggers and conditions\n     - Schedulers with run tracking\n     - Notification templates and notifications\n     - Automation rules\n     - Key classes: WorkflowDefinitionRepository, WorkflowInstanceRepository, TriggerRepository, SchedulerRepository, etc.\n\n   - **backend/app/repositories/legal.py** - Legal Repository (created)\n     - Contract types and contracts\n     - Contract parties, documents, signatures\n     - Contract milestones and renewals\n     - Legal entities and matters\n     - Litigation management\n     - Legal holds\n     - IP assets and registrations\n     - Compliance programs and assessments\n     - Key classes: ContractRepository, LegalMatterRepository, LitigationRepository, IPAssetRepository, ComplianceProgramRepository, etc.\n\n4. Errors and fixes:\n   - No errors were encountered during this session. All files were created successfully.\n\n5. Problem Solving:\n   - Successfully implemented comprehensive repository patterns for multiple business domains\n   - Implemented proper company scoping using CompanyScopedRepository base class\n   - Created consistent repository patterns with full CRUD operations\n   - Implemented soft delete, eager loading, and pagination patterns\n   - Added domain-specific methods for each repository (e.g., balance calculations, workflow transitions, tax nexus tracking)\n\n6. All user messages:\n   - "Do tool calling warmup"\n   - [Context continuation message with detailed system requirements from previous session]\n   - "Please continue where you left off."\n\n7. Pending Tasks:\n   From todo.md Phase 1.6 - Backend Repositories (remaining):\n   - [ ] Reports repository\n   - [ ] Consolidation repository\n   - [ ] Asset repository\n   - [ ] AI repository\n   - [ ] Repositories __init__.py\n   \n   Remaining phases:\n   - Phase 2: Core Backend Modules (services layer)\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 `backend/app/repositories/legal.py` with comprehensive contract lifecycle management, legal matters, litigation, IP assets, and compliance functionality. Was working through Phase 1.6 (Backend Repositories) sequentially.\n\n   Files created in this session:\n   - backend/app/repositories/account.py\n   - backend/app/repositories/treasury.py\n   - backend/app/repositories/invoice.py\n   - backend/app/repositories/hr.py\n   - backend/app/repositories/tax.py\n   - backend/app/repositories/workflow.py\n   - backend/app/repositories/legal.py\n\n9. Optional Next Step:\n   Continue creating the remaining repository files for Phase 1.6. The next repositories to create are:\n   - Reports repository (for reports and dashboard operations)\n   - Consolidation repository (for intercompany reconciliation)\n   - Asset repository (for fixed asset registry)\n   - AI repository (for AI and analytics features)\n   - Then create the Repositories __init__.py to export all repositories\n\n   The pattern established is: Each repository file contains multiple repository classes for related models, extending BaseRepository or CompanyScopedRepository, with comprehensive domain-specific methods.\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_1775263143_5905.txt'}
{'role': 'assistant', 'content': "I'll continue building the SESOS enterprise system. Let me first check the current state of the todo.md and then continue with the remaining repositories.\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- [ ] Account repository\\\\n- [ ] Journal entry repository\\\\n- [ ] Treasury repository\\\\n- [ ] Invoice repository\\\\n- [ ] HR repository\\\\n- [ ] Tax repository\\\\n- [ ] Workflow repository\\\\n- [ ] Legal repository\\\\n- [ ] Reports repository\\\\n- [ ] Consolidation repository\\\\n- [ ] Asset repository\\\\n- [ ] AI repository\\\\n- [ ] Repositories __init__.py\\\\n\\\\n## Phase 2: Core Backend Modules\\\\n- [ ] Company Management\\\\n- [ ] Dynamic Schema Builder\\\\n- [ ] Organizational Structure Engine\\\\n- [ ] Accounting System (double-entry)\\\\n- [ ] Treasury + Banking\\\\n- [ ] Revenue + Billing\\\\n- [ ] HR + Payroll\\\\n- [ ] Tax Engine\\\\n- [ ] Legal + Contract Lifecycle Management\\\\n- [ ] Governance + Policy Engine\\\\n- [ ] Workflow + Automation Engine\\\\n- [ ] Consolidation Engine\\\\n- [ ] Integration Hub\\\\n- [ ] Document + Data Room System\\\\n- [ ] Analytics + AI Engine\\\\n- [ ] Permissions + Security\\\\n- [ ] Event System\\\\n- [ ] Versioning + Time Machine\\\\n- [ ] Plugin System\\\\n- [ ] Simulation Engine\\\\n- [ ] Internal Marketplace\\\\n- [ ] Dashboard System\\\\n- [ ] Audit System\\\\n- [ ] Feature Flag System\\\\n- [ ] Localization\\\\n- [ ] SDK Generator\\\\n- [ ] CLI / Admin Terminal\\\\n- [ ] Reports Module\\\\n- [ ] Fixed Asset Registry\\\\n- [ ] Cash Flow Forecasting\\\\n- [ ] Tax Nexus Engine\\\\n- [ ] Intercompany Reconciliation\\\\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 is out of sync with what was actually completed in the previous session. According to the summary, the Account, Treasury, Invoice, HR, Tax, Workflow, and Legal repositories were all created. Let me verify and then continue with the remaining repositories.\n\n<function_calls>\n<execute-command>\nls -la backend/app/repositories/\n</execute-command>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <execute-command> ToolResult(success=True, output=\'{\\n  "output": "total 372\\\\ndrwxr-xr-x  2 root root  4096 Apr  4 00:37 .\\\\ndrwxr-xr-x 13 root root  4096 Apr  1 20:24 ..\\\\n-rw-r--r--  1 root root 47785 Apr  4 00:06 account.py\\\\n-rw-r--r--  1 root root 22634 Apr  3 23:54 base.py\\\\n-rw-r--r--  1 root root 17761 Apr  3 23:56 company.py\\\\n-rw-r--r--  1 root root 47284 Apr  4 00:18 hr.py\\\\n-rw-r--r--  1 root root 35706 Apr  4 00:13 invoice.py\\\\n-rw-r--r--  1 root root 41494 Apr  4 00:37 legal.py\\\\n-rw-r--r--  1 root root 35443 Apr  4 00:22 tax.py\\\\n-rw-r--r--  1 root root 43722 Apr  4 00:10 treasury.py\\\\n-rw-r--r--  1 root root 24403 Apr  3 23:59 user.py\\\\n-rw-r--r--  1 root root 39254 Apr  4 00:26 workflow.py\\\\n",\\n  "exit_code": 0,\\n  "cwd": "/workspace"\\n}\') </execute-command> </tool_result>'}
{'role': 'assistant', 'content': 'The repositories are all there. Let me update the todo.md to reflect the actual progress and then continue with the remaining repositories.\n\n<function_calls>\n<str-replace file_path="todo.md">\n<old_str>## Phase 1.6: Backend Repositories (Data Access Layer)\n- [x] Base repository\n- [x] Company repository\n- [x] User repository\n- [ ] Account repository\n- [ ] Journal entry repository\n- [ ] Treasury repository\n- [ ] Invoice repository\n- [ ] HR repository\n- [ ] Tax repository\n- [ ] Workflow repository\n- [ ] Legal repository\n- [ ] Reports repository\n- [ ] Consolidation repository\n- [ ] Asset repository\n- [ ] AI repository\n- [ ] Repositories __init__.py</old_str>\n<new_str>## 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- [ ] Reports repository\n- [ ] Consolidation repository\n- [ ] Asset repository\n- [ ] AI repository\n- [ ] Repositories __init__.py</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 let me create the Reports repository. This will handle report templates, reports, dashboards, widgets, and data sources.\n\n<function_calls>\n<create-file file_path="backend/app/repositories/reports.py">\n"""\nSAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM - Reports Repository\nComprehensive data access layer for reports, dashboards, and analytics.\n"""\n\nfrom datetime import datetime, date, timedelta\nfrom decimal import Decimal\nfrom typing import Optional, List, Dict, Any, Tuple, Union\nfrom uuid import UUID\nimport json\n\nfrom sqlalchemy import (\n    select, update, delete, and_, or_, func, case, desc, asc,\n    literal_column, text, cast, String, Integer, Numeric, Date, DateTime\n)\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom sqlalchemy.orm import selectinload, joinedload, contains_eager\nfrom sqlalchemy.dialects.postgresql import JSONB\n\nfrom .base import BaseRepository, CompanyScopedRepository\nfrom ..models.reports import (\n    ReportTemplate, Report, ReportExecution, Dashboard, DashboardWidget,\n    DataSource, DataQuery, ScheduledReport, ReportDistribution\n)\nfrom ..models.base import ReportStatus, ExecutionStatus\n\n\nclass ReportTemplateRepository(CompanyScopedRepository[ReportTemplate]):\n    """Repository for report template operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, ReportTemplate)\n    \n    async def get_by_code(self, company_id: UUID, code: str) -> Optional[ReportTemplate]:\n        """Get template by code within a company."""\n        result = await self.session.execute(\n            select(ReportTemplate).where(\n                and_(\n                    ReportTemplate.company_id == company_id,\n                    ReportTemplate.code == code,\n                    ReportTemplate.is_deleted == False\n                )\n            )\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_by_category(\n        self, \n        company_id: UUID, \n        category: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[ReportTemplate]:\n        """Get templates by category."""\n        result = await self.session.execute(\n            select(ReportTemplate)\n            .where(\n                and_(\n                    ReportTemplate.company_id == company_id,\n                    ReportTemplate.category == category,\n                    ReportTemplate.is_deleted == False\n                )\n            )\n            .order_by(ReportTemplate.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_active_templates(\n        self, \n        company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[ReportTemplate]:\n        """Get all active templates."""\n        result = await self.session.execute(\n            select(ReportTemplate)\n            .where(\n                and_(\n                    ReportTemplate.company_id == company_id,\n                    ReportTemplate.is_active == True,\n                    ReportTemplate.is_deleted == False\n                )\n            )\n            .order_by(ReportTemplate.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_templates_with_parameters(\n        self, \n        company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[ReportTemplate]:\n        """Get templates that have parameters defined."""\n        result = await self.session.execute(\n            select(ReportTemplate)\n            .where(\n                and_(\n                    ReportTemplate.company_id == company_id,\n                    ReportTemplate.parameters.isnot(None),\n                    func.jsonb_array_length(ReportTemplate.parameters) > 0,\n                    ReportTemplate.is_deleted == False\n                )\n            )\n            .order_by(ReportTemplate.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def search_templates(\n        self,\n        company_id: UUID,\n        search_term: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[ReportTemplate]:\n        """Search templates by name or description."""\n        search_pattern = f"%{search_term}%"\n        result = await self.session.execute(\n            select(ReportTemplate)\n            .where(\n                and_(\n                    ReportTemplate.company_id == company_id,\n                    or_(\n                        ReportTemplate.name.ilike(search_pattern),\n                        ReportTemplate.description.ilike(search_pattern),\n                        ReportTemplate.code.ilike(search_pattern)\n                    ),\n                    ReportTemplate.is_deleted == False\n                )\n            )\n            .order_by(ReportTemplate.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def duplicate_template(\n        self,\n        template_id: UUID,\n        new_name: str,\n        new_code: str,\n        created_by: UUID\n    ) -> ReportTemplate:\n        """Duplicate a template with a new name and code."""\n        original = await self.get(template_id)\n        if not original:\n            raise ValueError(f"Template {template_id} not found")\n        \n        new_template = ReportTemplate(\n            company_id=original.company_id,\n            name=new_name,\n            code=new_code,\n            description=original.description,\n            category=original.category,\n            template_type=original.template_type,\n            definition=original.definition,\n            parameters=original.parameters,\n            output_formats=original.output_formats,\n            is_active=True,\n            created_by=created_by\n        )\n        self.session.add(new_template)\n        await self.session.flush()\n        return new_template\n    \n    async def get_categories(self, company_id: UUID) -> List[str]:\n        """Get list of unique categories used by templates."""\n        result = await self.session.execute(\n            select(ReportTemplate.category)\n            .where(\n                and_(\n                    ReportTemplate.company_id == company_id,\n                    ReportTemplate.category.isnot(None),\n                    ReportTemplate.is_deleted == False\n                )\n            )\n            .distinct()\n            .order_by(ReportTemplate.category)\n        )\n        return [row[0] for row in result.all()]\n\n\nclass ReportRepository(CompanyScopedRepository[Report]):\n    """Repository for report instance operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, Report)\n    \n    async def get_with_template(self, report_id: UUID) -> Optional[Report]:\n        """Get report with template loaded."""\n        result = await self.session.execute(\n            select(Report)\n            .options(selectinload(Report.template))\n            .where(Report.id == report_id)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_by_status(\n        self,\n        company_id: UUID,\n        status: ReportStatus,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[Report]:\n        """Get reports by status."""\n        result = await self.session.execute(\n            select(Report)\n            .where(\n                and_(\n                    Report.company_id == company_id,\n                    Report.status == status,\n                    Report.is_deleted == False\n                )\n            )\n            .order_by(desc(Report.created_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_template(\n        self,\n        company_id: UUID,\n        template_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[Report]:\n        """Get all reports generated from a template."""\n        result = await self.session.execute(\n            select(Report)\n            .where(\n                and_(\n                    Report.company_id == company_id,\n                    Report.template_id == template_id,\n                    Report.is_deleted == False\n                )\n            )\n            .order_by(desc(Report.created_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_date_range(\n        self,\n        company_id: UUID,\n        start_date: date,\n        end_date: date,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[Report]:\n        """Get reports generated within a date range."""\n        result = await self.session.execute(\n            select(Report)\n            .where(\n                and_(\n                    Report.company_id == company_id,\n                    func.date(Report.created_at) >= start_date,\n                    func.date(Report.created_at) <= end_date,\n                    Report.is_deleted == False\n                )\n            )\n            .order_by(desc(Report.created_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_creator(\n        self,\n        company_id: UUID,\n        created_by: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[Report]:\n        """Get reports created by a specific user."""\n        result = await self.session.execute(\n            select(Report)\n            .where(\n                and_(\n                    Report.company_id == company_id,\n                    Report.created_by == created_by,\n                    Report.is_deleted == False\n                )\n            )\n            .order_by(desc(Report.created_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def update_status(\n        self, \n        report_id: UUID, \n        status: ReportStatus,\n        error_message: Optional[str] = None\n    ) -> Optional[Report]:\n        """Update report status."""\n        report = await self.get(report_id)\n        if not report:\n            return None\n        \n        report.status = status\n        if error_message:\n            report.error_message = error_message\n        if status == ReportStatus.COMPLETED:\n            report.completed_at = datetime.utcnow()\n        \n        await self.session.flush()\n        return report\n    \n    async def archive_report(self, report_id: UUID) -> Optional[Report]:\n        """Archive a report."""\n        report = await self.get(report_id)\n        if not report:\n            return None\n        \n        report.status = ReportStatus.ARCHIVED\n        await self.session.flush()\n        return report\n    \n    async def get_recent_reports(\n        self,\n        company_id: UUID,\n        days: int = 30,\n        limit: int = 10\n    ) -> List[Report]:\n        """Get most recent reports within specified days."""\n        cutoff = datetime.utcnow() - timedelta(days=days)\n        result = await self.session.execute(\n            select(Report)\n            .where(\n                and_(\n                    Report.company_id == company_id,\n                    Report.created_at >= cutoff,\n                    Report.is_deleted == False\n                )\n            )\n            .order_by(desc(Report.created_at))\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def count_by_status(self, company_id: UUID) -> Dict[str, int]:\n        """Get count of reports by status."""\n        result = await self.session.execute(\n            select(\n                Report.status,\n                func.count(Report.id)\n            )\n            .where(\n                and_(\n                    Report.company_id == company_id,\n                    Report.is_deleted == False\n                )\n            )\n            .group_by(Report.status)\n        )\n        return {row[0].value: row[1] for row in result.all()}\n\n\nclass ReportExecutionRepository(BaseRepository[ReportExecution]):\n    """Repository for report execution tracking."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, ReportExecution)\n    \n    async def get_by_report(self, report_id: UUID) -> List[ReportExecution]:\n        """Get all executions for a report."""\n        result = await self.session.execute(\n            select(ReportExecution)\n            .where(ReportExecution.report_id == report_id)\n            .order_by(desc(ReportExecution.started_at))\n        )\n        return list(result.scalars().all())\n    \n    async def get_latest_execution(self, report_id: UUID) -> Optional[ReportExecution]:\n        """Get the most recent execution for a report."""\n        result = await self.session.execute(\n            select(ReportExecution)\n            .where(ReportExecution.report_id == report_id)\n            .order_by(desc(ReportExecution.started_at))\n            .limit(1)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_running_executions(self) -> List[ReportExecution]:\n        """Get all currently running executions."""\n        result = await self.session.execute(\n            select(ReportExecution)\n            .where(ReportExecution.status == ExecutionStatus.RUNNING)\n            .order_by(ReportExecution.started_at)\n        )\n        return list(result.scalars().all())\n    \n    async def get_failed_executions(\n        self,\n        since: Optional[datetime] = None,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[ReportExecution]:\n        """Get failed executions, optionally since a specific time."""\n        query = select(ReportExecution).where(\n            ReportExecution.status == ExecutionStatus.FAILED\n        )\n        if since:\n            query = query.where(ReportExecution.started_at >= since)\n        \n        query = query.order_by(desc(ReportExecution.started_at)).offset(skip).limit(limit)\n        result = await self.session.execute(query)\n        return list(result.scalars().all())\n    \n    async def create_execution(\n        self,\n        report_id: UUID,\n        triggered_by: UUID,\n        parameters: Optional[Dict[str, Any]] = None\n    ) -> ReportExecution:\n        """Create a new execution record."""\n        execution = ReportExecution(\n            report_id=report_id,\n            triggered_by=triggered_by,\n            parameters=parameters or {},\n            status=ExecutionStatus.PENDING,\n            started_at=datetime.utcnow()\n        )\n        self.session.add(execution)\n        await self.session.flush()\n        return execution\n    \n    async def start_execution(self, execution_id: UUID) -> Optional[ReportExecution]:\n        """Mark execution as started."""\n        execution = await self.get(execution_id)\n        if not execution:\n            return None\n        \n        execution.status = ExecutionStatus.RUNNING\n        execution.started_at = datetime.utcnow()\n        await self.session.flush()\n        return execution\n    \n    async def complete_execution(\n        self,\n        execution_id: UUID,\n        output_path: Optional[str] = None,\n        row_count: Optional[int] = None,\n        file_size: Optional[int] = None\n    ) -> Optional[ReportExecution]:\n        """Mark execution as completed."""\n        execution = await self.get(execution_id)\n        if not execution:\n            return None\n        \n        execution.status = ExecutionStatus.COMPLETED\n        execution.completed_at = datetime.utcnow()\n        if execution.started_at:\n            execution.duration_seconds = int(\n                (execution.completed_at - execution.started_at).total_seconds()\n            )\n        execution.output_path = output_path\n        execution.row_count = row_count\n        execution.file_size = file_size\n        await self.session.flush()\n        return execution\n    \n    async def fail_execution(\n        self,\n        execution_id: UUID,\n        error_message: str\n    ) -> Optional[ReportExecution]:\n        """Mark execution as failed."""\n        execution = await self.get(execution_id)\n        if not execution:\n            return None\n        \n        execution.status = ExecutionStatus.FAILED\n        execution.completed_at = datetime.utcnow()\n        if execution.started_at:\n            execution.duration_seconds = int(\n                (execution.completed_at - execution.started_at).total_seconds()\n            )\n        execution.error_message = error_message\n        await self.session.flush()\n        return execution\n    \n    async def get_execution_statistics(\n        self,\n        report_id: Optional[UUID] = None,\n        days: int = 30\n    ) -> Dict[str, Any]:\n        """Get execution statistics."""\n        cutoff = datetime.utcnow() - timedelta(days=days)\n        \n        base_query = select(\n            ReportExecution.status,\n            func.count(ReportExecution.id).label(\'count\'),\n            func.avg(ReportExecution.duration_seconds).label(\'avg_duration\'),\n            func.max(ReportExecution.duration_seconds).label(\'max_duration\'),\n            func.min(ReportExecution.duration_seconds).label(\'min_duration\')\n        ).where(ReportExecution.started_at >= cutoff)\n        \n        if report_id:\n            base_query = base_query.where(ReportExecution.report_id == report_id)\n        \n        result = await self.session.execute(\n            base_query.group_by(ReportExecution.status)\n        )\n        \n        stats = {\n            \'by_status\': {},\n            \'total_count\': 0,\n            \'average_duration\': 0,\n            \'max_duration\': 0,\n            \'min_duration\': 0\n        }\n        \n        rows = result.all()\n        for row in rows:\n            stats[\'by_status\'][row[0].value] = {\n                \'count\': row[1],\n                \'avg_duration\': float(row[2] or 0),\n                \'max_duration\': row[3],\n                \'min_duration\': row[4]\n            }\n            stats[\'total_count\'] += row[1]\n        \n        return stats\n\n\nclass DashboardRepository(CompanyScopedRepository[Dashboard]):\n    """Repository for dashboard operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, Dashboard)\n    \n    async def get_with_widgets(self, dashboard_id: UUID) -> Optional[Dashboard]:\n        """Get dashboard with all widgets loaded."""\n        result = await self.session.execute(\n            select(Dashboard)\n            .options(selectinload(Dashboard.widgets))\n            .where(Dashboard.id == dashboard_id)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_by_owner(\n        self,\n        company_id: UUID,\n        owner_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[Dashboard]:\n        """Get dashboards owned by a user."""\n        result = await self.session.execute(\n            select(Dashboard)\n            .where(\n                and_(\n                    Dashboard.company_id == company_id,\n                    Dashboard.owner_id == owner_id,\n                    Dashboard.is_deleted == False\n                )\n            )\n            .order_by(Dashboard.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_public_dashboards(\n        self,\n        company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[Dashboard]:\n        """Get public dashboards."""\n        result = await self.session.execute(\n            select(Dashboard)\n            .where(\n                and_(\n                    Dashboard.company_id == company_id,\n                    Dashboard.is_public == True,\n                    Dashboard.is_deleted == False\n                )\n            )\n            .order_by(Dashboard.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_featured_dashboards(\n        self,\n        company_id: UUID,\n        limit: int = 10\n    ) -> List[Dashboard]:\n        """Get featured dashboards for the company."""\n        result = await self.session.execute(\n            select(Dashboard)\n            .where(\n                and_(\n                    Dashboard.company_id == company_id,\n                    Dashboard.is_featured == True,\n                    Dashboard.is_deleted == False\n                )\n            )\n            .order_by(Dashboard.name)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def search_dashboards(\n        self,\n        company_id: UUID,\n        search_term: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[Dashboard]:\n        """Search dashboards by name or description."""\n        search_pattern = f"%{search_term}%"\n        result = await self.session.execute(\n            select(Dashboard)\n            .where(\n                and_(\n                    Dashboard.company_id == company_id,\n                    or_(\n                        Dashboard.name.ilike(search_pattern),\n                        Dashboard.description.ilike(search_pattern)\n                    ),\n                    Dashboard.is_deleted == False\n                )\n            )\n            .order_by(Dashboard.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def set_featured(\n        self, \n        dashboard_id: UUID, \n        featured: bool = True\n    ) -> Optional[Dashboard]:\n        """Set dashboard as featured."""\n        dashboard = await self.get(dashboard_id)\n        if not dashboard:\n            return None\n        \n        dashboard.is_featured = featured\n        await self.session.flush()\n        return dashboard\n    \n    async def update_layout(\n        self,\n        dashboard_id: UUID,\n        layout: Dict[str, Any]\n    ) -> Optional[Dashboard]:\n        """Update dashboard layout configuration."""\n        dashboard = await self.get(dashboard_id)\n        if not dashboard:\n            return None\n        \n        dashboard.layout_config = layout\n        dashboard.updated_at = datetime.utcnow()\n        await self.session.flush()\n        return dashboard\n    \n    async def clone_dashboard(\n        self,\n        dashboard_id: UUID,\n        new_name: str,\n        owner_id: UUID\n    ) -> Dashboard:\n        """Clone a dashboard with all widgets."""\n        original = await self.get_with_widgets(dashboard_id)\n        if not original:\n            raise ValueError(f"Dashboard {dashboard_id} not found")\n        \n        new_dashboard = Dashboard(\n            company_id=original.company_id,\n            name=new_name,\n            description=original.description,\n            owner_id=owner_id,\n            layout_config=original.layout_config,\n            filters=original.filters,\n            refresh_interval=original.refresh_interval,\n            is_public=False,\n            is_featured=False\n        )\n        self.session.add(new_dashboard)\n        await self.session.flush()\n        \n        # Clone widgets\n        for widget in original.widgets:\n            new_widget = DashboardWidget(\n                dashboard_id=new_dashboard.id,\n                widget_type=widget.widget_type,\n                title=widget.title,\n                position=widget.position,\n                size=widget.size,\n                config=widget.config,\n                data_source=widget.data_source\n            )\n            self.session.add(new_widget)\n        \n        await self.session.flush()\n        return new_dashboard\n    \n    async def get_dashboard_count_by_owner(\n        self, \n        company_id: UUID\n    ) -> Dict[str, int]:\n        """Get count of dashboards per owner."""\n        result = await self.session.execute(\n            select(\n                Dashboard.owner_id,\n                func.count(Dashboard.id)\n            )\n            .where(\n                and_(\n                    Dashboard.company_id == company_id,\n                    Dashboard.is_deleted == False\n                )\n            )\n            .group_by(Dashboard.owner_id)\n        )\n        return {str(row[0]): row[1] for row in result.all()}\n\n\nclass DashboardWidgetRepository(BaseRepository[DashboardWidget]):\n    """Repository for dashboard widget operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, DashboardWidget)\n    \n    async def get_by_dashboard(self, dashboard_id: UUID) -> List[DashboardWidget]:\n        """Get all widgets for a dashboard."""\n        result = await self.session.execute(\n            select(DashboardWidget)\n            .where(DashboardWidget.dashboard_id == dashboard_id)\n            .order_by(DashboardWidget.position)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_type(\n        self,\n        dashboard_id: UUID,\n        widget_type: str\n    ) -> List[DashboardWidget]:\n        """Get widgets by type within a dashboard."""\n        result = await self.session.execute(\n            select(DashboardWidget)\n            .where(\n                and_(\n                    DashboardWidget.dashboard_id == dashboard_id,\n                    DashboardWidget.widget_type == widget_type\n                )\n            )\n            .order_by(DashboardWidget.position)\n        )\n        return list(result.scalars().all())\n    \n    async def create_widget(\n        self,\n        dashboard_id: UUID,\n        widget_type: str,\n        title: str,\n        position: int,\n        size: Dict[str, int],\n        config: Optional[Dict[str, Any]] = None,\n        data_source: Optional[Dict[str, Any]] = None\n    ) -> DashboardWidget:\n        """Create a new widget."""\n        widget = DashboardWidget(\n            dashboard_id=dashboard_id,\n            widget_type=widget_type,\n            title=title,\n            position=position,\n            size=size,\n            config=config or {},\n            data_source=data_source or {}\n        )\n        self.session.add(widget)\n        await self.session.flush()\n        return widget\n    \n    async def update_position(\n        self,\n        widget_id: UUID,\n        position: int,\n        size: Optional[Dict[str, int]] = None\n    ) -> Optional[DashboardWidget]:\n        """Update widget position and optionally size."""\n        widget = await self.get(widget_id)\n        if not widget:\n            return None\n        \n        widget.position = position\n        if size:\n            widget.size = size\n        widget.updated_at = datetime.utcnow()\n        await self.session.flush()\n        return widget\n    \n    async def reorder_widgets(\n        self,\n        dashboard_id: UUID,\n        widget_order: List[UUID]\n    ) -> List[DashboardWidget]:\n        """Reorder widgets based on provided order."""\n        widgets = await self.get_by_dashboard(dashboard_id)\n        widget_map = {w.id: w for w in widgets}\n        \n        for index, widget_id in enumerate(widget_order):\n            if widget_id in widget_map:\n                widget_map[widget_id].position = index\n        \n        await self.session.flush()\n        return await self.get_by_dashboard(dashboard_id)\n    \n    async def delete_by_dashboard(self, dashboard_id: UUID) -> int:\n        """Delete all widgets for a dashboard."""\n        result = await self.session.execute(\n            delete(DashboardWidget).where(\n                DashboardWidget.dashboard_id == dashboard_id\n            )\n        )\n        await self.session.flush()\n        return result.rowcount\n\n\nclass DataSourceRepository(CompanyScopedRepository[DataSource]):\n    """Repository for data source operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, DataSource)\n    \n    async def get_by_type(\n        self,\n        company_id: UUID,\n        source_type: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[DataSource]:\n        """Get data sources by type."""\n        result = await self.session.execute(\n            select(DataSource)\n            .where(\n                and_(\n                    DataSource.company_id == company_id,\n                    DataSource.source_type == source_type,\n                    DataSource.is_deleted == False\n                )\n            )\n            .order_by(DataSource.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_active_sources(\n        self,\n        company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[DataSource]:\n        """Get all active data sources."""\n        result = await self.session.execute(\n            select(DataSource)\n            .where(\n                and_(\n                    DataSource.company_id == company_id,\n                    DataSource.is_active == True,\n                    DataSource.is_deleted == False\n                )\n            )\n            .order_by(DataSource.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def test_connection(self, data_source_id: UUID) -> Dict[str, Any]:\n        """Test data source connection (placeholder for actual testing)."""\n        data_source = await self.get(data_source_id)\n        if not data_source:\n            return {\'success\': False, \'error\': \'Data source not found\'}\n        \n        # This would be implemented with actual connection testing\n        # based on the source type\n        return {\n            \'success\': True,\n            \'message\': \'Connection successful\',\n            \'tested_at\': datetime.utcnow().isoformat()\n        }\n    \n    async def update_last_used(self, data_source_id: UUID) -> Optional[DataSource]:\n        """Update last used timestamp."""\n        data_source = await self.get(data_source_id)\n        if not data_source:\n            return None\n        \n        data_source.last_used_at = datetime.utcnow()\n        await self.session.flush()\n        return data_source\n    \n    async def get_by_connection_string(\n        self,\n        company_id: UUID,\n        connection_string: str\n    ) -> Optional[DataSource]:\n        """Get data source by connection string (for deduplication)."""\n        result = await self.session.execute(\n            select(DataSource).where(\n                and_(\n                    DataSource.company_id == company_id,\n                    DataSource.connection_string == connection_string,\n                    DataSource.is_deleted == False\n                )\n            )\n        )\n        return result.scalar_one_or_none()\n\n\nclass DataQueryRepository(BaseRepository[DataQuery]):\n    """Repository for data query operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, DataQuery)\n    \n    async def get_by_data_source(\n        self,\n        data_source_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[DataQuery]:\n        """Get queries for a data source."""\n        result = await self.session.execute(\n            select(DataQuery)\n            .where(DataQuery.data_source_id == data_source_id)\n            .order_by(DataQuery.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_query_type(\n        self,\n        query_type: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[DataQuery]:\n        """Get queries by type."""\n        result = await self.session.execute(\n            select(DataQuery)\n            .where(DataQuery.query_type == query_type)\n            .order_by(DataQuery.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def execute_query(\n        self,\n        query_id: UUID,\n        parameters: Optional[Dict[str, Any]] = None\n    ) -> Dict[str, Any]:\n        """Execute a data query (placeholder for actual execution)."""\n        query = await self.get(query_id)\n        if not query:\n            return {\'success\': False, \'error\': \'Query not found\'}\n        \n        # This would be implemented with actual query execution\n        # based on the query type and data source\n        return {\n            \'success\': True,\n            \'data\': [],\n            \'row_count\': 0,\n            \'executed_at\': datetime.utcnow().isoformat()\n        }\n\n\nclass ScheduledReportRepository(CompanyScopedRepository[ScheduledReport]):\n    """Repository for scheduled report operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, ScheduledReport)\n    \n    async def get_by_template(\n        self,\n        company_id: UUID,\n        template_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[ScheduledReport]:\n        """Get scheduled reports for a template."""\n        result = await self.session.execute(\n            select(ScheduledReport)\n            .where(\n                and_(\n                    ScheduledReport.company_id == company_id,\n                    ScheduledReport.template_id == template_id,\n                    ScheduledReport.is_deleted == False\n                )\n            )\n            .order_by(ScheduledReport.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_active_schedules(\n        self,\n        company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[ScheduledReport]:\n        """Get all active scheduled reports."""\n        result = await self.session.execute(\n            select(ScheduledReport)\n            .where(\n                and_(\n                    ScheduledReport.company_id == company_id,\n                    ScheduledReport.is_active == True,\n                    ScheduledReport.is_deleted == False\n                )\n            )\n            .order_by(ScheduledReport.next_run_at)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_due_schedules(\n        self,\n        limit: int = 100\n    ) -> List[ScheduledReport]:\n        """Get schedules that are due for execution."""\n        now = datetime.utcnow()\n        result = await self.session.execute(\n            select(ScheduledReport)\n            .where(\n                and_(\n                    ScheduledReport.is_active == True,\n                    ScheduledReport.next_run_at <= now,\n                    ScheduledReport.is_deleted == False\n                )\n            )\n            .order_by(ScheduledReport.next_run_at)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def update_next_run(\n        self,\n        schedule_id: UUID,\n        next_run_at: datetime\n    ) -> Optional[ScheduledReport]:\n        """Update the next run timestamp."""\n        schedule = await self.get(schedule_id)\n        if not schedule:\n            return None\n        \n        schedule.next_run_at = next_run_at\n        schedule.last_run_at = datetime.utcnow()\n        schedule.run_count = (schedule.run_count or 0) + 1\n        await self.session.flush()\n        return schedule\n    \n    async def record_run(\n        self,\n        schedule_id: UUID,\n        success: bool,\n        error_message: Optional[str] = None\n    ) -> Optional[ScheduledReport]:\n        """Record a run outcome."""\n        schedule = await self.get(schedule_id)\n        if not schedule:\n            return None\n        \n        schedule.last_run_at = datetime.utcnow()\n        schedule.run_count = (schedule.run_count or 0) + 1\n        if not success:\n            schedule.failure_count = (schedule.failure_count or 0) + 1\n            schedule.last_error = error_message\n        await self.session.flush()\n        return schedule\n    \n    async def pause_schedule(self, schedule_id: UUID) -> Optional[ScheduledReport]:\n        """Pause a scheduled report."""\n        schedule = await self.get(schedule_id)\n        if not schedule:\n            return None\n        \n        schedule.is_active = False\n        await self.session.flush()\n        return schedule\n    \n    async def resume_schedule(self, schedule_id: UUID) -> Optional[ScheduledReport]:\n        """Resume a paused scheduled report."""\n        schedule = await self.get(schedule_id)\n        if not schedule:\n            return None\n        \n        schedule.is_active = True\n        await self.session.flush()\n        return schedule\n    \n    async def get_schedules_by_frequency(\n        self,\n        company_id: UUID,\n        frequency: str\n    ) -> List[ScheduledReport]:\n        """Get schedules by frequency type."""\n        result = await self.session.execute(\n            select(ScheduledReport)\n            .where(\n                and_(\n                    ScheduledReport.company_id == company_id,\n                    ScheduledReport.frequency == frequency,\n                    ScheduledReport.is_active == True,\n                    ScheduledReport.is_deleted == False\n                )\n            )\n            .order_by(ScheduledReport.name)\n        )\n        return list(result.scalars().all())\n\n\nclass ReportDistributionRepository(BaseRepository[ReportDistribution]):\n    """Repository for report distribution operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, ReportDistribution)\n    \n    async def get_by_report(self, report_id: UUID) -> List[ReportDistribution]:\n        """Get distributions for a report."""\n        result = await self.session.execute(\n            select(ReportDistribution)\n            .where(ReportDistribution.report_id == report_id)\n            .order_by(desc(ReportDistribution.distributed_at))\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_recipient(\n        self,\n        recipient: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[ReportDistribution]:\n        """Get distributions sent to a recipient."""\n        result = await self.session.execute(\n            select(ReportDistribution)\n            .where(ReportDistribution.recipient == recipient)\n            .order_by(desc(ReportDistribution.distributed_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_pending_distributions(\n        self,\n        limit: int = 100\n    ) -> List[ReportDistribution]:\n        """Get distributions pending delivery."""\n        result = await self.session.execute(\n            select(ReportDistribution)\n            .where(ReportDistribution.status == \'pending\')\n            .order_by(ReportDistribution.created_at)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def create_distribution(\n        self,\n        report_id: UUID,\n        distribution_type: str,\n        recipient: str,\n        format: str\n    ) -> ReportDistribution:\n        """Create a new distribution record."""\n        distribution = ReportDistribution(\n            report_id=report_id,\n            distribution_type=distribution_type,\n            recipient=recipient,\n            format=format,\n            status=\'pending\'\n        )\n        self.session.add(distribution)\n        await self.session.flush()\n        return distribution\n    \n    async def mark_sent(\n        self,\n        distribution_id: UUID,\n        delivery_details: Optional[Dict[str, Any]] = None\n    ) -> Optional[ReportDistribution]:\n        """Mark distribution as sent."""\n        distribution = await self.get(distribution_id)\n        if not distribution:\n            return None\n        \n        distribution.status = \'sent\'\n        distribution.distributed_at = datetime.utcnow()\n        distribution.delivery_details = delivery_details or {}\n        await self.session.flush()\n        return distribution\n    \n    async def mark_failed(\n        self,\n        distribution_id: UUID,\n        error_message: str\n    ) -> Optional[ReportDistribution]:\n        """Mark distribution as failed."""\n        distribution = await self.get(distribution_id)\n        if not distribution:\n            return None\n        \n        distribution.status = \'failed\'\n        distribution.error_message = error_message\n        distribution.distributed_at = datetime.utcnow()\n        await self.session.flush()\n        return distribution\n    \n    async def get_distribution_stats(\n        self,\n        report_id: Optional[UUID] = None,\n        days: int = 30\n    ) -> Dict[str, Any]:\n        """Get distribution statistics."""\n        cutoff = datetime.utcnow() - timedelta(days=days)\n        \n        query = select(\n            ReportDistribution.status,\n            func.count(ReportDistribution.id).label(\'count\')\n        ).where(ReportDistribution.created_at >= cutoff)\n        \n        if report_id:\n            query = query.where(ReportDistribution.report_id == report_id)\n        \n        result = await self.session.execute(\n            query.group_by(ReportDistribution.status)\n        )\n        \n        return {\n            \'by_status\': {row[0]: row[1] for row in result.all()},\n            \'period_days\': days\n        }\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/repositories/reports.py\' created successfully.") </create-file> </tool_result>'}
{'role': 'assistant', 'content': 'Now let me create the Consolidation repository for intercompany reconciliation and consolidation operations.\n\n<function_calls>\n<create-file file_path="backend/app/repositories/consolidation.py">\n"""\nSAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM - Consolidation Repository\nComprehensive data access layer for multi-company consolidation and intercompany reconciliation.\n"""\n\nfrom datetime import datetime, date, timedelta\nfrom decimal import Decimal\nfrom typing import Optional, List, Dict, Any, Tuple, Union\nfrom uuid import UUID\nimport json\n\nfrom sqlalchemy import (\n    select, update, delete, and_, or_, func, case, desc, asc,\n    literal_column, text, cast, String, Integer, Numeric, Date, DateTime\n)\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom sqlalchemy.orm import selectinload, joinedload, contains_eager\nfrom sqlalchemy.dialects.postgresql import JSONB\n\nfrom .base import BaseRepository, CompanyScopedRepository\nfrom ..models.consolidation import (\n    ConsolidationGroup, ConsolidationScenario, ConsolidationEntry,\n    IntercompanyTransaction, IntercompanyBalance, EliminationRule,\n    ConsolidationJournal, ConsolidationReport, CurrencyRate\n)\nfrom ..models.base import ConsolidationStatus, TransactionStatus\n\n\nclass ConsolidationGroupRepository(CompanyScopedRepository[ConsolidationGroup]):\n    """Repository for consolidation group operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, ConsolidationGroup)\n    \n    async def get_with_members(self, group_id: UUID) -> Optional[ConsolidationGroup]:\n        """Get consolidation group with member companies loaded."""\n        result = await self.session.execute(\n            select(ConsolidationGroup)\n            .options(selectinload(ConsolidationGroup.members))\n            .where(ConsolidationGroup.id == group_id)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_by_parent(\n        self,\n        parent_company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[ConsolidationGroup]:\n        """Get consolidation groups by parent company."""\n        result = await self.session.execute(\n            select(ConsolidationGroup)\n            .where(\n                and_(\n                    ConsolidationGroup.parent_company_id == parent_company_id,\n                    ConsolidationGroup.is_deleted == False\n                )\n            )\n            .order_by(ConsolidationGroup.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_active_groups(\n        self,\n        company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[ConsolidationGroup]:\n        """Get all active consolidation groups."""\n        result = await self.session.execute(\n            select(ConsolidationGroup)\n            .where(\n                and_(\n                    ConsolidationGroup.parent_company_id == company_id,\n                    ConsolidationGroup.is_active == True,\n                    ConsolidationGroup.is_deleted == False\n                )\n            )\n            .order_by(ConsolidationGroup.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def add_member(\n        self,\n        group_id: UUID,\n        company_id: UUID,\n        ownership_percentage: Decimal = Decimal(\'100\'),\n        consolidation_method: str = \'full\'\n    ) -> ConsolidationGroup:\n        """Add a member company to the consolidation group."""\n        group = await self.get(group_id)\n        if not group:\n            raise ValueError(f"Group {group_id} not found")\n        \n        # This would typically update a junction table\n        # For now, we update the group\'s member configuration\n        if not group.member_config:\n            group.member_config = {}\n        \n        group.member_config[str(company_id)] = {\n            \'ownership_percentage\': str(ownership_percentage),\n            \'consolidation_method\': consolidation_method,\n            \'added_at\': datetime.utcnow().isoformat()\n        }\n        \n        await self.session.flush()\n        return group\n    \n    async def remove_member(\n        self,\n        group_id: UUID,\n        company_id: UUID\n    ) -> Optional[ConsolidationGroup]:\n        """Remove a member company from the consolidation group."""\n        group = await self.get(group_id)\n        if not group or not group.member_config:\n            return None\n        \n        if str(company_id) in group.member_config:\n            del group.member_config[str(company_id)]\n            await self.session.flush()\n        \n        return group\n    \n    async def get_member_companies(self, group_id: UUID) -> List[Dict[str, Any]]:\n        """Get all member companies with their configurations."""\n        group = await self.get(group_id)\n        if not group or not group.member_config:\n            return []\n        \n        members = []\n        for company_id, config in group.member_config.items():\n            members.append({\n                \'company_id\': company_id,\n                \'ownership_percentage\': Decimal(config.get(\'ownership_percentage\', \'100\')),\n                \'consolidation_method\': config.get(\'consolidation_method\', \'full\'),\n                \'added_at\': config.get(\'added_at\')\n            })\n        \n        return members\n\n\nclass ConsolidationScenarioRepository(CompanyScopedRepository[ConsolidationScenario]):\n    """Repository for consolidation scenario operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, ConsolidationScenario)\n    \n    async def get_with_entries(self, scenario_id: UUID) -> Optional[ConsolidationScenario]:\n        """Get scenario with all consolidation entries."""\n        result = await self.session.execute(\n            select(ConsolidationScenario)\n            .options(selectinload(ConsolidationScenario.entries))\n            .where(ConsolidationScenario.id == scenario_id)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_by_group(\n        self,\n        group_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[ConsolidationScenario]:\n        """Get scenarios by consolidation group."""\n        result = await self.session.execute(\n            select(ConsolidationScenario)\n            .where(\n                and_(\n                    ConsolidationScenario.group_id == group_id,\n                    ConsolidationScenario.is_deleted == False\n                )\n            )\n            .order_by(desc(ConsolidationScenario.created_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_period(\n        self,\n        group_id: UUID,\n        fiscal_year: int,\n        fiscal_period: int\n    ) -> Optional[ConsolidationScenario]:\n        """Get scenario for a specific period."""\n        result = await self.session.execute(\n            select(ConsolidationScenario)\n            .where(\n                and_(\n                    ConsolidationScenario.group_id == group_id,\n                    ConsolidationScenario.fiscal_year == fiscal_year,\n                    ConsolidationScenario.fiscal_period == fiscal_period,\n                    ConsolidationScenario.is_deleted == False\n                )\n            )\n            .order_by(desc(ConsolidationScenario.version))\n            .limit(1)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_by_status(\n        self,\n        group_id: UUID,\n        status: ConsolidationStatus,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[ConsolidationScenario]:\n        """Get scenarios by status."""\n        result = await self.session.execute(\n            select(ConsolidationScenario)\n            .where(\n                and_(\n                    ConsolidationScenario.group_id == group_id,\n                    ConsolidationScenario.status == status,\n                    ConsolidationScenario.is_deleted == False\n                )\n            )\n            .order_by(desc(ConsolidationScenario.created_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def create_version(\n        self,\n        original_scenario_id: UUID,\n        new_version_name: str\n    ) -> ConsolidationScenario:\n        """Create a new version of an existing scenario."""\n        original = await self.get_with_entries(original_scenario_id)\n        if not original:\n            raise ValueError(f"Scenario {original_scenario_id} not found")\n        \n        new_version = ConsolidationScenario(\n            company_id=original.company_id,\n            group_id=original.group_id,\n            name=new_version_name,\n            description=original.description,\n            fiscal_year=original.fiscal_year,\n            fiscal_period=original.fiscal_period,\n            status=ConsolidationStatus.DRAFT,\n            version=original.version + 1,\n            parent_scenario_id=original.id,\n            consolidation_date=original.consolidation_date,\n            reporting_currency=original.reporting_currency,\n            exchange_rate_type=original.exchange_rate_type\n        )\n        self.session.add(new_version)\n        await self.session.flush()\n        \n        # Copy entries from original\n        for entry in original.entries:\n            new_entry = ConsolidationEntry(\n                scenario_id=new_version.id,\n                entry_type=entry.entry_type,\n                company_id=entry.company_id,\n                account_id=entry.account_id,\n                description=entry.description,\n                debit_amount=entry.debit_amount,\n                credit_amount=entry.credit_amount,\n                currency=entry.currency,\n                exchange_rate=entry.exchange_rate\n            )\n            self.session.add(new_entry)\n        \n        await self.session.flush()\n        return new_version\n    \n    async def update_status(\n        self,\n        scenario_id: UUID,\n        status: ConsolidationStatus\n    ) -> Optional[ConsolidationScenario]:\n        """Update scenario status."""\n        scenario = await self.get(scenario_id)\n        if not scenario:\n            return None\n        \n        scenario.status = status\n        if status == ConsolidationStatus.COMPLETED:\n            scenario.completed_at = datetime.utcnow()\n        elif status == ConsolidationStatus.APPROVED:\n            scenario.approved_at = datetime.utcnow()\n        \n        await self.session.flush()\n        return scenario\n    \n    async def finalize_scenario(\n        self,\n        scenario_id: UUID,\n        approved_by: UUID\n    ) -> Optional[ConsolidationScenario]:\n        """Finalize and approve a consolidation scenario."""\n        scenario = await self.get(scenario_id)\n        if not scenario:\n            return None\n        \n        scenario.status = ConsolidationStatus.APPROVED\n        scenario.approved_at = datetime.utcnow()\n        scenario.approved_by = approved_by\n        await self.session.flush()\n        return scenario\n    \n    async def get_scenario_summary(\n        self,\n        scenario_id: UUID\n    ) -> Dict[str, Any]:\n        """Get summary statistics for a scenario."""\n        scenario = await self.get(scenario_id)\n        if not scenario:\n            return {}\n        \n        # Aggregate entry statistics\n        result = await self.session.execute(\n            select(\n                ConsolidationEntry.entry_type,\n                func.count(ConsolidationEntry.id).label(\'count\'),\n                func.sum(ConsolidationEntry.debit_amount).label(\'total_debits\'),\n                func.sum(ConsolidationEntry.credit_amount).label(\'total_credits\')\n            ).where(ConsolidationEntry.scenario_id == scenario_id)\n            .group_by(ConsolidationEntry.entry_type)\n        )\n        \n        summary = {\n            \'scenario_id\': str(scenario_id),\n            \'status\': scenario.status.value if scenario.status else None,\n            \'fiscal_year\': scenario.fiscal_year,\n            \'fiscal_period\': scenario.fiscal_period,\n            \'by_entry_type\': {}\n        }\n        \n        for row in result.all():\n            summary[\'by_entry_type\'][row[0]] = {\n                \'count\': row[1],\n                \'total_debits\': float(row[2] or 0),\n                \'total_credits\': float(row[3] or 0)\n            }\n        \n        return summary\n\n\nclass ConsolidationEntryRepository(BaseRepository[ConsolidationEntry]):\n    """Repository for consolidation entry operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, ConsolidationEntry)\n    \n    async def get_by_scenario(\n        self,\n        scenario_id: UUID,\n        skip: int = 0,\n        limit: int = 1000\n    ) -> List[ConsolidationEntry]:\n        """Get all entries for a scenario."""\n        result = await self.session.execute(\n            select(ConsolidationEntry)\n            .where(ConsolidationEntry.scenario_id == scenario_id)\n            .order_by(ConsolidationEntry.entry_number)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_company(\n        self,\n        scenario_id: UUID,\n        company_id: UUID\n    ) -> List[ConsolidationEntry]:\n        """Get entries for a specific company in a scenario."""\n        result = await self.session.execute(\n            select(ConsolidationEntry)\n            .where(\n                and_(\n                    ConsolidationEntry.scenario_id == scenario_id,\n                    ConsolidationEntry.company_id == company_id\n                )\n            )\n            .order_by(ConsolidationEntry.entry_number)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_entry_type(\n        self,\n        scenario_id: UUID,\n        entry_type: str\n    ) -> List[ConsolidationEntry]:\n        """Get entries by type (elimination, adjustment, etc.)."""\n        result = await self.session.execute(\n            select(ConsolidationEntry)\n            .where(\n                and_(\n                    ConsolidationEntry.scenario_id == scenario_id,\n                    ConsolidationEntry.entry_type == entry_type\n                )\n            )\n            .order_by(ConsolidationEntry.entry_number)\n        )\n        return list(result.scalars().all())\n    \n    async def create_entry(\n        self,\n        scenario_id: UUID,\n        entry_type: str,\n        company_id: UUID,\n        account_id: UUID,\n        description: str,\n        debit_amount: Decimal = Decimal(\'0\'),\n        credit_amount: Decimal = Decimal(\'0\'),\n        currency: str = \'USD\',\n        exchange_rate: Decimal = Decimal(\'1\')\n    ) -> ConsolidationEntry:\n        """Create a new consolidation entry."""\n        # Get next entry number\n        result = await self.session.execute(\n            select(func.max(ConsolidationEntry.entry_number))\n            .where(ConsolidationEntry.scenario_id == scenario_id)\n        )\n        max_number = result.scalar() or 0\n        \n        entry = ConsolidationEntry(\n            scenario_id=scenario_id,\n            entry_number=max_number + 1,\n            entry_type=entry_type,\n            company_id=company_id,\n            account_id=account_id,\n            description=description,\n            debit_amount=debit_amount,\n            credit_amount=credit_amount,\n            currency=currency,\n            exchange_rate=exchange_rate\n        )\n        self.session.add(entry)\n        await self.session.flush()\n        return entry\n    \n    async def create_elimination_pair(\n        self,\n        scenario_id: UUID,\n        company_a_id: UUID,\n        company_b_id: UUID,\n        account_id: UUID,\n        amount: Decimal,\n        description: str\n    ) -> Tuple[ConsolidationEntry, ConsolidationEntry]:\n        """Create matching elimination entries for intercompany transactions."""\n        entry_a = await self.create_entry(\n            scenario_id=scenario_id,\n            entry_type=\'elimination\',\n            company_id=company_a_id,\n            account_id=account_id,\n            description=f"[ELIM] {description}",\n            credit_amount=amount\n        )\n        \n        entry_b = await self.create_entry(\n            scenario_id=scenario_id,\n            entry_type=\'elimination\',\n            company_id=company_b_id,\n            account_id=account_id,\n            description=f"[ELIM] {description}",\n            debit_amount=amount\n        )\n        \n        return (entry_a, entry_b)\n    \n    async def delete_by_scenario(self, scenario_id: UUID) -> int:\n        """Delete all entries for a scenario."""\n        result = await self.session.execute(\n            delete(ConsolidationEntry).where(\n                ConsolidationEntry.scenario_id == scenario_id\n            )\n        )\n        await self.session.flush()\n        return result.rowcount\n    \n    async def validate_balance(self, scenario_id: UUID) -> Dict[str, Any]:\n        """Validate that entries are balanced."""\n        result = await self.session.execute(\n            select(\n                func.sum(ConsolidationEntry.debit_amount).label(\'total_debits\'),\n                func.sum(ConsolidationEntry.credit_amount).label(\'total_credits\')\n            ).where(ConsolidationEntry.scenario_id == scenario_id)\n        )\n        \n        row = result.one()\n        total_debits = row[0] or Decimal(\'0\')\n        total_credits = row[1] or Decimal(\'0\')\n        difference = total_debits - total_credits\n        \n        return {\n            \'scenario_id\': str(scenario_id),\n            \'total_debits\': float(total_debits),\n            \'total_credits\': float(total_credits),\n            \'difference\': float(difference),\n            \'is_balanced\': difference == 0\n        }\n\n\nclass IntercompanyTransactionRepository(BaseRepository[IntercompanyTransaction]):\n    """Repository for intercompany transaction operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, IntercompanyTransaction)\n    \n    async def get_between_companies(\n        self,\n        company_a_id: UUID,\n        company_b_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[IntercompanyTransaction]:\n        """Get transactions between two companies."""\n        result = await self.session.execute(\n            select(IntercompanyTransaction)\n            .where(\n                or_(\n                    and_(\n                        IntercompanyTransaction.from_company_id == company_a_id,\n                        IntercompanyTransaction.to_company_id == company_b_id\n                    ),\n                    and_(\n                        IntercompanyTransaction.from_company_id == company_b_id,\n                        IntercompanyTransaction.to_company_id == company_a_id\n                    )\n                )\n            )\n            .order_by(desc(IntercompanyTransaction.transaction_date))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_from_company(\n        self,\n        from_company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[IntercompanyTransaction]:\n        """Get transactions originating from a company."""\n        result = await self.session.execute(\n            select(IntercompanyTransaction)\n            .where(IntercompanyTransaction.from_company_id == from_company_id)\n            .order_by(desc(IntercompanyTransaction.transaction_date))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_to_company(\n        self,\n        to_company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[IntercompanyTransaction]:\n        """Get transactions received by a company."""\n        result = await self.session.execute(\n            select(IntercompanyTransaction)\n            .where(IntercompanyTransaction.to_company_id == to_company_id)\n            .order_by(desc(IntercompanyTransaction.transaction_date))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_pending_transactions(\n        self,\n        company_id: Optional[UUID] = None,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[IntercompanyTransaction]:\n        """Get pending intercompany transactions."""\n        query = select(IntercompanyTransaction).where(\n            IntercompanyTransaction.status == TransactionStatus.PENDING\n        )\n        \n        if company_id:\n            query = query.where(\n                or_(\n                    IntercompanyTransaction.from_company_id == company_id,\n                    IntercompanyTransaction.to_company_id == company_id\n                )\n            )\n        \n        result = await self.session.execute(\n            query.order_by(IntercompanyTransaction.transaction_date)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_date_range(\n        self,\n        company_id: UUID,\n        start_date: date,\n        end_date: date\n    ) -> List[IntercompanyTransaction]:\n        """Get transactions within a date range."""\n        result = await self.session.execute(\n            select(IntercompanyTransaction)\n            .where(\n                and_(\n                    or_(\n                        IntercompanyTransaction.from_company_id == company_id,\n                        IntercompanyTransaction.to_company_id == company_id\n                    ),\n                    IntercompanyTransaction.transaction_date >= start_date,\n                    IntercompanyTransaction.transaction_date <= end_date\n                )\n            )\n            .order_by(IntercompanyTransaction.transaction_date)\n        )\n        return list(result.scalars().all())\n    \n    async def create_transaction(\n        self,\n        from_company_id: UUID,\n        to_company_id: UUID,\n        transaction_type: str,\n        amount: Decimal,\n        currency: str,\n        description: str,\n        transaction_date: date,\n        reference: Optional[str] = None\n    ) -> IntercompanyTransaction:\n        """Create a new intercompany transaction."""\n        transaction = IntercompanyTransaction(\n            from_company_id=from_company_id,\n            to_company_id=to_company_id,\n            transaction_type=transaction_type,\n            amount=amount,\n            currency=currency,\n            description=description,\n            transaction_date=transaction_date,\n            reference=reference,\n            status=TransactionStatus.PENDING\n        )\n        self.session.add(transaction)\n        await self.session.flush()\n        return transaction\n    \n    async def confirm_transaction(\n        self,\n        transaction_id: UUID,\n        confirmed_by: UUID\n    ) -> Optional[IntercompanyTransaction]:\n        """Confirm an intercompany transaction."""\n        transaction = await self.get(transaction_id)\n        if not transaction:\n            return None\n        \n        transaction.status = TransactionStatus.CONFIRMED\n        transaction.confirmed_at = datetime.utcnow()\n        transaction.confirmed_by = confirmed_by\n        await self.session.flush()\n        return transaction\n    \n    async def match_transactions(\n        self,\n        transaction_ids: List[UUID]\n    ) -> Dict[str, Any]:\n        """Match multiple intercompany transactions."""\n        transactions = []\n        for tx_id in transaction_ids:\n            tx = await self.get(tx_id)\n            if tx:\n                transactions.append(tx)\n        \n        matched_count = 0\n        for tx in transactions:\n            tx.status = TransactionStatus.MATCHED\n            tx.matched_at = datetime.utcnow()\n            matched_count += 1\n        \n        await self.session.flush()\n        \n        return {\n            \'matched_count\': matched_count,\n            \'transaction_ids\': [str(t.id) for t in transactions]\n        }\n    \n    async def get_transaction_summary(\n        self,\n        company_id: UUID,\n        start_date: Optional[date] = None,\n        end_date: Optional[date] = None\n    ) -> Dict[str, Any]:\n        """Get summary of intercompany transactions."""\n        base_query = select(\n            IntercompanyTransaction.transaction_type,\n            func.count(IntercompanyTransaction.id).label(\'count\'),\n            func.sum(IntercompanyTransaction.amount).label(\'total_amount\')\n        )\n        \n        conditions = [\n            or_(\n                IntercompanyTransaction.from_company_id == company_id,\n                IntercompanyTransaction.to_company_id == company_id\n            )\n        ]\n        \n        if start_date:\n            conditions.append(IntercompanyTransaction.transaction_date >= start_date)\n        if end_date:\n            conditions.append(IntercompanyTransaction.transaction_date <= end_date)\n        \n        result = await self.session.execute(\n            base_query.where(and_(*conditions))\n            .group_by(IntercompanyTransaction.transaction_type)\n        )\n        \n        summary = {\n            \'company_id\': str(company_id),\n            \'by_type\': {}\n        }\n        \n        for row in result.all():\n            summary[\'by_type\'][row[0]] = {\n                \'count\': row[1],\n                \'total_amount\': float(row[2] or 0)\n            }\n        \n        return summary\n\n\nclass IntercompanyBalanceRepository(BaseRepository[IntercompanyBalance]):\n    """Repository for intercompany balance tracking."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, IntercompanyBalance)\n    \n    async def get_balance(\n        self,\n        company_a_id: UUID,\n        company_b_id: UUID,\n        as_of_date: Optional[date] = None\n    ) -> Optional[IntercompanyBalance]:\n        """Get balance between two companies."""\n        conditions = [\n            or_(\n                and_(\n                    IntercompanyBalance.company_a_id == company_a_id,\n                    IntercompanyBalance.company_b_id == company_b_id\n                ),\n                and_(\n                    IntercompanyBalance.company_a_id == company_b_id,\n                    IntercompanyBalance.company_b_id == company_a_id\n                )\n            )\n        ]\n        \n        if as_of_date:\n            conditions.append(IntercompanyBalance.as_of_date <= as_of_date)\n        \n        result = await self.session.execute(\n            select(IntercompanyBalance)\n            .where(and_(*conditions))\n            .order_by(desc(IntercompanyBalance.as_of_date))\n            .limit(1)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_all_balances_for_company(\n        self,\n        company_id: UUID,\n        as_of_date: Optional[date] = None\n    ) -> List[IntercompanyBalance]:\n        """Get all intercompany balances for a company."""\n        conditions = [\n            or_(\n                IntercompanyBalance.company_a_id == company_id,\n                IntercompanyBalance.company_b_id == company_id\n            )\n        ]\n        \n        if as_of_date:\n            conditions.append(IntercompanyBalance.as_of_date <= as_of_date)\n        \n        result = await self.session.execute(\n            select(IntercompanyBalance)\n            .where(and_(*conditions))\n            .order_by(desc(IntercompanyBalance.as_of_date))\n        )\n        return list(result.scalars().all())\n    \n    async def update_balance(\n        self,\n        company_a_id: UUID,\n        company_b_id: UUID,\n        balance_amount: Decimal,\n        currency: str,\n        as_of_date: date\n    ) -> IntercompanyBalance:\n        """Update or create intercompany balance."""\n        existing = await self.get_balance(company_a_id, company_b_id, as_of_date)\n        \n        if existing:\n            existing.balance_amount = balance_amount\n            existing.as_of_date = as_of_date\n            existing.updated_at = datetime.utcnow()\n            await self.session.flush()\n            return existing\n        \n        new_balance = IntercompanyBalance(\n            company_a_id=company_a_id,\n            company_b_id=company_b_id,\n            balance_amount=balance_amount,\n            currency=currency,\n            as_of_date=as_of_date\n        )\n        self.session.add(new_balance)\n        await self.session.flush()\n        return new_balance\n    \n    async def reconcile_balances(\n        self,\n        company_a_id: UUID,\n        company_b_id: UUID\n    ) -> Dict[str, Any]:\n        """Check if balances between companies are reconciled."""\n        balances = await self.get_all_balances_for_company(company_a_id)\n        company_b_balances = [b for b in balances if \n                            b.company_b_id == company_b_id or b.company_a_id == company_b_id]\n        \n        if not company_b_balances:\n            return {\n                \'reconciled\': True,\n                \'message\': \'No balances found\'\n            }\n        \n        # Check if balances match (mirrored)\n        differences = []\n        for balance in company_b_balances:\n            # Logic to check if mirrored balances match\n            pass\n        \n        return {\n            \'reconciled\': len(differences) == 0,\n            \'differences\': differences\n        }\n    \n    async def get_net_position(\n        self,\n        company_id: UUID,\n        as_of_date: Optional[date] = None\n    ) -> Dict[str, Any]:\n        """Calculate net intercompany position for a company."""\n        balances = await self.get_all_balances_for_company(company_id, as_of_date)\n        \n        net_receivable = Decimal(\'0\')\n        net_payable = Decimal(\'0\')\n        \n        for balance in balances:\n            if balance.company_a_id == company_id:\n                if balance.balance_amount > 0:\n                    net_receivable += balance.balance_amount\n                else:\n                    net_payable += abs(balance.balance_amount)\n            else:\n                if balance.balance_amount > 0:\n                    net_payable += balance.balance_amount\n                else:\n                    net_receivable += abs(balance.balance_amount)\n        \n        return {\n            \'company_id\': str(company_id),\n            \'net_receivable\': float(net_receivable),\n            \'net_payable\': float(net_payable),\n            \'net_position\': float(net_receivable - net_payable),\n            \'as_of_date\': as_of_date.isoformat() if as_of_date else None\n        }\n\n\nclass EliminationRuleRepository(CompanyScopedRepository[EliminationRule]):\n    """Repository for elimination rule operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, EliminationRule)\n    \n    async def get_active_rules(\n        self,\n        company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[EliminationRule]:\n        """Get all active elimination rules."""\n        result = await self.session.execute(\n            select(EliminationRule)\n            .where(\n                and_(\n                    EliminationRule.company_id == company_id,\n                    EliminationRule.is_active == True,\n                    EliminationRule.is_deleted == False\n                )\n            )\n            .order_by(EliminationRule.priority)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_type(\n        self,\n        company_id: UUID,\n        rule_type: str\n    ) -> List[EliminationRule]:\n        """Get elimination rules by type."""\n        result = await self.session.execute(\n            select(EliminationRule)\n            .where(\n                and_(\n                    EliminationRule.company_id == company_id,\n                    EliminationRule.rule_type == rule_type,\n                    EliminationRule.is_deleted == False\n                )\n            )\n            .order_by(EliminationRule.priority)\n        )\n        return list(result.scalars().all())\n    \n    async def create_rule(\n        self,\n        company_id: UUID,\n        name: str,\n        rule_type: str,\n        description: str,\n        conditions: Dict[str, Any],\n        elimination_accounts: Dict[str, Any],\n        priority: int = 100,\n        created_by: Optional[UUID] = None\n    ) -> EliminationRule:\n        """Create a new elimination rule."""\n        rule = EliminationRule(\n            company_id=company_id,\n            name=name,\n            rule_type=rule_type,\n            description=description,\n            conditions=conditions,\n            elimination_accounts=elimination_accounts,\n            priority=priority,\n            is_active=True,\n            created_by=created_by\n        )\n        self.session.add(rule)\n        await self.session.flush()\n        return rule\n    \n    async def update_priority(\n        self,\n        rule_id: UUID,\n        new_priority: int\n    ) -> Optional[EliminationRule]:\n        """Update rule priority."""\n        rule = await self.get(rule_id)\n        if not rule:\n            return None\n        \n        rule.priority = new_priority\n        await self.session.flush()\n        return rule\n    \n    async def test_rule(\n        self,\n        rule_id: UUID,\n        test_data: Dict[str, Any]\n    ) -> Dict[str, Any]:\n        """Test an elimination rule against sample data."""\n        rule = await self.get(rule_id)\n        if not rule:\n            return {\'success\': False, \'error\': \'Rule not found\'}\n        \n        # This would contain logic to test rule conditions\n        return {\n            \'success\': True,\n            \'rule_id\': str(rule_id),\n            \'would_eliminate\': True,\n            \'elimination_amount\': 0\n        }\n\n\nclass ConsolidationJournalRepository(CompanyScopedRepository[ConsolidationJournal]):\n    """Repository for consolidation journal operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, ConsolidationJournal)\n    \n    async def get_by_scenario(\n        self,\n        scenario_id: UUID\n    ) -> Optional[ConsolidationJournal]:\n        """Get journal for a consolidation scenario."""\n        result = await self.session.execute(\n            select(ConsolidationJournal)\n            .where(ConsolidationJournal.scenario_id == scenario_id)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_by_period(\n        self,\n        company_id: UUID,\n        fiscal_year: int,\n        fiscal_period: int\n    ) -> List[ConsolidationJournal]:\n        """Get journals for a specific period."""\n        result = await self.session.execute(\n            select(ConsolidationJournal)\n            .where(\n                and_(\n                    ConsolidationJournal.company_id == company_id,\n                    ConsolidationJournal.fiscal_year == fiscal_year,\n                    ConsolidationJournal.fiscal_period == fiscal_period,\n                    ConsolidationJournal.is_deleted == False\n                )\n            )\n            .order_by(desc(ConsolidationJournal.created_at))\n        )\n        return list(result.scalars().all())\n    \n    async def create_journal(\n        self,\n        company_id: UUID,\n        scenario_id: UUID,\n        fiscal_year: int,\n        fiscal_period: int,\n        description: str,\n        total_debits: Decimal,\n        total_credits: Decimal\n    ) -> ConsolidationJournal:\n        """Create a consolidation journal."""\n        journal = ConsolidationJournal(\n            company_id=company_id,\n            scenario_id=scenario_id,\n            fiscal_year=fiscal_year,\n            fiscal_period=fiscal_period,\n            description=description,\n            total_debits=total_debits,\n            total_credits=total_credits\n        )\n        self.session.add(journal)\n        await self.session.flush()\n        return journal\n    \n    async def post_journal(\n        self,\n        journal_id: UUID,\n        posted_by: UUID\n    ) -> Optional[ConsolidationJournal]:\n        """Post a consolidation journal."""\n        journal = await self.get(journal_id)\n        if not journal:\n            return None\n        \n        journal.is_posted = True\n        journal.posted_at = datetime.utcnow()\n        journal.posted_by = posted_by\n        await self.session.flush()\n        return journal\n    \n    async def reverse_journal(\n        self,\n        journal_id: UUID,\n        reason: str,\n        reversed_by: UUID\n    ) -> Optional[ConsolidationJournal]:\n        """Reverse a posted journal."""\n        journal = await self.get(journal_id)\n        if not journal or not journal.is_posted:\n            return None\n        \n        journal.is_reversed = True\n        journal.reversed_at = datetime.utcnow()\n        journal.reversed_by = reversed_by\n        journal.reversal_reason = reason\n        await self.session.flush()\n        return journal\n\n\nclass ConsolidationReportRepository(CompanyScopedRepository[ConsolidationReport]):\n    """Repository for consolidation report operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, ConsolidationReport)\n    \n    async def get_by_scenario(\n        self,\n        scenario_id: UUID\n    ) -> List[ConsolidationReport]:\n        """Get all reports for a scenario."""\n        result = await self.session.execute(\n            select(ConsolidationReport)\n            .where(ConsolidationReport.scenario_id == scenario_id)\n            .order_by(desc(ConsolidationReport.created_at))\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_type(\n        self,\n        company_id: UUID,\n        report_type: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[ConsolidationReport]:\n        """Get reports by type."""\n        result = await self.session.execute(\n            select(ConsolidationReport)\n            .where(\n                and_(\n                    ConsolidationReport.company_id == company_id,\n                    ConsolidationReport.report_type == report_type,\n                    ConsolidationReport.is_deleted == False\n                )\n            )\n            .order_by(desc(ConsolidationReport.created_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def create_report(\n        self,\n        company_id: UUID,\n        scenario_id: UUID,\n        report_type: str,\n        name: str,\n        content: Dict[str, Any],\n        generated_by: Optional[UUID] = None\n    ) -> ConsolidationReport:\n        """Create a consolidation report."""\n        report = ConsolidationReport(\n            company_id=company_id,\n            scenario_id=scenario_id,\n            report_type=report_type,\n            name=name,\n            content=content,\n            generated_by=generated_by\n        )\n        self.session.add(report)\n        await self.session.flush()\n        return report\n    \n    async def get_latest_by_type(\n        self,\n        scenario_id: UUID,\n        report_type: str\n    ) -> Optional[ConsolidationReport]:\n        """Get the most recent report of a specific type for a scenario."""\n        result = await self.session.execute(\n            select(ConsolidationReport)\n            .where(\n                and_(\n                    ConsolidationReport.scenario_id == scenario_id,\n                    ConsolidationReport.report_type == report_type\n                )\n            )\n            .order_by(desc(ConsolidationReport.created_at))\n            .limit(1)\n        )\n        return result.scalar_one_or_none()\n\n\nclass CurrencyRateRepository(BaseRepository[CurrencyRate]):\n    """Repository for currency exchange rate operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, CurrencyRate)\n    \n    async def get_rate(\n        self,\n        from_currency: str,\n        to_currency: str,\n        rate_date: date,\n        rate_type: str = \'spot\'\n    ) -> Optional[CurrencyRate]:\n        """Get exchange rate for a specific date."""\n        result = await self.session.execute(\n            select(CurrencyRate)\n            .where(\n                and_(\n                    CurrencyRate.from_currency == from_currency,\n                    CurrencyRate.to_currency == to_currency,\n                    CurrencyRate.rate_date == rate_date,\n                    CurrencyRate.rate_type == rate_type\n                )\n            )\n            .limit(1)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_latest_rate(\n        self,\n        from_currency: str,\n        to_currency: str,\n        rate_type: str = \'spot\'\n    ) -> Optional[CurrencyRate]:\n        """Get the most recent exchange rate."""\n        result = await self.session.execute(\n            select(CurrencyRate)\n            .where(\n                and_(\n                    CurrencyRate.from_currency == from_currency,\n                    CurrencyRate.to_currency == to_currency,\n                    CurrencyRate.rate_type == rate_type\n                )\n            )\n            .order_by(desc(CurrencyRate.rate_date))\n            .limit(1)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_rates_for_date(\n        self,\n        rate_date: date,\n        rate_type: str = \'spot\'\n    ) -> List[CurrencyRate]:\n        """Get all rates for a specific date."""\n        result = await self.session.execute(\n            select(CurrencyRate)\n            .where(\n                and_(\n                    CurrencyRate.rate_date == rate_date,\n                    CurrencyRate.rate_type == rate_type\n                )\n            )\n        )\n        return list(result.scalars().all())\n    \n    async def set_rate(\n        self,\n        from_currency: str,\n        to_currency: str,\n        rate: Decimal,\n        rate_date: date,\n        rate_type: str = \'spot\',\n        source: Optional[str] = None\n    ) -> CurrencyRate:\n        """Set or update an exchange rate."""\n        existing = await self.get_rate(from_currency, to_currency, rate_date, rate_type)\n        \n        if existing:\n            existing.rate = rate\n            existing.source = source\n            existing.updated_at = datetime.utcnow()\n            await self.session.flush()\n            return existing\n        \n        new_rate = CurrencyRate(\n            from_currency=from_currency,\n            to_currency=to_currency,\n            rate=rate,\n            rate_date=rate_date,\n            rate_type=rate_type,\n            source=source\n        )\n        self.session.add(new_rate)\n        await self.session.flush()\n        return new_rate\n    \n    async def convert_amount(\n        self,\n        amount: Decimal,\n        from_currency: str,\n        to_currency: str,\n        rate_date: date\n    ) -> Dict[str, Any]:\n        """Convert an amount from one currency to another."""\n        if from_currency == to_currency:\n            return {\n                \'original_amount\': float(amount),\n                \'converted_amount\': float(amount),\n                \'from_currency\': from_currency,\n                \'to_currency\': to_currency,\n                \'rate\': 1.0,\n                \'rate_date\': rate_date.isoformat()\n            }\n        \n        rate = await self.get_rate(from_currency, to_currency, rate_date)\n        \n        if not rate:\n            # Try inverse rate\n            inverse_rate = await self.get_rate(to_currency, from_currency, rate_date)\n            if inverse_rate:\n                converted = amount / inverse_rate.rate\n                return {\n                    \'original_amount\': float(amount),\n                    \'converted_amount\': float(converted),\n                    \'from_currency\': from_currency,\n                    \'to_currency\': to_currency,\n                    \'rate\': float(1 / inverse_rate.rate),\n                    \'rate_date\': rate_date.isoformat(),\n                    \'inverted\': True\n                }\n            return {\n                \'success\': False,\n                \'error\': f\'No rate found for {from_currency} to {to_currency} on {rate_date}\'\n            }\n        \n        converted = amount * rate.rate\n        return {\n            \'original_amount\': float(amount),\n            \'converted_amount\': float(converted),\n            \'from_currency\': from_currency,\n            \'to_currency\': to_currency,\n            \'rate\': float(rate.rate),\n            \'rate_date\': rate_date.isoformat()\n        }\n    \n    async def get_rate_history(\n        self,\n        from_currency: str,\n        to_currency: str,\n        start_date: date,\n        end_date: date,\n        rate_type: str = \'spot\'\n    ) -> List[CurrencyRate]:\n        """Get exchange rate history for a period."""\n        result = await self.session.execute(\n            select(CurrencyRate)\n            .where(\n                and_(\n                    CurrencyRate.from_currency == from_currency,\n                    CurrencyRate.to_currency == to_currency,\n                    CurrencyRate.rate_date >= start_date,\n                    CurrencyRate.rate_date <= end_date,\n                    CurrencyRate.rate_type == rate_type\n                )\n            )\n            .order_by(CurrencyRate.rate_date)\n        )\n        return list(result.scalars().all())\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/repositories/consolidation.py\' created successfully.") </create-file> </tool_result>'}
{'role': 'assistant', 'content': 'Now let me create the Asset repository for fixed asset registry and depreciation management.\n\n<function_calls>\n<create-file file_path="backend/app/repositories/asset.py">\n"""\nSAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM - Asset Repository\nComprehensive data access layer for fixed asset registry and depreciation management.\n"""\n\nfrom datetime import datetime, date, timedelta\nfrom decimal import Decimal\nfrom typing import Optional, List, Dict, Any, Tuple, Union\nfrom uuid import UUID\nimport json\n\nfrom sqlalchemy import (\n    select, update, delete, and_, or_, func, case, desc, asc,\n    literal_column, text, cast, String, Integer, Numeric, Date, DateTime\n)\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom sqlalchemy.orm import selectinload, joinedload, contains_eager\nfrom sqlalchemy.dialects.postgresql import JSONB\n\nfrom .base import BaseRepository, CompanyScopedRepository\nfrom ..models.asset import (\n    AssetCategory, Asset, AssetDepreciation, AssetMaintenance,\n    AssetTransfer, AssetDisposal, AssetInsurance, AssetTag, AssetLocation\n)\nfrom ..models.base import AssetStatus, DepreciationMethod\n\n\nclass AssetCategoryRepository(CompanyScopedRepository[AssetCategory]):\n    """Repository for asset category operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AssetCategory)\n    \n    async def get_by_code(self, company_id: UUID, code: str) -> Optional[AssetCategory]:\n        """Get category by code within a company."""\n        result = await self.session.execute(\n            select(AssetCategory).where(\n                and_(\n                    AssetCategory.company_id == company_id,\n                    AssetCategory.code == code,\n                    AssetCategory.is_deleted == False\n                )\n            )\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_root_categories(\n        self,\n        company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AssetCategory]:\n        """Get root categories (no parent)."""\n        result = await self.session.execute(\n            select(AssetCategory)\n            .where(\n                and_(\n                    AssetCategory.company_id == company_id,\n                    AssetCategory.parent_id.is_(None),\n                    AssetCategory.is_deleted == False\n                )\n            )\n            .order_by(AssetCategory.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_children(\n        self,\n        parent_id: UUID\n    ) -> List[AssetCategory]:\n        """Get child categories of a parent."""\n        result = await self.session.execute(\n            select(AssetCategory)\n            .where(\n                and_(\n                    AssetCategory.parent_id == parent_id,\n                    AssetCategory.is_deleted == False\n                )\n            )\n            .order_by(AssetCategory.name)\n        )\n        return list(result.scalars().all())\n    \n    async def get_category_tree(\n        self,\n        company_id: UUID\n    ) -> List[Dict[str, Any]]:\n        """Get hierarchical category tree."""\n        categories = await self.get_all(company_id, limit=1000)\n        \n        # Build tree structure\n        category_map = {str(c.id): c for c in categories}\n        tree = []\n        \n        for category in categories:\n            if not category.parent_id:\n                tree.append({\n                    \'id\': str(category.id),\n                    \'code\': category.code,\n                    \'name\': category.name,\n                    \'depreciation_method\': category.depreciation_method,\n                    \'useful_life_years\': category.useful_life_years,\n                    \'children\': []\n                })\n        \n        # Add children\n        for category in categories:\n            if category.parent_id:\n                parent_id_str = str(category.parent_id)\n                for node in tree:\n                    self._add_child_to_tree(node, parent_id_str, category)\n        \n        return tree\n    \n    def _add_child_to_tree(\n        self,\n        node: Dict[str, Any],\n        parent_id: str,\n        category: AssetCategory\n    ) -> bool:\n        """Recursively add child to tree node."""\n        if node[\'id\'] == parent_id:\n            node[\'children\'].append({\n                \'id\': str(category.id),\n                \'code\': category.code,\n                \'name\': category.name,\n                \'depreciation_method\': category.depreciation_method,\n                \'useful_life_years\': category.useful_life_years,\n                \'children\': []\n            })\n            return True\n        \n        for child in node.get(\'children\', []):\n            if self._add_child_to_tree(child, parent_id, category):\n                return True\n        \n        return False\n    \n    async def get_by_depreciation_method(\n        self,\n        company_id: UUID,\n        method: DepreciationMethod\n    ) -> List[AssetCategory]:\n        """Get categories by depreciation method."""\n        result = await self.session.execute(\n            select(AssetCategory)\n            .where(\n                and_(\n                    AssetCategory.company_id == company_id,\n                    AssetCategory.depreciation_method == method,\n                    AssetCategory.is_deleted == False\n                )\n            )\n            .order_by(AssetCategory.name)\n        )\n        return list(result.scalars().all())\n\n\nclass AssetRepository(CompanyScopedRepository[Asset]):\n    """Repository for fixed asset operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, Asset)\n    \n    async def get_with_details(self, asset_id: UUID) -> Optional[Asset]:\n        """Get asset with all related details."""\n        result = await self.session.execute(\n            select(Asset)\n            .options(\n                selectinload(Asset.category),\n                selectinload(Asset.location),\n                selectinload(Asset.depreciations)\n            )\n            .where(Asset.id == asset_id)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_by_asset_number(\n        self,\n        company_id: UUID,\n        asset_number: str\n    ) -> Optional[Asset]:\n        """Get asset by asset number."""\n        result = await self.session.execute(\n            select(Asset).where(\n                and_(\n                    Asset.company_id == company_id,\n                    Asset.asset_number == asset_number,\n                    Asset.is_deleted == False\n                )\n            )\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_by_category(\n        self,\n        company_id: UUID,\n        category_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[Asset]:\n        """Get assets by category."""\n        result = await self.session.execute(\n            select(Asset)\n            .where(\n                and_(\n                    Asset.company_id == company_id,\n                    Asset.category_id == category_id,\n                    Asset.is_deleted == False\n                )\n            )\n            .order_by(Asset.asset_number)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_status(\n        self,\n        company_id: UUID,\n        status: AssetStatus,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[Asset]:\n        """Get assets by status."""\n        result = await self.session.execute(\n            select(Asset)\n            .where(\n                and_(\n                    Asset.company_id == company_id,\n                    Asset.status == status,\n                    Asset.is_deleted == False\n                )\n            )\n            .order_by(Asset.asset_number)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_location(\n        self,\n        company_id: UUID,\n        location_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[Asset]:\n        """Get assets by location."""\n        result = await self.session.execute(\n            select(Asset)\n            .where(\n                and_(\n                    Asset.company_id == company_id,\n                    Asset.location_id == location_id,\n                    Asset.is_deleted == False\n                )\n            )\n            .order_by(Asset.asset_number)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_custodian(\n        self,\n        company_id: UUID,\n        custodian_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[Asset]:\n        """Get assets by custodian/employee."""\n        result = await self.session.execute(\n            select(Asset)\n            .where(\n                and_(\n                    Asset.company_id == company_id,\n                    Asset.custodian_id == custodian_id,\n                    Asset.is_deleted == False\n                )\n            )\n            .order_by(Asset.asset_number)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_date_range(\n        self,\n        company_id: UUID,\n        start_date: date,\n        end_date: date,\n        date_field: str = \'acquisition_date\'\n    ) -> List[Asset]:\n        """Get assets by date range."""\n        date_column = getattr(Asset, date_field, Asset.acquisition_date)\n        \n        result = await self.session.execute(\n            select(Asset)\n            .where(\n                and_(\n                    Asset.company_id == company_id,\n                    date_column >= start_date,\n                    date_column <= end_date,\n                    Asset.is_deleted == False\n                )\n            )\n            .order_by(date_column)\n        )\n        return list(result.scalars().all())\n    \n    async def search_assets(\n        self,\n        company_id: UUID,\n        search_term: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[Asset]:\n        """Search assets by various fields."""\n        search_pattern = f"%{search_term}%"\n        result = await self.session.execute(\n            select(Asset)\n            .where(\n                and_(\n                    Asset.company_id == company_id,\n                    or_(\n                        Asset.asset_number.ilike(search_pattern),\n                        Asset.name.ilike(search_pattern),\n                        Asset.description.ilike(search_pattern),\n                        Asset.serial_number.ilike(search_pattern),\n                        Asset.tag_number.ilike(search_pattern)\n                    ),\n                    Asset.is_deleted == False\n                )\n            )\n            .order_by(Asset.asset_number)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_assets_due_for_depreciation(\n        self,\n        company_id: UUID,\n        as_of_date: date\n    ) -> List[Asset]:\n        """Get assets that are due for depreciation calculation."""\n        result = await self.session.execute(\n            select(Asset)\n            .where(\n                and_(\n                    Asset.company_id == company_id,\n                    Asset.status == AssetStatus.ACTIVE,\n                    Asset.acquisition_date <= as_of_date,\n                    Asset.is_deleted == False\n                )\n            )\n            .order_by(Asset.asset_number)\n        )\n        return list(result.scalars().all())\n    \n    async def get_fully_depreciated_assets(\n        self,\n        company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[Asset]:\n        """Get assets that are fully depreciated."""\n        result = await self.session.execute(\n            select(Asset)\n            .where(\n                and_(\n                    Asset.company_id == company_id,\n                    Asset.net_book_value <= Decimal(\'0\'),\n                    Asset.status == AssetStatus.ACTIVE,\n                    Asset.is_deleted == False\n                )\n            )\n            .order_by(Asset.asset_number)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def update_depreciation(\n        self,\n        asset_id: UUID,\n        accumulated_depreciation: Decimal,\n        net_book_value: Decimal\n    ) -> Optional[Asset]:\n        """Update asset depreciation values."""\n        asset = await self.get(asset_id)\n        if not asset:\n            return None\n        \n        asset.accumulated_depreciation = accumulated_depreciation\n        asset.net_book_value = net_book_value\n        asset.last_depreciation_date = datetime.utcnow().date()\n        await self.session.flush()\n        return asset\n    \n    async def change_status(\n        self,\n        asset_id: UUID,\n        new_status: AssetStatus,\n        reason: Optional[str] = None\n    ) -> Optional[Asset]:\n        """Change asset status."""\n        asset = await self.get(asset_id)\n        if not asset:\n            return None\n        \n        asset.status = new_status\n        if new_status == AssetStatus.DISPOSED:\n            asset.disposal_date = datetime.utcnow().date()\n        await self.session.flush()\n        return asset\n    \n    async def get_asset_summary(\n        self,\n        company_id: UUID\n    ) -> Dict[str, Any]:\n        """Get summary statistics for assets."""\n        # Count by status\n        status_result = await self.session.execute(\n            select(\n                Asset.status,\n                func.count(Asset.id).label(\'count\'),\n                func.sum(Asset.acquisition_cost).label(\'total_cost\'),\n                func.sum(Asset.accumulated_depreciation).label(\'total_depreciation\'),\n                func.sum(Asset.net_book_value).label(\'total_nbv\')\n            )\n            .where(\n                and_(\n                    Asset.company_id == company_id,\n                    Asset.is_deleted == False\n                )\n            )\n            .group_by(Asset.status)\n        )\n        \n        summary = {\n            \'by_status\': {},\n            \'total_assets\': 0,\n            \'total_cost\': Decimal(\'0\'),\n            \'total_depreciation\': Decimal(\'0\'),\n            \'total_net_book_value\': Decimal(\'0\')\n        }\n        \n        for row in status_result.all():\n            summary[\'by_status\'][row[0].value] = {\n                \'count\': row[1],\n                \'total_cost\': float(row[2] or 0),\n                \'total_depreciation\': float(row[3] or 0),\n                \'total_net_book_value\': float(row[4] or 0)\n            }\n            summary[\'total_assets\'] += row[1]\n            summary[\'total_cost\'] += row[2] or Decimal(\'0\')\n            summary[\'total_depreciation\'] += row[3] or Decimal(\'0\')\n            summary[\'total_net_book_value\'] += row[4] or Decimal(\'0\')\n        \n        return summary\n    \n    async def get_next_asset_number(\n        self,\n        company_id: UUID,\n        prefix: str = \'AST\'\n    ) -> str:\n        """Generate next asset number."""\n        result = await self.session.execute(\n            select(func.max(Asset.asset_number))\n            .where(\n                and_(\n                    Asset.company_id == company_id,\n                    Asset.asset_number.like(f\'{prefix}%\')\n                )\n            )\n        )\n        max_number = result.scalar()\n        \n        if max_number:\n            try:\n                number_part = int(max_number.replace(prefix, \'\'))\n                return f"{prefix}{number_part + 1:06d}"\n            except ValueError:\n                return f"{prefix}000001"\n        \n        return f"{prefix}000001"\n\n\nclass AssetDepreciationRepository(BaseRepository[AssetDepreciation]):\n    """Repository for asset depreciation operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AssetDepreciation)\n    \n    async def get_by_asset(\n        self,\n        asset_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AssetDepreciation]:\n        """Get all depreciation records for an asset."""\n        result = await self.session.execute(\n            select(AssetDepreciation)\n            .where(AssetDepreciation.asset_id == asset_id)\n            .order_by(desc(AssetDepreciation.fiscal_year), desc(AssetDepreciation.fiscal_period))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_period(\n        self,\n        company_id: UUID,\n        fiscal_year: int,\n        fiscal_period: int\n    ) -> List[AssetDepreciation]:\n        """Get depreciation records for a specific period."""\n        result = await self.session.execute(\n            select(AssetDepreciation)\n            .join(Asset)\n            .where(\n                and_(\n                    Asset.company_id == company_id,\n                    AssetDepreciation.fiscal_year == fiscal_year,\n                    AssetDepreciation.fiscal_period == fiscal_period\n                )\n            )\n            .order_by(Asset.asset_number)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_date_range(\n        self,\n        asset_id: UUID,\n        start_date: date,\n        end_date: date\n    ) -> List[AssetDepreciation]:\n        """Get depreciation records within a date range."""\n        result = await self.session.execute(\n            select(AssetDepreciation)\n            .where(\n                and_(\n                    AssetDepreciation.asset_id == asset_id,\n                    AssetDepreciation.depreciation_date >= start_date,\n                    AssetDepreciation.depreciation_date <= end_date\n                )\n            )\n            .order_by(AssetDepreciation.depreciation_date)\n        )\n        return list(result.scalars().all())\n    \n    async def create_depreciation(\n        self,\n        asset_id: UUID,\n        fiscal_year: int,\n        fiscal_period: int,\n        depreciation_date: date,\n        depreciation_amount: Decimal,\n        accumulated_depreciation: Decimal,\n        net_book_value: Decimal,\n        method: DepreciationMethod\n    ) -> AssetDepreciation:\n        """Create a depreciation record."""\n        depreciation = AssetDepreciation(\n            asset_id=asset_id,\n            fiscal_year=fiscal_year,\n            fiscal_period=fiscal_period,\n            depreciation_date=depreciation_date,\n            depreciation_amount=depreciation_amount,\n            accumulated_depreciation=accumulated_depreciation,\n            net_book_value=net_book_value,\n            method=method\n        )\n        self.session.add(depreciation)\n        await self.session.flush()\n        return depreciation\n    \n    async def get_total_depreciation_for_period(\n        self,\n        company_id: UUID,\n        fiscal_year: int,\n        fiscal_period: int\n    ) -> Decimal:\n        """Get total depreciation for a period."""\n        result = await self.session.execute(\n            select(func.sum(AssetDepreciation.depreciation_amount))\n            .join(Asset)\n            .where(\n                and_(\n                    Asset.company_id == company_id,\n                    AssetDepreciation.fiscal_year == fiscal_year,\n                    AssetDepreciation.fiscal_period == fiscal_period\n                )\n            )\n        )\n        return result.scalar() or Decimal(\'0\')\n    \n    async def reverse_depreciation(\n        self,\n        depreciation_id: UUID,\n        reason: str\n    ) -> Optional[AssetDepreciation]:\n        """Reverse a depreciation entry."""\n        depreciation = await self.get(depreciation_id)\n        if not depreciation:\n            return None\n        \n        depreciation.is_reversed = True\n        depreciation.reversal_reason = reason\n        depreciation.reversed_at = datetime.utcnow()\n        await self.session.flush()\n        return depreciation\n    \n    async def calculate_straight_line(\n        self,\n        acquisition_cost: Decimal,\n        salvage_value: Decimal,\n        useful_life_years: int,\n        periods_per_year: int = 12\n    ) -> Decimal:\n        """Calculate straight-line depreciation per period."""\n        depreciable_amount = acquisition_cost - salvage_value\n        annual_depreciation = depreciable_amount / useful_life_years\n        return annual_depreciation / periods_per_year\n    \n    async def calculate_declining_balance(\n        self,\n        net_book_value: Decimal,\n        rate: Decimal,\n        periods_per_year: int = 12\n    ) -> Decimal:\n        """Calculate declining balance depreciation per period."""\n        annual_depreciation = net_book_value * rate\n        return annual_depreciation / periods_per_year\n\n\nclass AssetMaintenanceRepository(CompanyScopedRepository[AssetMaintenance]):\n    """Repository for asset maintenance operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AssetMaintenance)\n    \n    async def get_by_asset(\n        self,\n        asset_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AssetMaintenance]:\n        """Get maintenance records for an asset."""\n        result = await self.session.execute(\n            select(AssetMaintenance)\n            .where(AssetMaintenance.asset_id == asset_id)\n            .order_by(desc(AssetMaintenance.maintenance_date))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_upcoming_maintenance(\n        self,\n        company_id: UUID,\n        days_ahead: int = 30\n    ) -> List[AssetMaintenance]:\n        """Get scheduled maintenance due within specified days."""\n        today = datetime.utcnow().date()\n        end_date = today + timedelta(days=days_ahead)\n        \n        result = await self.session.execute(\n            select(AssetMaintenance)\n            .where(\n                and_(\n                    AssetMaintenance.company_id == company_id,\n                    AssetMaintenance.next_maintenance_date >= today,\n                    AssetMaintenance.next_maintenance_date <= end_date,\n                    AssetMaintenance.is_completed == False,\n                    AssetMaintenance.is_deleted == False\n                )\n            )\n            .order_by(AssetMaintenance.next_maintenance_date)\n        )\n        return list(result.scalars().all())\n    \n    async def get_overdue_maintenance(\n        self,\n        company_id: UUID\n    ) -> List[AssetMaintenance]:\n        """Get overdue maintenance records."""\n        today = datetime.utcnow().date()\n        \n        result = await self.session.execute(\n            select(AssetMaintenance)\n            .where(\n                and_(\n                    AssetMaintenance.company_id == company_id,\n                    AssetMaintenance.next_maintenance_date < today,\n                    AssetMaintenance.is_completed == False,\n                    AssetMaintenance.is_deleted == False\n                )\n            )\n            .order_by(AssetMaintenance.next_maintenance_date)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_type(\n        self,\n        company_id: UUID,\n        maintenance_type: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AssetMaintenance]:\n        """Get maintenance records by type."""\n        result = await self.session.execute(\n            select(AssetMaintenance)\n            .where(\n                and_(\n                    AssetMaintenance.company_id == company_id,\n                    AssetMaintenance.maintenance_type == maintenance_type,\n                    AssetMaintenance.is_deleted == False\n                )\n            )\n            .order_by(desc(AssetMaintenance.maintenance_date))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def create_maintenance(\n        self,\n        company_id: UUID,\n        asset_id: UUID,\n        maintenance_type: str,\n        description: str,\n        maintenance_date: date,\n        cost: Decimal = Decimal(\'0\'),\n        vendor: Optional[str] = None,\n        next_maintenance_date: Optional[date] = None,\n        created_by: Optional[UUID] = None\n    ) -> AssetMaintenance:\n        """Create a maintenance record."""\n        maintenance = AssetMaintenance(\n            company_id=company_id,\n            asset_id=asset_id,\n            maintenance_type=maintenance_type,\n            description=description,\n            maintenance_date=maintenance_date,\n            cost=cost,\n            vendor=vendor,\n            next_maintenance_date=next_maintenance_date,\n            is_completed=True,\n            created_by=created_by\n        )\n        self.session.add(maintenance)\n        await self.session.flush()\n        return maintenance\n    \n    async def complete_maintenance(\n        self,\n        maintenance_id: UUID,\n        actual_cost: Decimal,\n        notes: Optional[str] = None\n    ) -> Optional[AssetMaintenance]:\n        """Mark maintenance as completed."""\n        maintenance = await self.get(maintenance_id)\n        if not maintenance:\n            return None\n        \n        maintenance.is_completed = True\n        maintenance.cost = actual_cost\n        maintenance.completion_notes = notes\n        maintenance.completed_at = datetime.utcnow()\n        await self.session.flush()\n        return maintenance\n    \n    async def get_maintenance_summary(\n        self,\n        company_id: UUID,\n        fiscal_year: Optional[int] = None\n    ) -> Dict[str, Any]:\n        """Get maintenance summary statistics."""\n        conditions = [\n            AssetMaintenance.company_id == company_id,\n            AssetMaintenance.is_deleted == False\n        ]\n        \n        if fiscal_year:\n            start_date = date(fiscal_year, 1, 1)\n            end_date = date(fiscal_year, 12, 31)\n            conditions.extend([\n                AssetMaintenance.maintenance_date >= start_date,\n                AssetMaintenance.maintenance_date <= end_date\n            ])\n        \n        result = await self.session.execute(\n            select(\n                AssetMaintenance.maintenance_type,\n                func.count(AssetMaintenance.id).label(\'count\'),\n                func.sum(AssetMaintenance.cost).label(\'total_cost\')\n            )\n            .where(and_(*conditions))\n            .group_by(AssetMaintenance.maintenance_type)\n        )\n        \n        summary = {\n            \'by_type\': {},\n            \'total_count\': 0,\n            \'total_cost\': Decimal(\'0\')\n        }\n        \n        for row in result.all():\n            summary[\'by_type\'][row[0]] = {\n                \'count\': row[1],\n                \'total_cost\': float(row[2] or 0)\n            }\n            summary[\'total_count\'] += row[1]\n            summary[\'total_cost\'] += row[2] or Decimal(\'0\')\n        \n        return summary\n\n\nclass AssetTransferRepository(CompanyScopedRepository[AssetTransfer]):\n    """Repository for asset transfer operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AssetTransfer)\n    \n    async def get_by_asset(\n        self,\n        asset_id: UUID\n    ) -> List[AssetTransfer]:\n        """Get transfer history for an asset."""\n        result = await self.session.execute(\n            select(AssetTransfer)\n            .where(AssetTransfer.asset_id == asset_id)\n            .order_by(desc(AssetTransfer.transfer_date))\n        )\n        return list(result.scalars().all())\n    \n    async def get_pending_transfers(\n        self,\n        company_id: UUID\n    ) -> List[AssetTransfer]:\n        """Get pending transfer requests."""\n        result = await self.session.execute(\n            select(AssetTransfer)\n            .where(\n                and_(\n                    AssetTransfer.company_id == company_id,\n                    AssetTransfer.status == \'pending\',\n                    AssetTransfer.is_deleted == False\n                )\n            )\n            .order_by(AssetTransfer.transfer_date)\n        )\n        return list(result.scalars().all())\n    \n    async def create_transfer(\n        self,\n        company_id: UUID,\n        asset_id: UUID,\n        from_location_id: Optional[UUID],\n        to_location_id: Optional[UUID],\n        from_custodian_id: Optional[UUID],\n        to_custodian_id: Optional[UUID],\n        transfer_date: date,\n        reason: str,\n        created_by: Optional[UUID] = None\n    ) -> AssetTransfer:\n        """Create an asset transfer request."""\n        transfer = AssetTransfer(\n            company_id=company_id,\n            asset_id=asset_id,\n            from_location_id=from_location_id,\n            to_location_id=to_location_id,\n            from_custodian_id=from_custodian_id,\n            to_custodian_id=to_custodian_id,\n            transfer_date=transfer_date,\n            reason=reason,\n            status=\'pending\',\n            created_by=created_by\n        )\n        self.session.add(transfer)\n        await self.session.flush()\n        return transfer\n    \n    async def approve_transfer(\n        self,\n        transfer_id: UUID,\n        approved_by: UUID\n    ) -> Optional[AssetTransfer]:\n        """Approve an asset transfer."""\n        transfer = await self.get(transfer_id)\n        if not transfer:\n            return None\n        \n        transfer.status = \'approved\'\n        transfer.approved_at = datetime.utcnow()\n        transfer.approved_by = approved_by\n        await self.session.flush()\n        return transfer\n    \n    async def complete_transfer(\n        self,\n        transfer_id: UUID\n    ) -> Optional[AssetTransfer]:\n        """Complete an approved transfer."""\n        transfer = await self.get(transfer_id)\n        if not transfer:\n            return None\n        \n        transfer.status = \'completed\'\n        transfer.completed_at = datetime.utcnow()\n        \n        # Update the asset\n        asset = await self.session.get(Asset, transfer.asset_id)\n        if asset:\n            if transfer.to_location_id:\n                asset.location_id = transfer.to_location_id\n            if transfer.to_custodian_id:\n                asset.custodian_id = transfer.to_custodian_id\n        \n        await self.session.flush()\n        return transfer\n\n\nclass AssetDisposalRepository(CompanyScopedRepository[AssetDisposal]):\n    """Repository for asset disposal operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AssetDisposal)\n    \n    async def get_by_asset(\n        self,\n        asset_id: UUID\n    ) -> Optional[AssetDisposal]:\n        """Get disposal record for an asset."""\n        result = await self.session.execute(\n            select(AssetDisposal)\n            .where(AssetDisposal.asset_id == asset_id)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_by_disposal_type(\n        self,\n        company_id: UUID,\n        disposal_type: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AssetDisposal]:\n        """Get disposals by type."""\n        result = await self.session.execute(\n            select(AssetDisposal)\n            .where(\n                and_(\n                    AssetDisposal.company_id == company_id,\n                    AssetDisposal.disposal_type == disposal_type,\n                    AssetDisposal.is_deleted == False\n                )\n            )\n            .order_by(desc(AssetDisposal.disposal_date))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_date_range(\n        self,\n        company_id: UUID,\n        start_date: date,\n        end_date: date\n    ) -> List[AssetDisposal]:\n        """Get disposals within a date range."""\n        result = await self.session.execute(\n            select(AssetDisposal)\n            .where(\n                and_(\n                    AssetDisposal.company_id == company_id,\n                    AssetDisposal.disposal_date >= start_date,\n                    AssetDisposal.disposal_date <= end_date,\n                    AssetDisposal.is_deleted == False\n                )\n            )\n            .order_by(AssetDisposal.disposal_date)\n        )\n        return list(result.scalars().all())\n    \n    async def create_disposal(\n        self,\n        company_id: UUID,\n        asset_id: UUID,\n        disposal_type: str,\n        disposal_date: date,\n        proceeds: Decimal = Decimal(\'0\'),\n        reason: Optional[str] = None,\n        buyer: Optional[str] = None,\n        created_by: Optional[UUID] = None\n    ) -> AssetDisposal:\n        """Create an asset disposal record."""\n        # Get asset details\n        asset = await self.session.get(Asset, asset_id)\n        if not asset:\n            raise ValueError(f"Asset {asset_id} not found")\n        \n        # Calculate gain/loss\n        gain_loss = proceeds - asset.net_book_value\n        \n        disposal = AssetDisposal(\n            company_id=company_id,\n            asset_id=asset_id,\n            disposal_type=disposal_type,\n            disposal_date=disposal_date,\n            proceeds=proceeds,\n            net_book_value=asset.net_book_value,\n            gain_loss=gain_loss,\n            reason=reason,\n            buyer=buyer,\n            created_by=created_by\n        )\n        self.session.add(disposal)\n        \n        # Update asset status\n        asset.status = AssetStatus.DISPOSED\n        asset.disposal_date = disposal_date\n        \n        await self.session.flush()\n        return disposal\n    \n    async def get_disposal_summary(\n        self,\n        company_id: UUID,\n        fiscal_year: Optional[int] = None\n    ) -> Dict[str, Any]:\n        """Get disposal summary statistics."""\n        conditions = [\n            AssetDisposal.company_id == company_id,\n            AssetDisposal.is_deleted == False\n        ]\n        \n        if fiscal_year:\n            start_date = date(fiscal_year, 1, 1)\n            end_date = date(fiscal_year, 12, 31)\n            conditions.extend([\n                AssetDisposal.disposal_date >= start_date,\n                AssetDisposal.disposal_date <= end_date\n            ])\n        \n        result = await self.session.execute(\n            select(\n                AssetDisposal.disposal_type,\n                func.count(AssetDisposal.id).label(\'count\'),\n                func.sum(AssetDisposal.proceeds).label(\'total_proceeds\'),\n                func.sum(AssetDisposal.gain_loss).label(\'total_gain_loss\')\n            )\n            .where(and_(*conditions))\n            .group_by(AssetDisposal.disposal_type)\n        )\n        \n        summary = {\n            \'by_type\': {},\n            \'total_disposals\': 0,\n            \'total_proceeds\': Decimal(\'0\'),\n            \'total_gain_loss\': Decimal(\'0\')\n        }\n        \n        for row in result.all():\n            summary[\'by_type\'][row[0]] = {\n                \'count\': row[1],\n                \'total_proceeds\': float(row[2] or 0),\n                \'total_gain_loss\': float(row[3] or 0)\n            }\n            summary[\'total_disposals\'] += row[1]\n            summary[\'total_proceeds\'] += row[2] or Decimal(\'0\')\n            summary[\'total_gain_loss\'] += row[3] or Decimal(\'0\')\n        \n        return summary\n\n\nclass AssetInsuranceRepository(CompanyScopedRepository[AssetInsurance]):\n    """Repository for asset insurance operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AssetInsurance)\n    \n    async def get_by_asset(\n        self,\n        asset_id: UUID\n    ) -> List[AssetInsurance]:\n        """Get insurance policies for an asset."""\n        result = await self.session.execute(\n            select(AssetInsurance)\n            .where(AssetInsurance.asset_id == asset_id)\n            .order_by(desc(AssetInsurance.start_date))\n        )\n        return list(result.scalars().all())\n    \n    async def get_active_policies(\n        self,\n        company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AssetInsurance]:\n        """Get active insurance policies."""\n        today = datetime.utcnow().date()\n        \n        result = await self.session.execute(\n            select(AssetInsurance)\n            .where(\n                and_(\n                    AssetInsurance.company_id == company_id,\n                    AssetInsurance.start_date <= today,\n                    AssetInsurance.end_date >= today,\n                    AssetInsurance.is_deleted == False\n                )\n            )\n            .order_by(AssetInsurance.end_date)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_expiring_policies(\n        self,\n        company_id: UUID,\n        days_ahead: int = 30\n    ) -> List[AssetInsurance]:\n        """Get policies expiring within specified days."""\n        today = datetime.utcnow().date()\n        end_date = today + timedelta(days=days_ahead)\n        \n        result = await self.session.execute(\n            select(AssetInsurance)\n            .where(\n                and_(\n                    AssetInsurance.company_id == company_id,\n                    AssetInsurance.end_date >= today,\n                    AssetInsurance.end_date <= end_date,\n                    AssetInsurance.is_deleted == False\n                )\n            )\n            .order_by(AssetInsurance.end_date)\n        )\n        return list(result.scalars().all())\n    \n    async def create_insurance(\n        self,\n        company_id: UUID,\n        asset_id: UUID,\n        policy_number: str,\n        insurance_company: str,\n        coverage_amount: Decimal,\n        premium: Decimal,\n        start_date: date,\n        end_date: date,\n        created_by: Optional[UUID] = None\n    ) -> AssetInsurance:\n        """Create an insurance policy record."""\n        insurance = AssetInsurance(\n            company_id=company_id,\n            asset_id=asset_id,\n            policy_number=policy_number,\n            insurance_company=insurance_company,\n            coverage_amount=coverage_amount,\n            premium=premium,\n            start_date=start_date,\n            end_date=end_date,\n            created_by=created_by\n        )\n        self.session.add(insurance)\n        await self.session.flush()\n        return insurance\n\n\nclass AssetTagRepository(BaseRepository[AssetTag]):\n    """Repository for asset tag operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AssetTag)\n    \n    async def get_by_name(self, name: str) -> Optional[AssetTag]:\n        """Get tag by name."""\n        result = await self.session.execute(\n            select(AssetTag).where(AssetTag.name == name)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_or_create(self, name: str) -> AssetTag:\n        """Get existing tag or create new one."""\n        tag = await self.get_by_name(name)\n        if tag:\n            return tag\n        \n        tag = AssetTag(name=name)\n        self.session.add(tag)\n        await self.session.flush()\n        return tag\n\n\nclass AssetLocationRepository(CompanyScopedRepository[AssetLocation]):\n    """Repository for asset location operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AssetLocation)\n    \n    async def get_by_code(\n        self,\n        company_id: UUID,\n        code: str\n    ) -> Optional[AssetLocation]:\n        """Get location by code."""\n        result = await self.session.execute(\n            select(AssetLocation).where(\n                and_(\n                    AssetLocation.company_id == company_id,\n                    AssetLocation.code == code,\n                    AssetLocation.is_deleted == False\n                )\n            )\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_root_locations(\n        self,\n        company_id: UUID\n    ) -> List[AssetLocation]:\n        """Get root locations (no parent)."""\n        result = await self.session.execute(\n            select(AssetLocation)\n            .where(\n                and_(\n                    AssetLocation.company_id == company_id,\n                    AssetLocation.parent_id.is_(None),\n                    AssetLocation.is_deleted == False\n                )\n            )\n            .order_by(AssetLocation.name)\n        )\n        return list(result.scalars().all())\n    \n    async def get_children(\n        self,\n        parent_id: UUID\n    ) -> List[AssetLocation]:\n        """Get child locations."""\n        result = await self.session.execute(\n            select(AssetLocation)\n            .where(\n                and_(\n                    AssetLocation.parent_id == parent_id,\n                    AssetLocation.is_deleted == False\n                )\n            )\n            .order_by(AssetLocation.name)\n        )\n        return list(result.scalars().all())\n    \n    async def get_location_tree(\n        self,\n        company_id: UUID\n    ) -> List[Dict[str, Any]]:\n        """Get hierarchical location tree."""\n        locations = await self.get_all(company_id, limit=1000)\n        \n        location_map = {str(l.id): l for l in locations}\n        tree = []\n        \n        for location in locations:\n            if not location.parent_id:\n                tree.append({\n                    \'id\': str(location.id),\n                    \'code\': location.code,\n                    \'name\': location.name,\n                    \'address\': location.address,\n                    \'children\': []\n                })\n        \n        for location in locations:\n            if location.parent_id:\n                parent_id_str = str(location.parent_id)\n                for node in tree:\n                    self._add_child_to_tree(node, parent_id_str, location)\n        \n        return tree\n    \n    def _add_child_to_tree(\n        self,\n        node: Dict[str, Any],\n        parent_id: str,\n        location: AssetLocation\n    ) -> bool:\n        """Recursively add child to tree node."""\n        if node[\'id\'] == parent_id:\n            node[\'children\'].append({\n                \'id\': str(location.id),\n                \'code\': location.code,\n                \'name\': location.name,\n                \'address\': location.address,\n                \'children\': []\n            })\n            return True\n        \n        for child in node.get(\'children\', []):\n            if self._add_child_to_tree(child, parent_id, location):\n                return True\n        \n        return False\n    \n    async def get_asset_count_by_location(\n        self,\n        company_id: UUID\n    ) -> Dict[str, int]:\n        """Get count of assets per location."""\n        result = await self.session.execute(\n            select(\n                Asset.location_id,\n                func.count(Asset.id)\n            )\n            .join(AssetLocation)\n            .where(\n                and_(\n                    AssetLocation.company_id == company_id,\n                    Asset.is_deleted == False\n                )\n            )\n            .group_by(Asset.location_id)\n        )\n        return {str(row[0]): row[1] for row in result.all()}\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/repositories/asset.py\' created successfully.") </create-file> </tool_result>'}
{'role': 'assistant', 'content': 'Now let me create the AI repository for AI and analytics features.\n\n<function_calls>\n<create-file file_path="backend/app/repositories/ai.py">\n"""\nSAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM - AI Repository\nComprehensive data access layer for AI, ML, and analytics features.\n"""\n\nfrom datetime import datetime, date, timedelta\nfrom decimal import Decimal\nfrom typing import Optional, List, Dict, Any, Tuple, Union\nfrom uuid import UUID\nimport json\n\nfrom sqlalchemy import (\n    select, update, delete, and_, or_, func, case, desc, asc,\n    literal_column, text, cast, String, Integer, Numeric, Date, DateTime\n)\nfrom sqlalchemy.ext.asyncio import AsyncSession\nfrom sqlalchemy.orm import selectinload, joinedload, contains_eager\nfrom sqlalchemy.dialects.postgresql import JSONB\n\nfrom .base import BaseRepository, CompanyScopedRepository\nfrom ..models.ai import (\n    AIModel, AIModelVersion, AIPrediction, AIAnomaly, AIRecommendation,\n    AIFeedback, AIConversation, AIConversationMessage, AITrainingJob,\n    AIFeatureImportance, AIModelMetric, AIDataset, AIDatasetRow\n)\nfrom ..models.base import ModelStatus, PredictionStatus, AnomalySeverity\n\n\nclass AIModelRepository(CompanyScopedRepository[AIModel]):\n    """Repository for AI model operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AIModel)\n    \n    async def get_by_name(\n        self,\n        company_id: UUID,\n        name: str\n    ) -> Optional[AIModel]:\n        """Get model by name within a company."""\n        result = await self.session.execute(\n            select(AIModel).where(\n                and_(\n                    AIModel.company_id == company_id,\n                    AIModel.name == name,\n                    AIModel.is_deleted == False\n                )\n            )\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_by_type(\n        self,\n        company_id: UUID,\n        model_type: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIModel]:\n        """Get models by type (forecasting, classification, etc.)."""\n        result = await self.session.execute(\n            select(AIModel)\n            .where(\n                and_(\n                    AIModel.company_id == company_id,\n                    AIModel.model_type == model_type,\n                    AIModel.is_deleted == False\n                )\n            )\n            .order_by(AIModel.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_active_models(\n        self,\n        company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIModel]:\n        """Get all active/deployed models."""\n        result = await self.session.execute(\n            select(AIModel)\n            .where(\n                and_(\n                    AIModel.company_id == company_id,\n                    AIModel.status == ModelStatus.DEPLOYED,\n                    AIModel.is_deleted == False\n                )\n            )\n            .order_by(AIModel.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_domain(\n        self,\n        company_id: UUID,\n        domain: str\n    ) -> List[AIModel]:\n        """Get models by domain (finance, hr, operations, etc.)."""\n        result = await self.session.execute(\n            select(AIModel)\n            .where(\n                and_(\n                    AIModel.company_id == company_id,\n                    AIModel.domain == domain,\n                    AIModel.is_deleted == False\n                )\n            )\n            .order_by(AIModel.name)\n        )\n        return list(result.scalars().all())\n    \n    async def get_with_versions(\n        self,\n        model_id: UUID\n    ) -> Optional[AIModel]:\n        """Get model with all versions loaded."""\n        result = await self.session.execute(\n            select(AIModel)\n            .options(selectinload(AIModel.versions))\n            .where(AIModel.id == model_id)\n        )\n        return result.scalar_one_or_none()\n    \n    async def update_model_status(\n        self,\n        model_id: UUID,\n        status: ModelStatus\n    ) -> Optional[AIModel]:\n        """Update model status."""\n        model = await self.get(model_id)\n        if not model:\n            return None\n        \n        model.status = status\n        if status == ModelStatus.DEPLOYED:\n            model.deployed_at = datetime.utcnow()\n        await self.session.flush()\n        return model\n    \n    async def record_prediction(\n        self,\n        model_id: UUID,\n        prediction_count: int = 1\n    ) -> Optional[AIModel]:\n        """Record that the model was used for predictions."""\n        model = await self.get(model_id)\n        if not model:\n            return None\n        \n        model.prediction_count = (model.prediction_count or 0) + prediction_count\n        model.last_prediction_at = datetime.utcnow()\n        await self.session.flush()\n        return model\n    \n    async def get_model_performance(\n        self,\n        model_id: UUID,\n        days: int = 30\n    ) -> Dict[str, Any]:\n        """Get model performance metrics over time."""\n        cutoff = datetime.utcnow() - timedelta(days=days)\n        \n        result = await self.session.execute(\n            select(AIModelMetric)\n            .where(\n                and_(\n                    AIModelMetric.model_id == model_id,\n                    AIModelMetric.recorded_at >= cutoff\n                )\n            )\n            .order_by(AIModelMetric.recorded_at)\n        )\n        \n        metrics = list(result.scalars().all())\n        \n        return {\n            \'model_id\': str(model_id),\n            \'period_days\': days,\n            \'metrics\': [\n                {\n                    \'metric_name\': m.metric_name,\n                    \'metric_value\': float(m.metric_value) if m.metric_value else None,\n                    \'recorded_at\': m.recorded_at.isoformat() if m.recorded_at else None\n                }\n                for m in metrics\n            ]\n        }\n\n\nclass AIModelVersionRepository(BaseRepository[AIModelVersion]):\n    """Repository for AI model version operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AIModelVersion)\n    \n    async def get_by_model(\n        self,\n        model_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIModelVersion]:\n        """Get all versions for a model."""\n        result = await self.session.execute(\n            select(AIModelVersion)\n            .where(AIModelVersion.model_id == model_id)\n            .order_by(desc(AIModelVersion.version_number))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_latest_version(\n        self,\n        model_id: UUID\n    ) -> Optional[AIModelVersion]:\n        """Get the latest version of a model."""\n        result = await self.session.execute(\n            select(AIModelVersion)\n            .where(AIModelVersion.model_id == model_id)\n            .order_by(desc(AIModelVersion.version_number))\n            .limit(1)\n        )\n        return result.scalar_one_or_none()\n    \n    async def get_by_version_number(\n        self,\n        model_id: UUID,\n        version_number: int\n    ) -> Optional[AIModelVersion]:\n        """Get a specific version by number."""\n        result = await self.session.execute(\n            select(AIModelVersion)\n            .where(\n                and_(\n                    AIModelVersion.model_id == model_id,\n                    AIModelVersion.version_number == version_number\n                )\n            )\n        )\n        return result.scalar_one_or_none()\n    \n    async def create_version(\n        self,\n        model_id: UUID,\n        version_number: int,\n        model_artifact_path: str,\n        training_metrics: Dict[str, Any],\n        hyperparameters: Dict[str, Any],\n        feature_columns: List[str],\n        created_by: Optional[UUID] = None\n    ) -> AIModelVersion:\n        """Create a new model version."""\n        version = AIModelVersion(\n            model_id=model_id,\n            version_number=version_number,\n            model_artifact_path=model_artifact_path,\n            training_metrics=training_metrics,\n            hyperparameters=hyperparameters,\n            feature_columns=feature_columns,\n            is_active=False,\n            created_by=created_by\n        )\n        self.session.add(version)\n        await self.session.flush()\n        return version\n    \n    async def activate_version(\n        self,\n        version_id: UUID\n    ) -> Optional[AIModelVersion]:\n        """Activate a model version for production use."""\n        version = await self.get(version_id)\n        if not version:\n            return None\n        \n        # Deactivate other versions\n        await self.session.execute(\n            update(AIModelVersion)\n            .where(AIModelVersion.model_id == version.model_id)\n            .values(is_active=False)\n        )\n        \n        version.is_active = True\n        version.activated_at = datetime.utcnow()\n        await self.session.flush()\n        return version\n    \n    async def compare_versions(\n        self,\n        version_id_1: UUID,\n        version_id_2: UUID\n    ) -> Dict[str, Any]:\n        """Compare two model versions."""\n        v1 = await self.get(version_id_1)\n        v2 = await self.get(version_id_2)\n        \n        if not v1 or not v2:\n            return {\'error\': \'One or both versions not found\'}\n        \n        return {\n            \'version_1\': {\n                \'version_number\': v1.version_number,\n                \'metrics\': v1.training_metrics,\n                \'created_at\': v1.created_at.isoformat() if v1.created_at else None\n            },\n            \'version_2\': {\n                \'version_number\': v2.version_number,\n                \'metrics\': v2.training_metrics,\n                \'created_at\': v2.created_at.isoformat() if v2.created_at else None\n            },\n            \'metric_differences\': self._calculate_metric_differences(\n                v1.training_metrics or {},\n                v2.training_metrics or {}\n            )\n        }\n    \n    def _calculate_metric_differences(\n        self,\n        metrics1: Dict[str, Any],\n        metrics2: Dict[str, Any]\n    ) -> Dict[str, float]:\n        """Calculate differences between metrics."""\n        differences = {}\n        all_keys = set(metrics1.keys()) | set(metrics2.keys())\n        \n        for key in all_keys:\n            v1 = metrics1.get(key, 0)\n            v2 = metrics2.get(key, 0)\n            \n            if isinstance(v1, (int, float)) and isinstance(v2, (int, float)):\n                differences[key] = v2 - v1\n        \n        return differences\n\n\nclass AIPredictionRepository(CompanyScopedRepository[AIPrediction]):\n    """Repository for AI prediction operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AIPrediction)\n    \n    async def get_by_model(\n        self,\n        model_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIPrediction]:\n        """Get predictions by model."""\n        result = await self.session.execute(\n            select(AIPrediction)\n            .where(AIPrediction.model_id == model_id)\n            .order_by(desc(AIPrediction.created_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_status(\n        self,\n        company_id: UUID,\n        status: PredictionStatus,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIPrediction]:\n        """Get predictions by status."""\n        result = await self.session.execute(\n            select(AIPrediction)\n            .where(\n                and_(\n                    AIPrediction.company_id == company_id,\n                    AIPrediction.status == status,\n                    AIPrediction.is_deleted == False\n                )\n            )\n            .order_by(desc(AIPrediction.created_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_entity(\n        self,\n        entity_type: str,\n        entity_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIPrediction]:\n        """Get predictions for a specific entity."""\n        result = await self.session.execute(\n            select(AIPrediction)\n            .where(\n                and_(\n                    AIPrediction.entity_type == entity_type,\n                    AIPrediction.entity_id == entity_id,\n                    AIPrediction.is_deleted == False\n                )\n            )\n            .order_by(desc(AIPrediction.created_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_date_range(\n        self,\n        company_id: UUID,\n        start_date: date,\n        end_date: date\n    ) -> List[AIPrediction]:\n        """Get predictions within a date range."""\n        result = await self.session.execute(\n            select(AIPrediction)\n            .where(\n                and_(\n                    AIPrediction.company_id == company_id,\n                    func.date(AIPrediction.prediction_date) >= start_date,\n                    func.date(AIPrediction.prediction_date) <= end_date,\n                    AIPrediction.is_deleted == False\n                )\n            )\n            .order_by(AIPrediction.prediction_date)\n        )\n        return list(result.scalars().all())\n    \n    async def create_prediction(\n        self,\n        company_id: UUID,\n        model_id: UUID,\n        prediction_type: str,\n        prediction_value: Any,\n        confidence_score: Decimal,\n        entity_type: Optional[str] = None,\n        entity_id: Optional[UUID] = None,\n        input_data: Optional[Dict[str, Any]] = None,\n        explanation: Optional[Dict[str, Any]] = None,\n        created_by: Optional[UUID] = None\n    ) -> AIPrediction:\n        """Create a new prediction record."""\n        prediction = AIPrediction(\n            company_id=company_id,\n            model_id=model_id,\n            prediction_type=prediction_type,\n            prediction_value=prediction_value,\n            confidence_score=confidence_score,\n            entity_type=entity_type,\n            entity_id=entity_id,\n            input_data=input_data,\n            explanation=explanation,\n            status=PredictionStatus.PENDING,\n            created_by=created_by\n        )\n        self.session.add(prediction)\n        await self.session.flush()\n        return prediction\n    \n    async def update_actual_value(\n        self,\n        prediction_id: UUID,\n        actual_value: Any\n    ) -> Optional[AIPrediction]:\n        """Update prediction with actual value for accuracy tracking."""\n        prediction = await self.get(prediction_id)\n        if not prediction:\n            return None\n        \n        prediction.actual_value = actual_value\n        prediction.status = PredictionStatus.VERIFIED\n        prediction.verified_at = datetime.utcnow()\n        \n        # Calculate error if prediction value is numeric\n        if isinstance(prediction.prediction_value, (int, float)) and isinstance(actual_value, (int, float)):\n            if actual_value != 0:\n                prediction.error_percentage = Decimal(str(\n                    abs(prediction.prediction_value - actual_value) / abs(actual_value) * 100\n                ))\n        \n        await self.session.flush()\n        return prediction\n    \n    async def get_prediction_accuracy(\n        self,\n        model_id: UUID,\n        days: int = 30\n    ) -> Dict[str, Any]:\n        """Calculate prediction accuracy for a model."""\n        cutoff = datetime.utcnow() - timedelta(days=days)\n        \n        result = await self.session.execute(\n            select(AIPrediction)\n            .where(\n                and_(\n                    AIPrediction.model_id == model_id,\n                    AIPrediction.status == PredictionStatus.VERIFIED,\n                    AIPrediction.verified_at >= cutoff\n                )\n            )\n        )\n        \n        predictions = list(result.scalars().all())\n        \n        if not predictions:\n            return {\n                \'model_id\': str(model_id),\n                \'period_days\': days,\n                \'total_verified\': 0,\n                \'average_error\': None\n            }\n        \n        errors = [p.error_percentage for p in predictions if p.error_percentage is not None]\n        \n        return {\n            \'model_id\': str(model_id),\n            \'period_days\': days,\n            \'total_verified\': len(predictions),\n            \'average_error\': float(sum(errors) / len(errors)) if errors else None,\n            \'min_error\': float(min(errors)) if errors else None,\n            \'max_error\': float(max(errors)) if errors else None\n        }\n    \n    async def get_high_confidence_predictions(\n        self,\n        company_id: UUID,\n        min_confidence: Decimal = Decimal(\'0.8\'),\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIPrediction]:\n        """Get predictions with high confidence scores."""\n        result = await self.session.execute(\n            select(AIPrediction)\n            .where(\n                and_(\n                    AIPrediction.company_id == company_id,\n                    AIPrediction.confidence_score >= min_confidence,\n                    AIPrediction.status == PredictionStatus.PENDING,\n                    AIPrediction.is_deleted == False\n                )\n            )\n            .order_by(desc(AIPrediction.confidence_score))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n\n\nclass AIAnomalyRepository(CompanyScopedRepository[AIAnomaly]):\n    """Repository for AI anomaly detection operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AIAnomaly)\n    \n    async def get_by_severity(\n        self,\n        company_id: UUID,\n        severity: AnomalySeverity,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIAnomaly]:\n        """Get anomalies by severity level."""\n        result = await self.session.execute(\n            select(AIAnomaly)\n            .where(\n                and_(\n                    AIAnomaly.company_id == company_id,\n                    AIAnomaly.severity == severity,\n                    AIAnomaly.is_deleted == False\n                )\n            )\n            .order_by(desc(AIAnomaly.detected_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_entity(\n        self,\n        entity_type: str,\n        entity_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIAnomaly]:\n        """Get anomalies for a specific entity."""\n        result = await self.session.execute(\n            select(AIAnomaly)\n            .where(\n                and_(\n                    AIAnomaly.entity_type == entity_type,\n                    AIAnomaly.entity_id == entity_id,\n                    AIAnomaly.is_deleted == False\n                )\n            )\n            .order_by(desc(AIAnomaly.detected_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_anomaly_type(\n        self,\n        company_id: UUID,\n        anomaly_type: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIAnomaly]:\n        """Get anomalies by type."""\n        result = await self.session.execute(\n            select(AIAnomaly)\n            .where(\n                and_(\n                    AIAnomaly.company_id == company_id,\n                    AIAnomaly.anomaly_type == anomaly_type,\n                    AIAnomaly.is_deleted == False\n                )\n            )\n            .order_by(desc(AIAnomaly.detected_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_unresolved_anomalies(\n        self,\n        company_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIAnomaly]:\n        """Get all unresolved anomalies."""\n        result = await self.session.execute(\n            select(AIAnomaly)\n            .where(\n                and_(\n                    AIAnomaly.company_id == company_id,\n                    AIAnomaly.is_resolved == False,\n                    AIAnomaly.is_deleted == False\n                )\n            )\n            .order_by(desc(AIAnomaly.severity), desc(AIAnomaly.detected_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_recent_anomalies(\n        self,\n        company_id: UUID,\n        hours: int = 24\n    ) -> List[AIAnomaly]:\n        """Get anomalies detected in recent hours."""\n        cutoff = datetime.utcnow() - timedelta(hours=hours)\n        \n        result = await self.session.execute(\n            select(AIAnomaly)\n            .where(\n                and_(\n                    AIAnomaly.company_id == company_id,\n                    AIAnomaly.detected_at >= cutoff,\n                    AIAnomaly.is_deleted == False\n                )\n            )\n            .order_by(desc(AIAnomaly.detected_at))\n        )\n        return list(result.scalars().all())\n    \n    async def create_anomaly(\n        self,\n        company_id: UUID,\n        anomaly_type: str,\n        entity_type: str,\n        entity_id: UUID,\n        severity: AnomalySeverity,\n        description: str,\n        expected_value: Optional[Any] = None,\n        actual_value: Optional[Any] = None,\n        deviation_score: Optional[Decimal] = None,\n        context: Optional[Dict[str, Any]] = None,\n        created_by: Optional[UUID] = None\n    ) -> AIAnomaly:\n        """Create a new anomaly record."""\n        anomaly = AIAnomaly(\n            company_id=company_id,\n            anomaly_type=anomaly_type,\n            entity_type=entity_type,\n            entity_id=entity_id,\n            severity=severity,\n            description=description,\n            expected_value=expected_value,\n            actual_value=actual_value,\n            deviation_score=deviation_score,\n            context=context,\n            is_resolved=False,\n            created_by=created_by\n        )\n        self.session.add(anomaly)\n        await self.session.flush()\n        return anomaly\n    \n    async def resolve_anomaly(\n        self,\n        anomaly_id: UUID,\n        resolution_notes: str,\n        resolved_by: UUID\n    ) -> Optional[AIAnomaly]:\n        """Mark an anomaly as resolved."""\n        anomaly = await self.get(anomaly_id)\n        if not anomaly:\n            return None\n        \n        anomaly.is_resolved = True\n        anomaly.resolved_at = datetime.utcnow()\n        anomaly.resolved_by = resolved_by\n        anomaly.resolution_notes = resolution_notes\n        await self.session.flush()\n        return anomaly\n    \n    async def get_anomaly_statistics(\n        self,\n        company_id: UUID,\n        days: int = 30\n    ) -> Dict[str, Any]:\n        """Get anomaly statistics."""\n        cutoff = datetime.utcnow() - timedelta(days=days)\n        \n        # Count by severity\n        severity_result = await self.session.execute(\n            select(\n                AIAnomaly.severity,\n                func.count(AIAnomaly.id).label(\'count\')\n            )\n            .where(\n                and_(\n                    AIAnomaly.company_id == company_id,\n                    AIAnomaly.detected_at >= cutoff,\n                    AIAnomaly.is_deleted == False\n                )\n            )\n            .group_by(AIAnomaly.severity)\n        )\n        \n        # Count by type\n        type_result = await self.session.execute(\n            select(\n                AIAnomaly.anomaly_type,\n                func.count(AIAnomaly.id).label(\'count\')\n            )\n            .where(\n                and_(\n                    AIAnomaly.company_id == company_id,\n                    AIAnomaly.detected_at >= cutoff,\n                    AIAnomaly.is_deleted == False\n                )\n            )\n            .group_by(AIAnomaly.anomaly_type)\n        )\n        \n        # Resolution rate\n        total_result = await self.session.execute(\n            select(func.count(AIAnomaly.id))\n            .where(\n                and_(\n                    AIAnomaly.company_id == company_id,\n                    AIAnomaly.detected_at >= cutoff,\n                    AIAnomaly.is_deleted == False\n                )\n            )\n        )\n        total_count = total_result.scalar() or 0\n        \n        resolved_result = await self.session.execute(\n            select(func.count(AIAnomaly.id))\n            .where(\n                and_(\n                    AIAnomaly.company_id == company_id,\n                    AIAnomaly.detected_at >= cutoff,\n                    AIAnomaly.is_resolved == True,\n                    AIAnomaly.is_deleted == False\n                )\n            )\n        )\n        resolved_count = resolved_result.scalar() or 0\n        \n        return {\n            \'period_days\': days,\n            \'total_anomalies\': total_count,\n            \'resolved\': resolved_count,\n            \'resolution_rate\': float(resolved_count / total_count) if total_count > 0 else 0,\n            \'by_severity\': {row[0].value: row[1] for row in severity_result.all()},\n            \'by_type\': {row[0]: row[1] for row in type_result.all()}\n        }\n\n\nclass AIRecommendationRepository(CompanyScopedRepository[AIRecommendation]):\n    """Repository for AI recommendation operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AIRecommendation)\n    \n    async def get_by_status(\n        self,\n        company_id: UUID,\n        status: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIRecommendation]:\n        """Get recommendations by status."""\n        result = await self.session.execute(\n            select(AIRecommendation)\n            .where(\n                and_(\n                    AIRecommendation.company_id == company_id,\n                    AIRecommendation.status == status,\n                    AIRecommendation.is_deleted == False\n                )\n            )\n            .order_by(desc(AIRecommendation.priority), desc(AIRecommendation.created_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_category(\n        self,\n        company_id: UUID,\n        category: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIRecommendation]:\n        """Get recommendations by category."""\n        result = await self.session.execute(\n            select(AIRecommendation)\n            .where(\n                and_(\n                    AIRecommendation.company_id == company_id,\n                    AIRecommendation.category == category,\n                    AIRecommendation.is_deleted == False\n                )\n            )\n            .order_by(desc(AIRecommendation.priority))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_high_priority(\n        self,\n        company_id: UUID,\n        min_priority: int = 7,\n        limit: int = 10\n    ) -> List[AIRecommendation]:\n        """Get high priority recommendations."""\n        result = await self.session.execute(\n            select(AIRecommendation)\n            .where(\n                and_(\n                    AIRecommendation.company_id == company_id,\n                    AIRecommendation.priority >= min_priority,\n                    AIRecommendation.status == \'pending\',\n                    AIRecommendation.is_deleted == False\n                )\n            )\n            .order_by(desc(AIRecommendation.priority))\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def create_recommendation(\n        self,\n        company_id: UUID,\n        title: str,\n        description: str,\n        category: str,\n        priority: int,\n        recommended_action: str,\n        expected_impact: Optional[str] = None,\n        supporting_data: Optional[Dict[str, Any]] = None,\n        entity_type: Optional[str] = None,\n        entity_id: Optional[UUID] = None,\n        created_by: Optional[UUID] = None\n    ) -> AIRecommendation:\n        """Create a new recommendation."""\n        recommendation = AIRecommendation(\n            company_id=company_id,\n            title=title,\n            description=description,\n            category=category,\n            priority=priority,\n            recommended_action=recommended_action,\n            expected_impact=expected_impact,\n            supporting_data=supporting_data,\n            entity_type=entity_type,\n            entity_id=entity_id,\n            status=\'pending\',\n            created_by=created_by\n        )\n        self.session.add(recommendation)\n        await self.session.flush()\n        return recommendation\n    \n    async def accept_recommendation(\n        self,\n        recommendation_id: UUID,\n        accepted_by: UUID,\n        notes: Optional[str] = None\n    ) -> Optional[AIRecommendation]:\n        """Accept a recommendation."""\n        recommendation = await self.get(recommendation_id)\n        if not recommendation:\n            return None\n        \n        recommendation.status = \'accepted\'\n        recommendation.accepted_at = datetime.utcnow()\n        recommendation.accepted_by = accepted_by\n        recommendation.acceptance_notes = notes\n        await self.session.flush()\n        return recommendation\n    \n    async def reject_recommendation(\n        self,\n        recommendation_id: UUID,\n        rejected_by: UUID,\n        reason: str\n    ) -> Optional[AIRecommendation]:\n        """Reject a recommendation."""\n        recommendation = await self.get(recommendation_id)\n        if not recommendation:\n            return None\n        \n        recommendation.status = \'rejected\'\n        recommendation.rejected_at = datetime.utcnow()\n        recommendation.rejected_by = rejected_by\n        recommendation.rejection_reason = reason\n        await self.session.flush()\n        return recommendation\n    \n    async def implement_recommendation(\n        self,\n        recommendation_id: UUID,\n        implemented_by: UUID,\n        outcome: Optional[str] = None\n    ) -> Optional[AIRecommendation]:\n        """Mark recommendation as implemented."""\n        recommendation = await self.get(recommendation_id)\n        if not recommendation:\n            return None\n        \n        recommendation.status = \'implemented\'\n        recommendation.implemented_at = datetime.utcnow()\n        recommendation.implemented_by = implemented_by\n        recommendation.implementation_outcome = outcome\n        await self.session.flush()\n        return recommendation\n    \n    async def get_recommendation_metrics(\n        self,\n        company_id: UUID,\n        days: int = 30\n    ) -> Dict[str, Any]:\n        """Get recommendation metrics."""\n        cutoff = datetime.utcnow() - timedelta(days=days)\n        \n        result = await self.session.execute(\n            select(\n                AIRecommendation.status,\n                func.count(AIRecommendation.id).label(\'count\')\n            )\n            .where(\n                and_(\n                    AIRecommendation.company_id == company_id,\n                    AIRecommendation.created_at >= cutoff,\n                    AIRecommendation.is_deleted == False\n                )\n            )\n            .group_by(AIRecommendation.status)\n        )\n        \n        return {\n            \'period_days\': days,\n            \'by_status\': {row[0]: row[1] for row in result.all()}\n        }\n\n\nclass AIFeedbackRepository(BaseRepository[AIFeedback]):\n    """Repository for AI feedback operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AIFeedback)\n    \n    async def get_by_prediction(\n        self,\n        prediction_id: UUID\n    ) -> List[AIFeedback]:\n        """Get feedback for a prediction."""\n        result = await self.session.execute(\n            select(AIFeedback)\n            .where(AIFeedback.prediction_id == prediction_id)\n            .order_by(desc(AIFeedback.created_at))\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_recommendation(\n        self,\n        recommendation_id: UUID\n    ) -> List[AIFeedback]:\n        """Get feedback for a recommendation."""\n        result = await self.session.execute(\n            select(AIFeedback)\n            .where(AIFeedback.recommendation_id == recommendation_id)\n            .order_by(desc(AIFeedback.created_at))\n        )\n        return list(result.scalars().all())\n    \n    async def create_feedback(\n        self,\n        feedback_type: str,\n        rating: int,\n        comments: Optional[str] = None,\n        prediction_id: Optional[UUID] = None,\n        recommendation_id: Optional[UUID] = None,\n        user_id: Optional[UUID] = None\n    ) -> AIFeedback:\n        """Create feedback for an AI output."""\n        feedback = AIFeedback(\n            prediction_id=prediction_id,\n            recommendation_id=recommendation_id,\n            feedback_type=feedback_type,\n            rating=rating,\n            comments=comments,\n            user_id=user_id\n        )\n        self.session.add(feedback)\n        await self.session.flush()\n        return feedback\n    \n    async def get_average_rating(\n        self,\n        feedback_type: Optional[str] = None,\n        days: int = 30\n    ) -> Dict[str, Any]:\n        """Get average feedback rating."""\n        cutoff = datetime.utcnow() - timedelta(days=days)\n        \n        conditions = [AIFeedback.created_at >= cutoff]\n        if feedback_type:\n            conditions.append(AIFeedback.feedback_type == feedback_type)\n        \n        result = await self.session.execute(\n            select(\n                func.avg(AIFeedback.rating).label(\'avg_rating\'),\n                func.count(AIFeedback.id).label(\'count\')\n            )\n            .where(and_(*conditions))\n        )\n        \n        row = result.one()\n        return {\n            \'average_rating\': float(row[0]) if row[0] else None,\n            \'total_feedback\': row[1],\n            \'period_days\': days\n        }\n\n\nclass AIConversationRepository(CompanyScopedRepository[AIConversation]):\n    """Repository for AI conversation operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AIConversation)\n    \n    async def get_by_user(\n        self,\n        user_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIConversation]:\n        """Get conversations for a user."""\n        result = await self.session.execute(\n            select(AIConversation)\n            .where(\n                and_(\n                    AIConversation.user_id == user_id,\n                    AIConversation.is_deleted == False\n                )\n            )\n            .order_by(desc(AIConversation.updated_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_with_messages(\n        self,\n        conversation_id: UUID\n    ) -> Optional[AIConversation]:\n        """Get conversation with all messages."""\n        result = await self.session.execute(\n            select(AIConversation)\n            .options(selectinload(AIConversation.messages))\n            .where(AIConversation.id == conversation_id)\n        )\n        return result.scalar_one_or_none()\n    \n    async def create_conversation(\n        self,\n        company_id: UUID,\n        user_id: UUID,\n        title: Optional[str] = None,\n        context: Optional[Dict[str, Any]] = None\n    ) -> AIConversation:\n        """Create a new conversation."""\n        conversation = AIConversation(\n            company_id=company_id,\n            user_id=user_id,\n            title=title,\n            context=context,\n            is_active=True\n        )\n        self.session.add(conversation)\n        await self.session.flush()\n        return conversation\n    \n    async def add_message(\n        self,\n        conversation_id: UUID,\n        role: str,\n        content: str,\n        tokens_used: Optional[int] = None\n    ) -> \'AIConversationMessage\':\n        """Add a message to the conversation."""\n        message = AIConversationMessage(\n            conversation_id=conversation_id,\n            role=role,\n            content=content,\n            tokens_used=tokens_used\n        )\n        self.session.add(message)\n        \n        # Update conversation timestamp\n        conversation = await self.get(conversation_id)\n        if conversation:\n            conversation.updated_at = datetime.utcnow()\n            conversation.message_count = (conversation.message_count or 0) + 1\n        \n        await self.session.flush()\n        return message\n    \n    async def end_conversation(\n        self,\n        conversation_id: UUID\n    ) -> Optional[AIConversation]:\n        """End a conversation."""\n        conversation = await self.get(conversation_id)\n        if not conversation:\n            return None\n        \n        conversation.is_active = False\n        conversation.ended_at = datetime.utcnow()\n        await self.session.flush()\n        return conversation\n\n\nclass AIConversationMessageRepository(BaseRepository[AIConversationMessage]):\n    """Repository for AI conversation message operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AIConversationMessage)\n    \n    async def get_by_conversation(\n        self,\n        conversation_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIConversationMessage]:\n        """Get messages for a conversation."""\n        result = await self.session.execute(\n            select(AIConversationMessage)\n            .where(AIConversationMessage.conversation_id == conversation_id)\n            .order_by(AIConversationMessage.created_at)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_recent_messages(\n        self,\n        conversation_id: UUID,\n        limit: int = 10\n    ) -> List[AIConversationMessage]:\n        """Get most recent messages for context."""\n        result = await self.session.execute(\n            select(AIConversationMessage)\n            .where(AIConversationMessage.conversation_id == conversation_id)\n            .order_by(desc(AIConversationMessage.created_at))\n            .limit(limit)\n        )\n        messages = list(result.scalars().all())\n        return list(reversed(messages))\n\n\nclass AITrainingJobRepository(CompanyScopedRepository[AITrainingJob]):\n    """Repository for AI training job operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AITrainingJob)\n    \n    async def get_by_model(\n        self,\n        model_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AITrainingJob]:\n        """Get training jobs for a model."""\n        result = await self.session.execute(\n            select(AITrainingJob)\n            .where(AITrainingJob.model_id == model_id)\n            .order_by(desc(AITrainingJob.created_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_by_status(\n        self,\n        company_id: UUID,\n        status: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AITrainingJob]:\n        """Get training jobs by status."""\n        result = await self.session.execute(\n            select(AITrainingJob)\n            .where(\n                and_(\n                    AITrainingJob.company_id == company_id,\n                    AITrainingJob.status == status,\n                    AITrainingJob.is_deleted == False\n                )\n            )\n            .order_by(AITrainingJob.created_at)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_running_jobs(\n        self,\n        company_id: UUID\n    ) -> List[AITrainingJob]:\n        """Get currently running training jobs."""\n        return await self.get_by_status(company_id, \'running\')\n    \n    async def create_training_job(\n        self,\n        company_id: UUID,\n        model_id: UUID,\n        training_config: Dict[str, Any],\n        dataset_id: Optional[UUID] = None,\n        created_by: Optional[UUID] = None\n    ) -> AITrainingJob:\n        """Create a new training job."""\n        job = AITrainingJob(\n            company_id=company_id,\n            model_id=model_id,\n            dataset_id=dataset_id,\n            training_config=training_config,\n            status=\'pending\',\n            created_by=created_by\n        )\n        self.session.add(job)\n        await self.session.flush()\n        return job\n    \n    async def start_training(\n        self,\n        job_id: UUID\n    ) -> Optional[AITrainingJob]:\n        """Mark training job as started."""\n        job = await self.get(job_id)\n        if not job:\n            return None\n        \n        job.status = \'running\'\n        job.started_at = datetime.utcnow()\n        await self.session.flush()\n        return job\n    \n    async def complete_training(\n        self,\n        job_id: UUID,\n        metrics: Dict[str, Any],\n        model_version_id: Optional[UUID] = None\n    ) -> Optional[AITrainingJob]:\n        """Mark training job as completed."""\n        job = await self.get(job_id)\n        if not job:\n            return None\n        \n        job.status = \'completed\'\n        job.completed_at = datetime.utcnow()\n        job.metrics = metrics\n        job.model_version_id = model_version_id\n        \n        if job.started_at:\n            job.duration_seconds = int(\n                (job.completed_at - job.started_at).total_seconds()\n            )\n        \n        await self.session.flush()\n        return job\n    \n    async def fail_training(\n        self,\n        job_id: UUID,\n        error_message: str\n    ) -> Optional[AITrainingJob]:\n        """Mark training job as failed."""\n        job = await self.get(job_id)\n        if not job:\n            return None\n        \n        job.status = \'failed\'\n        job.completed_at = datetime.utcnow()\n        job.error_message = error_message\n        await self.session.flush()\n        return job\n\n\nclass AIFeatureImportanceRepository(BaseRepository[AIFeatureImportance]):\n    """Repository for AI feature importance operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AIFeatureImportance)\n    \n    async def get_by_model(\n        self,\n        model_id: UUID\n    ) -> List[AIFeatureImportance]:\n        """Get feature importance for a model."""\n        result = await self.session.execute(\n            select(AIFeatureImportance)\n            .where(AIFeatureImportance.model_id == model_id)\n            .order_by(desc(AIFeatureImportance.importance_score))\n        )\n        return list(result.scalars().all())\n    \n    async def get_top_features(\n        self,\n        model_id: UUID,\n        limit: int = 10\n    ) -> List[AIFeatureImportance]:\n        """Get top features by importance."""\n        result = await self.session.execute(\n            select(AIFeatureImportance)\n            .where(AIFeatureImportance.model_id == model_id)\n            .order_by(desc(AIFeatureImportance.importance_score))\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def update_feature_importance(\n        self,\n        model_id: UUID,\n        feature_importances: Dict[str, float]\n    ) -> List[AIFeatureImportance]:\n        """Update feature importance scores."""\n        # Delete existing\n        await self.session.execute(\n            delete(AIFeatureImportance)\n            .where(AIFeatureImportance.model_id == model_id)\n        )\n        \n        # Create new records\n        records = []\n        for feature_name, importance_score in feature_importances.items():\n            record = AIFeatureImportance(\n                model_id=model_id,\n                feature_name=feature_name,\n                importance_score=Decimal(str(importance_score))\n            )\n            self.session.add(record)\n            records.append(record)\n        \n        await self.session.flush()\n        return records\n\n\nclass AIModelMetricRepository(BaseRepository[AIModelMetric]):\n    """Repository for AI model metrics operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AIModelMetric)\n    \n    async def get_by_model(\n        self,\n        model_id: UUID,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIModelMetric]:\n        """Get metrics for a model."""\n        result = await self.session.execute(\n            select(AIModelMetric)\n            .where(AIModelMetric.model_id == model_id)\n            .order_by(desc(AIModelMetric.recorded_at))\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_latest_metrics(\n        self,\n        model_id: UUID\n    ) -> Dict[str, float]:\n        """Get latest metric values for a model."""\n        result = await self.session.execute(\n            select(AIModelMetric)\n            .where(AIModelMetric.model_id == model_id)\n            .order_by(desc(AIModelMetric.recorded_at))\n            .limit(20)\n        )\n        \n        metrics = {}\n        for m in result.scalars().all():\n            if m.metric_name not in metrics:\n                metrics[m.metric_name] = float(m.metric_value) if m.metric_value else 0\n        \n        return metrics\n    \n    async def record_metric(\n        self,\n        model_id: UUID,\n        metric_name: str,\n        metric_value: Decimal,\n        metric_metadata: Optional[Dict[str, Any]] = None\n    ) -> AIModelMetric:\n        """Record a model metric."""\n        metric = AIModelMetric(\n            model_id=model_id,\n            metric_name=metric_name,\n            metric_value=metric_value,\n            metric_metadata=metric_metadata\n        )\n        self.session.add(metric)\n        await self.session.flush()\n        return metric\n\n\nclass AIDatasetRepository(CompanyScopedRepository[AIDataset]):\n    """Repository for AI dataset operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AIDataset)\n    \n    async def get_by_type(\n        self,\n        company_id: UUID,\n        dataset_type: str,\n        skip: int = 0,\n        limit: int = 100\n    ) -> List[AIDataset]:\n        """Get datasets by type."""\n        result = await self.session.execute(\n            select(AIDataset)\n            .where(\n                and_(\n                    AIDataset.company_id == company_id,\n                    AIDataset.dataset_type == dataset_type,\n                    AIDataset.is_deleted == False\n                )\n            )\n            .order_by(AIDataset.name)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def get_training_datasets(\n        self,\n        company_id: UUID\n    ) -> List[AIDataset]:\n        """Get datasets available for training."""\n        result = await self.session.execute(\n            select(AIDataset)\n            .where(\n                and_(\n                    AIDataset.company_id == company_id,\n                    AIDataset.is_training == True,\n                    AIDataset.is_deleted == False\n                )\n            )\n            .order_by(AIDataset.name)\n        )\n        return list(result.scalars().all())\n    \n    async def create_dataset(\n        self,\n        company_id: UUID,\n        name: str,\n        dataset_type: str,\n        schema: Dict[str, Any],\n        description: Optional[str] = None,\n        source: Optional[str] = None,\n        created_by: Optional[UUID] = None\n    ) -> AIDataset:\n        """Create a new dataset."""\n        dataset = AIDataset(\n            company_id=company_id,\n            name=name,\n            description=description,\n            dataset_type=dataset_type,\n            schema=schema,\n            source=source,\n            row_count=0,\n            created_by=created_by\n        )\n        self.session.add(dataset)\n        await self.session.flush()\n        return dataset\n    \n    async def update_row_count(\n        self,\n        dataset_id: UUID,\n        row_count: int\n    ) -> Optional[AIDataset]:\n        """Update dataset row count."""\n        dataset = await self.get(dataset_id)\n        if not dataset:\n            return None\n        \n        dataset.row_count = row_count\n        dataset.updated_at = datetime.utcnow()\n        await self.session.flush()\n        return dataset\n\n\nclass AIDatasetRowRepository(BaseRepository[AIDatasetRow]):\n    """Repository for AI dataset row operations."""\n    \n    def __init__(self, session: AsyncSession):\n        super().__init__(session, AIDatasetRow)\n    \n    async def get_by_dataset(\n        self,\n        dataset_id: UUID,\n        skip: int = 0,\n        limit: int = 1000\n    ) -> List[AIDatasetRow]:\n        """Get rows for a dataset."""\n        result = await self.session.execute(\n            select(AIDatasetRow)\n            .where(AIDatasetRow.dataset_id == dataset_id)\n            .order_by(AIDatasetRow.row_index)\n            .offset(skip)\n            .limit(limit)\n        )\n        return list(result.scalars().all())\n    \n    async def add_row(\n        self,\n        dataset_id: UUID,\n        row_index: int,\n        data: Dict[str, Any],\n        is_training: bool = True\n    ) -> AIDatasetRow:\n        """Add a row to the dataset."""\n        row = AIDatasetRow(\n            dataset_id=dataset_id,\n            row_index=row_index,\n            data=data,\n            is_training=is_training\n        )\n        self.session.add(row)\n        await self.session.flush()\n        return row\n    \n    async def add_rows_batch(\n        self,\n        dataset_id: UUID,\n        rows: List[Dict[str, Any]],\n        is_training: bool = True\n    ) -> int:\n        """Add multiple rows to the dataset."""\n        # Get current max index\n        result = await self.session.execute(\n            select(func.max(AIDatasetRow.row_index))\n            .where(AIDatasetRow.dataset_id == dataset_id)\n        )\n        max_index = result.scalar() or -1\n        \n        for i, row_data in enumerate(rows):\n            row = AIDatasetRow(\n                dataset_id=dataset_id,\n                row_index=max_index + 1 + i,\n                data=row_data,\n                is_training=is_training\n            )\n            self.session.add(row)\n        \n        await self.session.flush()\n        return len(rows)\n    \n    async def delete_by_dataset(\n        self,\n        dataset_id: UUID\n    ) -> int:\n        """Delete all rows for a dataset."""\n        result = await self.session.execute(\n            delete(AIDatasetRow)\n            .where(AIDatasetRow.dataset_id == dataset_id)\n        )\n        await self.session.flush()\n        return result.rowcount\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/repositories/ai.py\' created successfully.") </create-file> </tool_result>'}
{'role': 'assistant', 'content': 'Excellent! Now let me create the `__init__.py` for the repositories to export all repository classes.\n\n<function_calls>\n<create-file file_path="backend/app/repositories/__init__.py">\n"""\nSAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM - Repositories Package\nData access layer for all modules.\n"""\n\nfrom .base import (\n    BaseRepository,\n    CompanyScopedRepository,\n    CacheableRepository,\n    SoftDeleteRepository\n)\n\nfrom .company import (\n    CompanyRepository,\n    CompanySettingRepository,\n    CompanyIntegrationRepository\n)\n\nfrom .user import (\n    UserRepository,\n    UserRoleRepository,\n    PermissionRepository,\n    RoleRepository,\n    UserSessionRepository,\n    UserPreferenceRepository,\n    APIKeyRepository\n)\n\nfrom .account import (\n    AccountTypeRepository,\n    AccountRepository,\n    FiscalYearRepository,\n    FiscalPeriodRepository,\n    JournalEntryRepository,\n    JournalLineRepository,\n    AccountBalanceRepository,\n    CostCenterRepository,\n    BudgetRepository,\n    BudgetLineRepository,\n    BankReconciliationRepository\n)\n\nfrom .treasury import (\n    BankRepository,\n    BankAccountRepository,\n    CashPoolRepository,\n    TreasuryTransactionRepository,\n    TreasuryForecastRepository,\n    InvestmentRepository,\n    CreditFacilityRepository,\n    FXRateRepository,\n    HedgeRepository,\n    PaymentBatchRepository\n)\n\nfrom .invoice import (\n    InvoiceTemplateRepository,\n    InvoiceRepository,\n    InvoiceLineRepository,\n    InvoiceTaxRepository,\n    RecurringInvoiceRepository,\n    InvoicePaymentRepository,\n    CreditMemoRepository,\n    DunningLetterRepository\n)\n\nfrom .hr import (\n    DepartmentRepository,\n    JobPositionRepository,\n    EmployeeRepository,\n    PayrollPeriodRepository,\n    PayrollRunRepository,\n    PayslipRepository,\n    LeaveTypeRepository,\n    LeaveBalanceRepository,\n    LeaveRequestRepository,\n    AttendanceRepository,\n    PerformanceReviewRepository,\n    ExpenseReportRepository\n)\n\nfrom .tax import (\n    TaxJurisdictionRepository,\n    TaxTypeRepository,\n    TaxRateRepository,\n    TaxRegistrationRepository,\n    TaxTransactionRepository,\n    TaxFilingRepository,\n    TaxPaymentRepository,\n    TaxExemptionRepository,\n    NexusDeterminationRepository,\n    TaxAuditRepository\n)\n\nfrom .workflow import (\n    WorkflowDefinitionRepository,\n    WorkflowNodeRepository,\n    WorkflowTransitionRepository,\n    WorkflowInstanceRepository,\n    WorkflowTaskRepository,\n    TriggerRepository,\n    ConditionRepository,\n    SchedulerRepository,\n    SchedulerRunRepository,\n    NotificationTemplateRepository,\n    NotificationRepository,\n    AutomationRuleRepository\n)\n\nfrom .legal import (\n    ContractTypeRepository,\n    ContractRepository,\n    ContractPartyRepository,\n    ContractDocumentRepository,\n    ContractSignatureRepository,\n    ContractMilestoneRepository,\n    ContractRenewalRepository,\n    LegalEntityRepository,\n    LegalMatterRepository,\n    LitigationRepository,\n    LegalHoldRepository,\n    IPAssetRepository,\n    IPRegistrationRepository,\n    ComplianceProgramRepository,\n    ComplianceAssessmentRepository\n)\n\nfrom .reports import (\n    ReportTemplateRepository,\n    ReportRepository,\n    ReportExecutionRepository,\n    DashboardRepository,\n    DashboardWidgetRepository,\n    DataSourceRepository,\n    DataQueryRepository,\n    ScheduledReportRepository,\n    ReportDistributionRepository\n)\n\nfrom .consolidation import (\n    ConsolidationGroupRepository,\n    ConsolidationScenarioRepository,\n    ConsolidationEntryRepository,\n    IntercompanyTransactionRepository,\n    IntercompanyBalanceRepository,\n    EliminationRuleRepository,\n    ConsolidationJournalRepository,\n    ConsolidationReportRepository,\n    CurrencyRateRepository\n)\n\nfrom .asset import (\n    AssetCategoryRepository,\n    AssetRepository,\n    AssetDepreciationRepository,\n    AssetMaintenanceRepository,\n    AssetTransferRepository,\n    AssetDisposalRepository,\n    AssetInsuranceRepository,\n    AssetTagRepository,\n    AssetLocationRepository\n)\n\nfrom .ai import (\n    AIModelRepository,\n    AIModelVersionRepository,\n    AIPredictionRepository,\n    AIAnomalyRepository,\n    AIRecommendationRepository,\n    AIFeedbackRepository,\n    AIConversationRepository,\n    AIConversationMessageRepository,\n    AITrainingJobRepository,\n    AIFeatureImportanceRepository,\n    AIModelMetricRepository,\n    AIDatasetRepository,\n    AIDatasetRowRepository\n)\n\n__all__ = [\n    # Base\n    \'BaseRepository\',\n    \'CompanyScopedRepository\',\n    \'CacheableRepository\',\n    \'SoftDeleteRepository\',\n    \n    # Company\n    \'CompanyRepository\',\n    \'CompanySettingRepository\',\n    \'CompanyIntegrationRepository\',\n    \n    # User\n    \'UserRepository\',\n    \'UserRoleRepository\',\n    \'PermissionRepository\',\n    \'RoleRepository\',\n    \'UserSessionRepository\',\n    \'UserPreferenceRepository\',\n    \'APIKeyRepository\',\n    \n    # Accounting\n    \'AccountTypeRepository\',\n    \'AccountRepository\',\n    \'FiscalYearRepository\',\n    \'FiscalPeriodRepository\',\n    \'JournalEntryRepository\',\n    \'JournalLineRepository\',\n    \'AccountBalanceRepository\',\n    \'CostCenterRepository\',\n    \'BudgetRepository\',\n    \'BudgetLineRepository\',\n    \'BankReconciliationRepository\',\n    \n    # Treasury\n    \'BankRepository\',\n    \'BankAccountRepository\',\n    \'CashPoolRepository\',\n    \'TreasuryTransactionRepository\',\n    \'TreasuryForecastRepository\',\n    \'InvestmentRepository\',\n    \'CreditFacilityRepository\',\n    \'FXRateRepository\',\n    \'HedgeRepository\',\n    \'PaymentBatchRepository\',\n    \n    # Invoice\n    \'InvoiceTemplateRepository\',\n    \'InvoiceRepository\',\n    \'InvoiceLineRepository\',\n    \'InvoiceTaxRepository\',\n    \'RecurringInvoiceRepository\',\n    \'InvoicePaymentRepository\',\n    \'CreditMemoRepository\',\n    \'DunningLetterRepository\',\n    \n    # HR\n    \'DepartmentRepository\',\n    \'JobPositionRepository\',\n    \'EmployeeRepository\',\n    \'PayrollPeriodRepository\',\n    \'PayrollRunRepository\',\n    \'PayslipRepository\',\n    \'LeaveTypeRepository\',\n    \'LeaveBalanceRepository\',\n    \'LeaveRequestRepository\',\n    \'AttendanceRepository\',\n    \'PerformanceReviewRepository\',\n    \'ExpenseReportRepository\',\n    \n    # Tax\n    \'TaxJurisdictionRepository\',\n    \'TaxTypeRepository\',\n    \'TaxRateRepository\',\n    \'TaxRegistrationRepository\',\n    \'TaxTransactionRepository\',\n    \'TaxFilingRepository\',\n    \'TaxPaymentRepository\',\n    \'TaxExemptionRepository\',\n    \'NexusDeterminationRepository\',\n    \'TaxAuditRepository\',\n    \n    # Workflow\n    \'WorkflowDefinitionRepository\',\n    \'WorkflowNodeRepository\',\n    \'WorkflowTransitionRepository\',\n    \'WorkflowInstanceRepository\',\n    \'WorkflowTaskRepository\',\n    \'TriggerRepository\',\n    \'ConditionRepository\',\n    \'SchedulerRepository\',\n    \'SchedulerRunRepository\',\n    \'NotificationTemplateRepository\',\n    \'NotificationRepository\',\n    \'AutomationRuleRepository\',\n    \n    # Legal\n    \'ContractTypeRepository\',\n    \'ContractRepository\',\n    \'ContractPartyRepository\',\n    \'ContractDocumentRepository\',\n    \'ContractSignatureRepository\',\n    \'ContractMilestoneRepository\',\n    \'ContractRenewalRepository\',\n    \'LegalEntityRepository\',\n    \'LegalMatterRepository\',\n    \'LitigationRepository\',\n    \'LegalHoldRepository\',\n    \'IPAssetRepository\',\n    \'IPRegistrationRepository\',\n    \'ComplianceProgramRepository\',\n    \'ComplianceAssessmentRepository\',\n    \n    # Reports\n    \'ReportTemplateRepository\',\n    \'ReportRepository\',\n    \'ReportExecutionRepository\',\n    \'DashboardRepository\',\n    \'DashboardWidgetRepository\',\n    \'DataSourceRepository\',\n    \'DataQueryRepository\',\n    \'ScheduledReportRepository\',\n    \'ReportDistributionRepository\',\n    \n    # Consolidation\n    \'ConsolidationGroupRepository\',\n    \'ConsolidationScenarioRepository\',\n    \'ConsolidationEntryRepository\',\n    \'IntercompanyTransactionRepository\',\n    \'IntercompanyBalanceRepository\',\n    \'EliminationRuleRepository\',\n    \'ConsolidationJournalRepository\',\n    \'ConsolidationReportRepository\',\n    \'CurrencyRateRepository\',\n    \n    # Asset\n    \'AssetCategoryRepository\',\n    \'AssetRepository\',\n    \'AssetDepreciationRepository\',\n    \'AssetMaintenanceRepository\',\n    \'AssetTransferRepository\',\n    \'AssetDisposalRepository\',\n    \'AssetInsuranceRepository\',\n    \'AssetTagRepository\',\n    \'AssetLocationRepository\',\n    \n    # AI\n    \'AIModelRepository\',\n    \'AIModelVersionRepository\',\n    \'AIPredictionRepository\',\n    \'AIAnomalyRepository\',\n    \'AIRecommendationRepository\',\n    \'AIFeedbackRepository\',\n    \'AIConversationRepository\',\n    \'AIConversationMessageRepository\',\n    \'AITrainingJobRepository\',\n    \'AIFeatureImportanceRepository\',\n    \'AIModelMetricRepository\',\n    \'AIDatasetRepository\',\n    \'AIDatasetRowRepository\',\n]\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/repositories/__init__.py\' created successfully.") </create-file> </tool_result>'}
{'role': 'assistant', 'content': 'Now let me update the todo.md to mark Phase 1.6 as complete and check the overall progress.\n\n<function_calls>\n<str-replace file_path="todo.md">\n<old_str>## 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- [ ] Reports repository\n- [ ] Consolidation repository\n- [ ] Asset repository\n- [ ] AI repository\n- [ ] Repositories __init__.py</old_str>\n<new_str>## 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</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': 'Phase 1.6 is complete! Now let me verify the repositories directory structure and then move on to Phase 2 - the Services layer. Let me first check the current structure and then start creating the services.\n\n<function_calls>\n<execute-command>\nls -la backend/app/repositories/ && echo "---" && wc -l backend/app/repositories/*.py\n</execute-command>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <execute-command> ToolResult(success=True, output=\'{\\n  "output": "total 572\\\\ndrwxr-xr-x  2 root root  4096 Apr  4 01:04 .\\\\ndrwxr-xr-x 13 root root  4096 Apr  1 20:24 ..\\\\n-rw-r--r--  1 root root  8422 Apr  4 01:04 __init__.py\\\\n-rw-r--r--  1 root root 47785 Apr  4 00:06 account.py\\\\n-rw-r--r--  1 root root 51223 Apr  4 01:03 ai.py\\\\n-rw-r--r--  1 root root 43115 Apr  4 00:56 asset.py\\\\n-rw-r--r--  1 root root 22634 Apr  3 23:54 base.py\\\\n-rw-r--r--  1 root root 17761 Apr  3 23:56 company.py\\\\n-rw-r--r--  1 root root 45153 Apr  4 00:50 consolidation.py\\\\n-rw-r--r--  1 root root 47284 Apr  4 00:18 hr.py\\\\n-rw-r--r--  1 root root 35706 Apr  4 00:13 invoice.py\\\\n-rw-r--r--  1 root root 41494 Apr  4 00:37 legal.py\\\\n-rw-r--r--  1 root root 40969 Apr  4 00:44 reports.py\\\\n-rw-r--r--  1 root root 35443 Apr  4 00:22 tax.py\\\\n-rw-r--r--  1 root root 43722 Apr  4 00:10 treasury.py\\\\n-rw-r--r--  1 root root 24403 Apr  3 23:59 user.py\\\\n-rw-r--r--  1 root root 39254 Apr  4 00:26 workflow.py\\\\n---\\\\n   339 backend/app/repositories/__init__.py\\\\n  1189 backend/app/repositories/account.py\\\\n  1610 backend/app/repositories/ai.py\\\\n  1327 backend/app/repositories/asset.py\\\\n   728 backend/app/repositories/base.py\\\\n   484 backend/app/repositories/company.py\\\\n  1331 backend/app/repositories/consolidation.py\\\\n  1309 backend/app/repositories/hr.py\\\\n   971 backend/app/repositories/invoice.py\\\\n  1105 backend/app/repositories/legal.py\\\\n  1251 backend/app/repositories/reports.py\\\\n   950 backend/app/repositories/tax.py\\\\n  1146 backend/app/repositories/treasury.py\\\\n   737 backend/app/repositories/user.py\\\\n  1038 backend/app/repositories/workflow.py\\\\n 15515 total\\\\n",\\n  "exit_code": 0,\\n  "cwd": "/workspace"\\n}\') </execute-command> </tool_result>'}
{'role': 'assistant', 'content': 'Excellent! All repository files are created with over 15,000 lines of code. Now let me start Phase 2 - the Services layer. Let me check if the services directory exists and create the base service infrastructure.\n\n<function_calls>\n<execute-command>\nmkdir -p backend/app/services && ls -la backend/app/services/\n</execute-command>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <execute-command> ToolResult(success=True, output=\'{\\n  "output": "total 8\\\\ndrwxr-xr-x  2 root root 4096 Apr  1 20:24 .\\\\ndrwxr-xr-x 13 root root 4096 Apr  1 20:24 ..\\\\n",\\n  "exit_code": 0,\\n  "cwd": "/workspace"\\n}\') </execute-command> </tool_result>'}
{'role': 'assistant', 'content': 'Now let me create the base service and then continue with all the domain services. I\'ll start with the base service infrastructure.\n\n<function_calls>\n<create-file file_path="backend/app/services/__init__.py">\n"""\nSAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM - Services Package\nBusiness logic layer for all modules.\n"""\n\nfrom .base import (\n    BaseService,\n    CompanyScopedService,\n    Result,\n    ServiceResult,\n    PaginationResult\n)\n\nfrom .auth import AuthService\nfrom .company import CompanyService\nfrom .user import UserService\nfrom .account import AccountService\nfrom .treasury import TreasuryService\nfrom .invoice import InvoiceService\nfrom .hr import HRService\nfrom .tax import TaxService\nfrom .workflow import WorkflowService\nfrom .legal import LegalService\nfrom .reports import ReportService\nfrom .consolidation import ConsolidationService\nfrom .asset import AssetService\nfrom .ai import AIService\n\n__all__ = [\n    # Base\n    \'BaseService\',\n    \'CompanyScopedService\',\n    \'Result\',\n    \'ServiceResult\',\n    \'PaginationResult\',\n    \n    # Services\n    \'AuthService\',\n    \'CompanyService\',\n    \'UserService\',\n    \'AccountService\',\n    \'TreasuryService\',\n    \'InvoiceService\',\n    \'HRService\',\n    \'TaxService\',\n    \'WorkflowService\',\n    \'LegalService\',\n    \'ReportService\',\n    \'ConsolidationService\',\n    \'AssetService\',\n    \'AIService\',\n]\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/services/__init__.py\' created successfully.") </create-file> </tool_result>'}
{'role': 'assistant', 'content': 'Now let me create the base service with common functionality:\n\n<function_calls>\n<create-file file_path="backend/app/services/base.py">\n"""\nSAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM - Base Service\nFoundation service class with common business logic patterns.\n"""\n\nfrom abc import ABC, abstractmethod\nfrom dataclasses import dataclass, field\nfrom datetime import datetime\nfrom decimal import Decimal\nfrom typing import (\n    Optional, List, Dict, Any, Generic, TypeVar, Union,\n    Callable, Awaitable, Protocol\n)\nfrom uuid import UUID\nimport asyncio\nimport logging\nfrom functools import wraps\n\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\nfrom ..repositories.base import BaseRepository\nfrom ..core.exceptions import (\n    NotFoundError,\n    ValidationError,\n    BusinessLogicError,\n    PermissionDeniedError,\n    ConflictError\n)\nfrom ..core.events import EventPublisher\nfrom ..core.cache import CacheManager\n\n\nlogger = logging.getLogger(__name__)\n\n\n@dataclass\nclass Result(Generic[TypeVar(\'T\')]):\n    """\n    Generic result wrapper for service operations.\n    Provides success/failure status, data, errors, and metadata.\n    """\n    success: bool\n    data: Optional[Any] = None\n    errors: List[str] = field(default_factory=list)\n    warnings: List[str] = field(default_factory=list)\n    metadata: Dict[str, Any] = field(default_factory=dict)\n    \n    @classmethod\n    def ok(cls, data: Any = None, metadata: Optional[Dict[str, Any]] = None, warnings: Optional[List[str]] = None) -> \'Result\':\n        """Create a successful result."""\n        return cls(\n            success=True,\n            data=data,\n            warnings=warnings or [],\n            metadata=metadata or {}\n        )\n    \n    @classmethod\n    def fail(cls, errors: Union[str, List[str]], metadata: Optional[Dict[str, Any]] = None) -> \'Result\':\n        """Create a failed result."""\n        error_list = [errors] if isinstance(errors, str) else errors\n        return cls(\n            success=False,\n            errors=error_list,\n            metadata=metadata or {}\n        )\n    \n    @classmethod\n    def partial(cls, data: Any, warnings: List[str], metadata: Optional[Dict[str, Any]] = None) -> \'Result\':\n        """Create a partial success result with warnings."""\n        return cls(\n            success=True,\n            data=data,\n            warnings=warnings,\n            metadata=metadata or {}\n        )\n    \n    def add_error(self, error: str) -> \'Result\':\n        """Add an error to the result."""\n        self.errors.append(error)\n        self.success = False\n        return self\n    \n    def add_warning(self, warning: str) -> \'Result\':\n        """Add a warning to the result."""\n        self.warnings.append(warning)\n        return self\n    \n    def merge(self, other: \'Result\') -> \'Result\':\n        """Merge another result into this one."""\n        self.errors.extend(other.errors)\n        self.warnings.extend(other.warnings)\n        self.metadata.update(other.metadata)\n        if not other.success:\n            self.success = False\n        return self\n\n\n# Type alias for service results\nServiceResult = Result\n\n\n@dataclass\nclass PaginationResult(Generic[TypeVar(\'T\')]):\n    """\n    Result wrapper for paginated data.\n    """\n    items: List[Any]\n    total: int\n    page: int\n    page_size: int\n    total_pages: int\n    has_next: bool\n    has_prev: bool\n    \n    @classmethod\n    def create(\n        cls,\n        items: List[Any],\n        total: int,\n        page: int,\n        page_size: int\n    ) -> \'PaginationResult\':\n        """Create a pagination result."""\n        total_pages = (total + page_size - 1) // page_size if page_size > 0 else 0\n        return cls(\n            items=items,\n            total=total,\n            page=page,\n            page_size=page_size,\n            total_pages=total_pages,\n            has_next=page < total_pages,\n            has_prev=page > 1\n        )\n\n\nclass ServiceContext:\n    """\n    Context object passed through service operations.\n    Contains user info, company info, and request metadata.\n    """\n    def __init__(\n        self,\n        user_id: Optional[UUID] = None,\n        company_id: Optional[UUID] = None,\n        roles: Optional[List[str]] = None,\n        permissions: Optional[List[str]] = None,\n        request_id: Optional[str] = None,\n        ip_address: Optional[str] = None,\n        user_agent: Optional[str] = None,\n        metadata: Optional[Dict[str, Any]] = None\n    ):\n        self.user_id = user_id\n        self.company_id = company_id\n        self.roles = roles or []\n        self.permissions = permissions or []\n        self.request_id = request_id\n        self.ip_address = ip_address\n        self.user_agent = user_agent\n        self.metadata = metadata or {}\n        self.created_at = datetime.utcnow()\n    \n    def has_permission(self, permission: str) -> bool:\n        """Check if context has a specific permission."""\n        return permission in self.permissions\n    \n    def has_any_permission(self, permissions: List[str]) -> bool:\n        """Check if context has any of the specified permissions."""\n        return any(p in self.permissions for p in permissions)\n    \n    def has_all_permissions(self, permissions: List[str]) -> bool:\n        """Check if context has all specified permissions."""\n        return all(p in self.permissions for p in permissions)\n    \n    def has_role(self, role: str) -> bool:\n        """Check if context has a specific role."""\n        return role in self.roles\n    \n    def is_authenticated(self) -> bool:\n        """Check if context represents an authenticated user."""\n        return self.user_id is not None\n\n\nclass BaseService(ABC):\n    """\n    Base service class providing common business logic patterns.\n    \n    Features:\n    - Repository management\n    - Event publishing\n    - Caching\n    - Logging\n    - Transaction management\n    - Validation helpers\n    """\n    \n    def __init__(\n        self,\n        session: AsyncSession,\n        cache: Optional[CacheManager] = None,\n        event_publisher: Optional[EventPublisher] = None\n    ):\n        self.session = session\n        self.cache = cache\n        self.event_publisher = event_publisher\n        self._logger = logging.getLogger(self.__class__.__name__)\n    \n    @property\n    def logger(self) -> logging.Logger:\n        """Get the logger for this service."""\n        return self._logger\n    \n    async def begin_transaction(self) -> None:\n        """Begin a database transaction."""\n        await self.session.begin()\n    \n    async def commit_transaction(self) -> None:\n        """Commit the current transaction."""\n        await self.session.commit()\n    \n    async def rollback_transaction(self) -> None:\n        """Rollback the current transaction."""\n        await self.session.rollback()\n    \n    async def flush(self) -> None:\n        """Flush changes to the database without committing."""\n        await self.session.flush()\n    \n    async def refresh(self, instance: Any) -> None:\n        """Refresh an instance from the database."""\n        await self.session.refresh(instance)\n    \n    def result(\n        self,\n        success: bool,\n        data: Any = None,\n        errors: Optional[List[str]] = None,\n        warnings: Optional[List[str]] = None,\n        metadata: Optional[Dict[str, Any]] = None\n    ) -> Result:\n        """Create a result object."""\n        if success:\n            return Result.ok(data, metadata, warnings)\n        return Result.fail(errors or [], metadata)\n    \n    async def publish_event(\n        self,\n        event_type: str,\n        data: Dict[str, Any],\n        metadata: Optional[Dict[str, Any]] = None\n    ) -> None:\n        """Publish an event to the event bus."""\n        if self.event_publisher:\n            await self.event_publisher.publish(\n                event_type=event_type,\n                data=data,\n                metadata=metadata or {}\n            )\n    \n    async def get_cached(\n        self,\n        key: str,\n        default: Any = None\n    ) -> Any:\n        """Get a value from cache."""\n        if self.cache:\n            return await self.cache.get(key, default)\n        return default\n    \n    async def set_cached(\n        self,\n        key: str,\n        value: Any,\n        ttl: Optional[int] = None\n    ) -> None:\n        """Set a value in cache."""\n        if self.cache:\n            await self.cache.set(key, value, ttl)\n    \n    async def invalidate_cache(self, pattern: str) -> None:\n        """Invalidate cache entries matching a pattern."""\n        if self.cache:\n            await self.cache.delete_pattern(pattern)\n    \n    def validate_required(\n        self,\n        data: Dict[str, Any],\n        required_fields: List[str]\n    ) -> Optional[List[str]]:\n        """Validate that required fields are present and not empty."""\n        missing = []\n        for field in required_fields:\n            value = data.get(field)\n            if value is None or (isinstance(value, str) and not value.strip()):\n                missing.append(field)\n        \n        return missing if missing else None\n    \n    def validate_positive(\n        self,\n        value: Union[int, float, Decimal],\n        field_name: str\n    ) -> Optional[str]:\n        """Validate that a value is positive."""\n        if value <= 0:\n            return f"{field_name} must be positive"\n        return None\n    \n    def validate_non_negative(\n        self,\n        value: Union[int, float, Decimal],\n        field_name: str\n    ) -> Optional[str]:\n        """Validate that a value is non-negative."""\n        if value < 0:\n            return f"{field_name} must be non-negative"\n        return None\n    \n    def validate_date_range(\n        self,\n        start_date: datetime,\n        end_date: datetime,\n        start_field: str = "start_date",\n        end_field: str = "end_date"\n    ) -> Optional[str]:\n        """Validate that start_date is before end_date."""\n        if start_date > end_date:\n            return f"{start_field} must be before {end_field}"\n        return None\n    \n    def validate_in_list(\n        self,\n        value: Any,\n        allowed: List[Any],\n        field_name: str\n    ) -> Optional[str]:\n        """Validate that a value is in the allowed list."""\n        if value not in allowed:\n            return f"{field_name} must be one of: {\', \'.join(map(str, allowed))}"\n        return None\n    \n    async def check_permission(\n        self,\n        context: ServiceContext,\n        permission: str\n    ) -> None:\n        """Check if context has permission, raise if not."""\n        if not context.has_permission(permission):\n            raise PermissionDeniedError(f"Permission \'{permission}\' required")\n    \n    async def check_any_permission(\n        self,\n        context: ServiceContext,\n        permissions: List[str]\n    ) -> None:\n        """Check if context has any of the permissions, raise if not."""\n        if not context.has_any_permission(permissions):\n            raise PermissionDeniedError(\n                f"One of the following permissions required: {\', \'.join(permissions)}"\n            )\n    \n    def log_operation(\n        self,\n        operation: str,\n        entity_type: Optional[str] = None,\n        entity_id: Optional[UUID] = None,\n        details: Optional[Dict[str, Any]] = None\n    ) -> None:\n        """Log an operation for audit purposes."""\n        log_data = {\n            \'operation\': operation,\n            \'entity_type\': entity_type,\n            \'entity_id\': str(entity_id) if entity_id else None,\n            \'details\': details or {}\n        }\n        self._logger.info(f"Operation: {operation}", extra=log_data)\n    \n    async def execute_with_retry(\n        self,\n        operation: Callable[[], Awaitable[Any]],\n        max_retries: int = 3,\n        delay: float = 0.1,\n        backoff: float = 2.0\n    ) -> Any:\n        """Execute an operation with retry logic."""\n        last_error = None\n        \n        for attempt in range(max_retries):\n            try:\n                return await operation()\n            except Exception as e:\n                last_error = e\n                if attempt < max_retries - 1:\n                    wait_time = delay * (backoff ** attempt)\n                    self._logger.warning(\n                        f"Operation failed, retrying in {wait_time}s: {str(e)}"\n                    )\n                    await asyncio.sleep(wait_time)\n        \n        raise last_error\n\n\nclass CompanyScopedService(BaseService):\n    """\n    Base service for company-scoped operations.\n    Ensures all operations are properly scoped to a company.\n    """\n    \n    async def validate_company_access(\n        self,\n        context: ServiceContext,\n        company_id: UUID\n    ) -> None:\n        """\n        Validate that the context has access to the specified company.\n        Raises PermissionDeniedError if not.\n        """\n        if context.company_id and context.company_id != company_id:\n            # Check if user has cross-company access permission\n            if not context.has_permission(\'cross_company_access\'):\n                raise PermissionDeniedError(\n                    "Access denied to this company"\n                )\n    \n    async def ensure_company_scope(\n        self,\n        context: ServiceContext,\n        company_id: Optional[UUID] = None\n    ) -> UUID:\n        """\n        Ensure a valid company scope for the operation.\n        Returns the company ID to use.\n        """\n        effective_company_id = company_id or context.company_id\n        \n        if not effective_company_id:\n            raise ValidationError("Company ID is required")\n        \n        await self.validate_company_access(context, effective_company_id)\n        \n        return effective_company_id\n    \n    def get_cache_key(\n        self,\n        company_id: UUID,\n        *parts: str\n    ) -> str:\n        """Generate a company-scoped cache key."""\n        return f"company:{company_id}:" + ":".join(str(p) for p in parts)\n    \n    async def get_company_cached(\n        self,\n        company_id: UUID,\n        key_suffix: str,\n        default: Any = None\n    ) -> Any:\n        """Get a company-scoped cached value."""\n        key = self.get_cache_key(company_id, key_suffix)\n        return await self.get_cached(key, default)\n    \n    async def set_company_cached(\n        self,\n        company_id: UUID,\n        key_suffix: str,\n        value: Any,\n        ttl: Optional[int] = None\n    ) -> None:\n        """Set a company-scoped cached value."""\n        key = self.get_cache_key(company_id, key_suffix)\n        await self.set_cached(key, value, ttl)\n    \n    async def invalidate_company_cache(\n        self,\n        company_id: UUID,\n        pattern_suffix: str = "*"\n    ) -> None:\n        """Invalidate company-scoped cache entries."""\n        pattern = self.get_cache_key(company_id, pattern_suffix)\n        await self.invalidate_cache(pattern)\n\n\ndef transactional(func: Callable) -> Callable:\n    """\n    Decorator to wrap a service method in a transaction.\n    Commits on success, rolls back on failure.\n    """\n    @wraps(func)\n    async def wrapper(self: BaseService, *args, **kwargs):\n        try:\n            result = await func(self, *args, **kwargs)\n            await self.commit_transaction()\n            return result\n        except Exception as e:\n            await self.rollback_transaction()\n            raise\n    \n    return wrapper\n\n\ndef cached(\n    key_pattern: str,\n    ttl: Optional[int] = None,\n    key_builder: Optional[Callable] = None\n) -> Callable:\n    """\n    Decorator to cache service method results.\n    \n    Args:\n        key_pattern: Pattern for the cache key, can include {arg} placeholders\n        ttl: Time to live in seconds\n        key_builder: Optional function to build the cache key from arguments\n    """\n    def decorator(func: Callable) -> Callable:\n        @wraps(func)\n        async def wrapper(self: BaseService, *args, **kwargs):\n            if not self.cache:\n                return await func(self, *args, **kwargs)\n            \n            # Build cache key\n            if key_builder:\n                cache_key = key_builder(*args, **kwargs)\n            else:\n                # Simple pattern replacement\n                cache_key = key_pattern\n                # This is simplified - real implementation would parse args\n            \n            # Try to get from cache\n            cached_result = await self.get_cached(cache_key)\n            if cached_result is not None:\n                return cached_result\n            \n            # Execute function\n            result = await func(self, *args, **kwargs)\n            \n            # Cache result\n            await self.set_cached(cache_key, result, ttl)\n            \n            return result\n        \n        return wrapper\n    \n    return decorator\n\n\ndef emit_event(event_type: str) -> Callable:\n    """\n    Decorator to emit an event after successful service method execution.\n    """\n    def decorator(func: Callable) -> Callable:\n        @wraps(func)\n        async def wrapper(self: BaseService, *args, **kwargs):\n            result = await func(self, *args, **kwargs)\n            \n            # Emit event on success\n            if isinstance(result, Result) and result.success:\n                await self.publish_event(\n                    event_type=event_type,\n                    data={\'result\': result.data}\n                )\n            elif not isinstance(result, Result):\n                await self.publish_event(\n                    event_type=event_type,\n                    data={\'result\': result}\n                )\n            \n            return result\n        \n        return wrapper\n    \n    return decorator\n\n\ndef requires_permission(permission: str) -> Callable:\n    """\n    Decorator to check permission before executing service method.\n    """\n    def decorator(func: Callable) -> Callable:\n        @wraps(func)\n        async def wrapper(self: BaseService, context: ServiceContext, *args, **kwargs):\n            await self.check_permission(context, permission)\n            return await func(self, context, *args, **kwargs)\n        \n        return wrapper\n    \n    return decorator\n\n\ndef requires_any_permission(permissions: List[str]) -> Callable:\n    """\n    Decorator to check for any of the specified permissions.\n    """\n    def decorator(func: Callable) -> Callable:\n        @wraps(func)\n        async def wrapper(self: BaseService, context: ServiceContext, *args, **kwargs):\n            await self.check_any_permission(context, permissions)\n            return await func(self, context, *args, **kwargs)\n        \n        return wrapper\n    \n    return decorator\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/services/base.py\' created successfully.") </create-file> </tool_result>'}
{'role': 'assistant', 'content': 'Now let me create the Auth service for authentication and authorization:\n\n<function_calls>\n<create-file file_path="backend/app/services/auth.py">\n"""\nSAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM - Authentication Service\nHandles user authentication, authorization, token management, and security.\n"""\n\nfrom datetime import datetime, timedelta\nfrom typing import Optional, List, Dict, Any\nfrom uuid import UUID\nimport secrets\nimport hashlib\nimport logging\n\nfrom sqlalchemy import select, and_, or_, func\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\nfrom .base import (\n    BaseService, ServiceContext, ServiceResult, Result\n)\nfrom ..repositories.user import (\n    UserRepository, UserRoleRepository, PermissionRepository,\n    RoleRepository, UserSessionRepository, APIKeyRepository\n)\nfrom ..repositories.company import CompanyRepository\nfrom ..models.user import User, UserRole, Role, Permission, UserSession, APIKey\nfrom ..models.company import Company\nfrom ..core.security import (\n    verify_password, hash_password, create_access_token,\n    create_refresh_token, decode_token, generate_mfa_secret\n)\nfrom ..core.exceptions import (\n    AuthenticationError, ValidationError, NotFoundError,\n    BusinessLogicError, PermissionDeniedError\n)\nfrom ..core.events import EventPublisher\nfrom ..core.cache import CacheManager\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass AuthService(BaseService):\n    """\n    Authentication and Authorization Service.\n    \n    Features:\n    - User authentication (email/username)\n    - Password management\n    - JWT token generation and validation\n    - Multi-factor authentication (MFA)\n    - Session management\n    - API key management\n    - Role and permission checking\n    """\n    \n    def __init__(\n        self,\n        session: AsyncSession,\n        user_repo: Optional[UserRepository] = None,\n        user_role_repo: Optional[UserRoleRepository] = None,\n        role_repo: Optional[RoleRepository] = None,\n        permission_repo: Optional[PermissionRepository] = None,\n        session_repo: Optional[UserSessionRepository] = None,\n        api_key_repo: Optional[APIKeyRepository] = None,\n        company_repo: Optional[CompanyRepository] = None,\n        cache: Optional[CacheManager] = None,\n        event_publisher: Optional[EventPublisher] = None\n    ):\n        super().__init__(session, cache, event_publisher)\n        self.user_repo = user_repo or UserRepository(session)\n        self.user_role_repo = user_role_repo or UserRoleRepository(session)\n        self.role_repo = role_repo or RoleRepository(session)\n        self.permission_repo = permission_repo or PermissionRepository(session)\n        self.session_repo = session_repo or UserSessionRepository(session)\n        self.api_key_repo = api_key_repo or APIKeyRepository(session)\n        self.company_repo = company_repo or CompanyRepository(session)\n    \n    async def authenticate(\n        self,\n        username_or_email: str,\n        password: str,\n        company_id: Optional[UUID] = None,\n        ip_address: Optional[str] = None,\n        user_agent: Optional[str] = None,\n        device_info: Optional[Dict[str, Any]] = None\n    ) -> ServiceResult:\n        """\n        Authenticate a user with username/email and password.\n        \n        Returns a result with tokens and user info on success.\n        """\n        # Find user by email or username\n        user = await self.user_repo.get_by_email(username_or_email)\n        if not user:\n            user = await self.user_repo.get_by_username(username_or_email)\n        \n        if not user:\n            logger.warning(f"Authentication failed: user not found for {username_or_email}")\n            return Result.fail("Invalid credentials")\n        \n        # Check if user is active\n        if not user.is_active:\n            logger.warning(f"Authentication failed: user {user.id} is inactive")\n            return Result.fail("Account is disabled")\n        \n        # Check if user is locked\n        if user.locked_until and user.locked_until > datetime.utcnow():\n            logger.warning(f"Authentication failed: user {user.id} is locked")\n            return Result.fail("Account is temporarily locked. Please try again later.")\n        \n        # Verify password\n        if not verify_password(password, user.password_hash):\n            # Increment failed attempts\n            await self._handle_failed_login(user)\n            return Result.fail("Invalid credentials")\n        \n        # Check company access if specified\n        if company_id:\n            user_company_ids = [c.id for c in user.companies] if user.companies else []\n            if company_id not in user_company_ids:\n                return Result.fail("Access denied to this company")\n        \n        # Check MFA if enabled\n        if user.mfa_enabled:\n            return Result.ok({\n                \'requires_mfa\': True,\n                \'user_id\': str(user.id),\n                \'mfa_type\': user.mfa_type\n            })\n        \n        # Create session and tokens\n        session_result = await self._create_user_session(\n            user=user,\n            ip_address=ip_address,\n            user_agent=user_agent,\n            device_info=device_info\n        )\n        \n        # Reset failed attempts\n        user.failed_login_attempts = 0\n        user.locked_until = None\n        user.last_login_at = datetime.utcnow()\n        await self.session.flush()\n        \n        # Publish login event\n        await self.publish_event(\'user.login\', {\n            \'user_id\': str(user.id),\n            \'company_id\': str(company_id) if company_id else None,\n            \'ip_address\': ip_address,\n            \'user_agent\': user_agent\n        })\n        \n        logger.info(f"User {user.id} authenticated successfully")\n        \n        return Result.ok({\n            \'access_token\': session_result[\'access_token\'],\n            \'refresh_token\': session_result[\'refresh_token\'],\n            \'token_type\': \'bearer\',\n            \'expires_in\': 3600,  # 1 hour\n            \'user\': await self._get_user_profile(user)\n        })\n    \n    async def verify_mfa(\n        self,\n        user_id: UUID,\n        mfa_code: str,\n        ip_address: Optional[str] = None,\n        user_agent: Optional[str] = None,\n        device_info: Optional[Dict[str, Any]] = None\n    ) -> ServiceResult:\n        """Verify MFA code and complete authentication."""\n        user = await self.user_repo.get(user_id)\n        if not user:\n            return Result.fail("User not found")\n        \n        # Verify MFA code (simplified - would use proper TOTP in production)\n        if not await self._verify_mfa_code(user, mfa_code):\n            return Result.fail("Invalid MFA code")\n        \n        # Create session and tokens\n        session_result = await self._create_user_session(\n            user=user,\n            ip_address=ip_address,\n            user_agent=user_agent,\n            device_info=device_info\n        )\n        \n        # Update last login\n        user.last_login_at = datetime.utcnow()\n        await self.session.flush()\n        \n        return Result.ok({\n            \'access_token\': session_result[\'access_token\'],\n            \'refresh_token\': session_result[\'refresh_token\'],\n            \'token_type\': \'bearer\',\n            \'expires_in\': 3600,\n            \'user\': await self._get_user_profile(user)\n        })\n    \n    async def refresh_token(\n        self,\n        refresh_token: str,\n        ip_address: Optional[str] = None\n    ) -> ServiceResult:\n        """Refresh an access token using a refresh token."""\n        # Decode and validate refresh token\n        payload = decode_token(refresh_token, token_type=\'refresh\')\n        if not payload:\n            return Result.fail("Invalid or expired refresh token")\n        \n        user_id = payload.get(\'sub\')\n        session_id = payload.get(\'sid\')\n        \n        # Verify session exists and is valid\n        user_session = await self.session_repo.get(UUID(session_id)) if session_id else None\n        if not user_session or user_session.is_revoked:\n            return Result.fail("Session expired. Please login again.")\n        \n        # Get user\n        user = await self.user_repo.get(UUID(user_id))\n        if not user or not user.is_active:\n            return Result.fail("User not found or inactive")\n        \n        # Generate new tokens\n        access_token = create_access_token(\n            data={\n                \'sub\': str(user.id),\n                \'email\': user.email,\n                \'company_id\': str(user_session.company_id) if user_session.company_id else None\n            }\n        )\n        \n        new_refresh_token = create_refresh_token(\n            data={\n                \'sub\': str(user.id),\n                \'sid\': str(user_session.id)\n            }\n        )\n        \n        # Update session\n        user_session.last_activity = datetime.utcnow()\n        user_session.ip_address = ip_address or user_session.ip_address\n        await self.session.flush()\n        \n        return Result.ok({\n            \'access_token\': access_token,\n            \'refresh_token\': new_refresh_token,\n            \'token_type\': \'bearer\',\n            \'expires_in\': 3600\n        })\n    \n    async def logout(\n        self,\n        session_id: UUID,\n        context: ServiceContext\n    ) -> ServiceResult:\n        """Logout user by invalidating their session."""\n        user_session = await self.session_repo.get(session_id)\n        \n        if user_session and user_session.user_id == context.user_id:\n            user_session.is_revoked = True\n            user_session.revoked_at = datetime.utcnow()\n            await self.session.flush()\n            \n            await self.publish_event(\'user.logout\', {\n                \'user_id\': str(context.user_id),\n                \'session_id\': str(session_id)\n            })\n            \n            return Result.ok({\'message\': \'Logged out successfully\'})\n        \n        return Result.fail("Session not found")\n    \n    async def logout_all_sessions(\n        self,\n        context: ServiceContext\n    ) -> ServiceResult:\n        """Logout user from all sessions."""\n        sessions = await self.session_repo.get_active_sessions(context.user_id)\n        \n        count = 0\n        for session in sessions:\n            session.is_revoked = True\n            session.revoked_at = datetime.utcnow()\n            count += 1\n        \n        await self.session.flush()\n        \n        await self.publish_event(\'user.logout_all\', {\n            \'user_id\': str(context.user_id),\n            \'sessions_count\': count\n        })\n        \n        return Result.ok({\n            \'message\': f\'Logged out from {count} sessions\',\n            \'sessions_count\': count\n        })\n    \n    async def get_user_permissions(\n        self,\n        user_id: UUID,\n        company_id: Optional[UUID] = None\n    ) -> ServiceResult:\n        """Get all permissions for a user, optionally filtered by company."""\n        permissions = await self.permission_repo.get_user_permissions(\n            user_id=user_id,\n            company_id=company_id\n        )\n        \n        return Result.ok({\n            \'permissions\': permissions,\n            \'count\': len(permissions)\n        })\n    \n    async def get_user_roles(\n        self,\n        user_id: UUID,\n        company_id: Optional[UUID] = None\n    ) -> ServiceResult:\n        """Get all roles for a user."""\n        user_roles = await self.user_role_repo.get_by_user(user_id)\n        \n        if company_id:\n            user_roles = [ur for ur in user_roles if ur.company_id == company_id]\n        \n        roles = []\n        for ur in user_roles:\n            role = await self.role_repo.get(ur.role_id)\n            if role:\n                roles.append({\n                    \'id\': str(role.id),\n                    \'name\': role.name,\n                    \'code\': role.code,\n                    \'company_id\': str(ur.company_id) if ur.company_id else None\n                })\n        \n        return Result.ok({\n            \'roles\': roles,\n            \'count\': len(roles)\n        })\n    \n    async def assign_role(\n        self,\n        user_id: UUID,\n        role_id: UUID,\n        company_id: Optional[UUID] = None,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Assign a role to a user."""\n        # Verify role exists\n        role = await self.role_repo.get(role_id)\n        if not role:\n            return Result.fail("Role not found")\n        \n        # Check if already assigned\n        existing = await self.user_role_repo.get_by_user_and_role(\n            user_id=user_id,\n            role_id=role_id,\n            company_id=company_id\n        )\n        \n        if existing:\n            return Result.fail("Role already assigned to this user")\n        \n        # Create user role\n        user_role = await self.user_role_repo.create_user_role(\n            user_id=user_id,\n            role_id=role_id,\n            company_id=company_id,\n            assigned_by=context.user_id if context else None\n        )\n        \n        await self.session.flush()\n        \n        await self.publish_event(\'user.role_assigned\', {\n            \'user_id\': str(user_id),\n            \'role_id\': str(role_id),\n            \'role_name\': role.name,\n            \'company_id\': str(company_id) if company_id else None\n        })\n        \n        return Result.ok({\n            \'message\': \'Role assigned successfully\',\n            \'user_role_id\': str(user_role.id)\n        })\n    \n    async def revoke_role(\n        self,\n        user_id: UUID,\n        role_id: UUID,\n        company_id: Optional[UUID] = None,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Revoke a role from a user."""\n        user_role = await self.user_role_repo.get_by_user_and_role(\n            user_id=user_id,\n            role_id=role_id,\n            company_id=company_id\n        )\n        \n        if not user_role:\n            return Result.fail("Role assignment not found")\n        \n        await self.user_role_repo.delete(user_role.id)\n        await self.session.flush()\n        \n        await self.publish_event(\'user.role_revoked\', {\n            \'user_id\': str(user_id),\n            \'role_id\': str(role_id),\n            \'company_id\': str(company_id) if company_id else None\n        })\n        \n        return Result.ok({\'message\': \'Role revoked successfully\'})\n    \n    async def change_password(\n        self,\n        user_id: UUID,\n        current_password: str,\n        new_password: str,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Change a user\'s password."""\n        user = await self.user_repo.get(user_id)\n        if not user:\n            return Result.fail("User not found")\n        \n        # Verify current password\n        if not verify_password(current_password, user.password_hash):\n            return Result.fail("Current password is incorrect")\n        \n        # Validate new password\n        validation_errors = self._validate_password_strength(new_password)\n        if validation_errors:\n            return Result.fail(validation_errors)\n        \n        # Update password\n        user.password_hash = hash_password(new_password)\n        user.password_changed_at = datetime.utcnow()\n        user.require_password_change = False\n        await self.session.flush()\n        \n        # Invalidate all sessions except current\n        if context and context.user_id == user_id:\n            sessions = await self.session_repo.get_active_sessions(user_id)\n            for s in sessions:\n                if context.metadata.get(\'session_id\') != str(s.id):\n                    s.is_revoked = True\n                    s.revoked_at = datetime.utcnow()\n        \n        await self.publish_event(\'user.password_changed\', {\n            \'user_id\': str(user_id)\n        })\n        \n        return Result.ok({\'message\': \'Password changed successfully\'})\n    \n    async def request_password_reset(\n        self,\n        email: str\n    ) -> ServiceResult:\n        """Request a password reset email."""\n        user = await self.user_repo.get_by_email(email)\n        if not user:\n            # Don\'t reveal that user doesn\'t exist\n            return Result.ok({\'message\': \'If the email exists, a reset link has been sent\'})\n        \n        # Generate reset token\n        reset_token = secrets.token_urlsafe(32)\n        user.password_reset_token = hashlib.sha256(reset_token.encode()).hexdigest()\n        user.password_reset_expires = datetime.utcnow() + timedelta(hours=1)\n        await self.session.flush()\n        \n        # Send email (would be implemented with email service)\n        await self.publish_event(\'user.password_reset_requested\', {\n            \'user_id\': str(user.id),\n            \'email\': user.email,\n            \'reset_token\': reset_token\n        })\n        \n        return Result.ok({\'message\': \'If the email exists, a reset link has been sent\'})\n    \n    async def reset_password(\n        self,\n        token: str,\n        new_password: str\n    ) -> ServiceResult:\n        """Reset password using a reset token."""\n        token_hash = hashlib.sha256(token.encode()).hexdigest()\n        \n        # Find user by reset token\n        result = await self.session.execute(\n            select(User).where(User.password_reset_token == token_hash)\n        )\n        user = result.scalar_one_or_none()\n        \n        if not user:\n            return Result.fail("Invalid or expired reset token")\n        \n        if user.password_reset_expires < datetime.utcnow():\n            return Result.fail("Reset token has expired")\n        \n        # Validate new password\n        validation_errors = self._validate_password_strength(new_password)\n        if validation_errors:\n            return Result.fail(validation_errors)\n        \n        # Update password\n        user.password_hash = hash_password(new_password)\n        user.password_changed_at = datetime.utcnow()\n        user.password_reset_token = None\n        user.password_reset_expires = None\n        user.require_password_change = False\n        await self.session.flush()\n        \n        await self.publish_event(\'user.password_reset\', {\n            \'user_id\': str(user.id)\n        })\n        \n        return Result.ok({\'message\': \'Password reset successfully\'})\n    \n    async def setup_mfa(\n        self,\n        user_id: UUID,\n        mfa_type: str = \'totp\'\n    ) -> ServiceResult:\n        """Set up MFA for a user."""\n        user = await self.user_repo.get(user_id)\n        if not user:\n            return Result.fail("User not found")\n        \n        if mfa_type == \'totp\':\n            secret = generate_mfa_secret()\n            user.mfa_secret = secret\n            user.mfa_type = mfa_type\n            await self.session.flush()\n            \n            return Result.ok({\n                \'secret\': secret,\n                \'qr_code_url\': f"otpauth://totp/SESOS:{user.email}?secret={secret}&issuer=SESOS",\n                \'mfa_type\': mfa_type\n            })\n        \n        return Result.fail("Unsupported MFA type")\n    \n    async def enable_mfa(\n        self,\n        user_id: UUID,\n        verification_code: str\n    ) -> ServiceResult:\n        """Enable MFA after verification."""\n        user = await self.user_repo.get(user_id)\n        if not user:\n            return Result.fail("User not found")\n        \n        if not user.mfa_secret:\n            return Result.fail("MFA not set up. Please set up MFA first.")\n        \n        # Verify code\n        if not await self._verify_mfa_code(user, verification_code):\n            return Result.fail("Invalid verification code")\n        \n        # Generate backup codes\n        backup_codes = [secrets.token_hex(4).upper() for _ in range(10)]\n        user.mfa_backup_codes = backup_codes\n        user.mfa_enabled = True\n        await self.session.flush()\n        \n        await self.publish_event(\'user.mfa_enabled\', {\n            \'user_id\': str(user_id)\n        })\n        \n        return Result.ok({\n            \'message\': \'MFA enabled successfully\',\n            \'backup_codes\': backup_codes\n        })\n    \n    async def disable_mfa(\n        self,\n        user_id: UUID,\n        verification_code: str\n    ) -> ServiceResult:\n        """Disable MFA for a user."""\n        user = await self.user_repo.get(user_id)\n        if not user:\n            return Result.fail("User not found")\n        \n        if not user.mfa_enabled:\n            return Result.fail("MFA is not enabled")\n        \n        # Verify code\n        if not await self._verify_mfa_code(user, verification_code):\n            return Result.fail("Invalid verification code")\n        \n        user.mfa_enabled = False\n        user.mfa_secret = None\n        user.mfa_backup_codes = None\n        user.mfa_type = None\n        await self.session.flush()\n        \n        await self.publish_event(\'user.mfa_disabled\', {\n            \'user_id\': str(user_id)\n        })\n        \n        return Result.ok({\'message\': \'MFA disabled successfully\'})\n    \n    async def create_api_key(\n        self,\n        user_id: UUID,\n        name: str,\n        scopes: List[str],\n        expires_in_days: Optional[int] = None,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Create an API key for a user."""\n        user = await self.user_repo.get(user_id)\n        if not user:\n            return Result.fail("User not found")\n        \n        # Generate API key\n        key = f"ses_{secrets.token_urlsafe(32)}"\n        key_hash = hashlib.sha256(key.encode()).hexdigest()\n        \n        expires_at = None\n        if expires_in_days:\n            expires_at = datetime.utcnow() + timedelta(days=expires_in_days)\n        \n        api_key = await self.api_key_repo.create_api_key(\n            user_id=user_id,\n            name=name,\n            key_hash=key_hash,\n            scopes=scopes,\n            expires_at=expires_at,\n            created_by=context.user_id if context else None\n        )\n        \n        await self.session.flush()\n        \n        await self.publish_event(\'user.api_key_created\', {\n            \'user_id\': str(user_id),\n            \'api_key_id\': str(api_key.id),\n            \'name\': name\n        })\n        \n        # Return the actual key only once\n        return Result.ok({\n            \'id\': str(api_key.id),\n            \'name\': name,\n            \'key\': key,  # Only shown once!\n            \'scopes\': scopes,\n            \'expires_at\': expires_at.isoformat() if expires_at else None,\n            \'created_at\': api_key.created_at.isoformat()\n        })\n    \n    async def revoke_api_key(\n        self,\n        api_key_id: UUID,\n        context: ServiceContext\n    ) -> ServiceResult:\n        """Revoke an API key."""\n        api_key = await self.api_key_repo.get(api_key_id)\n        \n        if not api_key:\n            return Result.fail("API key not found")\n        \n        if api_key.user_id != context.user_id:\n            # Check if user has permission to revoke others\' keys\n            if not context.has_permission(\'api_keys:manage\'):\n                return Result.fail("Permission denied")\n        \n        api_key.is_revoked = True\n        api_key.revoked_at = datetime.utcnow()\n        api_key.revoked_by = context.user_id\n        await self.session.flush()\n        \n        await self.publish_event(\'user.api_key_revoked\', {\n            \'api_key_id\': str(api_key_id),\n            \'user_id\': str(api_key.user_id)\n        })\n        \n        return Result.ok({\'message\': \'API key revoked successfully\'})\n    \n    async def validate_api_key(\n        self,\n        key: str,\n        required_scope: Optional[str] = None\n    ) -> ServiceResult:\n        """Validate an API key and return associated user info."""\n        key_hash = hashlib.sha256(key.encode()).hexdigest()\n        \n        api_key = await self.api_key_repo.get_by_key_hash(key_hash)\n        \n        if not api_key:\n            return Result.fail("Invalid API key")\n        \n        if api_key.is_revoked:\n            return Result.fail("API key has been revoked")\n        \n        if api_key.expires_at and api_key.expires_at < datetime.utcnow():\n            return Result.fail("API key has expired")\n        \n        if required_scope and required_scope not in api_key.scopes:\n            return Result.fail("API key does not have required scope")\n        \n        # Update last used\n        api_key.last_used_at = datetime.utcnow()\n        await self.session.flush()\n        \n        user = await self.user_repo.get(api_key.user_id)\n        \n        return Result.ok({\n            \'user_id\': str(api_key.user_id),\n            \'api_key_id\': str(api_key.id),\n            \'scopes\': api_key.scopes,\n            \'user\': {\n                \'email\': user.email,\n                \'username\': user.username\n            } if user else None\n        })\n    \n    async def get_active_sessions(\n        self,\n        user_id: UUID,\n        context: ServiceContext\n    ) -> ServiceResult:\n        """Get all active sessions for a user."""\n        if user_id != context.user_id and not context.has_permission(\'users:read\'):\n            return Result.fail("Permission denied")\n        \n        sessions = await self.session_repo.get_active_sessions(user_id)\n        \n        session_list = [\n            {\n                \'id\': str(s.id),\n                \'ip_address\': s.ip_address,\n                \'user_agent\': s.user_agent,\n                \'device_info\': s.device_info,\n                \'created_at\': s.created_at.isoformat(),\n                \'last_activity\': s.last_activity.isoformat() if s.last_activity else None,\n                \'is_current\': str(s.id) == context.metadata.get(\'session_id\')\n            }\n            for s in sessions\n        ]\n        \n        return Result.ok({\n            \'sessions\': session_list,\n            \'count\': len(session_list)\n        })\n    \n    # Private helper methods\n    \n    async def _handle_failed_login(self, user: User) -> None:\n        """Handle failed login attempt."""\n        user.failed_login_attempts = (user.failed_login_attempts or 0) + 1\n        \n        # Lock account after 5 failed attempts\n        if user.failed_login_attempts >= 5:\n            user.locked_until = datetime.utcnow() + timedelta(minutes=30)\n            logger.warning(f"User {user.id} locked due to too many failed attempts")\n        \n        await self.session.flush()\n    \n    async def _create_user_session(\n        self,\n        user: User,\n        ip_address: Optional[str] = None,\n        user_agent: Optional[str] = None,\n        device_info: Optional[Dict[str, Any]] = None\n    ) -> Dict[str, str]:\n        """Create a new user session with tokens."""\n        # Create session record\n        user_session = await self.session_repo.create_session(\n            user_id=user.id,\n            ip_address=ip_address,\n            user_agent=user_agent,\n            device_info=device_info\n        )\n        \n        # Generate tokens\n        access_token = create_access_token(\n            data={\n                \'sub\': str(user.id),\n                \'email\': user.email,\n                \'session_id\': str(user_session.id)\n            }\n        )\n        \n        refresh_token = create_refresh_token(\n            data={\n                \'sub\': str(user.id),\n                \'sid\': str(user_session.id)\n            }\n        )\n        \n        await self.session.flush()\n        \n        return {\n            \'access_token\': access_token,\n            \'refresh_token\': refresh_token,\n            \'session_id\': str(user_session.id)\n        }\n    \n    async def _verify_mfa_code(self, user: User, code: str) -> bool:\n        """Verify an MFA code."""\n        # Check backup codes first\n        if user.mfa_backup_codes and code.upper() in user.mfa_backup_codes:\n            # Remove used backup code\n            user.mfa_backup_codes.remove(code.upper())\n            await self.session.flush()\n            return True\n        \n        # Verify TOTP code (simplified)\n        if user.mfa_type == \'totp\' and user.mfa_secret:\n            # Would use proper TOTP verification in production\n            # For now, accept any 6-digit code\n            if len(code) == 6 and code.isdigit():\n                return True\n        \n        return False\n    \n    async def _get_user_profile(self, user: User) -> Dict[str, Any]:\n        """Get user profile data."""\n        return {\n            \'id\': str(user.id),\n            \'email\': user.email,\n            \'username\': user.username,\n            \'first_name\': user.first_name,\n            \'last_name\': user.last_name,\n            \'full_name\': user.full_name,\n            \'is_active\': user.is_active,\n            \'mfa_enabled\': user.mfa_enabled,\n            \'created_at\': user.created_at.isoformat() if user.created_at else None\n        }\n    \n    def _validate_password_strength(self, password: str) -> List[str]:\n        """Validate password strength."""\n        errors = []\n        \n        if len(password) < 8:\n            errors.append("Password must be at least 8 characters long")\n        \n        if not any(c.isupper() for c in password):\n            errors.append("Password must contain at least one uppercase letter")\n        \n        if not any(c.islower() for c in password):\n            errors.append("Password must contain at least one lowercase letter")\n        \n        if not any(c.isdigit() for c in password):\n            errors.append("Password must contain at least one digit")\n        \n        if not any(c in \'!@#$%^&*()_+-=[]{}|;:,.<?>\' for c in password):\n            errors.append("Password must contain at least one special character")\n        \n        return errors\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/services/auth.py\' created successfully.") </create-file> </tool_result>'}
{'role': 'assistant', 'content': 'Now let me create the Company service:\n\n<function_calls>\n<create-file file_path="backend/app/services/company.py">\n"""\nSAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM - Company Service\nBusiness logic for company management, settings, and integrations.\n"""\n\nfrom datetime import datetime, timedelta\nfrom typing import Optional, List, Dict, Any\nfrom uuid import UUID\nimport logging\n\nfrom sqlalchemy import select, and_, or_, func\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\nfrom .base import (\n    BaseService, CompanyScopedService, ServiceContext, ServiceResult, Result\n)\nfrom ..repositories.company import (\n    CompanyRepository, CompanySettingRepository, CompanyIntegrationRepository\n)\nfrom ..models.company import Company, CompanySetting, CompanyIntegration\nfrom ..models.base import CompanyStatus\nfrom ..core.exceptions import (\n    ValidationError, NotFoundError, BusinessLogicError, PermissionDeniedError\n)\nfrom ..core.events import EventPublisher\nfrom ..core.cache import CacheManager\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass CompanyService(CompanyScopedService):\n    """\n    Company Management Service.\n    \n    Features:\n    - Company CRUD operations\n    - Company hierarchy management (holdings, subsidiaries)\n    - Company settings and configuration\n    - Integrations management\n    - Multi-tenancy support\n    """\n    \n    def __init__(\n        self,\n        session: AsyncSession,\n        company_repo: Optional[CompanyRepository] = None,\n        setting_repo: Optional[CompanySettingRepository] = None,\n        integration_repo: Optional[CompanyIntegrationRepository] = None,\n        cache: Optional[CacheManager] = None,\n        event_publisher: Optional[EventPublisher] = None\n    ):\n        super().__init__(session, cache, event_publisher)\n        self.company_repo = company_repo or CompanyRepository(session)\n        self.setting_repo = setting_repo or CompanySettingRepository(session)\n        self.integration_repo = integration_repo or CompanyIntegrationRepository(session)\n    \n    async def create_company(\n        self,\n        name: str,\n        code: str,\n        legal_name: Optional[str] = None,\n        tax_id: Optional[str] = None,\n        registration_number: Optional[str] = None,\n        currency: str = \'USD\',\n        country: Optional[str] = None,\n        timezone: str = \'UTC\',\n        fiscal_year_start: int = 1,\n        parent_id: Optional[UUID] = None,\n        company_type: str = \'operating\',\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Create a new company."""\n        # Check if code is unique\n        existing = await self.company_repo.get_by_code(code)\n        if existing:\n            return Result.fail("Company code already exists")\n        \n        # Validate parent company if specified\n        parent = None\n        if parent_id:\n            parent = await self.company_repo.get(parent_id)\n            if not parent:\n                return Result.fail("Parent company not found")\n        \n        # Create company\n        company = Company(\n            name=name,\n            code=code,\n            legal_name=legal_name or name,\n            tax_id=tax_id,\n            registration_number=registration_number,\n            currency=currency,\n            country=country,\n            timezone=timezone,\n            fiscal_year_start=fiscal_year_start,\n            parent_id=parent_id,\n            company_type=company_type,\n            status=CompanyStatus.ACTIVE\n        )\n        \n        self.session.add(company)\n        await self.session.flush()\n        \n        # Create default settings\n        await self._create_default_settings(company.id)\n        \n        await self.publish_event(\'company.created\', {\n            \'company_id\': str(company.id),\n            \'name\': name,\n            \'code\': code,\n            \'parent_id\': str(parent_id) if parent_id else None\n        })\n        \n        logger.info(f"Company created: {company.id} ({name})")\n        \n        return Result.ok({\n            \'id\': str(company.id),\n            \'name\': company.name,\n            \'code\': company.code,\n            \'status\': company.status.value\n        })\n    \n    async def get_company(\n        self,\n        company_id: UUID,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Get company details."""\n        company = await self.company_repo.get(company_id)\n        \n        if not company:\n            return Result.fail("Company not found")\n        \n        if company.is_deleted:\n            return Result.fail("Company not found")\n        \n        return Result.ok(await self._format_company(company))\n    \n    async def get_company_by_code(\n        self,\n        code: str,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Get company by code."""\n        company = await self.company_repo.get_by_code(code)\n        \n        if not company:\n            return Result.fail("Company not found")\n        \n        return Result.ok(await self._format_company(company))\n    \n    async def update_company(\n        self,\n        company_id: UUID,\n        updates: Dict[str, Any],\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Update company details."""\n        company = await self.company_repo.get(company_id)\n        \n        if not company:\n            return Result.fail("Company not found")\n        \n        # Track changes\n        changes = {}\n        \n        # Update allowed fields\n        allowed_fields = [\n            \'name\', \'legal_name\', \'tax_id\', \'registration_number\',\n            \'currency\', \'country\', \'timezone\', \'fiscal_year_start\',\n            \'address\', \'phone\', \'email\', \'website\', \'logo_url\'\n        ]\n        \n        for field in allowed_fields:\n            if field in updates:\n                old_value = getattr(company, field, None)\n                new_value = updates[field]\n                \n                if old_value != new_value:\n                    setattr(company, field, new_value)\n                    changes[field] = {\n                        \'old\': old_value,\n                        \'new\': new_value\n                    }\n        \n        if changes:\n            company.updated_at = datetime.utcnow()\n            await self.session.flush()\n            \n            await self.publish_event(\'company.updated\', {\n                \'company_id\': str(company_id),\n                \'changes\': changes\n            })\n        \n        return Result.ok({\n            \'id\': str(company.id),\n            \'changes\': changes\n        })\n    \n    async def delete_company(\n        self,\n        company_id: UUID,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Soft delete a company."""\n        company = await self.company_repo.get(company_id)\n        \n        if not company:\n            return Result.fail("Company not found")\n        \n        # Check for subsidiaries\n        subsidiaries = await self.company_repo.get_subsidiaries(company_id)\n        if subsidiaries:\n            return Result.fail("Cannot delete company with subsidiaries. Please delete or reassign subsidiaries first.")\n        \n        # Check for active users\n        # This would check for users associated with the company\n        \n        # Soft delete\n        company.is_deleted = True\n        company.deleted_at = datetime.utcnow()\n        company.status = CompanyStatus.INACTIVE\n        \n        await self.session.flush()\n        \n        await self.publish_event(\'company.deleted\', {\n            \'company_id\': str(company_id)\n        })\n        \n        return Result.ok({\'message\': \'Company deleted successfully\'})\n    \n    async def get_company_tree(\n        self,\n        root_company_id: UUID,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Get the full company hierarchy tree."""\n        root = await self.company_repo.get(root_company_id)\n        if not root:\n            return Result.fail("Company not found")\n        \n        tree = await self._build_company_tree(root)\n        \n        return Result.ok(tree)\n    \n    async def get_subsidiaries(\n        self,\n        company_id: UUID,\n        recursive: bool = True,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Get all subsidiaries of a company."""\n        company = await self.company_repo.get(company_id)\n        if not company:\n            return Result.fail("Company not found")\n        \n        if recursive:\n            subsidiaries = await self._get_all_subsidiaries(company_id)\n        else:\n            subsidiaries = await self.company_repo.get_subsidiaries(company_id)\n        \n        return Result.ok({\n            \'parent_id\': str(company_id),\n            \'subsidiaries\': [await self._format_company(s) for s in subsidiaries],\n            \'count\': len(subsidiaries)\n        })\n    \n    async def add_subsidiary(\n        self,\n        parent_id: UUID,\n        subsidiary_id: UUID,\n        ownership_percentage: float = 100.0,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Add a subsidiary to a parent company."""\n        parent = await self.company_repo.get(parent_id)\n        if not parent:\n            return Result.fail("Parent company not found")\n        \n        subsidiary = await self.company_repo.get(subsidiary_id)\n        if not subsidiary:\n            return Result.fail("Subsidiary company not found")\n        \n        # Check for circular reference\n        if await self._is_circular_reference(parent_id, subsidiary_id):\n            return Result.fail("Circular reference detected. Cannot create this hierarchy.")\n        \n        subsidiary.parent_id = parent_id\n        subsidiary.ownership_percentage = ownership_percentage\n        \n        await self.session.flush()\n        \n        await self.publish_event(\'company.subsidiary_added\', {\n            \'parent_id\': str(parent_id),\n            \'subsidiary_id\': str(subsidiary_id),\n            \'ownership_percentage\': ownership_percentage\n        })\n        \n        return Result.ok({\n            \'message\': \'Subsidiary added successfully\',\n            \'parent_id\': str(parent_id),\n            \'subsidiary_id\': str(subsidiary_id)\n        })\n    \n    async def remove_subsidiary(\n        self,\n        parent_id: UUID,\n        subsidiary_id: UUID,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Remove a subsidiary from a parent company."""\n        subsidiary = await self.company_repo.get(subsidiary_id)\n        \n        if not subsidiary:\n            return Result.fail("Subsidiary not found")\n        \n        if subsidiary.parent_id != parent_id:\n            return Result.fail("Company is not a subsidiary of the specified parent")\n        \n        subsidiary.parent_id = None\n        subsidiary.ownership_percentage = None\n        \n        await self.session.flush()\n        \n        await self.publish_event(\'company.subsidiary_removed\', {\n            \'parent_id\': str(parent_id),\n            \'subsidiary_id\': str(subsidiary_id)\n        })\n        \n        return Result.ok({\'message\': \'Subsidiary removed successfully\'})\n    \n    # Settings Management\n    \n    async def get_settings(\n        self,\n        company_id: UUID,\n        category: Optional[str] = None,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Get company settings, optionally filtered by category."""\n        if category:\n            settings = await self.setting_repo.get_by_category(company_id, category)\n        else:\n            settings = await self.setting_repo.get_all_settings(company_id)\n        \n        return Result.ok({\n            \'company_id\': str(company_id),\n            \'settings\': {s.key: s.value for s in settings},\n            \'category\': category\n        })\n    \n    async def get_setting(\n        self,\n        company_id: UUID,\n        key: str,\n        default: Any = None,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Get a specific setting value."""\n        setting = await self.setting_repo.get_setting(company_id, key)\n        \n        if not setting:\n            return Result.ok({\n                \'key\': key,\n                \'value\': default,\n                \'is_default\': True\n            })\n        \n        return Result.ok({\n            \'key\': key,\n            \'value\': setting.value,\n            \'is_default\': False\n        })\n    \n    async def set_setting(\n        self,\n        company_id: UUID,\n        key: str,\n        value: Any,\n        category: str = \'general\',\n        description: Optional[str] = None,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Set a company setting."""\n        setting = await self.setting_repo.get_setting(company_id, key)\n        \n        if setting:\n            setting.value = value\n            setting.updated_at = datetime.utcnow()\n        else:\n            setting = CompanySetting(\n                company_id=company_id,\n                key=key,\n                value=value,\n                category=category,\n                description=description\n            )\n            self.session.add(setting)\n        \n        await self.session.flush()\n        \n        # Invalidate settings cache\n        await self.invalidate_company_cache(company_id, \'settings\')\n        \n        await self.publish_event(\'company.setting_changed\', {\n            \'company_id\': str(company_id),\n            \'key\': key,\n            \'value\': value\n        })\n        \n        return Result.ok({\n            \'key\': key,\n            \'value\': value,\n            \'message\': \'Setting updated successfully\'\n        })\n    \n    async def delete_setting(\n        self,\n        company_id: UUID,\n        key: str,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Delete a company setting."""\n        setting = await self.setting_repo.get_setting(company_id, key)\n        \n        if not setting:\n            return Result.fail("Setting not found")\n        \n        await self.setting_repo.delete(setting.id)\n        await self.session.flush()\n        \n        await self.invalidate_company_cache(company_id, \'settings\')\n        \n        return Result.ok({\'message\': \'Setting deleted successfully\'})\n    \n    # Integration Management\n    \n    async def get_integrations(\n        self,\n        company_id: UUID,\n        active_only: bool = False,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Get company integrations."""\n        if active_only:\n            integrations = await self.integration_repo.get_active_integrations(company_id)\n        else:\n            integrations = await self.integration_repo.get_by_company(company_id)\n        \n        return Result.ok({\n            \'company_id\': str(company_id),\n            \'integrations\': [\n                {\n                    \'id\': str(i.id),\n                    \'integration_type\': i.integration_type,\n                    \'name\': i.name,\n                    \'is_active\': i.is_active,\n                    \'config\': i.config,\n                    \'last_sync_at\': i.last_sync_at.isoformat() if i.last_sync_at else None\n                }\n                for i in integrations\n            ],\n            \'count\': len(integrations)\n        })\n    \n    async def add_integration(\n        self,\n        company_id: UUID,\n        integration_type: str,\n        name: str,\n        config: Dict[str, Any],\n        credentials: Optional[Dict[str, Any]] = None,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Add an integration to a company."""\n        # Check for duplicate\n        existing = await self.integration_repo.get_by_type(company_id, integration_type)\n        if existing:\n            return Result.fail(f"Integration of type \'{integration_type}\' already exists")\n        \n        integration = CompanyIntegration(\n            company_id=company_id,\n            integration_type=integration_type,\n            name=name,\n            config=config,\n            credentials=credentials,\n            is_active=True\n        )\n        \n        self.session.add(integration)\n        await self.session.flush()\n        \n        await self.publish_event(\'company.integration_added\', {\n            \'company_id\': str(company_id),\n            \'integration_id\': str(integration.id),\n            \'integration_type\': integration_type\n        })\n        \n        return Result.ok({\n            \'id\': str(integration.id),\n            \'integration_type\': integration_type,\n            \'name\': name,\n            \'message\': \'Integration added successfully\'\n        })\n    \n    async def update_integration(\n        self,\n        integration_id: UUID,\n        updates: Dict[str, Any],\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Update an integration."""\n        integration = await self.integration_repo.get(integration_id)\n        \n        if not integration:\n            return Result.fail("Integration not found")\n        \n        allowed_fields = [\'name\', \'config\', \'credentials\', \'sync_settings\']\n        \n        for field in allowed_fields:\n            if field in updates:\n                setattr(integration, field, updates[field])\n        \n        integration.updated_at = datetime.utcnow()\n        await self.session.flush()\n        \n        await self.publish_event(\'company.integration_updated\', {\n            \'integration_id\': str(integration_id),\n            \'company_id\': str(integration.company_id)\n        })\n        \n        return Result.ok({\n            \'id\': str(integration.id),\n            \'message\': \'Integration updated successfully\'\n        })\n    \n    async def toggle_integration(\n        self,\n        integration_id: UUID,\n        active: bool,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Enable or disable an integration."""\n        integration = await self.integration_repo.get(integration_id)\n        \n        if not integration:\n            return Result.fail("Integration not found")\n        \n        integration.is_active = active\n        integration.updated_at = datetime.utcnow()\n        await self.session.flush()\n        \n        await self.publish_event(\'company.integration_toggled\', {\n            \'integration_id\': str(integration_id),\n            \'company_id\': str(integration.company_id),\n            \'is_active\': active\n        })\n        \n        return Result.ok({\n            \'id\': str(integration_id),\n            \'is_active\': active,\n            \'message\': f"Integration {\'enabled\' if active else \'disabled\'} successfully"\n        })\n    \n    async def test_integration(\n        self,\n        integration_id: UUID,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Test an integration connection."""\n        integration = await self.integration_repo.get(integration_id)\n        \n        if not integration:\n            return Result.fail("Integration not found")\n        \n        # This would contain actual integration testing logic\n        # based on the integration type\n        \n        result = await self._test_integration_connection(integration)\n        \n        if result[\'success\']:\n            integration.last_sync_at = datetime.utcnow()\n            integration.sync_status = \'success\'\n            await self.session.flush()\n        \n        return Result.ok(result)\n    \n    async def sync_integration(\n        self,\n        integration_id: UUID,\n        full_sync: bool = False,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Trigger an integration sync."""\n        integration = await self.integration_repo.get(integration_id)\n        \n        if not integration:\n            return Result.fail("Integration not found")\n        \n        if not integration.is_active:\n            return Result.fail("Integration is not active")\n        \n        # This would contain actual sync logic based on integration type\n        \n        await self.publish_event(\'company.integration_sync_started\', {\n            \'integration_id\': str(integration_id),\n            \'company_id\': str(integration.company_id),\n            \'full_sync\': full_sync\n        })\n        \n        return Result.ok({\n            \'integration_id\': str(integration_id),\n            \'status\': \'syncing\',\n            \'message\': \'Sync started successfully\'\n        })\n    \n    # Private helper methods\n    \n    async def _create_default_settings(self, company_id: UUID) -> None:\n        """Create default settings for a new company."""\n        default_settings = {\n            \'general.locale\': \'en_US\',\n            \'general.date_format\': \'YYYY-MM-DD\',\n            \'general.number_format\': \'#,##0.00\',\n            \'accounting.functional_currency\': \'USD\',\n            \'accounting.retain_earnings_account\': None,\n            \'accounting.rounding_method\': \'round_half_up\',\n            \'tax.default_jurisdiction\': None,\n            \'reports.default_currency\': \'USD\',\n            \'reports.decimal_places\': 2\n        }\n        \n        for key, value in default_settings.items():\n            category = key.split(\'.\')[0]\n            setting = CompanySetting(\n                company_id=company_id,\n                key=key,\n                value=value,\n                category=category\n            )\n            self.session.add(setting)\n        \n        await self.session.flush()\n    \n    async def _build_company_tree(\n        self,\n        company: Company,\n        depth: int = 0\n    ) -> Dict[str, Any]:\n        """Build a hierarchical company tree."""\n        node = await self._format_company(company)\n        node[\'depth\'] = depth\n        \n        subsidiaries = await self.company_repo.get_subsidiaries(company.id)\n        \n        if subsidiaries:\n            node[\'subsidiaries\'] = []\n            for sub in subsidiaries:\n                sub_tree = await self._build_company_tree(sub, depth + 1)\n                node[\'subsidiaries\'].append(sub_tree)\n        \n        return node\n    \n    async def _get_all_subsidiaries(\n        self,\n        company_id: UUID,\n        collected: Optional[List[Company]] = None\n    ) -> List[Company]:\n        """Recursively get all subsidiaries."""\n        if collected is None:\n            collected = []\n        \n        direct_subs = await self.company_repo.get_subsidiaries(company_id)\n        \n        for sub in direct_subs:\n            collected.append(sub)\n            await self._get_all_subsidiaries(sub.id, collected)\n        \n        return collected\n    \n    async def _is_circular_reference(\n        self,\n        parent_id: UUID,\n        subsidiary_id: UUID\n    ) -> bool:\n        """Check if adding subsidiary would create a circular reference."""\n        # Get all ancestors of the parent\n        ancestors = set()\n        current_id = parent_id\n        \n        while current_id:\n            if current_id in ancestors:\n                break\n            ancestors.add(current_id)\n            \n            company = await self.company_repo.get(current_id)\n            if not company or not company.parent_id:\n                break\n            current_id = company.parent_id\n        \n        # Check if subsidiary is an ancestor\n        return subsidiary_id in ancestors\n    \n    async def _test_integration_connection(\n        self,\n        integration: CompanyIntegration\n    ) -> Dict[str, Any]:\n        """Test integration connection based on type."""\n        # This is a placeholder - actual implementation would test\n        # based on the specific integration type\n        \n        return {\n            \'success\': True,\n            \'message\': \'Connection successful\',\n            \'tested_at\': datetime.utcnow().isoformat()\n        }\n    \n    async def _format_company(\n        self,\n        company: Company\n    ) -> Dict[str, Any]:\n        """Format company data for API response."""\n        return {\n            \'id\': str(company.id),\n            \'name\': company.name,\n            \'code\': company.code,\n            \'legal_name\': company.legal_name,\n            \'tax_id\': company.tax_id,\n            \'registration_number\': company.registration_number,\n            \'currency\': company.currency,\n            \'country\': company.country,\n            \'timezone\': company.timezone,\n            \'fiscal_year_start\': company.fiscal_year_start,\n            \'parent_id\': str(company.parent_id) if company.parent_id else None,\n            \'company_type\': company.company_type,\n            \'status\': company.status.value if company.status else None,\n            \'ownership_percentage\': float(company.ownership_percentage) if company.ownership_percentage else None,\n            \'address\': company.address,\n            \'phone\': company.phone,\n            \'email\': company.email,\n            \'website\': company.website,\n            \'logo_url\': company.logo_url,\n            \'created_at\': company.created_at.isoformat() if company.created_at else None,\n            \'updated_at\': company.updated_at.isoformat() if company.updated_at else None\n        }\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/services/company.py\' created successfully.") </create-file> </tool_result>'}
{'role': 'assistant', 'content': 'Now let me create the User service:\n\n<function_calls>\n<create-file file_path="backend/app/services/user.py">\n"""\nSAMMS ENTERPRISE SOVEREIGN OPERATING SYSTEM - User Service\nBusiness logic for user management, preferences, and profile operations.\n"""\n\nfrom datetime import datetime, timedelta\nfrom typing import Optional, List, Dict, Any\nfrom uuid import UUID\nimport logging\nimport secrets\n\nfrom sqlalchemy import select, and_, or_, func\nfrom sqlalchemy.ext.asyncio import AsyncSession\n\nfrom .base import (\n    BaseService, CompanyScopedService, ServiceContext, ServiceResult, Result, PaginationResult\n)\nfrom ..repositories.user import (\n    UserRepository, UserRoleRepository, UserPreferenceRepository\n)\nfrom ..repositories.company import CompanyRepository\nfrom ..models.user import User, UserPreference\nfrom ..core.security import hash_password, verify_password\nfrom ..core.exceptions import (\n    ValidationError, NotFoundError, BusinessLogicError, PermissionDeniedError, ConflictError\n)\nfrom ..core.events import EventPublisher\nfrom ..core.cache import CacheManager\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass UserService(CompanyScopedService):\n    """\n    User Management Service.\n    \n    Features:\n    - User CRUD operations\n    - Profile management\n    - User preferences\n    - User-company associations\n    - Bulk operations\n    """\n    \n    def __init__(\n        self,\n        session: AsyncSession,\n        user_repo: Optional[UserRepository] = None,\n        user_role_repo: Optional[UserRoleRepository] = None,\n        preference_repo: Optional[UserPreferenceRepository] = None,\n        company_repo: Optional[CompanyRepository] = None,\n        cache: Optional[CacheManager] = None,\n        event_publisher: Optional[EventPublisher] = None\n    ):\n        super().__init__(session, cache, event_publisher)\n        self.user_repo = user_repo or UserRepository(session)\n        self.user_role_repo = user_role_repo or UserRoleRepository(session)\n        self.preference_repo = preference_repo or UserPreferenceRepository(session)\n        self.company_repo = company_repo or CompanyRepository(session)\n    \n    async def create_user(\n        self,\n        email: str,\n        username: str,\n        password: str,\n        first_name: str,\n        last_name: str,\n        company_id: Optional[UUID] = None,\n        roles: Optional[List[str]] = None,\n        send_invitation: bool = True,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Create a new user."""\n        # Validate required fields\n        missing = self.validate_required(\n            {\'email\': email, \'username\': username, \'password\': password,\n             \'first_name\': first_name, \'last_name\': last_name},\n            [\'email\', \'username\', \'password\', \'first_name\', \'last_name\']\n        )\n        if missing:\n            return Result.fail(f"Missing required fields: {\', \'.join(missing)}")\n        \n        # Check for existing email\n        existing_email = await self.user_repo.get_by_email(email)\n        if existing_email:\n            return Result.fail("Email already registered")\n        \n        # Check for existing username\n        existing_username = await self.user_repo.get_by_username(username)\n        if existing_username:\n            return Result.fail("Username already taken")\n        \n        # Validate password strength\n        password_errors = self._validate_password_strength(password)\n        if password_errors:\n            return Result.fail(password_errors)\n        \n        # Create user\n        user = User(\n            email=email.lower(),\n            username=username,\n            password_hash=hash_password(password),\n            first_name=first_name,\n            last_name=last_name,\n            is_active=True,\n            require_password_change=send_invitation,\n            email_verified=not send_invitation\n        )\n        \n        self.session.add(user)\n        await self.session.flush()\n        \n        # Associate with company if specified\n        if company_id:\n            company = await self.company_repo.get(company_id)\n            if company:\n                user.companies.append(company)\n        \n        # Create default preferences\n        await self._create_default_preferences(user.id)\n        \n        # Assign roles\n        if roles:\n            for role_name in roles:\n                await self._assign_role_by_name(user.id, role_name, company_id)\n        \n        await self.session.flush()\n        \n        # Send invitation if requested\n        if send_invitation:\n            invitation_token = secrets.token_urlsafe(32)\n            user.invitation_token = invitation_token\n            user.invitation_expires = datetime.utcnow() + timedelta(days=7)\n            await self.session.flush()\n            \n            await self.publish_event(\'user.invitation_created\', {\n                \'user_id\': str(user.id),\n                \'email\': email,\n                \'invitation_token\': invitation_token\n            })\n        \n        await self.publish_event(\'user.created\', {\n            \'user_id\': str(user.id),\n            \'email\': email,\n            \'username\': username,\n            \'company_id\': str(company_id) if company_id else None\n        })\n        \n        logger.info(f"User created: {user.id} ({email})")\n        \n        return Result.ok({\n            \'id\': str(user.id),\n            \'email\': user.email,\n            \'username\': user.username,\n            \'full_name\': user.full_name,\n            \'is_active\': user.is_active,\n            \'require_password_change\': user.require_password_change,\n            \'invitation_sent\': send_invitation\n        })\n    \n    async def get_user(\n        self,\n        user_id: UUID,\n        include_preferences: bool = False,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Get user details."""\n        user = await self.user_repo.get(user_id)\n        \n        if not user:\n            return Result.fail("User not found")\n        \n        if user.is_deleted:\n            return Result.fail("User not found")\n        \n        data = await self._format_user(user)\n        \n        if include_preferences:\n            preferences = await self.preference_repo.get_by_user(user_id)\n            data[\'preferences\'] = {p.key: p.value for p in preferences}\n        \n        return Result.ok(data)\n    \n    async def get_user_by_email(\n        self,\n        email: str,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Get user by email address."""\n        user = await self.user_repo.get_by_email(email)\n        \n        if not user:\n            return Result.fail("User not found")\n        \n        return Result.ok(await self._format_user(user))\n    \n    async def update_user(\n        self,\n        user_id: UUID,\n        updates: Dict[str, Any],\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Update user profile."""\n        user = await self.user_repo.get(user_id)\n        \n        if not user:\n            return Result.fail("User not found")\n        \n        # Track changes\n        changes = {}\n        \n        # Update allowed fields\n        allowed_fields = [\n            \'first_name\', \'last_name\', \'phone\', \'avatar_url\',\n            \'job_title\', \'department\', \'location\', \'timezone\', \'locale\'\n        ]\n        \n        for field in allowed_fields:\n            if field in updates:\n                old_value = getattr(user, field, None)\n                new_value = updates[field]\n                \n                if old_value != new_value:\n                    setattr(user, field, new_value)\n                    changes[field] = {\'old\': old_value, \'new\': new_value}\n        \n        if changes:\n            user.updated_at = datetime.utcnow()\n            await self.session.flush()\n            \n            await self.publish_event(\'user.updated\', {\n                \'user_id\': str(user_id),\n                \'changes\': changes\n            })\n        \n        return Result.ok({\n            \'id\': str(user.id),\n            \'changes\': changes\n        })\n    \n    async def update_email(\n        self,\n        user_id: UUID,\n        new_email: str,\n        password: str,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Update user email address."""\n        user = await self.user_repo.get(user_id)\n        \n        if not user:\n            return Result.fail("User not found")\n        \n        # Verify password\n        if not verify_password(password, user.password_hash):\n            return Result.fail("Invalid password")\n        \n        # Check if email is already taken\n        existing = await self.user_repo.get_by_email(new_email)\n        if existing and existing.id != user_id:\n            return Result.fail("Email already in use")\n        \n        old_email = user.email\n        user.email = new_email.lower()\n        user.email_verified = False\n        user.email_verification_token = secrets.token_urlsafe(32)\n        user.updated_at = datetime.utcnow()\n        \n        await self.session.flush()\n        \n        await self.publish_event(\'user.email_changed\', {\n            \'user_id\': str(user_id),\n            \'old_email\': old_email,\n            \'new_email\': new_email\n        })\n        \n        return Result.ok({\n            \'message\': \'Email updated. Please verify your new email address.\',\n            \'new_email\': new_email\n        })\n    \n    async def verify_email(\n        self,\n        token: str\n    ) -> ServiceResult:\n        """Verify user email address."""\n        user = await self.user_repo.get_by_verification_token(token)\n        \n        if not user:\n            return Result.fail("Invalid or expired verification token")\n        \n        user.email_verified = True\n        user.email_verification_token = None\n        user.updated_at = datetime.utcnow()\n        \n        await self.session.flush()\n        \n        await self.publish_event(\'user.email_verified\', {\n            \'user_id\': str(user.id),\n            \'email\': user.email\n        })\n        \n        return Result.ok({\n            \'message\': \'Email verified successfully\',\n            \'email\': user.email\n        })\n    \n    async def deactivate_user(\n        self,\n        user_id: UUID,\n        reason: Optional[str] = None,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Deactivate a user account."""\n        user = await self.user_repo.get(user_id)\n        \n        if not user:\n            return Result.fail("User not found")\n        \n        if not user.is_active:\n            return Result.fail("User is already inactive")\n        \n        user.is_active = False\n        user.deactivated_at = datetime.utcnow()\n        user.deactivation_reason = reason\n        user.updated_at = datetime.utcnow()\n        \n        await self.session.flush()\n        \n        await self.publish_event(\'user.deactivated\', {\n            \'user_id\': str(user_id),\n            \'reason\': reason\n        })\n        \n        logger.info(f"User deactivated: {user_id}")\n        \n        return Result.ok({\n            \'message\': \'User deactivated successfully\',\n            \'is_active\': False\n        })\n    \n    async def activate_user(\n        self,\n        user_id: UUID,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Activate a user account."""\n        user = await self.user_repo.get(user_id)\n        \n        if not user:\n            return Result.fail("User not found")\n        \n        if user.is_active:\n            return Result.fail("User is already active")\n        \n        user.is_active = True\n        user.deactivated_at = None\n        user.deactivation_reason = None\n        user.updated_at = datetime.utcnow()\n        \n        await self.session.flush()\n        \n        await self.publish_event(\'user.activated\', {\n            \'user_id\': str(user_id)\n        })\n        \n        return Result.ok({\n            \'message\': \'User activated successfully\',\n            \'is_active\': True\n        })\n    \n    async def delete_user(\n        self,\n        user_id: UUID,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Soft delete a user account."""\n        user = await self.user_repo.get(user_id)\n        \n        if not user:\n            return Result.fail("User not found")\n        \n        user.is_deleted = True\n        user.deleted_at = datetime.utcnow()\n        user.email = f"deleted_{user_id}@deleted.invalid"\n        user.username = f"deleted_{user_id}"\n        user.is_active = False\n        \n        await self.session.flush()\n        \n        await self.publish_event(\'user.deleted\', {\n            \'user_id\': str(user_id)\n        })\n        \n        return Result.ok({\'message\': \'User deleted successfully\'})\n    \n    async def list_users(\n        self,\n        company_id: Optional[UUID] = None,\n        is_active: Optional[bool] = None,\n        search: Optional[str] = None,\n        page: int = 1,\n        page_size: int = 20,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """List users with filtering and pagination."""\n        filters = {}\n        \n        if company_id:\n            filters[\'company_id\'] = company_id\n        if is_active is not None:\n            filters[\'is_active\'] = is_active\n        \n        # Get users with pagination\n        if search:\n            users = await self.user_repo.search_users(\n                company_id=company_id,\n                search_term=search,\n                skip=(page - 1) * page_size,\n                limit=page_size\n            )\n            total = await self.user_repo.count_search_results(\n                company_id=company_id,\n                search_term=search\n            )\n        else:\n            users = await self.user_repo.get_all(\n                skip=(page - 1) * page_size,\n                limit=page_size\n            )\n            total = await self.user_repo.count_all()\n        \n        return Result.ok(PaginationResult.create(\n            items=[await self._format_user(u) for u in users],\n            total=total,\n            page=page,\n            page_size=page_size\n        ))\n    \n    async def get_user_by_company(\n        self,\n        company_id: UUID,\n        skip: int = 0,\n        limit: int = 100,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Get users associated with a company."""\n        users = await self.user_repo.get_by_company(company_id, skip, limit)\n        \n        return Result.ok({\n            \'company_id\': str(company_id),\n            \'users\': [await self._format_user(u) for u in users],\n            \'count\': len(users)\n        })\n    \n    async def add_user_to_company(\n        self,\n        user_id: UUID,\n        company_id: UUID,\n        roles: Optional[List[str]] = None,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Add a user to a company."""\n        user = await self.user_repo.get(user_id)\n        if not user:\n            return Result.fail("User not found")\n        \n        company = await self.company_repo.get(company_id)\n        if not company:\n            return Result.fail("Company not found")\n        \n        # Check if already associated\n        if company in user.companies:\n            return Result.fail("User is already associated with this company")\n        \n        user.companies.append(company)\n        \n        # Assign roles\n        if roles:\n            for role_name in roles:\n                await self._assign_role_by_name(user_id, role_name, company_id)\n        \n        await self.session.flush()\n        \n        await self.publish_event(\'user.added_to_company\', {\n            \'user_id\': str(user_id),\n            \'company_id\': str(company_id)\n        })\n        \n        return Result.ok({\n            \'message\': \'User added to company successfully\',\n            \'user_id\': str(user_id),\n            \'company_id\': str(company_id)\n        })\n    \n    async def remove_user_from_company(\n        self,\n        user_id: UUID,\n        company_id: UUID,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Remove a user from a company."""\n        user = await self.user_repo.get(user_id)\n        if not user:\n            return Result.fail("User not found")\n        \n        company = await self.company_repo.get(company_id)\n        if not company:\n            return Result.fail("Company not found")\n        \n        if company not in user.companies:\n            return Result.fail("User is not associated with this company")\n        \n        user.companies.remove(company)\n        \n        # Remove company-specific roles\n        user_roles = await self.user_role_repo.get_by_user_and_company(user_id, company_id)\n        for ur in user_roles:\n            await self.user_role_repo.delete(ur.id)\n        \n        await self.session.flush()\n        \n        await self.publish_event(\'user.removed_from_company\', {\n            \'user_id\': str(user_id),\n            \'company_id\': str(company_id)\n        })\n        \n        return Result.ok({\n            \'message\': \'User removed from company successfully\'\n        })\n    \n    # Preference Management\n    \n    async def get_preferences(\n        self,\n        user_id: UUID,\n        category: Optional[str] = None,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Get user preferences."""\n        if category:\n            preferences = await self.preference_repo.get_by_category(user_id, category)\n        else:\n            preferences = await self.preference_repo.get_by_user(user_id)\n        \n        return Result.ok({\n            \'user_id\': str(user_id),\n            \'preferences\': {p.key: p.value for p in preferences}\n        })\n    \n    async def set_preference(\n        self,\n        user_id: UUID,\n        key: str,\n        value: Any,\n        category: str = \'general\',\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Set a user preference."""\n        preference = await self.preference_repo.get_preference(user_id, key)\n        \n        if preference:\n            preference.value = value\n            preference.updated_at = datetime.utcnow()\n        else:\n            preference = UserPreference(\n                user_id=user_id,\n                key=key,\n                value=value,\n                category=category\n            )\n            self.session.add(preference)\n        \n        await self.session.flush()\n        \n        return Result.ok({\n            \'key\': key,\n            \'value\': value,\n            \'message\': \'Preference updated\'\n        })\n    \n    async def delete_preference(\n        self,\n        user_id: UUID,\n        key: str,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Delete a user preference."""\n        preference = await self.preference_repo.get_preference(user_id, key)\n        \n        if not preference:\n            return Result.fail("Preference not found")\n        \n        await self.preference_repo.delete(preference.id)\n        await self.session.flush()\n        \n        return Result.ok({\'message\': \'Preference deleted\'})\n    \n    # Bulk Operations\n    \n    async def bulk_create_users(\n        self,\n        users_data: List[Dict[str, Any]],\n        company_id: Optional[UUID] = None,\n        send_invitations: bool = True,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Create multiple users at once."""\n        created = []\n        failed = []\n        \n        for user_data in users_data:\n            result = await self.create_user(\n                email=user_data.get(\'email\'),\n                username=user_data.get(\'username\'),\n                password=user_data.get(\'password\', secrets.token_urlsafe(16)),\n                first_name=user_data.get(\'first_name\'),\n                last_name=user_data.get(\'last_name\'),\n                company_id=company_id,\n                roles=user_data.get(\'roles\'),\n                send_invitation=send_invitations,\n                context=context\n            )\n            \n            if result.success:\n                created.append(result.data)\n            else:\n                failed.append({\n                    \'email\': user_data.get(\'email\'),\n                    \'errors\': result.errors\n                })\n        \n        return Result.ok({\n            \'created_count\': len(created),\n            \'failed_count\': len(failed),\n            \'created\': created,\n            \'failed\': failed\n        })\n    \n    async def bulk_update_users(\n        self,\n        updates: List[Dict[str, Any]],\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Update multiple users at once."""\n        updated = []\n        failed = []\n        \n        for update in updates:\n            user_id = update.get(\'user_id\')\n            if not user_id:\n                failed.append({\'error\': \'Missing user_id\'})\n                continue\n            \n            result = await self.update_user(\n                user_id=UUID(user_id),\n                updates=update,\n                context=context\n            )\n            \n            if result.success:\n                updated.append(result.data)\n            else:\n                failed.append({\n                    \'user_id\': user_id,\n                    \'errors\': result.errors\n                })\n        \n        return Result.ok({\n            \'updated_count\': len(updated),\n            \'failed_count\': len(failed),\n            \'updated\': updated,\n            \'failed\': failed\n        })\n    \n    async def export_users(\n        self,\n        company_id: Optional[UUID] = None,\n        format: str = \'json\',\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Export users data."""\n        if company_id:\n            users = await self.user_repo.get_by_company(company_id, limit=10000)\n        else:\n            users = await self.user_repo.get_all(limit=10000)\n        \n        export_data = [await self._format_user(u) for u in users]\n        \n        return Result.ok({\n            \'format\': format,\n            \'count\': len(export_data),\n            \'data\': export_data\n        })\n    \n    # Statistics\n    \n    async def get_user_statistics(\n        self,\n        company_id: Optional[UUID] = None,\n        context: Optional[ServiceContext] = None\n    ) -> ServiceResult:\n        """Get user statistics."""\n        stats = await self.user_repo.get_user_statistics(company_id)\n        \n        return Result.ok(stats)\n    \n    # Private helper methods\n    \n    async def _create_default_preferences(self, user_id: UUID) -> None:\n        """Create default preferences for a new user."""\n        default_preferences = {\n            \'ui.theme\': \'light\',\n            \'ui.language\': \'en\',\n            \'ui.timezone\': \'UTC\',\n            \'ui.date_format\': \'YYYY-MM-DD\',\n            \'ui.time_format\': \'24h\',\n            \'notifications.email\': True,\n            \'notifications.push\': True,\n            \'notifications.desktop\': False,\n            \'dashboard.default_view\': \'overview\',\n            \'dashboard.refresh_interval\': 300\n        }\n        \n        for key, value in default_preferences.items():\n            category = key.split(\'.\')[0]\n            pref = UserPreference(\n                user_id=user_id,\n                key=key,\n                value=value,\n                category=category\n            )\n            self.session.add(pref)\n        \n        await self.session.flush()\n    \n    async def _assign_role_by_name(\n        self,\n        user_id: UUID,\n        role_name: str,\n        company_id: Optional[UUID] = None\n    ) -> None:\n        """Assign a role to a user by role name."""\n        from ..models.user import Role\n        \n        result = await self.session.execute(\n            select(Role).where(Role.name == role_name)\n        )\n        role = result.scalar_one_or_none()\n        \n        if role:\n            await self.user_role_repo.create_user_role(\n                user_id=user_id,\n                role_id=role.id,\n                company_id=company_id\n            )\n    \n    def _validate_password_strength(self, password: str) -> List[str]:\n        """Validate password strength."""\n        errors = []\n        \n        if len(password) < 8:\n            errors.append("Password must be at least 8 characters long")\n        if not any(c.isupper() for c in password):\n            errors.append("Password must contain at least one uppercase letter")\n        if not any(c.islower() for c in password):\n            errors.append("Password must contain at least one lowercase letter")\n        if not any(c.isdigit() for c in password):\n            errors.append("Password must contain at least one digit")\n        \n        return errors\n    \n    async def _format_user(self, user: User) -> Dict[str, Any]:\n        """Format user data for API response."""\n        return {\n            \'id\': str(user.id),\n            \'email\': user.email,\n            \'username\': user.username,\n            \'first_name\': user.first_name,\n            \'last_name\': user.last_name,\n            \'full_name\': user.full_name,\n            \'phone\': user.phone,\n            \'avatar_url\': user.avatar_url,\n            \'job_title\': user.job_title,\n            \'department\': user.department,\n            \'location\': user.location,\n            \'timezone\': user.timezone,\n            \'locale\': user.locale,\n            \'is_active\': user.is_active,\n            \'is_admin\': user.is_admin,\n            \'email_verified\': user.email_verified,\n            \'mfa_enabled\': user.mfa_enabled,\n            \'last_login_at\': user.last_login_at.isoformat() if user.last_login_at else None,\n            \'created_at\': user.created_at.isoformat() if user.created_at else None,\n            \'updated_at\': user.updated_at.isoformat() if user.updated_at else None\n        }\n</create-file>\n</function_calls>'}
{'role': 'user', 'content': '<tool_result> <create-file> ToolResult(success=True, output="File \'backend/app/services/user.py\' created successfully.") </create-file> </tool_result>'}