Initial commit

This commit is contained in:
gsinghpal
2026-02-22 01:22:18 -05:00
commit 5200d5baf0
2394 changed files with 386834 additions and 0 deletions

View File

@@ -0,0 +1,350 @@
# -*- coding: utf-8 -*-
# Copyright 2024-2026 Nexa Systems Inc.
# License OPL-1 (Odoo Proprietary License v1.0)
import json
import logging
from odoo import api, fields, models
from odoo.exceptions import UserError
_logger = logging.getLogger(__name__)
class FusionClientChatSession(models.Model):
_name = 'fusion.client.chat.session'
_description = 'Client Intelligence Chat Session'
_order = 'create_date desc'
name = fields.Char(string='Session Title', required=True,
default=lambda self: f'Chat - {fields.Date.today()}')
profile_id = fields.Many2one(
'fusion.client.profile', string='Client Profile',
ondelete='set null',
help='If set, chat is scoped to this specific client',
)
user_id = fields.Many2one(
'res.users', string='User', default=lambda self: self.env.user,
required=True,
)
message_ids = fields.One2many(
'fusion.client.chat.message', 'session_id', string='Messages',
)
state = fields.Selection([
('active', 'Active'),
('archived', 'Archived'),
], default='active', string='State')
# Input field for the form view
user_input = fields.Text(string='Your Question')
def action_send_message(self):
"""Process user message and generate AI response."""
self.ensure_one()
if not self.user_input or not self.user_input.strip():
return
question = self.user_input.strip()
# Create user message
self.env['fusion.client.chat.message'].create({
'session_id': self.id,
'role': 'user',
'content': question,
})
# Generate AI response
try:
response = self._generate_ai_response(question)
except Exception as e:
_logger.exception('AI chat error: %s', e)
response = f'Sorry, I encountered an error processing your question. Error: {str(e)}'
# Create assistant message
msg = self.env['fusion.client.chat.message'].create({
'session_id': self.id,
'role': 'assistant',
'content': response,
})
# Clear input
self.user_input = False
return {
'type': 'ir.actions.act_window',
'res_model': 'fusion.client.chat.session',
'view_mode': 'form',
'res_id': self.id,
'target': 'current',
}
def _generate_ai_response(self, question):
"""Generate an AI-powered response to the user question.
Uses OpenAI API to analyze the question, query relevant data,
and formulate a response.
"""
ICP = self.env['ir.config_parameter'].sudo()
api_key = ICP.get_param('fusion_claims.ai_api_key', '')
if not api_key:
return self._generate_local_response(question)
ai_model = ICP.get_param('fusion_claims.ai_model', 'gpt-4o-mini')
# Build context about available data
context_data = self._build_data_context(question)
# Build system prompt
system_prompt = self._build_system_prompt()
# Build messages
messages = [{'role': 'system', 'content': system_prompt}]
# Add conversation history (last 10 messages)
history = self.message_ids.sorted('create_date')[-10:]
for msg in history:
messages.append({'role': msg.role, 'content': msg.content})
# Add current question with data context
user_msg = question
if context_data:
user_msg += f'\n\n--- Retrieved Data ---\n{context_data}'
messages.append({'role': 'user', 'content': user_msg})
# Call OpenAI API
try:
import requests
response = requests.post(
'https://api.openai.com/v1/chat/completions',
headers={
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json',
},
json={
'model': ai_model,
'messages': messages,
'max_tokens': 2000,
'temperature': 0.3,
},
timeout=30,
)
response.raise_for_status()
result = response.json()
return result['choices'][0]['message']['content']
except ImportError:
return self._generate_local_response(question)
except Exception as e:
_logger.warning('OpenAI API error: %s', e)
return self._generate_local_response(question)
def _generate_local_response(self, question):
"""Generate a response without AI, using direct database queries.
This is the fallback when no API key is configured.
"""
question_lower = question.lower()
Profile = self.env['fusion.client.profile']
SaleOrder = self.env['sale.order']
# If scoped to a specific profile
if self.profile_id:
profile = self.profile_id
orders = SaleOrder.search([
('partner_id', '=', profile.partner_id.id),
('x_fc_sale_type', '!=', False),
]) if profile.partner_id else SaleOrder
lines = []
lines.append(f'**Client: {profile.display_name}**')
lines.append(f'- Health Card: {profile.health_card_number or "N/A"}')
lines.append(f'- Date of Birth: {profile.date_of_birth or "N/A"}')
lines.append(f'- City: {profile.city or "N/A"}')
lines.append(f'- Medical Condition: {profile.medical_condition or "N/A"}')
lines.append(f'- Mobility Status: {profile.mobility_status or "N/A"}')
lines.append(f'- Total Claims: {len(orders)}')
lines.append(f'- Total ADP Funded: ${profile.total_adp_funded:,.2f}')
lines.append(f'- Total Client Portion: ${profile.total_client_portion:,.2f}')
if orders:
lines.append('\n**Claims History:**')
for order in orders[:10]:
status = dict(order._fields['x_fc_adp_application_status'].selection).get(
order.x_fc_adp_application_status, order.x_fc_adp_application_status or 'N/A'
)
lines.append(
f'- {order.name}: {order.x_fc_sale_type or "N/A"} | '
f'Status: {status} | '
f'ADP: ${order.x_fc_adp_portion_total:,.2f} | '
f'Client: ${order.x_fc_client_portion_total:,.2f}'
)
apps = profile.application_data_ids[:5]
if apps:
lines.append('\n**Application History:**')
for app in apps:
lines.append(
f'- {app.application_date or "No date"}: '
f'{app.device_category or "N/A"} | '
f'Device: {app.base_device or "N/A"} | '
f'Reason: {app.reason_for_application or "N/A"}'
)
return '\n'.join(lines)
# Global queries
total_profiles = Profile.search_count([])
total_orders = SaleOrder.search_count([('x_fc_sale_type', '!=', False)])
if 'how many' in question_lower or 'count' in question_lower:
if 'client' in question_lower or 'profile' in question_lower:
return f'There are currently **{total_profiles}** client profiles in the system.'
if 'claim' in question_lower or 'order' in question_lower or 'case' in question_lower:
return f'There are currently **{total_orders}** claims/orders in the system.'
# Search for specific client
if 'find' in question_lower or 'search' in question_lower or 'show' in question_lower:
# Try to extract name from question
words = question.split()
profiles = Profile.search([], limit=20)
for word in words:
if len(word) > 2 and word[0].isupper():
found = Profile.search([
'|',
('first_name', 'ilike', word),
('last_name', 'ilike', word),
], limit=5)
if found:
profiles = found
break
if profiles:
lines = [f'Found **{len(profiles)}** matching profile(s):']
for p in profiles[:10]:
lines.append(
f'- **{p.display_name}** | HC: {p.health_card_number or "N/A"} | '
f'City: {p.city or "N/A"} | Claims: {p.claim_count}'
)
return '\n'.join(lines)
return (
f'I have access to **{total_profiles}** client profiles and **{total_orders}** claims. '
f'You can ask me questions like:\n'
f'- "How many clients are from Brampton?"\n'
f'- "Find client Raymond Wellesley"\n'
f'- "Show all clients with CVA diagnosis"\n\n'
f'For more intelligent responses, configure an OpenAI API key in '
f'Fusion Claims > Configuration > Settings.'
)
def _build_system_prompt(self):
"""Build the system prompt for the AI."""
profile_context = ''
if self.profile_id:
p = self.profile_id
profile_context = f"""
You are currently looking at a specific client profile:
- Name: {p.display_name}
- Health Card: {p.health_card_number or 'N/A'}
- DOB: {p.date_of_birth or 'N/A'}
- City: {p.city or 'N/A'}
- Medical Condition: {p.medical_condition or 'N/A'}
- Mobility Status: {p.mobility_status or 'N/A'}
- Total Claims: {p.claim_count}
- Total ADP Funded: ${p.total_adp_funded:,.2f}
- Total Client Portion: ${p.total_client_portion:,.2f}
"""
return f"""You are a helpful AI assistant for Fusion Claims, a healthcare equipment claims management system.
You help users find information about clients, their ADP (Assistive Devices Program) claims, funding history,
medical conditions, and devices.
Available data includes:
- Client profiles with personal info, health card numbers, addresses, medical conditions
- ADP application data parsed from XML submissions
- Sale orders with funding type, status, ADP/client portions
- Device information (wheelchairs, walkers, power bases, seating)
Funding types: ADP, ODSP, WSIB, March of Dimes, Muscular Dystrophy, Insurance, Hardship Funding, Rentals, Direct/Private
Client types: REG (75%/25%), ODS, OWP, ACS, LTC, SEN, CCA (100%/0%)
{profile_context}
Answer concisely and include specific data when available. Format monetary values with $ and commas."""
def _build_data_context(self, question):
"""Query relevant data based on the question to provide context to AI."""
question_lower = question.lower()
context_parts = []
Profile = self.env['fusion.client.profile']
SaleOrder = self.env['sale.order']
if self.profile_id:
# Scoped to specific client - load their data
p = self.profile_id
orders = SaleOrder.search([
('partner_id', '=', p.partner_id.id),
('x_fc_sale_type', '!=', False),
], limit=20) if p.partner_id else SaleOrder
if orders:
order_data = []
for o in orders:
order_data.append({
'name': o.name,
'sale_type': o.x_fc_sale_type,
'status': o.x_fc_adp_application_status,
'adp_total': o.x_fc_adp_portion_total,
'client_total': o.x_fc_client_portion_total,
'amount_total': o.amount_total,
'date': str(o.date_order) if o.date_order else '',
})
context_parts.append(f'Orders: {json.dumps(order_data)}')
apps = p.application_data_ids[:10]
if apps:
app_data = []
for a in apps:
app_data.append({
'date': str(a.application_date) if a.application_date else '',
'device_category': a.device_category,
'base_device': a.base_device or '',
'condition': a.medical_condition or '',
'reason': a.reason_for_application or '',
'authorizer': f'{a.authorizer_first_name} {a.authorizer_last_name}'.strip(),
})
context_parts.append(f'Applications: {json.dumps(app_data)}')
else:
# Global query - provide summary stats
total_profiles = Profile.search_count([])
total_orders = SaleOrder.search_count([('x_fc_sale_type', '!=', False)])
# City distribution
if 'city' in question_lower or 'cities' in question_lower or 'where' in question_lower:
city_data = SaleOrder.read_group(
[('x_fc_sale_type', '!=', False), ('partner_id.city', '!=', False)],
['partner_id'],
['partner_id'],
limit=20,
)
context_parts.append(f'Total profiles: {total_profiles}, Total orders: {total_orders}')
context_parts.append(f'Summary: {total_profiles} profiles, {total_orders} orders')
return '\n'.join(context_parts) if context_parts else ''
class FusionClientChatMessage(models.Model):
_name = 'fusion.client.chat.message'
_description = 'Chat Message'
_order = 'create_date asc'
session_id = fields.Many2one(
'fusion.client.chat.session', string='Session',
required=True, ondelete='cascade',
)
role = fields.Selection([
('user', 'User'),
('assistant', 'Assistant'),
], string='Role', required=True)
content = fields.Text(string='Content', required=True)
timestamp = fields.Datetime(
string='Timestamp', default=fields.Datetime.now,
)