From 8761d0e7c75df747276c9c941e0f2abc5e224cd5 Mon Sep 17 00:00:00 2001 From: Nexa Admin Date: Tue, 10 Mar 2026 02:45:51 +0000 Subject: [PATCH] feat: add Demographics & Analytics tool to Fusion Claims Intelligence - Add Tool 6 for demographic analysis using direct SQL queries - Age group breakdowns: clients, applications, avg apps/client, avg funding - Device popularity by age bracket (under 45, 45-60, 61-75, 75+) - City demographics with average age and funding per city - Benefit type analysis (ODSP, OWP, ACSD, Regular) - Top devices with average client age - Overall funding summary (totals, averages, age range) - Update AI topic and system prompt with Tool 6 routing examples --- fusion_claims/data/ai_agent_data.xml | 30 +++- fusion_claims/models/ai_agent_ext.py | 218 +++++++++++++++++++++++++++ 2 files changed, 245 insertions(+), 3 deletions(-) diff --git a/fusion_claims/data/ai_agent_data.xml b/fusion_claims/data/ai_agent_data.xml index 649d928..7d6a16a 100644 --- a/fusion_claims/data/ai_agent_data.xml +++ b/fusion_claims/data/ai_agent_data.xml @@ -69,26 +69,45 @@ ai['result'] = record._fc_tool_adp_billing_period(period) {"type": "object", "properties": {"period": {"type": "string", "description": "Which period to query: 'current' (default), 'previous', 'next', or a specific date in YYYY-MM-DD format"}}, "required": []} + + + Fusion: Demographics & Analytics + code + + + +ai['result'] = record._fc_tool_demographics(analysis_type, city_filter, sale_type_filter) + + Run demographic and analytical queries on client data. Returns age group breakdowns, device popularity by age, city demographics with average age and funding, benefit type analysis, top devices with average client age, and overall funding summaries. Use for questions like "average applications by age group", "what devices do clients over 75 use", "demographics by city", "how old are our clients on average". + {"type": "object", "properties": {"analysis_type": {"type": "string", "description": "Type of analysis: 'full' (all reports), 'age_groups' (clients/apps by age), 'devices_by_age' (device popularity per age bracket), 'city_demographics' (per-city stats with avg age), 'benefits' (benefit type breakdown), 'top_devices' (most popular devices with avg client age), 'funding_summary' (overall totals and averages)"}, "city_filter": {"type": "string", "description": "Optional: filter city demographics to a specific city"}, "sale_type_filter": {"type": "string", "description": "Optional: filter by sale type (adp, odsp, wsib, etc.)"}}, "required": []} + + Fusion Claims Client Intelligence - Query client profiles, ADP claims, funding history, billing periods, and device information. - You help users find information about ADP clients, claims, medical conditions, devices, funding history, and billing periods. Use the Fusion tools to query data. + Query client profiles, ADP claims, funding history, billing periods, demographics, and device information. + You help users find information about ADP clients, claims, medical conditions, devices, funding history, billing periods, and demographics. Use the Fusion tools to query data. Common questions and which tool to use: - "What is the status of [name]?" -> Use Client Status Lookup (Tool 4) - "What is the ADP billing for this period?" -> Use ADP Billing Period (Tool 5) - "Tell me about [name]'s funding history" -> Use Client Status Lookup first, then Client Details for more depth - "How many claims do we have?" -> Use Claims Statistics (Tool 3) -- "Find clients in [city]" -> Use Search Client Profiles (Tool 1) +- "Find clients in [city]" -> Use Search Client Profiles (Tool 1) +- "Average applications by age group" -> Use Demographics (Tool 6) with analysis_type="age_groups" +- "What devices do seniors use?" -> Use Demographics (Tool 6) with analysis_type="devices_by_age" +- "What is the average age of our clients?" -> Use Demographics (Tool 6) with analysis_type="funding_summary" +- "Show demographics for Brampton" -> Use Demographics (Tool 6) with analysis_type="city_demographics" and city_filter="Brampton" +- "How many ODSP clients do we have?" -> Use Demographics (Tool 6) with analysis_type="benefits" @@ -146,12 +165,17 @@ Capabilities: 3. Provide aggregated statistics about claims, funding types, and demographics 4. Look up a client's complete status by name -- including all orders, invoices, documents, and next steps 5. Query ADP billing period summaries -- total invoiced to ADP, paid vs unpaid, submission deadlines +6. Run demographic analytics -- age group breakdowns, device popularity by age, city demographics, benefit analysis, funding summaries How to handle common requests: - "What is the status of [name]?" -> Use the Client Status Lookup tool with the client's name - "What is the ADP billing this period?" -> Use the ADP Billing Period tool with period="current" - "What was the ADP billing last period?" -> Use the ADP Billing Period tool with period="previous" - "Show me [name]'s funding history" -> Use Client Status Lookup, then Client Details for full history +- "Average applications by age group" -> Use Demographics tool with analysis_type="age_groups" +- "What devices do clients over 75 use?" -> Use Demographics tool with analysis_type="devices_by_age" +- "What is the average age of our clients?" -> Use Demographics tool with analysis_type="funding_summary" +- "Demographics for [city]" -> Use Demographics tool with city_filter - If a client is not found by profile, the system also searches by contact/partner name Response guidelines: diff --git a/fusion_claims/models/ai_agent_ext.py b/fusion_claims/models/ai_agent_ext.py index 9c8990e..e72f056 100644 --- a/fusion_claims/models/ai_agent_ext.py +++ b/fusion_claims/models/ai_agent_ext.py @@ -50,6 +50,20 @@ STATUS_NEXT_STEPS = { 'expired': 'Contact client about reapplication if still needed', } +BASE_DEVICE_LABELS = { + 'adultWalkertype1': 'Adult Walker Type 1', + 'adultWalkertype2': 'Adult Walker Type 2', + 'adultWalkertype3': 'Adult Walker Type 3', + 'adultlightwtStdwheelchair': 'Lightweight Standard Wheelchair', + 'adultlightwtPermwheelchair': 'Lightweight Permanent Wheelchair', + 'adultTiltwheelchair': 'Adult Tilt Wheelchair', + 'adultStdwheelchair': 'Adult Standard Wheelchair', + 'adultHighperfwheelchair': 'Adult High Performance Wheelchair', + 'adultType2': 'Adult Power Type 2', + 'adultType3': 'Adult Power Type 3', + 'powerScooter': 'Power Scooter', +} + class AIAgentFusionClaims(models.Model): """Extend ai.agent with Fusion Claims tool methods.""" @@ -450,3 +464,207 @@ class AIAgentFusionClaims(models.Model): }, 'invoices': invoice_details, }) + + # ------------------------------------------------------------------ + # Tool 6: Demographics & Analytics + # ------------------------------------------------------------------ + def _fc_tool_demographics(self, analysis_type=None, city_filter=None, sale_type_filter=None): + """AI Tool: Run demographic and analytical queries on client data.""" + cr = self.env.cr + results = {} + + if not analysis_type: + analysis_type = 'full' + + if analysis_type in ('full', 'age_groups'): + cr.execute(""" + SELECT + CASE + WHEN age < 18 THEN 'Under 18' + WHEN age BETWEEN 18 AND 30 THEN '18-30' + WHEN age BETWEEN 31 AND 45 THEN '31-45' + WHEN age BETWEEN 46 AND 60 THEN '46-60' + WHEN age BETWEEN 61 AND 75 THEN '61-75' + ELSE '75+' + END AS age_group, + COUNT(DISTINCT p.id) AS clients, + COUNT(app.id) AS applications, + ROUND(COUNT(app.id)::numeric / NULLIF(COUNT(DISTINCT p.id), 0), 2) AS avg_applications, + COALESCE(ROUND(SUM(p.total_adp_funded) / NULLIF(COUNT(DISTINCT p.id), 0), 2), 0) AS avg_adp_funded, + COALESCE(ROUND(SUM(p.total_amount) / NULLIF(COUNT(DISTINCT p.id), 0), 2), 0) AS avg_total + FROM fusion_client_profile p + CROSS JOIN LATERAL ( + SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.date_of_birth))::int AS age + ) a + LEFT JOIN fusion_adp_application_data app ON app.profile_id = p.id + WHERE p.date_of_birth IS NOT NULL + GROUP BY age_group + ORDER BY MIN(age) + """) + rows = cr.fetchall() + results['age_groups'] = [ + { + 'age_group': r[0], 'clients': r[1], 'applications': r[2], + 'avg_applications': float(r[3]), 'avg_adp_funded': float(r[4]), + 'avg_total': float(r[5]), + } for r in rows + ] + + if analysis_type in ('full', 'devices_by_age'): + cr.execute(""" + SELECT + CASE + WHEN age < 45 THEN 'Under 45' + WHEN age BETWEEN 45 AND 60 THEN '45-60' + WHEN age BETWEEN 61 AND 75 THEN '61-75' + ELSE '75+' + END AS age_group, + app.base_device, + COUNT(*) AS count + FROM fusion_client_profile p + CROSS JOIN LATERAL ( + SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.date_of_birth))::int AS age + ) a + JOIN fusion_adp_application_data app ON app.profile_id = p.id + WHERE p.date_of_birth IS NOT NULL + AND app.base_device IS NOT NULL AND app.base_device != '' + GROUP BY age_group, app.base_device + ORDER BY age_group, count DESC + """) + rows = cr.fetchall() + devices_by_age = {} + for age_group, device, count in rows: + if age_group not in devices_by_age: + devices_by_age[age_group] = [] + label = BASE_DEVICE_LABELS.get(device, device) + devices_by_age[age_group].append({'device': label, 'count': count}) + results['devices_by_age'] = devices_by_age + + if analysis_type in ('full', 'city_demographics'): + city_clause = "" + params = [] + if city_filter: + city_clause = "AND LOWER(p.city) = LOWER(%s)" + params = [city_filter] + + cr.execute(f""" + SELECT + p.city, + COUNT(DISTINCT p.id) AS clients, + COUNT(app.id) AS applications, + ROUND(AVG(a.age), 1) AS avg_age, + COALESCE(ROUND(SUM(p.total_adp_funded) / NULLIF(COUNT(DISTINCT p.id), 0), 2), 0) AS avg_adp_funded + FROM fusion_client_profile p + CROSS JOIN LATERAL ( + SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.date_of_birth))::int AS age + ) a + LEFT JOIN fusion_adp_application_data app ON app.profile_id = p.id + WHERE p.city IS NOT NULL AND p.city != '' + AND p.date_of_birth IS NOT NULL + {city_clause} + GROUP BY p.city + ORDER BY clients DESC + LIMIT 15 + """, params) + rows = cr.fetchall() + results['city_demographics'] = [ + { + 'city': r[0], 'clients': r[1], 'applications': r[2], + 'avg_age': float(r[3]), 'avg_adp_funded': float(r[4]), + } for r in rows + ] + + if analysis_type in ('full', 'benefits'): + cr.execute(""" + SELECT + CASE + WHEN p.benefit_type = 'odsp' THEN 'ODSP' + WHEN p.benefit_type = 'owp' THEN 'Ontario Works' + WHEN p.benefit_type = 'acsd' THEN 'ACSD' + WHEN p.receives_social_assistance THEN 'Social Assistance (other)' + ELSE 'Regular (no assistance)' + END AS benefit_category, + COUNT(DISTINCT p.id) AS clients, + COUNT(app.id) AS applications, + ROUND(AVG(a.age), 1) AS avg_age, + COALESCE(ROUND(AVG(p.total_adp_funded), 2), 0) AS avg_adp_funded + FROM fusion_client_profile p + CROSS JOIN LATERAL ( + SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.date_of_birth))::int AS age + ) a + LEFT JOIN fusion_adp_application_data app ON app.profile_id = p.id + WHERE p.date_of_birth IS NOT NULL + GROUP BY benefit_category + ORDER BY clients DESC + """) + rows = cr.fetchall() + results['benefits_breakdown'] = [ + { + 'category': r[0], 'clients': r[1], 'applications': r[2], + 'avg_age': float(r[3]), 'avg_adp_funded': float(r[4]), + } for r in rows + ] + + if analysis_type in ('full', 'top_devices'): + cr.execute(""" + SELECT + app.base_device, + COUNT(*) AS count, + COUNT(DISTINCT app.profile_id) AS unique_clients, + ROUND(AVG(a.age), 1) AS avg_age + FROM fusion_adp_application_data app + JOIN fusion_client_profile p ON p.id = app.profile_id + CROSS JOIN LATERAL ( + SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.date_of_birth))::int AS age + ) a + WHERE app.base_device IS NOT NULL AND app.base_device != '' AND app.base_device != 'none' + AND p.date_of_birth IS NOT NULL + GROUP BY app.base_device + ORDER BY count DESC + LIMIT 15 + """) + rows = cr.fetchall() + results['top_devices'] = [ + { + 'device': BASE_DEVICE_LABELS.get(r[0], r[0]), + 'device_code': r[0], + 'applications': r[1], + 'unique_clients': r[2], + 'avg_client_age': float(r[3]), + } for r in rows + ] + + if analysis_type in ('full', 'funding_summary'): + cr.execute(""" + SELECT + COUNT(*) AS total_profiles, + ROUND(AVG(a.age), 1) AS avg_age, + ROUND(SUM(p.total_adp_funded), 2) AS total_adp_funded, + ROUND(SUM(p.total_client_portion), 2) AS total_client_portion, + ROUND(SUM(p.total_amount), 2) AS grand_total, + ROUND(AVG(p.total_adp_funded), 2) AS avg_adp_per_client, + ROUND(AVG(p.total_client_portion), 2) AS avg_client_per_client, + ROUND(AVG(p.claim_count), 2) AS avg_claims_per_client, + MIN(a.age) AS youngest, + MAX(a.age) AS oldest + FROM fusion_client_profile p + CROSS JOIN LATERAL ( + SELECT EXTRACT(YEAR FROM AGE(CURRENT_DATE, p.date_of_birth))::int AS age + ) a + WHERE p.date_of_birth IS NOT NULL + """) + r = cr.fetchone() + results['funding_summary'] = { + 'total_profiles': r[0], + 'avg_age': float(r[1]), + 'total_adp_funded': float(r[2]), + 'total_client_portion': float(r[3]), + 'grand_total': float(r[4]), + 'avg_adp_per_client': float(r[5]), + 'avg_client_per_client': float(r[6]), + 'avg_claims_per_client': float(r[7]), + 'youngest_client_age': r[8], + 'oldest_client_age': r[9], + } + + return json.dumps(results)