← Portfolio

Catalogue

The working record

Everything on my portfolio is a claim. This page is where I back the claims up: real code I wrote, real schemas I designed, and a real inventory of what exists — all pulled straight off my working drive.

Every exhibit below is genuine, not a reconstruction. Where something identified my sector or a person, I renamed the identifier and marked the change in the caption. Nothing has been invented to look better, and nothing has been tidied to look cleaner — the comments and the print statements are exactly as I wrote them.

Exhibit 01

The specification I write before building anything

Backs the claim: the spec comes before the work

This is the section structure of a real build specification I wrote before starting a dataset. Nothing here is generated after the fact — this document existed before a single row did, and the build was run against it.

## What I want built
### Deliverable per item

## Scope — what I already know about the landscape
    ### [authority 1] — about 104 sponsoring institutions
    ### [authority 2] — recognised since 2021
    ### [authority 3]
    ### [authority 4]
    ### A cross-source shortcut worth using early
    ### Sources to start from

## Rules — these are not negotiable, they were learned the hard way

## Traps that cost real time on the previous build

## What already exists in this repo — reuse it, do not rebuild it
    - extractor      renders JS, returns page text
    - sync layer     sheet I/O and routing
    - change watcher  currently filtered; widening it is one line
    - registry lookup city/state/website, no key needed
    - overview doc    renames and moves from the last build
    - contract doc    the extraction contract. Read it first.

## How to work

Real document, 11 KB. Authority names and filenames replaced with [bracketed] descriptions. Two headings do the real work here: “Rules — not negotiable, learned the hard way” and “Traps that cost real time on the previous build”. Both exist because I wrote down what went wrong last time instead of rediscovering it.

Exhibit 02

State is saved only after a successful write

Backs the claim: resumable batch jobs · idempotence

My most-repeated rule, in the code that taught it to me. The batch pointer is a single integer on disk. It only moves once the write has actually landed.

Python · extraction & sync pipelineload_state / save_state
def load_state():
    try:
        with open(STATE_FILE, encoding="utf-8") as f:
            return json.load(f)
    except (FileNotFoundError, json.JSONDecodeError):
        return {"last_processed_row": 0}

def save_state(state):
    with open(STATE_FILE, "w", encoding="utf-8") as f:
        json.dump(state, f)
Same file · the write pathbatch commit
try:
    # Write all batches at once (avoids rate limit)
    if review_rows:
        review_ws.append_rows(review_rows, value_input_option="USER_ENTERED")
    if queue_a_rows:
        queue_a_ws.append_rows(queue_a_rows, value_input_option="USER_ENTERED")
    if queue_b_rows:
        queue_b_ws.append_rows(queue_b_rows, value_input_option="USER_ENTERED")

    # Save state only after successful write
    if not args.all:
        save_state({"last_processed_row": 0 if end >= total else end})
    print(f"✓ Wrote {len(review_rows)} review rows.")

except Exception as e:
    print(f"✗ Sheet write failed: {e}")
    print(f"  Data NOT saved to Sheet. state.json NOT updated.")
    sys.exit(1)

Verbatim, except two queue variables renamed. The comment “Save state only after successful write” is mine and is in the original. The failure path is the point: it says out loud that the pointer did not move, so a re-run redoes the batch rather than skipping it.

Exhibit 03

The integrity audit that found what I had missed

Backs the claim: auditing structure, not just contents

This is the script from case study A — the one that found 157 duplicate clusters and thousands of orphaned links in a dataset whose contents I had verified twice from primary sources. It loads all four tables and simply asks which rows point at parents that do not exist.

Python · pandasstructural audit
"""
Comprehensive data audit. Reads the workbook downloaded from the
live sheet and emits findings as JSON, which the report generator
turns into audit_report.md.
"""

entities  = load("Entities")
offerings = load("Offerings")
people    = load("People")
roles     = load("Roles")

F["counts"] = {
    "entities":  len(entities),
    "offerings": len(offerings),
    "people":    len(people),
    "roles":     len(roles),
}

# a role whose offering_id resolves to nothing is an orphan
offering_label  = dict(zip(offerings["offering_id"], offerings["_label"]))
roles["_label"] = roles["offering_id"].map(offering_label)
orphans         = roles[roles["_label"].isna()]

F["roles_orphan_offering_count"] = len(orphans)

Verbatim, with the four table names replaced by neutral equivalents. Six lines of .map() and .isna() is all it took — which is exactly why I run it on a schedule now instead of waiting until something feels wrong.

Exhibit 04

The provenance record, as it is actually stored

Backs the claim: provenance captured at collection, 1,655 times

One CSV sits beside every subject's assets. This is the real header row and a real record, with the two identifying columns replaced.

CSV · one per subject foldersources.csv
"File","Subject","Affiliation","Search","SourcePage",
 "ImageUrl","ResultTitle","MatchEvidence","RetrievedAt"

"asset-01.jpg", "[subject]", "[affiliation]",
"\"[subject]\" [descriptor] [region]",
"https://[video-platform]/watch?v=…",
"https://[cdn]/…/maxresdefault.jpg",
"[subject] Highlights (Class of 2024) - [platform]",
"name in image result metadata",
"2026-09-09T08:17:49.175-06:00"

