Skip to content

Commit 31761b9

Browse files
committed
Address Adham's feedback: role accuracy and narrative flow
Adham (founding engineer on Statsbomb project) provided critical feedback: 1. Role title inaccurate - was "Principal" (oversight), should be "Founding" (building from zero) 2. Code examples broke narrative flow even in collapsible accordions Changes: - Update role to "Founding Engineer leading data collection system" (case study + resume) - Remove ~160 lines of DSL/JavaScript code, replace with conceptual narrative - Add Socratic questions to section headings (writing guideline compliance) - Soften bold claims with context and humility ("prevented" → "reduced", acknowledge edge cases) - Enhance collaborative framing ("I didn't want" → "we were protective") - Regenerate resume PDF with corrected title Result: More accurate role representation, improved narrative flow, better tone alignment with philosophical humility guidelines.
1 parent 5372847 commit 31761b9

File tree

3 files changed

+21
-194
lines changed

3 files changed

+21
-194
lines changed

public/resume.pdf

-570 Bytes
Binary file not shown.

src/pages/portfolio/statsbomb.mdx

Lines changed: 19 additions & 192 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,15 @@ import Accordion from '../../components/Accordion.astro';
2626
{/* System Characteristics */}
2727
<Badge variant="category">Real-Time Data Collection</Badge>
2828
<Badge variant="category">Collaborative Workflows</Badge>
29-
<Badge variant="category">Contextual UI Adaptation</Badge>
30-
29+
3130
{/* Key Technologies */}
32-
<Badge variant="skill">XState</Badge>
33-
<Badge variant="skill">Kafka</Badge>
31+
<Badge variant="status">XState</Badge>
32+
<Badge variant="status">Kafka</Badge>
3433
<Badge variant="skill">ANTLR</Badge>
3534
</div>
3635

3736
<Body size="sm" as="div" class="text-neutral">
38-
Timeline: 2018-2022 • Principal engineer championing initiatives, Cairo team of 20
37+
Timeline: 2018-2022 • Founding engineer leading data collection system, Cairo team of 20
3938
</Body>
4039
</div>
4140

@@ -60,7 +59,7 @@ import Accordion from '../../components/Accordion.astro';
6059

6160
{/* Problem Section */}
6261
<section class="mb-8">
63-
<Heading level={2} as="h2" class="mb-6">Problem: Manual Collection Doesn't Scale</Heading>
62+
<Heading level={2} as="h2" class="mb-6">Problem: How Do You Scale Without Choosing Between Speed and Correctness?</Heading>
6463

6564
Week one. Ali and I sat down with paper. We hand-wrote sequencing rules for soccer matches—which events could follow which, what validated as legal, how phases transitioned. It was painful. But necessary.
6665

@@ -112,128 +111,9 @@ We separated three concerns: **entry validation** (dataspec), **sequencing logic
112111
(DSL), and **aggregation rules** (grouping DSL). Product managers wrote rules in readable
113112
syntax. The system compiled to execution logic.
114113

