Files
spsays-training/lib/book-reference.ts
T

187 lines
5.5 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Book + chapter derivation for transcriptions.
*
* Transcriptions carry a free-text scripture `reference` (e.g. "SB 1.2.3",
* "BG Introduction", "CC Madhya-līlā 20.101") rather than structured
* book/chapter columns. This module is the single source of truth for turning
* those strings into a book identity and a chapter grouping, reused by the
* books grid, the listing page, and the book browser.
*
* The book registry mirrors `TranscriptionBooks` in the Go BFF
* (internal/repository/transcription_repo.go).
*/
export type BookCode = "SB" | "BG" | "CC" | "BS" | "NOD" | "ISO";
export const BOOKS: Record<BookCode, { title: string; order: number }> = {
SB: { title: "Śrīmad-Bhāgavatam", order: 1 },
BG: { title: "Bhagavad-gītā", order: 2 },
CC: { title: "Caitanya-caritāmṛta", order: 3 },
BS: { title: "Brahma-saṁhitā", order: 4 },
NOD: { title: "The Nectar of Devotion", order: 5 },
ISO: { title: "Śrī Īśopaniṣad", order: 6 },
};
/** Display title for a (possibly unknown) book code; "" if unknown. */
export function bookTitle(code: string): string {
const b = BOOKS[code.toUpperCase() as BookCode];
return b ? b.title : "";
}
/**
* Extract the (starting) verse number from a reference, for ordering lectures
* within a chapter. The verse is the last dotted numeric component, and for a
* range we take the start: "BG 2.13" → 13, "SB 1.2.3" → 3, "SB 3.27.11–16" →
* 11, "CC Madhya-līlā 20.101" → 101, "ISO Mantra 4" → 4. References with no
* number (e.g. bare "NOD") sort last (stable → keep their incoming order).
*/
export function parseVerse(reference: string | null): number {
const m = (reference ?? "").trim().match(/(\d+(?:\.\d+)*)/);
if (!m) return Number.MAX_SAFE_INTEGER;
const parts = m[1].split(".");
return Number(parts[parts.length - 1]);
}
/** A section is a canto (SB) or līlā (CC); null for books without that level. */
export type ChapterSection = {
key: string;
label: string;
order: number;
};
/** A chapter grouping for the accordion list. */
export type ChapterGroup = {
key: string;
label: string;
sort: [number, number];
/** Canto/līlā this chapter belongs to (SB, CC); null for flat books. */
section: ChapterSection | null;
};
// Caitanya-caritāmṛta līlā ordering (Ādi → Madhya → Antya).
const LILA_ORDER: Record<string, { label: string; order: number }> = {
ADI: { label: "Ādi-līlā", order: 1 },
MADHYA: { label: "Madhya-līlā", order: 2 },
ANTYA: { label: "Antya-līlā", order: 3 },
};
const OTHER: ChapterGroup = {
key: "other",
label: "Other",
sort: [9999, 0],
section: null,
};
/**
* Parse a transcription reference into its chapter group, given the book code.
* Tolerates the corpus's noise (en-dash vs hyphen, "Madhya" vs "Madhya-līlā",
* a stray "SB 7", etc.). Unparseable / missing references fall into "Other".
*/
export function parseChapter(code: string, reference: string | null): ChapterGroup {
const ref = (reference ?? "").trim();
if (!ref) return OTHER;
switch (code.toUpperCase() as BookCode) {
case "BG": {
if (/^BG\s+Introduction/i.test(ref)) {
return {
key: "bg-intro",
label: "Introduction",
sort: [0, 0],
section: null,
};
}
const m = ref.match(/^BG\s+(\d+)/);
if (m) {
const n = Number(m[1]);
return {
key: `bg-${n}`,
label: `Chapter ${n}`,
sort: [n, 0],
section: null,
};
}
return OTHER;
}
case "SB": {
const m = ref.match(/^SB\s+(\d+)\.(\d+)/);
if (m) {
const c = Number(m[1]);
const ch = Number(m[2]);
return {
key: `sb-${c}-${ch}`,
label: `Chapter ${ch}`,
sort: [c, ch],
section: { key: `sb-${c}`, label: `Canto ${c}`, order: c },
};
}
const cantoOnly = ref.match(/^SB\s+(\d+)\b/);
if (cantoOnly) {
const c = Number(cantoOnly[1]);
return {
key: `sb-${c}-0`,
label: "Other",
sort: [c, 0],
section: { key: `sb-${c}`, label: `Canto ${c}`, order: c },
};
}
return OTHER;
}
case "CC": {
const m = ref.match(/^CC\s+(Ādi|Adi|Madhya|Antya)(?:-līlā)?\s+(\d+)/i);
if (m) {
const lila = LILA_ORDER[m[1].toUpperCase().replace("Ā", "A")];
const ch = Number(m[2]);
if (lila) {
return {
key: `cc-${lila.order}-${ch}`,
label: `Chapter ${ch}`,
sort: [lila.order, ch],
section: {
key: `cc-${lila.order}`,
label: lila.label,
order: lila.order,
},
};
}
}
return OTHER;
}
case "BS": {
const m = ref.match(/^BS\s+(\d+)/);
if (m) {
const n = Number(m[1]);
return {
key: `bs-${n}`,
label: `Chapter ${n}`,
sort: [n, 0],
section: null,
};
}
return OTHER;
}
case "ISO": {
const m = ref.match(/^ISO\s+Mantra\s+(\d+)/i);
if (m) {
const n = Number(m[1]);
return {
key: `iso-${n}`,
label: `Mantra ${n}`,
sort: [n, 0],
section: null,
};
}
return { key: "iso", label: "Śrī Īśopaniṣad", sort: [0, 0], section: null };
}
case "NOD":
return {
key: "nod",
label: "The Nectar of Devotion",
sort: [0, 0],
section: null,
};
default:
return OTHER;
}
}