Powered by AppSignal & Oban Pro

Risk Analysis Dashboard

notebooks/risk_analysis.livemd

Risk Analysis Dashboard

Introduction

This notebook provides comprehensive risk analysis tools for project management, including:

  • Risk Matrix Visualization
  • Temporal Analysis
  • Category Distribution
  • Mitigation Effectiveness
  • Resource Allocation Analysis
  • Predictive Analytics

Setup and Data Loading

# Initialize required modules and libraries
alias Resolvinator.Notebooks.Setup
setup = Setup.setup()
import Explorer.DataFrame
alias Explorer.Series
alias Resolvinator.{Repo, Risks, Resources}
alias Resolvinator.Analytics.RiskAnalyzer

# Setup visualization tools
Kino.VegaLite.setup()

# Create interactive inputs for analysis parameters
date_range = Kino.Input.date_range("Date Range")
category_select = Kino.Input.select("Risk Category", ["All" | Risks.list_categories()])
impact_threshold = Kino.Input.number("Impact Threshold", default: 5)

Data Preparation and Validation

# Fetch and validate risk data
risks_with_associations =
  try do
    Risks.list_risks(includes: [:mitigations, :impacts, :requirements])
    |> Enum.map(fn risk ->
      %{
        id: risk.id,
        name: risk.name,
        category: risk.category,
        probability: risk.probability,
        impact: risk.impact,
        status: risk.status,
        description: risk.description,
        mitigation_count: length(risk.mitigations),
        total_impact_score: Enum.sum(Enum.map(risk.impacts, & &1.score)),
        resource_requirements: length(risk.requirements),
        created_at: risk.inserted_at,
        updated_at: risk.updated_at,
        days_open: Date.diff(Date.utc_today(), Date.from_iso8601!(risk.inserted_at))
      }
    end)
    |> DataFrame.new()
  rescue
    e in RuntimeError -> 
      IO.puts("Error loading risk data: #{inspect(e)}")
      DataFrame.new([])
  end

# Display interactive data table with filtering
Kino.DataTable.new(risks_with_associations, keys: [:id, :name, :category])

Risk Matrix Analysis

# Create Risk Matrix Heatmap
matrix_data = 
  risks_with_associations
  |> group_by([:probability, :impact])
  |> summarise(count: count())
  |> collect()

VegaLite.new(width: 400, height: 400)
|> VegaLite.data(matrix_data)
|> VegaLite.mark(:rect)
|> VegaLite.encode_field(:x, "probability", type: :ordinal, title: "Probability")
|> VegaLite.encode_field(:y, "impact", type: :ordinal, title: "Impact")
|> VegaLite.encode_field(:color, "count", 
    type: :quantitative,
    scale: [scheme: "reds"],
    title: "Number of Risks"
)
|> VegaLite.config(view: [stroke: nil])
|> VegaLite.properties(title: "Risk Matrix Heatmap")

Temporal Analysis

# Risk Creation Timeline with Impact Overlay
timeline_data = 
  risks_with_associations
  |> mutate(month: date_trunc(created_at, "month"))
  |> group_by(:month)
  |> summarise(
    new_risks: count(),
    avg_impact: mean(total_impact_score)
  )
  |> arrange(desc: :month)
  |> collect()

# Create dual-axis chart
VegaLite.new(width: 600, height: 300)
|> VegaLite.layers([
  VegaLite.new()
  |> VegaLite.mark(:line)
  |> VegaLite.encode_field(:x, "month", type: :temporal)
  |> VegaLite.encode_field(:y, "new_risks", type: :quantitative)
  |> VegaLite.encode_field(:color, value: "steelblue"),
  
  VegaLite.new()
  |> VegaLite.mark(:line, stroke: "red")
  |> VegaLite.encode_field(:x, "month", type: :temporal)
  |> VegaLite.encode_field(:y, "avg_impact", type: :quantitative)
])
|> VegaLite.properties(title: "Risk Creation and Impact Trends")

Category Analysis

# Category Distribution and Impact Analysis
category_analysis = 
  risks_with_associations
  |> group_by(:category)
  |> summarise(
    count: count(),
    avg_impact: mean(total_impact_score),
    avg_mitigations: mean(mitigation_count),
    avg_requirements: mean(resource_requirements)
  )
  |> arrange(desc: :count)
  |> collect()

VegaLite.new(width: 500, height: 300)
|> VegaLite.data(category_analysis)
|> VegaLite.mark(:bar)
|> VegaLite.encode_field(:x, "category", type: :nominal)
|> VegaLite.encode_field(:y, "count", type: :quantitative)
|> VegaLite.encode_field(:color, "avg_impact", type: :quantitative)
|> VegaLite.properties(title: "Risk Categories by Count and Impact")

Predictive Analysis

# Simple prediction model for risk escalation
high_risk_factors = 
  risks_with_associations
  |> filter(impact == "high" or probability == "high")
  |> summarise(
    avg_days_to_escalate: mean(days_open),
    avg_mitigations_needed: mean(mitigation_count),
    common_categories: mode(category)
  )
  |> collect()

# Display risk factors
Kino.DataTable.new(high_risk_factors)

Export Analysis

# Export analysis results
Form.new(
  [
    export_format: Kino.Input.select("Export Format", ["CSV", "JSON", "Excel"])
  ],
  submit: "Export Analysis"
)
|> Kino.render()
|> Kino.listen(fn %{data: %{export_format: format}} ->
  case format do
    "CSV" -> 
      risks_with_associations
      |> collect()
      |> DataFrame.write_csv("risk_analysis_export.csv")
    "JSON" ->
      risks_with_associations
      |> collect()
      |> Jason.encode!()
      |> File.write!("risk_analysis_export.json")
    "Excel" ->
      risks_with_associations
      |> collect()
      |> DataFrame.write_xlsx("risk_analysis_export.xlsx")
  end
end)