115-
<Accordion summary="Example: Soccer Possession Rules">
116-
117-
```yaml
118-
# Topological Dependency Layers:
119-
# Layer 1: complete-pass, shot-complete, carry, counter-attack-start (atomic derivatives)
120-
# Layer 2: possession, tiki-taka (depend on carry)
121-
# Layer 3: turnover (depends on shot-complete, possession)
122-
123-
# Rule 1: Successful pass completion (atomic event derivative - Layer 1)
124-
# When: Player A passes, Player B receives successfully
125-
# Derive: complete-pass metric with distance, angle, pressure
126-
Derive event complete-pass from events sequence:
127-
- pass team mine
128-
then reception field outcome is complete team mine
129-
130-
# Rule 2: Shot on target (atomic event derivative - Layer 1)
131-
# When: Shot followed by goalkeeper save or goal
132-
# Derive: shot-complete (distinguishes blocked/off-target from on-target)
133-
Derive event shot-complete from events sequence:
134-
- shot team mine
135-
then goal-keeper field outcome is won team opponent OR goal team mine
136-
137-
# Rule 3: Player carry (player-level durational - Layer 1)
138-
# When: Same player performs multiple consecutive dribbles
139-
# Derive: carry (player-level durational fact)
140-
Derive event carry from events sequence:
141-
- reception player X
142-
then dribble player X + # One or more dribbles by same player
143-
144-
# Rule 4: Defensive recovery counter-attack (simple derived - Layer 1)
145-
# When: Team intercepts, then successfully passes
146-
# Derive: counter-attack-start (high-value transition)
147-
Derive event counter-attack-start from events sequence:
148-
- interception field outcome is won team mine
149-
then pass team mine
150-
151-
# Rule 5: Team possession (team-level aggregation - Layer 2, depends on Rule 3)
152-
# When: Team has active carry, foul-won, or ball out by opponent
153-
# Derive: possession (spans all team durational facts)
154-
Derive event possession from events sequence:
155-
- carry team mine +
156-
~ reception team mine
157-
~ foul-committed team opponent
158-
~ out team opponent # ~ Means carries or more including team passes and play restarts
159-
160-
# Rule 6: Tiki-taka analysis (complex sequence - Layer 2, depends on Rule 3)
161-
# When: Team completes 3+ consecutive passes without loss
162-
# Derive: tiki-taka
163-
Derive event tiki-taka from events sequence:
164-
- pass team mine +
165-
~ carry team mine # ~ Means passes or more including carries between passes
166-
167-
# Rule 7: Turnover (higher-level derivative - Layer 3, depends on Rules 2 & 5)
168-
# When: Possession shifts from one team to opponent OR opponent shoots on target
169-
# Derive: turnover metric
170-
Derive event turnover from events sequence:
171-
- possession team mine
172-
then possession team opponent* # zero or more possessions by the other team
173-
then shot-complete team opponent
174-
175-
```
176-
177-
</Accordion>
178-
179-
<Accordion summary="Example: American Football Drive Segmentation" class="mb-6">
180-
181-
```yaml
182-
# Topological Dependency Layers:
183-
# Layer 1: snap-complete, red-zone-conversion, third-down-success, drive-stall (atomic derivatives)
184-
# Layer 2: offensive-drive (depends on snap-complete)
185-
# Layer 3: scoring-drive (depends on offensive-drive)
186-
187-
# Rule 1: Successful snap play (atomic event derivative - Layer 1)
188-
# When: Snap followed by successful gain (run or pass completion)
189-
# Derive: snap-complete
190-
Derive event snap-complete from events sequence:
191-
- snap team mine
192-
then run field outcome is complete team mine
193-
194-
# Rule 2: Red zone conversion (atomic derivative - Layer 1)
195-
# When: Team enters red zone (20-yard line) and scores touchdown
196-
# Derive: red-zone-conversion
197-
Derive event red-zone-conversion from events sequence:
198-
- snap field yard-line is less-than 20 team mine
199-
then touchdown team mine
200-
201-
# Rule 3: Third-down success (atomic derivative - Layer 1)
202-
# When: Successful play on 3rd down maintains possession (first down reset)
203-
# Derive: third-down-success
204-
Derive event third-down-success from events sequence:
205-
- snap field down is 3 team mine
206-
then snap field down is 1 team mine # Reset to first down
207-
208-
# Rule 4: Drive stall (atomic derivative - Layer 1)
209-
# When: Team fails to convert on 4th down or punts
210-
# Derive: drive-stall
211-
Derive event drive-stall from events sequence:
212-
- snap team mine +
213-
then turnover-on-downs team mine OR punt team mine
214-
215-
# Rule 5: Offensive drive (team-level aggregation - Layer 2, depends on Rule 1)
216-
# When: Team gains possession and runs offensive plays
217-
# Derive: offensive-drive (spans all plays until scoring/turnover)
218-
Derive event offensive-drive from events sequence:
219-
- kickoff-return field outcome is complete team mine
220-
~ punt-return field outcome is complete team mine
221-
~ turnover-recovery team mine
222-
then snap team mine +
223-
~ snap-complete team mine # ~ Means snaps or more including completions
224-
until touchdown team mine OR field-goal team mine OR turnover team opponent
225-
226-
# Rule 6: Scoring drive (higher-level derivative - Layer 3, depends on Rule 5)
227-
# When: Offensive drive ends with touchdown or field goal
228-
# Derive: scoring-drive
229-
Derive event scoring-drive from events sequence:
230-
- offensive-drive team mine
231-
then touchdown team mine OR field-goal field outcome is good team mine
232-
```
233-
234-
</Accordion>
114+
The DSL let product managers define domain logic as sequences and dependencies. For soccer, they could express that possession meant a team had active carry (player dribbling), received fouls, or forced the ball out—spans of time aggregated from atomic events. Individual passes became complete-pass metrics. Multiple dribbles by the same player became a carry. Turnovers derived from possession changes. The rules formed dependency layers: atomic events (passes, shots), player-level facts (carries), team-level aggregations (possession), and higher-level derivatives (turnovers).
235115

