| 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
beautifulsoup4
requests
firecrawl-py
supabase-py
openai
pyyaml
gitpython
Implementation Patterns
Forum Scraper
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')
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}"
thread_data = self.scrape_thread_details(thread_url)
if thread_data:
threads.append(thread_data)
self.store_thread(thread_data)
time.sleep(2)
time.sleep(5)
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')
question_elem = soup.select_one('.o_wforum_question_text')
question = question_elem.text.strip() if question_elem else ""
title_elem = soup.select_one('h1.o_wforum_title')
title = title_elem.text.strip() if title_elem else ""
accepted_answer = soup.select_one('.o_wforum_answer.o_wforum_answer_correct')
if not accepted_answer:
return None
answer_text = accepted_answer.select_one('.o_wforum_answer_text').text.strip()
code_blocks = accepted_answer.select('pre code')
code_snippets = [block.text.strip() for block in code_blocks]
tags = [tag.text.strip() for tag in soup.select('.o_wforum_tag')]
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
"""
threads = self.supabase.table('odoo_solved_threads').select('*').execute()
from openai import OpenAI
client = OpenAI()
patterns = {}
for thread in threads.data:
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
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
"""
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'
scraper = OdooForumScraper(
supabase_url=os.getenv('SUPABASE_URL'),
supabase_key=os.getenv('SUPABASE_KEY')
)
threads = scraper.scrape_solved_threads(pages=100)
patterns = scraper.extract_error_patterns()
print(f"Scraped {len(threads)} solved threads")
print(f"Identified {len(patterns)} error patterns")
Guardrail Generator
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'