Atlas Plan
Plans004 2026 02 20 Format Layer

Phase 1: Package Scaffold + Types

  • Purpose: Create the workspace package and define the Report type hierarchy

T-001 - Scaffold @packages/format package

Create package.json, tsconfig.json, eslint.config.mjs for @packages/format.

  • Status: completed
  • Priority: P0
  • Dependencies: none

Acceptance

  • @packages/format/package.json with name @packages/format, scripts (build/dev/lint/lint:fix/test/test:type), deps via catalog references
  • @packages/format/tsconfig.json extending @repo/typescript/lib
  • @packages/format/eslint.config.mjs using base() from @repo/lint/base
  • Added to root workspace (if not already covered by glob)

Files

  • @packages/format/package.json
  • @packages/format/tsconfig.json
  • @packages/format/eslint.config.mjs

T-002 - Define Report TypeScript types

Define the full Report type hierarchy in @source/types.ts. This is the contract between the Format layer and the Present + Dashboard layers.

  • Status: completed
  • Priority: P0
  • Dependencies: T-001

Acceptance

  • Report type: { meta: ReportMeta, units: Record<string, UnitReport> }
  • ReportMeta: { entity, period, generated_at, scope: string[] }
  • UnitReport: { revenue: RevenueSection, programs: ProgramProgressSection, channels: ChannelMarketingSection, schools: SchoolProgressSection }
  • RevenueSection: includes revenue_actuals, revenue_target, gap_to_target, achievement_pct, last_period, last_year, best_year (all nullable except actuals)
  • ProgramProgressSection: array of program rows with product_name, order_count, student_count, revenue, customer_type, target (nullable)
  • ChannelMarketingSection: array of channel rows with channel_name, closings, students, contribution_pct, customer_type
  • SchoolProgressSection: { organizations: string[], years: number[], matrix: Record<string, Record<number, number>> }
  • All types exported from @source/index.ts

Files

  • @packages/format/@source/types.ts
  • @packages/format/@source/index.ts

Phase 2: DuckDB Client Helper

  • Purpose: Shared DuckDB connection utility for the format section readers

T-003 - Implement DuckDB client helper

Create a lightweight wrapper around duckdb-async for use by section readers. Handles connection lifecycle and provides a query<T> helper.

  • Status: completed
  • Priority: P0
  • Dependencies: T-001

Acceptance

  • createDuckClient(dbPath?) opens connection to atlas.db (path defaults to ./atlas.db)
  • query<T>(sql, params?) executes a query and returns typed rows
  • Client is closeable (close() method)
  • Exported from @source/duck.ts

Notes

  • Implemented using @duckdb/node-api for Bun stability (accepted deviation from duckdb-async wording in task description).

Files

  • @packages/format/@source/duck.ts

Phase 3: Section Readers

  • Purpose: One reader per mart — queries mart data for a specific unit + period and returns typed section data

T-004 - revenue.ts — Revenue section reader

Reads mart_revenue and stg_targets, computes all comparison periods (last_period, last_year, best_year), and returns RevenueSection.

  • Status: completed
  • Priority: P0
  • Dependencies: T-002, T-003

Acceptance

  • readRevenue(client, unitCode, year, month) returns RevenueSection
  • Queries mart_revenue for current period actuals and stg_targets for target
  • last_period: previous calendar month revenue (handles January → December of prior year)
  • last_year: same month, year - 1
  • best_year: MAX revenue_actuals for same month across all years in mart
  • All values nullable — returns nulls if no data found, never throws

Files

  • @packages/format/@source/sections/revenue.ts

T-005 - orders.ts — Program progress section reader

Reads mart_program_progress for a unit + period and returns ProgramProgressSection.

  • Status: completed
  • Priority: P0
  • Dependencies: T-002, T-003

Acceptance

  • readPrograms(client, unitCode, year, month) returns ProgramProgressSection
  • Returns all programs for the unit+period with order_count, student_count, revenue, customer_type breakdown
  • Empty array if no data

Files

  • @packages/format/@source/sections/orders.ts

T-006 - marketing.ts — Channel marketing section reader

Reads mart_channel_marketing for a unit + period and returns ChannelMarketingSection.

  • Status: completed
  • Priority: P0
  • Dependencies: T-002, T-003

Acceptance

  • readChannels(client, unitCode, year, month) returns ChannelMarketingSection
  • Returns all channels with closings, students, contribution_pct, customer_type
  • Empty array if no data

Files

  • @packages/format/@source/sections/marketing.ts

T-007 - schools.ts — School progress section reader

Reads mart_school_progress for a unit across all years and returns SchoolProgressSection as a year × organization matrix.

  • Status: completed
  • Priority: P0
  • Dependencies: T-002, T-003

Acceptance

  • readSchools(client, unitCode) returns SchoolProgressSection (no period filter — school progress is annual)
  • organizations: sorted list of all org names that appear in any year
  • years: sorted ascending list of all years in the mart
  • matrix: Record<orgName, Record<year, studentCount>> — 0 for years with no students
  • Pivot performed in TypeScript from flat mart rows

Files

  • @packages/format/@source/sections/schools.ts

Phase 4: Report Assembler + CLI

  • Purpose: Assemble all sections into a Report, write to disk, and wire the CLI

T-008 - report.ts — Report assembler

Combines all 4 section readers into a complete Report, writes output/monthly/{period}-report.json, and returns the report in-memory.

  • Status: completed
  • Priority: P0
  • Dependencies: T-004, T-005, T-006, T-007

Acceptance

  • assembleReport(options: { entity, year, month, units? }) returns Report
  • Calls all 4 section readers for each unit in scope
  • Writes to output/monthly/{YYYY-MM}-report.json (creates directory if missing)
  • Returns the assembled Report in-memory
  • meta.generated_at set to current ISO timestamp
  • meta.scope lists the unit codes included

Files

  • @packages/format/@source/report.ts

T-009 - index.ts — CLI entry point

Wire the CLI with --entity, --period, --unit flags. Parse --period to year/month integers. Call assembleReport.

  • Status: completed
  • Priority: P0
  • Dependencies: T-008

Acceptance

  • --entity IONS (required)
  • --period 2026-02 (required) parsed to { year: 2026, month: 2 }
  • --unit TM,WLC_ENGLISH (optional) parsed to array; if omitted, all units for entity
  • Logs output file path on success
  • Exits 0 on success, non-zero on error

Files

  • @packages/format/@source/index.ts

Phase 5: Verification

  • Purpose: End-to-end run against real mart data

T-010 - bun install + type-check

Install dependencies and verify type-check passes.

  • Status: completed
  • Priority: P0
  • Dependencies: T-001, T-002, T-003, T-004, T-005, T-006, T-007, T-008, T-009

Acceptance

  • bun install completes
  • bun run test:type --filter @packages/format passes

Files

  • bun.lock

T-011 - End-to-end format verification

Run the CLI against real 2026-02 data and verify the output.

  • Status: completed
  • Priority: P0
  • Dependencies: T-010

Acceptance

  • bun run format --entity IONS --period 2026-02 exits 0
  • output/monthly/2026-02-report.json exists
  • JSON contains meta, units with at least TM and WLC_ENGLISH entries
  • Revenue section has non-null revenue_actuals
  • Programs section has at least 1 program row
  • Channels section has at least 1 channel row
  • Schools section has at least 1 organization in the matrix

Notes

  • Initial run exposed two environment issues: package-relative DB path and Bun crash with duckdb-async; both were resolved.

On this page