refraction almost completed
This commit is contained in:
318
app/tarot-database-assembly.js
Normal file
318
app/tarot-database-assembly.js
Normal file
@@ -0,0 +1,318 @@
|
||||
/* 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
|
||||
};
|
||||
})();
|
||||
Reference in New Issue
Block a user