Files
claude-skills-reference/c-level-advisor/coo-advisor/scripts/okr_tracker.py
Alireza Rezvani 466aa13a7b feat: C-Suite expansion — 8 new executive advisory roles (2→10) (#264)
* feat: C-Suite expansion — 8 new executive advisory roles

Add COO, CPO, CMO, CFO, CRO, CISO, CHRO advisors and Executive Mentor.
Expands C-level advisory from 2 to 10 roles with 74 total files.

Each role includes:
- SKILL.md (lean, <5KB, ~1200 tokens for context efficiency)
- Reference docs (loaded on demand, not at startup)
- Python analysis scripts (stdlib only, runnable CLI)

Executive Mentor features /em: slash commands (challenge, board-prep,
hard-call, stress-test, postmortem) with devil's advocate agent.

21 Python tools, 24 reference frameworks, 28,379 total lines.
All SKILL.md files combined: ~17K tokens (8.5% of 200K context window).

Badge: 88 → 116 skills

* feat: C-Suite orchestration layer + 18 complementary skills

ORCHESTRATION (new):
- cs-onboard: Founder interview → company-context.md
- chief-of-staff: Routing, synthesis, inter-agent orchestration
- board-meeting: 6-phase multi-agent deliberation protocol
- decision-logger: Two-layer memory (raw transcripts + approved decisions)
- agent-protocol: Inter-agent invocation with loop prevention
- context-engine: Company context loading + anonymization

CROSS-CUTTING CAPABILITIES (new):
- board-deck-builder: Board/investor update assembly
- scenario-war-room: Cascading multi-variable what-if modeling
- competitive-intel: Systematic competitor tracking + battlecards
- org-health-diagnostic: Cross-functional health scoring (8 dimensions)
- ma-playbook: M&A strategy (acquiring + being acquired)
- intl-expansion: International market entry frameworks

CULTURE & COLLABORATION (new):
- culture-architect: Values → behaviors, culture code, health assessment
- company-os: EOS/Scaling Up operating system selection + implementation
- founder-coach: Founder development, delegation, blind spots
- strategic-alignment: Strategy cascade, silo detection, alignment scoring
- change-management: ADKAR-based change rollout framework
- internal-narrative: One story across employees/investors/customers

UPGRADES TO EXISTING ROLES:
- All 10 roles get reasoning technique directives
- All 10 roles get company-context.md integration
- All 10 roles get board meeting isolation rules
- CEO gets stage-adaptive temporal horizons (seed→C)

Key design decisions:
- Two-layer memory prevents hallucinated consensus from rejected ideas
- Phase 2 isolation: agents think independently before cross-examination
- Executive Mentor (The Critic) sees all perspectives, others don't
- 25 Python tools total (stdlib only, no dependencies)

52 new files, 10 modified, 10,862 new lines.
Total C-suite ecosystem: 134 files, 39,131 lines.

* fix: connect all dots — Chief of Staff routes to all 28 skills

- Added complementary skills registry to routing-matrix.md
- Chief of Staff SKILL.md now lists all 28 skills in ecosystem
- Added integration tables to scenario-war-room and competitive-intel
- Badge: 116 → 134 skills
- README: C-Level Advisory count 10 → 28

Quality audit passed:
 All 10 roles: company-context, reasoning, isolation, invocation
 All 6 phases in board meeting
 Two-layer memory with DO_NOT_RESURFACE
 Loop prevention (no self-invoke, max depth 2, no circular)
 All /em: commands present
 All complementary skills cross-reference roles
 Chief of Staff routes to every skill in ecosystem

* refactor: CEO + CTO advisors upgraded to C-suite parity

Both roles now match the structural standard of all new roles:
- CEO: 11.7KB → 6.8KB SKILL.md (heavy content stays in references)
- CTO: 10KB → 7.2KB SKILL.md (heavy content stays in references)

Added to both:
- Integration table (who they work with and when)
- Key diagnostic questions
- Structured metrics dashboard table
- Consistent section ordering (Keywords → Quick Start → Responsibilities → Questions → Metrics → Red Flags → Integration → Reasoning → Context)

CEO additions:
- Stage-adaptive temporal horizons (seed=3m/6m/12m → B+=1y/3y/5y)
- Cross-references to culture-architect and board-deck-builder

CTO additions:
- Key Questions section (7 diagnostic questions)
- Structured metrics table (DORA + debt + team + architecture + cost)
- Cross-references to all peer roles

All 10 roles now pass structural parity:  Keywords  QuickStart  Questions  Metrics  RedFlags  Integration

* feat: add proactive triggers + output artifacts to all 10 roles

Every C-suite role now specifies:
- Proactive Triggers: 'surface these without being asked' — context-driven
  early warnings that make advisors proactive, not reactive
- Output Artifacts: concrete deliverables per request type (what you ask →
  what you get)

CEO: runway alerts, board prep triggers, strategy review nudges
CTO: deploy frequency monitoring, tech debt thresholds, bus factor flags
COO: blocker detection, scaling threshold warnings, cadence gaps
CPO: retention curve monitoring, portfolio dog detection, research gaps
CMO: CAC trend monitoring, positioning gaps, budget staleness
CFO: runway forecasting, burn multiple alerts, scenario planning gaps
CRO: NRR monitoring, pipeline coverage, pricing review triggers
CISO: audit overdue alerts, compliance gaps, vendor risk
CHRO: retention risk, comp band gaps, org scaling thresholds
Executive Mentor: board prep triggers, groupthink detection, hard call surfacing

This transforms the C-suite from reactive advisors into proactive partners.

* feat: User Communication Standard — structured output for all roles

Defines 3 output formats in agent-protocol/SKILL.md:

1. Standard Output: Bottom Line → What → Why → How to Act → Risks → Your Decision
2. Proactive Alert: What I Noticed → Why It Matters → Action → Urgency (🔴🟡)
3. Board Meeting: Decision Required → Perspectives → Agree/Disagree → Critic → Action Items

10 non-negotiable rules:
- Bottom line first, always
- Results and decisions only (no process narration)
- What + Why + How for every finding
- Actions have owners and deadlines ('we should consider' is banned)
- Decisions framed as options with trade-offs
- Founder is the highest authority — roles recommend, founder decides
- Risks are concrete (if X → Y, costs $Z)
- Max 5 bullets per section
- No jargon without explanation
- Silence over fabricated updates

All 10 roles reference this standard.
Chief of Staff enforces it as a quality gate.
Board meeting Phase 4 uses the Board Meeting Output format.

* feat: Internal Quality Loop — verification before delivery

No role presents to the founder without passing verification:

Step 1: Self-Verification (every role, every time)
  - Source attribution: where did each data point come from?
  - Assumption audit: [VERIFIED] vs [ASSUMED] tags on every finding
  - Confidence scoring: 🟢 high / 🟡 medium / 🔴 low per finding
  - Contradiction check against company-context + decision log
  - 'So what?' test: every finding needs a business consequence

Step 2: Peer Verification (cross-functional)
  - Financial claims → CFO validates math
  - Revenue projections → CRO validates pipeline backing
  - Technical feasibility → CTO validates
  - People/hiring impact → CHRO validates
  - Skip for single-domain, low-stakes questions

Step 3: Critic Pre-Screen (high-stakes only)
  - Irreversible decisions, >20% runway impact, strategy changes
  - Executive Mentor finds weakest point before founder sees it
  - Suspicious consensus triggers mandatory pre-screen

Step 4: Course Correction (after founder feedback)
  - Approve → log + assign actions
  - Modify → re-verify changed parts
  - Reject → DO_NOT_RESURFACE + learn why
  - 30/60/90 day post-decision review

Board meeting contributions now require self-verified format with
confidence tags and source attribution on every finding.

* fix: resolve PR review issues 1, 4, and minor observation

Issue 1: c-level-advisor/CLAUDE.md — completely rewritten
  - Was: 2 skills (CEO, CTO only), dated Nov 2025
  - Now: full 28-skill ecosystem map with architecture diagram,
    all roles/orchestration/cross-cutting/culture skills listed,
    design decisions, integration with other domains

Issue 4: Root CLAUDE.md — updated all stale counts
  - 87 → 134 skills across all 3 references
  - C-Level: 2 → 33 (10 roles + 5 mentor commands + 18 complementary)
  - Tool count: 160+ → 185+
  - Reference count: 200+ → 250+

Minor observation: Documented plugin.json convention
  - Explained in c-level-advisor/CLAUDE.md that only executive-mentor
    has plugin.json because only it has slash commands (/em: namespace)
  - Other skills are invoked by name through Chief of Staff or directly

Also fixed: README.md 88+ → 134 in two places (first line + skills section)

* fix: update all plugin/index registrations for 28-skill C-suite

1. c-level-advisor/.claude-plugin/plugin.json — v2.0.0
   - Was: 2 skills, generic description
   - Now: all 28 skills listed with descriptions, all 25 scripts,
     namespace 'cs', full ecosystem description

2. .codex/skills-index.json — added 18 complementary skills
   - Was: 10 roles only
   - Now: 28 total c-level entries (10 roles + 6 orchestration +
     6 cross-cutting + 6 culture)
   - Each with full description for skill discovery

3. .claude-plugin/marketplace.json — updated c-level-skills entry
   - Was: generic 2-skill description
   - Now: v2.0.0, full 28-skill ecosystem description,
     skills_count: 28, scripts_count: 25

* feat: add root SKILL.md for c-level-advisor ClawHub package

---------

Co-authored-by: Leo <leo@openclaw.ai>
2026-03-06 01:35:08 +01:00

1101 lines
43 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""
okr_tracker.py — OKR Cascade and Alignment Tracker
Tracks OKR progress from company → department → team level.
Calculates scores, flags at-risk key results, and generates alignment reports.
Scoring: Google's 0.01.0 scale (target: 0.60.7; hitting 1.0 means goal was too easy)
Usage:
python okr_tracker.py # Runs with sample data
python okr_tracker.py --input okrs.json # Custom OKR data
python okr_tracker.py --input okrs.json --output report.txt
python okr_tracker.py --format json # Machine-readable output
"""
import json
import sys
import argparse
from datetime import datetime, date
from typing import Any
# ---------------------------------------------------------------------------
# Scoring Engine
# ---------------------------------------------------------------------------
# OKR health thresholds (Google-style 0.01.0 scale)
SCORE_THRESHOLDS = {
"on_track": 0.70, # Above this: healthy
"at_risk": 0.40, # Between at_risk and on_track: needs attention
# Below at_risk: off track
}
STATUS_LABELS = {
"on_track": "🟢 On Track",
"at_risk": "🟡 At Risk",
"off_track": "🔴 Off Track",
"complete": "✅ Complete",
"not_started": "⬜ Not Started",
}
RISK_LABELS = {
"critical": "🔴 Critical",
"high": "🟠 High",
"medium": "🟡 Medium",
"low": "🟢 Low",
}
def calculate_kr_score(kr: dict) -> float:
"""
Calculate a Key Result's progress score (0.01.0).
Supports multiple KR types:
- numeric: current_value / target_value
- percentage: current_pct / target_pct
- milestone: milestone_score (0.01.0 provided directly)
- boolean: done (1.0) / not done (0.0)
"""
kr_type = kr.get("type", "numeric")
if kr_type == "boolean":
return 1.0 if kr.get("done", False) else 0.0
elif kr_type == "milestone":
# Milestone KRs have explicit score (0.01.0) or count of milestones hit
milestones_total = kr.get("milestones_total", 1)
milestones_hit = kr.get("milestones_hit", 0)
explicit_score = kr.get("score")
if explicit_score is not None:
return max(0.0, min(1.0, float(explicit_score)))
return milestones_hit / milestones_total if milestones_total > 0 else 0.0
elif kr_type == "percentage":
target = kr.get("target_pct", 100)
current = kr.get("current_pct", 0)
baseline = kr.get("baseline_pct", 0)
if target == baseline:
return 0.0
score = (current - baseline) / (target - baseline)
return max(0.0, min(1.0, score))
else: # numeric (default)
target = kr.get("target_value", 0)
current = kr.get("current_value", 0)
baseline = kr.get("baseline_value", 0)
if target == baseline:
return 0.0
# Handle "lower is better" metrics (e.g., churn, response time)
if kr.get("lower_is_better", False):
if current <= target:
return 1.0
improvement = baseline - current
needed = baseline - target
score = improvement / needed if needed != 0 else 0.0
else:
score = (current - baseline) / (target - baseline)
return max(0.0, min(1.0, score))
def get_kr_status(score: float, quarter_progress: float, kr: dict) -> str:
"""
Determine KR status based on score, time elapsed in quarter, and trend.
A KR is at-risk if its score is significantly behind the time elapsed.
E.g., if we're 70% through the quarter but KR is at 30%, it's at risk.
"""
if kr.get("done", False):
return "complete"
# Not started
if score == 0.0 and quarter_progress < 0.1:
return "not_started"
# Check against absolute thresholds
if score >= SCORE_THRESHOLDS["on_track"]:
return "on_track"
# Adjust for time: if we're early in quarter, lower scores are acceptable
adjusted_threshold = SCORE_THRESHOLDS["at_risk"] * (quarter_progress or 0.5)
if score >= max(adjusted_threshold, SCORE_THRESHOLDS["at_risk"]):
return "at_risk"
return "off_track"
def calculate_objective_score(objective: dict, quarter_progress: float) -> dict:
"""
Score an objective based on its key results.
Returns scored objective with KR scores and status.
"""
key_results = objective.get("key_results", [])
if not key_results:
return {**objective, "score": 0.0, "status": "not_started", "key_results_scored": []}
scored_krs = []
for kr in key_results:
score = calculate_kr_score(kr)
status = get_kr_status(score, quarter_progress, kr)
# Calculate time-adjusted gap
expected_score = quarter_progress * 0.85 # Expect 85% of time-proportional progress
gap = expected_score - score
risk_level = _assess_kr_risk(score, status, gap, quarter_progress, kr)
scored_krs.append({
**kr,
"score": round(score, 3),
"score_pct": f"{score * 100:.0f}%",
"status": status,
"status_label": STATUS_LABELS.get(status, status),
"expected_score": round(expected_score, 3),
"gap_vs_expected": round(gap, 3),
"risk_level": risk_level,
"risk_label": RISK_LABELS.get(risk_level, risk_level),
})
# Objective score = weighted average of KR scores
# Weight is explicit in KR data or defaults to equal weight
total_weight = sum(kr.get("weight", 1.0) for kr in key_results)
weighted_score = sum(
kr_scored["score"] * kr.get("weight", 1.0)
for kr_scored, kr in zip(scored_krs, key_results)
)
obj_score = weighted_score / total_weight if total_weight > 0 else 0.0
# Objective status = worst KR status (a chain is only as strong as weakest link)
status_priority = {"off_track": 0, "at_risk": 1, "not_started": 2, "on_track": 3, "complete": 4}
obj_status = min(scored_krs, key=lambda x: status_priority.get(x["status"], 2))["status"]
return {
**objective,
"score": round(obj_score, 3),
"score_pct": f"{obj_score * 100:.0f}%",
"status": obj_status,
"status_label": STATUS_LABELS.get(obj_status, obj_status),
"key_results_scored": scored_krs,
}
def _assess_kr_risk(
score: float,
status: str,
gap: float,
quarter_progress: float,
kr: dict,
) -> str:
"""Assess risk level for a key result."""
if status == "complete" or status == "on_track":
return "low"
weeks_remaining = kr.get("weeks_remaining", max(1, int((1 - quarter_progress) * 13)))
# Critical: off track with <4 weeks left
if status == "off_track" and weeks_remaining <= 4:
return "critical"
# High: significantly behind with limited time
if gap > 0.3 and weeks_remaining <= 6:
return "high"
# High: off track regardless of time
if status == "off_track":
return "high"
# Medium: at risk
if status == "at_risk":
return "medium"
return "low"
# ---------------------------------------------------------------------------
# OKR Cascade and Alignment Analysis
# ---------------------------------------------------------------------------
def build_okr_tree(data: dict, quarter_progress: float) -> dict:
"""
Build scored OKR tree: company → departments → teams.
Returns full hierarchy with scores at every level.
"""
company = data.get("company_okrs", {})
departments = data.get("department_okrs", [])
teams = data.get("team_okrs", [])
# Score company-level OKRs
company_scored = {
"name": company.get("name", "Company"),
"quarter": company.get("quarter", ""),
"objectives": [
calculate_objective_score(obj, quarter_progress)
for obj in company.get("objectives", [])
],
}
# Score department-level OKRs
depts_scored = []
for dept in departments:
dept_objectives = [
calculate_objective_score(obj, quarter_progress)
for obj in dept.get("objectives", [])
]
dept_score = (
sum(o["score"] for o in dept_objectives) / len(dept_objectives)
if dept_objectives else 0.0
)
depts_scored.append({
**dept,
"objectives": dept_objectives,
"overall_score": round(dept_score, 3),
"overall_score_pct": f"{dept_score * 100:.0f}%",
})
# Score team-level OKRs
teams_scored = []
for team in teams:
team_objectives = [
calculate_objective_score(obj, quarter_progress)
for obj in team.get("objectives", [])
]
team_score = (
sum(o["score"] for o in team_objectives) / len(team_objectives)
if team_objectives else 0.0
)
teams_scored.append({
**team,
"objectives": team_objectives,
"overall_score": round(team_score, 3),
"overall_score_pct": f"{team_score * 100:.0f}%",
})
return {
"company": company_scored,
"departments": depts_scored,
"teams": teams_scored,
}
def analyze_alignment(okr_tree: dict) -> dict:
"""
Analyze how team and department OKRs align to company OKRs.
Flags: orphaned OKRs (no company parent), missing coverage (company OKR with no team support).
"""
company_objective_ids = {
obj.get("id") for obj in okr_tree["company"].get("objectives", [])
if obj.get("id")
}
# Collect all alignment references from dept and team OKRs
alignment_map: dict[str, list[str]] = {oid: [] for oid in company_objective_ids}
orphaned = []
all_supporting = []
def check_objectives(objectives: list, owner_name: str, level: str):
for obj in objectives:
supports = obj.get("supports_company_objective_ids", [])
if not supports:
# Check if it's supposed to support something
if obj.get("supports_company_objective_id"):
supports = [obj["supports_company_objective_id"]]
if not supports:
orphaned.append({
"level": level,
"owner": owner_name,
"objective": obj.get("title", obj.get("name", "Unknown")),
"issue": "No link to company objective — may be misaligned or low priority",
})
else:
for cid in supports:
if cid in alignment_map:
alignment_map[cid].append(f"{level}:{owner_name}")
all_supporting.append(cid)
else:
orphaned.append({
"level": level,
"owner": owner_name,
"objective": obj.get("title", obj.get("name", "Unknown")),
"issue": f"References company objective '{cid}' which doesn't exist",
})
for dept in okr_tree["departments"]:
check_objectives(dept["objectives"], dept.get("name", "Unknown Dept"), "Department")
for team in okr_tree["teams"]:
check_objectives(team["objectives"], team.get("name", "Unknown Team"), "Team")
# Find company objectives with no support from below
unsupported = []
for obj in okr_tree["company"].get("objectives", []):
obj_id = obj.get("id")
if obj_id and obj_id not in all_supporting:
unsupported.append({
"objective_id": obj_id,
"objective": obj.get("title", obj.get("name", "Unknown")),
"issue": "No department or team OKR explicitly supports this company objective",
})
coverage_score = (
len(set(all_supporting)) / len(company_objective_ids) * 100
if company_objective_ids else 100
)
return {
"alignment_map": alignment_map,
"orphaned_okrs": orphaned,
"unsupported_company_objectives": unsupported,
"coverage_score_pct": round(coverage_score, 1),
}
def collect_at_risk_krs(okr_tree: dict) -> list[dict]:
"""Collect all at-risk and off-track key results across the full OKR tree."""
at_risk = []
def scan_objectives(objectives: list, owner: str, level: str):
for obj in objectives:
for kr in obj.get("key_results_scored", []):
if kr["status"] in ("at_risk", "off_track"):
at_risk.append({
"level": level,
"owner": owner,
"objective": obj.get("title", obj.get("name", "Unknown")),
"key_result": kr.get("title", kr.get("name", "Unknown")),
"score": kr["score"],
"score_pct": kr["score_pct"],
"status": kr["status"],
"status_label": kr["status_label"],
"risk_level": kr["risk_level"],
"risk_label": kr["risk_label"],
"gap_vs_expected": kr["gap_vs_expected"],
"notes": kr.get("notes", ""),
})
scan_objectives(
okr_tree["company"].get("objectives", []),
okr_tree["company"].get("name", "Company"),
"Company",
)
for dept in okr_tree["departments"]:
scan_objectives(dept["objectives"], dept.get("name", ""), "Department")
for team in okr_tree["teams"]:
scan_objectives(team["objectives"], team.get("name", ""), "Team")
# Sort: off_track before at_risk, then by gap
status_order = {"off_track": 0, "at_risk": 1}
at_risk.sort(key=lambda x: (status_order.get(x["status"], 2), -x.get("gap_vs_expected", 0)))
return at_risk
# ---------------------------------------------------------------------------
# Report Formatter
# ---------------------------------------------------------------------------
def _score_bar(score: float, width: int = 20) -> str:
"""Render a text progress bar for a 0.01.0 score."""
filled = round(score * width)
bar = "" * filled + "" * (width - filled)
return f"[{bar}] {score * 100:.0f}%"
def format_report(
okr_tree: dict,
alignment: dict,
at_risk_krs: list[dict],
quarter_progress: float,
quarter_label: str,
) -> str:
"""Format full OKR tracking report as plain text."""
lines = []
now = datetime.now().strftime("%Y-%m-%d %H:%M")
company_name = okr_tree["company"].get("name", "Company")
lines.append("=" * 70)
lines.append(f"OKR TRACKING REPORT — {company_name}")
lines.append(f"Quarter: {quarter_label} | Quarter progress: {quarter_progress * 100:.0f}%")
lines.append(f"Generated: {now}")
lines.append("=" * 70)
# --- Executive Summary ---
lines.append("\n📊 EXECUTIVE SUMMARY")
lines.append("-" * 40)
company_objectives = okr_tree["company"].get("objectives", [])
if company_objectives:
company_avg = sum(o["score"] for o in company_objectives) / len(company_objectives)
on_track = sum(1 for o in company_objectives if o["status"] == "on_track")
at_risk = sum(1 for o in company_objectives if o["status"] == "at_risk")
off_track = sum(1 for o in company_objectives if o["status"] == "off_track")
lines.append(f"Company OKR Score: {_score_bar(company_avg)}")
lines.append(f"Objectives: {len(company_objectives)} total — "
f"🟢 {on_track} on track, 🟡 {at_risk} at risk, 🔴 {off_track} off track")
lines.append(f"At-risk KRs (all): {len(at_risk_krs)}")
lines.append(f"Alignment coverage: {alignment['coverage_score_pct']}% of company objectives have team support")
# Overall health assessment
if company_avg >= 0.7:
health = "🟢 HEALTHY — On track for a strong quarter"
elif company_avg >= 0.5:
health = "🟡 CAUTION — Some objectives need attention"
elif company_avg >= 0.3:
health = "🔴 AT RISK — Multiple objectives behind; intervention needed"
else:
health = "🚨 CRITICAL — Quarter in serious jeopardy; executive review required"
lines.append(f"\nOverall Health: {health}")
# --- Company OKRs ---
lines.append("\n\n🏢 COMPANY OKRs")
lines.append("-" * 40)
for obj in company_objectives:
lines.append(f"\n Objective: {obj.get('title', obj.get('name', 'Unknown'))}")
lines.append(f" Owner: {obj.get('owner', 'Unassigned')} | Score: {_score_bar(obj['score'], 15)} {obj['status_label']}")
for kr in obj.get("key_results_scored", []):
risk_marker = f" {kr['risk_label']}" if kr["risk_level"] in ("critical", "high") else ""
lines.append(f"\n KR: {kr.get('title', kr.get('name', 'Unknown'))}")
lines.append(f" Score: {_score_bar(kr['score'], 12)} {kr['status_label']}{risk_marker}")
# Show actual progress
if kr.get("type") == "numeric":
current = kr.get("current_value", "?")
target = kr.get("target_value", "?")
baseline = kr.get("baseline_value", 0)
unit = kr.get("unit", "")
lines.append(f" Progress: {current}{unit} / {target}{unit} (baseline: {baseline}{unit})")
elif kr.get("type") == "percentage":
lines.append(f" Progress: {kr.get('current_pct', '?')}% / {kr.get('target_pct', '?')}%")
elif kr.get("type") == "milestone":
hit = kr.get("milestones_hit", "?")
total = kr.get("milestones_total", "?")
lines.append(f" Milestones: {hit} / {total}")
if kr.get("notes"):
lines.append(f" Note: {kr['notes']}")
# --- Department OKRs ---
lines.append("\n\n🏬 DEPARTMENT OKRs")
lines.append("-" * 40)
for dept in okr_tree["departments"]:
lines.append(f"\n 📁 {dept.get('name', 'Unknown')} | Score: {_score_bar(dept['overall_score'], 15)}")
for obj in dept.get("objectives", []):
lines.append(f"\n Objective: {obj.get('title', obj.get('name', 'Unknown'))}")
lines.append(f" Owner: {obj.get('owner', 'Unassigned')} | {obj['status_label']}")
supports = obj.get("supports_company_objective_ids", [])
if supports:
lines.append(f" Supports: Company Objective(s) {', '.join(supports)}")
for kr in obj.get("key_results_scored", []):
risk_marker = f" {kr['risk_label']}" if kr["risk_level"] in ("critical", "high") else ""
lines.append(f"\n KR: {kr.get('title', kr.get('name', 'Unknown'))}")
lines.append(f" {_score_bar(kr['score'], 10)} {kr['status_label']}{risk_marker}")
# --- Team OKRs ---
if okr_tree["teams"]:
lines.append("\n\n👥 TEAM OKRs")
lines.append("-" * 40)
for team in okr_tree["teams"]:
lines.append(f"\n 📋 {team.get('name', 'Unknown')} | Score: {_score_bar(team['overall_score'], 15)}")
for obj in team.get("objectives", []):
lines.append(f"\n Objective: {obj.get('title', obj.get('name', 'Unknown'))}")
supports = obj.get("supports_company_objective_ids", [])
if supports:
lines.append(f" Supports: {', '.join(supports)}")
for kr in obj.get("key_results_scored", []):
risk_marker = f" {kr['risk_label']}" if kr["risk_level"] in ("critical", "high") else ""
lines.append(
f"{kr.get('title', kr.get('name', 'Unknown'))}: "
f"{kr['score_pct']} {kr['status_label']}{risk_marker}"
)
# --- At-Risk KRs ---
lines.append("\n\n⚠️ AT-RISK KEY RESULTS (Action Required)")
lines.append("-" * 40)
if not at_risk_krs:
lines.append("✅ No key results currently at risk or off track.")
else:
critical = [kr for kr in at_risk_krs if kr["risk_level"] == "critical"]
high = [kr for kr in at_risk_krs if kr["risk_level"] == "high"]
medium = [kr for kr in at_risk_krs if kr["risk_level"] == "medium"]
for group_label, group in [("🔴 CRITICAL", critical), ("🟠 HIGH", high), ("🟡 MEDIUM", medium)]:
if not group:
continue
lines.append(f"\n{group_label} ({len(group)} items):")
for kr in group:
lines.append(f"\n [{kr['level']}] {kr['owner']}")
lines.append(f" Obj: {kr['objective']}")
lines.append(f" KR: {kr['key_result']}")
lines.append(f" Score: {kr['score_pct']} {kr['status_label']} (gap vs expected: {kr['gap_vs_expected'] * 100:.0f}pp)")
if kr["notes"]:
lines.append(f" Note: {kr['notes']}")
# --- Alignment Report ---
lines.append("\n\n🔗 ALIGNMENT REPORT")
lines.append("-" * 40)
lines.append(f"Alignment coverage: {alignment['coverage_score_pct']}% of company objectives have explicit support\n")
# Show alignment map
lines.append("Company Objective Coverage:")
for obj in company_objectives:
obj_id = obj.get("id", "")
supporters = alignment["alignment_map"].get(obj_id, [])
obj_name = obj.get("title", obj.get("name", obj_id))
count = len(supporters)
marker = "" if count > 0 else "⚠️ "
lines.append(f" {marker} [{obj_id}] {obj_name}")
if supporters:
for s in supporters:
lines.append(f"{s}")
else:
lines.append(f" ↑ (no department or team OKR supports this)")
if alignment["unsupported_company_objectives"]:
lines.append(f"\n⚠️ Unsupported Company Objectives ({len(alignment['unsupported_company_objectives'])}):")
for u in alignment["unsupported_company_objectives"]:
lines.append(f" • [{u['objective_id']}] {u['objective']}")
lines.append(f"{u['issue']}")
if alignment["orphaned_okrs"]:
lines.append(f"\n⚠️ Orphaned OKRs (not linked to company objectives):")
for o in alignment["orphaned_okrs"]:
lines.append(f" • [{o['level']}] {o['owner']}: {o['objective']}")
lines.append(f"{o['issue']}")
# --- Recommendations ---
lines.append("\n\n📋 RECOMMENDED ACTIONS")
lines.append("-" * 40)
recs = _generate_recommendations(okr_tree, at_risk_krs, alignment, quarter_progress)
for i, rec in enumerate(recs, 1):
lines.append(f"\n{i}. {rec['title']}")
lines.append(f" {rec['detail']}")
lines.append(f" Owner: {rec['owner']} | When: {rec['when']}")
lines.append("\n" + "=" * 70)
lines.append("END OF REPORT")
lines.append("=" * 70)
return "\n".join(lines)
def _generate_recommendations(
okr_tree: dict,
at_risk_krs: list[dict],
alignment: dict,
quarter_progress: float,
) -> list[dict]:
"""Generate actionable recommendations based on OKR analysis."""
recs = []
# Critical KRs
critical = [kr for kr in at_risk_krs if kr["risk_level"] == "critical"]
if critical:
recs.append({
"title": f"Emergency review: {len(critical)} critical key result(s) need immediate intervention",
"detail": f"Critical KRs: {', '.join(kr['key_result'] for kr in critical[:3])}. "
f"With limited time remaining, these need escalation today.",
"owner": "COO + KR owners",
"when": "This week",
})
# Off-track objectives
off_track_objs = [
o for o in okr_tree["company"].get("objectives", [])
if o["status"] == "off_track"
]
if off_track_objs:
recs.append({
"title": f"Scope reset for {len(off_track_objs)} off-track company objective(s)",
"detail": "When a company objective is off track by mid-quarter, "
"the options are: (1) resource surge, (2) scope reduction, or (3) accept the miss. "
"Choose explicitly — don't let it drift.",
"owner": "CEO + COO",
"when": "Within 1 week",
})
# Alignment gaps
if alignment["coverage_score_pct"] < 80:
recs.append({
"title": "OKR alignment gap — not all company objectives have team support",
"detail": f"Only {alignment['coverage_score_pct']}% of company objectives have explicit team/dept OKRs supporting them. "
"Either add supporting OKRs or acknowledge these objectives are founder-owned.",
"owner": "COO + VPs",
"when": "Next OKR planning cycle",
})
if alignment["orphaned_okrs"]:
recs.append({
"title": f"{len(alignment['orphaned_okrs'])} orphaned OKR(s) with no company objective linkage",
"detail": "Team OKRs that don't connect to company objectives waste capacity. "
"Either link them explicitly or discontinue them.",
"owner": "Team leads + COO",
"when": "OKR review session",
})
# Late quarter: force ranking
if quarter_progress >= 0.67:
at_risk_count = sum(
1 for o in okr_tree["company"].get("objectives", [])
if o["status"] in ("at_risk", "off_track")
)
if at_risk_count > 0:
recs.append({
"title": f"Late quarter: force-rank which at-risk OKRs to save vs. accept as miss",
"detail": f"{at_risk_count} objectives at risk with <{int((1 - quarter_progress) * 13)} weeks left. "
"You cannot save everything. Pick the 12 most important and resource them fully. "
"Explicitly accept the others as misses and learn from them.",
"owner": "CEO + COO",
"when": "Immediately",
})
# Measurement gaps
unscored_krs = []
for obj in okr_tree["company"].get("objectives", []):
for kr in obj.get("key_results_scored", []):
if kr["score"] == 0.0 and kr["status"] == "not_started" and quarter_progress > 0.25:
unscored_krs.append(kr.get("title", kr.get("name", "Unknown")))
if unscored_krs:
recs.append({
"title": f"{len(unscored_krs)} key result(s) show no progress past Q1",
"detail": "KRs with zero progress after 25% of quarter has elapsed are either not started, "
"unmeasured, or forgotten. Require owners to update scores this week.",
"owner": "KR owners",
"when": "This week — before next leadership sync",
})
return recs
def format_json_output(okr_tree: dict, alignment: dict, at_risk_krs: list[dict]) -> str:
"""Format analysis as machine-readable JSON."""
return json.dumps(
{
"generated_at": datetime.now().isoformat(),
"company_score": (
sum(o["score"] for o in okr_tree["company"].get("objectives", []))
/ max(1, len(okr_tree["company"].get("objectives", [])))
),
"at_risk_count": len(at_risk_krs),
"alignment_coverage_pct": alignment["coverage_score_pct"],
"objectives": okr_tree["company"].get("objectives", []),
"departments": okr_tree["departments"],
"teams": okr_tree["teams"],
"at_risk_key_results": at_risk_krs,
"alignment": alignment,
},
indent=2,
)
# ---------------------------------------------------------------------------
# Main Entrypoint
# ---------------------------------------------------------------------------
def main():
parser = argparse.ArgumentParser(
description="OKR Cascade and Alignment Tracker — COO Advisor Tool",
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog=__doc__,
)
parser.add_argument("--input", "-i", help="Path to JSON OKR data file", default=None)
parser.add_argument("--output", "-o", help="Path to write report (default: stdout)", default=None)
parser.add_argument(
"--format", "-f",
choices=["text", "json"],
default="text",
help="Output format: text (default) or json",
)
parser.add_argument(
"--quarter-progress",
type=float,
default=None,
help="Override quarter progress (0.01.0). Default: auto-calculated from quarter dates.",
)
args = parser.parse_args()
if args.input:
try:
with open(args.input, "r") as f:
data = json.load(f)
except FileNotFoundError:
print(f"Error: Input file not found: {args.input}", file=sys.stderr)
sys.exit(1)
except json.JSONDecodeError as e:
print(f"Error: Invalid JSON: {e}", file=sys.stderr)
sys.exit(1)
else:
print("No input file specified — running with sample data.\n")
data = SAMPLE_DATA
# Determine quarter progress
if args.quarter_progress is not None:
quarter_progress = args.quarter_progress
else:
quarter_progress = _calculate_quarter_progress(data)
quarter_label = data.get("company_okrs", {}).get("quarter", "Unknown Quarter")
# Run analysis
okr_tree = build_okr_tree(data, quarter_progress)
alignment = analyze_alignment(okr_tree)
at_risk_krs = collect_at_risk_krs(okr_tree)
# Format output
if args.format == "json":
output = format_json_output(okr_tree, alignment, at_risk_krs)
else:
output = format_report(okr_tree, alignment, at_risk_krs, quarter_progress, quarter_label)
if args.output:
with open(args.output, "w") as f:
f.write(output)
print(f"Report written to: {args.output}")
else:
print(output)
def _calculate_quarter_progress(data: dict) -> float:
"""Auto-calculate quarter progress from start/end dates in data, or default to 0.5."""
q = data.get("company_okrs", {})
start_str = q.get("quarter_start")
end_str = q.get("quarter_end")
if not start_str or not end_str:
return 0.5 # Default to mid-quarter if not specified
try:
start = date.fromisoformat(start_str)
end = date.fromisoformat(end_str)
today = date.today()
total_days = (end - start).days
elapsed_days = (today - start).days
progress = elapsed_days / total_days if total_days > 0 else 0.5
return max(0.0, min(1.0, progress))
except (ValueError, TypeError):
return 0.5
# ---------------------------------------------------------------------------
# Sample Data
# ---------------------------------------------------------------------------
SAMPLE_DATA = {
"company_okrs": {
"name": "AcmeSaaS",
"quarter": "Q1 2025",
"quarter_start": "2025-01-01",
"quarter_end": "2025-03-31",
"objectives": [
{
"id": "CO1",
"title": "Achieve breakout revenue growth",
"owner": "CEO",
"key_results": [
{
"id": "CO1-KR1",
"title": "Reach $5M net new ARR",
"type": "numeric",
"baseline_value": 0,
"current_value": 2800000,
"target_value": 5000000,
"unit": "",
"notes": "Strong January, February softer; pipeline looks better for March",
},
{
"id": "CO1-KR2",
"title": "Achieve 115% NRR",
"type": "percentage",
"baseline_pct": 108,
"current_pct": 110,
"target_pct": 115,
"notes": "Expansion motion improved; churn still elevated in SMB segment",
},
{
"id": "CO1-KR3",
"title": "Close 3 enterprise deals (>$150K ACV)",
"type": "numeric",
"baseline_value": 0,
"current_value": 1,
"target_value": 3,
"unit": " deals",
"notes": "1 closed, 2 in late-stage negotiation",
},
],
},
{
"id": "CO2",
"title": "Build a world-class product that customers love",
"owner": "CPO",
"key_results": [
{
"id": "CO2-KR1",
"title": "Increase feature adoption rate to 65% (% of customers using 3+ core features)",
"type": "percentage",
"baseline_pct": 48,
"current_pct": 52,
"target_pct": 65,
"notes": "Onboarding improvements shipped; adoption curve is moving",
},
{
"id": "CO2-KR2",
"title": "Ship the integration platform (milestone)",
"type": "milestone",
"milestones_total": 4,
"milestones_hit": 1,
"milestones": [
"API design complete",
"Internal alpha",
"Beta with 5 customers",
"GA launch",
],
"notes": "API design shipped. Internal alpha delayed 2 weeks.",
},
{
"id": "CO2-KR3",
"title": "NPS score reaches 45",
"type": "numeric",
"baseline_value": 32,
"current_value": 38,
"target_value": 45,
"unit": "",
},
],
},
{
"id": "CO3",
"title": "Build an operationally excellent company",
"owner": "COO",
"key_results": [
{
"id": "CO3-KR1",
"title": "Reduce burn multiple from 1.8x to 1.3x",
"type": "numeric",
"baseline_value": 1.8,
"current_value": 1.65,
"target_value": 1.3,
"lower_is_better": True,
"unit": "x",
},
{
"id": "CO3-KR2",
"title": "Achieve <30-day customer onboarding (avg)",
"type": "numeric",
"baseline_value": 47,
"current_value": 38,
"target_value": 30,
"lower_is_better": True,
"unit": " days",
"notes": "Good progress; blocked by technical setup step (avg 12 days)",
},
{
"id": "CO3-KR3",
"title": "Voluntary attrition <10%",
"type": "numeric",
"baseline_value": 15,
"current_value": 12,
"target_value": 10,
"lower_is_better": True,
"unit": "%",
"notes": "2 unexpected departures in January; retention initiatives launched",
},
],
},
],
},
"department_okrs": [
{
"name": "Sales",
"owner": "VP Sales",
"objectives": [
{
"title": "Drive net new ARR to hit company growth target",
"owner": "VP Sales",
"supports_company_objective_ids": ["CO1"],
"key_results": [
{
"title": "Close $4M in new business ARR",
"type": "numeric",
"baseline_value": 0,
"current_value": 2200000,
"target_value": 4000000,
"unit": "",
},
{
"title": "Maintain pipeline coverage ratio ≥3x",
"type": "numeric",
"baseline_value": 2.5,
"current_value": 3.1,
"target_value": 3.0,
"unit": "x",
},
{
"title": "Reduce average sales cycle to 42 days",
"type": "numeric",
"baseline_value": 58,
"current_value": 50,
"target_value": 42,
"lower_is_better": True,
"unit": " days",
},
],
}
],
},
{
"name": "Engineering",
"owner": "VP Engineering",
"objectives": [
{
"title": "Deliver the integration platform on schedule",
"owner": "VP Engineering",
"supports_company_objective_ids": ["CO2"],
"key_results": [
{
"title": "Integration platform beta live with 5 customers",
"type": "milestone",
"milestones_total": 3,
"milestones_hit": 1,
"notes": "Alpha delayed — dependency on API gateway refactor",
},
{
"title": "Deploy frequency ≥10/week",
"type": "numeric",
"baseline_value": 6,
"current_value": 9,
"target_value": 10,
"unit": "/week",
},
{
"title": "P0/P1 incidents <2 per month",
"type": "numeric",
"baseline_value": 5,
"current_value": 2.5,
"target_value": 2,
"lower_is_better": True,
"unit": "/month",
},
],
}
],
},
{
"name": "Customer Success",
"owner": "VP CS",
"objectives": [
{
"title": "Drive retention and expansion to fuel NRR growth",
"owner": "VP CS",
"supports_company_objective_ids": ["CO1", "CO2"],
"key_results": [
{
"title": "Gross retention ≥92%",
"type": "percentage",
"baseline_pct": 88,
"current_pct": 89,
"target_pct": 92,
"notes": "3 at-risk accounts in red status",
},
{
"title": "Average onboarding time ≤30 days",
"type": "numeric",
"baseline_value": 47,
"current_value": 38,
"target_value": 30,
"lower_is_better": True,
"unit": " days",
},
{
"title": "Expansion ARR from existing customers: $800K",
"type": "numeric",
"baseline_value": 0,
"current_value": 580000,
"target_value": 800000,
"unit": "",
},
],
}
],
},
],
"team_okrs": [
{
"name": "Platform Engineering",
"department": "Engineering",
"objectives": [
{
"title": "Build the integration API infrastructure",
"supports_company_objective_ids": ["CO2"],
"key_results": [
{
"title": "API gateway v2 deployed to production",
"type": "boolean",
"done": False,
"notes": "Targeting end of week 8",
},
{
"title": "Webhook system handles 10K events/sec",
"type": "boolean",
"done": False,
},
{
"title": "P99 API latency <200ms",
"type": "numeric",
"baseline_value": 380,
"current_value": 290,
"target_value": 200,
"lower_is_better": True,
"unit": "ms",
},
],
}
],
},
{
"name": "Enterprise Sales Team",
"department": "Sales",
"objectives": [
{
"title": "Land 3 enterprise accounts",
"supports_company_objective_ids": ["CO1"],
"key_results": [
{
"title": "3 enterprise deals closed",
"type": "numeric",
"baseline_value": 0,
"current_value": 1,
"target_value": 3,
"unit": " deals",
},
{
"title": "5 enterprise POCs initiated",
"type": "numeric",
"baseline_value": 0,
"current_value": 4,
"target_value": 5,
"unit": " POCs",
},
],
}
],
},
],
}
if __name__ == "__main__":
main()