Files
TaroTime/app/tarot-database-assembly.js

493 lines
19 KiB
JavaScript
Raw Permalink Normal View History

2026-03-07 13:38:13 -08:00
/* 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");
}
2026-04-01 16:08:52 -07:00
function buildTokenDateRange(startToken, endToken) {
const parseToken = (value) => {
const match = String(value || "").trim().match(/^(\d{2})-(\d{2})$/);
if (!match) {
return null;
}
const month = Number(match[1]);
const day = Number(match[2]);
if (!Number.isFinite(month) || !Number.isFinite(day)) {
return null;
}
return new Date(2025, month - 1, day);
};
const start = parseToken(startToken);
const endBase = parseToken(endToken);
if (!start || !endBase) {
return null;
}
const wraps = endBase.getTime() < start.getTime();
const end = wraps ? new Date(2026, endBase.getMonth(), endBase.getDate()) : endBase;
return {
start,
end,
startToken: startToken || null,
endToken: endToken || null,
label: `${formatMonthDayLabel(start)}${formatMonthDayLabel(end)}`
};
}
2026-03-07 13:38:13 -08:00
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,
2026-03-08 03:52:25 -07:00
kabbalahPathNumber: Number.isFinite(Number(card?.number)) ? Number(card.number) + 11 : null,
2026-03-07 13:38:13 -08:00
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]));
2026-03-08 03:52:25 -07:00
const planets = referenceData?.planets || {};
2026-03-07 13:38:13 -08:00
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);
2026-04-02 22:06:19 -07:00
const windowSignIds = tarotDb.courtSignWindows?.[cardName] || [];
const windowSigns = windowSignIds
.map((signId) => signById[signId] || null)
.filter(Boolean);
2026-04-01 16:08:52 -07:00
const explicitWindowRange = buildTokenDateRange(
tarotDb.courtDateRanges?.[cardName]?.start,
tarotDb.courtDateRanges?.[cardName]?.end
);
2026-03-07 13:38:13 -08:00
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
}
)
);
2026-04-02 22:06:19 -07:00
if (meta?.decan?.tarotMinorArcana) {
dynamicRelations.push(
createRelation(
"tarotCard",
`${meta.signId}-${meta.index}-${rankKey}-${suitKey}-decan-card`,
`Decan card: ${meta.decan.tarotMinorArcana} (${meta.signName} decan ${meta.index})`,
{
cardName: meta.decan.tarotMinorArcana,
decanId: meta.decan.id || `${meta.signId}-${meta.index}`,
signId: meta.signId,
signName: meta.signName,
decanIndex: meta.index,
dateRange: meta.dateRange?.label || null
}
)
);
}
2026-03-08 03:52:25 -07:00
const ruler = planets?.[meta?.decan?.rulerPlanetId] || null;
if (ruler) {
dynamicRelations.push(
createRelation(
"decanRuler",
`${meta.signId}-${meta.index}-${ruler.id || meta.decan?.rulerPlanetId || rankKey}-${rankKey}-${suitKey}`,
`Decan ruler: ${ruler.symbol || ""} ${ruler.name || meta.decan?.rulerPlanetId || ""}`.trim(),
{
signId: meta.signId,
decanIndex: meta.index,
planetId: ruler.id || meta.decan?.rulerPlanetId || null,
symbol: ruler.symbol || "",
name: ruler.name || meta.decan?.rulerPlanetId || ""
}
)
);
}
2026-03-07 13:38:13 -08:00
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
}
)
);
});
}
});
2026-04-02 22:06:19 -07:00
windowSigns.forEach((sign) => {
const signDateRange = buildTokenDateRange(sign?.start, sign?.end);
const signName = sign?.name || sign?.id || "";
const signSymbol = sign?.symbol || "";
const relatedDecans = Array.isArray(decansBySign?.[sign.id]) ? decansBySign[sign.id] : [];
dynamicRelations.push(
createRelation(
"signWindow",
`${sign.id}-${rankKey}-${suitKey}`,
`Sign window: ${signSymbol} ${signName} (0°30°)${signDateRange ? ` · ${signDateRange.label}` : ""}`.trim(),
{
signId: sign.id,
signName,
signSymbol,
startDegree: 0,
endDegree: 30,
dateStart: signDateRange?.startToken || null,
dateEnd: signDateRange?.endToken || null,
dateRange: signDateRange?.label || null
}
)
);
relatedDecans.forEach((decan) => {
if (!decan?.tarotMinorArcana) {
return;
}
dynamicRelations.push(
createRelation(
"tarotCard",
`${sign.id}-${decan.id || decan.tarotMinorArcana}-${rankKey}-${suitKey}-sign-card`,
`Sign card: ${decan.tarotMinorArcana} (${signName})`,
{
cardName: decan.tarotMinorArcana,
decanId: decan.id || null,
signId: sign.id,
signName,
signSymbol,
dateRange: signDateRange?.label || null
}
)
);
});
if (signDateRange?.start && signDateRange?.end) {
const monthNumbers = listMonthNumbersBetween(signDateRange.start, signDateRange.end);
monthNumbers.forEach((monthNo) => {
const monthId = monthIdByNumber[monthNo];
const monthName = monthNameByNumber[monthNo] || `Month ${monthNo}`;
const monthKey = `${monthId}:${sign.id}:sign-window`;
if (!monthId || monthKeys.has(monthKey)) {
return;
}
monthKeys.add(monthKey);
dynamicRelations.push(
createRelation(
"calendarMonth",
`${monthId}-${sign.id}-${rankKey}-${suitKey}-sign-window`,
`Calendar month: ${monthName} (${signName} sign window)`,
{
monthId,
name: monthName,
monthOrder: monthNo,
signId: sign.id,
signName,
dateRange: signDateRange?.label || null
}
)
);
});
}
});
if (windowDecans.length || windowSigns.length) {
const firstRange = windowDecans.length ? windowDecans[0].dateRange : null;
const lastRange = windowDecans.length ? windowDecans[windowDecans.length - 1].dateRange : null;
const firstSignRange = windowSigns.length ? buildTokenDateRange(windowSigns[0]?.start, windowSigns[0]?.end) : null;
const lastSignRange = windowSigns.length ? buildTokenDateRange(windowSigns[windowSigns.length - 1]?.start, windowSigns[windowSigns.length - 1]?.end) : null;
2026-04-01 16:08:52 -07:00
const fallbackWindowRange = firstRange && lastRange
? {
start: firstRange.start,
end: lastRange.end,
startToken: firstRange.startToken,
endToken: lastRange.endToken,
label: `${formatMonthDayLabel(firstRange.start)}${formatMonthDayLabel(lastRange.end)}`
}
2026-04-02 22:06:19 -07:00
: (firstSignRange && lastSignRange
? {
start: firstSignRange.start,
end: lastSignRange.end,
startToken: firstSignRange.startToken,
endToken: lastSignRange.endToken,
label: `${formatMonthDayLabel(firstSignRange.start)}${formatMonthDayLabel(lastSignRange.end)}`
}
: null);
2026-04-01 16:08:52 -07:00
const windowRange = explicitWindowRange || fallbackWindowRange;
const windowLabel = windowRange?.label || "--";
2026-03-07 13:38:13 -08:00
dynamicRelations.unshift(
createRelation(
"courtDateWindow",
`${rankKey}-${suitKey}`,
`Court date window: ${windowLabel}`,
{
2026-04-01 16:08:52 -07:00
dateStart: windowRange?.startToken || null,
dateEnd: windowRange?.endToken || null,
2026-03-07 13:38:13 -08:00
dateRange: windowLabel,
2026-04-02 22:06:19 -07:00
decanIds: windowDecanIds,
signIds: windowSignIds
2026-03-07 13:38:13 -08:00
}
)
);
}
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
};
})();