mcpbeat Sign in

Workload Analysis Agent Skill

Generate comprehensive workload analysis visualizations for PostHog customer accounts. Use when user requests account analysis, workload breakdown, SDK analysis, spend allocation, or expansion opportunity assessment. Triggers include "analyze [account]", "workload analysis for [account]", "SDK breakdown for [account]", "show me how [account] uses PostHog", or any request to understand customer usage patterns across products and platforms.

17k tokens
context cost
the whole folder, loaded on every use
6
files
instructions only
0
copies elsewhere
how many repositories repackaged it
57
stars on the repo
on the repository, not the skill itself

Install

one command, takes just this skill from the repository
npx skills add https://github.com/PostHog/skills --skill workload-analysis

What comes with it

53 714 bytes besides the instruction
README.md
assets/workload-template.jsx
references/data-mapping.md
references/opportunity-framework.md
references/spend-allocation.md

The instruction itself

16 sections, as written by the author

Workload Analysis Skill

Generate interactive React visualizations showing how customer spend flows from products → workloads → teams, with SDK breakdowns, opportunity identification, and risk assessment.

Workflow

Step 1: Collect Account Data

  • Find account in Vitally:
   vitally:find_account_by_name → get externalId (organization_id)
   vitally:get_account_full with filterUnnecessaryFields=false
  • Query billing data from PostHog:
   SELECT date, report 
   FROM postgres.prod.billing_usagereport
   WHERE organization_id = '[externalId]'
   ORDER BY date DESC LIMIT 3
  • Extract key fields (see references/data-mapping.md for complete field list):
  • Vitally: MRR, forecasted_mrr, diff_dollars, product spend fields
  • Billing: SDK event counts, AI events, recordings, exceptions, feature flag requests

Step 2: Calculate SDK Proportions

⚠️ CRITICAL: Check ALL SDKs, not just common ones. Flutter/React Native are often the dominant SDK for mobile-first companies.

From billing data, extract COMPLETE SDK breakdown:

const sdkBreakdown = {
  // Web
  web: report.web_events_count_in_period || 0,
  // Backend
  node: report.node_events_count_in_period || 0,
  python: report.python_events_count_in_period || 0,
  go: report.go_events_count_in_period || 0,
  ruby: report.ruby_events_count_in_period || 0,
  php: report.php_events_count_in_period || 0,
  java: report.java_events_count_in_period || 0,
  // Mobile Native
  ios: report.ios_events_count_in_period || 0,
  android: report.android_events_count_in_period || 0,
  // Mobile Cross-Platform (DON'T MISS THESE!)
  flutter: report.flutter_events_count_in_period || 0,
  react_native: report.react_native_events_count_in_period || 0,
};
const total = Object.values(sdkBreakdown).reduce((a, b) => a + b, 0);

// Verify: SDK total should ≈ event_count_in_period
// If big gap, you're missing an SDK!

Step 3: Define Workloads

Create workloads based on SDK proportions and product usage patterns:

| Pattern | Workload Type | Examples |

|---------|---------------|----------|

| Web >80% | Web Application (primary) | SaaS dashboard |

| Node/Python/Go/Ruby/PHP/Java >80% | Backend API (primary) | API-first product |

| iOS + Android >20% | Native Mobile Apps (primary) | Consumer iOS/Android |

| Flutter >20% | Flutter Mobile App (primary) | Cross-platform mobile |

| React Native >20% | React Native App (primary) | Cross-platform mobile |

| AI events >0 | LLM/AI Platform | AI-first company |

| External schemas >0 | Data Platform | Data warehouse usage |

Mobile Priority: If Flutter or React Native dominates, that's the primary mobile workload (not iOS/Android native).

Step 4: Allocate Spend to Workloads

Apply allocation rules from references/spend-allocation.md:

| Product | Allocation Rule |

|---------|-----------------|

| Product Analytics | By SDK event proportion |

| Identified Events | By SDK event proportion |

| Session Replay (Web) | 100% to Web workload |

| Mobile Replay | Split by iOS/Android event ratio |

| Feature Flags | 100% to primary SDK consumer |

| Error Tracking | By exception source (web vs node) |

