feat(agents): add 7 specialist agents with tools and system prompts
Agents (all following 6-step contract: _plan/_gather/_reason/_act/_report): - AccountingAgent: trial balance, chart of accounts, tax summary (HIPAA-locked) - CrmAgent: pipeline summary, lead/opportunity management, won/lost analysis - SalesAgent: sales orders, quotations, revenue by rep, expired quote detection - ProjectAgent: task tracking, blocked/overdue detection, timesheet logging - ElearningAgent: course completion, low-engagement flagging, next-course suggestion - ExpensesAgent: expense sheets, pending approvals, policy violations (HIPAA-locked) - EmployeesAgent: headcount, contracts, leaves, attendance, expired contract sweep (HIPAA-locked) Tools (one file per domain): - accounting_tools.py, crm_tools.py, sales_tools.py, project_tools.py - elearning_tools.py, expenses_tools.py, employees_tools.py System prompts: each agent has a domain-specific system.txt with rules and output format All agents implement handle_peer_request() and sweep() for proactive monitoring HIPAA-locked agents (accounting, expenses, employees) enforced via LLMRouter Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
91
agent_service/tools/employees_tools.py
Normal file
91
agent_service/tools/employees_tools.py
Normal file
@@ -0,0 +1,91 @@
|
||||
from __future__ import annotations
|
||||
import logging
|
||||
from ..tools.odoo_client import OdooClient
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class EmployeesTools:
|
||||
def __init__(self, odoo: OdooClient):
|
||||
self._o = odoo
|
||||
|
||||
async def get_employees(self, department_id: int = None, active: bool = True,
|
||||
limit: int = 100) -> list:
|
||||
domain = [('active', '=', active)]
|
||||
if department_id:
|
||||
domain.append(('department_id', '=', department_id))
|
||||
fields = ['name', 'department_id', 'job_id', 'job_title', 'work_email',
|
||||
'coach_id', 'parent_id', 'employee_type']
|
||||
return await self._o.search_read('hr.employee', domain, fields, limit=limit)
|
||||
|
||||
async def get_employee_profile(self, employee_id: int) -> dict:
|
||||
employees = await self._o.search_read(
|
||||
'hr.employee', [('id', '=', employee_id)],
|
||||
['name', 'department_id', 'job_id', 'job_title', 'work_email',
|
||||
'coach_id', 'parent_id', 'employee_type', 'study_field', 'study_school'],
|
||||
limit=1,
|
||||
)
|
||||
return employees[0] if employees else {}
|
||||
|
||||
async def get_leaves(self, employee_id: int = None, state: str = None,
|
||||
date_from: str = None, limit: int = 50) -> list:
|
||||
domain = []
|
||||
if employee_id:
|
||||
domain.append(('employee_id', '=', employee_id))
|
||||
if state:
|
||||
domain.append(('state', '=', state))
|
||||
if date_from:
|
||||
domain.append(('date_from', '>=', date_from))
|
||||
fields = ['name', 'employee_id', 'holiday_status_id', 'date_from',
|
||||
'date_to', 'number_of_days', 'state']
|
||||
return await self._o.search_read('hr.leave', domain, fields, limit=limit)
|
||||
|
||||
async def get_contracts(self, employee_id: int = None, state: str = 'open',
|
||||
limit: int = 50) -> list:
|
||||
domain = [('state', '=', state)]
|
||||
if employee_id:
|
||||
domain.append(('employee_id', '=', employee_id))
|
||||
fields = ['name', 'employee_id', 'wage', 'date_start', 'date_end',
|
||||
'state', 'structure_type_id']
|
||||
return await self._o.search_read('hr.contract', domain, fields, limit=limit)
|
||||
|
||||
async def get_attendance_summary(self, employee_id: int, date_from: str,
|
||||
date_to: str) -> dict:
|
||||
domain = [
|
||||
('employee_id', '=', employee_id),
|
||||
('check_in', '>=', date_from),
|
||||
('check_in', '<=', date_to),
|
||||
]
|
||||
records = await self._o.search_read('hr.attendance', domain, ['worked_hours', 'check_in'], limit=200)
|
||||
total_hours = sum(r.get('worked_hours', 0) for r in records)
|
||||
days_present = len(set(r['check_in'][:10] for r in records if r.get('check_in')))
|
||||
return {
|
||||
'employee_id': employee_id,
|
||||
'period_from': date_from,
|
||||
'period_to': date_to,
|
||||
'total_hours': round(total_hours, 2),
|
||||
'days_present': days_present,
|
||||
'attendance_records': len(records),
|
||||
}
|
||||
|
||||
async def get_department_summary(self, department_id: int) -> dict:
|
||||
employees = await self.get_employees(department_id=department_id)
|
||||
active_contracts = await self.get_contracts(state='open')
|
||||
dept_contracts = [c for c in active_contracts
|
||||
if any(e['id'] == (c['employee_id'][0] if isinstance(c['employee_id'], list) else c['employee_id'])
|
||||
for e in employees)]
|
||||
return {
|
||||
'department_id': department_id,
|
||||
'headcount': len(employees),
|
||||
'active_contracts': len(dept_contracts),
|
||||
'avg_wage': sum(c.get('wage', 0) for c in dept_contracts) / max(len(dept_contracts), 1),
|
||||
}
|
||||
|
||||
async def flag_for_review(self, model: str, record_id: int, reason: str, severity: str = 'medium') -> bool:
|
||||
msg = f'[AI FLAG - {severity.upper()}] {reason}'
|
||||
await self._o.call(model, 'message_post', [[record_id]], {'body': msg, 'message_type': 'comment'})
|
||||
return True
|
||||
|
||||
async def post_chatter_note(self, model: str, record_id: int, note: str) -> bool:
|
||||
await self._o.call(model, 'message_post', [[record_id]], {'body': note, 'message_type': 'comment'})
|
||||
return True
|
||||
Reference in New Issue
Block a user