hours: add the day's-collect resolver
Terce/Sext/None's Oratio is the day's own Proper-of-season/-saints collect, not a fixed text — getDayCollect() resolves it from the LiturgicalDay's winner (temporal id or sanctoral saint's propers), falling through to an honest "missing" status for saints that don't have authored propers yet. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -12,7 +12,7 @@
|
|||||||
import type { FeastClass } from './types';
|
import type { FeastClass } from './types';
|
||||||
import sanctoralCalendarData from '../data/calendar/sanctoral-calendar.yml';
|
import sanctoralCalendarData from '../data/calendar/sanctoral-calendar.yml';
|
||||||
|
|
||||||
interface SaintRecord {
|
export interface SaintRecord {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
rank: FeastClass;
|
rank: FeastClass;
|
||||||
@@ -38,6 +38,13 @@ export interface SanctoralCandidate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Which saint(s) (if any) are assigned to this date. `isoDate`'s year is ignored — the sanctoral cycle repeats every civil year. */
|
/** Which saint(s) (if any) are assigned to this date. `isoDate`'s year is ignored — the sanctoral cycle repeats every civil year. */
|
||||||
|
/** The saint's full record — used when something more than id/name/rank is
|
||||||
|
* needed, e.g. hours/resolve-common.ts's getDayCollect looking up whether
|
||||||
|
* a real collect has been authored for a winning saint. */
|
||||||
|
export function getSaintRecord(id: string): SaintRecord | undefined {
|
||||||
|
return saintsById.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
export function getSanctoralCandidatesFor(isoDate: string): SanctoralCandidate[] {
|
export function getSanctoralCandidatesFor(isoDate: string): SanctoralCandidate[] {
|
||||||
const monthDay = isoDate.slice(5);
|
const monthDay = isoDate.slice(5);
|
||||||
const ids = sanctoralCalendar.days[monthDay] ?? [];
|
const ids = sanctoralCalendar.days[monthDay] ?? [];
|
||||||
|
|||||||
@@ -1,12 +1,38 @@
|
|||||||
import type { ResolvedText } from './types';
|
import type { ResolvedText } from './types';
|
||||||
import { getCommonProper } from '../propers';
|
import type { LiturgicalDay } from '../calendar/types';
|
||||||
|
import type { ProperText } from '../propers';
|
||||||
|
import { getCommonProper, getTemporalProper } from '../propers';
|
||||||
|
import { getSaintRecord } from '../calendar/feasts';
|
||||||
import { splitAntiphon } from './antiphon';
|
import { splitAntiphon } from './antiphon';
|
||||||
|
|
||||||
|
function toResolvedText(proper: ProperText): ResolvedText {
|
||||||
|
return { text: proper.text, status: proper.status, citation: proper.citation };
|
||||||
|
}
|
||||||
|
|
||||||
/** Shared by every hour's resolver — looks up a common proper by id and
|
/** Shared by every hour's resolver — looks up a common proper by id and
|
||||||
* shapes it as a ResolvedText. */
|
* shapes it as a ResolvedText. */
|
||||||
export function resolveCommon(id: string): ResolvedText {
|
export function resolveCommon(id: string): ResolvedText {
|
||||||
const proper = getCommonProper(id);
|
return toResolvedText(getCommonProper(id));
|
||||||
return { text: proper.text, status: proper.status, citation: proper.citation };
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The day's own collect — real for the vast majority of days (a temporal
|
||||||
|
* winner always resolves, since all 52 Sunday collects are authored and
|
||||||
|
* ferias inherit the governing Sunday's via calendar/temporal-id.ts), and
|
||||||
|
* honestly "missing" for the handful of days a saint wins outright until
|
||||||
|
* that saint's own propers are authored (`saints/<id>.yml`'s `propers`
|
||||||
|
* field is still `null` for all of them today) — same "resolve as
|
||||||
|
* pending" convention as everywhere else, not a special case to handle.
|
||||||
|
*/
|
||||||
|
export function getDayCollect(day: LiturgicalDay): ResolvedText {
|
||||||
|
if (day.winner.kind === 'temporal') {
|
||||||
|
return toResolvedText(getTemporalProper(`${day.winner.id}-collect`));
|
||||||
|
}
|
||||||
|
const saint = getSaintRecord(day.winner.id);
|
||||||
|
if (saint?.propers) {
|
||||||
|
return resolveCommon(`${saint.propers}-collect`);
|
||||||
|
}
|
||||||
|
return { text: {}, status: { la: 'missing', en: 'missing' } };
|
||||||
}
|
}
|
||||||
|
|
||||||
export function verifiedText(text: Partial<Record<string, string>>): ResolvedText {
|
export function verifiedText(text: Partial<Record<string, string>>): ResolvedText {
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { resolveDay } from '../../src/calendar';
|
||||||
|
import { getDayCollect } from '../../src/hours/resolve-common';
|
||||||
|
|
||||||
|
describe('getDayCollect', () => {
|
||||||
|
it('resolves a real, verified collect for an ordinary Sunday', () => {
|
||||||
|
const day = resolveDay('2026-08-09'); // post-pentecost-11
|
||||||
|
const collect = getDayCollect(day);
|
||||||
|
expect(collect.status.la).toBe('verified');
|
||||||
|
expect(collect.status.en).toBe('verified');
|
||||||
|
expect(collect.text.en).toContain('Let us pray.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves a real, verified collect for a ferial day via its governing Sunday', () => {
|
||||||
|
const day = resolveDay('2026-06-14'); // post-pentecost-03's Sunday
|
||||||
|
const collect = getDayCollect(day);
|
||||||
|
expect(collect.status.la).toBe('verified');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('resolves as honestly missing for a saint with no authored propers yet', () => {
|
||||||
|
const day = resolveDay('2026-08-10'); // St. Lawrence wins outright, propers: null
|
||||||
|
expect(day.winner.kind).toBe('sanctoral');
|
||||||
|
const collect = getDayCollect(day);
|
||||||
|
expect(collect.status.la).toBe('missing');
|
||||||
|
expect(collect.status.en).toBe('missing');
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user