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.
Useful resources
Or clone locally
git clone -b challenge/2026-08-26 https://github.com/niltonheck/typedrop.git