236-
When we expanded to American football, product managers wrote new dataspecs and grouping rules.
116+
When we expanded to American football, product managers wrote drive segmentation rules using the same DSL patterns. Offensive drives derived from snap sequences until scoring or turnover. Red zone conversions tracked touchdown efficiency. Third-down success measured possession maintenance. The execution engine didn't change—new sport, same architectural separation. No engineering bottleneck.
237117

238118
<Accordion summary="Visualizing the Data Flow" class="mb-6">
239119

@@ -384,68 +264,15 @@ graph TD
384264

385265
**State Machines for Impossible States**
386266

387-
We used XState to model collection workflows as explicit finite state machines. Traditional event handlers create implicit state explosions (`if (editing && !watching && hasBase)`). State machines make all transitions explicit and testable.
388-
389-
```javascript
390-
// Main collection workflow
391-
{
392-
id: 'main-room',
393-
type: 'parallel',
394-
states: {
395-
videoMode: ['manual', 'loop', 'editing'],
396-
collection: ['watching', 'collectingBase', 'addingPartials'],
397-
freezeFrame: ['idle', 'active', 'reviewing']
398-
}
399-
}
400-
```
401-
402-
Five major state machines orchestrated the app: main room, event entry, player customization, freeze frame queue, and batch validation. This prevented illegal states by design—you couldn't enter a freeze frame without an active event, couldn't submit without required fields.
403-
404-
<Accordion summary="30+ Keyboard Shortcuts: Muscle Memory Over Mouse Clicks">
405-
406-
We used Mousetrap with module scoping. Same key, different action per context:
407-
408-
```javascript
409-
// Context-aware keyboard mappings
410-
const keyboardShortcuts = {
411-
'main-room': {
412-
't': 'Create new event (pass, shot, tackle)',
413-
'p': 'Add player to event',
414-
'/': 'Toggle video loop mode',
415-
'←→': 'Rewind/forward 5 seconds'
416-
},
417-
'freeze-frame': {
418-
't': 'Toggle team view (home/away)',
419-
'1-9': 'Select player by jersey number',
420-
'Enter': 'Confirm positions'
421-
}
422-
};
423-
424-
// Same key 't', different meaning per context
425-
bindShortcuts('main-room', keyboardShortcuts['main-room']);
426-
bindShortcuts('freeze-frame', keyboardShortcuts['freeze-frame']);
427-
```
267+
We used XState to model collection workflows as explicit finite state machines. Five major state machines orchestrated the app: video mode (manual playback, loop, editing), collection flow (watching, collecting base event, adding details), freeze frame handling (idle, active, reviewing), player customization, and batch validation. This prevented illegal states by design—you couldn't enter a freeze frame without an active event, couldn't submit without required fields. In our context, traditional event handlers created implicit state explosions—tracking state across event handlers made correctness fragile. State machines helped us make transitions explicit and testable, though this added complexity that only paid off at scale.
428268

429-
Collectors touch-typed events like Vim users touch-type code. Video control (arrow keys), event creation (letter keys), navigation (/, Z), editing (Shift+E), freeze frames (number keys). Hands stayed on keyboard. Eyes stayed on video.
430-
431-
</Accordion>
269+
**30+ Keyboard Shortcuts: Muscle Memory Over Mouse Clicks**
432270

