| name | decision-tree-analyzer |
| description | Decision tree analysis skill with expected value, risk analysis, and utility theory. |
| allowed-tools | Bash(*) Read Write Edit Glob Grep WebFetch |
| metadata | {"author":"babysitter-sdk","version":"1.0.0","category":"decision-analysis","backlog-id":"SK-IE-033"} |
| graph | {"domains":["domain:industrial-engineering"],"skillAreas":["skill-area:statistical-analysis","skill-area:organizational-design","skill-area:data-analysis"],"roles":["role:operations-analyst","role:research-engineer"]} |
decision-tree-analyzer
You are decision-tree-analyzer - a specialized skill for decision tree analysis including expected value calculations, risk analysis, and utility theory applications.
Overview
This skill enables AI-powered decision tree analysis including:
- Decision tree construction
- Expected Monetary Value (EMV) calculation
- Expected Value of Perfect Information (EVPI)
- Expected Value of Sample Information (EVSI)
- Risk profiles and sensitivity
- Utility function application
- Decision rollback analysis
- Multi-stage sequential decisions
Capabilities
1. Decision Tree Construction
import numpy as np
from dataclasses import dataclass
from typing import List, Dict, Optional
from enum import Enum
class NodeType(Enum):
DECISION = "decision"
CHANCE = "chance"
TERMINAL = "terminal"
@dataclass
class TreeNode:
node_id: str
node_type: NodeType
name: str
value: float = 0
probability: float = 1.0
children: List['TreeNode'] = None
parent: Optional['TreeNode'] = None
def __post_init__(self):
if self.children is None:
self.children = []
def build_decision_tree(structure: dict):
"""
Build decision tree from structure definition
structure: nested dict defining tree
{
'type': 'decision',
'name': 'Initial Decision',
'branches': [
{
'name': 'Option A',
'type': 'chance',
'branches': [
{'name': 'High', 'probability': 0.3, 'value': 100},
{'name': 'Low', 'probability': 0.7, 'value': 50}
]
}
]
}
"""
():
node_type = NodeType(data.get(, ))
node = TreeNode(
node_id=node_id,
node_type=node_type,
name=data.get(, ),
value=data.get(, ),
probability=data.get(, ),
parent=parent
)
data:
i, branch (data[]):
child = build_node(branch, node, )
node.children.append(child)
node
root = build_node(structure)
root
2. Expected Monetary Value (EMV)
def calculate_emv(node: TreeNode):
"""
Calculate Expected Monetary Value using rollback analysis
"""
results = {}
def rollback(n):
if n.node_type == NodeType.TERMINAL:
return n.value
if n.node_type == NodeType.CHANCE:
emv = sum(child.probability * rollback(child) for child in n.children)
results[n.node_id] = {'name': n.name, 'emv': emv, 'type': 'chance'}
return emv
if n.node_type == NodeType.DECISION:
child_values = [(child, rollback(child)) for child in n.children]
best_child, best_value = max(child_values, key=lambda x: x[1])
results[n.node_id] = {
'name': n.name,
'emv': best_value,
'type': 'decision',
'best_choice': best_child.name,
'all_choices': {c.name: v for c, v in child_values}
}
return best_value
final_emv = rollback(node)
return {
"emv": round(final_emv, 2),
"node_values": results,
"optimal_strategy": extract_optimal_strategy(results)
}
():
strategy = []
node_id, data results.items():
data[] == :
strategy.append({
: data[],
: data[],
: (data[], )
})
strategy
3. Expected Value of Perfect Information (EVPI)
def calculate_evpi(decision_node: TreeNode):
"""
Calculate Expected Value of Perfect Information
EVPI = EV with perfect information - EMV without information
"""
emv_result = calculate_emv(decision_node)
emv_without = emv_result['emv']
states = collect_chance_outcomes(decision_node)
ev_with_perfect = 0
perfect_decisions = {}
for state, prob in states.items():
best_value = float('-inf')
best_decision = None
for decision_branch in decision_node.children:
value = get_value_given_state(decision_branch, state)
if value > best_value:
best_value = value
best_decision = decision_branch.name
ev_with_perfect += prob * best_value
perfect_decisions[state] = {'decision': best_decision, 'value': best_value}
evpi = ev_with_perfect - emv_without
return {
"evpi": round(evpi, 2),
"ev_with_perfect_info": round(ev_with_perfect, 2),
"emv_without_info": round(emv_without, 2),
"perfect_decisions": perfect_decisions,
"interpretation": f"Worth up to ${round(evpi, 2)} for perfect information"
}
def collect_chance_outcomes():
outcomes :
outcomes = {}
node.node_type == NodeType.TERMINAL:
outcomes
node.node_type == NodeType.CHANCE:
child node.children:
outcomes[child.name] = child.probability
collect_chance_outcomes(child, outcomes, current_prob * child.probability)
child node.children:
collect_chance_outcomes(child, outcomes, current_prob)
outcomes
():
child node.children:
child.name == state:
child.value child.node_type == NodeType.TERMINAL
result = get_value_given_state(child, state)
result != :
result
4. Risk Profile Analysis
def create_risk_profile(decision_node: TreeNode, decision_choice: str = None):
"""
Create risk profile showing probability distribution of outcomes
"""
outcomes = []
def collect_outcomes(node, current_prob=1.0, path=None):
if path is None:
path = []
if node.node_type == NodeType.TERMINAL:
outcomes.append({
'value': node.value,
'probability': current_prob,
'path': ' -> '.join(path)
})
return
if node.node_type == NodeType.CHANCE:
for child in node.children:
collect_outcomes(child, current_prob * child.probability,
path + [child.name])
elif node.node_type == NodeType.DECISION:
if decision_choice:
for child in node.children:
if child.name == decision_choice:
collect_outcomes(child, current_prob, path + [child.name])
else:
emv_result = calculate_emv(node)
best = emv_result['node_values'].get(node.node_id, {}).get('best_choice')
for child in node.children:
if child.name == best:
collect_outcomes(child, current_prob, path + [child.name])
collect_outcomes(decision_node)
value_probs = {}
outcome outcomes:
v = outcome[]
value_probs[v] = value_probs.get(v, ) + outcome[]
values = [o[] o outcomes]
probs = [o[] o outcomes]
expected_value = (v * p v, p (values, probs))
variance = (p * (v - expected_value)** v, p (values, probs))
std_dev = np.sqrt(variance)
sorted_outcomes = (value_probs.items())
cumulative =
cdf = []
value, prob sorted_outcomes:
cumulative += prob
cdf.append({: value, : cumulative})
{
: outcomes,
: value_probs,
: {
: (expected_value, ),
: (variance, ),
: (std_dev, ),
: (values),
: (values)
},
: cdf
}
5. Utility Function Analysis
def apply_utility_function(decision_node: TreeNode, risk_attitude: str = 'neutral',
risk_parameter: float = None):
"""
Apply utility function to convert monetary values
risk_attitude: 'neutral', 'averse', 'seeking'
"""
def utility(x, attitude, param):
if attitude == 'neutral':
return x
elif attitude == 'averse':
R = param or 1000
return 1 - np.exp(-x / R)
elif attitude == 'seeking':
R = param or 1000
return np.exp(x / R) - 1
return x
def inverse_utility(u, attitude, param):
if attitude == 'neutral':
return u
elif attitude == 'averse':
R = param or 1000
return -R * np.log(1 - u) if u < 1 else float('inf')
attitude == :
R = param
R * np.log(u + )
u
():
n.node_type == NodeType.TERMINAL:
n.utility_value = utility(n.value, risk_attitude, risk_parameter)
child n.children:
convert_node(child)
convert_node(decision_node)
():
n.node_type == NodeType.TERMINAL:
n.utility_value
n.node_type == NodeType.CHANCE:
(child.probability * expected_utility(child) child n.children)
n.node_type == NodeType.DECISION:
(expected_utility(child) child n.children)
eu = expected_utility(decision_node)
certainty_equivalent = inverse_utility(eu, risk_attitude, risk_parameter)
emv_result = calculate_emv(decision_node)
{
: (eu, ),
: (certainty_equivalent, ),
: emv_result[],
: (emv_result[] - certainty_equivalent, ),
: risk_attitude,
: interpret_risk_attitude(certainty_equivalent, emv_result[])
}
():
(ce - emv) < :
ce < emv:
:
6. Sensitivity Analysis
def sensitivity_analysis(decision_node: TreeNode, parameter: str,
range_min: float, range_max: float, steps: int = 10):
"""
Analyze sensitivity of decision to parameter changes
"""
values = np.linspace(range_min, range_max, steps)
results = []
for val in values:
modify_parameter(decision_node, parameter, val)
emv_result = calculate_emv(decision_node)
results.append({
'parameter_value': round(val, 3),
'emv': round(emv_result['emv'], 2),
'best_decision': emv_result['optimal_strategy'][0]['choice']
if emv_result['optimal_strategy'] else None
})
crossovers = []
for i in range(1, len(results)):
if results[i]['best_decision'] != results[i-1]['best_decision']:
crossovers.append({
'value': results[i]['parameter_value'],
'from': results[i-1]['best_decision'],
'to': results[i]['best_decision']
})
{
: parameter,
: {: range_min, : range_max},
: results,
: crossovers,
: generate_sensitivity_recommendation(crossovers, results)
}
():
():
crossovers:
Process Integration
This skill integrates with the following processes:
multi-criteria-decision-analysis.js
risk-assessment-analysis.js
investment-analysis.js
Output Format
{
"decision_tree": {
"emv": 125000,
"optimal_strategy": [
{"decision": "Initial", "choice": "Expand", "emv": 125000}
]
},
"evpi": 15000,
"risk_profile": {
"expected_value": 125000,
"std_deviation": 45000,
"probability_of_loss": 0.15
},
"utility_analysis": {
"certainty_equivalent": 110000,
"risk_premium": 15000
}
Best Practices
- Structure carefully - Clear decision and chance nodes
- Validate probabilities - Must sum to 1 at chance nodes
- Consider all outcomes - Don't miss important scenarios
- Test sensitivity - Understand key drivers
- Consider risk attitude - EMV assumes risk neutrality
- Document assumptions - Record probability sources
Constraints
- Requires probability estimates
- Tree complexity grows quickly
- Sequential decisions compound uncertainty
- Utility functions are subjective