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.jsonwith name@packages/format, scripts (build/dev/lint/lint:fix/test/test:type), deps via catalog references -
@packages/format/tsconfig.jsonextending@repo/typescript/lib -
@packages/format/eslint.config.mjsusingbase()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
-
Reporttype:{ meta: ReportMeta, units: Record<string, UnitReport> } -
ReportMeta:{ entity, period, generated_at, scope: string[] } -
UnitReport:{ revenue: RevenueSection, programs: ProgramProgressSection, channels: ChannelMarketingSection, schools: SchoolProgressSection } -
RevenueSection: includesrevenue_actuals,revenue_target,gap_to_target,achievement_pct,last_period,last_year,best_year(all nullable except actuals) -
ProgramProgressSection: array of program rows withproduct_name,order_count,student_count,revenue,customer_type,target(nullable) -
ChannelMarketingSection: array of channel rows withchannel_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 toatlas.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-apifor Bun stability (accepted deviation fromduckdb-asyncwording 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)returnsRevenueSection - Queries
mart_revenuefor current period actuals andstg_targetsfor target -
last_period: previous calendar month revenue (handles January → December of prior year) -
last_year: same month, year - 1 -
best_year: MAXrevenue_actualsfor 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)returnsProgramProgressSection - 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)returnsChannelMarketingSection - 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)returnsSchoolProgressSection(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? })returnsReport - 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
Reportin-memory -
meta.generated_atset to current ISO timestamp -
meta.scopelists 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 installcompletes -
bun run test:type --filter @packages/formatpasses
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-02exits 0 -
output/monthly/2026-02-report.jsonexists - JSON contains
meta,unitswith 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.