import logging from datetime import datetime, timedelta from odoo import api, fields, models, _ _logger = logging.getLogger(__name__) class FlAnalysisWizard(models.TransientModel): """ Phase 7 — Full AI analysis trigger wizard. Checks for a recent analysis (<24 h) before running a new one unless force_reanalysis is True. Opens the resulting fl.analysis form on success. """ _name = 'fl.analysis.wizard' _description = 'Trigger AI Case Analysis' case_id = fields.Many2one( 'fl.case', string='Case', required=True, default=lambda self: self.env.context.get('active_id'), ) force_reanalysis = fields.Boolean( string='Force Re-analysis', help='Run a new analysis even if one was completed in the last 24 hours' ) # Computed info fields shown in the form recent_analysis_id = fields.Many2one( 'fl.analysis', string='Most Recent Analysis', compute='_compute_recent_analysis', ) recent_analysis_age_label = fields.Char( string='Last Analysis', compute='_compute_recent_analysis' ) has_recent = fields.Boolean(compute='_compute_recent_analysis') @api.depends('case_id') def _compute_recent_analysis(self): for rec in self: if not rec.case_id: rec.recent_analysis_id = False rec.recent_analysis_age_label = '' rec.has_recent = False continue latest = self.env['fl.analysis'].search( [('case_id', '=', rec.case_id.id), ('state', '=', 'complete')], order='create_date desc', limit=1, ) rec.recent_analysis_id = latest if latest: age = datetime.now() - latest.create_date hours = int(age.total_seconds() // 3600) minutes = int((age.total_seconds() % 3600) // 60) if hours == 0: rec.recent_analysis_age_label = f'{minutes} minute(s) ago' elif hours < 24: rec.recent_analysis_age_label = f'{hours} hour(s) ago' else: days = age.days rec.recent_analysis_age_label = f'{days} day(s) ago' rec.has_recent = age < timedelta(hours=24) else: rec.recent_analysis_age_label = 'No analysis yet' rec.has_recent = False def action_run_analysis(self): """ Run the AI analysis pipeline via fl.ai.engine. If a recent analysis exists and force_reanalysis is False, just open the existing one. """ self.ensure_one() if self.has_recent and not self.force_reanalysis: # Surface the existing analysis instead return { 'type': 'ir.actions.act_window', 'name': _('AI Analysis'), 'res_model': 'fl.analysis', 'res_id': self.recent_analysis_id.id, 'view_mode': 'form', 'target': 'new', } # Previous analyses remain in history — new one will appear first # (fl.analysis is ordered by create_date desc) # Post start note self.case_id.message_post( body='šŸ¤– AI analysis started by %s…' % self.env.user.name, subtype_xmlid='mail.mt_note', ) try: analysis = self.env['fl.ai.engine'].analyze_case(self.case_id.id) except Exception as e: _logger.error('Analysis wizard: engine error for case %s: %s', self.case_id.id, e) self.case_id.message_post( body=f'āŒ AI analysis failed: {e}', subtype_xmlid='mail.mt_note', ) return { 'type': 'ir.actions.act_window', 'name': _('Case'), 'res_model': 'fl.case', 'res_id': self.case_id.id, 'view_mode': 'form', 'target': 'current', } return { 'type': 'ir.actions.act_window', 'name': _('AI Analysis Result'), 'res_model': 'fl.analysis', 'res_id': analysis.id, 'view_mode': 'form', 'target': 'new', } def action_view_recent(self): """Open the most recent analysis form view.""" self.ensure_one() if not self.recent_analysis_id: return False return { 'type': 'ir.actions.act_window', 'name': _('AI Analysis'), 'res_model': 'fl.analysis', 'res_id': self.recent_analysis_id.id, 'view_mode': 'form', 'target': 'new', }