Chapter 60

Capstone - Complete Analytics System

Intermediate 30 min read 5 sections 10 code examples
0 of 60 chapters completed (0%)
Learning Objectives
  • Design organizational structures for analytics departments
  • Define roles, responsibilities, and skill requirements
  • Build effective workflows and communication channels
  • Select and implement appropriate technology stacks
  • Integrate analytics with existing football operations

Building a successful analytics department requires more than hiring data scientists. It demands careful organizational design, clear processes, the right technology, and—most critically—effective integration with the football side of the club. This chapter provides a blueprint for building analytics capabilities from the ground up.

Analytics Department Structure

The structure of an analytics department varies based on club size, budget, and strategic priorities. We'll examine models ranging from a single analyst to a full department of 10+ staff.

Starter (1-2 staff)
Typical: Lower league clubs
  • 1 Data Analyst (generalist)
  • Part-time video analyst
  • Reports to: Head Coach or DoF
  • Budget: €50-100k/year
Growth (3-5 staff)
Typical: Mid-table top flight
  • 1 Head of Analytics
  • 2 Data Analysts
  • 1-2 Video Analysts
  • Reports to: DoF/Technical Director
  • Budget: €300-500k/year
Enterprise (8+ staff)
Typical: Top clubs, multi-club groups
  • Director of Analytics
  • Data Engineers
  • ML Engineers
  • Analysts (performance, recruitment)
  • Budget: €1M+/year
department_structure.R / department_structure.py
# Python: Define analytics department structure
import pandas as pd
from dataclasses import dataclass
from typing import List, Dict
from enum import Enum

class DepartmentSize(Enum):
    STARTER = "Starter"
    GROWTH = "Growth"
    ENTERPRISE = "Enterprise"

@dataclass
class Role:
    """Analytics department role definition."""
    title: str
    reports_to: str
    primary_stakeholders: List[str]
    key_skills: List[str]
    min_experience_years: int
    salary_range: tuple  # (min, max) in thousands

# Define standard roles
ANALYTICS_ROLES = {
    "director": Role(
        title="Director of Analytics",
        reports_to="Technical Director / CEO",
        primary_stakeholders=["Board", "Technical Director", "Head Coach"],
        key_skills=["Leadership", "Strategy", "Communication", "Football Knowledge"],
        min_experience_years=8,
        salary_range=(120, 200)
    ),
    "head_performance": Role(
        title="Head of Performance Analytics",
        reports_to="Director of Analytics",
        primary_stakeholders=["First Team Coach", "Sports Science"],
        key_skills=["Sports Science", "Statistics", "Communication", "SQL"],
        min_experience_years=5,
        salary_range=(70, 100)
    ),
    "head_recruitment": Role(
        title="Head of Recruitment Analytics",
        reports_to="Director of Analytics",
        primary_stakeholders=["Director of Football", "Chief Scout"],
        key_skills=["Scouting", "Statistics", "Market Analysis", "Communication"],
        min_experience_years=5,
        salary_range=(70, 100)
    ),
    "data_engineer": Role(
        title="Data Engineer",
        reports_to="Director of Analytics",
        primary_stakeholders=["All Analytics Staff"],
        key_skills=["SQL", "Python", "Cloud (AWS/GCP)", "ETL", "APIs"],
        min_experience_years=3,
        salary_range=(60, 90)
    ),
    "senior_analyst": Role(
        title="Senior Data Analyst",
        reports_to="Head of Performance/Recruitment",
        primary_stakeholders=["Coaching Staff", "Scouts"],
        key_skills=["Statistics", "Python/R", "Visualization", "Football"],
        min_experience_years=3,
        salary_range=(50, 75)
    ),
    "analyst": Role(
        title="Data Analyst",
        reports_to="Senior Data Analyst",
        primary_stakeholders=["Coaching Staff", "Scouts"],
        key_skills=["Statistics", "Python/R", "Excel", "Visualization"],
        min_experience_years=1,
        salary_range=(35, 55)
    )
}

