Back to buildsBUILD RECORD

footie / Football-content automation / 2026

Connecting football posts to the searches that found them.

Thirteen n8n workflows collect, normalize, rank and draft football material, with separate query-hit records to retain discovery context alongside the post.

THE WORK

The project.

footie takes collected football posts through normalization, ranking, model-assisted drafting and a style check before a Telegram handoff. Its data model separates what a post says from how it was found: raw and normalized records use the post ID, while query hits pair that ID with the search query. Later stages can use discovery context without storing another full copy of the text for every match.

That distinction runs through the workflow design, but one early filter currently works against it. The normalizer skips repeated post IDs before constructing query-hit rows, so a second match in the same batch can disappear. The exported implementation makes both the intended relationship and the correction needed visible; this account doesn't establish current scheduler operation or a completed drafting run.

IMPLEMENTATION

Design choices.

  1. Posts and discoveries have different identities. One post can belong to several query results without duplicating its text. To preserve that relationship within a batch, the early seenIds filter needs to stop discarding the second hit before it reaches the compound key.
  2. Refresh timestamps advance before dispatch. This keeps the next scheduler tick from immediately selecting the same refresh, but a failed branch can leave a recent timestamp without completed data. Recovery needs to account for that difference.
  3. Optional text-hash deduplication is off by default. Its normalization strips links, mentions, hashtags and punctuation, which can make different original contexts look the same. The hash is a content filter, not a security check.
  4. Each representation uses a stable-key upsert. Replaying a write can update its existing row, but the three writes don't share a transaction. A raw post can exist while its normalized form or query history is still missing.
  5. The static-data lock supplies an expiring orchestrator guard. The export doesn't establish that it behaves as a distributed lock across every possible n8n deployment.

HOW IT FITS TOGETHER

Architecture.

  • WF-00 decides which collections need refreshing using intervals from a control table. It holds an expiring lockUntil value in workflow static data and advances the selected refresh timestamps before dispatching asynchronous branches.
  • Query and polling workflows feed WF-30 with post text, metadata and discovery context. JavaScript normalizes the text, applies batch filters and creates separate row sets for raw posts, normalized posts and query hits.
  • Raw and normalized records are upserted by post_id; hits use post_id plus query_id. The three Data Table writes are independent, allowing existing rows to be updated on replay but leaving partial completion to reconcile.
  • Ranking and model-assisted drafting use the collected material, followed by a style check and Telegram handoff. The query-hit relation supplies the discovery context separately from the normalized text that those stages consume.
Explore the architecture map9 components · 10 connections

Scheduled collection and three-table normalization

A controller starts collection; accepted posts fan out into content and provenance stores before drafting.

Direct callAsync taskResponse / return
Control and collection
Content processing
Records and delivery

Scroll or drag the background to move. Use the zoom buttons to resize.Arrow keys move between components. Enter selects.

Choose a component to explore

Select a numbered component on the map or use the component menu. Its details and connections will appear here.

No component selected.

All connections (10)
  1. 01 · Scheduled controller02 · Query and polling workflowsDispatch collection · Async task
  2. 02 · Query and polling workflows03 · NormalizerCollected items · Direct call
  3. 03 · Normalizer04 · Raw postsUpsert · Async task
  4. 03 · Normalizer05 · Normalized postsUpsert · Async task
  5. 03 · Normalizer06 · Query hitsUpsert · Async task
  6. 03 · Normalizer07 · Candidate rankingContinue content path · Direct call
  7. 05 · Normalized posts07 · Candidate rankingNormalized material · Direct call
  8. 07 · Candidate ranking08 · Draft and style guardSelected candidates · Direct call
  9. 08 · Draft and style guard09 · Telegram handoffPrepared draft · Async task
  10. 09 · Telegram handoff01 · Scheduled controllerRun completion / lock release · Response / return

MADE WITH

n8n · Data Tables · JavaScript · OpenAI · Grok · Structured outputs · Telegram

CHECKS & RESULTS

What was checked.

A source review shows what the code does. It is not a fresh test of the running app.

  1. Code and development records reviewedReviewed

    The code, tests and available development records were checked. Reported test results keep their original scope. No fresh app or live deployment check was run for this write-up.

    Scope of this check
    Code review, not a fresh run of the app

NEXT

Still to work through.

The first regression I'd add is small: one post returned under two queries should produce one raw row, one normalized row and two hits. Then I'd interrupt each write and check that a retry restores the missing records. Those tests would exercise the data model and its recovery together; neither was run against the workflow runtime for this review.