TypeDrop

2026-08-26 Challenge

2026-08-26 Medium

Typed CSV Report Aggregator with Schema Validation & Grouped Statistics

You're building the analytics backend for a SaaS billing platform. Raw sales CSVs arrive from multiple regional offices — each row must be validated against a typed schema, invalid rows collected as structured errors, and valid rows aggregated into per-region, per-product summaries with typed statistics.

Goals

  • Implement branded-type factory functions (`toRegionCode`, `toProductSku`) that validate raw strings and return a branded type or `null` — without type assertions.
  • Implement `validateRow` to parse a raw CSV row into a `SalesRecord`, returning a discriminated `Result<SalesRecord, ParseError>` with first-field-wins error reporting.
  • Implement `aggregateReport` to validate all rows in a single pass, collect structured errors, and group valid records into a nested `Map<RegionCode, Map<ProductSku, GroupStats>>` with correct statistics.
  • Implement the generic `groupBy<T, K extends string>` utility whose return type is the narrowed `Record<K, T[]>` — not a wider `Record<string, T[]>`.
challenge.ts
// Core types at a glance:

declare const __brand: unique symbol;
type Brand<T, B> = T & { readonly [__brand]: B };

type RegionCode = Brand<string, "RegionCode">;
type ProductSku  = Brand<string, "ProductSku">;

interface SalesRecord {
  readonly region:  RegionCode;
  readonly sku:     ProductSku;
  readonly units:   number;
  readonly revenue: number;
  readonly date:    Date;
}

type Ok<T>  = { readonly ok: true;  readonly value: T };
type Err<E> = { readonly ok: false; readonly error: E };
type Result<T, E> = Ok<T> | Err<E>;

// Main functions you must implement:
function validateRow(raw: Record<string, string>, rowIndex: number): Result<SalesRecord, ParseError>;

function aggregateReport(rows: Record<string, string>[]): AggregateReport;

function groupBy<T, K extends string>(
  items: readonly T[],
  keyFn: (item: T) => K
): Record<K, T[]>;
Hints (click to reveal)

Hints

  • Branded types can be 'created' without `as` by using a helper function whose return type is explicitly declared as the branded type — the compiler trusts the annotation at the function boundary.
  • For `validateRow`, validate each field sequentially and return early on the first failure; this naturally implements first-field-wins without any complex logic.
  • For the `summary` mapped type, write `{ [K in keyof Omit<GroupStats, 'region' | 'sku'>]: number }` — iterate over your `byRegion` Map's entries in a second pass to accumulate totals.

Or clone locally

git clone -b challenge/2026-08-26 https://github.com/niltonheck/typedrop.git