class DepartmentPlanner:
    """Plan analytics department structure based on budget."""

    def __init__(self, annual_budget: float):
        self.budget = annual_budget
        self.salary_budget = annual_budget * 0.70
        self.data_budget = annual_budget * 0.20
        self.tools_budget = annual_budget * 0.10

    def recommend_structure(self) -> Dict:
        """Recommend department structure based on budget."""
        avg_salary = 55000  # Average across all roles

        max_headcount = int(self.salary_budget / avg_salary)

        if max_headcount >= 8:
            size = DepartmentSize.ENTERPRISE
            recommended_roles = self._enterprise_structure()
        elif max_headcount >= 3:
            size = DepartmentSize.GROWTH
            recommended_roles = self._growth_structure()
        else:
            size = DepartmentSize.STARTER
            recommended_roles = self._starter_structure()

        return {
            "size": size.value,
            "max_headcount": max_headcount,
            "salary_budget": self.salary_budget,
            "data_budget": self.data_budget,
            "tools_budget": self.tools_budget,
            "recommended_roles": recommended_roles
        }

    def _starter_structure(self) -> List[str]:
        return ["Data Analyst (generalist)", "Part-time Video Analyst"]

    def _growth_structure(self) -> List[str]:
        return [
            "Head of Analytics",
            "Performance Analyst",
            "Recruitment Analyst",
            "Video Analyst"
        ]

    def _enterprise_structure(self) -> List[str]:
        return [
            "Director of Analytics",
            "Head of Performance Analytics",
            "Head of Recruitment Analytics",
            "Data Engineer",
            "Senior Data Analyst x2",
            "Data Analyst x2",
            "ML Engineer",
            "Video Analyst x2"
        ]

# Example
planner = DepartmentPlanner(400000)
structure = planner.recommend_structure()
print(f"Recommended: {structure['size']} ({structure['max_headcount']} staff)")
# R: Define analytics department structure
library(tidyverse)

# Role definitions for analytics department
roles <- tibble(
  role = c(
    "Director of Analytics",
    "Head of Performance Analytics",
    "Head of Recruitment Analytics",
    "Senior Data Analyst",
    "Data Analyst",
    "Data Engineer",
    "ML Engineer",
    "Video Analyst",
    "Junior Analyst"
  ),
  reports_to = c(
    "Technical Director",
    "Director of Analytics",
    "Director of Analytics",
    "Head of Performance/Recruitment",
    "Senior Data Analyst",
    "Director of Analytics",
    "Director of Analytics",
    "Head of Performance",
    "Senior Data Analyst"
  ),
  primary_stakeholders = c(
    "Board, Technical Director",
    "First Team Coach, Sports Science",
    "DoF, Scouting",
    "Coaching Staff",
    "Coaching Staff, Scouts",
    "All Analytics",
    "All Analytics",
    "Coaching Staff",
    "Senior Analysts"
  ),
  key_skills = c(
    "Leadership, Strategy, Communication",
    "Sports Science, Statistics, Communication",
    "Scouting, Statistics, Markets",
    "Statistics, Programming, Football Knowledge",
    "Statistics, Programming, Visualization",
    "SQL, Cloud, ETL, APIs",
    "ML/AI, Python, Deep Learning",
    "Video Software, Football Tactics",
    "Statistics, Excel, Learning Attitude"
  ),
  min_experience_years = c(8, 5, 5, 3, 1, 3, 3, 2, 0)
)

# Department size calculator
calculate_department_size <- function(budget, avg_salary = 60000) {
  # Rule of thumb: 70% budget on salaries, 20% on data, 10% on tools
  salary_budget <- budget * 0.70
  headcount <- floor(salary_budget / avg_salary)

  # Minimum viable structure
  if (headcount >= 8) {
    structure <- "Enterprise"
  } else if (headcount >= 3) {
    structure <- "Growth"
  } else {
    structure <- "Starter"
  }

  list(
    budget = budget,
    salary_budget = salary_budget,
    data_budget = budget * 0.20,
    tools_budget = budget * 0.10,
    max_headcount = headcount,
    structure = structure
  )
}

# Example
dept_size <- calculate_department_size(400000)
cat(sprintf("Structure: %s | Max Headcount: %d\n",
            dept_size$structure, dept_size$max_headcount))
Output
Recommended: Growth (5 staff)

Roles and Responsibilities

Clear role definitions prevent overlap, ensure coverage of key areas, and help with hiring. Here we detail the core roles in a football analytics department.

Responsibilities
  • Set strategic direction for analytics
  • Build and manage the team
  • Present to board and ownership
  • Manage data provider relationships
  • Ensure analytics integration with football ops
