Joel Stevick

Appendix B · section 23 of 32

ask/chunk.ts

From A Corpus You Can Trust.

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

// A section of a state's vehicle code, cut into the pieces the box retrieves.
// The unit is a subsection: the model reads a handful of laws, and a handful
// of whole sections of a parking statute would be a chapter. Every piece is
// lines of the committed source joined with a space, so a test can find each
// one verbatim in its file.

/** Roughly a paragraph: long enough for a whole rule, short enough that eight are a page. */
export const CHUNK_CHARS = 900;

export interface CodeSection {
  /** The state: "MD". */
  code: string;
  /** "16-112". */
  section: string;
  /** "Md. Code, Transp. § 16-112". */
  cite: string;
  /** The section's catchline, where the state publishes one. */
  heading: string;
  /** The day the section was fetched. */
  asOf: string;
  text: string;
}

type Kind = "lower" | "digit";

interface Label {
  kind: Kind;
  /** The ordinal: a=1, b=2; 1=1. */
  value: number;
  text: string;
}

// "(a)", "(b)" or "(1)", "(2)": whichever the state writes its top-level
// subsections in. The production chunker reads a dozen more shapes -- "A.",
// "1-A.", "I.", "(d–1)" -- each added when a state's code filed a rule under
// the wrong label.
const LABEL = /^\(([a-z]{1,2}|\d{1,3})\)/;

function labelOf(line: string): Label | null {
  const match = LABEL.exec(line);
  if (match === null) return null;
  const body = match[1];
  if (/^\d+$/.test(body)) return { kind: "digit", value: Number(body), text: body };
  return { kind: "lower", value: body.charCodeAt(0) - 96, text: body };
}

/** A page's own banner above the section, and a heading line that is only the section's own number. */
function isHeader(line: string, section: string): boolean {
  return /^Article\s+[-–—]\s+\S/.test(line) || line.replace(/[–—‑]/g, "-") === `§${section}.`;
}

export function bodyLines(text: string, section: string): string[] {
  const lines = text.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
  let start = 0;
  while (start < Math.min(lines.length - 1, 2) && isHeader(lines[start], section)) start += 1;
  return lines.slice(start);
}

/** Lines into pieces of at most CHUNK_CHARS, cut at line boundaries. */
function pieces(lines: string[]): string[][] {
  const out: string[][] = [];
  let current: string[] = [];
  for (const line of lines) {
    if (current.length > 0 && [...current, line].join(" ").length > CHUNK_CHARS) {
      out.push(current);
      current = [];
    }
    current.push(line);
  }
  if (current.length > 0) out.push(current);
  return out;
}

export function chunkSection(section: CodeSection): Law[] {
  const lines = bodyLines(section.text, section.section);
  const first = lines.findIndex((line) => labelOf(line) !== null);
  const top = first < 0 ? null : labelOf(lines[first]);

  // Group the lines by top-level label: a new group opens on the next label
  // of the top kind in sequence, so a nested "(1)" under "(c)" stays inside
  // (c), and "(i)" after "(h)" is the ninth subsection.
  const groups: { label: string | null; lines: string[] }[] = [];
  if (top === null) {
    groups.push({ label: null, lines });
  } else {
    if (first > 0) groups.push({ label: null, lines: lines.slice(0, first) });
    let current = top.value;
    let group = { label: top.text, lines: [lines[first]] };
    for (const line of lines.slice(first + 1)) {
      const label = labelOf(line);
      if (label !== null && label.kind === top.kind && label.value === current + 1) {
        groups.push(group);
        current = label.value;
        group = { label: label.text, lines: [] };
      }
      group.lines.push(line);
    }
    groups.push(group);
  }

  const base = `${section.code}-C-${section.section.replace(/[^A-Za-z0-9.]+/g, "-")}`;
  const chunks: Law[] = [];
  let paragraph = 0;
  for (const group of groups) {
    for (const [n, piece] of pieces(group.lines).entries()) {
      const text = piece.join(" ");
      const suffix = group.label === null ? `p${(paragraph += 1)}` : n === 0 ? group.label : `${group.label}-p${n + 1}`;
      const reference = group.label === null ? "" : `(${group.label})`;
      chunks.push({
        id: `${base}-${suffix}`,
        law: text,
        question: "",
        concept: section.heading,
        citation: `${section.cite}${reference} — '${text}'`,
        kind: "statute",
        asOf: section.asOf,
      });
    }
  }
  return chunks;
}