| name | enterprise-risk-aggregator |
| description | Aggregate and analyze risks across construction project portfolio. Identify correlated risks, systemic exposures, and portfolio-level risk mitigation strategies. |
| homepage | https://datadrivenconstruction.io |
| metadata | {"openclaw":{"emoji":"๐","os":["darwin","linux","win32"],"homepage":"https://datadrivenconstruction.io","requires":{"bins":["python3"]}}} |
Enterprise Risk Aggregator
Overview
Aggregate individual project risks into a portfolio-level view. Identify correlated risks across projects, calculate enterprise risk exposure, and develop portfolio-wide mitigation strategies.
Risk Aggregation Framework
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ ENTERPRISE RISK AGGREGATION โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ PROJECT RISKS CORRELATION PORTFOLIO VIEW โ
โ โโโโโโโโโโโโโ โโโโโโโโโโโ โโโโโโโโโโโโโโ โ
โ โ
โ Project A: Market risks โโ Total Exposure: โ
โ โข Material cost โ affect all $45M โ
โ โข Labor shortage projects โโโโโโโโโโโโโโโ โ
โ โ Risk Categories:โ
โ Project B: Weather impacts โข Market: 35% โ
โ โข Weather delay multiple sites โข Schedule: 25% โ
โ โข Permit issue โ โข Safety: 15% โ
โ Supply chain โข Regulatory:15%โ
โ Project C: affects โข Technical:10% โ
โ โข Subcontractor โ entire region โโโโโโโโโโโโโโโ โ
โ โข Design change Top 5 Risks: โ
โ 1. Steel prices โ
โ 2. Labor market โ
โ 3. Supply chain โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Technical Implementation
from dataclasses import dataclass, field
from typing import List, Dict, Optional, Tuple, Set
from datetime import datetime, timedelta
from enum import Enum
import statistics
import math
class RiskCategory(Enum):
MARKET = "market"
SCHEDULE = "schedule"
SAFETY = "safety"
REGULATORY = "regulatory"
TECHNICAL = "technical"
FINANCIAL = "financial"
ENVIRONMENTAL = "environmental"
SUPPLY_CHAIN = "supply_chain"
LABOR = "labor"
WEATHER = "weather"
class RiskLevel(Enum):
LOW = 1
MEDIUM = 2
HIGH = 3
CRITICAL = 4
class CorrelationType(Enum):
POSITIVE = "positive"
NEGATIVE = "negative"
INDEPENDENT = "independent"
@dataclass
class ProjectRisk:
id: str
project_id: str
project_name: str
category: RiskCategory
description: str
probability: float
impact: float
score: float = 0.0
level: RiskLevel = RiskLevel.MEDIUM
status: str = "open"
mitigation: str = ""
triggers: List[str] = field(default_factory=list)
def __post_init__(self):
self.score = self.probability * self.impact
if self.score > 5000000:
self.level = RiskLevel.CRITICAL
elif self.score > 1000000:
self.level = RiskLevel.HIGH
elif self.score > 250000:
self.level = RiskLevel.MEDIUM
else:
self.level = RiskLevel.LOW
@dataclass
class RiskCorrelation:
risk1_id: str
risk2_id: str
correlation_type: CorrelationType
strength: float
shared_triggers: List[str]
notes: str = ""
@dataclass
class AggregatedRisk:
category: RiskCategory
total_exposure: float
expected_loss: float
worst_case: float
risk_count: int
projects_affected: int
mitigation_cost: float
residual_exposure: float
@dataclass
class PortfolioRiskProfile:
report_date: datetime
total_projects: int
total_risks: int
total_exposure: float
expected_loss: float
var_95: float
by_category: Dict[str, AggregatedRisk]
top_risks: List[ProjectRisk]
correlations: List[RiskCorrelation]
systemic_risks: List[str]
class EnterpriseRiskAggregator:
"""Aggregate risks across project portfolio."""
SYSTEMIC_TRIGGERS = [
"steel_price_increase",
"labor_shortage",
"supply_chain_disruption",
"interest_rate_change",
"regulatory_change",
"weather_event",
"economic_downturn",
"pandemic",
"trade_restrictions"
]
def __init__(self, portfolio_name: str):
self.portfolio_name = portfolio_name
self.risks: Dict[str, ProjectRisk] = {}
self.correlations: List[RiskCorrelation] = []
self.projects: Set[str] = set()
def add_risk(self, project_id: str, project_name: str,
category: RiskCategory, description: str,
probability: float, impact: float,
triggers: List[str] = None,
mitigation: str = "") -> ProjectRisk:
"""Add project risk to portfolio."""
risk_id = f"RISK-{project_id}-{len(self.risks)+1:04d}"
risk = ProjectRisk(
id=risk_id,
project_id=project_id,
project_name=project_name,
category=category,
description=description,
probability=probability,
impact=impact,
triggers=triggers or [],
mitigation=mitigation
)
self.risks[risk_id] = risk
self.projects.add(project_id)
return risk
def import_project_risks(self, project_id: str, project_name: str,
risks: List[Dict]) -> int:
"""Import risks from project risk register."""
count = 0
for r in risks:
self.add_risk(
project_id=project_id,
project_name=project_name,
category=RiskCategory(r['category']),
description=r['description'],
probability=r['probability'],
impact=r['impact'],
triggers=r.get('triggers', []),
mitigation=r.get('mitigation', '')
)
count += 1
return count
def detect_correlations(self) -> List[RiskCorrelation]:
"""Automatically detect correlated risks."""
self.correlations = []
risks = list(self.risks.values())
for i, risk1 in enumerate(risks):
for risk2 in risks[i+1:]:
shared = set(risk1.triggers) & set(risk2.triggers)
if shared:
total_triggers = len(set(risk1.triggers) | set(risk2.triggers))
strength = len(shared) / total_triggers if total_triggers > 0 else 0
correlation = RiskCorrelation(
risk1_id=risk1.id,
risk2_id=risk2.id,
correlation_type=CorrelationType.POSITIVE,
strength=strength,
shared_triggers=list(shared)
)
self.correlations.append(correlation)
elif (risk1.category == risk2.category and
risk1.project_id != risk2.project_id):
correlation = RiskCorrelation(
risk1_id=risk1.id,
risk2_id=risk2.id,
correlation_type=CorrelationType.POSITIVE,
strength=0.3,
shared_triggers=[],
notes=f"Same category: {risk1.category.value}"
)
self.correlations.append(correlation)
return self.correlations
def identify_systemic_risks(self) -> List[Dict]:
"""Identify systemic risks affecting multiple projects."""
systemic = []
trigger_count: Dict[str, Set[str]] = {}
for risk in self.risks.values():
for trigger in risk.triggers:
if trigger not in trigger_count:
trigger_count[trigger] = set()
trigger_count[trigger].add(risk.project_id)
for trigger, projects in trigger_count.items():
if len(projects) > 1:
affected_risks = [r for r in self.risks.values()
if trigger in r.triggers]
total_exposure = sum(r.score for r in affected_risks)
systemic.append({
"trigger": trigger,
"projects_affected": len(projects),
"risks_affected": len(affected_risks),
"total_exposure": total_exposure,
"is_systemic": trigger in self.SYSTEMIC_TRIGGERS
})
return sorted(systemic, key=lambda x: -x['total_exposure'])
def aggregate_by_category(self) -> Dict[RiskCategory, AggregatedRisk]:
"""Aggregate risks by category."""
by_category = {}
for category in RiskCategory:
cat_risks = [r for r in self.risks.values() if r.category == category]
if not cat_risks:
continue
projects = set(r.project_id for r in cat_risks)
total_exposure = sum(r.impact for r in cat_risks)
expected_loss = sum(r.score for r in cat_risks)
worst_case = total_exposure
by_category[category] = AggregatedRisk(
category=category,
total_exposure=total_exposure,
expected_loss=expected_loss,
worst_case=worst_case,
risk_count=len(cat_risks),
projects_affected=len(projects),
mitigation_cost=0,
residual_exposure=expected_loss
)
return by_category
def calculate_var(self, confidence: float = 0.95,
simulations: int = 10000) -> float:
"""Calculate Value at Risk using Monte Carlo simulation."""
import random
losses = []
risks = list(self.risks.values())
for _ in range(simulations):
sim_loss = 0
for risk in risks:
if random.random() < risk.probability:
sim_loss += risk.impact
losses.append(sim_loss)
losses.sort()
var_index = int(simulations * confidence)
return losses[var_index]
def generate_portfolio_profile(self) -> PortfolioRiskProfile:
"""Generate comprehensive portfolio risk profile."""
if not self.correlations:
self.detect_correlations()
total_exposure = sum(r.impact for r in self.risks.values())
expected_loss = sum(r.score for r in self.risks.values())
by_category = self.aggregate_by_category()
top_risks = sorted(self.risks.values(), key=lambda x: -x.score)[:10]
systemic = self.identify_systemic_risks()
systemic_triggers = [s['trigger'] for s in systemic if s['is_systemic']]
var_95 = self.calculate_var(0.95)
return PortfolioRiskProfile(
report_date=datetime.now(),
total_projects=len(self.projects),
total_risks=len(self.risks),
total_exposure=total_exposure,
expected_loss=expected_loss,
var_95=var_95,
by_category={k.value: v for k, v in by_category.items()},
top_risks=top_risks,
correlations=self.correlations,
systemic_risks=systemic_triggers
)
def suggest_mitigation_priorities(self) -> List[Dict]:
"""Suggest prioritized mitigation actions."""
priorities = []
systemic = self.identify_systemic_risks()
for s in systemic[:5]:
if s['is_systemic']:
priorities.append({
"priority": 1,
"type": "systemic",
"target": s['trigger'],
"exposure": s['total_exposure'],
"projects": s['projects_affected'],
"recommendation": f"Portfolio-wide mitigation for {s['trigger']}"
})
high_corr = [c for c in self.correlations if c.strength > 0.5]
for corr in high_corr[:5]:
r1 = self.risks.get(corr.risk1_id)
r2 = self.risks.get(corr.risk2_id)
if r1 and r2:
priorities.append({
"priority": 2,
"type": "correlated",
"target": f"{r1.description[:30]} / {r2.description[:30]}",
"exposure": r1.score + r2.score,
"projects": 2,
"recommendation": f"Joint mitigation via {corr.shared_triggers}"
})
top_risks = sorted(self.risks.values(), key=lambda x: -x.score)[:10]
for risk in top_risks:
if not any(p['target'].startswith(risk.description[:20]) for p in priorities):
priorities.append({
"priority": 3,
"type": "individual",
"target": risk.description[:50],
"exposure": risk.score,
"projects": 1,
"recommendation": risk.mitigation or "Develop mitigation plan"
})
return sorted(priorities, key=lambda x: (x['priority'], -x['exposure']))
def generate_report(self) -> str:
"""Generate enterprise risk report."""
profile = self.generate_portfolio_profile()
lines = [
"# Enterprise Risk Aggregation Report",
"",
f"**Portfolio:** {self.portfolio_name}",
f"**Report Date:** {profile.report_date.strftime('%Y-%m-%d')}",
"",
"## Executive Summary",
"",
f"| Metric | Value |",
f"|--------|-------|",
f"| Total Projects | {profile.total_projects} |",
f"| Total Risks | {profile.total_risks} |",
f"| Total Exposure | ${profile.total_exposure:,.0f} |",
f"| Expected Loss | ${profile.expected_loss:,.0f} |",
f"| VaR (95%) | ${profile.var_95:,.0f} |",
"",
"## Risk Distribution by Category",
"",
"| Category | Risks | Projects | Expected Loss | % of Total |",
"|----------|-------|----------|---------------|------------|"
]
for cat, agg in profile.by_category.items():
pct = (agg.expected_loss / profile.expected_loss * 100) if profile.expected_loss > 0 else 0
lines.append(
f"| {cat} | {agg.risk_count} | {agg.projects_affected} | "
f"${agg.expected_loss:,.0f} | {pct:.1f}% |"
)
if profile.systemic_risks:
lines.extend([
"",
"## Systemic Risks (Portfolio-Wide)",
""
])
for trigger in profile.systemic_risks[:5]:
lines.append(f"- **{trigger}**")
lines.extend([
"",
"## Top 10 Individual Risks",
"",
"| Project | Risk | Prob | Impact | Score |",
"|---------|------|------|--------|-------|"
])
for risk in profile.top_risks:
lines.append(
f"| {risk.project_name} | {risk.description[:30]} | "
f"{risk.probability:.0%} | ${risk.impact:,.0f} | ${risk.score:,.0f} |"
)
high_corr = [c for c in profile.correlations if c.strength > 0.3]
if high_corr:
lines.extend([
"",
f"## Risk Correlations ({len(high_corr)} significant)",
"",
"| Strength | Shared Triggers |",
"|----------|-----------------|"
])
for c in high_corr[:10]:
lines.append(
f"| {c.strength:.0%} | {', '.join(c.shared_triggers[:3])} |"
)
return "\n".join(lines)
Quick Start
aggregator = EnterpriseRiskAggregator("Regional Portfolio")
aggregator.add_risk(
"PRJ-A", "Downtown Tower",
RiskCategory.MARKET, "Steel price increase",
probability=0.7, impact=2000000,
triggers=["steel_price_increase", "trade_restrictions"]
)
aggregator.add_risk(
"PRJ-A", "Downtown Tower",
RiskCategory.LABOR, "Skilled labor shortage",
probability=0.5, impact=1500000,
triggers=["labor_shortage"]
)
aggregator.add_risk(
"PRJ-B", "Hospital Wing",
RiskCategory.MARKET, "Material cost escalation",
probability=0.6, impact=1800000,
triggers=["steel_price_increase", "supply_chain_disruption"]
)
aggregator.add_risk(
"PRJ-B", "Hospital Wing",
RiskCategory.SCHEDULE, "Weather delays",
probability=0.4, impact=500000,
triggers=["weather_event"]
)
correlations = aggregator.detect_correlations()
print(f"Found {len(correlations)} correlated risk pairs")
systemic = aggregator.identify_systemic_risks()
for s in systemic[:3]:
print(f"Systemic: {s['trigger']} affects {s['projects_affected']} projects")
profile = aggregator.generate_portfolio_profile()
print(f"Total Exposure: ${profile.total_exposure:,.0f}")
print(f"VaR (95%): ${profile.var_95:,.0f}")
priorities = aggregator.suggest_mitigation_priorities()
for p in priorities[:5]:
print(f"Priority {p['priority']}: {p['recommendation']}")
print(aggregator.generate_report())
Requirements
pip install (no external dependencies)