Key Skills
  • Leadership and people management
  • Strategic thinking
  • Executive communication
  • Deep football knowledge
  • Technical credibility

Responsibilities
  • Match day analytics (pre/post)
  • Opposition analysis reports
  • Individual player performance tracking
  • Training load monitoring support
  • Coach-facing presentations
Key Skills
  • Strong football tactical knowledge
  • Data visualization
  • Communication with coaches
  • SQL, Python/R
  • Working under time pressure

Responsibilities
  • Player identification and screening
  • Scouting report generation
  • Transfer target analysis
  • Player valuation models
  • Contract and squad planning support
Key Skills
  • Understanding of scouting process
  • Statistical modeling
  • Market knowledge
  • Report writing
  • Collaboration with scouts

Responsibilities
  • Build and maintain data pipelines
  • Database design and management
  • API integrations with data providers
  • Data quality monitoring
  • Infrastructure and deployment
Key Skills
  • SQL and database management
  • Python, ETL tools
  • Cloud platforms (AWS/GCP/Azure)
  • API development
  • DevOps basics
role_workflows.R / role_workflows.py
# Python: Role-based workflow assignment
import pandas as pd
from typing import Dict, List

# Define workflow ownership
workflows = pd.DataFrame({
    "workflow": [
        "Pre-match opposition report",
        "Post-match performance report",
        "Weekly recruitment shortlist",
        "Transfer window targets",
        "Injury risk dashboard",
        "Season review presentation",
        "Live match stats feed",
        "Player development reports"
    ],
    "primary_owner": [
        "Performance Analyst",
        "Performance Analyst",
        "Recruitment Analyst",
        "Recruitment Analyst",
        "Performance Analyst",
        "Head of Analytics",
        "Data Engineer",
        "Performance Analyst"
    ],
    "stakeholders": [
        "First Team Coach",
        "First Team Coach, Players",
        "Chief Scout, DoF",
        "DoF, Board",
        "Sports Science, Medical",
        "Board, Technical Director",
        "Coaching Staff",
        "Academy Director"
    ],
    "frequency": [
        "Per match (MD-2)",
        "Per match (MD+1)",
        "Weekly",
        "Window periods",
        "Daily",
        "Annual",
        "Real-time",
        "Monthly"
    ],
    "estimated_hours": [4, 3, 8, 20, 1, 40, 2, 6]
})

# Calculate workload
workload = workflows.groupby("primary_owner").agg({
    "workflow": "count",
    "estimated_hours": "sum"
}).reset_index()
workload.columns = ["role", "workflow_count", "total_hours"]

print("Workflow ownership by role:")
print(workload)
# R: Role-based workflow assignment
library(tidyverse)

# Define workflow ownership
workflow_ownership <- tibble(
  workflow = c(
    "Pre-match opposition report",
    "Post-match performance report",
    "Weekly recruitment shortlist",
    "Transfer window targets",
    "Injury risk dashboard",
    "Season review presentation",
    "Live match stats feed",
    "Player development reports"
  ),
  primary_owner = c(
    "Performance Analyst",
    "Performance Analyst",
    "Recruitment Analyst",
    "Recruitment Analyst",
    "Performance Analyst",
    "Head of Analytics",
    "Data Engineer",
    "Performance Analyst"
  ),
  stakeholders = c(
    "First Team Coach",
    "First Team Coach, Players",
    "Chief Scout, DoF",
    "DoF, Board",
    "Sports Science, Medical",
    "Board, Technical Director",
    "Coaching Staff",
    "Academy Director"
  ),
  frequency = c(
    "Per match (MD-2)",
    "Per match (MD+1)",
    "Weekly",
    "Window periods",
    "Daily",
    "Annual",
    "Real-time during matches",
    "Monthly"
  ),
  estimated_hours = c(4, 3, 8, 20, 1, 40, 2, 6)
)

# Calculate workload by role
workload_by_role <- workflow_ownership %>%
  group_by(primary_owner) %>%
  summarise(
    workflows = n(),
    weekly_hours = sum(estimated_hours),
    .groups = "drop"
  )

print(workload_by_role)
Output
Workflow ownership by role:
                    role  workflow_count  total_hours
0         Data Engineer               1            2
1     Head of Analytics               1           40
2  Performance Analyst               4           14
3  Recruitment Analyst               2           28

