- name
- odoo-knowledge-agent
- description
- Scrape Odoo forum solved threads to build error prevention guardrails and auto-fix scripts. Extract patterns from 1000+ solved issues to prevent common mistakes before deployment and auto-heal production problems. Combines web scraping, AI pattern extraction, preventive guardrails generation, and auto-patch scripts for Odoo custom development lifecycle.
# Odoo Knowledge Agent - Error Prevention & Auto-Fix System
## When to Use This Skill
Use this skill when you need to:
- **Prevent Odoo errors before deployment** with guardrails
- **Auto-heal production issues** using community-validated fixes
- **Build intelligence from Odoo forum** solved threads (1000+ cases)
- **Generate error code reference** and troubleshooting guides
- **Create preventive checks** for module development
- **Implement auto-patch systems** for known failure patterns
- **Reduce debugging time** by learning from community solutions
- **Build SOP library** for common Odoo development errors
## Core Capabilities
### 1. Forum Scraping Intelligence
**Scrape Odoo Forum for Solved Threads**
- Extract 1000+ solved issues across all categories
- Parse accepted answers and code fixes
- Identify error patterns and root causes
- Build searchable knowledge base
### 2. Guardrails Generation
**Preventive Error Checks**
- Block common mistakes before deployment
- Validate manifest files automatically
- Check field synchronization issues
- Enforce best practices (OCA standards)
### 3. Auto-Patch Scripts
**Automated Error Fixes**
- Apply community-validated solutions
- Fix known issues automatically
- Handle common migration problems
- Self-healing production systems
### 4. Vision-Based UI Automation
**askui-Compatible Automation**
- UI actions without brittle selectors
- Cross-browser compatibility
- Screenshot-based validation
- Self-healing test scripts
## Prerequisites
### Required Software
- Python 3.11+
- Beautiful Soup 4 / Firecrawl
- Odoo 19 instance for testing
- Supabase for knowledge storage
### Optional Integrations
- OpenAI API for pattern extraction
- GitHub Actions for CI/CD integration
- Perplexity for research enhancement
### Python Dependencies
```python
beautifulsoup4
requests
firecrawl-py
supabase-py
openai
pyyaml
gitpython
```
## Implementation Patterns
### Forum Scraper
```python
# scrape_solved_threads.py
import requests
from bs4 import BeautifulSoup
from supabase import create_client
import time
from datetime import datetime
class OdooForumScraper:
def __init__(self, supabase_url, supabase_key):
self.base_url = "https://www.odoo.com/forum"
self.supabase = create_client(supabase_url, supabase_key)
self.session = requests.Session()
self.session.headers.update({
'User-Agent': 'OdooKnowledgeBot/1.0'
})
def scrape_solved_threads(self, pages=100):
"""
Scrape Odoo forum for solved threads
"""
threads = []
for page in range(1, pages + 1):
print(f"Scraping page {page}/{pages}...")
url = f"{self.base_url}/questions?filters=solved&page={page}"
response = self.session.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
# Extract thread links
thread_elements = soup.select('.o_wforum_question')
for thread in thread_elements:
thread_url = thread.find('a')['href']
if not thread_url.startswith('http'):
thread_url = f"https://www.odoo.com{thread_url}"
# Get thread details
thread_data = self.scrape_thread_details(thread_url)
if thread_data:
threads.append(thread_data)
self.store_thread(thread_data)
time.sleep(2) # Rate limiting
time.sleep(5) # Pause between pages
return threads
def scrape_thread_details(self, url):
"""
Extract question, accepted answer, and code snippets
"""
try:
response = self.session.get(url)
soup = BeautifulSoup(response.content, 'html.parser')
# Extract question
question_elem = soup.select_one('.o_wforum_question_text')
question = question_elem.text.strip() if question_elem else ""
# Extract title
title_elem = soup.select_one('h1.o_wforum_title')
title = title_elem.text.strip() if title_elem else ""
# Extract accepted answer
accepted_answer = soup.select_one('.o_wforum_answer.o_wforum_answer_correct')
if not accepted_answer:
return None # Skip if no accepted answer
answer_text = accepted_answer.select_one('.o_wforum_answer_text').text.strip()
# Extract code snippets
code_blocks = accepted_answer.select('pre code')
code_snippets = [block.text.strip() for block in code_blocks]
# Extract tags/category
tags = [tag.text.strip() for tag in soup.select('.o_wforum_tag')]
# Extract metadata
views_elem = soup.select_one('.o_wforum_views')
views = int(views_elem.text.strip()) if views_elem else 0
return {
'url': url,
'title': title,
'question': question,
'answer': answer_text,
'code_snippets': code_snippets,
'tags': tags,
'views': views,
'scraped_at': datetime.now().isoformat()
}
except Exception as e:
print(f"Error scraping {url}: {e}")
return None
def store_thread(self, thread_data):
"""
Store thread in Supabase knowledge base
"""
self.supabase.table('odoo_solved_threads').upsert({
'thread_url': thread_data['url'],
'title': thread_data['title'],
'question': thread_data['question'],
'answer': thread_data['answer'],
'code_snippets': thread_data['code_snippets'],
'tags': thread_data['tags'],
'views': thread_data['views'],
'scraped_at': thread_data['scraped_at']
}).execute()
def extract_error_patterns(self):
"""
Use AI to extract common error patterns
"""
# Fetch all threads
threads = self.supabase.table('odoo_solved_threads').select('*').execute()
# Group by error type using AI
from openai import OpenAI
client = OpenAI()
patterns = {}
for thread in threads.data:
# Ask GPT to categorize the error
response = client.chat.completions.create(
model="gpt-4",
messages=[
{"role": "system", "content": "You are an Odoo expert. Categorize this error and extract the fix pattern."},
{"role": "user", "content": f"Title: {thread['title']}\n\nQuestion: {thread['question']}\n\nAnswer: {thread['answer']}"}
]
)
pattern = response.choices[0].message.content
# Extract category
category = self.extract_category(pattern)
if category not in patterns:
patterns[category] = []
patterns[category].append({
'thread': thread,
'pattern': pattern
})
return patterns
def extract_category(self, pattern_text):
"""
Extract error category from AI analysis
"""
# Common Odoo error categories
categories = {
'manifest': ['__manifest__', 'module', 'dependency'],
'field': ['field', 'column', 'attribute'],
'view': ['view', 'xml', 'template'],
'security': ['access', 'rights', 'permission'],
'orm': ['orm', 'recordset', 'browse'],
'workflow': ['workflow', 'state', 'transition'],
'accounting': ['invoice', 'payment', 'journal'],
'inventory': ['stock', 'picking', 'quant']
}
pattern_lower = pattern_text.lower()
for category, keywords in categories.items():
if any(keyword in pattern_lower for keyword in keywords):
return category
return 'general'
# Usage
scraper = OdooForumScraper(
supabase_url=os.getenv('SUPABASE_URL'),
supabase_key=os.getenv('SUPABASE_KEY')
)
# Scrape 100 pages (~1,100 solved threads)
threads = scraper.scrape_solved_threads(pages=100)
# Extract patterns
patterns = scraper.extract_error_patterns()
print(f"Scraped {len(threads)} solved threads")
print(f"Identified {len(patterns)} error patterns")
```
### Guardrail Generator
```python
# generate_guardrails.py
import yaml
from pathlib import Path
class GuardrailGenerator:
def __init__(self, patterns):
self.patterns = patterns
self.guardrails_dir = Path('guardrails')
self.guardrails_dir.mkdir(exist_ok=True)
def generate_manifest_guardrail(self):
"""
GR-INSTALL-004: Manifest validation
"""
guardrail = {
'id': 'GR-INSTALL-004',
'name': 'Manifest Validation',
'description': 'Prevent module installation failures due to invalid __manifest__.py',
'category': 'manifest',
'severity': 'HIGH',
'checks': [
{
'name': 'Required Keys Present',
'pattern': r"'name':|\"name\":",
'error_message': "Missing required 'name' key in __manifest__.py",
'fix': 'Add name = "Module Name" to __manifest__.py'
},
{
'name': 'Valid Dependencies',
'pattern': r"'depends':\s*\[.*?\]",
'error_message': "Invalid or missing 'depends' list",
'fix': 'Ensure depends list contains only installed modules'
},
{
'name': 'Version Format',
'pattern': r"'version':\s*'\\d+\\.\\d+'",
'error_message': "Invalid version format (should be X.Y)",
'fix': 'Use semantic versioning: version = "1.0"'
}
],
'prevention_script': '''
def validate_manifest(manifest_path):
with open(manifest_path) as f:
manifest = eval(f.read())
required_keys = ['name', 'version', 'depends', 'data']
missing = [k for k in required_keys if k not in manifest]
if missing:
raise ValueError(f"Missing required keys: {missing}")
# Validate version format
version = manifest['version']
if not re.match(r'^\\d+\\.\\d+$', version):
raise ValueError(f"Invalid version format: {version}")
return True
''',
'auto_fix_script': '''
def fix_manifest(manifest_path):
with open(manifest_path) as f:
content = f.read()
# Add missing keys with defaults
if "'name'" not in content and '"name"' not in content:
content = "{'name': 'My Module',\n" + content
if "'version'" not in content:
content += "\\n'version': '1.0',"
if "'depends'" not in content:
content += "\\n'depends': ['base'],"
with open(manifest_path, 'w') as f:
f.write(content)
'''
}
output_path = self.guardrails_dir / 'GR-INSTALL-004.yaml'
with open(output_path, 'w') as f:
yaml.dump(guardrail, f, default_flow_style=False)
return guardrail
def generate_field_sync_guardrail(self):
"""
GR-POS-001: POS field synchronization
"""
guardrail = {
'id': 'GR-POS-001',
'name': 'POS Field Sync Prevention',
'description': 'Prevent POS order/line desync when adding custom fields',
'category': 'field',
'severity': 'CRITICAL',
'background': '''
Common Issue: Adding fields to pos.order.line but forgetting to add
export/import in pos.order causes data loss on session closure.
Affected Models:
- pos.order (parent)
- pos.order.line (child)
Root Cause: POS uses JSON export/import for order data persistence.
Custom fields not included in _export_for_ui() are silently dropped.
''',
'checks': [
{
'name': 'Check POS Line Fields',
'pattern': r'class PosOrderLine.*?:',
'error_message': 'Added field to pos.order.line without updating pos.order export',
'fix': 'Add field to _export_for_ui() in pos.order'
}
],
'prevention_script': '''
def check_pos_field_sync(module_path):
"""Validate POS field synchronization"""
pos_order_line_file = module_path / 'models' / 'pos_order_line.py'
Ver en GitHub