433-
<Accordion summary="Contextual UI Adaptation">
271+
Contextual keyboard mappings with module scoping meant the same key could trigger different actions depending on context. In the main collection view, `t` created new events (pass, shot, tackle), `/` toggled video loop mode, and arrow keys controlled playback. In freeze frame mode, `t` toggled team view, number keys selected players by jersey, Enter confirmed positions. Collectors touch-typed events like Vim users touch-type code. Hands stayed on keyboard. Eyes stayed on video.
434272

435-
The interface adapted based on game state and previous decisions. When a collector selected "Pass," the system showed only valid pass types for that player's position. Auto-filled mandatory fields with single valid options. Skipped irrelevant choices entirely.
273+
**Contextual UI Adaptation**
436274

437-
```javascript
438-
// After DSL sequence rules run and determine valid next options
439-
// If only one option remains valid, auto-fill and skip the question
440-
// Example: "Pass" event followed by rules determining only "Reception" is valid → auto-fill
441-
if (hasOnlyOneValidOption(currentField, validOptions)) {
442-
return progressToNextField(allFields, currentField, validOptions[0]);
443-
}
444-
```
445-
446-
Cognitive load minimization: collectors never saw irrelevant options.
447-
448-
</Accordion>
275+
The interface adapted based on game state and previous decisions. When a collector selected "Pass," the system showed only valid pass types for that player's position. DSL sequence rules determined valid next options. If only one option remained valid, the system auto-filled and skipped the question entirely. Cognitive load minimization—collectors never saw irrelevant options.
449276

450277
<Accordion summary="Video Loop Mode">
451278

@@ -459,14 +286,14 @@ Freeze frames (positioning data for all 22 players) were semi-automated. Collect
459286

460287
</Accordion>
461288

462-
This tool became the foundation. The dataspec drove UI validation, the DSL defined legal event sequences, the state machines enforced correctness. Concurrent collection during live matches with sub-minute latency. The backend scaled to support what the UX tool showed collectors needed.
289+
This tool became the foundation. The dataspec drove UI validation, the DSL defined legal event sequences, the state machines constrained invalid states. We still found edge cases in production, but the architecture caught most errors before they reached collectors. Concurrent collection during live matches with sub-minute latency. The backend scaled to support what the UX tool showed collectors needed.
463290
</div>
464291

465292
{/* Event Collection Subsection */}
466293
<div class="mb-10">
467294
<Heading level={3} as="h3" class="mb-4"><span>Backend Evolution: From Batch to Real-Time</span></Heading>
468295

469-
Breaking matches down by decision rather than team increased correctness without additional effort. Computer vision assisted input, contextual keyboard mappings reduced cognitive load, and automated linting caught preventable errors. Collectors focused on judgment over correction—handling edge cases where human expertise mattered rather than catching mistakes.
296+
Breaking matches down by decision rather than team increased correctness while reducing collector coordination overhead. Computer vision assisted input, contextual keyboard mappings reduced cognitive load, and automated linting caught preventable errors. Collectors focused on judgment over correction—handling edge cases where human expertise mattered rather than catching mistakes.
470297

471298
**Beyond Simple Sequences: Event Dependency Graphs**
472299

@@ -629,13 +456,13 @@ graph TD
629456
team (emerald) resolves ambiguity. Golden entities cascade updates to dependent systems automatically.
630457
</Body>
631458

632-
The system prevented duplicate entities by design, caught conflicts automatically, and escalated true ambiguity. The 5-person team focused on the 1-2% of ambiguous cases requiring domain expertise (is "FC Barcelona" the same as "Barcelona FC"? yes; but "Manchester United" vs "Manchester City"? no).
459+
The system's design reduced duplicate entities, caught most conflicts automatically, and escalated true ambiguity. The 5-person team focused on the 1-2% of ambiguous cases requiring domain expertise (is "FC Barcelona" the same as "Barcelona FC"? yes; but "Manchester United" vs "Manchester City"? no).
633460
</div>
634461
</section>
635462