Technology Stack

The right technology stack enables efficient workflows without unnecessary complexity. Here we outline common tools and considerations for different department sizes.

Recommended Technology Stack
Layer Starter Growth Enterprise
Data Storage PostgreSQL, SQLite PostgreSQL, S3 Snowflake, BigQuery, Data Lake
Analysis Python, R, Excel Python, R, SQL Python, R, Spark, dbt
Visualization Excel, Matplotlib Tableau, Power BI Tableau, Custom Dashboards
Video Hudl, Sportscode Hudl, Custom integration Custom platform, API integration
Collaboration Google Workspace Notion, Confluence Custom internal tools
Infrastructure Local servers AWS/GCP basic Full cloud, Kubernetes
tech_stack.R / tech_stack.py
# Python: Technology stack cost calculator
import pandas as pd
from typing import Dict, List

# Define tool costs
tool_costs = pd.DataFrame({
    "category": [
        "Data Provider", "Data Provider", "Data Provider",
        "Cloud", "Cloud",
        "Visualization", "Visualization",
        "Video", "Video",
        "Collaboration"
    ],
    "tool": [
        "StatsBomb", "Opta", "Wyscout",
        "AWS Basic", "AWS Enterprise",
        "Tableau", "Power BI",
        "Hudl", "Sportscode",
        "Notion/Confluence"
    ],
    "annual_cost": [
        50000, 100000, 30000,
        6000, 50000,
        15000, 10000,
        10000, 25000,
        2000
    ],
    "tier": [
        "Growth", "Enterprise", "Starter",
        "Growth", "Enterprise",
        "Growth", "Starter",
        "Starter", "Growth",
        "Starter"
    ]
})

def calculate_stack_cost(tier: str) -> Dict:
    """Calculate technology stack cost for a tier."""
    # Filter to tier and starter (baseline)
    available = tool_costs[
        (tool_costs["tier"] == tier) | (tool_costs["tier"] == "Starter")
    ]

    # Take cheapest per category
    selected = available.loc[
        available.groupby("category")["annual_cost"].idxmin()
    ]

    return {
        "tier": tier,
        "total_cost": selected["annual_cost"].sum(),
        "tools": selected["tool"].tolist(),
        "breakdown": selected[["category", "tool", "annual_cost"]].to_dict("records")
    }

# Compare tiers
for tier in ["Starter", "Growth", "Enterprise"]:
    result = calculate_stack_cost(tier)
    print(f"{tier}: €{result['total_cost']:,} ({len(result['tools'])} tools)")
# R: Technology stack cost calculator
library(tidyverse)

# Define tool costs (annual, approximate)
tool_costs <- tibble(
  category = c(
    "Data Provider", "Data Provider", "Data Provider",
    "Cloud", "Cloud",
    "Visualization", "Visualization",
    "Video", "Video",
    "Collaboration"
  ),
  tool = c(
    "StatsBomb", "Opta", "Wyscout",
    "AWS Basic", "AWS Enterprise",
    "Tableau", "Power BI",
    "Hudl", "Sportscode",
    "Notion/Confluence"
  ),
  annual_cost = c(
    50000, 100000, 30000,
    6000, 50000,
    15000, 10000,
    10000, 25000,
    2000
  ),
  tier = c(
    "Growth", "Enterprise", "Starter",
    "Growth", "Enterprise",
    "Growth", "Starter",
    "Starter", "Growth",
    "Starter"
  )
)

# Calculate total stack cost by tier
stack_cost <- function(tier_level) {
  tool_costs %>%
    filter(tier == tier_level | tier == "Starter") %>%
    group_by(category) %>%
    slice_min(annual_cost) %>%  # Take cheapest in category
    ungroup() %>%
    summarise(
      total_cost = sum(annual_cost),
      tools = paste(tool, collapse = ", ")
    )
}

# Compare tiers
cat("Starter Stack:\n")
print(stack_cost("Starter"))

cat("\nGrowth Stack:\n")
print(stack_cost("Growth"))
Output
Starter: €52,000 (5 tools)
Growth: €83,000 (5 tools)
Enterprise: €167,000 (5 tools)

Integration with Football Operations

The biggest challenge isn't building analytics—it's getting football people to use it. Successful integration requires understanding stakeholder needs, building trust, and delivering value consistently.

