Joel Stevick

Appendix B · section 20 of 32

ask/schedule.ts

From Once Found, Look It Up.

import type { Law } from "./corpus.js";

// The court's fine schedule: what a driver actually pays, whether it can be
// paid without going to court, and the points. The code says what a driver
// must do and, at most, the range a fine may fall in; the figure a learner
// pays for not showing a license is $50 in the District Court's schedule and
// in no section of the Transportation Article.

export interface PenaltyRow {
  id: string;
  state: string;
  /** The section the row joins to: the key the code's rows carry too. */
  section: string;
  /** The schedule's own reference, "16-112(c)", which the id is made from. */
  code: string;
  /** The offence in the schedule's own wording. */
  offence: string;
  fine_text: string;
  fine_min_cents: number | null;
  fine_max_cents: number | null;
  points: number | null;
  appear: "payable" | "must-appear" | "court-sets";
  source_url: string;
  source_title: string;
  as_of: string;
  /** The schedule's own line, verbatim: what the figure is grounded on. */
  quote: string;
}

const SOURCE = {
  state: "MD",
  source_url: "https://www.mdcourts.gov/sites/default/files/court-forms/dccr090.pdf",
  source_title: "District Court of Maryland, DC-CR-090 (Rev. 10/2026)",
  as_of: "2026-10-11",
} as const;

export const ROWS: PenaltyRow[] = [
  {
    ...SOURCE,
    id: "MD-P-16-112-b",
    section: "Md. Code, Transp. § 16-112",
    code: "16-112(b)",
    offence: "Failure of individual driving on hwy. to have lic. with them",
    fine_text: "$50.00",
    fine_min_cents: 5000,
    fine_max_cents: 5000,
    points: 0,
    appear: "payable",
    quote: "16 112 b Failure of individual driving on hwy. to have lic. with them $ 50.00 00",
  },
  {
    ...SOURCE,
    id: "MD-P-16-112-c",
    section: "Md. Code, Transp. § 16-112",
    code: "16-112(c)",
    offence: "Failure of individual driving on hwy. to display lic. to uniformed police on demand",
    fine_text: "$50.00",
    fine_min_cents: 5000,
    fine_max_cents: 5000,
    points: 0,
    appear: "payable",
    quote: "16 112 c Failure of individual driving on hwy. to display lic. to uniformed police on demand $ 50.00 00",
  },
  {
    ...SOURCE,
    id: "MD-P-21-604-d",
    section: "Md. Code, Transp. § 21-604",
    code: "21-604(d)",
    offence: "Driver failing to give turn signal continuously during last 100 ft. of travel before turning",
    fine_text: "$90.00",
    fine_min_cents: 9000,
    fine_max_cents: 9000,
    points: 1,
    appear: "payable",
    quote: "21 604 d Driver failing to give turn signal continuously during last 100 ft. of travel before turning $ 90.00 01",
  },
  {
    ...SOURCE,
    id: "MD-P-21-801-1",
    section: "Md. Code, Transp. § 21-801.1",
    code: "21-801.1",
    offence: "Driving veh. on hwy. at speed exceeding limit Exceeding posted speed limit by: 1 to 9 mph",
    fine_text: "$80.00",
    fine_min_cents: 8000,
    fine_max_cents: 8000,
    points: 1,
    appear: "payable",
    quote: "21 801.1 Driving veh. on hwy. at speed exceeding limit Exceeding posted speed limit by: 1 to 9 mph $ 80.00 01",
  },
];

/** Cents as a learner reads a price: "$50", "$25.50", "$1,000". */
export function dollars(cents: number): string {
  const whole = Math.floor(cents / 100);
  const rest = cents % 100;
  return `$${whole.toLocaleString("en-US")}${rest === 0 ? "" : `.${String(rest).padStart(2, "0")}`}`;
}

function pointsPhrase(points: number | null): string {
  if (points === null) return "";
  return points === 0 ? ", no points" : `, ${points} point${points === 1 ? "" : "s"}`;
}

// The law a schedule row is given to the model as: one sentence a learner can
// read. A row the court sets names the range and says so, never the low
// figure alone: "$25" for an offence a court may fine $200 is a promise the
// schedule does not make. Points only where the row has them: "no points"
// from a source that does not list points would be invented.
export function penaltySentence(row: PenaltyRow): string {
  const points = pointsPhrase(row.points);
  const { fine_min_cents: min, fine_max_cents: max } = row;
  if (row.appear === "must-appear") {
    return `${row.offence}: you must go to court, and the court sets the fine${points}.`;
  }
  if (row.appear === "court-sets") {
    const range =
      min !== null && max !== null
        ? `within ${dollars(min)} to ${dollars(max)}`
        : max !== null
          ? `of up to ${dollars(max)}`
          : min !== null
            ? `of at least ${dollars(min)}`
            : "in an amount the law leaves to it";
    return `${row.offence}: a fine set by the court ${range}${points}.`;
  }
  const fine = min === null ? row.fine_text : max === null || max === min ? dollars(min) : `${dollars(min)} to ${dollars(max)}`;
  return `${row.offence}: ${fine}, can be paid without going to court${points}.`;
}

/** A schedule row as a law the model is given and the checker grounds, cited to the schedule's own line. */
export function penaltyLaw(row: PenaltyRow): Law {
  return {
    id: row.id,
    law: penaltySentence(row),
    question: "",
    concept: row.offence,
    citation: `${row.source_title} — '${row.quote}'`,
    kind: "penalty",
    asOf: row.as_of,
  };
}