Task S5T09: Batch Aggregation Functions¶
Sprint Context¶
- Sprint index: Sprint 5 Task Index
- Tags: database, database-refactor, sprint-5, task-index
Source¶
- DBA source:
task-S5T09.dba.md - Specification source:
task-S5T09.specification.md
Resolved Decision¶
- S5T09 canonically owns the batch aggregation helpers for
stats.hourly_activityandstats.country_pair_flowsonly. - The canonical migration name is
20260310500800_create_batch_aggregation_functions.php. - Milestone and first-finder backfill functions are stale merge residue and are not part of this task.
- Batch helpers must use the canonical
stats.job_logcontract withjob_name,status,metadata,started_at, andcompleted_at.
Purpose & Scope¶
Creates idempotent batch functions that seed Sprint 5 aggregate tables from existing move history and can be safely re-run during deployment or repair.
| Function | Seeds into |
|---|---|
stats.fn_snapshot_hourly_activity() |
stats.hourly_activity |
stats.fn_snapshot_country_pair_flows() |
stats.country_pair_flows |
Out of scope:
- milestone backfills
- first-finder backfills
- live trigger logic
Requirements¶
| ID | Description | MoSCoW |
|---|---|---|
| REQ-650 | stats.fn_snapshot_hourly_activity() aggregates gk_moves into (date, hour, move_type) buckets |
MUST |
| REQ-651 | stats.fn_snapshot_hourly_activity() is idempotent via ON CONFLICT DO UPDATE |
MUST |
| REQ-652 | stats.fn_snapshot_country_pair_flows() detects cross-country pairs from canonical qualifying move history |
MUST |
| REQ-653 | stats.fn_snapshot_country_pair_flows() counts distinct GeoKrety per month/from/to tuple |
MUST |
| REQ-654 | Both functions are defined in stats schema with SECURITY DEFINER |
MUST |
| REQ-655 | Both functions write canonical stats.job_log rows using job_name, status, metadata, started_at, completed_at |
MUST |
| REQ-656 | Both functions return BIGINT affected-row counts, not status text |
MUST |
| REQ-657 | phinx rollback drops both functions cleanly |
MUST |
Acceptance Criteria¶
| # | Criterion | How to Verify |
|---|---|---|
| 1 | Both snapshot functions exist in stats schema |
\df stats.fn_snapshot_* |
| 2 | Hourly snapshot produces expected bucket counts | Seed known sample and compare exact rows |
| 3 | Country-pair snapshot produces only cross-country pairs | Verify same-country pairs are absent |
| 4 | Re-running both functions is idempotent | Row counts and values unchanged on second run |
| 5 | Both functions write canonical stats.job_log rows |
Inspect job_name, status, metadata, timestamps |
| 6 | Rollback removes both functions | phinx rollback |
Migration File¶
20260310500800_create_batch_aggregation_functions.php
Full SQL DDL¶
CREATE OR REPLACE FUNCTION stats.fn_snapshot_hourly_activity()
RETURNS BIGINT
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
-- Recompute hourly buckets from canonical move history, upsert into
-- `stats.hourly_activity`, log execution in `stats.job_log`, and
-- return the affected-row count.
RETURN 0;
END;
$$;
CREATE OR REPLACE FUNCTION stats.fn_snapshot_country_pair_flows()
RETURNS BIGINT
LANGUAGE plpgsql
SECURITY DEFINER
AS $$
BEGIN
-- Recompute cross-country month buckets from canonical qualifying move
-- history, upsert into `stats.country_pair_flows`, log execution in
-- `stats.job_log`, and return the affected-row count.
RETURN 0;
END;
$$;
Full Phinx Migration PHP Code¶
<?php
declare(strict_types=1);
use Phinx\Migration\AbstractMigration;
final class CreateBatchAggregationFunctions extends AbstractMigration
{
public function up(): void
{
$this->execute(<<<'SQL'
CREATE OR REPLACE FUNCTION stats.fn_snapshot_hourly_activity()
RETURNS BIGINT LANGUAGE plpgsql SECURITY DEFINER
AS $$
BEGIN
RETURN 0;
END;
$$;
CREATE OR REPLACE FUNCTION stats.fn_snapshot_country_pair_flows()
RETURNS BIGINT LANGUAGE plpgsql SECURITY DEFINER
AS $$
BEGIN
RETURN 0;
END;
$$;
SQL
);
}
public function down(): void
{
$this->execute(<<<'SQL'
DROP FUNCTION IF EXISTS stats.fn_snapshot_hourly_activity();
DROP FUNCTION IF EXISTS stats.fn_snapshot_country_pair_flows();
SQL
);
}
}
The placeholder bodies above must be replaced by the canonical batch aggregation logic required by REQ-650 through REQ-656.
Canonical Notes¶
stats.job_logwrites must use the canonical column set only;rows_affectedanddurationare obsolete.- Batch helpers are idempotent repair/backfill tools, not live triggers.
- Any merged text assigning milestone or first-finder batch ownership to S5T09 is obsolete.
SQL Usage Examples¶
SELECT stats.fn_snapshot_hourly_activity();
SELECT stats.fn_snapshot_country_pair_flows();
SELECT *
FROM stats.job_log
WHERE job_name IN ('fn_snapshot_hourly_activity', 'fn_snapshot_country_pair_flows')
ORDER BY completed_at DESC;
pgTAP Unit Tests¶
| Test ID | Assertion | Pass Condition |
|---|---|---|
| T-5.9.001 | stats.fn_snapshot_hourly_activity() exists |
has_function() |
| T-5.9.002 | stats.fn_snapshot_country_pair_flows() exists |
has_function() |
| T-5.9.003 | Hourly snapshot produces expected aggregate rows | exact match |
| T-5.9.004 | Country-pair snapshot produces expected cross-country rows | exact match |
| T-5.9.005 | Re-running functions is idempotent | exact match |
| T-5.9.006 | Canonical stats.job_log rows are written |
pass |
| T-5.9.007 | Rollback removes both functions | pass |
Implementation Checklist¶
- 1. Create
20260310500800_create_batch_aggregation_functions.php - 2.
phinx migrate— no errors - 3. Verify
\df stats.fn_snapshot_hourly_activity - 4. Verify
\df stats.fn_snapshot_country_pair_flows - 5. Validate hourly aggregation against known sample data
- 6. Validate country-pair aggregation against sequence test data
- 7. Verify canonical
stats.job_logwrites - 8. Run pgTAP T-5.9.001 through T-5.9.007
- 9.
phinx rollback— both functions dropped
Canonical Alignment¶
- S5T09 is the batch/manual aggregation task for hourly activity and country-pair flows only.
- Live milestone and first-finder detection belong to S5T07 and S5T08 respectively.
- Any stale return type or obsolete
stats.job_logcolumn usage in merged drafts is superseded by this task definition.
Agent Loop Log¶
- 2026-03-10T19:55:00Z —
dba: removed milestone and first-finder batch bodies from S5T09 and restored the canonical two-function batch scope. - 2026-03-10T19:55:00Z —
critical-thinking: reconciled the abbreviated Sprint 5 index description with the merged draft by treating extra batch helpers as stale residue. - 2026-03-10T19:55:00Z —
specification: canonized the migration name,BIGINTreturn contract, and canonicalstats.job_logusage.
Resolution¶
Q-035 is resolved by canonizing S5T09 as the hourly and country-pair batch aggregation task only.