
Board Deck Builder
- 94 installs
- 451 repo stars
- Updated July 21, 2026
- borghei/claude-skills
board-deck-builder is a Claude skill that assembles board and investor update decks by pulling perspectives from all C-suite roles.
About
board-deck-builder is a Claude skill that assembles board and investor update decks by pulling perspectives from each C-suite role. A founder or operator uses it to prepare board meetings, investor updates, quarterly business reviews, or fundraising narratives. It provides section-by-section templates, deck-type structures, and rules like requiring a one-sentence explanation for every metric variance.
- Assembles board and investor decks with a section owner and narrative for each C-suite role
- Section templates for exec summary, metrics dashboard, financials, pipeline, product, and asks
- Four deck types (quarterly, monthly, fundraising, emergency) with structure and slide counts
Board Deck Builder by the numbers
- 94 all-time installs (skills.sh)
- Ranked #1,388 of 3,282 Productivity & Planning skills by installs in the Skillselion catalog
- Data as of Aug 5, 2026 (Skillselion catalog sync)
board-deck-builder capabilities & compatibility
Free; generates deck content, no API keys stated.
- Capabilities
- board meeting · presentation builder · investor update
- Use cases
- presentations · project management
- Pricing
- Free
What board-deck-builder says it does
Assembles board and investor update decks by pulling perspectives from all C-suite roles.
Build board decks that tell a story, not just show data.
Boards see 10+ decks per quarter -- yours needs a through-line.
npx skills add https://github.com/borghei/claude-skills --skill board-deck-builderAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 94 |
|---|---|
| repo stars | ★ 451 |
| Last updated | July 21, 2026 |
| Repository | borghei/claude-skills ↗ |
What it does
Assemble a board or investor update deck with a per-section owner, narrative, and metrics dashboard.
Who is it for?
Founders and operators preparing board meetings, investor updates, QBRs, or fundraising decks.
When should I use this skill?
When preparing board meetings, investor updates, quarterly business reviews, or fundraising narratives.
What you get
A structured board deck where every section has an owner, a narrative, and a clear so-what.
- board or investor deck
- metrics dashboard section
- asks and decisions slide
By the numbers
- 4 deck types (quarterly, monthly, fundraising, emergency)
- quarterly board deck is 20-30 slides
- metrics dashboard limited to 6-8 metrics
Files
Board Deck Builder
Build board decks that tell a story, not just show data. Every section has an owner, a narrative, and a "so what." Boards see 10+ decks per quarter -- yours needs a through-line.
Keywords
board deck, investor update, board meeting, board pack, investor relations, quarterly review, board presentation, fundraising deck, investor deck, board narrative, QBR, quarterly business review, board report, metrics dashboard, bad news delivery, variance explanation
---
Clarify First
Before generating, confirm these inputs. If any is unknown or vague, ASK — do not assume:
- [ ] Deck type and audience (quarterly board, monthly update, fundraising, emergency) — sets slide count, structure, and which sections appear
- [ ] The headline message this period (beat plan / missed plan / need a decision) — drives the opening frame and whether the SOUF bad-news structure is needed
- [ ] Which metrics the board actually tracks (and their targets) — the metrics dashboard should show only these, with RAG status against targets
- [ ] Specific asks or decisions needed from the board — the asks section is the highest-value slide and must be concrete and owner-assigned
Stop rule: ask only the 2-3 that most change the output. If the user says "just draft it," proceed and list your assumptions at the top of the artifact.
---
Deck Types and Structure
Deck Type Selection
| Type | When | Slide Count | Sent in Advance | Key Section |
|---|---|---|---|---|
| Quarterly Board Deck | Standard board meeting | 20-30 slides | 48 hours ahead | Full deck below |
| Monthly Update | Early-stage boards | 8-12 slides | 24 hours ahead | Metrics + risks |
| Fundraising Deck | Active fundraise | 12-15 slides | During meeting | Vision + traction |
| Emergency/Ad-hoc | Crisis or major decision | 5-8 slides | Depends | Situation + options |
---
Standard Board Deck (Section by Section)
Section 1: Executive Summary (CEO)
Three sentences. No more. No less.
| Sentence | Purpose | Example |
|---|---|---|
| 1 | State of the business | "We closed Q3 at $2.4M ARR, up 22% QoQ" |
| 2 | Biggest development this period | "Signed our largest enterprise contract ($180K ACV)" |
| 3 | Forward-looking priority | "Q4 priority: close Series A and hit $2.8M ARR" |
Anti-pattern: "We had a good quarter with lots of progress across all areas." Why it fails: Says nothing. Board learns nothing. Time wasted.
Section 2: Key Metrics Dashboard (COO)
6-8 metrics maximum. Every metric needs a target and a status.
| Metric | This Period | Last Period | Target | Status | Trend |
|---|---|---|---|---|---|
| ARR | $2.4M | $1.97M | $2.3M | [G] | Up |
| MoM Growth | 8.1% | 7.2% | 7.5% | [G] | Up |
| Burn Multiple | 1.8x | 2.1x | < 2x | [G] | Improving |
| NRR | 112% | 108% | > 110% | [G] | Up |
| CAC Payback | 11 mo | 14 mo | < 12 mo | [G] | Improving |
| Headcount | 24 | 21 | 25 | [Y] | Below plan |
Rule: Only show metrics the board actually tracks. Ask what they care about. Remove anything they have said they do not care about.
Section 3: Financial Update (CFO)
| Component | Include | Format |
|---|---|---|
| P&L Summary | Revenue, COGS, Gross Margin, OpEx, Net Burn | Table with variance column |
| Cash Position | Current balance + runway in months | Single number, bold |
| Burn Multiple Trend | 3-quarter trend | Line chart |
| Variance to Plan | Each line item vs. budget | Table with one-sentence explanations |
| Forecast Update | Next quarter projections | Conservative, base, upside |
Rule: Every variance needs a one-sentence explanation. "Revenue was below target" with no explanation is unacceptable.
Section 4: Revenue and Pipeline (CRO)
| Component | Include | Format |
|---|---|---|
| ARR Waterfall | Opening -> New -> Expansion -> Contraction -> Churn -> Closing | Waterfall chart |
| NRR and Logo Churn | Current + 4-quarter trend | Table + trend line |
| Pipeline by Stage | Dollar amounts, not just counts | Funnel visualization |
| Forecast | Next quarter with confidence level | "High confidence $2.6M, upside to $2.9M" |
| Top 3 Deals | Name, amount, close date, risk | Table |
Rule: Forecast MUST include a confidence level. "We expect $2.8M" is weak.
Section 5: Product Update (CPO)
| Component | Include | Format |
|---|---|---|
| Shipped This Quarter | 3-5 items with user impact | Bullet list |
| Shipping Next Quarter | 3-5 items with target dates | Bullet list |
| PMF Signals | NPS trend, DAU/MAU, feature adoption | Metrics table |
| Key Learning | One insight from customer research | Narrative paragraph |
Rule: No feature lists. Only features with evidence of user impact.
Section 6: Growth and Marketing (CMO)
| Component | Include |
|---|---|
| CAC by Channel | Table with efficiency trend |
| Pipeline Contribution | $ by channel |
| What's Working | Specific channels/campaigns with data |
| What's Being Cut | Underperforming channels |
| What's Being Tested | New experiments with hypothesis |
Section 7: Engineering and Technical (CTO)
| Component | Include |
|---|---|
| Delivery Velocity | 4-quarter trend |
| Tech Debt Ratio | Current + plan to address |
| Infrastructure | Uptime, incidents, cost trend |
| Security Posture | One line unless there is a material issue |
Rule: Keep this short unless there is a material issue. Boards do not need sprint details.
Section 8: Team and People (CHRO)
| Component | Include |
|---|---|
| Headcount | Actual vs. plan |
| Hiring | Offers out, pipeline, time-to-fill trend |
| Attrition | Regrettable vs. non-regrettable |
| Engagement | Latest survey score and trend |
| Notable | Key hires, key departures, key open roles |
Section 9: Risk and Security (CISO)
| Component | Include |
|---|---|
| Security Posture | Status of critical controls |
| Compliance | Certifications in progress, deadlines |
| Incidents | This quarter (if any): impact, resolution, prevention |
| Top 3 Risks | Risk description + mitigation status |
Section 10: Strategic Outlook (CEO)
| Component | Include |
|---|---|
| Next Quarter Priorities | 3-5 items, ranked by importance |
| Board Decisions Needed | Specific votes or approvals |
| Asks | Specific, actionable requests |
Rule: The "asks" section is the most important. "We'd like 3 warm introductions to CFOs at Series B companies" beats "any help would be appreciated."
Section 11: Appendix
Include but do not present unless asked:
- Detailed financial model
- Full pipeline data
- Cohort retention charts
- Customer case studies
- Detailed headcount breakdown
---
Narrative Framework
The 4-Act Structure
Every board deck should follow this through-line:
Act 1: WHERE WE SAID WE'D BE
Last quarter's targets and commitments
Act 2: WHERE WE ACTUALLY ARE
Honest assessment -- good and bad
Act 3: WHY THE GAP EXISTS
One cause per variance. Not excuses -- explanations.
Act 4: WHAT WE'RE DOING ABOUT IT
Specific, dated, owned actionsThis works for good news AND bad news. It is credible because it acknowledges reality.
Opening Frame
The board should know the key message by slide 3, not slide 30.
| Good Opening | Bad Opening |
|---|---|
| "We beat ARR target by 8% and NRR hit 112%" | "Let me walk you through our quarter..." |
| "We missed revenue by $300K. Here's why and the fix." | "First, some context about market conditions..." |
| "We need a board vote on the acquisition opportunity" | "Before we get to the main topic, some updates..." |
---
Delivering Bad News
The SOUF Framework
| Step | What | Example |
|---|---|---|
| State | State it plainly | "We missed Q3 ARR target by $300K (12% gap)" |
| Own | Own the cause | "Primary driver: longer enterprise sales cycle than modeled" |
| Understand | Show you understand it | "Analyzed 8 stalled deals; pattern is procurement delays" |
| Fix | Present the fix | "Three changes: [specific, dated], revised Q4 target: $2.6M" |
Bad News Anti-Patterns
| Anti-Pattern | Why It Fails | Better Approach |
|---|---|---|
| Leading with good news to soften | Boards notice and distrust framing | Lead with the most important thing |
| "Market conditions" as cause | That is context, not a cause | Name specific, controllable causes |
| Fix without data | Board does not trust unfounded fixes | Show analysis behind the fix |
| Revised forecast without assumptions | Looks like guessing | Show bottom-up build |
---
Common Board Deck Mistakes
| Mistake | Fix |
|---|---|
| Too many slides (> 25) | Cut ruthlessly. If you can not explain it, the slide is wrong. |
| Metrics without targets | Every metric needs a target and a status indicator |
| No narrative | Data without story forces boards to draw conclusions |
| Burying bad news | Lead with it, own it, fix it |
| Vague asks | Specific, actionable, person-assigned asks only |
| No variance explanation | Every gap from target needs a one-sentence cause |
| Stale appendix | Appendix is only useful if current |
| Designed for reader, not room | Decks are presented -- they must work spoken aloud |
| Inconsistent formatting | Use one template, one color scheme, one font |
| Too much text per slide | Max 6 lines per slide. Use speaker notes for detail. |
---
Cadence Notes
| Type | Timing | Length | Advance Send |
|---|---|---|---|
| Quarterly (standard) | 48 hours before meeting | 20-30 slides | 48 hours |
| Monthly (early-stage) | 24 hours before meeting | 8-12 slides | 24 hours |
| Fundraising | In the meeting | 12-15 slides | Sometimes not shared |
---
Deck Assembly Workflow
T-14 days: CEO sets agenda, identifies key messages
T-10 days: Section owners begin drafting their sections
T-7 days: First drafts due. CEO reviews for narrative alignment.
T-5 days: Second drafts. CFO validates all numbers.
T-3 days: Final deck assembly. Narrative check.
T-2 days: Send to board. Include cover note with 3 key takeaways.
T-0: Meeting. CEO presents. Section owners available for questions.---
Red Flags
- Deck assembled night before the meeting -- no time for review or narrative alignment
- Numbers in deck do not match financial model -- credibility destroyer
- No asks section -- wasting board meeting time
- Same deck format for 4+ quarters with no iteration -- not responsive to feedback
- Board members surprised by information -- they should never learn bad news in the meeting
- More than 30 slides -- attention lost after slide 20
- No follow-up on previous meeting's action items -- accountability gap
---
Integration with C-Suite
Each section is owned by a specific C-suite role:
| Section | Owner | Reference Skill |
|---|---|---|
| Executive Summary | CEO | ceo-advisor |
| Metrics Dashboard | COO | coo-advisor |
| Financial Update | CFO | cfo-advisor |
| Revenue/Pipeline | CRO | cro-advisor |
| Product Update | CPO | cpo-advisor |
| Growth/Marketing | CMO | cmo-advisor |
| Engineering | CTO | cto-advisor |
| Team/People | CHRO | chro-advisor |
| Risk/Security | CISO | ciso-advisor |
| Strategic Outlook | CEO | ceo-advisor |
---
Output Artifacts
| Request | Deliverable |
|---|---|
| "Prepare the board deck" | Complete deck outline with section owners and data requirements |
| "Monthly investor update" | Condensed update: metrics, highlights, risks, asks |
| "Help with the narrative" | 4-act narrative structure with key messages |
| "Deliver bad news" | SOUF framework applied to specific situation |
| "Fundraising deck" | Vision-led deck with traction, team, market, ask |
| "Review my board deck" | Critique against best practices, identify gaps |
---
Tool Reference
deck_structure_validator.py
Validates board deck completeness against best-practice section requirements.
# Validate with demo data
python scripts/deck_structure_validator.py
# Validate specific deck type with slide count
python scripts/deck_structure_validator.py --type quarterly --slides 24
# Validate with section list
python scripts/deck_structure_validator.py --sections executive_summary metrics_dashboard financial_update
# Validate from JSON file
python scripts/deck_structure_validator.py --input deck.json
# JSON output
python scripts/deck_structure_validator.py --input deck.json --jsonmetrics_dashboard_generator.py
Generates formatted board-ready metrics dashboards with RAG status and trends.
# Generate demo dashboard
python scripts/metrics_dashboard_generator.py
# From JSON metrics file
python scripts/metrics_dashboard_generator.py --input metrics.json
# From CSV file
python scripts/metrics_dashboard_generator.py --csv metrics.csv
# JSON output
python scripts/metrics_dashboard_generator.py --jsonboard_prep_checklist.py
Generates T-minus timeline checklists for board meeting preparation.
# Generate checklist for meeting 14 days out
python scripts/board_prep_checklist.py
# Specific meeting date with completed tasks
python scripts/board_prep_checklist.py --meeting-date 2026-04-15 --completed agenda_set topics_confirmed
# List all task IDs
python scripts/board_prep_checklist.py --list-tasks
# JSON output
python scripts/board_prep_checklist.py --meeting-date 2026-04-15 --json---
Troubleshooting
| Problem | Likely Cause | Fix |
|---|---|---|
| Board members ask basic questions answered in the deck | Deck not sent far enough in advance or not self-explanatory | Send 48+ hours ahead with a 3-takeaway cover note; ensure deck reads standalone |
| Financial numbers don't match across sections | No CFO cross-validation step | Add T-5 day CFO validation checkpoint; single source of truth for all metrics |
| Meeting runs over time on early sections | Too much detail in presenter slides, no time boxing | Enforce max 6 lines per slide; use speaker notes for depth; assign timekeeper |
| Board members surprised by bad news | Information not previewed in 1:1 pre-calls | CEO pre-briefs each board member on material issues before the meeting |
| Asks section produces no follow-through | Asks are vague ("any help appreciated") | Make asks specific, named, and actionable ("3 warm intros to Series B CFOs") |
| Same deck format for 4+ quarters with no improvement | No post-meeting feedback loop | Rate deck effectiveness at meeting end; iterate one section per quarter |
| Appendix never referenced | Appendix is stale or not relevant to current agenda | Update appendix before every meeting; remove data nobody has asked about in 2+ quarters |
---
Success Criteria
- Board members report reading the deck in advance in 80%+ of meetings (measured by pre-meeting questions received)
- Meeting stays within time allocation with 70%+ of time on strategic discussion, not status updates
- Every board meeting produces at least 2 logged decisions with named owners and deadlines
- Bad news is never a surprise in the meeting -- all material issues previewed via 1:1 pre-calls
- Board NPS (informal quarterly check) scores 8+/10 on meeting usefulness
- Deck assembly completes by T-2 days with zero last-minute scrambles in 90%+ of quarters
- Action items from previous meeting have 80%+ completion rate reported at the next meeting
---
Scope & Limitations
In Scope: Board deck structure, section templates, narrative frameworks, bad news delivery, metrics dashboards, deck assembly workflows, board prep checklists, quality validation.
Out of Scope: Actual financial data collection, slide design/visual formatting, board member relationship management, legal governance requirements, proxy statement preparation, regulatory filings.
Limitations: This skill provides structure and process but cannot replace the judgment required for crafting board narratives. The deck structure validator checks completeness, not quality of content. Metrics dashboard tools calculate RAG status from provided data but do not source live metrics.
---
Integration Points
| Skill | Integration |
|---|---|
ceo-advisor | Executive summary and strategic outlook sections; overall narrative direction |
cfo-advisor | Financial update section; all numbers validated through CFO tools |
cro-advisor | Revenue and pipeline section; ARR waterfall and forecast confidence |
cmo-advisor | Growth/marketing section; CAC by channel, marketing ROI data |
chro-advisor | Team/people section; headcount, attrition, engagement metrics |
ciso-advisor | Risk/security section; security posture, compliance status |
board-meeting | Deck feeds into the 6-phase board meeting protocol |
chief-of-staff | Prep checklist coordination; section owner orchestration |
company-os | Scorecard metrics flow directly into the metrics dashboard section |
#!/usr/bin/env python3
"""
Board Meeting Preparation Checklist - Tracks board meeting prep timeline and readiness.
Generates a T-minus timeline checklist for board meeting preparation,
tracks completion status, identifies blockers, and calculates overall readiness.
"""
import argparse
import json
import sys
from datetime import datetime, timedelta
DEFAULT_CHECKLIST = {
"t_minus_14": {
"label": "T-14 days: Agenda & Key Messages",
"tasks": [
{"id": "agenda_set", "task": "CEO sets agenda and identifies key messages", "owner": "CEO", "critical": True},
{"id": "topics_confirmed", "task": "Board chair confirms or adjusts proposed topics", "owner": "CEO", "critical": True},
{"id": "decision_items", "task": "List specific decisions requiring board vote", "owner": "CEO", "critical": False},
{"id": "section_owners", "task": "Assign section owners and brief them", "owner": "Chief of Staff", "critical": True},
{"id": "previous_actions", "task": "Review action items from previous meeting", "owner": "Chief of Staff", "critical": False},
]
},
"t_minus_10": {
"label": "T-10 days: Section Drafting Begins",
"tasks": [
{"id": "cfo_draft", "task": "CFO financial section first draft", "owner": "CFO", "critical": True},
{"id": "cro_draft", "task": "CRO revenue/pipeline section first draft", "owner": "CRO", "critical": True},
{"id": "cpo_draft", "task": "CPO product update first draft", "owner": "CPO", "critical": False},
{"id": "chro_draft", "task": "CHRO people section first draft", "owner": "CHRO", "critical": False},
{"id": "cmo_draft", "task": "CMO growth/marketing first draft", "owner": "CMO", "critical": False},
{"id": "cto_draft", "task": "CTO engineering/tech first draft", "owner": "CTO", "critical": False},
{"id": "ciso_draft", "task": "CISO risk/security first draft", "owner": "CISO", "critical": False},
]
},
"t_minus_7": {
"label": "T-7 days: First Drafts Due / CEO Review",
"tasks": [
{"id": "all_drafts_in", "task": "All section first drafts submitted", "owner": "All C-suite", "critical": True},
{"id": "ceo_narrative", "task": "CEO reviews for narrative alignment and through-line", "owner": "CEO", "critical": True},
{"id": "feedback_sent", "task": "CEO sends feedback to section owners", "owner": "CEO", "critical": True},
{"id": "bad_news_identified", "task": "Any bad news identified and SOUF framework prepared", "owner": "CEO", "critical": True},
]
},
"t_minus_5": {
"label": "T-5 days: Second Drafts / Financial Validation",
"tasks": [
{"id": "second_drafts", "task": "Second drafts incorporating CEO feedback", "owner": "All C-suite", "critical": True},
{"id": "cfo_validates", "task": "CFO validates all numbers across all sections match", "owner": "CFO", "critical": True},
{"id": "metrics_consistent", "task": "Metrics dashboard cross-checked against section data", "owner": "COO", "critical": True},
{"id": "asks_finalized", "task": "Specific board asks finalized with CEO", "owner": "CEO", "critical": False},
]
},
"t_minus_3": {
"label": "T-3 days: Final Assembly / Narrative Check",
"tasks": [
{"id": "deck_assembled", "task": "Final deck assembled in presentation format", "owner": "Chief of Staff", "critical": True},
{"id": "narrative_check", "task": "End-to-end narrative review (4-act structure)", "owner": "CEO", "critical": True},
{"id": "formatting_consistent", "task": "Formatting, fonts, colors consistent throughout", "owner": "Chief of Staff", "critical": False},
{"id": "appendix_current", "task": "Appendix materials are current and accurate", "owner": "CFO", "critical": False},
{"id": "speaker_notes", "task": "Speaker notes added for presentation slides", "owner": "CEO", "critical": False},
]
},
"t_minus_2": {
"label": "T-2 days: Board Distribution",
"tasks": [
{"id": "cover_note", "task": "Cover note with 3 key takeaways drafted", "owner": "CEO", "critical": True},
{"id": "deck_sent", "task": "Deck + cover note sent to all board members", "owner": "Chief of Staff", "critical": True},
{"id": "calendar_confirmed", "task": "Meeting logistics confirmed (time, location/link)", "owner": "Chief of Staff", "critical": True},
]
},
"t_minus_0": {
"label": "T-0: Meeting Day",
"tasks": [
{"id": "presenters_briefed", "task": "All presenters briefed on their section timing", "owner": "CEO", "critical": True},
{"id": "backup_ready", "task": "Backup presenter assigned for each section", "owner": "Chief of Staff", "critical": False},
{"id": "note_taker", "task": "Designated note-taker for decisions and action items", "owner": "Chief of Staff", "critical": True},
{"id": "tech_tested", "task": "Video/screen sharing tested (if remote)", "owner": "Chief of Staff", "critical": False},
]
},
}
def generate_checklist(meeting_date: str, completed: list = None, deck_type: str = "quarterly") -> dict:
"""Generate a board prep checklist with dates and status."""
completed = completed or []
try:
meeting_dt = datetime.strptime(meeting_date, "%Y-%m-%d")
except ValueError:
meeting_dt = datetime.now() + timedelta(days=14)
today = datetime.now().replace(hour=0, minute=0, second=0, microsecond=0)
days_until = (meeting_dt - today).days
results = {
"meeting_date": meeting_dt.strftime("%Y-%m-%d"),
"generated": datetime.now().isoformat(),
"days_until_meeting": days_until,
"deck_type": deck_type,
"overall_readiness_pct": 0,
"phases": [],
"overdue_tasks": [],
"upcoming_tasks": [],
"blockers": [],
"summary": {
"total_tasks": 0,
"completed": 0,
"pending": 0,
"overdue": 0,
"critical_incomplete": 0,
},
}
total_tasks = 0
completed_count = 0
t_minus_map = {
"t_minus_14": 14,
"t_minus_10": 10,
"t_minus_7": 7,
"t_minus_5": 5,
"t_minus_3": 3,
"t_minus_2": 2,
"t_minus_0": 0,
}
for phase_key, phase_data in DEFAULT_CHECKLIST.items():
t_days = t_minus_map[phase_key]
phase_date = meeting_dt - timedelta(days=t_days)
phase_overdue = today > phase_date
phase_result = {
"phase": phase_data["label"],
"due_date": phase_date.strftime("%Y-%m-%d"),
"is_overdue": phase_overdue and not all(t["id"] in completed for t in phase_data["tasks"]),
"tasks": [],
}
for task in phase_data["tasks"]:
total_tasks += 1
is_done = task["id"] in completed
if is_done:
completed_count += 1
task_result = {
"id": task["id"],
"task": task["task"],
"owner": task["owner"],
"critical": task["critical"],
"status": "done" if is_done else ("overdue" if phase_overdue else "pending"),
}
phase_result["tasks"].append(task_result)
if not is_done and phase_overdue:
results["overdue_tasks"].append(task_result)
results["summary"]["overdue"] += 1
if task["critical"]:
results["blockers"].append({
"task": task["task"],
"owner": task["owner"],
"due": phase_date.strftime("%Y-%m-%d"),
"days_overdue": (today - phase_date).days,
})
results["summary"]["critical_incomplete"] += 1
elif not is_done and not phase_overdue:
days_until_phase = (phase_date - today).days
if days_until_phase <= 3:
results["upcoming_tasks"].append({
"task": task["task"],
"owner": task["owner"],
"due_in_days": days_until_phase,
})
results["phases"].append(phase_result)
results["summary"]["total_tasks"] = total_tasks
results["summary"]["completed"] = completed_count
results["summary"]["pending"] = total_tasks - completed_count - results["summary"]["overdue"]
results["overall_readiness_pct"] = round(completed_count / total_tasks * 100) if total_tasks > 0 else 0
return results
def format_text(results: dict) -> str:
"""Format checklist as human-readable text."""
lines = [
"=" * 65,
"BOARD MEETING PREPARATION CHECKLIST",
"=" * 65,
f"Meeting Date: {results['meeting_date']}",
f"Days Until Meeting: {results['days_until_meeting']}",
f"Overall Readiness: {results['overall_readiness_pct']}%",
f"Tasks: {results['summary']['completed']}/{results['summary']['total_tasks']} complete "
f"({results['summary']['overdue']} overdue, {results['summary']['critical_incomplete']} critical blockers)",
"",
]
# Blockers first
if results["blockers"]:
lines.append("CRITICAL BLOCKERS:")
for b in results["blockers"]:
lines.append(f" [!!] {b['task']} (Owner: {b['owner']}, {b['days_overdue']} days overdue)")
lines.append("")
# Phase by phase
for phase in results["phases"]:
overdue_marker = " ** OVERDUE **" if phase["is_overdue"] else ""
lines.append(f"{phase['phase']} (Due: {phase['due_date']}){overdue_marker}")
for t in phase["tasks"]:
if t["status"] == "done":
icon = "[x]"
elif t["status"] == "overdue":
icon = "[!]" if t["critical"] else "[ ]"
else:
icon = "[ ]"
critical_mark = " *" if t["critical"] else ""
lines.append(f" {icon} {t['task']} ({t['owner']}){critical_mark}")
lines.append("")
# Upcoming tasks
if results["upcoming_tasks"]:
lines.append("UPCOMING (next 3 days):")
for t in results["upcoming_tasks"]:
lines.append(f" -> {t['task']} ({t['owner']}, due in {t['due_in_days']} days)")
lines.append("")
lines.append("* = critical task")
lines.append("=" * 65)
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Generate and track board meeting preparation checklist"
)
parser.add_argument(
"--meeting-date", "-d",
default=(datetime.now() + timedelta(days=14)).strftime("%Y-%m-%d"),
help="Meeting date in YYYY-MM-DD format (default: 14 days from now)"
)
parser.add_argument(
"--completed", "-c",
nargs="*",
default=[],
help="List of completed task IDs"
)
parser.add_argument(
"--input", "-i",
help="JSON file with completion status"
)
parser.add_argument(
"--type", "-t",
choices=["quarterly", "monthly", "fundraising", "emergency"],
default="quarterly",
help="Deck type (default: quarterly)"
)
parser.add_argument(
"--json", action="store_true",
help="Output as JSON"
)
parser.add_argument(
"--list-tasks", action="store_true",
help="List all task IDs for reference"
)
args = parser.parse_args()
if args.list_tasks:
print("Available task IDs:")
for phase_data in DEFAULT_CHECKLIST.values():
print(f"\n{phase_data['label']}:")
for task in phase_data["tasks"]:
crit = " [CRITICAL]" if task["critical"] else ""
print(f" {task['id']}: {task['task']}{crit}")
return
completed = args.completed or []
if args.input:
with open(args.input) as f:
data = json.load(f)
completed = data.get("completed", [])
results = generate_checklist(args.meeting_date, completed, args.type)
if args.json:
print(json.dumps(results, indent=2))
else:
print(format_text(results))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Board Deck Structure Validator - Validates board deck completeness and quality.
Checks a board deck outline against best-practice section requirements,
identifies missing sections, flags quality issues, and scores overall readiness.
"""
import argparse
import json
import sys
from datetime import datetime
REQUIRED_SECTIONS = {
"executive_summary": {
"owner": "CEO",
"weight": 15,
"checks": ["three_sentence_rule", "forward_looking", "no_vague_language"],
"description": "3-sentence executive summary with state, development, priority"
},
"metrics_dashboard": {
"owner": "COO",
"weight": 15,
"checks": ["has_targets", "has_status_indicators", "max_8_metrics"],
"description": "6-8 key metrics with targets and RAG status"
},
"financial_update": {
"owner": "CFO",
"weight": 15,
"checks": ["pnl_summary", "cash_position", "variance_explanations", "forecast"],
"description": "P&L, cash position, burn multiple, variance to plan, forecast"
},
"revenue_pipeline": {
"owner": "CRO",
"weight": 10,
"checks": ["arr_waterfall", "nrr_trend", "pipeline_by_stage", "forecast_confidence"],
"description": "ARR waterfall, NRR, pipeline, forecast with confidence"
},
"product_update": {
"owner": "CPO",
"weight": 8,
"checks": ["shipped_items", "upcoming_items", "pmf_signals"],
"description": "Shipped, shipping next, PMF signals, key learning"
},
"growth_marketing": {
"owner": "CMO",
"weight": 7,
"checks": ["cac_by_channel", "pipeline_contribution", "experiments"],
"description": "CAC by channel, pipeline contribution, what works/cut/testing"
},
"engineering_technical": {
"owner": "CTO",
"weight": 5,
"checks": ["delivery_velocity", "tech_debt", "infrastructure"],
"description": "Delivery velocity, tech debt, infrastructure, security posture"
},
"team_people": {
"owner": "CHRO",
"weight": 8,
"checks": ["headcount_vs_plan", "hiring_pipeline", "attrition", "engagement"],
"description": "Headcount, hiring, attrition, engagement, notable changes"
},
"risk_security": {
"owner": "CISO",
"weight": 5,
"checks": ["security_posture", "compliance_status", "incidents", "top_risks"],
"description": "Security posture, compliance, incidents, top 3 risks"
},
"strategic_outlook": {
"owner": "CEO",
"weight": 10,
"checks": ["next_quarter_priorities", "board_decisions_needed", "specific_asks"],
"description": "Next quarter priorities, decisions needed, specific asks"
},
"appendix": {
"owner": "Multiple",
"weight": 2,
"checks": ["financial_model", "pipeline_data", "cohort_charts"],
"description": "Detailed data (not presented unless asked)"
}
}
DECK_TYPES = {
"quarterly": {"max_slides": 30, "min_slides": 20, "advance_send_hours": 48},
"monthly": {"max_slides": 12, "min_slides": 8, "advance_send_hours": 24},
"fundraising": {"max_slides": 15, "min_slides": 12, "advance_send_hours": 0},
"emergency": {"max_slides": 8, "min_slides": 5, "advance_send_hours": 0},
}
QUALITY_CHECKS = [
{"id": "narrative_throughline", "description": "Deck follows 4-act structure (where we said, where we are, why gap, what we do)", "severity": "high"},
{"id": "opening_by_slide_3", "description": "Key message clear by slide 3", "severity": "high"},
{"id": "no_vague_asks", "description": "Asks section has specific, actionable requests", "severity": "high"},
{"id": "numbers_consistent", "description": "Financial numbers consistent across sections", "severity": "critical"},
{"id": "bad_news_souf", "description": "Bad news uses SOUF framework (State, Own, Understand, Fix)", "severity": "medium"},
{"id": "max_6_lines_per_slide", "description": "No slide exceeds 6 lines of text", "severity": "medium"},
{"id": "variance_explained", "description": "Every variance from target has a one-sentence cause", "severity": "high"},
{"id": "action_items_from_last", "description": "Follow-up on previous meeting action items included", "severity": "medium"},
{"id": "consistent_formatting", "description": "One template, one color scheme, one font throughout", "severity": "low"},
{"id": "pre_read_ready", "description": "Deck works as a standalone document for pre-reading", "severity": "medium"},
]
def validate_deck(deck_data: dict) -> dict:
"""Validate a board deck against best practices."""
deck_type = deck_data.get("type", "quarterly")
sections = deck_data.get("sections", {})
slide_count = deck_data.get("slide_count", 0)
quality_flags = deck_data.get("quality_flags", {})
type_config = DECK_TYPES.get(deck_type, DECK_TYPES["quarterly"])
results = {
"timestamp": datetime.now().isoformat(),
"deck_type": deck_type,
"overall_score": 0,
"max_score": 100,
"grade": "",
"section_results": [],
"missing_sections": [],
"quality_issues": [],
"slide_count_assessment": {},
"recommendations": [],
}
# Section completeness
total_weight = 0
earned_weight = 0
for section_id, config in REQUIRED_SECTIONS.items():
section_present = section_id in sections
section_data = sections.get(section_id, {})
checks_passed = 0
checks_total = len(config["checks"])
failed_checks = []
if section_present:
for check in config["checks"]:
if section_data.get(check, False):
checks_passed += 1
else:
failed_checks.append(check)
completeness = (checks_passed / checks_total) if checks_total > 0 else 0
section_score = config["weight"] * completeness
earned_weight += section_score
results["section_results"].append({
"section": section_id,
"owner": config["owner"],
"present": True,
"checks_passed": checks_passed,
"checks_total": checks_total,
"completeness_pct": round(completeness * 100),
"score": round(section_score, 1),
"max_score": config["weight"],
"failed_checks": failed_checks,
})
else:
results["missing_sections"].append({
"section": section_id,
"owner": config["owner"],
"weight": config["weight"],
"description": config["description"],
})
results["section_results"].append({
"section": section_id,
"owner": config["owner"],
"present": False,
"checks_passed": 0,
"checks_total": checks_total,
"completeness_pct": 0,
"score": 0,
"max_score": config["weight"],
"failed_checks": config["checks"],
})
total_weight += config["weight"]
# Slide count assessment
if slide_count > 0:
if slide_count > type_config["max_slides"]:
results["slide_count_assessment"] = {
"status": "over",
"count": slide_count,
"max": type_config["max_slides"],
"message": f"Too many slides ({slide_count} vs max {type_config['max_slides']}). Cut ruthlessly.",
}
elif slide_count < type_config["min_slides"]:
results["slide_count_assessment"] = {
"status": "under",
"count": slide_count,
"min": type_config["min_slides"],
"message": f"Too few slides ({slide_count} vs min {type_config['min_slides']}). May lack depth.",
}
else:
results["slide_count_assessment"] = {
"status": "ok",
"count": slide_count,
"range": f"{type_config['min_slides']}-{type_config['max_slides']}",
"message": "Slide count within recommended range.",
}
# Quality checks
for check in QUALITY_CHECKS:
passed = quality_flags.get(check["id"], False)
if not passed:
results["quality_issues"].append({
"check": check["id"],
"description": check["description"],
"severity": check["severity"],
})
# Calculate overall score
quality_penalty = sum(
3 if issue["severity"] == "critical"
else 2 if issue["severity"] == "high"
else 1
for issue in results["quality_issues"]
)
raw_score = (earned_weight / total_weight * 100) if total_weight > 0 else 0
results["overall_score"] = max(0, round(raw_score - quality_penalty))
# Grade
score = results["overall_score"]
if score >= 90:
results["grade"] = "A"
elif score >= 80:
results["grade"] = "B"
elif score >= 70:
results["grade"] = "C"
elif score >= 60:
results["grade"] = "D"
else:
results["grade"] = "F"
# Recommendations
if results["missing_sections"]:
owners = set(s["owner"] for s in results["missing_sections"])
results["recommendations"].append(
f"Missing {len(results['missing_sections'])} sections. Contact: {', '.join(sorted(owners))}"
)
critical_quality = [i for i in results["quality_issues"] if i["severity"] == "critical"]
if critical_quality:
results["recommendations"].append(
f"CRITICAL: {len(critical_quality)} critical quality issue(s) must be fixed before board send."
)
high_quality = [i for i in results["quality_issues"] if i["severity"] == "high"]
if high_quality:
results["recommendations"].append(
f"HIGH: {len(high_quality)} high-severity quality issue(s) should be addressed."
)
incomplete = [s for s in results["section_results"] if s["present"] and s["completeness_pct"] < 80]
if incomplete:
results["recommendations"].append(
f"{len(incomplete)} section(s) below 80% completeness: {', '.join(s['section'] for s in incomplete)}"
)
return results
def format_text(results: dict) -> str:
"""Format results as human-readable text."""
lines = [
"=" * 60,
"BOARD DECK STRUCTURE VALIDATION REPORT",
"=" * 60,
f"Deck Type: {results['deck_type'].upper()}",
f"Date: {results['timestamp'][:10]}",
f"Overall Score: {results['overall_score']}/100 (Grade: {results['grade']})",
"",
]
# Slide count
sca = results.get("slide_count_assessment", {})
if sca:
status_icon = {"ok": "[OK]", "over": "[!]", "under": "[?]"}.get(sca.get("status", ""), "")
lines.append(f"Slide Count: {status_icon} {sca.get('message', '')}")
lines.append("")
# Section results
lines.append("SECTION COMPLETENESS:")
lines.append("-" * 60)
lines.append(f"{'Section':<25} {'Owner':<8} {'Score':>6} {'Complete':>9} {'Status'}")
lines.append("-" * 60)
for s in results["section_results"]:
status = "PRESENT" if s["present"] else "MISSING"
icon = "[G]" if s["completeness_pct"] >= 80 else "[Y]" if s["completeness_pct"] >= 50 else "[R]"
if not s["present"]:
icon = "[R]"
lines.append(
f"{s['section']:<25} {s['owner']:<8} {s['score']:>5.1f}/{s['max_score']:<2} "
f"{s['completeness_pct']:>7}% {icon} {status}"
)
lines.append("")
# Missing sections
if results["missing_sections"]:
lines.append("MISSING SECTIONS:")
for ms in results["missing_sections"]:
lines.append(f" - {ms['section']} ({ms['owner']}): {ms['description']}")
lines.append("")
# Quality issues
if results["quality_issues"]:
lines.append("QUALITY ISSUES:")
for qi in results["quality_issues"]:
sev = qi["severity"].upper()
lines.append(f" [{sev}] {qi['description']}")
lines.append("")
# Recommendations
if results["recommendations"]:
lines.append("RECOMMENDATIONS:")
for rec in results["recommendations"]:
lines.append(f" -> {rec}")
lines.append("")
lines.append("=" * 60)
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Validate board deck structure against best practices"
)
parser.add_argument(
"--input", "-i",
help="JSON file with deck structure data (stdin if omitted)"
)
parser.add_argument(
"--type", "-t",
choices=["quarterly", "monthly", "fundraising", "emergency"],
default="quarterly",
help="Deck type (default: quarterly)"
)
parser.add_argument(
"--slides", "-s",
type=int, default=0,
help="Number of slides in the deck"
)
parser.add_argument(
"--sections",
nargs="*",
help="List of section IDs present in the deck"
)
parser.add_argument(
"--json", action="store_true",
help="Output as JSON"
)
args = parser.parse_args()
# Build deck data
if args.input:
with open(args.input) as f:
deck_data = json.load(f)
elif args.sections:
deck_data = {
"type": args.type,
"slide_count": args.slides,
"sections": {s: {} for s in args.sections},
}
else:
# Demo mode
deck_data = {
"type": args.type,
"slide_count": args.slides if args.slides else 24,
"sections": {
"executive_summary": {"three_sentence_rule": True, "forward_looking": True, "no_vague_language": False},
"metrics_dashboard": {"has_targets": True, "has_status_indicators": True, "max_8_metrics": True},
"financial_update": {"pnl_summary": True, "cash_position": True, "variance_explanations": False, "forecast": True},
"revenue_pipeline": {"arr_waterfall": True, "nrr_trend": True, "pipeline_by_stage": True, "forecast_confidence": False},
"product_update": {"shipped_items": True, "upcoming_items": True, "pmf_signals": False},
"strategic_outlook": {"next_quarter_priorities": True, "board_decisions_needed": True, "specific_asks": False},
},
"quality_flags": {
"narrative_throughline": True,
"opening_by_slide_3": True,
"numbers_consistent": True,
"action_items_from_last": False,
},
}
deck_data["type"] = deck_data.get("type", args.type)
if args.slides:
deck_data["slide_count"] = args.slides
results = validate_deck(deck_data)
if args.json:
print(json.dumps(results, indent=2))
else:
print(format_text(results))
if __name__ == "__main__":
main()
#!/usr/bin/env python3
"""
Board Metrics Dashboard Generator - Generates formatted board-ready metrics dashboards.
Takes raw metric values and targets, calculates status (RAG), trends, and produces
a formatted dashboard suitable for board decks. Supports multiple output formats.
"""
import argparse
import csv
import io
import json
import sys
from datetime import datetime
RAG_THRESHOLDS = {
"higher_is_better": {"green": 0.95, "yellow": 0.85},
"lower_is_better": {"green": 1.05, "yellow": 1.15},
"exact": {"green": 0.02, "yellow": 0.05},
}
def calculate_rag(actual: float, target: float, direction: str = "higher_is_better") -> str:
"""Calculate RAG status based on actual vs target."""
if target == 0:
return "N/A"
ratio = actual / target
thresholds = RAG_THRESHOLDS.get(direction, RAG_THRESHOLDS["higher_is_better"])
if direction == "higher_is_better":
if ratio >= thresholds["green"]:
return "G"
elif ratio >= thresholds["yellow"]:
return "Y"
else:
return "R"
elif direction == "lower_is_better":
if ratio <= thresholds["green"]:
return "G"
elif ratio <= thresholds["yellow"]:
return "Y"
else:
return "R"
else:
deviation = abs(1 - ratio)
if deviation <= thresholds["green"]:
return "G"
elif deviation <= thresholds["yellow"]:
return "Y"
else:
return "R"
def calculate_trend(current: float, previous: float) -> str:
"""Determine trend direction."""
if previous == 0:
return "New"
change = (current - previous) / abs(previous)
if change > 0.02:
return "Up"
elif change < -0.02:
return "Down"
else:
return "Flat"
def format_value(value: float, fmt: str = "number") -> str:
"""Format a metric value for display."""
if fmt == "currency":
if abs(value) >= 1_000_000:
return f"${value / 1_000_000:.1f}M"
elif abs(value) >= 1_000:
return f"${value / 1_000:.0f}K"
else:
return f"${value:,.0f}"
elif fmt == "percent":
return f"{value:.1f}%"
elif fmt == "ratio":
return f"{value:.1f}x"
elif fmt == "months":
return f"{value:.0f} mo"
elif fmt == "integer":
return f"{int(value)}"
else:
return f"{value:,.1f}"
def generate_dashboard(metrics: list) -> dict:
"""Generate a complete metrics dashboard."""
dashboard = {
"timestamp": datetime.now().isoformat(),
"metrics_count": len(metrics),
"summary": {"green": 0, "yellow": 0, "red": 0, "na": 0},
"metrics": [],
"alerts": [],
"recommendations": [],
}
for m in metrics:
name = m.get("name", "Unnamed")
actual = float(m.get("actual", 0))
target = float(m.get("target", 0))
previous = float(m.get("previous", 0))
direction = m.get("direction", "higher_is_better")
fmt = m.get("format", "number")
owner = m.get("owner", "Unassigned")
rag = calculate_rag(actual, target, direction)
trend = calculate_trend(actual, previous)
variance_pct = ((actual - target) / abs(target) * 100) if target != 0 else 0
metric_result = {
"name": name,
"actual": actual,
"actual_formatted": format_value(actual, fmt),
"target": target,
"target_formatted": format_value(target, fmt),
"previous": previous,
"previous_formatted": format_value(previous, fmt),
"variance_pct": round(variance_pct, 1),
"status": rag,
"trend": trend,
"owner": owner,
}
dashboard["metrics"].append(metric_result)
# Update summary
if rag == "G":
dashboard["summary"]["green"] += 1
elif rag == "Y":
dashboard["summary"]["yellow"] += 1
elif rag == "R":
dashboard["summary"]["red"] += 1
else:
dashboard["summary"]["na"] += 1
# Generate alerts for red metrics
if rag == "R":
dashboard["alerts"].append({
"metric": name,
"owner": owner,
"message": f"{name} is {format_value(actual, fmt)} vs target {format_value(target, fmt)} ({variance_pct:+.1f}%)",
})
# Recommendations
red_count = dashboard["summary"]["red"]
if red_count >= 3:
dashboard["recommendations"].append(
f"{red_count} metrics in RED. Schedule an executive review before board meeting."
)
if red_count > 0:
owners_with_red = set(a["owner"] for a in dashboard["alerts"])
dashboard["recommendations"].append(
f"Variance explanations needed from: {', '.join(sorted(owners_with_red))}"
)
if dashboard["summary"]["green"] == len(metrics):
dashboard["recommendations"].append(
"All metrics green. Verify targets are ambitious enough."
)
return dashboard
def format_text(dashboard: dict) -> str:
"""Format dashboard as board-ready text."""
lines = [
"=" * 72,
"KEY METRICS DASHBOARD",
"=" * 72,
f"Date: {dashboard['timestamp'][:10]}",
f"Summary: {dashboard['summary']['green']}G / {dashboard['summary']['yellow']}Y / {dashboard['summary']['red']}R",
"",
f"{'Metric':<22} {'Actual':>10} {'Target':>10} {'Prev':>10} {'Var%':>7} {'RAG':>4} {'Trend':<6} {'Owner':<8}",
"-" * 72,
]
for m in dashboard["metrics"]:
rag_display = f"[{m['status']}]"
lines.append(
f"{m['name']:<22} {m['actual_formatted']:>10} {m['target_formatted']:>10} "
f"{m['previous_formatted']:>10} {m['variance_pct']:>+6.1f}% {rag_display:>4} "
f"{m['trend']:<6} {m['owner']:<8}"
)
if dashboard["alerts"]:
lines.extend(["", "ALERTS:"])
for a in dashboard["alerts"]:
lines.append(f" [R] {a['message']} (Owner: {a['owner']})")
if dashboard["recommendations"]:
lines.extend(["", "RECOMMENDATIONS:"])
for r in dashboard["recommendations"]:
lines.append(f" -> {r}")
lines.extend(["", "=" * 72])
return "\n".join(lines)
def main():
parser = argparse.ArgumentParser(
description="Generate board-ready metrics dashboard"
)
parser.add_argument(
"--input", "-i",
help="JSON file with metrics data (uses demo data if omitted)"
)
parser.add_argument(
"--csv",
help="CSV file with metrics (columns: name,actual,target,previous,direction,format,owner)"
)
parser.add_argument(
"--json", action="store_true",
help="Output as JSON"
)
args = parser.parse_args()
if args.input:
with open(args.input) as f:
metrics = json.load(f)
if isinstance(metrics, dict):
metrics = metrics.get("metrics", [])
elif args.csv:
metrics = []
with open(args.csv) as f:
reader = csv.DictReader(f)
for row in reader:
for field in ["actual", "target", "previous"]:
if field in row:
row[field] = float(row[field])
metrics.append(row)
else:
# Demo data
metrics = [
{"name": "ARR", "actual": 2400000, "target": 2300000, "previous": 1970000, "direction": "higher_is_better", "format": "currency", "owner": "CRO"},
{"name": "MoM Growth", "actual": 8.1, "target": 7.5, "previous": 7.2, "direction": "higher_is_better", "format": "percent", "owner": "CRO"},
{"name": "Burn Multiple", "actual": 1.8, "target": 2.0, "previous": 2.1, "direction": "lower_is_better", "format": "ratio", "owner": "CFO"},
{"name": "NRR", "actual": 112, "target": 110, "previous": 108, "direction": "higher_is_better", "format": "percent", "owner": "CRO"},
{"name": "CAC Payback", "actual": 11, "target": 12, "previous": 14, "direction": "lower_is_better", "format": "months", "owner": "CMO"},
{"name": "Headcount", "actual": 24, "target": 25, "previous": 21, "direction": "higher_is_better", "format": "integer", "owner": "CHRO"},
{"name": "Runway", "actual": 16, "target": 18, "previous": 19, "direction": "higher_is_better", "format": "months", "owner": "CFO"},
{"name": "eNPS", "actual": 42, "target": 30, "previous": 38, "direction": "higher_is_better", "format": "integer", "owner": "CHRO"},
]
dashboard = generate_dashboard(metrics)
if args.json:
print(json.dumps(dashboard, indent=2))
else:
print(format_text(dashboard))
if __name__ == "__main__":
main()
Related skills
FAQ
What deck types does it support?
Quarterly board deck, monthly update, fundraising deck, and emergency/ad-hoc, each with a set structure and slide count.
What does it require before generating?
Deck type and audience, the headline message this period, which metrics the board tracks, and the specific asks or decisions needed.