Real schema, real timestamp precision, real structure. The column that matters is MatchEvidence — it records why I believed this asset belonged to this person. Where that column can't be filled honestly, the asset is withheld rather than used. 149 of these files exist across 234 subject folders.

Exhibit 05

Inventory

Backs the claim: scale of what was actually produced

A count of what is sitting on my drive right now. Not estimates — these are file counts.

ArtefactCountNote
Python scripts, primary data project85extraction, audit, merge, repair, reporting
Build / parse / verify scripts, content pipeline57Node and Python, one set per daily run
Structured article records57JSON, the source of truth for every render
Rendered delivery folders76three sized outputs each, plus a manifest
Written specifications & handover docs22Markdown, written before or handed forward
Subject folders collected2342,285 images; 1,655 of them under a provenance file
Recorded working sessions126across 8 separate project areas
Working data retained2.0 GBprimary dataset plus content pipeline

The two numbers I'd point at: 22 written specifications, because it shows the specs weren't a one-off; and 76 delivery folders, because a pipeline that ran once proves nothing.

Exhibit 06

A design system built from measurements, not guesses

Backs the claim: production design systems · deterministic rendering

I was asked for article graphics. What I delivered was a token system, because doing it once by hand and doing it every day are different problems. The opening line of my own documentation is “nothing here was eyeballed off a screenshot” — every value has a recorded source.

--primaryHeadlines, rules, accents pixel-sampled from the logo artwork
--secondaryRates, highlights, motifs wordmark detail; matches the site stylesheet's most-used token
--creamLight-family ground taken from the live site stylesheet
--ink-700Default dark ground live site stylesheet

These hues are shifted off the real brand values on purpose — publishing a client's exact palette would identify them. Token names, roles and the sourcing method are untouched. Real values, and the reasoning for each, are in the private pack.

Two decisions in that system I'd defend anywhere. Fonts are bundled locally rather than pulled from a CDN, so a render is byte-stable offline and cannot change under me because someone upstream shipped a new version. And when the brand's real display face turned out to be unavailable, I wrote the substitution down in the documentation instead of quietly shipping a lookalike: which faces stand in, that they are close but not the same, and that swapping the real one back in is a one-line token change plus a re-run.

Exhibit 07

The motif kit — ten devices, no image assets

Backs the claim: CSS/SVG engineering · resolution independence

The existing graphics had recurring decorative devices that only existed as flat pixels inside finished artwork. I rebuilt each one as pure CSS or SVG so it scales to any canvas without going soft. Five of the ten, rendered live on this page by the same declarations that render them in production:

m-waves · masked line field
m-topo · contour rings
m-halftone · dot grid
m-plusgrid · plus lattice
m-stripes · diagonal bars
m-spark · diamond field

Real declarations, verbatim, with the two brand hues swapped for the shifted tokens above. Each motif is documented with the artwork it was lifted from, so nobody has to guess where a device belongs.

Exhibit 08

One template, two states — and neither is the other with a hole in it

Backs the claim: designing for the data you actually have

Most of the people we publish about are not clients, so very often there is no photograph we are entitled to use. A template that simply leaves a gap would have broadcast that difference on every post. So the no-photo state was designed as its own composition: the numbers become the picture.

Wordmark.
Weekly Board · Sample
Placeholder
Name
Position · Affiliation
412Total
4.9Per set
84Sets
State A · with a portrait — cutout right, name left, stat strip below
Wordmark.
Weekly Board · Sample
412
4.9 per set
84 sets played
Placeholder
Name
Position · Affiliation
State B · no portrait — the stat becomes the artwork, strip suppressed

Faithful reconstruction at the production ratio (1080 × 1350) using the real layout rules and type hierarchy. All content is invented — no real person, affiliation or figure appears. The portrait is a shape, not a photograph. The design requirement, written into my own spec: neither state should look like the other one with a hole in it.

Exhibit 09

The override that satisfies a classifier and a search engine at once

Backs the claim: systems thinking across two conflicting constraints

A legitimate technical term in this field is also a word that automated moderation on social platforms reads badly — and we publish about named minors, so the risk wasn't worth it. But that term is exactly what people type into a search engine, so removing it from the writing would have cost real traffic.

Two constraints, apparently opposed. The resolution is a single observation: a search engine cannot read text baked into a PNG.

Optional block in each article recordwording
// Every field here overrides the top-level field of the
// same name, for EVERY template. Absent block = render unchanged.
"wording": {
  "slug":      // social exports only — the blog master and OG crop
                 // keep the real slug, because a filename on the site
                 // is a search signal and one on social is invisible
  "headline":  // cover + blog headline
  "statLabel": // what the big number is called
  "eyebrow", "subtitle", "coverBullets", "coverBlurb",
  "groupSlideTitle", "notes"
}

Real structure, real comments, field list intact. The artwork gets platform-safe wording at zero cost to the page's ranking, while the prose keeps the term people actually search for. Alt text in the manifest describes what the graphic really says, so the keyword still survives in the one place a crawler reads.