stakeholder_engagement.R / stakeholder_engagement.py
# Python: Stakeholder engagement framework
import pandas as pd
from dataclasses import dataclass
from typing import List, Dict

@dataclass
class Stakeholder:
    """Football operations stakeholder profile."""
    name: str
    role: str
    primary_needs: List[str]
    preferred_format: str
    meeting_frequency: str
    trust_building_tips: List[str]

# Define key stakeholders
STAKEHOLDERS = {
    "head_coach": Stakeholder(
        name="Head Coach",
        role="First Team Manager",
        primary_needs=[
            "Opposition analysis",
            "Player performance insights",
            "Set piece analysis",
            "Quick pre-match summaries"
        ],
        preferred_format="Brief presentations, 1-page visual summaries",
        meeting_frequency="Daily during season",
        trust_building_tips=[
            "Deliver quick wins early",
            "Respect their experience",
            "Never contradict in public",
            "Be available on match days"
        ]
    ),
    "dof": Stakeholder(
        name="Director of Football",
        role="Technical Director",
        primary_needs=[
            "Transfer targets with data backing",
            "Squad planning analysis",
            "Contract situation overview",
            "Competitor intelligence"
        ],
        preferred_format="Detailed reports, spreadsheets, presentations",
        meeting_frequency="Weekly",
        trust_building_tips=[
            "Provide market context",
            "Track recommendation success",
            "Be honest about uncertainty",
            "Support negotiation with data"
        ]
    ),
    "scouts": Stakeholder(
        name="Scouting Team",
        role="Chief Scout + Scouts",
        primary_needs=[
            "Player identification filters",
            "Comparison tools",
            "League-adjusted metrics",
            "Video integration"
        ],
        preferred_format="Interactive tools, player profiles",
        meeting_frequency="2x weekly",
        trust_building_tips=[
            "Position as a complement, not replacement",
            "Celebrate when scouts find gems data missed",
            "Learn from their expertise",
            "Make their workflow easier"
        ]
    )
}

class StakeholderEngagement:
    """Manage stakeholder relationships and deliverables."""

    def __init__(self):
        self.stakeholders = STAKEHOLDERS
        self.engagement_log = []

    def get_weekly_deliverables(self) -> Dict[str, List[str]]:
        """Get weekly deliverables by stakeholder."""
        return {
            "Head Coach": [
                "Pre-match opposition report (MD-2)",
                "Post-match performance summary (MD+1)",
                "Set piece update if applicable"
            ],
            "Director of Football": [
                "Weekly recruitment shortlist update",
                "Market movement summary",
                "Contract situation flags"
            ],
            "Scouting Team": [
                "New player alerts matching profiles",
                "Video clips for top targets",
                "League adjustment updates"
            ],
            "Sports Science": [
                "Daily load monitoring dashboard",
                "Weekly injury risk report",
                "Return-to-play tracking"
            ]
        }

    def measure_engagement(self) -> Dict[str, str]:
        """Define engagement success metrics."""
        return {
            "Report adoption": "Target >80% opened/used",
            "Meeting attendance": "Target >90%",
            "Ad-hoc requests": "Target >5/week (indicates value)",
            "Decisions influenced": "Track recommendations adopted",
            "Satisfaction score": "Quarterly survey, target >4/5"
        }

engagement = StakeholderEngagement()
print("Weekly deliverables:")
for stakeholder, deliverables in engagement.get_weekly_deliverables().items():
    print(f"\n{stakeholder}:")
    for d in deliverables:
        print(f"  - {d}")
# R: Stakeholder engagement framework
library(tidyverse)

