Power BI Correlation Dashboard
Parent: Deny Event Correlation Report
Overview
This guide describes the Power BI data model and dashboard for visualizing deny event correlations across three sources: Purview CopilotInteraction, Purview DLP, and Application Insights RAI telemetry. The v2.0 data model connects to Dataverse tables (fsi_DenyEvent, fsi_DenyCorrelation, fsi_DenyAlert) as the primary data source. Note: Power BI connects via OData, which exposes these as the plural lowercase entity set names (e.g., fsi_denyevents) in queries and DAX references below.
Data Model Architecture
Dataverse Tables
erDiagram
fsi_denyevents {
uniqueidentifier fsi_deny_event_id PK
datetime fsi_event_timestamp
string fsi_agent_id
string fsi_session_id
string fsi_filter_reason
string fsi_filter_category
picklist fsi_filter_severity
picklist fsi_zone
string fsi_source_type
}
fsi_denycorrelations {
uniqueidentifier fsi_deny_correlation_id PK
datetime fsi_correlation_date
string fsi_agent_id
picklist fsi_zone
integer fsi_event_count
memo fsi_severity_distribution_json
memo fsi_trend_7day_json
}
fsi_denyalerts {
uniqueidentifier fsi_deny_alert_id PK
string fsi_alert_type
picklist fsi_alert_severity
string fsi_agent_id
picklist fsi_zone
datetime fsi_alert_timestamp
boolean fsi_acknowledged
}
fsi_denyevents ||--o{ fsi_denycorrelations : "grouped by agent+zone"
fsi_denycorrelations ||--o{ fsi_denyalerts : "triggers alerts"
Correlation Logic
The fsi_denycorrelations table provides pre-computed daily summaries grouped by:
- AgentId — Groups events per Copilot Studio agent
- Zone — Governance zone classification (Zone 1/2/3)
- Time window — Configurable correlation window (default 24 hours)
Data Refresh Configuration
Data Sources
| Source | Connection Type | Refresh Method |
|---|---|---|
| fsi_denyevents (Dataverse) | Dataverse connector | Daily scheduled |
| fsi_denycorrelations (Dataverse) | Dataverse connector | Daily scheduled |
| fsi_denyalerts (Dataverse) | Dataverse connector | Daily scheduled |
Recommended Refresh Schedule
| Zone | Refresh Frequency | Time |
|---|---|---|
| Zone 2 | Daily | 7:00 AM local |
| Zone 3 | Every 4 hours | 6 AM, 10 AM, 2 PM, 6 PM, 10 PM |
DAX Measures
Total Deny Events
Total Deny Events =
COUNTROWS(fsi_denyevents)
Deny Events by Category
Policy Block Count =
CALCULATE(
COUNTROWS(fsi_denyevents),
CONTAINSSTRING(fsi_denyevents[fsi_filter_category], "PolicyViolation")
)
XPIA Count =
CALCULATE(
COUNTROWS(fsi_denyevents),
CONTAINSSTRING(fsi_denyevents[fsi_filter_category], "XPIA")
)
Jailbreak Count =
CALCULATE(
COUNTROWS(fsi_denyevents),
CONTAINSSTRING(fsi_denyevents[fsi_filter_category], "Jailbreak")
)
Content Safety Count =
CALCULATE(
COUNTROWS(fsi_denyevents),
CONTAINSSTRING(fsi_denyevents[fsi_filter_category], "ContentSafety")
)
Events by Source Type
Purview Audit Count =
CALCULATE(
COUNTROWS(fsi_denyevents),
fsi_denyevents[fsi_source_type] = "PurviewAudit"
)
Purview DLP Count =
CALCULATE(
COUNTROWS(fsi_denyevents),
fsi_denyevents[fsi_source_type] = "PurviewDlp"
)
RAI Telemetry Count =
CALCULATE(
COUNTROWS(fsi_denyevents),
fsi_denyevents[fsi_source_type] = "RaiTelemetry"
)
Daily Trend
Daily Deny Trend =
CALCULATE(
[Total Deny Events],
DATESINPERIOD(
fsi_denyevents[fsi_event_timestamp],
MAX(fsi_denyevents[fsi_event_timestamp]),
-30,
DAY
)
)
High Severity Events
High Severity Events =
CALCULATE(
COUNTROWS(fsi_denyevents),
fsi_denyevents[fsi_filter_severity] = 4
) -- fsi_acv_severity value 4 = Failed
Active Alerts =
CALCULATE(
COUNTROWS(fsi_denyalerts),
fsi_denyalerts[fsi_acknowledged] = FALSE()
)
Dashboard Pages
Page 1: Executive Summary
| Visual | Data | Purpose |
|---|---|---|
| KPI Card | Total Deny Events (24h) | Quick health check |
| KPI Card | High Severity Events | Immediate attention items |
| KPI Card | Active Alerts | Unacknowledged alert count |
| Pie Chart | Events by Source Type | Distribution across PurviewAudit, PurviewDlp, RaiTelemetry |
| Line Chart | Daily Trend (30 days) | Pattern identification |
Page 2: Event Details
| Visual | Data | Purpose |
|---|---|---|
| Table | fsi_denyevents (sortable) |
Drill-down investigation |
| Bar Chart | Events by Agent (fsi_agent_id) |
Identify problematic agents |
| Bar Chart | Events by Zone | Zone-level distribution |
| Slicer | Date range, Source type, Severity, Zone | Filtering |
Page 3: Correlation Analysis
| Visual | Data | Purpose |
|---|---|---|
| Matrix | Agent × Zone event counts | Multi-zone correlation |
| Line Chart | 7-day trend per agent (from fsi_trend_7day_json) |
Trend direction |
| Table | fsi_denycorrelations summaries |
Daily correlation review |
Page 4: Alerts and Policy Effectiveness
| Visual | Data | Purpose |
|---|---|---|
| Table | fsi_denyalerts (active) |
Unacknowledged alerts |
| Bar Chart | Alert type distribution | Volume anomaly vs new agent vs zone critical |
| KPI Card | Alert acknowledgment rate | Operational responsiveness |
| Bar Chart | Events by filter category | Policy effectiveness overview |
Alerting Integration
Power BI Data Alerts
Configure alerts for Zone 3 environments:
- Pin KPI card to dashboard
- Set alert threshold (e.g., High Severity > 0)
- Configure email notification
Power Automate Integration
For 15-minute SLA response:
Trigger: When Power BI data alert triggers
Action 1: Post to Teams channel
Action 2: Create incident in ServiceNow/Jira
Action 3: Send email to Security Operations
Power Query Transformations
Deny Events Query (Dataverse)
let
Source = CommonDataService.Database("https://your-org.crm.dynamics.com"),
DenyEvents = Source{[Name="fsi_denyevents"]}[Data],
SelectedColumns = Table.SelectColumns(DenyEvents, {
"fsi_deny_event_id", "fsi_event_timestamp", "fsi_agent_id",
"fsi_session_id", "fsi_filter_reason", "fsi_filter_category",
"fsi_filter_severity", "fsi_zone", "fsi_source_type"
}),
RenamedColumns = Table.RenameColumns(SelectedColumns, {
{"fsi_event_timestamp", "Timestamp"},
{"fsi_agent_id", "AgentId"},
{"fsi_filter_reason", "DenyReason"},
{"fsi_filter_category", "Category"},
{"fsi_source_type", "SourceType"},
{"fsi_filter_severity", "Severity"},
{"fsi_zone", "Zone"}
}),
TypedColumns = Table.TransformColumnTypes(RenamedColumns, {
{"Timestamp", type datetimezone}
})
in
TypedColumns
Correlations Query (Dataverse)
let
Source = CommonDataService.Database("https://your-org.crm.dynamics.com"),
Correlations = Source{[Name="fsi_denycorrelations"]}[Data],
SelectedColumns = Table.SelectColumns(Correlations, {
"fsi_deny_correlation_id", "fsi_correlation_date", "fsi_agent_id",
"fsi_zone", "fsi_event_count", "fsi_severity_distribution_json",
"fsi_trend_7day_json"
}),
RenamedColumns = Table.RenameColumns(SelectedColumns, {
{"fsi_correlation_date", "CorrelationDate"},
{"fsi_agent_id", "AgentId"},
{"fsi_event_count", "EventCount"},
{"fsi_zone", "Zone"}
}),
// Parse trend JSON for 7-day direction
AddedTrendDirection = Table.AddColumn(RenamedColumns, "TrendDirection", each
try Json.Document([fsi_trend_7day_json])[Direction] otherwise "Unknown"
)
in
AddedTrendDirection
Alerts Query (Dataverse)
let
Source = CommonDataService.Database("https://your-org.crm.dynamics.com"),
Alerts = Source{[Name="fsi_denyalerts"]}[Data],
SelectedColumns = Table.SelectColumns(Alerts, {
"fsi_deny_alert_id", "fsi_alert_type", "fsi_alert_severity",
"fsi_agent_id", "fsi_zone", "fsi_alert_timestamp",
"fsi_alert_message", "fsi_acknowledged"
}),
RenamedColumns = Table.RenameColumns(SelectedColumns, {
{"fsi_alert_type", "AlertType"},
{"fsi_alert_severity", "Severity"},
{"fsi_agent_id", "AgentId"},
{"fsi_zone", "Zone"},
{"fsi_alert_timestamp", "AlertTimestamp"},
{"fsi_alert_message", "Message"},
{"fsi_acknowledged", "Acknowledged"}
})
in
RenamedColumns
Legacy Alternative: CSV Data Sources
If your organization has not yet deployed the Dataverse schema, you can use CSV file exports as an interim data source. The extraction scripts support file export via the -OutputPath and -OutputFormat parameters.
// Example: CSV-based deny events (legacy)
let
Source = Csv.Document(
File.Contents("C:\Reports\CopilotDenyEvents-2026-02-10.csv"),
[Delimiter=",", Encoding=65001, QuoteStyle=QuoteStyle.Csv]
),
PromotedHeaders = Table.PromoteHeaders(Source, [PromoteAllScalars=true])
in
PromotedHeaders
CSV Limitations
CSV data sources do not support incremental refresh, cross-table relationships, or automatic correlation summaries. Migrate to the Dataverse connector for the full DEC experience.
Template Deployment
Using the Power BI Template (.pbit)
- Download
DenyEventCorrelation.pbitfrom FSI-AgentGov-Solutions - Open in Power BI Desktop
- Configure Dataverse connection:
- Dataverse Environment URL:
https://your-org.crm.dynamics.com - Authenticate with organizational account (Entra ID)
- Refresh data
- Publish to Power BI Service
Data Source Configuration
| Setting | Value |
|---|---|
| Connector | Dataverse |
| Environment URL | https://your-org.crm.dynamics.com |
| Authentication | Organizational account (Entra ID) |
Security Considerations
Row-Level Security (RLS)
For multi-business unit deployments, implement RLS:
// RLS Role: BusinessUnitFilter
[BusinessUnit] = USERPRINCIPALNAME()
// Or use a mapping table for complex scenarios
Data Classification
The deny event data may contain:
- User identities (PII)
- Policy names (internal config)
- SIT match details (indicators of sensitive data)
Recommendation: Apply "Internal - Confidential" sensitivity label to the Power BI report.
Next Steps
- Deployment Guide - End-to-end deployment instructions
- Download template from FSI-AgentGov-Solutions
FSI Agent Governance Framework v1.2.51 - February 2026