Files
TaroTime/app/tarot-database-assembly.js
2026-03-07 13:38:13 -08:00

318 lines
12 KiB
JavaScript
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.
/* tarot-database-assembly.js — Tarot card assembly helpers */
(function () {
"use strict";
function createTarotDatabaseAssembly(dependencies) {
const getTarotDbConfig = dependencies?.getTarotDbConfig;
const canonicalCardName = dependencies?.canonicalCardName;
const formatMonthDayLabel = dependencies?.formatMonthDayLabel;
const applyMeaningText = dependencies?.applyMeaningText;
const buildDecanMetadata = dependencies?.buildDecanMetadata;
const listMonthNumbersBetween = dependencies?.listMonthNumbersBetween;
const buildHebrewLetterLookup = dependencies?.buildHebrewLetterLookup;
const createRelation = dependencies?.createRelation;
const parseLegacyRelation = dependencies?.parseLegacyRelation;
const buildHebrewLetterRelation = dependencies?.buildHebrewLetterRelation;
const buildMajorDynamicRelations = dependencies?.buildMajorDynamicRelations;
const buildMinorDecanRelations = dependencies?.buildMinorDecanRelations;
const monthNameByNumber = dependencies?.monthNameByNumber && typeof dependencies.monthNameByNumber === "object"
? dependencies.monthNameByNumber
: {};
const monthIdByNumber = dependencies?.monthIdByNumber && typeof dependencies.monthIdByNumber === "object"
? dependencies.monthIdByNumber
: {};
const majorHebrewLetterIdByCard = dependencies?.majorHebrewLetterIdByCard && typeof dependencies.majorHebrewLetterIdByCard === "object"
? dependencies.majorHebrewLetterIdByCard
: {};
if (
typeof getTarotDbConfig !== "function"
|| typeof canonicalCardName !== "function"
|| typeof formatMonthDayLabel !== "function"
|| typeof applyMeaningText !== "function"
|| typeof buildDecanMetadata !== "function"
|| typeof listMonthNumbersBetween !== "function"
|| typeof buildHebrewLetterLookup !== "function"
|| typeof createRelation !== "function"
|| typeof parseLegacyRelation !== "function"
|| typeof buildHebrewLetterRelation !== "function"
|| typeof buildMajorDynamicRelations !== "function"
|| typeof buildMinorDecanRelations !== "function"
) {
throw new Error("Tarot database assembly dependencies are incomplete");
}
function buildMajorCards(referenceData, magickDataset) {
const tarotDb = getTarotDbConfig(referenceData);
const dynamicRelations = buildMajorDynamicRelations(referenceData);
const hebrewLookup = buildHebrewLetterLookup(magickDataset);
return tarotDb.majorCards.map((card) => {
const canonicalName = canonicalCardName(card.name);
const dynamic = dynamicRelations.get(canonicalName) || [];
const hebrewLetterId = majorHebrewLetterIdByCard[canonicalName] || null;
const hebrewLetterRelation = buildHebrewLetterRelation(hebrewLetterId, hebrewLookup);
const staticRelations = (card.relations || [])
.map((relation) => parseLegacyRelation(relation))
.filter((relation) => relation.type !== "hebrewLetter" && relation.type !== "zodiac" && relation.type !== "planet");
return {
arcana: "Major",
name: card.name,
number: card.number,
suit: null,
rank: null,
hebrewLetterId,
hebrewLetter: hebrewLetterRelation?.data || null,
summary: card.summary,
meanings: {
upright: card.upright,
reversed: card.reversed
},
keywords: [...card.keywords],
relations: [
...staticRelations,
...(hebrewLetterRelation ? [hebrewLetterRelation] : []),
...dynamic
]
};
});
}
function buildNumberMinorCards(referenceData) {
const tarotDb = getTarotDbConfig(referenceData);
const decanRelations = buildMinorDecanRelations(referenceData);
const cards = [];
tarotDb.suits.forEach((suit) => {
const suitKey = suit.toLowerCase();
const suitInfo = tarotDb.suitInfo[suitKey];
if (!suitInfo) {
return;
}
tarotDb.numberRanks.forEach((rank) => {
const rankKey = rank.toLowerCase();
const rankInfo = tarotDb.rankInfo[rankKey];
if (!rankInfo) {
return;
}
const cardName = `${rank} of ${suit}`;
const dynamicRelations = decanRelations.get(canonicalCardName(cardName)) || [];
cards.push({
arcana: "Minor",
name: cardName,
number: null,
suit,
rank,
summary: `${rank} energy expressed through ${suitInfo.domain}.`,
meanings: {
upright: `${rankInfo.upright} In ${suit}, this emphasizes ${suitInfo.domain}.`,
reversed: `${rankInfo.reversed} In ${suit}, this may distort ${suitInfo.domain}.`
},
keywords: [...rankInfo.keywords, ...suitInfo.keywords],
relations: [
createRelation("element", suitInfo.element, `Element: ${suitInfo.element}`, {
name: suitInfo.element
}),
createRelation("suitDomain", `${suitKey}-${rankKey}`, `Suit domain: ${suitInfo.domain}`, {
suit,
rank,
domain: suitInfo.domain
}),
createRelation("numerology", rankInfo.number, `Numerology: ${rankInfo.number}`, {
value: rankInfo.number
}),
...dynamicRelations
]
});
});
});
return cards;
}
function buildCourtMinorCards(referenceData) {
const tarotDb = getTarotDbConfig(referenceData);
const cards = [];
const signs = Array.isArray(referenceData?.signs) ? referenceData.signs : [];
const signById = Object.fromEntries(signs.map((sign) => [sign.id, sign]));
const decanById = new Map();
const decansBySign = referenceData?.decansBySign || {};
Object.entries(decansBySign).forEach(([signId, decans]) => {
const sign = signById[signId];
if (!sign || !Array.isArray(decans)) {
return;
}
decans.forEach((decan) => {
if (!decan?.id) {
return;
}
const meta = buildDecanMetadata(decan, sign);
if (meta) {
decanById.set(decan.id, meta);
}
});
});
tarotDb.suits.forEach((suit) => {
const suitKey = suit.toLowerCase();
const suitInfo = tarotDb.suitInfo[suitKey];
if (!suitInfo) {
return;
}
tarotDb.courtRanks.forEach((rank) => {
const rankKey = rank.toLowerCase();
const courtInfo = tarotDb.courtInfo[rankKey];
if (!courtInfo) {
return;
}
const cardName = `${rank} of ${suit}`;
const windowDecanIds = tarotDb.courtDecanWindows[cardName] || [];
const windowDecans = windowDecanIds
.map((decanId) => decanById.get(decanId) || null)
.filter(Boolean);
const dynamicRelations = [];
const monthKeys = new Set();
windowDecans.forEach((meta) => {
dynamicRelations.push(
createRelation(
"decan",
`${meta.signId}-${meta.index}-${rankKey}-${suitKey}`,
`Decan ${meta.index}: ${meta.signSymbol} ${meta.signName} (${meta.startDegree}°–${meta.endDegree}°)${meta.dateRange ? ` · ${meta.dateRange.label}` : ""}`.trim(),
{
signId: meta.signId,
signName: meta.signName,
signSymbol: meta.signSymbol,
index: meta.index,
startDegree: meta.startDegree,
endDegree: meta.endDegree,
dateStart: meta.dateRange?.startToken || null,
dateEnd: meta.dateRange?.endToken || null,
dateRange: meta.dateRange?.label || null
}
)
);
const dateRange = meta.dateRange;
if (dateRange?.start && dateRange?.end) {
const monthNumbers = listMonthNumbersBetween(dateRange.start, dateRange.end);
monthNumbers.forEach((monthNo) => {
const monthId = monthIdByNumber[monthNo];
const monthName = monthNameByNumber[monthNo] || `Month ${monthNo}`;
const monthKey = `${monthId}:${meta.signId}:${meta.index}`;
if (!monthId || monthKeys.has(monthKey)) {
return;
}
monthKeys.add(monthKey);
dynamicRelations.push(
createRelation(
"calendarMonth",
`${monthId}-${meta.signId}-${meta.index}-${rankKey}-${suitKey}`,
`Calendar month: ${monthName} (${meta.signName} decan ${meta.index})`,
{
monthId,
name: monthName,
monthOrder: monthNo,
signId: meta.signId,
signName: meta.signName,
decanIndex: meta.index,
dateRange: meta.dateRange?.label || null
}
)
);
});
}
});
if (windowDecans.length) {
const firstRange = windowDecans[0].dateRange;
const lastRange = windowDecans[windowDecans.length - 1].dateRange;
const windowLabel = firstRange && lastRange
? `${formatMonthDayLabel(firstRange.start)}${formatMonthDayLabel(lastRange.end)}`
: "--";
dynamicRelations.unshift(
createRelation(
"courtDateWindow",
`${rankKey}-${suitKey}`,
`Court date window: ${windowLabel}`,
{
dateStart: firstRange?.startToken || null,
dateEnd: lastRange?.endToken || null,
dateRange: windowLabel,
decanIds: windowDecanIds
}
)
);
}
cards.push({
arcana: "Minor",
name: cardName,
number: null,
suit,
rank,
summary: `${rank} as ${courtInfo.role} within ${suitInfo.domain}.`,
meanings: {
upright: `${courtInfo.upright} In ${suit}, this guides ${suitInfo.domain}.`,
reversed: `${courtInfo.reversed} In ${suit}, this complicates ${suitInfo.domain}.`
},
keywords: [...courtInfo.keywords, ...suitInfo.keywords],
relations: [
createRelation("element", suitInfo.element, `Element: ${suitInfo.element}`, {
name: suitInfo.element
}),
createRelation(
"elementalFace",
`${rankKey}-${suitKey}`,
`${courtInfo.elementalFace} ${suitInfo.element}`,
{
rank,
suit,
elementalFace: courtInfo.elementalFace,
element: suitInfo.element
}
),
createRelation("courtRole", rankKey, `Court role: ${courtInfo.role}`, {
rank,
role: courtInfo.role
}),
...dynamicRelations
]
});
});
});
return cards;
}
function buildTarotDatabase(referenceData, magickDataset = null) {
const cards = [
...buildMajorCards(referenceData, magickDataset),
...buildNumberMinorCards(referenceData),
...buildCourtMinorCards(referenceData)
];
return applyMeaningText(cards, referenceData);
}
return {
buildCourtMinorCards,
buildMajorCards,
buildNumberMinorCards,
buildTarotDatabase
};
}
window.TarotDatabaseAssembly = {
createTarotDatabaseAssembly
};
})();