| LLM Analytics | 100% to LLM Platform workload |

| Data Warehouse | 100% to Data Platform workload |

| Group Analytics | 100% to primary workload |

| Teams/Scale | Platform-wide (separate) |

Step 5: Identify Opportunities & Risks

Apply frameworks from references/opportunity-framework.md:

Opportunities (check in order):

  • Monthly → Annual conversion (if not annual)
  • Product gaps (enrolled but $0 spend)
  • Cross-sell (missing products for their profile)
  • Usage expansion (approaching limits)

Risks (flag if present):

  • Declining MRR (forecasted < current)
  • No annual contract (>$3K MRR monthly)
  • Low health score (<6)
  • Single product dependency (>50% of spend)

Step 6: Generate React Visualization

Use the template from assets/workload-template.jsx. ALL components below are REQUIRED:

MANDATORY Components (include ALL of these):

  • SummaryCards - MRR, health score, contract type, alerts
  • SdkBreakdownBar - Visual SDK proportion bar
  • WorkloadCards - Cards for each workload with products
  • OpportunitiesTable - Ranked opportunities with confidence
  • RisksTable - If risks present, severity-ranked
  • TreeView - REQUIRED: Hierarchical view showing Account → Workloads → Products → Teams with connecting lines
  • SankeyView - REQUIRED: SVG revenue flow diagram showing Products (left bars) flowing to Workloads (right bars) with curved paths
  • MatrixView - Products × Workloads grid with spend cells

View Tabs (MUST include all 5):

const tabs = [
  { id: 'overview', label: '📊 Overview' },
  { id: 'risks', label: '🚨 Risks' },      // Show if risks.length > 0
  { id: 'tree', label: '🌳 Tree View' },   // REQUIRED
  { id: 'sankey', label: '💰 Revenue Flow' }, // REQUIRED
  { id: 'matrix', label: '📋 Matrix' },
];

⚠️ DO NOT SKIP TreeView or SankeyView. These are the most valuable visualizations for understanding account structure.

Required Component: TreeView

Copy this component exactly:

const TreeView = () => {
  const activeWorkloads = workloadTotals.filter(w => w.type !== 'inactive');
  return (
    <div className="bg-white p-4 rounded-lg shadow border mb-4">
      <div className="text-sm font-bold text-gray-700 mb-4">Hierarchical View: Account → Workloads → Products → Teams</div>
      <div className="flex flex-col items-start">
        <div className="bg-gray-900 text-white px-4 py-2 rounded font-bold mb-4">
          {accountSummary.name} (${(accountSummary.arr/1000).toFixed(0)}k ARR)
          <span className="text-gray-400 text-sm ml-2">| {accountSummary.employees} employees | {accountSummary.fundingStage}</span>
        </div>
        <div className="ml-8 border-l-2 border-gray-300">
          {activeWorkloads.map((workload, wIdx) => (
            <div key={wIdx} className="ml-4 mb-6">
              <div className="flex items-center">
                <div className="w-6 h-px bg-gray-300 mr-2"></div>
                <div className="px-3 py-2 rounded text-sm font-medium"
                  style={{ backgroundColor: workload.type === 'primary' ? '#dbeafe' : '#f3e8ff',
                    color: workload.type === 'primary' ? '#1e40af' : '#6b21a8',
                    borderLeft: `4px solid ${workloadTypeColors[workload.type]}` }}>
                  <div className="font-bold">{workload.name}</div>
                  <div className="text-xs opacity-75">{workload.sdk} • {formatNumber(workload.dailyEvents)} events/day • {formatCurrency(workload.totalSpend)}/mo</div>
                </div>
              </div>
              <div className="ml-10 mt-2 border-l border-gray-200">
                {workload.products.map((product, pIdx) => (
                  <div key={pIdx} className="ml-4 mb-2 flex items-center">
                    <div className="w-4 h-px bg-gray-200 mr-2"></div>
                    <div className="w-3 h-3 rounded-full mr-2" style={{ backgroundColor: statusColors[product.status] }}></div>
                    <span className={`text-sm ${product.status === 'opportunity' ? 'text-gray-400' : 'text-gray-700'}`}>
                      <span className="font-medium">{product.name}</span>
                      {product.spend > 0 && <span className="text-green-600 ml-2">({formatCurrency(product.spend)}/mo)</span>}
                    </span>
                  </div>
                ))}
              </div>
              <div className="ml-10 mt-2 flex items-center gap-2">
                <div className="w-4 h-px bg-gray-200"></div>
                <span className="text-xs text-gray-500">Teams:</span>
                {workload.teams.map((team, tIdx) => (
                  <span key={tIdx} className="bg-purple-100 text-purple-700 px-2 py-0.5 rounded text-xs">{team}</span>
                ))}
              </div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
};

Required Component: SankeyView

Copy this component exactly:

const SankeyView = () => {
  const productsWithSpend = [];
  const productSpendMap = {};
  workloadTotals.forEach(w => {
    w.products.forEach(p => {
      if (p.spend > 0) {
        productSpendMap[p.name] = (productSpendMap[p.name] || 0) + p.spend;
      }
    });
  });
  Object.entries(productSpendMap).forEach(([name, amount]) => {
    productsWithSpend.push({ name, amount });
  });
  productsWithSpend.sort((a, b) => b.amount - a.amount);
  
  const totalSpend = productsWithSpend.reduce((s, p) => s + p.amount, 0) || 1;
  const activeWorkloads = workloadTotals.filter(w => w.type !== 'inactive' && w.totalSpend > 0);
  
  const productColors = ['#3b82f6', '#8b5cf6', '#22c55e', '#f59e0b', '#ec4899', '#06b6d4'];
  productsWithSpend.forEach((p, i) => { p.color = productColors[i % productColors.length]; });
  activeWorkloads.forEach((w, i) => { w.color = productColors[i % productColors.length]; });

  const svgWidth = 800;
  const svgHeight = Math.max(300, Math.max(productsWithSpend.length, activeWorkloads.length) * 60 + 60);
  const leftX = 140, rightX = svgWidth - 200, barWidth = 30, gap = 8;
  
  let leftY = 40;
  const leftBars = productsWithSpend.map(p => {
    const height = Math.max((p.amount / totalSpend) * (svgHeight - 80), 25);
    const bar = { ...p, x: leftX, y: leftY, width: barWidth, height };
    leftY += height + gap;
    return bar;
  });
  
  let rightY = 40;
  const rightBars = activeWorkloads.map(w => {
    const height = Math.max((w.totalSpend / totalSpend) * (svgHeight - 80), 25);
    const bar = { ...w, amount: w.totalSpend, x: rightX, y: rightY, width: barWidth, height };
    rightY += height + gap;
    return bar;
  });

  const flows = [];
  const leftOffsets = {}, rightOffsets = {};
  leftBars.forEach(b => { leftOffsets[b.name] = b.y; });
  rightBars.forEach(b => { rightOffsets[b.name] = b.y; });
  
  activeWorkloads.forEach(workload => {
    workload.products.forEach(product => {
      if (product.spend > 0) {
        flows.push({ from: product.name, to: workload.name, amount: product.spend, 
          color: leftBars.find(b => b.name === product.name)?.color || '#888' });
      }
    });
  });

  return (
    <div className="bg-white p-4 rounded-lg shadow border mb-4">
      <div className="text-sm font-bold text-gray-700 mb-4">Revenue Flow: Products → Workloads</div>
      <svg width={svgWidth} height={svgHeight} className="mx-auto">
        {flows.map((flow, idx) => {
          const fromBar = leftBars.find(b => b.name === flow.from);
          const toBar = rightBars.find(b => b.name === flow.to);
          if (!fromBar || !toBar) return null;
          const flowHeight = Math.max((flow.amount / totalSpend) * (svgHeight - 80), 3);
          const startY = leftOffsets[flow.from], endY = rightOffsets[flow.to];
          leftOffsets[flow.from] += flowHeight;
          rightOffsets[flow.to] += flowHeight;
          const path = `M ${fromBar.x + barWidth} ${startY} C ${fromBar.x + barWidth + 100} ${startY}, ${toBar.x - 100} ${endY}, ${toBar.x} ${endY} L ${toBar.x} ${endY + flowHeight} C ${toBar.x - 100} ${endY + flowHeight}, ${fromBar.x + barWidth + 100} ${startY + flowHeight}, ${fromBar.x + barWidth} ${startY + flowHeight} Z`;
          return <path key={idx} d={path} fill={flow.color} fillOpacity={0.3} stroke={flow.color} strokeWidth={0.5} />;
        })}
        {leftBars.map((bar, idx) => (
          <g key={`left-${idx}`}>
            <rect x={bar.x} y={bar.y} width={bar.width} height={bar.height} fill={bar.color} rx={4} />
            <text x={bar.x - 10} y={bar.y + bar.height / 2} textAnchor="end" dominantBaseline="middle" fontSize={11} fill="#374151" fontWeight="500">{bar.name}</text>
            <text x={bar.x - 10} y={bar.y + bar.height / 2 + 13} textAnchor="end" dominantBaseline="middle" fontSize={10} fill="#6b7280">{formatCurrency(bar.amount)}</text>
          </g>
        ))}
        {rightBars.map((bar, idx) => (
          <g key={`right-${idx}`}>
            <rect x={bar.x} y={bar.y} width={bar.width} height={bar.height} fill={bar.color} rx={4} />
            <text x={bar.x + bar.width + 10} y={bar.y + bar.height / 2} textAnchor="start" dominantBaseline="middle" fontSize={11} fill="#374151" fontWeight="500">{bar.name}</text>
            <text x={bar.x + bar.width + 10} y={bar.y + bar.height / 2 + 13} textAnchor="start" dominantBaseline="middle" fontSize={10} fill="#6b7280">{formatCurrency(bar.amount)} ({((bar.amount / totalSpend) * 100).toFixed(0)}%)</text>
          </g>
        ))}
        <text x={leftX + barWidth/2} y={20} textAnchor="middle" fontSize={11} fill="#6b7280" fontWeight="bold">PRODUCTS</text>
        <text x={rightX + barWidth/2} y={20} textAnchor="middle" fontSize={11} fill="#6b7280" fontWeight="bold">WORKLOADS</text>
      </svg>
      <div className="text-xs text-gray-500 text-center mt-2">Flow width represents dollar allocation</div>
    </div>
  );
};

Output Format

Generate a complete React component file named {account-name}-workload-analysis.jsx that:

  • Contains all raw data in variables (for reference)
  • Calculates proportions and allocations
  • Defines workloads with products array
  • Includes all visualization components
  • Has view tabs for different perspectives

Quick Reference

Status Thresholds

  • Significant: ≥$500/mo (green)
  • Adopted: $100-499/mo (blue)
  • Experimenting: $1-99/mo (amber)
  • Opportunity: $0/mo (gray)

Key SQL Fields

event_count_in_period, recording_count_in_period, 
ai_event_count_in_period, exceptions_captured_in_period,
billable_feature_flag_requests_count_in_period,
enhanced_persons_event_count_in_period

Vitally Spend Fields

product_analytics_forecasted_mrr, session_replay_forecasted_mrr,
feature_flags_forecasted_mrr, llm_analytics_forecasted_mrr,
enhanced_persons_forecasted_mrr, error_tracking_forecasted_mrr,
data_warehouse_forecasted_mrr, surveys_forecasted_mrr

Pre-Delivery Checklist

Before presenting the React artifact, verify ALL of these are included:

  • [ ] SummaryCards component with MRR, health, contract type
  • [ ] SdkBreakdownBar showing ALL SDKs (including Flutter/React Native if present)
  • [ ] WorkloadCards for each identified workload
  • [ ] OpportunitiesTable with ranked opportunities
  • [ ] RisksTable (if any risks identified)
  • [ ] TreeView component - hierarchical Account → Workloads → Products → Teams
  • [ ] SankeyView component - SVG with curved flow paths from Products to Workloads
  • [ ] MatrixView with Products × Workloads grid
  • [ ] View tabs including: Overview, Risks, 🌳 Tree View, 💰 Revenue Flow, Matrix
  • [ ] Raw data reference section (collapsible)

Other skills for the same job

different authors, same section of the catalogue
D3 Viz
by chrisvoncsefalvay
×3

Creating interactive data visualisations using d3.js. This skill should be used when creating custom charts, graphs, network diagrams, geographic visualisations, or any complex SVG-based data visualisation that requires fine-grained control over visual elements, transitions, or interactions. Use this for bespoke visualisations beyond standard charting libraries, whether in React, Vue, Svelte, vanilla JavaScript, or any other environment.

20k tokens
Astropy
by christophacham
×3

Comprehensive Python library for astronomy and astrophysics. This skill should be used when working with astronomical data including celestial coordinates, physical units, FITS files, cosmological calculations, time systems, tables, world coordinate systems (WCS), and astronomical data analysis. Use when tasks involve coordinate transformations, unit conversions, FITS file manipulation, cosmological distance calculations, time scale conversions, or astronomical data processing.

16k tokens
Instrument Data To Allotrope
by anthropics
vendor ×2

Convert laboratory instrument output files (PDF, CSV, Excel, TXT) to Allotrope Simple Model (ASM) JSON format or flattened 2D CSV. Use this skill when scientists need to standardize instrument data for LIMS systems, data lakes, or downstream analysis. Supports auto-detection of instrument types. Outputs include full ASM JSON, flattened CSV for easy import, and exportable Python code for data engineers. Common triggers include converting instrument files, standardizing lab data, preparing data for upload to LIMS/ELN systems, or generating parser code for production pipelines.

33k tokens scripts
Qutip
by ComeOnOliver
×2

Quantum mechanics simulations and analysis using QuTiP (Quantum Toolbox in Python). Use when working with quantum systems including: (1) quantum states (kets, bras, density matrices), (2) quantum operators and gates, (3) time evolution and dynamics (Schrödinger, master equations, Monte Carlo), (4) open quantum systems with dissipation, (5) quantum measurements and entanglement, (6) visualization (Bloch sphere, Wigner functions), (7) steady states and correlation functions, or (8) advanced methods (Floquet theory, HEOM, stochastic solvers). Handles both closed and open quantum systems across various domains including quantum optics, quantum computing, and condensed matter physics.

27k tokens
Copilot Usage Metrics
by github
vendor ×1

Retrieve and display GitHub Copilot usage metrics for organizations and enterprises using the GitHub CLI and REST API.

1k tokens scripts
Mentoring Juniors
by github
vendor ×1

Socratic mentoring for junior developers and AI newcomers. Guides through questions, never answers. Triggers: "help me understand", "explain this code", "I''m stuck", "Im stuck", "I''m confused", "Im confused", "I don''t understand", "I dont understand", "can you teach me", "teach me", "mentor me", "guide me", "what does this error mean", "why doesn''t this work", "why does not this work", "I''m a beginner", "Im a beginner", "I''m learning", "Im learning", "I''m new to this", "Im new to this", "walk me through", "how does this work", "what''s wrong with my code", "what''s wrong", "can you break this down", "ELI5", "step by step", "where do I start", "what am I missing", "newbie here", "junior dev", "first time using", "how do I", "what is", "is this right", "not sure", "need help", "struggling", "show me", "help me debug", "best practice", "too complex", "overwhelmed", "lost", "debug this", "/socratic", "/hint", "/concept", "/pseudocode". Progressive clue systems, teaching techniques, and success metrics.

4k tokens
Astropy
by K-Dense-AI
×1

Core Python library for astronomy and astrophysics workflows that need Astropy APIs, including units/quantities, coordinates, FITS I/O, tables, time systems, WCS, and cosmology. Use when implementing or debugging astronomical data analysis code with Astropy.

18k tokens
Polars
by K-Dense-AI
×1

High-performance DataFrame library for Python ETL, analytics, and pandas migration. Use for expression-based data manipulation with lazy query optimization, parallel execution, streaming out-of-core processing, Arrow interoperability, and optional GPU execution.

20k tokens

How to use it

Copy the folder

Take posthog/workload-analysis from the repository into ~/.claude/skills for personal use, or into .claude/skills inside a project.

Check the name does not clash

The agent identifies a skill by the name field in its header. Two skills with the same name cannot sit side by side — one of them will be ignored.