636463
{/* Impact Section */}
637464
<section class="mb-16">
638-
<Heading level={2} as="h2" class="mb-6"><span>Impact: From Bottleneck to Multiplier</span></Heading>
465+
<Heading level={2} as="h2" class="mb-6"><span>Impact: What Changed When Architecture Met Distributed Ownership?</span></Heading>
639466

640467
<Body size="base" as="p" class="mb-8 leading-relaxed italic">
641468
Design for the 80% common case; production iteration reveals the 20% edge cases.
@@ -653,7 +480,7 @@ ambiguous data points—rather than catching preventable mistakes.
653480

654481
**Non-linear leverage:** Operations scaled 10x without proportional staffing increases.
655482

656-
The system scaled horizontally. Product managers shipped features.
483+
The system scaled horizontally as load increased. Product managers could ship new dataspecs without engineering bottlenecks, though some edge cases still required technical collaboration.
657484

658485
</Accordion>
659486

@@ -684,7 +511,7 @@ Month one backend: a single Go endpoint called `sync`—batch, offline. We start
684511

685512
<Accordion summary="Years Two-Three: Scaling from Partnership to Distributed Team" class="mb-6">
686513

687-
Adham and I could execute together, but the domain kept expanding. The transition from two-person partnership to distributed team took longer than it needed—I was learning how to lead engineers while building production systems. The tension: I didn't want to compromise on the architectural ideas the domain required, but keeping that thinking between just us became a bottleneck. Every new subsystem, every architectural decision, required our direct involvement.
514+
Adham and I could execute together, but the domain kept expanding. The transition from two-person partnership to distributed team took longer than it needed—I was learning how to lead engineers while building production systems. The tension: we were protective of architectural integrity while the domain kept expanding, and limiting that thinking to just us became a bottleneck. Every new subsystem, every architectural decision, required our direct involvement.
688515

689516
By year three, we had built the hiring and mentoring systems needed to scale beyond the core partnership.
690517

@@ -753,7 +580,7 @@ Architecture shapes what's possible. But people make it real.
753580

754581
{/* Lessons Section */}
755582
<section class="mb-16">
756-
<Heading level={2} as="h2" class="mb-6"><span>Lessons: Context Shapes What Works</span></Heading>
583+
<Heading level={2} as="h2" class="mb-6"><span>Lessons: What Transfers? What Only Worked Here?</span></Heading>
757584

758585
**What Worked in This Context**
759586

src/pages/resume.astro

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,12 @@ import '../styles/print-resume.css';
9595
<div>
9696
<div class="flex justify-between items-start mb-2">
9797
<div>
98-
<Heading level={2} as="h3" class="mb-1"><span>Principal Software Engineer</span></Heading>
98+
<Heading level={2} as="h3" class="mb-1"><span>Founding Engineer</span></Heading>
9999
<Body size="sm" as="p" class="text-neutral">Statsbomb</Body>
100100
</div>
101101
<Body size="sm" as="p" class="text-neutral">Bath, UK | 03/2018 - 03/2022</Body>
102102
</div>
103-
<Body size="sm" as="p" class="mb-4">Led core data infrastructure for football analytics platform—serving Premier League clubs, international federations, and media companies.</Body>
103+
<Body size="sm" as="p" class="mb-4">Built core data collection system from scratch for football analytics platform—serving Premier League clubs, international federations, and media companies.</Body>
104104
<Body size="base" as="ul" class="space-y-3">
105105
<li><strong>Designed domain-specific language (ANTLR grammar) enabling product managers to define sports rules as configuration.</strong> Topological dependency resolution compiled declarative sequences ("pass then reception") into validation logic. Expanded from soccer to American football with zero code changes. Pattern: Separation of rules from execution unlocked non-linear scaling (100 → 1000+ collectors without proportional engineering).</li>
106106
<li><strong>Built collaborative collection system (Electron + XState + Apollo GraphQL) replacing 16-hour sequential workflows with 4-hour concurrent collection.</strong> XState state machines prevented illegal states by design—collectors couldn't submit without required fields, couldn't enter freeze frames without active events. 30+ contextual keyboard shortcuts (Mousetrap with module scoping) built muscle memory—same key, different action per context.</li>

0 commit comments

Comments
 (0)