Appendix B · section 21 of 32
ask/sections.ts
From Once Found, Look It Up.
import { referenceOf } from "./answer.js";
import type { Law } from "./corpus.js";
import { penaltyLaw, type PenaltyRow } from "./schedule.js";
// Once retrieval has found the section, the rest is a lookup, not a search.
// What *happens* is spread over three bodies, and all three are keyed by the
// section the code uses: the code itself holds the duty, the penalty range
// and any way out; the court's schedule holds the figure actually paid; the
// agency's own pages hold the process no section states.
/** At most this many laws of one section reach the model. */
export const SECTION_LAWS = 12;
/** At most this many laws reach the model on a question once its sections are whole. */
export const GIVEN_LAWS = 40;
/**
* The section a law belongs to, as its citation names it: the reference
* before " — " with its subsection removed. "§ 16-112(c)" and "§ 16-112(d)"
* are both "Md. Code, Transp. § 16-112". Null for a manual law, whose citation
* is a page.
*/
export function sectionOf(law: Pick<Law, "kind" | "citation">): string | null {
if (law.kind !== "statute") return null;
const section = referenceOf(law.citation)
.replace(/,\s*subd\..*$/, "")
.replace(/(?:\s*[–-]?\s*\([^()]*\))+[\s–-]*$/, "")
.trim();
return section === "" ? null : section;
}
/**
* The laws chosen, each statute with the rest of its section. The chosen
* laws stay first, in the order they were chosen, so the matched row is never
* pushed down by its neighbours; then each section's other rows in the order
* the file holds them. A manual law has no section and brings nothing.
*/
export function wholeSections(chosen: Law[], file: Law[]): Law[] {
const sections = [...new Set(chosen.map(sectionOf).filter((section): section is string => section !== null))];
if (sections.length === 0) return chosen;
const given = [...chosen];
const seen = new Set(chosen.map((law) => law.id));
for (const section of sections) {
let count = chosen.filter((law) => sectionOf(law) === section).length;
for (const law of file) {
if (given.length >= GIVEN_LAWS) return given;
if (count >= SECTION_LAWS) break;
if (seen.has(law.id) || sectionOf(law) !== section) continue;
given.push(law);
seen.add(law.id);
count += 1;
}
}
return given;
}
/** At most this many schedule rows join one section. */
export const SECTION_PENALTIES = 8;
/** At most this many schedule rows join a question's laws in all. */
export const GIVEN_PENALTIES = 16;
/**
* The laws given, each statute section followed by its rows of the court's
* fine schedule. Each row goes after the last law of its section, so it reads
* as the price of the law above it. The rows for the subsections the question
* matched come first; a row for a section that was not given is put last
* rather than hidden, so a lookup that matched too much is seen in what the
* model is given.
*/
export function withPenalties(given: Law[], rows: PenaltyRow[], chosen: Law[] = given): Law[] {
const sections = [...new Set(given.map(sectionOf).filter((section): section is string => section !== null))];
if (sections.length === 0) return given;
const found = rows.filter((row) => sections.includes(row.section));
const references = chosen
.filter((law) => sectionOf(law) !== null)
.map((law) => referenceOf(law.citation));
const named = (row: PenaltyRow): boolean => {
const own = `${row.section}${row.code.includes("(") ? row.code.slice(row.code.indexOf("(")) : ""}`;
return references.some((reference) => reference.startsWith(own) || own.startsWith(reference));
};
const ordered = [...found.filter(named), ...found.filter((row) => !named(row))];
const after = new Map<string, Law[]>();
const counted = new Map<string, number>();
let total = 0;
for (const row of ordered) {
if (total >= GIVEN_PENALTIES) break;
if ((counted.get(row.section) ?? 0) >= SECTION_PENALTIES) continue;
counted.set(row.section, (counted.get(row.section) ?? 0) + 1);
total += 1;
after.set(row.section, [...(after.get(row.section) ?? []), penaltyLaw(row)]);
}
const lastOf = new Map(given.map((law, index) => [sectionOf(law), index]));
const out: Law[] = [];
given.forEach((law, index) => {
out.push(law);
for (const [section, laws] of after) if (lastOf.get(section) === index) out.push(...laws);
});
return out;
}
/**
* The laws given, with the state's own pages after the manual and the code:
* the page is preferred for process and never for the rule of the road, and
* a page's paraphrase of the hydrant rule listed above the manual's own would
* be read first by a model told to answer from what it is given.
*/
export function pagesLast(laws: Law[]): Law[] {
return [...laws.filter((law) => law.kind !== "process"), ...laws.filter((law) => law.kind === "process")];
}