Skip to main content
Compendium / Maturity & Process / Instruments / Composition Throughput Meter
#482

Composition Throughput Meter

Analog dial reading composed units per day — the rate at which AI labour leaves the build line.

STABLE DE
#svg #gauge #animated #reduced-motion #real-content #dark-mode-tested
Live
// composition CO rate
0 5 10 15 20 17 units / day
Spec

Intent

The CO twin of the spec throughput meter. Measures composed units (PRs, modules, files) shipped per day. Paired with the spec dial, the gap reveals where the bottleneck lives.

Acceptance Criteria

  • [ ] Same arc scale as spec-throughput-meter for comparability
  • [ ] Coloured bands (success/attention/danger) match the family
  • [ ] Numeric readout + "units / day" label
  • [ ] Negative: does NOT require JS

Constraints

  • Pure SVG, no runtime
  • Theme-aware via tokens
// composition-throughput-meter.astro
---
import type { CompendiumMeta } from '../../../data/types';

export const meta: CompendiumMeta = {
  id: 482,
  slug: 'composition-throughput-meter',
  name: 'Composition Throughput Meter',
  category: 'maturity-process',
  subcategory: 'instruments',
  pillar: 'de',
  description: 'Analog dial reading composed units per day — the rate at which AI labour leaves the build line.',
  spec: `## Intent
The CO twin of the spec throughput meter. Measures composed units (PRs, modules, files) shipped per day. Paired with the spec dial, the gap reveals where the bottleneck lives.

## Acceptance Criteria
- [ ] Same arc scale as spec-throughput-meter for comparability
- [ ] Coloured bands (success/attention/danger) match the family
- [ ] Numeric readout + "units / day" label
- [ ] Negative: does NOT require JS

## Constraints
- Pure SVG, no runtime
- Theme-aware via tokens`,
  tags: ['svg', 'gauge', 'animated', 'reduced-motion', 'real-content', 'dark-mode-tested'],
  status: 'stable',
  health: { ics: 88, a11y: 'aa', themed: true, animated: true },
};

const value = 17;
const max = 20;
const pct = Math.min(value / max, 1);
const angle = -120 + pct * 240;
const ticks = [0, 25, 50, 75, 100].map((p) => ({ a: -120 + (p / 100) * 240, n: Math.round((p / 100) * max) }));
---

<div data-this-component class="w-full bg-paper text-ink border-4 border-ink overflow-hidden">
  <header class="flex items-baseline justify-between px-3 py-2 border-b-2 border-ink">
    <span class="font-mono text-[10px] uppercase tracking-widest opacity-60">// composition</span>
    <span class="font-mono text-[10px] uppercase tracking-widest">CO rate</span>
  </header>
  <div class="p-3 flex flex-col items-center">
    <svg viewBox="0 0 200 140" class="w-full max-w-[240px] block" role="img" aria-label={`Composition throughput ${value} units per day`}>
      <path d="M 30 110 A 70 70 0 1 1 170 110" fill="none" stroke="var(--color-ink)" stroke-width="3" />
      <path d="M 30 110 A 70 70 0 0 1 100 40" fill="none" stroke="var(--color-success)" stroke-width="6" opacity="0.85" />
      <path d="M 100 40 A 70 70 0 0 1 140 52" fill="none" stroke="var(--color-co)" stroke-width="6" opacity="0.85" />
      <path d="M 140 52 A 70 70 0 0 1 170 110" fill="none" stroke="var(--color-danger)" stroke-width="6" opacity="0.85" />
      {ticks.map((t) => {
        const rad = ((t.a - 90) * Math.PI) / 180;
        const x1 = 100 + Math.cos(rad) * 60, y1 = 100 + Math.sin(rad) * 60;
        const x2 = 100 + Math.cos(rad) * 72, y2 = 100 + Math.sin(rad) * 72;
        const lx = 100 + Math.cos(rad) * 50, ly = 100 + Math.sin(rad) * 50;
        return (
          <>
            <line x1={x1} y1={y1} x2={x2} y2={y2} stroke="var(--color-ink)" stroke-width="2" />
            <text x={lx} y={ly + 3} text-anchor="middle" font-family="JetBrains Mono, monospace" font-size="7" fill="var(--color-ink)" opacity="0.7">{t.n}</text>
          </>
        );
      })}
      <g transform={`rotate(${angle} 100 100)`} class="needle">
        <polygon points="100,38 96,104 104,104" fill="var(--color-ink)" />
        <circle cx="100" cy="100" r="6" fill="var(--color-co)" stroke="var(--color-ink)" stroke-width="2" />
      </g>
      <text x="100" y="130" text-anchor="middle" font-family="Inter, sans-serif" font-weight="900" font-size="20" fill="var(--color-ink)">{value}</text>
    </svg>
    <span class="font-mono text-[10px] uppercase tracking-widest opacity-60 -mt-1">units / day</span>
  </div>
</div>

<style>
  [data-this-component] .needle { transition: transform 800ms cubic-bezier(0.2,0,0.13,1.4); }
  @media (prefers-reduced-motion: reduce) {
    [data-this-component] .needle { transition: none; }
  }
</style>