# Define stakeholder needs and engagement strategies
stakeholder_engagement <- tibble(
  stakeholder = c(
    "Head Coach",
    "Assistant Coaches",
    "Director of Football",
    "Chief Scout",
    "Sports Science",
    "Academy Director",
    "Board/Ownership"
  ),
  primary_needs = c(
    "Match prep, opposition weaknesses, player performance",
    "Training insights, set pieces, video clips",
    "Transfer targets, contract planning, squad building",
    "Player identification, scouting reports, market intelligence",
    "Load monitoring, injury risk, recovery optimization",
    "Youth development tracking, promotion decisions",
    "ROI, competitive advantage, strategic overview"
  ),
  preferred_format = c(
    "Brief presentations, 1-page summaries",
    "Video playlists, quick dashboards",
    "Detailed reports, data tables",
    "Player profiles, comparison tools",
    "Real-time dashboards, alerts",
    "Development reports, benchmarking",
    "Executive summaries, KPI dashboards"
  ),
  meeting_frequency = c(
    "Daily during season, weekly off-season",
    "Pre and post match",
    "Weekly",
    "2x weekly",
    "Daily",
    "Monthly",
    "Quarterly"
  ),
  trust_building = c(
    "Quick wins, accurate predictions, respect for experience",
    "Useful clips, practical insights",
    "Market knowledge, successful recommendations",
    "Complement not replace scouts, find hidden gems",
    "Collaboration, shared goals, respect medical autonomy",
    "Long-term tracking, fair assessments",
    "Clear ROI metrics, honest reporting"
  )
)

# Engagement success metrics
engagement_metrics <- tibble(
  metric = c(
    "Report adoption rate",
    "Meeting attendance",
    "Ad-hoc requests",
    "Positive feedback mentions",
    "Decisions influenced",
    "Time to insight delivery"
  ),
  target = c(
    ">80% of reports opened/used",
    ">90% attendance at scheduled meetings",
    ">5 requests per week (sign of value)",
    ">3 positive mentions per month",
    ">50% of recommendations considered",
    "<24 hours for standard requests"
  ),
  measurement = c(
    "Report tracking, dashboard logins",
    "Calendar records",
    "Request log",
    "Feedback surveys, informal tracking",
    "Decision log review",
    "Ticket/request system"
  )
)

print(stakeholder_engagement %>% select(stakeholder, primary_needs))
Integration Best Practices
  • Start small: Prove value with one use case before expanding
  • Listen first: Understand what stakeholders actually need
  • Speak their language: Avoid jargon, use football terminology
  • Be present: Attend matches, training, and meetings
  • Track success: Document when analytics influenced decisions

Roadmap: Building from Zero

Here's a practical roadmap for establishing analytics capabilities at a club starting from nothing.

Phase 1: Foundation (Months 1-3)
  • Hire first analyst (generalist with football knowledge)
  • Establish data provider relationship (start with one)
  • Set up basic database and reporting infrastructure
  • Build relationships with key stakeholders
  • Deliver first opposition report and recruitment shortlist
Phase 2: Establishing Value (Months 4-9)
  • Standardize recurring reports (pre-match, post-match, recruitment)
  • Build dashboards for self-service insights
  • Integrate with video analysis workflow
  • Track and document successful recommendations
  • Identify need for additional resources
Phase 3: Scaling (Months 10-18)
  • Add specialized roles (recruitment analyst, data engineer)
  • Expand data sources
  • Build custom models (xG, player valuation, projections)
  • Extend to academy and women's team
  • Establish analytics as part of decision-making culture
Phase 4: Excellence (18+ months)
  • Advanced modeling (tracking data, computer vision)
  • Predictive systems
  • Real-time match analysis
  • Cross-club analytics (for multi-club groups)
  • R&D and innovation projects

Practice Exercises

Exercise 1: Design Your Department

Given a budget of €250,000/year, design an analytics department for a Championship (second tier English) club. Define roles, responsibilities, technology stack, and key deliverables. Justify your choices.

Exercise 2: Stakeholder Presentation

Create a 5-slide presentation pitching the value of analytics to a skeptical head coach. Focus on practical benefits, respect for their expertise, and concrete examples of how analytics can help them win matches.

Exercise 3: 90-Day Plan

You've just been hired as the first data analyst at a club. Create a detailed 90-day plan covering: stakeholder meetings, quick wins to deliver, infrastructure to build, and success metrics to track.

Summary

Key Takeaways
  • Scale appropriately: Department size should match budget and club needs
  • Define clear roles: Avoid overlap and ensure coverage of key areas
  • Technology enables, doesn't solve: Tools support workflows, people drive value
  • Integration is critical: Analytics only works if football people use it
  • Build trust gradually: Quick wins and consistent delivery build credibility
Department Building Checklist
  • Budget allocated and approved
  • Reporting structure defined
  • Key roles identified and hired
  • Data provider contracts signed
  • Technology stack implemented
  • Stakeholder meetings scheduled
  • Recurring deliverables defined
  • Success metrics established