TypeDrop
2026-05-04 Challenge
2026-05-04
Easy
Typed Expense Report Builder
You're building the expense-reporting layer for a travel management app. Raw expense entries arrive as `unknown` from an employee submission form; your engine must validate them, group them by category, and return a strongly-typed report summary — with zero `any`.
Goals
- Implement `isExpenseCategory` as a type predicate that narrows `unknown` to `ExpenseCategory`.
- Implement `validateExpenseEntry` to validate all required fields of a raw unknown payload and return a discriminated-union `ValidationResult`.
- Implement `buildExpenseReport` to validate every raw entry, group valid ones into a `CategorySummary` (all five keys always present), and compute the grand total.
- Ensure every `ExpenseCategory` key exists in `categorySummary` even when no entries belong to that category.
challenge.ts
export type ExpenseCategory =
| "travel" | "meals" | "accommodation" | "equipment" | "other";
export interface ExpenseEntry {
id: string;
employeeId: string;
category: ExpenseCategory;
amountCents: number; // positive integer, in cents
date: string; // "YYYY-MM-DD"
description: string;
}
export type CategorySummary = Record<
ExpenseCategory,
{ totalCents: number; count: number }
>;
export interface ExpenseReport {
entries: ExpenseEntry[];
categorySummary: CategorySummary;
grandTotalCents: number;
failures: ValidationFailure[];
}
// Your main entry point:
export function buildExpenseReport(rawEntries: unknown[]): ExpenseReport {
// TODO
throw new Error("Not implemented");
}
Hints (click to reveal)
Hints
- A type predicate has the form `(value: unknown): value is SomeType` — use it on `isExpenseCategory` so the compiler trusts the narrowing downstream.
- Seed your `categorySummary` object by iterating over a const array of all five `ExpenseCategory` values before processing any entries — this guarantees every key is present.
- When checking `amountCents`, combine `typeof x === 'number'`, `x > 0`, and `Math.trunc(x) === x` to satisfy the positive-integer requirement without casting.
Useful resources
Or clone locally
git clone -b challenge/2026-05-04 https://github.com/niltonheck/typedrop.git