Exhibit 10

A findings report, as delivered

Backs the claim: analysis written to be acted on

The audit from case study A didn't stop at a script. It produced a report with every finding ranked by severity and scaled against the table it affects — so whoever picks it up knows what to fix first and how big the job is. This is its executive summary, redacted.

#FindingSeverityScale
1Scope record never updated when the project widenedHigh · governance1 cell, blocks everything downstream
2Offering rows referencing an entity that does not existHigh · integrity877 of 2,189
3Role rows pointing at a missing offeringHigh · integrity1,341 of 3,734
4External registry key is placeholder text or blankHigh1,598 of 1,598
5Person IDs reused across different people — primary key brokenHigh52 IDs / ~110 rows
6Entities sharing an identical name with another rowHigh133 names → ~266 rows
7Appended rows missing source, status and date provenanceMedium199 rows
8Region stored two ways — abbreviated and fullMedium · consistency231 vs 1,367
10Uppercase in addresses; 15 values that are not addresses at allMedium / Low211 rows

Real report, real counts. Column headings and finding text are mine. Two things I'd point at: severity is split into governance, integrity and consistency rather than one blurred scale, and every finding carries “n of total” — so nobody has to guess whether 877 is a crisis or a rounding error. Exact row references follow in the full document.

Exhibit 11

Two implementations, one contract

Backs the claim: pipeline architecture · idempotent batch processing

Some sources render as plain pages. Others only exist once a browser has run their JavaScript. One fetcher cannot do both, so there are two — and everything downstream of them is deliberately shared, because two pipelines that drift apart are worse than one pipeline that can't reach every source.

Staticsource pages JS-renderedsource pages Lightweight script cannot run JS Python + headless renders the page first Router by record type Queue A Queue B Review queue Extraction one schema one prompt Batch pointer advances only after every write succeeds fetch route on success resume here
The fetchers differ; the schema, the prompt, the router and the destinations do not. The dashed path is the only thing that makes the job safe to re-run: the pointer moves after the writes land, never before.

Drawn from the running code, not from memory. The shared-contract box is the part that has to be defended — when the two fetchers were changed independently, that box is where the drift would have shown up first, which is why the extraction schema and the prompt live in one file and are imported by both.

Exhibit 12

A delivery manifest that declares its own gaps

Backs the claim: machine-readable delivery · honest handover

Every rendered package ships with a manifest. It records what was produced, at what size, with what alt text — and, the part I care about most, it records what is missing, by name, so nobody downstream has to discover the gap themselves.

JSON · one per delivery folderMANIFEST.json
{
  "article":    "[title] ([month year])",
  "articleId":  "2026-08-[slug]",
  "renderedAt": "2026-08-30T03:46:59.492Z",

  "outputs": [
    { "file": "…-blog-1920x1080.png",
      "width": 1920, "height": 1080,
      "alt": "[title] — [brand]" },

    { "file": "…-social-01-cover.png",
      "width": 1080, "height": 1350,
      "alt": "[title] — carousel cover" },

    { "file": "…-social-02-[subject].png",
      "width": 1080, "height": 1350,
      "alt": "[subject], [role], [affiliation] — No. 1 with
              541 [metric], [title]" }
    // … one entry per slide
  ],

  "photosMissing": [
    { "rank": 11, "name": "[withheld]", "school": "[withheld]", "state": "TX" },
    { "rank": 13, "name": "[withheld]", "school": "[withheld]", "state": "GA" }
    // … 20 entries in this delivery
  ]
}

Real keys, real timestamp, real sizes. Names and affiliations replaced with [withheld]. Two things worth noticing: the alt text is written to be genuinely descriptive — rank, figure and role, not “image of a person” — which is the one place a crawler can read what a PNG says. And photosMissing is a first-class field, not a note in an email. If twenty subjects had no usable photograph, the delivery says so in a form a script can read.

Exhibit 13

The delivery note that flags what it didn't do

Backs the claim: handover under conflicting instructions

Each batch ships with a written note. It opens with status — everything is a draft, nothing is live, only two named people publish — then the deliverables and their counts. Then this, which is the section I'd want anyone to read:

## One instruction in the daily brief was not followed, deliberately

The brief says: "[term] must NOT appear on any graphic
or in any caption. Use [alternatives]."

The client reversed that on 28–29 August — "[term] is the proper
term" — and the first six were re-rendered to match. These
six follow the client. Every graphic and caption uses it.

The original reason for the ban was platform moderation on
posts about named minors, and that risk has not gone away;
it is just a call the client has made and owns. If the brief's
rule is meant to stand again, say so and the graphics re-render
in about ten minutes — the mechanism for making artwork and
prose differ is still in the code, unused.

Real section, with the term and the names removed. Two stakeholders gave opposite instructions. I followed one, said clearly which and why, restated the risk that hadn't gone away, named who owns the decision, and quoted the cost of reversing it. The note ends with a corrections log — things I changed in the draft and the arithmetic behind each one.

Note

What isn't here, and why

I could show more, and it would be more persuasive. I've chosen not to, and I'd rather say what I'm holding back than let this page look complete.