From 712d34eac16ed9379834f0964fa1f586d14ca24f Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 31 Jan 2026 01:03:31 -0300 Subject: [PATCH 1/4] feat: Sacred Remedy Engine + GET /api/instant-light MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - lib/sacredRemedy: diagnosisEngine, sacredSelector, instantLightComposer - dictionaries/sacred: yoga_sutras, puranas, upanishads com kleshaTargets e qualities - dictionaries/remedyMatrix.json: 30 estados (klesha, samkhya, prática, pergunta) - GET /api/instant-light: diagnosisPersonal/Universal, resposta estruturada - lib/diagnosis + lib/instantLight: matriz de remédios integrada ao mock /api/darshan - SymbolicMap, engines (Jyotish, HD, numerologia), readings, APIs map/reading - Histórico: historyStorage, APIs history, HistoryModal - Docs: SACRED_REMEDY_ENGINE, INSTANT_LIGHT_ENGINE, SYMBOLIC_MAP_ENGINE, etc. - /api/darshan mantido intacto (motor paralelo) Co-authored-by: Cursor --- __tests__/lib/finance/creditsManager.test.ts | 2 +- __tests__/lib/stripe.test.ts | 4 +- __tests__/lib/supabase.test.ts | 6 +- app/api/darshan/route.ts | 35 +- app/api/history/count/route.ts | 21 + app/api/history/readings/route.ts | 23 + app/api/history/revelations/route.ts | 23 + app/api/instant-light/route.ts | 49 ++ app/api/map/personal/route.ts | 9 +- app/api/map/route.ts | 33 ++ app/api/reading/career/route.ts | 30 ++ app/api/reading/general/route.ts | 26 + app/api/reading/love/route.ts | 26 + app/api/reading/route.ts | 61 +++ app/api/reading/year/route.ts | 30 ++ app/components/CrystalOrb.tsx | 8 +- app/components/DarshanMessage.tsx | 6 +- app/components/HistoryIcon.tsx | 39 ++ app/components/HistoryModal.tsx | 277 ++++++++++ app/components/PersonalMapIcon.tsx | 6 +- app/components/ProfileIcon.tsx | 8 +- app/components/ProfilePanel.tsx | 30 +- app/components/TimeHeader.tsx | 12 +- app/components/Tooltip.tsx | 12 +- app/page.tsx | 128 +++-- docs/DICIONARIO_OFFLINE.md | 2 +- docs/ENGINE_2.0.md | 118 +++++ docs/FLUXO_ORACULO_OFFLINE.md | 19 +- docs/HISTORICO_RESPOSTAS_LEITURAS.md | 48 ++ docs/INSTANT_LIGHT_ENGINE.md | 101 ++++ docs/OFFLINE_ENGINE_PIPELINE.md | 127 +++++ docs/README.md | 7 + docs/ROADMAP_PARTE_2.md | 72 +++ docs/SACRED_REMEDY_ENGINE.md | 99 ++++ docs/SWISS_EPHEMERIS_INTEGRATION.md | 69 +++ docs/SYMBOLIC_MAP_ENGINE.md | 132 +++++ lib/core/ephemerisProvider.ts | 11 + lib/core/ephemerisResolver.ts | 26 + lib/core/providers/mhahProvider.ts | 59 +++ lib/core/providers/swissProvider.ts | 224 ++++++++ lib/core/types.ts | 29 ++ lib/diagnosis/diagnosisEngine.ts | 106 ++++ lib/diagnosis/index.ts | 23 + lib/diagnosis/types.ts | 71 +++ lib/dictionaries/action.ts | 21 + lib/dictionaries/humanDesign.ts | 24 + lib/dictionaries/index.ts | 37 ++ lib/dictionaries/jyotish.ts | 213 ++++++++ lib/dictionaries/numerology.ts | 139 +++++ lib/dictionaries/phrasesForSymbolic.ts | 242 +++++++++ lib/dictionaries/remedyMatrix.json | 302 +++++++++++ lib/dictionaries/sacred/puranas.json | 8 + lib/dictionaries/sacred/upanishads.json | 10 + lib/dictionaries/sacred/yoga_sutras.json | 15 + lib/engines/buildSymbolicMap.ts | 34 ++ lib/engines/humanDesignEngine.ts | 22 + lib/engines/jyotishEngine.ts | 73 +++ lib/engines/numerologyEngine.ts | 27 + lib/historyStorage.ts | 129 +++++ lib/insights/collectInsights.ts | 17 + lib/insights/collectInsightsForSymbolic.ts | 37 ++ lib/insights/humanDesignInsights.ts | 28 + lib/insights/jyotishInsights.ts | 71 +++ lib/insights/jyotishInsightsForSymbolic.ts | 479 ++++++++++++++++++ lib/insights/numerologyInsights.ts | 42 ++ lib/insights/types.ts | 15 + lib/instantLight/index.ts | 8 + lib/instantLight/instantLightComposer.ts | 76 +++ lib/knowledge/numerology.ts | 22 + lib/narrative/composer.ts | 44 ++ lib/narrative/dictionaryLoader.ts | 30 ++ lib/narrative/phraseTemplates.ts | 36 ++ lib/oracleOffline.ts | 101 +--- lib/readingOffline.ts | 140 ++--- lib/readings/actionReading.ts | 14 + lib/readings/careerReading.ts | 19 + lib/readings/generalReading.ts | 19 + lib/readings/loveReading.ts | 19 + lib/readings/symbolicReadings.ts | 117 +++++ lib/readings/yearReading.ts | 19 + lib/sacred/index.ts | 7 + lib/sacred/sacredPicker.ts | 84 +++ lib/sacred/types.ts | 26 + lib/sacredRemedy/diagnosisEngine.ts | 118 +++++ lib/sacredRemedy/index.ts | 26 + lib/sacredRemedy/instantLightComposer.ts | 85 ++++ lib/sacredRemedy/sacredSelector.ts | 77 +++ lib/sacredRemedy/types.ts | 67 +++ lib/symbolic/SymbolicMap.ts | 8 + lib/symbolic/builder.ts | 46 ++ lib/symbolic/types.ts | 28 + package-lock.json | 64 ++- package.json | 7 +- .../20250129100000_history_tables.sql | 40 ++ 94 files changed, 5300 insertions(+), 279 deletions(-) create mode 100644 app/api/history/count/route.ts create mode 100644 app/api/history/readings/route.ts create mode 100644 app/api/history/revelations/route.ts create mode 100644 app/api/instant-light/route.ts create mode 100644 app/api/map/route.ts create mode 100644 app/api/reading/career/route.ts create mode 100644 app/api/reading/general/route.ts create mode 100644 app/api/reading/love/route.ts create mode 100644 app/api/reading/route.ts create mode 100644 app/api/reading/year/route.ts create mode 100644 app/components/HistoryIcon.tsx create mode 100644 app/components/HistoryModal.tsx create mode 100644 docs/ENGINE_2.0.md create mode 100644 docs/HISTORICO_RESPOSTAS_LEITURAS.md create mode 100644 docs/INSTANT_LIGHT_ENGINE.md create mode 100644 docs/OFFLINE_ENGINE_PIPELINE.md create mode 100644 docs/ROADMAP_PARTE_2.md create mode 100644 docs/SACRED_REMEDY_ENGINE.md create mode 100644 docs/SWISS_EPHEMERIS_INTEGRATION.md create mode 100644 docs/SYMBOLIC_MAP_ENGINE.md create mode 100644 lib/core/ephemerisProvider.ts create mode 100644 lib/core/ephemerisResolver.ts create mode 100644 lib/core/providers/mhahProvider.ts create mode 100644 lib/core/providers/swissProvider.ts create mode 100644 lib/core/types.ts create mode 100644 lib/diagnosis/diagnosisEngine.ts create mode 100644 lib/diagnosis/index.ts create mode 100644 lib/diagnosis/types.ts create mode 100644 lib/dictionaries/action.ts create mode 100644 lib/dictionaries/humanDesign.ts create mode 100644 lib/dictionaries/index.ts create mode 100644 lib/dictionaries/jyotish.ts create mode 100644 lib/dictionaries/numerology.ts create mode 100644 lib/dictionaries/phrasesForSymbolic.ts create mode 100644 lib/dictionaries/remedyMatrix.json create mode 100644 lib/dictionaries/sacred/puranas.json create mode 100644 lib/dictionaries/sacred/upanishads.json create mode 100644 lib/dictionaries/sacred/yoga_sutras.json create mode 100644 lib/engines/buildSymbolicMap.ts create mode 100644 lib/engines/humanDesignEngine.ts create mode 100644 lib/engines/jyotishEngine.ts create mode 100644 lib/engines/numerologyEngine.ts create mode 100644 lib/historyStorage.ts create mode 100644 lib/insights/collectInsights.ts create mode 100644 lib/insights/collectInsightsForSymbolic.ts create mode 100644 lib/insights/humanDesignInsights.ts create mode 100644 lib/insights/jyotishInsights.ts create mode 100644 lib/insights/jyotishInsightsForSymbolic.ts create mode 100644 lib/insights/numerologyInsights.ts create mode 100644 lib/insights/types.ts create mode 100644 lib/instantLight/index.ts create mode 100644 lib/instantLight/instantLightComposer.ts create mode 100644 lib/narrative/composer.ts create mode 100644 lib/narrative/dictionaryLoader.ts create mode 100644 lib/narrative/phraseTemplates.ts create mode 100644 lib/readings/actionReading.ts create mode 100644 lib/readings/careerReading.ts create mode 100644 lib/readings/generalReading.ts create mode 100644 lib/readings/loveReading.ts create mode 100644 lib/readings/symbolicReadings.ts create mode 100644 lib/readings/yearReading.ts create mode 100644 lib/sacred/index.ts create mode 100644 lib/sacred/sacredPicker.ts create mode 100644 lib/sacred/types.ts create mode 100644 lib/sacredRemedy/diagnosisEngine.ts create mode 100644 lib/sacredRemedy/index.ts create mode 100644 lib/sacredRemedy/instantLightComposer.ts create mode 100644 lib/sacredRemedy/sacredSelector.ts create mode 100644 lib/sacredRemedy/types.ts create mode 100644 lib/symbolic/SymbolicMap.ts create mode 100644 lib/symbolic/builder.ts create mode 100644 lib/symbolic/types.ts create mode 100644 supabase/migrations/20250129100000_history_tables.sql diff --git a/__tests__/lib/finance/creditsManager.test.ts b/__tests__/lib/finance/creditsManager.test.ts index 97301ee..cb68027 100644 --- a/__tests__/lib/finance/creditsManager.test.ts +++ b/__tests__/lib/finance/creditsManager.test.ts @@ -288,7 +288,7 @@ describe("lib/finance/creditsManager (com Supabase)", () => { it("retorna null quando insert payment falha", async () => { mockGetSupabase.mockReturnValue( createMockSupabaseClient({ - selectUser: { data: { id: "u1" } }, + selectUser: { data: { id: "u1", credits_balance: 0 } }, insertPayment: { data: null }, }) ); diff --git a/__tests__/lib/stripe.test.ts b/__tests__/lib/stripe.test.ts index 664a189..fc8eaf9 100644 --- a/__tests__/lib/stripe.test.ts +++ b/__tests__/lib/stripe.test.ts @@ -1,4 +1,4 @@ -const originalEnv = process.env; +const stripeTestEnv = process.env; jest.mock("stripe", () => { return jest.fn().mockImplementation(() => ({ _mock: "stripe" })); @@ -6,7 +6,7 @@ jest.mock("stripe", () => { describe("lib/stripe", () => { afterEach(() => { - process.env = { ...originalEnv }; + process.env = { ...stripeTestEnv }; jest.resetModules(); }); diff --git a/__tests__/lib/supabase.test.ts b/__tests__/lib/supabase.test.ts index 359ee74..e394482 100644 --- a/__tests__/lib/supabase.test.ts +++ b/__tests__/lib/supabase.test.ts @@ -2,16 +2,16 @@ jest.mock("@supabase/supabase-js", () => ({ createClient: jest.fn(() => ({ mock: true })), })); -const originalEnv = process.env; +const supabaseTestEnv = process.env; describe("lib/supabase", () => { beforeEach(() => { jest.resetModules(); - process.env = { ...originalEnv }; + process.env = { ...supabaseTestEnv }; }); afterEach(() => { - process.env = originalEnv; + process.env = supabaseTestEnv; }); describe("isSupabaseConfigured", () => { diff --git a/app/api/darshan/route.ts b/app/api/darshan/route.ts index 8199800..c0e07b9 100644 --- a/app/api/darshan/route.ts +++ b/app/api/darshan/route.ts @@ -4,7 +4,7 @@ import { getConnector } from "@/lib/ai"; import { loadMasterPrompt } from "@/lib/darshanPrompt"; import { getConfig } from "@/lib/configStore"; import { PHASE_NAMES } from "@/lib/darshan"; -import { getOfflineRevelation } from "@/lib/oracleOffline"; +import { composeInstantLight } from "@/lib/instantLight/instantLightComposer"; import { getSessionFromCookie } from "@/lib/auth"; import { getCreditsFromCookie, @@ -22,6 +22,7 @@ import { import { logger } from "@/lib/logger"; import { checkAndRecordRateLimit, checkDailyLimit, recordDailyRequest } from "@/lib/usageLimits"; import { isSupabaseConfigured } from "@/lib/supabase"; +import { saveRevelation } from "@/lib/historyStorage"; export const dynamic = "force-dynamic"; @@ -107,6 +108,9 @@ export async function POST(req: Request) { birthTime: typeof body.userProfile.birthTime === "string" ? body.userProfile.birthTime : undefined, } : {}; + const recentSacredIds = Array.isArray(body.recentSacredIds) + ? body.recentSacredIds.filter((id: unknown) => typeof id === "string") + : []; const config = getConfig(); const mockMessages = @@ -116,17 +120,17 @@ export async function POST(req: Request) { : [...MOCK_MESSAGES, ...config.mockMessagesOverride] : MOCK_MESSAGES; - // IA desativada (mock): 100% offline — NÃO chama getConnector() nem APIs externas. + // IA desativada (mock): Instant Light híbrido — Sacred Library + Personal Insight (se houver mapa). + // Sem perfil → Universal Light (texto sagrado + prática + pergunta). Com perfil → Personal Light (+ insight e prática do mapa). if (useMock) { - const lastRevelations = history.slice(-5).map((t) => t.darshanMessage); - const phrases = lastRevelations.flatMap((msg) => - msg.split(/\n\n/).map((s) => s.trim()).filter(Boolean) - ); - const recentlyUsedPhrases = phrases.flatMap((p) => - /o que em você já sabe/i.test(p) ? [p, "O que em você já sabe?"] : [p] - ); - const message = getOfflineRevelation(userProfile, userMessage, recentlyUsedPhrases); - return NextResponse.json({ message: message || getMockMessage(mockMessages), phase: 1 } satisfies { message: string; phase: number }); + const { message, sacredId } = composeInstantLight(userProfile, { + recentSacredIds, + }); + return NextResponse.json({ + message: message || getMockMessage(mockMessages), + phase: 1, + sacredId, + } satisfies { message: string; phase: number; sacredId?: string }); } const cookieStore = await cookies(); @@ -273,8 +277,15 @@ export async function POST(req: Request) { if (!isSupabaseConfigured()) { recordDailyRequest(session.email); } + const finalMessage = message || "Respire. O que em você já sabe?"; + if (revelation) { + await saveRevelation(session.email, { + questionText: userMessage || null, + responseText: finalMessage, + }); + } const res = NextResponse.json({ - message: message || "Respire. O que em você já sabe?", + message: finalMessage, phase: revelation ? 1 : (parsed.phase ?? phase), creditsUsed: creditsPerRevelation, balance: debitResult.newBalance, diff --git a/app/api/history/count/route.ts b/app/api/history/count/route.ts new file mode 100644 index 0000000..5c30852 --- /dev/null +++ b/app/api/history/count/route.ts @@ -0,0 +1,21 @@ +/** + * GET /api/history/count — contagem de revelações e leituras do usuário logado. + * Usado para exibir o ícone de histórico quando há dados. + */ + +import { NextResponse } from "next/server"; +import { cookies } from "next/headers"; +import { getSessionFromCookie } from "@/lib/auth"; +import { getHistoryCounts } from "@/lib/historyStorage"; + +export const dynamic = "force-dynamic"; + +export async function GET() { + const cookieStore = await cookies(); + const session = getSessionFromCookie(cookieStore.toString()); + if (!session) { + return NextResponse.json({ revelations: 0, readings: 0 }); + } + const counts = await getHistoryCounts(session.email); + return NextResponse.json(counts); +} diff --git a/app/api/history/readings/route.ts b/app/api/history/readings/route.ts new file mode 100644 index 0000000..99ff322 --- /dev/null +++ b/app/api/history/readings/route.ts @@ -0,0 +1,23 @@ +/** + * GET /api/history/readings — lista leituras (mapa pessoal) do usuário logado. + */ + +import { NextResponse } from "next/server"; +import { cookies } from "next/headers"; +import { getSessionFromCookie } from "@/lib/auth"; +import { listReadings } from "@/lib/historyStorage"; + +export const dynamic = "force-dynamic"; + +export async function GET(req: Request) { + const cookieStore = await cookies(); + const session = getSessionFromCookie(cookieStore.toString()); + if (!session) { + return NextResponse.json({ error: "Faça login para ver seu histórico." }, { status: 401 }); + } + const { searchParams } = new URL(req.url); + const limit = Math.min(100, Math.max(1, parseInt(searchParams.get("limit") ?? "50", 10) || 50)); + const offset = Math.max(0, parseInt(searchParams.get("offset") ?? "0", 10) || 0); + const list = await listReadings(session.email, { limit, offset }); + return NextResponse.json({ items: list }); +} diff --git a/app/api/history/revelations/route.ts b/app/api/history/revelations/route.ts new file mode 100644 index 0000000..6ae914e --- /dev/null +++ b/app/api/history/revelations/route.ts @@ -0,0 +1,23 @@ +/** + * GET /api/history/revelations — lista revelações (respostas do orb) do usuário logado. + */ + +import { NextResponse } from "next/server"; +import { cookies } from "next/headers"; +import { getSessionFromCookie } from "@/lib/auth"; +import { listRevelations } from "@/lib/historyStorage"; + +export const dynamic = "force-dynamic"; + +export async function GET(req: Request) { + const cookieStore = await cookies(); + const session = getSessionFromCookie(cookieStore.toString()); + if (!session) { + return NextResponse.json({ error: "Faça login para ver seu histórico." }, { status: 401 }); + } + const { searchParams } = new URL(req.url); + const limit = Math.min(100, Math.max(1, parseInt(searchParams.get("limit") ?? "50", 10) || 50)); + const offset = Math.max(0, parseInt(searchParams.get("offset") ?? "0", 10) || 0); + const list = await listRevelations(session.email, { limit, offset }); + return NextResponse.json({ items: list }); +} diff --git a/app/api/instant-light/route.ts b/app/api/instant-light/route.ts new file mode 100644 index 0000000..27357be --- /dev/null +++ b/app/api/instant-light/route.ts @@ -0,0 +1,49 @@ +/** + * GET /api/instant-light — Sacred Remedy Engine (motor medicinal offline). + * Não consome créditos; não depende de IA. Motor paralelo ao /api/darshan. + * + * Query: fullName?, birthDate?, birthTime?, birthPlace?, recentSacredIds?, recentStateKeys? + * - Se userProfile existe (nome ou data) → diagnosisPersonal(SymbolicMap) + insight. + * - Se não existe → diagnosisUniversal(). + * + * Resposta: sacredText, insight? (se personal), practice, question, sacredId?, stateKey? + */ + +import { NextResponse } from "next/server"; +import { composeInstantLight } from "@/lib/sacredRemedy"; + +export const dynamic = "force-dynamic"; + +export async function GET(req: Request) { + const { searchParams } = new URL(req.url); + const fullName = searchParams.get("fullName") ?? undefined; + const birthDate = searchParams.get("birthDate") ?? undefined; + const birthTime = searchParams.get("birthTime") ?? undefined; + const birthPlace = searchParams.get("birthPlace") ?? undefined; + const recentSacredIdsParam = searchParams.get("recentSacredIds"); + const recentStateKeysParam = searchParams.get("recentStateKeys"); + + const recentSacredIds = recentSacredIdsParam + ? recentSacredIdsParam.split(",").map((s) => s.trim()).filter(Boolean) + : []; + const recentStateKeys = recentStateKeysParam + ? recentStateKeysParam.split(",").map((s) => s.trim()).filter(Boolean) + : []; + + const userProfile = + (fullName?.trim() || birthDate?.trim()) + ? { + fullName: fullName?.trim() || undefined, + birthDate: birthDate?.trim() || undefined, + birthTime: birthTime?.trim() || undefined, + birthPlace: birthPlace?.trim() || undefined, + } + : null; + + const result = composeInstantLight(userProfile, { + recentSacredIds, + recentStateKeys, + }); + + return NextResponse.json(result); +} diff --git a/app/api/map/personal/route.ts b/app/api/map/personal/route.ts index 6193adb..51fc9de 100644 --- a/app/api/map/personal/route.ts +++ b/app/api/map/personal/route.ts @@ -16,7 +16,8 @@ import { getCreditsBalance, debitCredits, logAiUsage, estimateCost, refreshUsdTo import type { AiUsageProvider } from "@/lib/finance"; import { logger } from "@/lib/logger"; import { getConfig } from "@/lib/configStore"; -import { getOfflineReading } from "@/lib/readingOffline"; +import { getOfflineReading, getOfflineReadingFullText } from "@/lib/readingOffline"; +import { saveReading } from "@/lib/historyStorage"; import { computeVedicChartSimplified } from "@/lib/knowledge/vedic"; import { getRulingNumberFromName, getNumberTraits } from "@/lib/knowledge/numerology"; import { RASHI_NAMES } from "@/lib/knowledge/archetypes"; @@ -156,11 +157,13 @@ export async function POST(req: Request) { const useOffline = body.offline === true; if (useOffline) { - const message = getOfflineReading(profile); + const sections = getOfflineReading(profile); + const message = getOfflineReadingFullText(profile); const balanceFromCookie = getCreditsFromCookie(cookieHeader); const balance = await getCreditsBalance(session.email, balanceFromCookie); return NextResponse.json({ message, + sections, balance, creditsUsed: 0, offline: true, @@ -281,6 +284,8 @@ export async function POST(req: Request) { currentBalanceFromCookie: balance, }); + await saveReading(session.email, message); + const res = NextResponse.json({ message, balance: debitResult.newBalance, diff --git a/app/api/map/route.ts b/app/api/map/route.ts new file mode 100644 index 0000000..0471efe --- /dev/null +++ b/app/api/map/route.ts @@ -0,0 +1,33 @@ +/** + * POST /api/map — retorna SymbolicMap completo (offline, sem IA, sem créditos). + * Motor simbólico real: Jyotish + Numerologia + Arquétipos. + */ + +import { NextResponse } from "next/server"; +import { buildSymbolicMap } from "@/lib/symbolic/builder"; + +export const dynamic = "force-dynamic"; + +type ProfileInput = { + fullName?: string; + birthDate?: string; + birthTime?: string; + birthPlace?: string; +}; + +export async function POST(req: Request) { + let body: { profile?: ProfileInput } = {}; + try { + body = await req.json(); + } catch { + return NextResponse.json({ error: "Corpo inválido." }, { status: 400 }); + } + const profile = body.profile ?? {}; + const map = buildSymbolicMap({ + fullName: profile.fullName, + birthDate: profile.birthDate, + birthTime: profile.birthTime, + birthPlace: profile.birthPlace, + }); + return NextResponse.json({ map }); +} diff --git a/app/api/reading/career/route.ts b/app/api/reading/career/route.ts new file mode 100644 index 0000000..48db118 --- /dev/null +++ b/app/api/reading/career/route.ts @@ -0,0 +1,30 @@ +/** + * POST /api/reading/career — leitura de carreira offline (mapa + narrativa + action). + */ + +import { NextResponse } from "next/server"; +import { getCareerReading } from "@/lib/readings/careerReading"; +import type { CoreProfile } from "@/lib/core/types"; + +export const dynamic = "force-dynamic"; + +export async function POST(req: Request) { + let body: { profile?: CoreProfile } = {}; + try { + body = await req.json(); + } catch { + return NextResponse.json({ error: "Corpo inválido." }, { status: 400 }); + } + const profile: CoreProfile = body.profile ?? {}; + const { map, reading, action } = await getCareerReading(profile); + return NextResponse.json({ + map: { + core: { providerUsed: map.core.providerUsed, moonRashi: map.core.moonRashi, nakshatra: map.core.nakshatra }, + jyotish: map.jyotish, + numerology: map.numerology, + humanDesign: map.humanDesign, + }, + reading, + action, + }); +} diff --git a/app/api/reading/general/route.ts b/app/api/reading/general/route.ts new file mode 100644 index 0000000..da04355 --- /dev/null +++ b/app/api/reading/general/route.ts @@ -0,0 +1,26 @@ +import { NextResponse } from "next/server"; +import { getGeneralReading } from "@/lib/readings/generalReading"; +import type { CoreProfile } from "@/lib/core/types"; + +export const dynamic = "force-dynamic"; + +export async function POST(req: Request) { + let body: { profile?: CoreProfile } = {}; + try { + body = await req.json(); + } catch { + return NextResponse.json({ error: "Corpo inválido." }, { status: 400 }); + } + const profile: CoreProfile = body.profile ?? {}; + const { map, reading, action } = await getGeneralReading(profile); + return NextResponse.json({ + map: { + core: { providerUsed: map.core.providerUsed, moonRashi: map.core.moonRashi, nakshatra: map.core.nakshatra }, + jyotish: map.jyotish, + numerology: map.numerology, + humanDesign: map.humanDesign, + }, + reading, + action, + }); +} diff --git a/app/api/reading/love/route.ts b/app/api/reading/love/route.ts new file mode 100644 index 0000000..618acb7 --- /dev/null +++ b/app/api/reading/love/route.ts @@ -0,0 +1,26 @@ +import { NextResponse } from "next/server"; +import { getLoveReading } from "@/lib/readings/loveReading"; +import type { CoreProfile } from "@/lib/core/types"; + +export const dynamic = "force-dynamic"; + +export async function POST(req: Request) { + let body: { profile?: CoreProfile } = {}; + try { + body = await req.json(); + } catch { + return NextResponse.json({ error: "Corpo inválido." }, { status: 400 }); + } + const profile: CoreProfile = body.profile ?? {}; + const { map, reading, action } = await getLoveReading(profile); + return NextResponse.json({ + map: { + core: { providerUsed: map.core.providerUsed, moonRashi: map.core.moonRashi, nakshatra: map.core.nakshatra }, + jyotish: map.jyotish, + numerology: map.numerology, + humanDesign: map.humanDesign, + }, + reading, + action, + }); +} diff --git a/app/api/reading/route.ts b/app/api/reading/route.ts new file mode 100644 index 0000000..69aa119 --- /dev/null +++ b/app/api/reading/route.ts @@ -0,0 +1,61 @@ +/** + * POST /api/reading?theme= — retorna leitura temática offline (sem IA, sem créditos). + * theme: general | love | relationship | career | work | year | yearly | action + */ + +import { NextResponse } from "next/server"; +import { buildSymbolicMap } from "@/lib/symbolic/builder"; +import { getReadingByTheme } from "@/lib/readings/symbolicReadings"; + +export const dynamic = "force-dynamic"; + +type ProfileInput = { + fullName?: string; + birthDate?: string; + birthTime?: string; + birthPlace?: string; +}; + +const VALID_THEMES = [ + "general", + "love", + "relationship", + "career", + "work", + "year", + "yearly", + "action", +]; + +export async function POST(req: Request) { + let body: { profile?: ProfileInput } = {}; + try { + body = await req.json(); + } catch { + return NextResponse.json({ error: "Corpo inválido." }, { status: 400 }); + } + const profile = body.profile ?? {}; + const { searchParams } = new URL(req.url); + const themeParam = searchParams.get("theme") ?? "general"; + const theme = VALID_THEMES.includes(themeParam.toLowerCase()) + ? themeParam.toLowerCase() + : "general"; + + const map = buildSymbolicMap({ + fullName: profile.fullName, + birthDate: profile.birthDate, + birthTime: profile.birthTime, + birthPlace: profile.birthPlace, + }); + const reading = getReadingByTheme(map, theme); + + return NextResponse.json({ + map: { + jyotish: map.jyotish, + numerology: map.numerology, + archetypes: map.archetypes, + }, + reading, + theme, + }); +} diff --git a/app/api/reading/year/route.ts b/app/api/reading/year/route.ts new file mode 100644 index 0000000..d1118c9 --- /dev/null +++ b/app/api/reading/year/route.ts @@ -0,0 +1,30 @@ +/** + * POST /api/reading/year — leitura de ano offline (mapa + narrativa + action). + */ + +import { NextResponse } from "next/server"; +import { getYearReading } from "@/lib/readings/yearReading"; +import type { CoreProfile } from "@/lib/core/types"; + +export const dynamic = "force-dynamic"; + +export async function POST(req: Request) { + let body: { profile?: CoreProfile } = {}; + try { + body = await req.json(); + } catch { + return NextResponse.json({ error: "Corpo inválido." }, { status: 400 }); + } + const profile: CoreProfile = body.profile ?? {}; + const { map, reading, action } = await getYearReading(profile); + return NextResponse.json({ + map: { + core: { providerUsed: map.core.providerUsed, moonRashi: map.core.moonRashi, nakshatra: map.core.nakshatra }, + jyotish: map.jyotish, + numerology: map.numerology, + humanDesign: map.humanDesign, + }, + reading, + action, + }); +} diff --git a/app/components/CrystalOrb.tsx b/app/components/CrystalOrb.tsx index 038717a..1803e77 100644 --- a/app/components/CrystalOrb.tsx +++ b/app/components/CrystalOrb.tsx @@ -12,17 +12,17 @@ export default function CrystalOrb({ isRevealing = false, onClick, clickable = f const Wrapper = clickable ? "button" : "div"; return ( diff --git a/app/components/DarshanMessage.tsx b/app/components/DarshanMessage.tsx index 258cd7e..38cd85c 100644 --- a/app/components/DarshanMessage.tsx +++ b/app/components/DarshanMessage.tsx @@ -55,7 +55,7 @@ export default function DarshanMessage({ message, onComplete }: Props) { } return ( -
+
!exiting && e.key === "Enter" && handleClick()} - initial={{ opacity: 0, y: 16 }} + initial={{ opacity: 0, y: 12 }} animate={exiting ? { opacity: 0, y: -12 } : { opacity: 1, y: 0 }} exit={{ opacity: 0, y: -12 }} transition={{ duration: FADE_DURATION_S, ease: [0.25, 0.1, 0.25, 1] }} - className="text-center w-full cursor-pointer rounded-2xl px-6 py-8 focus:outline-none" + className="text-center w-full cursor-pointer rounded-2xl px-4 sm:px-6 py-5 sm:py-8 focus:outline-none" aria-label={exiting ? undefined : isLast ? "Toque para voltar" : "Toque para a próxima"} >

diff --git a/app/components/HistoryIcon.tsx b/app/components/HistoryIcon.tsx new file mode 100644 index 0000000..7b7e8ff --- /dev/null +++ b/app/components/HistoryIcon.tsx @@ -0,0 +1,39 @@ +"use client"; + +import { motion } from "framer-motion"; +import Tooltip from "./Tooltip"; + +type Props = { + onClick: () => void; +}; + +/** Ícone de histórico (respostas e leituras) — aparece quando o usuário tem dados armazenados. */ +export default function HistoryIcon({ onClick }: Props) { + return ( + + + + + + + + + ); +} diff --git a/app/components/HistoryModal.tsx b/app/components/HistoryModal.tsx new file mode 100644 index 0000000..e07e120 --- /dev/null +++ b/app/components/HistoryModal.tsx @@ -0,0 +1,277 @@ +"use client"; + +import { useState, useEffect, useCallback } from "react"; +import { motion, AnimatePresence } from "framer-motion"; + +type RevelationItem = { + id: string; + user_id: string; + question_text: string | null; + response_text: string; + created_at: string; +}; + +type ReadingItem = { + id: string; + user_id: string; + content: string; + created_at: string; +}; + +type TabId = "respostas" | "leituras"; + +function formatDate(iso: string): string { + try { + const d = new Date(iso); + return d.toLocaleDateString("pt-BR", { + day: "2-digit", + month: "short", + year: "numeric", + hour: "2-digit", + minute: "2-digit", + }); + } catch { + return iso.slice(0, 16); + } +} + +type Props = { + onClose: () => void; +}; + +export default function HistoryModal({ onClose }: Props) { + const [tab, setTab] = useState("respostas"); + const [revelations, setRevelations] = useState([]); + const [readings, setReadings] = useState([]); + const [loadingRevelations, setLoadingRevelations] = useState(false); + const [loadingReadings, setLoadingReadings] = useState(false); + const [expandedRevelationId, setExpandedRevelationId] = useState(null); + const [expandedReadingId, setExpandedReadingId] = useState(null); + + const fetchRevelations = useCallback(async () => { + setLoadingRevelations(true); + try { + const res = await fetch("/api/history/revelations", { credentials: "include" }); + if (res.ok) { + const data = await res.json(); + setRevelations(Array.isArray(data.items) ? data.items : []); + } else { + setRevelations([]); + } + } catch { + setRevelations([]); + } finally { + setLoadingRevelations(false); + } + }, []); + + const fetchReadings = useCallback(async () => { + setLoadingReadings(true); + try { + const res = await fetch("/api/history/readings", { credentials: "include" }); + if (res.ok) { + const data = await res.json(); + setReadings(Array.isArray(data.items) ? data.items : []); + } else { + setReadings([]); + } + } catch { + setReadings([]); + } finally { + setLoadingReadings(false); + } + }, []); + + useEffect(() => { + if (tab === "respostas") fetchRevelations(); + else fetchReadings(); + }, [tab, fetchRevelations, fetchReadings]); + + const tabClass = (t: TabId) => + `py-2.5 px-3 text-sm font-light border-b-2 transition ${ + tab === t + ? "border-white/30 text-white/90" + : "border-transparent text-white/50 hover:text-white/70" + }`; + + return ( + <> + + +

+

Histórico

+ +
+ +
+ + +
+ +
+ + {tab === "respostas" && ( + + {loadingRevelations && ( +

Carregando...

+ )} + {!loadingRevelations && revelations.length === 0 && ( +

+ Nenhuma resposta da interação com o orb ainda. +

+ )} + {!loadingRevelations && + revelations.map((r) => { + const expanded = expandedRevelationId === r.id; + return ( +
+ + {expanded && ( +
+ {r.question_text && ( +

+ Pergunta: + {r.question_text} +

+ )} +

+ {r.response_text} +

+
+ )} +
+ ); + })} +
+ )} + + {tab === "leituras" && ( + + {loadingReadings && ( +

Carregando...

+ )} + {!loadingReadings && readings.length === 0 && ( +

+ Nenhuma leitura completa ainda. +

+ )} + {!loadingReadings && + readings.map((r) => { + const expanded = expandedReadingId === r.id; + return ( +
+ + {expanded && ( +
+

+ {r.content} +

+
+ )} +
+ ); + })} +
+ )} +
+
+ + + ); +} diff --git a/app/components/PersonalMapIcon.tsx b/app/components/PersonalMapIcon.tsx index 2e9648c..79164f3 100644 --- a/app/components/PersonalMapIcon.tsx +++ b/app/components/PersonalMapIcon.tsx @@ -7,19 +7,19 @@ type Props = { onClick: () => void; }; -/** Ícone minimalista de mapa (documento/folha) — posicionado abaixo do ícone de cadastro. */ +/** Ícone de leitura (resumo completo) — posicionado abaixo do ícone Dados. */ export default function PersonalMapIcon({ onClick }: Props) { return ( - + {hasData ? ( - + ) : ( - + )} diff --git a/app/components/ProfilePanel.tsx b/app/components/ProfilePanel.tsx index fd0b26f..4822034 100644 --- a/app/components/ProfilePanel.tsx +++ b/app/components/ProfilePanel.tsx @@ -14,7 +14,7 @@ type Props = { }; const inputClass = - "w-full bg-transparent border-0 border-b border-white/25 rounded-none px-0 py-2.5 text-sm text-mist placeholder:text-white/40 outline-none focus:border-white/50 transition-colors"; + "w-full bg-transparent border-0 border-b border-white/[0.12] rounded-none px-0 py-2.5 text-sm text-white/90 placeholder:text-white/30 outline-none focus:border-white/25 transition-colors"; export default function ProfilePanel({ isOpen, onClose, profile, onSave, onDeleteAccount }: Props) { const [fullName, setFullName] = useState(profile.fullName ?? ""); @@ -67,7 +67,7 @@ export default function ProfilePanel({ isOpen, onClose, profile, onSave, onDelet animate={{ opacity: 1 }} exit={{ opacity: 0 }} transition={{ duration: 0.2 }} - className="fixed inset-0 bg-black/60 z-40" + className="fixed inset-0 bg-black/40 z-40" onClick={onClose} aria-hidden /> @@ -76,25 +76,25 @@ export default function ProfilePanel({ isOpen, onClose, profile, onSave, onDelet animate={{ x: 0 }} exit={{ x: "100%" }} transition={{ type: "tween", duration: 0.3, ease: [0.25, 0.1, 0.25, 1] }} - className="fixed top-0 right-0 bottom-0 w-full max-w-sm bg-deep-night border-l border-white/10 z-50 flex flex-col shadow-2xl" + className="fixed top-0 right-0 bottom-0 w-full max-w-sm bg-deep-night border-l border-white/[0.06] z-50 flex flex-col shadow-2xl" > -
-

Seu mapa

+
+

Dados de Entrada

-

+

Dados opcionais para respostas mais precisas (astrologia e numerologia).

- + - + - + - + Cancelar @@ -150,7 +150,7 @@ export default function ProfilePanel({ isOpen, onClose, profile, onSave, onDelet @@ -158,7 +158,7 @@ export default function ProfilePanel({ isOpen, onClose, profile, onSave, onDelet diff --git a/app/components/TimeHeader.tsx b/app/components/TimeHeader.tsx index 3ced3a2..0f77294 100644 --- a/app/components/TimeHeader.tsx +++ b/app/components/TimeHeader.tsx @@ -12,7 +12,7 @@ type Props = { moonPhase: string; }; -const iconClass = "w-4 h-4 shrink-0 text-white/70"; +const iconClass = "w-3.5 h-3.5 sm:w-4 sm:h-4 shrink-0 text-white/70"; function IconSun() { return ( @@ -39,11 +39,11 @@ function IconCrescent() { ); } -const itemClass = "flex items-center gap-2 text-[11px] uppercase tracking-widest text-white/60"; +const itemClass = "flex items-center gap-1.5 sm:gap-2 text-[11px] uppercase tracking-widest text-white/60 whitespace-nowrap"; export default function TimeHeader({ sunrise, sunset, moonPhase }: Props) { return ( -
+
@@ -79,14 +79,14 @@ export function TimeHeaderSunrise({ sunrise }: { sunrise: string }) { export function TimeHeaderSunsetMoon({ sunset, moonPhase }: { sunset: string; moonPhase: string }) { return ( -
- +
+ {sunset} - + {moonPhase} diff --git a/app/components/Tooltip.tsx b/app/components/Tooltip.tsx index ea693d1..313555f 100644 --- a/app/components/Tooltip.tsx +++ b/app/components/Tooltip.tsx @@ -5,6 +5,8 @@ type Props = { children: React.ReactNode; /** Alinhamento do tooltip em relação ao elemento. default: left */ align?: "left" | "right" | "center"; + /** Posição vertical: acima ou abaixo do elemento. default: bottom */ + side?: "top" | "bottom"; /** Nome do group para hover (deve ser único no mesmo pai). */ groupName?: string; /** Quando definido, o wrapper usa esta classe (ex.: para botão fixo). */ @@ -12,15 +14,21 @@ type Props = { }; const tooltipBase = - "absolute top-full mt-1 text-[10px] text-white/70 opacity-0 pointer-events-none transition-opacity duration-150 z-50 max-w-[200px] break-words "; + "absolute py-2 px-3 min-w-[140px] max-w-[260px] " + + "text-[11px] text-white/80 leading-snug text-left whitespace-normal " + + "bg-black/75 backdrop-blur-sm border border-white/10 rounded-md " + + "opacity-0 pointer-events-none transition-opacity duration-150 z-50 " + + "shadow-lg break-words"; export default function Tooltip({ text, children, align = "left", + side = "bottom", groupName = "tip", wrapperClassName, }: Props) { + const vertical = side === "top" ? "bottom-full mb-1.5" : "top-full mt-1.5"; const position = align === "right" ? "right-0" @@ -35,7 +43,7 @@ export default function Tooltip({ {children} {text} diff --git a/app/page.tsx b/app/page.tsx index 2953270..feda862 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -8,7 +8,9 @@ import DarshanMessage from "./components/DarshanMessage"; import TimeHeader, { TimeHeaderSunrise, TimeHeaderSunsetMoon } from "./components/TimeHeader"; import ProfileIcon from "./components/ProfileIcon"; import PersonalMapIcon from "./components/PersonalMapIcon"; +import HistoryIcon from "./components/HistoryIcon"; import ProfilePanel from "./components/ProfilePanel"; +import HistoryModal from "./components/HistoryModal"; import LoginModal from "./components/LoginModal"; import SupportModal from "./components/SupportModal"; import CreditsModal from "./components/CreditsModal"; @@ -70,6 +72,8 @@ function Home() { const [supportOpen, setSupportOpen] = useState(false); const [creditsModalOpen, setCreditsModalOpen] = useState(false); const [personalMapOpen, setPersonalMapOpen] = useState(false); + const [historyOpen, setHistoryOpen] = useState(false); + const [historyCount, setHistoryCount] = useState({ revelations: 0, readings: 0 }); const [creditsPerRevelation, setCreditsPerRevelation] = useState(CREDITS_PER_AI_REQUEST); const [creditsPerReading, setCreditsPerReading] = useState(CREDITS_PER_PERSONAL_MAP); const [authError, setAuthError] = useState(null); @@ -184,6 +188,24 @@ function Home() { } }, []); + const refreshHistoryCount = useCallback(async () => { + try { + const res = await fetch("/api/history/count", { credentials: "include" }); + const data = await res.json(); + setHistoryCount({ + revelations: typeof data.revelations === "number" ? data.revelations : 0, + readings: typeof data.readings === "number" ? data.readings : 0, + }); + } catch { + setHistoryCount({ revelations: 0, readings: 0 }); + } + }, []); + + useEffect(() => { + if (session) refreshHistoryCount(); + else setHistoryCount({ revelations: 0, readings: 0 }); + }, [session, refreshHistoryCount]); + useEffect(() => { let cancelled = false; (async () => { @@ -303,6 +325,7 @@ function Home() { ]); setUserInput(""); if (typeof data.balance === "number") setCredits(data.balance); + refreshHistoryCount(); } catch { setCurrentMessage(FALLBACK_MESSAGE); } finally { @@ -376,7 +399,7 @@ function Home() { const showConfigError = aiReady === false && !mockMode; return ( -
+
{!introDone ? ( -
-
-
- {session && ( -
- - - - + {/* Linha 1: créditos + nascer do sol | Luz do Tempo | pôr do sol + lua — mesma margem do topo */} +
+ {session && ( +
+ +
- )} + + Créditos + + + + {credits} + +
+ )} + -
+ +
+
+ Luz do Tempo +
+
+ +
+ {/* Linha 2: AI desligada | horário atual | (vazio) */} +
-
-

Luz do Tempo

+
{currentTime ? ( -

{currentTime}

+ {currentTime} ) : null}
-
- -
+
); diff --git a/docs/DICIONARIO_OFFLINE.md b/docs/DICIONARIO_OFFLINE.md index 302be16..2f06244 100644 --- a/docs/DICIONARIO_OFFLINE.md +++ b/docs/DICIONARIO_OFFLINE.md @@ -42,7 +42,7 @@ lib/ ## Uso na API -Quando a requisição vem com `mock: true`, a rota `/api/darshan` usa `getOfflineRevelation(perfil, pergunta)` em vez da IA. A resposta tem o mesmo formato (`message`, `phase`), então o frontend não precisa mudar. +Quando a requisição vem com `mock: true`, a rota `/api/darshan` usa o **Instant Light Engine** (`composeInstantLight(perfil, { recentSacredIds })`) em vez da IA: texto sagrado + (se houver perfil) insight e prática do mapa + pergunta. A resposta tem o mesmo formato (`message`, `phase`) e opcionalmente `sacredId` para o cliente enviar em `recentSacredIds` e reduzir repetição. Ver [INSTANT_LIGHT_ENGINE.md](./INSTANT_LIGHT_ENGINE.md). ## Limitações atuais diff --git a/docs/ENGINE_2.0.md b/docs/ENGINE_2.0.md new file mode 100644 index 0000000..f9b8cd5 --- /dev/null +++ b/docs/ENGINE_2.0.md @@ -0,0 +1,118 @@ +# Engine 2.0 — Mapa simbólico + Composer modular + Readings temáticos + +Este documento descreve o **motor profissional** do Darshan: **SymbolicMap → Insights → Composer → Readings modulares**. O Oracle offline continua existindo como “ritual rápido”; a **leitura profissional** é baseada neste mapa. + +--- + +## Visão geral + +``` +Perfil (nome, data, hora, local) + ↓ +buildSymbolicMap(profile) → SymbolicMap canônico + ↓ +collectInsights(map) → Insight[] (key, topic, weight, evidence) + ↓ +composeReading(map, topic) → texto por tópico (general | love | career | year | action) + ↓ +getOfflineReading(profile) → { general, love, career, year, action } +``` + +- **SymbolicMap** é o objeto estruturado reutilizável (jyotish, numerology, themes, traits, evidence). +- **Insights** são interpretações determinísticas derivadas do mapa (não blocos fixos). +- **Composer** monta o texto por tópico a partir dos insights (composeReading(map, "love"), etc.). +- **Readings temáticos** existem: love, career, year, action — cada um chama composeReading(map, topic). +- **Action** é derivada de insights (número regente, arquétipo, geral), não fixa. + +--- + +## 1. SymbolicMap canônico (`lib/symbolic/`) + +- **SymbolicMap.ts** — ponto de entrada: exporta o tipo `SymbolicMap` e `buildSymbolicMap(profile)`. +- **types.ts** — estrutura: + - `jyotish`: { moonRashi, nakshatra, archetypeKey } + - `numerology`: { rulingNumber } + - `themes`: string[] + - `traits`: string[] + - `evidence`: Record (ex.: { chart } para auditoria e extensão) +- **builder.ts** — `buildSymbolicMap(profile)` usa `computeVedicChartSimplified` e `getRulingNumberFromName`; retorna o mapa com themes/traits vazios e evidence.chart. + +Existe **mapa estruturado reutilizável**, não só cálculo solto. + +--- + +## 2. Insight Layer (`lib/insights/`) + +- **types.ts** — `Insight` = { key, topic, weight, evidence } (e system para compatibilidade). +- **jyotishInsightsForSymbolic.ts** — regras Jyotish (20+): por nakshatra e moonRashi; topics general, love, career, year. +- **collectInsightsForSymbolicMap.ts** — agrega insights Jyotish + **Action derivada do mapa**: + - `action.n.{rulingNumber}` (numerologia) + - `action.general` + - `action.archetype.{archetypeKey}` (jyotish) + +As interpretações são **derivadas** do mapa (key, topic, weight, evidence), não blocos fixos. + +--- + +## 3. Narrative Composer (`lib/narrative/composer.ts`) + +- **composeReading(map, topic)** — assinatura principal: + - coleta insights do mapa; + - filtra por `topic` (general | love | career | year | action); + - ordena por peso; + - pega top 3; + - resolve frase por key (PHRASES[i.key][0]); + - junta com `\n\n`. + +Existe **composeReading(map, "love")**, **composeReading(map, "career")**, **composeReading(map, "year")**, **composeReading(map, "action")** — modularidade por tópico. + +--- + +## 4. Readings modulares (`lib/readings/symbolicReadings.ts`) + +Cada getter recebe o **mapa** e devolve texto para um tópico: + +- **getGeneral(map)** → composeReading(map, "general") +- **getLove(map)** → composeReading(map, "love") +- **getCareer(map)** → composeReading(map, "career") +- **getYear(map)** → composeReading(map, "year") +- **getAction(map)** → composeReading(map, "action") + +O produto pode oferecer “fale só sobre relacionamento”, “fale só sobre trabalho”, “fale sobre o ano atual” usando o mesmo mapa. + +--- + +## 5. getOfflineReading(profile) — wrapper + +- **lib/readingOffline.ts**: + 1. `map = buildSymbolicMap(profile)` + 2. `return { general: getGeneral(map), love: getLove(map), career: getCareer(map), year: getYear(map), action: getAction(map) }` + +Retorno estruturado: **general, love, career, year, action**. A API `/api/map/personal?offline=true` usa esse objeto e pode enviar `sections` ao frontend. + +--- + +## 6. Action derivada do mapa + +A prática concreta (Action) **não é fixa**; vem dos insights: + +- Número regente → `action.n.{n}` (ex.: número 5 → foco e limites). +- Arquétipo → `action.archetype.{archetypeKey}` (ex.: cuidador → boundary). +- Fallback → `action.general`. + +Frases no dicionário (`phrasesForSymbolic.ts`, action.1 … action.30, action.general, action.archetype.*) são escolhidas por key com base no mapa. + +--- + +## Resumo do estado + +| Camada | Status | +|------------------------|--------| +| SymbolicMap universal | ✅ lib/symbolic (jyotish, numerology, themes, traits, evidence) | +| Insight Rules | ✅ key, topic, weight, evidence (Jyotish + Action por mapa) | +| Composer modular | ✅ composeReading(map, topic) | +| Readings Love/Career/Year | ✅ getLove(map), getCareer(map), getYear(map) | +| Action baseado no mapa | ✅ action.n.X, action.archetype.X, action.general | +| getOfflineReading | ✅ retorna { general, love, career, year, action } | + +O Darshan tem **leituras temáticas reutilizáveis** baseadas no mesmo mapa simbólico (Engine 2.0). O Oracle continua como ritual rápido; a leitura profissional é map-based. diff --git a/docs/FLUXO_ORACULO_OFFLINE.md b/docs/FLUXO_ORACULO_OFFLINE.md index 5771dcd..0760f12 100644 --- a/docs/FLUXO_ORACULO_OFFLINE.md +++ b/docs/FLUXO_ORACULO_OFFLINE.md @@ -4,11 +4,20 @@ Este documento descreve **como** o Darshan usa a biblioteca Jyotish (mhah-pancha --- +## Oracle vs Leitura profissional (Engine 2.0) + +- **Oracle (revelação rápida):** quando o frontend envia `mock: true` para `/api/darshan`, usamos o **ritual rápido** em `lib/oracleOffline.ts`: chart védico → **primeiro arquétipo** como principal da revelação → blocos de frase por seed. É o “oráculo em blocos”. +- **Leitura profissional (mapa pessoal):** quando o frontend envia `offline: true` para `/api/map/personal`, usamos o **Engine 2.0** em `lib/readingOffline.ts`: **SymbolicMap** → **Insight Rules** → **Narrative Composer** → readings modulares (general, love, career, year, action). Não é texto fixo; é mapa estruturado + composer modular. Ver [ENGINE_2.0.md](./ENGINE_2.0.md). + +Ou seja: existe **mapa simbólico universal** e **composer modular**; o Oracle continua usando o primeiro arquétipo para o ritual rápido; a leitura profissional é baseada no mapa. + +--- + ## 0. Garantias: IA desativada = 100% offline (revelação) - **A IA desativada não chama a IA por baixo dos panos.** Quando o frontend envia `mock: true` no POST para `/api/darshan`, a rota retorna **antes** de qualquer `getConnector()` ou `connector.complete()`. Nenhum provedor externo (OpenAI, Google, Anthropic) é chamado. -- **Quem faz o trabalho é só o nosso código:** `lib/oracleOffline.ts` + `lib/knowledge/*` (formulações, arquétipos, Jyotish, numerologia, textos clássicos). Tudo roda no servidor Node/Next sem chamadas a APIs de IA. -- **Não há bypass em dev.** O modo mock não é “desconsiderado” em desenvolvimento: se `body.mock === true`, o servidor sempre usa `getOfflineRevelation` e responde imediatamente, em qualquer ambiente. +- **Quem faz o trabalho é só o nosso código:** o modo mock usa `lib/instantLight/` (Sacred Library + mapa quando houver perfil). A **leitura offline** usa `lib/readingOffline.ts` → **Engine 2.0**: `buildSymbolicMap(profile)` → `getGeneral(map)`, `getLove(map)`, `getCareer(map)`, `getYear(map)`, `getAction(map)` (composer modular). Tudo roda no servidor Node/Next sem chamadas a APIs de IA. Ver [ENGINE_2.0.md](./ENGINE_2.0.md) e [OFFLINE_ENGINE_PIPELINE.md](./OFFLINE_ENGINE_PIPELINE.md). +- **Não há bypass em dev.** O modo mock não é “desconsiderado” em desenvolvimento: se `body.mock === true`, o servidor usa o **Instant Light Engine** (`composeInstantLight`) e responde imediatamente (texto sagrado + insight/prática do mapa quando houver perfil). Ver [INSTANT_LIGHT_ENGINE.md](./INSTANT_LIGHT_ENGINE.md). - **Leitura (mapa pessoal):** quando a IA está desligada (toggle “AI desligada”), o modal **Leitura** pode gerar uma **leitura offline** (sem IA e sem custo): o frontend envia `offline: true` para `/api/map/personal`, que chama `getOfflineReading(profile)` em `lib/readingOffline.ts` e monta o texto a partir do conhecimento local (Jyotish, numerologia, arquétipos, textos clássicos). Nenhum crédito é debitado. Com IA ligada, a Leitura usa a IA e debita créditos normalmente. --- @@ -16,7 +25,7 @@ Este documento descreve **como** o Darshan usa a biblioteca Jyotish (mhah-pancha ## 1. Quando o oráculo offline é usado - O frontend envia `mock: true` no body do POST para `/api/darshan` (toggle "AI desligada"). -- A rota chama `getOfflineRevelation(userProfile, userMessage)` em vez da IA. +- A rota chama `composeInstantLight(userProfile, { recentSacredIds })` em vez da IA (Universal Light sem perfil, Personal Light com perfil). - O **perfil** (nome, data de nascimento, local, horário) é o que alimenta todo o fluxo; a **pergunta** do usuário hoje não altera o conteúdo (pode ser usada em versões futuras). --- @@ -40,7 +49,7 @@ Este documento descreve **como** o Darshan usa a biblioteca Jyotish (mhah-pancha 3. **Mapeamento:** - `Raasi.ino` → nosso **Rashi** (mesha, vrishabha, …, mina). - `Nakshatra.ino` → nossa **Nakshatra** (ashwini, bharani, …, revati). -4. **Arquétipos:** em `archetypes.ts`, cada Rashi e cada Nakshatra estão ligados a um **arquétipo** (ex.: lua em Karka → arquétipo "cuidador"). O chart devolve uma lista `archetypeKeys`; usamos o **primeiro** como arquétipo principal da revelação. +4. **Arquétipos:** em `archetypes.ts`, cada Rashi e cada Nakshatra estão ligados a um **arquétipo** (ex.: lua em Karka → arquétipo "cuidador"). O chart devolve uma lista `archetypeKeys`; no **Oracle (ritual rápido)** usamos o **primeiro** como arquétipo principal da revelação. Na **leitura profissional** (Engine 2.0), o mapa simbólico guarda esse archetypeKey e os insights + composer montam love/career/year/action a partir do mapa. Se a biblioteca **não** estiver instalada ou der erro, usamos o **cálculo simplificado** em `vedic.ts` (Sol/Lua aproximados por fórmulas locais) e, a partir daí, o mesmo mapeamento Rashi/Nakshatra → arquétipo. @@ -105,5 +114,5 @@ A resposta final é a concatenação desses blocos com `\n\n`, no mesmo formato - **Mais/menos blocos:** em `oracleOffline.ts`, ordem e condições dos `blocks.push` e o `slice(0, 7)`. - **Peso do arquétipo vs numerologia:** hoje arquétipo define bloco 1 e 2; numerologia só bloco 4; pode-se inverter ou acrescentar mais frases de um deles. -- **Usar a pergunta do usuário:** `getOfflineRevelation` recebe `userMessage`; hoje não é usado; pode-se, por exemplo, escolher fonte (Upanishad vs Gita) ou tema conforme palavras-chave da pergunta. +- **Usar a pergunta do usuário:** `composeInstantLight` pode receber `themeTags` ou `recentSacredIds`; o cliente pode enviar `recentSacredIds` (ids dos últimos textos sagrados) para reduzir repetição. Pode-se, no futuro, escolher fonte (Upanishad vs Gita) ou tema conforme palavras-chave da pergunta. - **Conteúdo:** editar listas em `formulations.ts`, `classicTexts.ts`, `archetypeTraits.ts`, `numerology.ts` para mudar o que pode ser retornado. diff --git a/docs/HISTORICO_RESPOSTAS_LEITURAS.md b/docs/HISTORICO_RESPOSTAS_LEITURAS.md new file mode 100644 index 0000000..6419515 --- /dev/null +++ b/docs/HISTORICO_RESPOSTAS_LEITURAS.md @@ -0,0 +1,48 @@ +# Histórico de respostas e leituras + +O Darshan armazena no Supabase as respostas da interação com o orb (revelações) e as leituras completas (mapa pessoal), associadas ao usuário logado (por `user_id`). Isso permite exibir um ícone de **Histórico** quando há dados e uma página com abas para consultar o passado. + +--- + +## Tabelas (Supabase) + +- **revelations** — respostas da IA na interação com o orb + - `user_id`, `question_text` (opcional), `response_text`, `created_at` +- **readings** — resultado das leituras completas (mapa pessoal) + - `user_id`, `content`, `created_at` + +Migration: `supabase/migrations/20250129100000_history_tables.sql`. + +--- + +## Quando os dados são gravados + +Apenas **respostas geradas pela IA** são gravadas no histórico. + +- **Revelações:** após cada resposta da IA no orb (POST `/api/darshan` com `revelation: true`). Modo mock/oráculo offline **não** grava em `revelations`. +- **Leituras:** após cada leitura completa gerada pela IA (POST `/api/map/personal`). Modo offline (leitura sem IA) **não** grava em `readings`. + +Ambos usam o email da sessão para resolver o `user_id` na tabela `users`; se o Supabase não estiver configurado, a gravação é silenciosa (não quebra o fluxo). + +--- + +## APIs + +| Método | Rota | Descrição | +|--------|------|-----------| +| GET | `/api/history/count` | Retorna `{ revelations, readings }` do usuário logado (para exibir o ícone quando > 0). | +| GET | `/api/history/revelations` | Lista revelações (query: `limit`, `offset`). | +| GET | `/api/history/readings` | Lista leituras (query: `limit`, `offset`). | + +Todas exigem sessão (cookie); sem login retornam 401 ou contagem zerada. + +--- + +## UI + +- **Ícone Histórico:** aparece à direita (abaixo do ícone Leitura) **somente quando** o usuário está logado e há pelo menos uma revelação ou leitura armazenada. +- **Página de histórico:** painel lateral com duas abas: + - **Respostas** — lista de revelações (data, pergunta se houver, resposta; expandir para ver texto completo). + - **Leituras** — lista de leituras (data, trecho; expandir para ver conteúdo completo). + +Estilo alinhado ao painel "Dados de Entrada" (cores suaves, minimalista). diff --git a/docs/INSTANT_LIGHT_ENGINE.md b/docs/INSTANT_LIGHT_ENGINE.md new file mode 100644 index 0000000..6128181 --- /dev/null +++ b/docs/INSTANT_LIGHT_ENGINE.md @@ -0,0 +1,101 @@ +# Instant Light Engine — Sacred Library + Personal Insight + +O Darshan é **duas coisas ao mesmo tempo**: + +1. **Universal Light (Instant Light)** — qualquer pessoa, sem cadastro, sem mapa: um sutra, um verso purânico, uma prática mínima, uma pergunta final. +2. **Personal Light** — quando a pessoa fornece dados (nome, data/hora de nascimento): o mesmo Instant Light **filtrado e ancorado no SymbolicMap** (tradição + mapa pessoal + destino simbólico). + +O **grande tesouro** do Darshan são os textos sagrados (Purāṇas, Yoga Sūtras, Upanishads, Bhagavad Gita). Eles **não são substituídos** pelo mapa; o mapa é uma **camada adicional**. + +--- + +## Arquitetura: Sacred Remedy Matrix + Diagnosis Engine + +O Instant Light usa a **matriz de remédios** (30 estados) e o **diagnóstico consciente**: + +- **Camada 1 — Prakṛti (Jyotish):** dosha/elemento do mapa (Lua + Nakshatra). +- **Camada 2 — Sāṃkhya Guṇas (3):** estado macro (sattva/rajas/tamas) — “céu interno”. +- **Camada 3 — Ayurvedic Qualities (20):** qualidades em excesso/deficiência (prática, alimento). + +``` +light = SacredVerse(remedy) + [InsightFromMap se perfil] + Practice(remedy) + Question(remedy) +``` + +- **selectRemedy(profile?, { seed, recentSacredIds })** escolhe uma entrada da matriz: + - Com perfil: filtra por guna dominante do mapa (arquétipo → guna) e evita `recentSacredIds`. + - Sem perfil: escolhe entre todas por seed, evitando repetição. +- Cada entrada traz: **verso sagrado** (embutido), **prática**, **alimento**, **pergunta**. +- Nunca aleatório puro; cooldown via `recentSacredIds`. + +--- + +## 1. Sacred Library Engine (`lib/sacred/`) + +- **pickSacredText({ themeTags?, avoidLastIds?, seed? })** — escolhe um texto sagrado com: + - **Rotação determinística** (seed, ex.: dailySeed). + - **Evitar repetição** (avoidLastIds = cooldown). + - **Tags temáticas** (filtrar por source, guna, archetype). +- Fonte atual: `lib/knowledge/classicTexts` (Upanishads, Bhagavad Gita, Yoga Sutras). +- Estrutura de dicionário em `lib/dictionaries/sacred/` (ex.: `yoga_sutras.json`) com `id`, `verse`, `tags` para expansão futura. + +--- + +## 2. Diagnosis Engine + Remedy Matrix (`lib/diagnosis/`) + +- **remedyMatrix.json** — 30 estados (ansiedade, apego, medo, raiva, letargia, clareza, etc.). +- Cada estado: **klesha**, **samkhyaGuna**, **qualities**, **sacred** (corpus, id, verse), **practice**, **food**, **question**. +- **selectRemedy(profile?, { seed, recentSacredIds })** — seleção determinística com cooldown. +- **getPrakritiFromMap(map)**, **getDominantSamkhyaGuna(map)**, **buildDiagnosisFromMapAndRemedy(map, remedy)**. +- Tipos: `SamkhyaGuna`, `AyurvedicQuality`, `ConsciousDiagnosis`, `RemedyMatrixEntry`. + +## 3. Instant Light Composer (`lib/instantLight/`) + +- **composeInstantLight(profile?, options?)** → `{ message, sacredId }`. +- Fonte do conteúdo: **matriz de remédios** (selectRemedy) — verso + prática + pergunta da entrada. +- **Com perfil:** adiciona insight do mapa (getGeneral) entre verso e prática. +- **sacredId** = `corpus.id` (ex.: `yoga_sutras.YS.1.33`) para cooldown. + +--- + +## 4. API Darshan (modo mock) + +- **POST /api/darshan** com `mock: true`: + - Chama **composeInstantLight(userProfile, { recentSacredIds })**. + - **Sem perfil** (sem nome/data) → Universal Light. + - **Com perfil** → Personal Light. +- Resposta inclui **sacredId** para o cliente acumular e enviar em `recentSacredIds` nas próximas requisições. + +--- + +## 5. Numerologia expandida (SymbolicMap) + +- **lifePathNumber** — data de nascimento (soma dos dígitos reduzida a 1–9 ou 11/22). +- **expressionNumber** — nome completo (número regente / Pitágoras). +- **rulingNumber** — mantido para compatibilidade (= expressionNumber). +- Retornado em `map.numerology` (POST /api/map, leituras offline). + +--- + +## 6. Onde está cada peça + +| Peça | Arquivo / pasta | +|------|------------------| +| Remedy Matrix (30 estados) | `lib/dictionaries/remedyMatrix.json` | +| Diagnosis Engine | `lib/diagnosis/diagnosisEngine.ts` | +| Tipos Diagnosis | `lib/diagnosis/types.ts` | +| Sacred Picker | `lib/sacred/sacredPicker.ts` | +| Instant Light Composer | `lib/instantLight/instantLightComposer.ts` | +| Textos clássicos (fonte) | `lib/knowledge/classicTexts.ts` | +| lifePathNumber / expressionNumber | `lib/knowledge/numerology.ts`, `lib/symbolic/builder.ts` | +| /api/darshan (mock) | `app/api/darshan/route.ts` → composeInstantLight | + +--- + +## 7. Resultado + +- **Darshan universal** (sem cadastro) — texto sagrado + prática + pergunta. +- **Darshan pessoal** (com mapa) — texto sagrado + insight do mapa + prática do mapa + pergunta. +- **Textos sagrados preservados e rotacionados** (seed + avoidLastIds). +- **Personalização elegante** quando o mapa existe. +- **Numerologia mais profunda** (lifePath + expression). +- **getOfflineRevelation** continua disponível em `lib/oracleOffline.ts` para outros fluxos; o modo mock da API passou a usar o Instant Light híbrido. diff --git a/docs/OFFLINE_ENGINE_PIPELINE.md b/docs/OFFLINE_ENGINE_PIPELINE.md new file mode 100644 index 0000000..b35e10a --- /dev/null +++ b/docs/OFFLINE_ENGINE_PIPELINE.md @@ -0,0 +1,127 @@ +# Pipeline offline — engine high-end (sem IA) + +Este documento descreve a arquitetura do **engine offline** do Darshan: núcleo astronômico multi-provider, engines (Jyotish, numerologia, Human Design), mapa simbólico, insights determinísticos, dicionários e leituras modulares. Tudo roda **sem IA**, com lógica e dados determinísticos. + +--- + +## Visão geral do pipeline + +``` +User Profile (nome, data, hora, local) + ↓ +AstronomicalCore (multi-provider: Swiss Ephemeris → mhah-panchang) + ↓ +Engines (Jyotish + Numerologia + Human Design) + ↓ +SymbolicMap universal + ↓ +Insight Rules determinísticas + ↓ +Narrative Composer offline + ↓ +Readings modulares (general / love / career / year) + Action + ↓ +API endpoints temáticos +``` + +--- + +## 1. Core astronômico (`lib/core/`) + +- **types.ts** — `AstronomicalCore` (providerUsed, julianDay?, planets?, moonRashi?, nakshatra?, ascendant?, houses?), `CoreProfile`. +- **ephemerisProvider.ts** — interface `EphemerisProvider` (name, compute(profile)). +- **providers/mhahProvider.ts** — mhah-panchang (Lua: Rashi + Nakshatra). **Não removido;** fallback oficial. +- **providers/swissProvider.ts** — stub Swiss Ephemeris (lança para fallback; futuro: @fusionstrings/panchangam ou sweph-wasm). +- **ephemerisResolver.ts** — `computeAstronomicalCore(profile)`: tenta Swiss, depois mhah; nada quebra. + +--- + +## 2. Engines (`lib/engines/`) + +- **jyotishEngine.ts** — só símbolos: moonRashi, nakshatra, planets, houses, precisionLevel. +- **numerologyEngine.ts** — número regente e traits (refator do conhecimento em `lib/knowledge/numerology`). +- **humanDesignEngine.ts** — stub (retorna null se não houver planetas; tipo/autoridade/perfil fixos quando houver). +- **buildSymbolicMap.ts** — `buildSymbolicMap(profile)` → { core, jyotish, numerology, humanDesign }. + +--- + +## 3. Insights (`lib/insights/`) + +- **types.ts** — `Insight` (key, weight, system, topic, evidence?), `InsightTopic`, `InsightSystem`. +- **jyotishInsights.ts** — regras por nakshatra e rashi (general, love, career, year). +- **numerologyInsights.ts** — regras por número regente (general, career, love, year). +- **humanDesignInsights.ts** — regras HD (stub). +- **collectInsights.ts** — `collectAllInsights(map)` agrega todos os insights. + +Nada aleatório; cada insight tem key justificável pelo mapa. + +--- + +## 4. Dicionários (`lib/dictionaries/`) + +- **jyotish.ts** — `JYOTISH_PHRASES` (chaves por rashi/nakshatra e tópico). +- **numerology.ts** — `NUMEROLOGY_PHRASES` (chaves por número regente e tópico). +- **humanDesign.ts** — `HD_PHRASES` (tipo, autoridade). +- **action.ts** — `ACTION_PHRASES` (práticas concretas). +- **index.ts** — `phraseFor(key)` resolve frase por chave (primeira do array; futuro: seed para variedade). + +--- + +## 5. Narrative Composer (`lib/narrative/`) + +- **composer.ts** — `composeReading(insights, topic)`: filtra por tópico, ordena por peso, pega até 5 insights, resolve frase por key, junta com `\n\n`. Evita redundância; sempre justifica via key → dictionary. + +--- + +## 6. Leituras modulares (`lib/readings/`) + +- **generalReading.ts** — `getGeneralReading(profile)` → { reading, action }. +- **loveReading.ts** — `getLoveReading(profile)` → { reading, action }. +- **careerReading.ts** — `getCareerReading(profile)` → { reading, action }. +- **yearReading.ts** — `getYearReading(profile)` → { reading, action }. +- **actionReading.ts** — `getActionReading(profile)` → string (prática final). + +Toda leitura termina com **Action** (prática concreta). + +--- + +## 7. Integração com o Darshan atual + +- **oracleOffline.ts** — usa chart védico + primeiro arquétipo + seed para revelação do orb (1–3 blocos, ritual rápido). +- **readingOffline.ts** — usa **Engine 2.0**: `buildSymbolicMap(profile)` (lib/symbolic) → `getGeneral(map)`, `getLove(map)`, `getCareer(map)`, `getYear(map)`, `getAction(map)` (composer modular). Retorna `{ general, love, career, year, action }`. Ver [ENGINE_2.0.md](./ENGINE_2.0.md). + +Nada do engine anterior foi removido; mhah-panchang continua como fallback. + +--- + +## 8. APIs temáticas + +| Método | Rota | Corpo | Resposta | +|--------|------|--------|----------| +| POST | `/api/reading/general` | `{ profile?: CoreProfile }` | `{ map, reading, action }` | +| POST | `/api/reading/love` | `{ profile?: CoreProfile }` | `{ map, reading, action }` | +| POST | `/api/reading/career` | `{ profile?: CoreProfile }` | `{ map, reading, action }` | +| POST | `/api/reading/year` | `{ profile?: CoreProfile }` | `{ map, reading, action }` | + +Cada resposta inclui o **mapa simbólico** (core, jyotish, numerology, humanDesign), o **texto da leitura** e a **ação** (prática final). + +--- + +## Princípios + +- Nada depende de IA. +- Nada substitui mhah-panchang; Swiss Ephemeris entra como camada premium opcional. +- Insights são determinísticos e justificáveis. +- Leituras são modulares e expansíveis. +- Action sempre presente (menos etéreo). + +--- + +## Próximos passos (Parte 2) + +- **Swiss Ephemeris:** ver [SWISS_EPHEMERIS_INTEGRATION.md](./SWISS_EPHEMERIS_INTEGRATION.md) para integrar @fusionstrings/panchangam ou @fusionstrings/swiss-eph em `swissProvider.ts`. +- **Human Design real:** gates, canais, tipo, autoridade, perfil (BodyGraph completo). +- **Year Engine:** trânsitos e dashas para enriquecer a leitura de ano. +- **Ayurveda Engine:** dosha e práticas corporais no Action Engine. + +Detalhes e ordem sugerida: [ROADMAP_PARTE_2.md](./ROADMAP_PARTE_2.md). diff --git a/docs/README.md b/docs/README.md index 19fca75..21f4a57 100644 --- a/docs/README.md +++ b/docs/README.md @@ -23,6 +23,12 @@ |-----------|-----------| | [FLUXO_E_REGRAS.md](./FLUXO_E_REGRAS.md) | Fluxo completo da aplicação e regras (validação IA, estados, créditos). | | [FLUXO_ORACULO_OFFLINE.md](./FLUXO_ORACULO_OFFLINE.md) | Modo oráculo offline (sem IA) e dicionário simbólico. | +| [INSTANT_LIGHT_ENGINE.md](./INSTANT_LIGHT_ENGINE.md) | Instant Light híbrido: Sacred Library + Personal Insight (Universal vs Personal Light). | +| [SYMBOLIC_MAP_ENGINE.md](./SYMBOLIC_MAP_ENGINE.md) | **Motor simbólico real**: engines (Jyotish, Numerologia, HD), SymbolicMap, readings modulares, Composer, APIs POST /api/map e POST /api/reading?theme=. | +| [ENGINE_2.0.md](./ENGINE_2.0.md) | Engine 2.0: SymbolicMap canônico → Insights → Composer → Readings modulares (love/career/year/action). | +| [OFFLINE_ENGINE_PIPELINE.md](./OFFLINE_ENGINE_PIPELINE.md) | Pipeline offline high-end: core, engines, SymbolicMap, insights, leituras modulares. | +| [SWISS_EPHEMERIS_INTEGRATION.md](./SWISS_EPHEMERIS_INTEGRATION.md) | Como integrar Swiss Ephemeris (opcional) em `swissProvider.ts`. | +| [ROADMAP_PARTE_2.md](./ROADMAP_PARTE_2.md) | Roadmap Parte 2: Human Design real, Year Engine, Ayurveda. | | [RITUAL_REVELATION_FLOW.md](./RITUAL_REVELATION_FLOW.md) | Fluxo do ritual de revelação. | | [DICIONARIO_OFFLINE.md](./DICIONARIO_OFFLINE.md) | Dicionário e significados usados no modo offline. | @@ -54,6 +60,7 @@ | Documento | Descrição | |-----------|-----------| | [AUTH_CREDITS_GOOGLE_PAY.md](./AUTH_CREDITS_GOOGLE_PAY.md) | Login (código por e-mail, Google), créditos, Stripe, Mercado Pago, Google Pay. | +| [HISTORICO_RESPOSTAS_LEITURAS.md](./HISTORICO_RESPOSTAS_LEITURAS.md) | Armazenamento e UI de histórico (respostas do orb e leituras completas). | | [PRECIFICACAO_CREDITOS.md](./PRECIFICACAO_CREDITOS.md) | Preços dos pacotes de créditos e taxa da plataforma. | | [PAYMENTS_MERCADOPAGO.md](./PAYMENTS_MERCADOPAGO.md) | Integração Mercado Pago (Checkout Pro, webhook). | | [FINANCE_SYSTEM.md](./FINANCE_SYSTEM.md) | Sistema financeiro (ledger, uso de IA, export CSV). | diff --git a/docs/ROADMAP_PARTE_2.md b/docs/ROADMAP_PARTE_2.md new file mode 100644 index 0000000..467fb6d --- /dev/null +++ b/docs/ROADMAP_PARTE_2.md @@ -0,0 +1,72 @@ +# Roadmap Parte 2 — Human Design, Year Engine, Ayurveda + +Este documento descreve os **próximos passos** do engine offline do Darshan após a base atual (AstronomicalCore, SymbolicMap, Insights, Composer, leituras modulares). Tudo continua **sem IA**, determinístico e expansível. + +--- + +## 1. Human Design real (BodyGraph completo) + +**Estado atual:** stub em `lib/engines/humanDesignEngine.ts` — retorna tipo/autoridade/perfil fixos quando há `core.planets`; senão `null`. + +**Objetivo:** Cálculo real de Human Design a partir das posições planetárias (e opcionalmente hora/local para casas). + +**Entregas sugeridas:** + +- **Gates (64)** — posições planetárias em signos → ativação de portas (ex.: Sol em 15° Áries → Porta 51). +- **Canais** — pares de portas ativadas (Sol/Lua, etc.) → definição de canais. +- **Tipo** — Generator, Manifestor, Projector, Reflector, Manifesting Generator a partir dos centros definidos/abertos. +- **Autoridade** — Emocional, Sacral, Ego, etc., a partir do corpo gráfico. +- **Perfil** — 1/4, 4/6, etc. (linhas consciente/inconsciente). +- **Centros** — definido/aberto por centro (Head, Ajna, Throat, G, Heart, Sacral, Solar Plexus, Spleen, Root). + +**Fontes:** Cálculo HD usa posições planetárias (e às vezes casas). Existem tabelas de portas por grau/signo; APIs ou libs especializadas em BodyGraph podem ser integradas. O core já pode fornecer `planets` quando Swiss Ephemeris estiver ativo. + +**Inserção no pipeline:** `humanDesignEngine(core)` deixa de ser stub e passa a calcular tipo, autoridade, perfil, centros (e opcionalmente gates/canais). Os insights em `lib/insights/humanDesignInsights.ts` e o dicionário `lib/dictionaries/humanDesign.ts` já estão preparados para chaves como `hd.type.*`, `hd.authority.*`; basta alimentar com dados reais. + +--- + +## 2. Year Engine (trânsitos + dashas) + +**Estado atual:** Leituras de ano usam os mesmos insights do mapa natal (tópico `year`); não há trânsitos nem dashas. + +**Objetivo:** Enriquecer a leitura de ano com: + +- **Trânsitos** — posições atuais dos planetas (ou da Lua) em relação ao mapa natal (ex.: “Lua em trânsito em seu Sol natal”). +- **Dashas** — períodos védicos (ex.: Mahadasha, Antardasha) a partir da data de nascimento e da data “do ano” (ou hoje). + +**Entregas sugeridas:** + +- Módulo **trânsitos:** dado mapa natal (core no nascimento) e data de referência (ex.: ano corrente), calcular posições na data de referência e comparar com o natal (aspectos, trânsito por casa/signo). +- Módulo **dashas:** dado nascimento e data de referência, calcular qual Mahadasha/Antardasha está ativa (Vimshottari ou outro sistema). +- **Insights de ano** — novas regras em `lib/insights/` que geram keys como `jyotish.transit.*`, `jyotish.dasha.*`; dicionário com frases para esses keys. +- **Composer** — tópico `year` passa a incluir insights de trânsito/dasha além dos atuais (natal + year). + +**Dependência:** Cálculo de posições para uma data arbitrária (nascimento vs. “hoje” ou “ano”). Com **Swiss Ephemeris** integrado, isso fica direto; com só mhah-panchang, pode ser limitado a Lua/Nakshatra para o ano. + +--- + +## 3. Ayurveda Engine (corporal) + +**Estado atual:** Não existe; o Action Engine usa frases genéricas de prática (respiração, pausa, etc.). + +**Objetivo:** Sugerir práticas corporais e rotinas alinhadas ao mapa (dosha, constituição, momento do dia/estação). + +**Entregas sugeridas:** + +- **Constituição (dosha)** — derivar Vata/Pitta/Kapha (ou predominância) a partir do mapa (ex.: elementos por signos planetários, Lua, Ascendente) e/ou perguntas simples (opcional). +- **Insights Ayurveda** — novo sistema em `lib/insights/` (ex.: `ayurveda.dosha.*`, `ayurveda.season.*`) com regras determinísticas. +- **Dicionário** — `lib/dictionaries/ayurveda.ts` ou `action.ts` ampliado com frases de prática (respiração, alimentação, movimento) por dosha/momento. +- **Action Engine** — além das frases atuais, incluir sugestões de prática corporal (ex.: “Prática mínima: 7 respirações lentas ao acordar”) baseadas em insights Ayurveda quando disponíveis. + +**Nota:** Pode começar com regras simples (ex.: Lua em signos de fogo → Pitta; Lua em signos de ar → Vata) e expandir depois com estação do ano, hora do dia, etc. + +--- + +## Ordem sugerida + +1. **Swiss Ephemeris** — integrar em `swissProvider.ts` (ver [SWISS_EPHEMERIS_INTEGRATION.md](./SWISS_EPHEMERIS_INTEGRATION.md)) para ter planetas completos e casas; habilita Human Design e trânsitos com mais precisão. +2. **Human Design real** — gates, canais, tipo, autoridade, perfil; alimentar `humanDesignEngine` e dicionários. +3. **Year Engine** — trânsitos e dashas; ampliar insights e dicionário para o tópico `year`. +4. **Ayurveda Engine** — dosha + práticas corporais; novos insights e Action. + +Cada passo pode ser feito de forma incremental sem quebrar o pipeline atual. diff --git a/docs/SACRED_REMEDY_ENGINE.md b/docs/SACRED_REMEDY_ENGINE.md new file mode 100644 index 0000000..c85eb6b --- /dev/null +++ b/docs/SACRED_REMEDY_ENGINE.md @@ -0,0 +1,99 @@ +# Sacred Remedy Engine — núcleo offline medicinal + +Motor **paralelo** ao fluxo atual do Darshan. Não substitui `/api/darshan`; adiciona um endpoint e uma pilha dedicada ao “texto como remédio”. + +--- + +## 1. O que é + +- **Diagnóstico consciente:** klesha + samkhya guna + qualidades ayurvédicas (excesso/deficiência). +- **Corpus sagrado taggeado:** yoga_sutras, puranas, upanishads com `kleshaTargets` e `qualities`. +- **Seleção dirigida:** texto sagrado escolhido por klesha e qualidades (anti-repetição por `avoidIds`). +- **Matriz de remédios (30 estados):** cada estado mapeia klesha, samkhyaGuna, qualidades, prática, alimento, pergunta. + +--- + +## 2. Onde está + +| Peça | Caminho | +|------|---------| +| Tipos | `lib/sacredRemedy/types.ts` | +| Diagnosis Engine | `lib/sacredRemedy/diagnosisEngine.ts` | +| Sacred Selector | `lib/sacredRemedy/sacredSelector.ts` | +| Instant Light Composer | `lib/sacredRemedy/instantLightComposer.ts` | +| Yoga Sutras (taggeado) | `lib/dictionaries/sacred/yoga_sutras.json` | +| Puranas | `lib/dictionaries/sacred/puranas.json` | +| Upanishads | `lib/dictionaries/sacred/upanishads.json` | +| Matriz de remédios | `lib/dictionaries/remedyMatrix.json` | +| Endpoint | **GET** `/api/instant-light` | + +--- + +## 3. GET /api/instant-light + +- **Método:** GET (sem corpo; sem créditos; sem IA). +- **Query (opcional):** + `fullName`, `birthDate`, `birthTime`, `birthPlace`, `recentSacredIds`, `recentStateKeys`. +- **Com perfil (nome ou data):** `diagnosisPersonal(SymbolicMap)` + insight do mapa + prática + pergunta. +- **Sem perfil:** `diagnosisUniversal()` + texto sagrado dirigido + prática + pergunta. + +**Resposta:** + +```json +{ + "sacredText": "...", + "insight": "...", + "practice": "...", + "question": "...", + "sacredId": "yoga_sutras.YS.1.33", + "stateKey": "anxiety" +} +``` + +- `insight` só vem quando há perfil (personal). +- `sacredId` e `stateKey` para o cliente enviar em `recentSacredIds` / `recentStateKeys` e reduzir repetição. + +--- + +## 4. Fluxo do motor + +1. **Diagnóstico:** + - Com perfil → `diagnosisPersonal(profile)` (mapa → guna dominante → estado da matriz). + - Sem perfil → `diagnosisUniversal()` (estado por seed + recentStateKeys). +2. **Remédio:** `getRemedyForDiagnosis(diagnosis)` → prática, pergunta, referência sagrada (fallback). +3. **Texto sagrado:** `selectSacredText({ kleshaTargets, qualities, avoidIds, seed })` → entrada do corpus (yoga_sutras / puranas / upanishads). +4. **Composição:** sacredText (do selector ou verse da matriz) + insight (se personal) + practice + question. + +--- + +## 5. Formato do corpus sagrado + +Cada entrada em `yoga_sutras.json`, `puranas.json`, `upanishads.json`: + +```json +{ + "id": "YS.1.33", + "text": "Amizade, compaixão, alegria e equanimidade...", + "kleshaTargets": ["raga", "dvesha"], + "qualities": ["chala", "ruksha", "tikshna", "ushna", "khara"] +} +``` + +- **kleshaTargets:** kleśas que o texto ajuda a equilibrar. +- **qualities:** qualidades ayurvédicas associadas (para matching com o diagnóstico). + +--- + +## 6. Relação com o resto do produto + +- **`/api/darshan` (POST, mock/IA):** continua como está (IA + fallback; 7 fases; `getOfflineRevelation` etc.). **Não foi alterado.** +- **`/api/instant-light` (GET):** motor novo, só offline, só Sacred Remedy (diagnóstico + corpus taggeado + matriz + prática + pergunta). +- **Leitura offline narrativa (`readingOffline`, `oracleOffline`):** segue separada; o Instant Light do Sacred Remedy é outra camada (resposta estruturada: sacredText, insight, practice, question). + +--- + +## 7. Próximos passos (editorial) + +- Ampliar entradas em yoga_sutras, puranas, upanishads (mais versos + tags). +- Refinar mapeamento Nakshatra → tendência → klesha provável (para diagnóstico personal mais fino). +- Testes automatizados para `diagnosisEngine`, `sacredSelector`, `composeInstantLight` e GET `/api/instant-light`. diff --git a/docs/SWISS_EPHEMERIS_INTEGRATION.md b/docs/SWISS_EPHEMERIS_INTEGRATION.md new file mode 100644 index 0000000..7e05937 --- /dev/null +++ b/docs/SWISS_EPHEMERIS_INTEGRATION.md @@ -0,0 +1,69 @@ +# Integração Swiss Ephemeris (opcional) + +**Status:** Integração com **@fusionstrings/swiss-eph** implementada. O pacote está instalado e o provider em `lib/core/providers/swissProvider.ts` retorna planetas completos, casas (Placidus), ascendente e moonRashi/nakshatra (derivados da longitude lunar Lahiri). O WASM é carregado na primeira chamada a `buildSymbolicMap` (via `initSwissEphemeris()`); se o Swiss não estiver disponível ou falhar, o resolver usa **mhah-panchang** como fallback. + +--- + +## Implementação atual + +- **Pacote:** `@fusionstrings/swiss-eph` (WASM; Node/Next.js). +- **Inicialização:** `initSwissEphemeris()` é chamado automaticamente no início de `buildSymbolicMap(profile)` (async), garantindo que o Swiss esteja carregado antes do resolver. +- **Entrada:** `profile.birthDate`, `profile.birthTime`; lat/lon por enquanto padrão (0,0) — geocoding opcional no futuro. +- **Saída:** `AstronomicalCore` com `planets` (sun, moon, mercury, venus, mars, jupiter, saturn), `houses` (1–12), `ascendant`, `moonRashi`, `nakshatra` (chaves do dicionário). + +--- + +## Pacotes recomendados + +| Pacote | Uso | Observação | +|--------|-----|-------------| +| **@fusionstrings/panchangam** | Astrologia védica + Panchangam sobre Swiss Ephemeris (WASM) | Precisão alta; calendário védico (Tithi, Nakshatra). | +| **@fusionstrings/swiss-eph** | Swiss Ephemeris em WASM (Node, browser, edge) | 95+ funções; posições, casas, eclipses. | +| **swisseph** | Binding Node.js nativo para Swiss Ephemeris | Requer compilação nativa; não roda em browser. | + +Para **Next.js (Node + eventual browser)**, prefira **@fusionstrings/panchangam** (já védico) ou **@fusionstrings/swiss-eph** (WASM, sem nativo). + +--- + +## Passos para integrar + +1. **Instalar** (ex.: Panchangam): + ```bash + npm install @fusionstrings/panchangam + ``` + +2. **Consultar a API** do pacote (README no npm/GitHub) para: + - Entrada: data/hora/latitude/longitude (ou só data/hora se usar lugar padrão). + - Saída: posições planetárias (longitude), casas, ascendente, Nakshatra/Rashi se o pacote já devolver. + +3. **Implementar** `trySwissCompute` em `lib/core/providers/swissProvider.ts`: + - Converter `profile.birthDate` e `profile.birthTime` para o formato que o pacote exige. + - Opcional: usar `profile.birthPlace` para lat/lon (geocoding) ou valor padrão. + - Chamar o pacote e mapear o resultado para `AstronomicalCore`: + - `planets`: longitudes (ex.: `{ sun: 45.2, moon: 120.1, ... }`). + - `houses`: cúspides 1–12 se disponível. + - `ascendant`: longitude do ascendente. + - `moonRashi` / `nakshatra`: se o pacote já devolver em formato védico, mapear para as chaves usadas no Darshan (ex.: mesha, ashwini, …); senão, derivar das longitudes usando as regras em `lib/knowledge/vedic.ts` (graus → signo/nakshatra). + - Retornar `{ providerUsed: "swiss-ephemeris", ... }`. + - Em caso de erro (data inválida, pacote não instalado), retornar `null` para o resolver usar mhah-panchang. + +4. **Testar** com e sem o pacote instalado: sem ele, o resolver deve cair no mhah-panchang sem quebrar. + +--- + +## Mapeamento para AstronomicalCore + +O tipo em `lib/core/types.ts` espera: + +- `planets`: `Record` — longitude em graus (0–360) por planeta (ex.: `sun`, `moon`, `mars`, …). +- `houses`: `Record` — cúspide em graus por casa (ex.: `"1"` a `"12"`). +- `ascendant`: número — longitude do ascendente ( = cúspide da casa 1). +- `moonRashi` / `nakshatra`: strings — chaves do dicionário (mesha, ashwini, …) para compatibilidade com os engines e dicionários atuais. + +Se o pacote retornar Nakshatra/Rashi em outro formato (número, nome em inglês), mapear para as chaves usadas em `lib/core/providers/mhahProvider.ts` (INO_TO_RASHI, INO_TO_NAKSHATRA) ou para as chaves em `lib/knowledge/types.ts`. + +--- + +## Resolver + +O `ephemerisResolver` já tenta **SwissProvider** primeiro e usa **MhahProvider** em seguida. Nada precisa ser alterado no resolver ao ativar o Swiss Ephemeris; basta fazer `trySwissCompute` retornar um `AstronomicalCore` válido quando o pacote estiver instalado e a entrada for válida. diff --git a/docs/SYMBOLIC_MAP_ENGINE.md b/docs/SYMBOLIC_MAP_ENGINE.md new file mode 100644 index 0000000..a9ea46a --- /dev/null +++ b/docs/SYMBOLIC_MAP_ENGINE.md @@ -0,0 +1,132 @@ +# Symbolic Map Engine — Motor simbólico offline + +O Darshan possui **motor simbólico real**: mapa calculado (Jyotish + Numerologia + Human Design), readings modulares por tema e Narrative Composer offline. Tudo **sem IA**, determinístico e expansível. + +--- + +## Visão geral + +| Camada | O que é | Onde está | +|--------|---------|-----------| +| **A — Engines** | Cálculo estruturado (só dados) | `lib/engines/`, `lib/symbolic/`, `lib/core/` | +| **B — Readings** | Interpretação por tema | `lib/readings/`, `lib/insights/` | +| **C — Composer** | Frase humana a partir do mapa | `lib/narrative/`, `lib/dictionaries/` | + +- **SymbolicMap único** é a base; engines preenchem; readings consomem por tema. +- O usuário acessa por pergunta: "relacionamento" → módulo love, "trabalho" → career, "ano" → year, "visão geral" → general. + +--- + +## 1. SymbolicMap (objeto canônico) + +Dois fluxos coexistem (sem redundância de lógica): + +1. **`lib/symbolic/`** — Mapa canônico para **leitura offline** (getOfflineReading, /api/map, /api/reading): + - `types.ts` — `SymbolicMap`: jyotish (moonRashi, nakshatra, archetypeKey), numerology (rulingNumber), archetypes, themes, evidence. + - `builder.ts` — `buildSymbolicMap(profile)` síncrono: usa `computeVedicChartSimplified` (vedic + resolver) e `getRulingNumberFromName`. + +2. **`lib/engines/`** — Mapa com **core astronômico** (Swiss Ephemeris / mhah-panchang) para Oracle e APIs que precisam de planetas/casas: + - `buildSymbolicMap.ts` — `buildSymbolicMap(profile)` **async**: chama `computeAstronomicalCore`, depois `jyotishEngine`, `numerologyEngine`, `humanDesignEngine`. + - Retorna: `{ core, jyotish, numerology, humanDesign }`. + +**Uso recomendado:** +- **Leitura temática e APIs offline** → `lib/symbolic` (builder síncrono). +- **Oracle e integrações que usam planetas/casas** → `lib/engines` (builder async). + +--- + +## 2. Engines determinísticos + +### Jyotish (`lib/engines/jyotishEngine.ts`) + +- **jyotishEngine(core)** — Retorna: moonRashi, nakshatra, planets, houses, precisionLevel. +- **getNakshatraProfile(core)** — Perfil interpretativo: nakshatra, moonRashi, namePt, consciousnessThemes, psychologicalEffects, archetypeHints (para Composer/Readings). +- **detectYogas(core)** — Lista de yogas detectados (ex.: nakshatra-X, moon-in-Y, chandra-surya-same-sign). Expandir depois com Neecha Bhanga, etc. + +### Numerologia (`lib/engines/numerologyEngine.ts`) + +- Calcula número regente a partir do nome e data; usado pelo SymbolicMap e pelos insights de ação. + +### Human Design (`lib/engines/humanDesignEngine.ts`) + +- Stub plugável: type, authority, profile. Cálculo real (gates/channels) virá depois. + +### Core astronômico (`lib/core/`) + +- **ephemerisResolver** — Escolhe provider (Swiss Ephemeris ou mhah-panchang). +- **swissProvider** — Planetas, casas, ascendente; deriva moonRashi/nakshatra da longitude lunar (Lahiri). + +--- + +## 3. Interpretação modular (Readings) + +Cada reading consome o **mesmo mapa** e um **tema**: + +| Tema (API) | Tópico interno | Função | +|------------|----------------|--------| +| general | general | `getGeneral(map)` | +| love / relationship | love | `getLove(map)` | +| career / work | career | `getCareer(map)` | +| year / yearly | year | `getYear(map)` | +| action | action | `getAction(map)` | + +- **`lib/readings/symbolicReadings.ts`** — `getReadingByTheme(map, theme)` mapeia theme → topic e chama `composeReading(map, topic)`. +- **`lib/insights/`** — Regras determinísticas por sistema (Jyotish, numerologia, action); cada insight tem key, topic, weight, evidence. +- **`lib/narrative/composer.ts`** — `composeReading(map, topic)`: coleta insights do mapa, filtra por topic, ordena por peso, top 3, `phraseFor(key)` → texto. + +Nada de frase solta: tudo derivado do mapa e dos dicionários. + +--- + +## 4. Narrative Composer e dicionários + +- **composeReading(map, topic)** — Entrada principal: SymbolicMap + tópico → texto da leitura. +- **phraseFor(key)** — Resolve frase por chave (action.n.X → action.X; action.archetype.X com fallback). +- **lib/narrative/dictionaryLoader.ts** — `getMergedDictionaries()`: agrega Jyotish, HD, Action, Numerologia, PHRASES_FOR_SYMBOLIC. +- **lib/narrative/phraseTemplates.ts** — `getPhraseForInsight(key)`, `getPhraseForContext(ctx)` para chamadas que já têm placements/theme. + +Dicionários em código: `lib/dictionaries/` (jyotish, humanDesign, action, numerology, phrasesForSymbolic). Expansão futura: JSON em /dictionaries se desejado. + +--- + +## 5. APIs offline + +| Endpoint | Uso | Retorno | +|---------|-----|---------| +| **POST /api/map** | Mapa completo (sem IA, sem créditos) | `{ map: SymbolicMap }` | +| **POST /api/reading?theme=** | Leitura temática (general \| love \| relationship \| career \| work \| year \| yearly \| action) | `{ map, reading, theme }` | +| **POST /api/map/personal** | Mapa pessoal + leitura (IA ou offline conforme parâmetro) | Ver doc do endpoint | + +Body em ambos: `{ profile: { fullName?, birthDate?, birthTime?, birthPlace? } }`. + +--- + +## 6. Onde está cada peça + +| Peça | Arquivo / pasta | +|------|------------------| +| SymbolicMap (canônico) | `lib/symbolic/types.ts`, `builder.ts` | +| SymbolicMap (com core) | `lib/engines/buildSymbolicMap.ts` | +| Jyotish calculate / getNakshatraProfile / detectYogas | `lib/engines/jyotishEngine.ts` | +| Numerologia | `lib/engines/numerologyEngine.ts` | +| Human Design (stub) | `lib/engines/humanDesignEngine.ts` | +| General / Love / Career / Year / Action readings | `lib/readings/symbolicReadings.ts` | +| Leitura por tema (API) | `getReadingByTheme(map, theme)` em symbolicReadings | +| Composer | `lib/narrative/composer.ts` | +| Dictionary loader | `lib/narrative/dictionaryLoader.ts` | +| Phrase templates | `lib/narrative/phraseTemplates.ts` | +| Dicionários | `lib/dictionaries/*.ts` | +| getOfflineReading (wrapper) | `lib/readingOffline.ts` | +| POST /api/map | `app/api/map/route.ts` | +| POST /api/reading?theme= | `app/api/reading/route.ts` | + +--- + +## 7. Fluxo resumido + +1. **Mapa:** `buildSymbolicMap(profile)` (symbolic ou engines) → SymbolicMap. +2. **Insights:** `collectInsightsForSymbolicMap(map)` → lista de Insight (key, topic, weight, evidence). +3. **Leitura por tema:** `composeReading(map, topic)` → filtra insights por topic, top 3, phraseFor(key) → texto. +4. **API:** POST /api/map retorna o mapa; POST /api/reading?theme=X retorna mapa + reading para o tema. + +Sem aleatoriedade solta; sem duplicar conteúdo entre temas; expansão por novos engines e dicionários. diff --git a/lib/core/ephemerisProvider.ts b/lib/core/ephemerisProvider.ts new file mode 100644 index 0000000..1a52dd0 --- /dev/null +++ b/lib/core/ephemerisProvider.ts @@ -0,0 +1,11 @@ +/** + * Interface de um provider de efemérides (mhah-panchang, Swiss Ephemeris, etc.). + * Cada provider expõe name e compute(profile) → AstronomicalCore. + */ + +import type { AstronomicalCore, CoreProfile } from "./types"; + +export interface EphemerisProvider { + name: string; + compute(profile: CoreProfile): AstronomicalCore; +} diff --git a/lib/core/ephemerisResolver.ts b/lib/core/ephemerisResolver.ts new file mode 100644 index 0000000..3f95fc0 --- /dev/null +++ b/lib/core/ephemerisResolver.ts @@ -0,0 +1,26 @@ +/** + * Resolver multi-provider: Swiss Ephemeris (prioridade) → mhah-panchang (fallback). + * Nada quebra: se Swiss falhar ou não estiver disponível, usa mhah. + */ + +import type { AstronomicalCore, CoreProfile } from "./types"; +import type { EphemerisProvider } from "./ephemerisProvider"; +import { SwissProvider } from "./providers/swissProvider"; +import { MhahProvider } from "./providers/mhahProvider"; + +const PROVIDERS: EphemerisProvider[] = [SwissProvider, MhahProvider]; + +/** + * Calcula o núcleo astronômico com o primeiro provider que conseguir. + * Ordem: Swiss Ephemeris → mhah-panchang. + */ +export function computeAstronomicalCore(profile: CoreProfile): AstronomicalCore { + for (const provider of PROVIDERS) { + try { + return provider.compute(profile); + } catch { + continue; + } + } + throw new Error("No ephemeris provider available"); +} diff --git a/lib/core/providers/mhahProvider.ts b/lib/core/providers/mhahProvider.ts new file mode 100644 index 0000000..322fa81 --- /dev/null +++ b/lib/core/providers/mhahProvider.ts @@ -0,0 +1,59 @@ +/** + * Provider mhah-panchang — NÃO remover; fallback oficial do Darshan. + * Retorna Lua (Rashi + Nakshatra); planetas/casas ficam para Swiss Ephemeris. + */ + +import type { EphemerisProvider } from "../ephemerisProvider"; +import type { AstronomicalCore, CoreProfile } from "../types"; + +const INO_TO_RASHI: string[] = [ + "mesha", "vrishabha", "mithuna", "karka", "simha", "kanya", + "tula", "vrischika", "dhanu", "makara", "kumbha", "mina", +]; + +const INO_TO_NAKSHATRA: string[] = [ + "ashwini", "bharani", "krittika", "rohini", "mrigashira", "ardra", "punarvasu", + "pushya", "ashlesha", "magha", "purva-phalguni", "uttara-phalguni", "hasta", + "chitra", "swati", "vishakha", "anuradha", "jyestha", "mula", "purva-ashadha", + "uttara-ashadha", "shravana", "dhanishta", "shatabhisha", "purva-bhadra", + "uttara-bhadra", "revati", +]; + +export const MhahProvider: EphemerisProvider = { + name: "mhah-panchang", + + compute(profile: CoreProfile): AstronomicalCore { + const birthDate = profile.birthDate?.trim() ?? ""; + if (!birthDate) { + return { providerUsed: "mhah-panchang" }; + } + const birthTime = profile.birthTime?.trim() ?? "12:00"; + const dateStr = birthTime ? `${birthDate}T${birthTime}:00` : `${birthDate}T12:00:00`; + const d = new Date(dateStr); + if (Number.isNaN(d.getTime())) { + return { providerUsed: "mhah-panchang" }; + } + + const { MhahPanchang } = require("mhah-panchang"); + const obj = new MhahPanchang(); + const result = obj.calculate(d); + + const raasi = result?.Raasi; + const nakshatra = result?.Nakshatra; + + const moonRashi = + raasi?.ino != null + ? INO_TO_RASHI[(raasi.ino - 1) % 12] + : undefined; + const nakshatraKey = + nakshatra?.ino != null + ? INO_TO_NAKSHATRA[(nakshatra.ino - 1) % 27] + : undefined; + + return { + providerUsed: "mhah-panchang", + moonRashi, + nakshatra: nakshatraKey, + }; + }, +}; diff --git a/lib/core/providers/swissProvider.ts b/lib/core/providers/swissProvider.ts new file mode 100644 index 0000000..aa62556 --- /dev/null +++ b/lib/core/providers/swissProvider.ts @@ -0,0 +1,224 @@ +/** + * Provider Swiss Ephemeris (premium) — @fusionstrings/swiss-eph (WASM). + * Retorna planetas completos, casas e ascendente; moonRashi/nakshatra derivados da longitude lunar (Lahiri). + * Se falhar ou não estiver instalado, o resolver usa mhah-panchang como fallback. + */ + +import type { EphemerisProvider } from "../ephemerisProvider"; +import type { AstronomicalCore, CoreProfile } from "../types"; + +/** Chaves Rashi (signo) por índice 0–11 — mesma ordem que mhahProvider */ +const INO_TO_RASHI: string[] = [ + "mesha", "vrishabha", "mithuna", "karka", "simha", "kanya", + "tula", "vrischika", "dhanu", "makara", "kumbha", "mina", +]; + +/** Chaves Nakshatra por índice 0–26 — mesma ordem que mhahProvider */ +const INO_TO_NAKSHATRA: string[] = [ + "ashwini", "bharani", "krittika", "rohini", "mrigashira", "ardra", "punarvasu", + "pushya", "ashlesha", "magha", "purva-phalguni", "uttara-phalguni", "hasta", + "chitra", "swati", "vishakha", "anuradha", "jyestha", "mula", "purva-ashadha", + "uttara-ashadha", "shravana", "dhanishta", "shatabhisha", "purva-bhadra", + "uttara-bhadra", "revati", +]; + +/** Longitude (0–360) → Rashi key (sidereal; longitude já em Lahiri se usar SEFLG_SIDEREAL) */ +function longitudeToRashi(longitudeDeg: number): string { + const L = longitudeDeg % 360; + const index = Math.floor(L / 30) % 12; + return INO_TO_RASHI[index] ?? "mesha"; +} + +/** Longitude (0–360) → Nakshatra key */ +function longitudeToNakshatra(longitudeDeg: number): string { + const L = longitudeDeg % 360; + const index = Math.floor(L / (360 / 27)) % 27; + return INO_TO_NAKSHATRA[index] ?? "ashwini"; +} + +/** Mapa planeta SE_* → chave para AstronomicalCore.planets */ +const PLANET_KEYS: Record = { + 0: "sun", // SE_SUN + 1: "moon", // SE_MOON + 2: "mercury",// SE_MERCURY + 3: "venus", // SE_VENUS + 4: "mars", // SE_MARS + 5: "jupiter",// SE_JUPITER + 6: "saturn", // SE_SATURN +}; + +/** Lat/lon padrão quando birthPlace não fornece (ex.: 0,0 ou São Paulo) */ +const DEFAULT_LAT = 0; +const DEFAULT_LON = 0; + +/** Cache do módulo WASM carregado (lazy). Preencher com initSwissEphemeris() para usar Swiss. */ +let ephCache: { eph: { swe_julday: (y: number, m: number, d: number, h: number, g: number) => number; + swe_calc_ut: (jd: number, ipl: number, iflag: number) => { returnCode: number; xx: Float64Array; error: string }; + swe_houses: (jd: number, lat: number, lon: number, hsys: number) => { cusps: Float64Array; ascmc: Float64Array; returnCode: number }; + swe_set_sid_mode: (mode: number, t0: number, ayan_t0: number) => void }; + Constants: { SE_GREG_CAL: number; SE_SUN: number; SE_MOON: number; SE_MERCURY: number; SE_VENUS: number; SE_MARS: number; SE_JUPITER: number; SE_SATURN: number; SEFLG_SPEED: number; SEFLG_SIDEREAL?: number; SE_SIDM_LAHIRI: number }; +} | null = null; + +async function loadSwissEph(): Promise> { + const mod = await import("@fusionstrings/swiss-eph"); + const eph = await mod.load(); + const Constants = mod.Constants; + if (typeof eph.swe_set_sid_mode === "function" && Constants.SE_SIDM_LAHIRI != null) { + eph.swe_set_sid_mode(Constants.SE_SIDM_LAHIRI, 0, 0); + } + return { eph, Constants }; +} + +function parseProfileDateTime(profile: CoreProfile): { year: number; month: number; day: number; hourDecimal: number } | null { + const birthDate = profile.birthDate?.trim(); + if (!birthDate) return null; + const birthTime = (profile.birthTime?.trim() ?? "12:00").slice(0, 8); + const [h, m, s] = birthTime.split(":").map((x) => (x ? parseFloat(x) : 0)); + const dateStr = birthTime ? `${birthDate}T${birthTime}` : `${birthDate}T12:00`; + const d = new Date(dateStr); + if (Number.isNaN(d.getTime())) return null; + const hourDecimal = (d.getUTCHours() ?? 0) + ((d.getUTCMinutes() ?? 0) / 60) + ((d.getUTCSeconds() ?? 0) / 3600); + return { + year: d.getUTCFullYear(), + month: d.getUTCMonth() + 1, + day: d.getUTCDate(), + hourDecimal, + }; +} + +/** Obtém lat/lon do perfil; por enquanto usa valor padrão (geocoding futuro). */ +function getLatLon(_profile: CoreProfile): { lat: number; lon: number } { + return { lat: DEFAULT_LAT, lon: DEFAULT_LON }; +} + +/** + * Calcula o núcleo astronômico com Swiss Ephemeris (async). + * Retorna null se o pacote não estiver disponível, dados inválidos ou erro. + */ +export async function trySwissCompute(profile: CoreProfile): Promise { + try { + const dt = parseProfileDateTime(profile); + if (!dt) return null; + + if (!ephCache) { + ephCache = await loadSwissEph(); + } + const { eph, Constants } = ephCache; + + const jd = eph.swe_julday(dt.year, dt.month, dt.day, dt.hourDecimal, Constants.SE_GREG_CAL); + const flag = (Constants.SEFLG_SPEED ?? 256) | (Constants.SEFLG_SIDEREAL ?? 0); + + const planets: Record = {}; + for (const [seId, key] of Object.entries(PLANET_KEYS)) { + const res = eph.swe_calc_ut(jd, Number(seId), flag); + if (res.returnCode >= 0 && res.xx && res.xx[0] != null) { + planets[key] = res.xx[0] % 360; + } + } + + const moonLong = planets.moon != null ? planets.moon : undefined; + const moonRashi = moonLong != null ? longitudeToRashi(moonLong) : undefined; + const nakshatra = moonLong != null ? longitudeToNakshatra(moonLong) : undefined; + + const { lat, lon } = getLatLon(profile); + const hsys = "P".charCodeAt(0); + const housesRes = eph.swe_houses(jd, lat, lon, hsys); + const houses: Record = {}; + if (housesRes.cusps && housesRes.returnCode >= 0) { + for (let i = 1; i <= 12; i++) { + const c = housesRes.cusps[i]; + if (typeof c === "number" && !Number.isNaN(c)) { + houses[String(i)] = c % 360; + } + } + } + const ascendant = housesRes.ascmc && housesRes.ascmc[0] != null + ? (housesRes.ascmc[0] % 360) + : (houses["1"]); + + return { + providerUsed: "swiss-ephemeris", + julianDay: jd, + planets: Object.keys(planets).length ? planets : undefined, + moonRashi, + nakshatra, + ascendant, + houses: Object.keys(houses).length ? houses : undefined, + }; + } catch { + return null; + } +} + +export const SwissProvider: EphemerisProvider = { + name: "swiss-ephemeris", + + compute(profile: CoreProfile): AstronomicalCore { + const result = trySwissComputeSync(profile); + if (result) return result; + throw new Error("Swiss Ephemeris not available"); + }, +}; + +/** + * Versão síncrona: só retorna resultado se o WASM já estiver em cache. + * Caso contrário retorna null para o resolver cair no mhah-panchang. + */ +function trySwissComputeSync(profile: CoreProfile): AstronomicalCore | null { + if (ephCache === null) return null; + try { + const dt = parseProfileDateTime(profile); + if (!dt) return null; + const { eph, Constants } = ephCache; + const jd = eph.swe_julday(dt.year, dt.month, dt.day, dt.hourDecimal, Constants.SE_GREG_CAL); + const flag = (Constants.SEFLG_SPEED ?? 256) | (Constants.SEFLG_SIDEREAL ?? 0); + + const planets: Record = {}; + for (const [seId, key] of Object.entries(PLANET_KEYS)) { + const res = eph.swe_calc_ut(jd, Number(seId), flag); + if (res.returnCode >= 0 && res.xx && res.xx[0] != null) { + planets[key] = res.xx[0] % 360; + } + } + const moonLong = planets.moon; + const moonRashi = moonLong != null ? longitudeToRashi(moonLong) : undefined; + const nakshatra = moonLong != null ? longitudeToNakshatra(moonLong) : undefined; + + const { lat, lon } = getLatLon(profile); + const hsys = "P".charCodeAt(0); + const housesRes = eph.swe_houses(jd, lat, lon, hsys); + const houses: Record = {}; + if (housesRes.cusps && housesRes.returnCode >= 0) { + for (let i = 1; i <= 12; i++) { + const c = housesRes.cusps[i]; + if (typeof c === "number" && !Number.isNaN(c)) { + houses[String(i)] = c % 360; + } + } + } + const ascendant = housesRes.ascmc && housesRes.ascmc[0] != null + ? (housesRes.ascmc[0] % 360) + : (houses["1"]); + + return { + providerUsed: "swiss-ephemeris", + julianDay: jd, + planets: Object.keys(planets).length ? planets : undefined, + moonRashi, + nakshatra, + ascendant, + houses: Object.keys(houses).length ? houses : undefined, + }; + } catch { + return null; + } +} + +/** + * Inicializa o Swiss Ephemeris (carrega WASM). Chamar uma vez no startup ou antes do primeiro uso + * para que o resolver use Swiss em vez de mhah-panchang nas próximas chamadas síncronas. + */ +export async function initSwissEphemeris(): Promise { + if (ephCache) return; + ephCache = await loadSwissEph(); +} diff --git a/lib/core/types.ts b/lib/core/types.ts new file mode 100644 index 0000000..e2b7816 --- /dev/null +++ b/lib/core/types.ts @@ -0,0 +1,29 @@ +/** + * Núcleo astronômico — resultado de um provider de efemérides. + * Usado por todos os engines (Jyotish, Human Design, etc.) sem depender de IA. + */ + +export type AstronomicalCore = { + /** Provider que gerou os dados (ex.: "mhah-panchang", "swiss-ephemeris") */ + providerUsed: string; + /** Dia juliano (opcional; Swiss Ephemeris) */ + julianDay?: number; + /** Longitudes planetárias em graus (0–360) — chave = planeta (sun, moon, ...) */ + planets?: Record; + /** Signo lunar sidereal (Rashi) — chave do dicionário (ex.: "karka", "revati") */ + moonRashi?: string; + /** Nakshatra lunar — chave (ex.: "ashwini", "rohini") */ + nakshatra?: string; + /** Ascendente em graus (opcional) */ + ascendant?: number; + /** Casas em graus (1–12) — chave = número da casa */ + houses?: Record; +}; + +/** Perfil mínimo para cálculo (data e opcionalmente hora/local) */ +export type CoreProfile = { + birthDate?: string; + birthTime?: string; + birthPlace?: string; + fullName?: string; +}; diff --git a/lib/diagnosis/diagnosisEngine.ts b/lib/diagnosis/diagnosisEngine.ts new file mode 100644 index 0000000..a2ef9f0 --- /dev/null +++ b/lib/diagnosis/diagnosisEngine.ts @@ -0,0 +1,106 @@ +/** + * Diagnosis Engine — detecta estado dominante via SymbolicMap + heurísticas e seleciona + * entrada da matriz de remédios (Sacred Remedy Matrix). Nunca aleatório puro; usa cooldown/histórico. + */ + +import { buildSymbolicMap } from "@/lib/symbolic/builder"; +import { ARCHETYPE_TO_GUNA } from "@/lib/knowledge/classicTexts"; +import type { UserProfileForOracle } from "@/lib/knowledge/types"; +import type { SymbolicMap } from "@/lib/symbolic/types"; +import type { RemedyMatrixEntry, SamkhyaGuna, ConsciousDiagnosis, PrakritiFromJyotish } from "./types"; + +import remedyMatrixJson from "@/lib/dictionaries/remedyMatrix.json"; + +const REMEDY_MATRIX: RemedyMatrixEntry[] = remedyMatrixJson as RemedyMatrixEntry[]; + +/** Rashi → tendência dosha (simplificado para prakriti simbólica) */ +const RASHI_TO_DOSHA: Record = { + mesha: "pitta", vrishabha: "kapha", mithuna: "vata", karka: "kapha", + simha: "pitta", kanya: "vata", tula: "vata", vrischika: "pitta", + dhanu: "pitta", makara: "kapha", kumbha: "vata", mina: "kapha", +}; + +const RASHI_TO_ELEMENT: Record = { + mesha: "fire", vrishabha: "earth", mithuna: "air", karka: "water", + simha: "fire", kanya: "earth", tula: "air", vrischika: "water", + dhanu: "fire", makara: "earth", kumbha: "air", mina: "water", +}; + +export function getRemedyMatrix(): RemedyMatrixEntry[] { + return REMEDY_MATRIX; +} + +/** Deriva prakriti simbólica (dosha/elemento) do mapa Jyotish */ +export function getPrakritiFromMap(map: SymbolicMap): PrakritiFromJyotish { + const rashi = map.jyotish?.moonRashi ?? "mesha"; + return { + dosha: RASHI_TO_DOSHA[rashi] ?? "vata", + element: RASHI_TO_ELEMENT[rashi] ?? "air", + }; +} + +/** Dominante Sāṃkhya do mapa (arquétipo → guna) */ +export function getDominantSamkhyaGuna(map: SymbolicMap): SamkhyaGuna { + const primary = map.archetypes?.primary ?? map.jyotish?.archetypeKey ?? "dissolvente"; + const guna = ARCHETYPE_TO_GUNA[primary]; + return (guna ?? "tamas") as SamkhyaGuna; +} + +/** + * Monta diagnóstico consciente a partir do mapa e da entrada de remédio selecionada. + */ +export function buildDiagnosisFromMapAndRemedy( + map: SymbolicMap, + remedy: RemedyMatrixEntry +): ConsciousDiagnosis { + const prakriti = getPrakritiFromMap(map); + const dominant = getDominantSamkhyaGuna(map); + const sattva = dominant === "sattva" ? 0.6 : 0.2; + const rajas = dominant === "rajas" ? 0.6 : 0.2; + const tamas = dominant === "tamas" ? 0.6 : 0.2; + return { + klesha: remedy.klesha, + samkhyaGunas: { sattva, rajas, tamas }, + ayurvedicQualities: { + excess: remedy.qualities ?? [], + deficient: [], + }, + prakritiFromJyotish: prakriti, + }; +} + +export type SelectRemedyOptions = { + /** Seed para escolha determinística */ + seed?: number; + /** IDs de textos sagrados recentes (evitar repetição) */ + recentSacredIds?: string[]; +}; + +/** + * Seleciona uma entrada da matriz de remédios. + * Com perfil: filtra por guna dominante do mapa e evita recentSacredIds; escolha por seed. + * Sem perfil: escolhe entre todas por seed, evitando recentSacredIds. + */ +export function selectRemedy( + profile: UserProfileForOracle | undefined, + options: SelectRemedyOptions = {} +): RemedyMatrixEntry { + const { seed = 0, recentSacredIds = [] } = options; + const avoidSet = new Set(recentSacredIds); + + let pool = REMEDY_MATRIX; + if (profile?.birthDate || profile?.fullName) { + const map = buildSymbolicMap(profile as UserProfileForOracle); + const dominantGuna = getDominantSamkhyaGuna(map); + const byGuna = REMEDY_MATRIX.filter((e) => e.samkhyaGuna === dominantGuna); + if (byGuna.length > 0) pool = byGuna; + } + + const preferred = pool.filter((e) => { + const sacredId = e.sacred?.id ?? e.state; + return !avoidSet.has(sacredId); + }); + const candidates = preferred.length > 0 ? preferred : pool; + const idx = Math.abs(Math.floor(seed)) % candidates.length; + return candidates[idx] ?? candidates[0]; +} diff --git a/lib/diagnosis/index.ts b/lib/diagnosis/index.ts new file mode 100644 index 0000000..26f6000 --- /dev/null +++ b/lib/diagnosis/index.ts @@ -0,0 +1,23 @@ +/** + * Diagnosis Engine — diagnóstico consciente e matriz de remédios (Sacred Remedy Matrix). + * Camada 1: Prakṛti (Jyotish). Camada 2: Sāṃkhya Guṇas. Camada 3: Ayurvedic Qualities. + */ + +export { + getRemedyMatrix, + getPrakritiFromMap, + getDominantSamkhyaGuna, + buildDiagnosisFromMapAndRemedy, + selectRemedy, +} from "./diagnosisEngine"; +export type { SelectRemedyOptions } from "./diagnosisEngine"; +export type { + SamkhyaGuna, + AyurvedicQuality, + KleshaKey, + PrakritiFromJyotish, + SamkhyaGunas, + ConsciousDiagnosis, + SacredRef, + RemedyMatrixEntry, +} from "./types"; diff --git a/lib/diagnosis/types.ts b/lib/diagnosis/types.ts new file mode 100644 index 0000000..2a870e3 --- /dev/null +++ b/lib/diagnosis/types.ts @@ -0,0 +1,71 @@ +/** + * Tipos do diagnóstico consciente e da matriz de remédios sagrados (Darshan high-end). + * Camada 1: Prakṛti (Jyotish). Camada 2: Sāṃkhya Guṇas (3). Camada 3: Ayurvedic Qualities (20). + */ + +/** Guṇas Sāṃkhya — estado global da consciência (macro): clareza, movimento, inércia */ +export type SamkhyaGuna = "sattva" | "rajas" | "tamas"; + +/** As 20 qualidades ayurvédicas (fenomenológicas): mente, corpo, alimento, clima, emoções */ +export type AyurvedicQuality = + | "guru" | "laghu" + | "snigdha" | "ruksha" + | "sita" | "ushna" + | "manda" | "tikshna" + | "sthira" | "chala" + | "mridu" | "kathina" + | "vishada" | "picchila" + | "shlakshna" | "khara" + | "sukshma" | "sthula" + | "sandra" | "drava" + | "sara"; + +/** Kleśas — obstáculos da mente (Yoga Sutras) */ +export type KleshaKey = "avidya" | "asmita" | "raga" | "dvesha" | "abhinivesha" | null; + +/** Prakṛti simbólica a partir do Jyotish (Lua + Nakshatra → dosha/elemento) */ +export type PrakritiFromJyotish = { + dosha?: string; // "vata" | "pitta" | "kapha" | "vata-pitta" etc. + element?: string; // "earth" | "water" | "fire" | "air" | "ether" +}; + +/** Estado macro Sāṃkhya (3 guṇas) — valores relativos 0–1 ou proporção */ +export type SamkhyaGunas = { + sattva: number; + rajas: number; + tamas: number; +}; + +/** Diagnóstico consciente completo — entrada do Sacred Remedy Engine */ +export type ConsciousDiagnosis = { + /** Kleśa dominante no momento */ + klesha: KleshaKey; + /** Estado macro (céu interno) */ + samkhyaGunas: SamkhyaGunas; + /** Qualidades em desequilíbrio (micro) — chaves das 20 qualidades ayurvédicas */ + ayurvedicQualities: { + excess: AyurvedicQuality[] | string[]; + deficient: AyurvedicQuality[] | string[]; + }; + /** Constituição simbólica do mapa (base da pessoa) */ + prakritiFromJyotish?: PrakritiFromJyotish; +}; + +/** Referência a texto sagrado (corpus + id para resolução ou verso embutido) */ +export type SacredRef = { + corpus: string; + id: string; + verse?: string; +}; + +/** Uma entrada da matriz de remédios (30 estados) */ +export type RemedyMatrixEntry = { + state: string; + klesha: KleshaKey; + samkhyaGuna: SamkhyaGuna; + qualities: string[]; + sacred: SacredRef; + practice: string; + food: string; + question: string; +}; diff --git a/lib/dictionaries/action.ts b/lib/dictionaries/action.ts new file mode 100644 index 0000000..bb6a2a4 --- /dev/null +++ b/lib/dictionaries/action.ts @@ -0,0 +1,21 @@ +/** + * Frases de prática concreta (Action Engine) — toda leitura deve terminar com ação. + */ + +export const ACTION_PHRASES: Record = { + "action.grounding.breath": [ + "Prática mínima: respire lentamente 7 vezes antes de tomar qualquer decisão hoje.", + ], + "action.presence.body": [ + "Prática: por 1 minuto, sinta os pés no chão e o ar entrando e saindo; depois escolha o próximo passo.", + ], + "action.general.pause": [ + "Prática: antes de responder a alguém, faça uma pausa de três respirações.", + ], + "action.love.self": [ + "Prática: hoje, uma ação concreta de cuidado consigo mesmo (um gesto que te nutra).", + ], + "action.career.clarity": [ + "Prática: escreva em uma linha o que você quer que o seu trabalho gere no mundo; releia ao acordar.", + ], +}; diff --git a/lib/dictionaries/humanDesign.ts b/lib/dictionaries/humanDesign.ts new file mode 100644 index 0000000..3824f7f --- /dev/null +++ b/lib/dictionaries/humanDesign.ts @@ -0,0 +1,24 @@ +/** + * Frases ancoradas por chave Human Design — dicionário offline. + */ + +export const HD_PHRASES: Record = { + "hd.type.generator": [ + "Sua energia floresce quando você responde ao que a vida traz, não quando força caminhos.", + ], + "hd.type.manifestor": [ + "Você foi feito para iniciar; informar antes de agir reduz resistência e traz paz.", + ], + "hd.type.projector": [ + "Seu dom é ver o outro; esperar o convite evita amargura e traz reconhecimento.", + ], + "hd.type.reflector": [ + "Você reflete o ambiente; dar-se tempo (e a Lua) para decidir é sua sabedoria.", + ], + "hd.authority.emotional": [ + "Decida apenas após atravessar a onda emocional; a clareza vem com o tempo.", + ], + "hd.authority.sacral": [ + "Seu corpo sabe; respostas sim ou não vêm do corpo.", + ], +}; diff --git a/lib/dictionaries/index.ts b/lib/dictionaries/index.ts new file mode 100644 index 0000000..a03df6a --- /dev/null +++ b/lib/dictionaries/index.ts @@ -0,0 +1,37 @@ +/** + * Resolve frase por chave de insight — consulta Jyotish, HD, Action e mapa simbólico. + * Retorna a primeira frase do array (determinístico). + */ + +import { JYOTISH_PHRASES } from "./jyotish"; +import { HD_PHRASES } from "./humanDesign"; +import { ACTION_PHRASES } from "./action"; +import { NUMEROLOGY_PHRASES } from "./numerology"; +import { PHRASES_FOR_SYMBOLIC } from "./phrasesForSymbolic"; + +const ALL: Record = { + ...JYOTISH_PHRASES, + ...HD_PHRASES, + ...ACTION_PHRASES, + ...NUMEROLOGY_PHRASES, + ...PHRASES_FOR_SYMBOLIC, +}; + +export function phraseFor(key: string): string { + let lookupKey = key; + if (key.startsWith("action.n.")) { + const num = key.slice("action.n.".length); + lookupKey = `action.${num}`; + } + // action.archetype.X: fallback para action.general se arquétipo sem frase + if (key.startsWith("action.archetype.")) { + const phrases = ALL[key]; + if (phrases && phrases.length > 0) return phrases[0]; + return (ALL["action.general"] as string[])?.[0] ?? ""; + } + const phrases = ALL[lookupKey] ?? ALL[key]; + if (phrases && phrases.length > 0) return phrases[0]; + return ""; +} + +export { JYOTISH_PHRASES, HD_PHRASES, ACTION_PHRASES, NUMEROLOGY_PHRASES }; diff --git a/lib/dictionaries/jyotish.ts b/lib/dictionaries/jyotish.ts new file mode 100644 index 0000000..8070a2d --- /dev/null +++ b/lib/dictionaries/jyotish.ts @@ -0,0 +1,213 @@ +/** + * Frases ancoradas por chave Jyotish — dicionário offline. + */ + +export const JYOTISH_PHRASES: Record = { + "jyotish.nakshatra.revati.spiritualTheme": [ + "Há em você um chamado profundo para caminhar com suavidade e sentido espiritual.", + ], + "jyotish.nakshatra.revati.general": [ + "A estação lunar Revati traz suavidade e conclusão de ciclos; o que termina abre espaço para o novo.", + ], + "jyotish.nakshatra.rohini.general": [ + "Rohini traz fertilidade e beleza ao que você cultiva; a paciência é sua aliada.", + ], + "jyotish.nakshatra.ashwini.general": [ + "Ashwini traz impulso de novo começo; a cura começa quando você aceita o que já está aqui.", + ], + "jyotish.rashi.karka.general": [ + "Sua Lua em Câncer fala de cuidado e raiz emocional; proteger e ser protegido são dois lados do mesmo gesto.", + ], + "jyotish.rashi.karka.love": [ + "No amor, sua natureza lunar pede segurança e profundidade; a entrega nasce da confiança.", + ], + "jyotish.rashi.karka.career": [ + "No trabalho, sua Lua em Câncer floresce em ambientes que honram o humano e o sensível.", + ], + "jyotish.rashi.mesha.general": [ + "Sua Lua em Áries traz coragem e impulso; a sabedoria está em saber quando pausar.", + ], + "jyotish.rashi.mesha.love": [ + "No amor, a iniciativa e a autenticidade são seus trunfos; a paciência complementa.", + ], + "jyotish.rashi.mesha.career": [ + "No trabalho, liderar e iniciar vêm naturalmente; o equilíbrio está em escutar antes de agir.", + ], + "jyotish.rashi.mina.general": [ + "Sua Lua em Peixes traz sensibilidade e conexão com o invisível; os limites são o seu desafio e a compaixão o seu dom.", + ], + "jyotish.rashi.mina.love": [ + "No amor, a entrega e a fusão são naturais; cuidar de si é o primeiro ato de amor.", + ], + "jyotish.rashi.mina.career": [ + "No trabalho, a intuição e o serviço ao outro florescem; ancorar no corpo evita dissipação.", + ], + "jyotish.rashi.karka.year": [ + "Este ano, sua Lua em Câncer pede que você cultive raiz e cuidado; o que você nutre nutre você.", + ], + "jyotish.rashi.mesha.year": [ + "Este ano, impulso e novo começo estão no ar; escolha um rumo e dê o primeiro passo.", + ], + "jyotish.rashi.mina.year": [ + "Este ano, a sensibilidade e a conexão com o invisível se ampliam; proteja seus limites para não dissipar.", + ], + "jyotish.nakshatra.revati.year": [ + "O ciclo Revati traz conclusão e suavidade; o que termina abre espaço para o próximo ciclo.", + ], + "jyotish.nakshatra.rohini.year": [ + "Rohini no ano favorece fertilidade e beleza no que você cultiva; paciência e constância.", + ], + "jyotish.nakshatra.ashwini.year": [ + "Ashwini no ano traz impulso de cura e novo começo; aceite o que já está aqui.", + ], + "jyotish.rashi.vrishabha.general": [ + "Sua Lua em Touro fala de raiz e beleza; a estabilidade que você busca nasce da paciência.", + ], + "jyotish.rashi.vrishabha.love": [ + "No amor, segurança e sensorialidade são naturais; a leveza complementa.", + ], + "jyotish.rashi.vrishabha.career": [ + "No trabalho, constância e beleza florescem; evite a rigidez.", + ], + "jyotish.rashi.vrishabha.year": [ + "Este ano, cultive raiz e paciência; o que você planta com calma colhe em paz.", + ], + "jyotish.rashi.mithuna.general": [ + "Sua Lua em Gêmeos traz comunicação e curiosidade; a profundidade é o complemento.", + ], + "jyotish.rashi.mithuna.love": [ + "No amor, a troca e a leveza são seus trunfos; o silêncio a dois equilibra.", + ], + "jyotish.rashi.mithuna.career": [ + "No trabalho, versatilidade e comunicação florescem; ancorar em um projeto evita dispersão.", + ], + "jyotish.rashi.mithuna.year": [ + "Este ano, conexão e aprendizado se ampliam; escolha um fio e aprofunde.", + ], + "jyotish.rashi.simha.general": [ + "Sua Lua em Leão traz centro e reconhecimento; a humildade equilibra o brilho.", + ], + "jyotish.rashi.simha.love": [ + "No amor, generosidade e calor são naturais; receber é tão importante quanto dar.", + ], + "jyotish.rashi.simha.career": [ + "No trabalho, liderança e criatividade florescem; compartilhar o palco traz paz.", + ], + "jyotish.rashi.simha.year": [ + "Este ano, seu centro pede reconhecimento; reconheça primeiro a si mesmo.", + ], + "jyotish.rashi.kanya.general": [ + "Sua Lua em Virgem traz discernimento e serviço; a aceitação do imperfeito libera.", + ], + "jyotish.rashi.kanya.love": [ + "No amor, cuidado e precisão são dons; não exija de si o que não exige do outro.", + ], + "jyotish.rashi.kanya.career": [ + "No trabalho, organização e eficiência florescem; o descanso também é produtivo.", + ], + "jyotish.rashi.kanya.year": [ + "Este ano, refine sem rigidificar; a perfeição está no processo, não no resultado.", + ], + "jyotish.rashi.tula.general": [ + "Sua Lua em Libra traz equilíbrio e relação; a justiça começa por você.", + ], + "jyotish.rashi.tula.love": [ + "No amor, harmonia e parceria são naturais; o conflito bem resolvido aproxima.", + ], + "jyotish.rashi.tula.career": [ + "No trabalho, diplomacia e beleza florescem; decidir é também um ato de equilíbrio.", + ], + "jyotish.rashi.tula.year": [ + "Este ano, equilíbrio e parceria se ampliam; cuide dos vínculos que nutrem.", + ], + "jyotish.rashi.vrischika.general": [ + "Sua Lua em Escorpião traz profundidade e transformação; a entrega libera o que prende.", + ], + "jyotish.rashi.vrischika.love": [ + "No amor, intensidade e lealdade são profundas; a confiança se constrói aos poucos.", + ], + "jyotish.rashi.vrischika.career": [ + "No trabalho, poder de realização e foco florescem; use-os com integridade.", + ], + "jyotish.rashi.vrischika.year": [ + "Este ano, transformação e renovação estão no ar; deixe ir o que não serve mais.", + ], + "jyotish.rashi.dhanu.general": [ + "Sua Lua em Sagitário traz busca de sentido e expansão; a pausa também ensina.", + ], + "jyotish.rashi.dhanu.love": [ + "No amor, liberdade e verdade atraem; o compromisso pode ser amplo e livre.", + ], + "jyotish.rashi.dhanu.career": [ + "No trabalho, visão e otimismo florescem; o próximo passo conta mais que o mapa inteiro.", + ], + "jyotish.rashi.dhanu.year": [ + "Este ano, expansão e aprendizado se ampliam; um passo de cada vez.", + ], + "jyotish.rashi.makara.general": [ + "Sua Lua em Capricórnio traz estrutura e ambição; o afeto não enfraquece.", + ], + "jyotish.rashi.makara.love": [ + "No amor, lealdade e constância são profundas; permita-se ser vulnerável.", + ], + "jyotish.rashi.makara.career": [ + "No trabalho, disciplina e longo prazo florescem; celebre as pequenas vitórias.", + ], + "jyotish.rashi.makara.year": [ + "Este ano, construir com paciência traz resultados; cuide do corpo e do repouso.", + ], + "jyotish.rashi.kumbha.general": [ + "Sua Lua em Aquário traz visão de futuro e humanidade; a conexão humana aterriz.", + ], + "jyotish.rashi.kumbha.love": [ + "No amor, amizade e liberdade são bases; o diferente complementa.", + ], + "jyotish.rashi.kumbha.career": [ + "No trabalho, inovação e coletivo florescem; sua contribuição única importa.", + ], + "jyotish.rashi.kumbha.year": [ + "Este ano, visão e comunidade se ampliam; una o ideal ao cotidiano.", + ], + "jyotish.nakshatra.bharani.general": [ + "Bharani traz força de carregar e dar à luz; o que você sustenta o sustenta.", + ], + "jyotish.nakshatra.krittika.general": [ + "Krittika traz fogo de clareza e corte; use-o para iluminar, não para queimar.", + ], + "jyotish.nakshatra.pushya.general": [ + "Pushya traz nutrição e proteção; cuidar e ser cuidado são um só.", + ], + "jyotish.nakshatra.magha.general": [ + "Magha traz dignidade e raiz ancestral; honre o que veio antes e siga em frente.", + ], + "jyotish.nakshatra.hasta.general": [ + "Hasta traz habilidade e precisão; as mãos que criam também acolhem.", + ], + "jyotish.nakshatra.vishakha.general": [ + "Vishakha traz determinação e realização; o objetivo e o caminho são um.", + ], + "jyotish.nakshatra.shravana.general": [ + "Shravana traz escuta e aprendizado; o que você ouve transforma.", + ], + "jyotish.nakshatra.bharani.year": [ + "Bharani no ano favorece carregar e dar à luz; o que você sustenta o sustenta.", + ], + "jyotish.nakshatra.krittika.year": [ + "Krittika no ano traz clareza e corte; use o fogo para iluminar.", + ], + "jyotish.nakshatra.pushya.year": [ + "Pushya no ano favorece nutrição e proteção; cuidar e ser cuidado.", + ], + "jyotish.nakshatra.magha.year": [ + "Magha no ano traz dignidade e raiz; honre o passado e siga em frente.", + ], + "jyotish.nakshatra.hasta.year": [ + "Hasta no ano favorece habilidade e realização; as mãos que criam também acolhem.", + ], + "jyotish.nakshatra.vishakha.year": [ + "Vishakha no ano traz determinação; o objetivo e o caminho são um.", + ], + "jyotish.nakshatra.shravana.year": [ + "Shravana no ano favorece escuta e aprendizado; o que você ouve transforma.", + ], +}; diff --git a/lib/dictionaries/numerology.ts b/lib/dictionaries/numerology.ts new file mode 100644 index 0000000..05333ac --- /dev/null +++ b/lib/dictionaries/numerology.ts @@ -0,0 +1,139 @@ +/** + * Frases ancoradas por número regente (numerologia) — dicionário offline. + */ + +export const NUMEROLOGY_PHRASES: Record = { + "numerology.ruling.1.general": [ + "Seu número fala de unidade: o um que contém o todo.", + "A tendência do um é liderar; a sabedoria é saber quando seguir.", + ], + "numerology.ruling.1.career": [ + "No trabalho, independência e iniciativa são seus dons; a colaboração é o complemento.", + ], + "numerology.ruling.1.love": [ + "No amor, autenticidade e coragem atraem; a entrega equilibra.", + ], + "numerology.ruling.2.general": [ + "O dois traz diplomacia e parceria; a sabedoria está em saber quando dizer não.", + ], + "numerology.ruling.2.career": [ + "No trabalho, cooperação e sensibilidade florescem; limites claros protegem.", + ], + "numerology.ruling.2.love": [ + "No amor, a parceria e a harmonia são naturais; cuidar de si não é egoísmo.", + ], + "numerology.ruling.7.general": [ + "O sete fala de busca interior e sabedoria; a solidão é fertilizante, não fuga.", + ], + "numerology.ruling.7.career": [ + "No trabalho, profundidade e reflexão são seus trunfos; a ação nasce do silêncio.", + ], + "numerology.ruling.7.love": [ + "No amor, a conexão verdadeira nasce quando você se aceita primeiro.", + ], + "numerology.ruling.1.year": [ + "Este ano, liderança e iniciativa são seus trunfos; saber quando seguir complementa.", + ], + "numerology.ruling.2.year": [ + "Este ano, parceria e diplomacia florescem; limites claros protegem sua energia.", + ], + "numerology.ruling.7.year": [ + "Este ano, profundidade e reflexão trazem clareza; a ação nasce do silêncio.", + ], + "numerology.ruling.3.general": [ + "O três fala de expressão e criatividade; a profundidade completa a alegria.", + ], + "numerology.ruling.3.career": [ + "No trabalho, comunicação e criatividade florescem; um projeto de cada vez.", + ], + "numerology.ruling.3.love": [ + "No amor, leveza e troca são naturais; o silêncio a dois também fala.", + ], + "numerology.ruling.3.year": [ + "Este ano, expressão e conexão se ampliam; use sua voz com clareza.", + ], + "numerology.ruling.4.general": [ + "O quatro fala de base e trabalho; a flexibilidade evita a rigidez.", + ], + "numerology.ruling.4.career": [ + "No trabalho, disciplina e estrutura são seus trunfos; descanse para construir melhor.", + ], + "numerology.ruling.4.love": [ + "No amor, lealdade e constância florescem; permita-se receber.", + ], + "numerology.ruling.4.year": [ + "Este ano, construir com paciência traz resultados; celebre cada etapa.", + ], + "numerology.ruling.5.general": [ + "O cinco fala de liberdade e mudança; o centro acompanha o movimento.", + ], + "numerology.ruling.5.career": [ + "No trabalho, versatilidade e adaptação florescem; ancorar em um rumo ajuda.", + ], + "numerology.ruling.5.love": [ + "No amor, liberdade e verdade atraem; o compromisso pode ser amplo.", + ], + "numerology.ruling.5.year": [ + "Este ano, mudança e experiência se ampliam; o instante é seu porto.", + ], + "numerology.ruling.6.general": [ + "O seis fala de cuidado e responsabilidade; cuidar de si vem primeiro.", + ], + "numerology.ruling.6.career": [ + "No trabalho, harmonia e serviço florescem; limites claros protegem.", + ], + "numerology.ruling.6.love": [ + "No amor, dar e receber são um; o autocuidado não é egoísmo.", + ], + "numerology.ruling.6.year": [ + "Este ano, cuidado e beleza se ampliam; nutra os vínculos que nutrem você.", + ], + "numerology.ruling.8.general": [ + "O oito fala de realização e poder; a humildade equilibra a ambição.", + ], + "numerology.ruling.8.career": [ + "No trabalho, manifestação e organização florescem; o descanso também produz.", + ], + "numerology.ruling.8.love": [ + "No amor, lealdade e profundidade são naturais; poder e afeto coexistem.", + ], + "numerology.ruling.8.year": [ + "Este ano, realização e abundância se ampliam; use o poder com integridade.", + ], + "numerology.ruling.9.general": [ + "O nove fala de completude e entrega; a sabedoria está em receber também.", + ], + "numerology.ruling.9.career": [ + "No trabalho, visão ampla e generosidade florescem; cuide de si para cuidar do mundo.", + ], + "numerology.ruling.9.love": [ + "No amor, entrega e compaixão são profundas; permita-se ser amado.", + ], + "numerology.ruling.9.year": [ + "Este ano, ciclos se completam e novos começam; desapego e gratidão andam juntos.", + ], + "numerology.ruling.11.general": [ + "O onze fala de intuição e inspiração; aterrar a visão traz realização.", + ], + "numerology.ruling.11.career": [ + "No trabalho, visão e idealismo florescem; um passo prático por vez.", + ], + "numerology.ruling.11.love": [ + "No amor, conexão além do óbvio; a presença vale mais que a perfeição.", + ], + "numerology.ruling.11.year": [ + "Este ano, intuição e inspiração se ampliam; ancorar no corpo equilibra.", + ], + "numerology.ruling.22.general": [ + "O vinte e dois fala de realização em grande escala; o pequeno gesto também constrói.", + ], + "numerology.ruling.22.career": [ + "No trabalho, visão de mestre e construção florescem; delegar e descansar.", + ], + "numerology.ruling.22.love": [ + "No amor, profundidade e visão de longo prazo; o dia a dia também importa.", + ], + "numerology.ruling.22.year": [ + "Este ano, grandes ciclos e pequenos gestos se unem; um passo de cada vez.", + ], +}; diff --git a/lib/dictionaries/phrasesForSymbolic.ts b/lib/dictionaries/phrasesForSymbolic.ts new file mode 100644 index 0000000..9e388ab --- /dev/null +++ b/lib/dictionaries/phrasesForSymbolic.ts @@ -0,0 +1,242 @@ +/** + * Frases por Insight.key do mapa simbólico canônico (general, love, career, year, action). + * Cada chave usada em jyotishInsightsForSymbolic e collectInsightsForSymbolicMap. + */ + +export const PHRASES_FOR_SYMBOLIC: Record = { + // ——— General (spiritualPath, soulPath + rashi/nakshatra) ——— + "jyotish.revati.spiritualPath": [ + "Há em você um chamado profundo para caminhar com suavidade espiritual.", + ], + "jyotish.revati.soulPath": [ + "A estação lunar Revati traz conclusão e suavidade; o que termina abre espaço para o novo.", + ], + "jyotish.rohini.soulPath": [ + "Rohini traz fertilidade e beleza ao que você cultiva; a paciência é sua aliada.", + ], + "jyotish.ashwini.soulPath": [ + "Ashwini traz impulso de novo começo; a cura começa quando você aceita o que já está aqui.", + ], + "jyotish.karka.general": [ + "Sua Lua em Câncer fala de cuidado e raiz emocional; proteger e ser protegido são dois lados do mesmo gesto.", + ], + "jyotish.mesha.general": [ + "Sua Lua em Áries traz coragem e impulso; a sabedoria está em saber quando pausar.", + ], + "jyotish.mina.general": [ + "Sua Lua em Peixes traz sensibilidade e conexão com o invisível; a compaixão é o seu dom.", + ], + "jyotish.pushya.general": [ + "Pushya traz nutrição e proteção; o que você alimenta com presença floresce.", + ], + "jyotish.magha.general": [ + "Magha traz dignidade e legado; honrar suas raízes fortalece seu caminho.", + ], + "jyotish.hasta.general": [ + "Hasta traz habilidade e precisão; suas mãos podem materializar o que o coração deseja.", + ], + "jyotish.chitra.general": [ + "Chitra traz beleza e estrutura; construir com consciência é o seu dom.", + ], + "jyotish.vrishabha.general": [ + "Sua Lua em Touro fala de raiz e beleza; a estabilidade que você busca nasce da paciência.", + ], + "jyotish.mithuna.general": [ + "Sua Lua em Gêmeos traz comunicação e curiosidade; a profundidade é o complemento.", + ], + "jyotish.simha.general": [ + "Sua Lua em Leão traz dignidade e generosidade; o que você honra honra você.", + ], + "jyotish.kanya.general": [ + "Sua Lua em Virgem traz discernimento e serviço; o detalhe a serviço do todo.", + ], + "jyotish.tula.general": [ + "Sua Lua em Libra traz equilíbrio e parceria; a justiça começa em si.", + ], + "jyotish.vrischika.general": [ + "Sua Lua em Escorpião traz profundidade e transformação; o que você toca se transforma.", + ], + "jyotish.dhanu.general": [ + "Sua Lua em Sagitário traz visão e expansão; o sentido nasce do caminho.", + ], + "jyotish.makara.general": [ + "Sua Lua em Capricórnio traz estrutura e ambição; construir com paciência é o dom.", + ], + "jyotish.kumbha.general": [ + "Sua Lua em Aquário traz inovação e humanidade; o futuro que você quer começa no gesto de hoje.", + ], + // ——— Love ——— + "jyotish.revati.love": [ + "No amor, Revati traz suavidade e conclusão; relacionamentos evoluem quando há espaço para o novo.", + ], + "jyotish.rohini.love": [ + "No amor, Rohini favorece fertilidade e beleza; a paciência e o cultivo diário fortalecem o vínculo.", + ], + "jyotish.karka.love": [ + "No amor, sua natureza lunar pede segurança e profundidade; a entrega nasce da confiança.", + ], + "jyotish.mesha.love": [ + "No amor, a iniciativa e a autenticidade são seus trunfos; a paciência complementa.", + ], + "jyotish.mina.love": [ + "No amor, a entrega e a fusão são naturais; cuidar de si é o primeiro ato de amor.", + ], + "jyotish.vrishabha.love": [ + "No amor, segurança e sensorialidade são naturais; a leveza complementa.", + ], + "jyotish.mithuna.love": [ + "No amor, a troca e a leveza são seus trunfos; o silêncio a dois equilibra.", + ], + "jyotish.tula.love": [ + "No amor, o equilíbrio e a parceria florescem; cuidar do vínculo é cuidar de si.", + ], + "jyotish.simha.love": [ + "No amor, a generosidade e a dignidade são seus trunfos; o reconhecimento mútuo fortalece.", + ], + "jyotish.kanya.love": [ + "No amor, o discernimento e o cuidado com o detalhe fortalecem o vínculo; evite o perfeccionismo.", + ], + // ——— Career ——— + "jyotish.revati.career": [ + "Na carreira, Revati favorece conclusões e transições suaves; feche ciclos para abrir novos.", + ], + "jyotish.karka.career": [ + "No trabalho, sua Lua em Câncer floresce em ambientes que honram o humano e o sensível.", + ], + "jyotish.mesha.career": [ + "No trabalho, liderar e iniciar vêm naturalmente; o equilíbrio está em escutar antes de agir.", + ], + "jyotish.rohini.career": [ + "Na carreira, Rohini favorece crescimento constante; cultive com paciência o que deseja colher.", + ], + "jyotish.ashwini.career": [ + "Na carreira, Ashwini traz impulso de novo começo; a cura e a inovação andam juntas.", + ], + "jyotish.mina.career": [ + "No trabalho, a intuição e o serviço ao outro florescem; ancorar no corpo evita dissipação.", + ], + "jyotish.vrishabha.career": [ + "No trabalho, constância e beleza florescem; evite a rigidez.", + ], + "jyotish.mithuna.career": [ + "Na carreira, comunicação e curiosidade são seus trunfos; a profundidade complementa.", + ], + "jyotish.tula.career": [ + "No trabalho, o equilíbrio e a parceria florescem; ambientes colaborativos te nutrem.", + ], + "jyotish.dhanu.career": [ + "Na carreira, visão e expansão são naturais; alinhe o trabalho a um sentido maior.", + ], + "jyotish.kanya.career": [ + "No trabalho, discernimento e serviço florescem; o detalhe a serviço do todo.", + ], + // ——— Year ——— + "jyotish.revati.year": [ + "O ciclo Revati traz conclusão e suavidade; o que termina abre espaço para o próximo ciclo.", + ], + "jyotish.karka.year": [ + "Este ano, sua Lua em Câncer pede que você cultive raiz e cuidado; o que você nutre nutre você.", + ], + "jyotish.mesha.year": [ + "Este ano, impulso e novo começo estão no ar; escolha um rumo e dê o primeiro passo.", + ], + "jyotish.rohini.year": [ + "Rohini no ano favorece fertilidade e beleza no que você cultiva; paciência e constância.", + ], + "jyotish.ashwini.year": [ + "Ashwini no ano traz impulso de cura e novo começo; aceite o que já está aqui.", + ], + "jyotish.mina.year": [ + "Este ano, a sensibilidade e a conexão com o invisível se ampliam; proteja seus limites para não dissipar.", + ], + "jyotish.vrishabha.year": [ + "Este ano, cultive raiz e paciência; o que você planta com calma colhe em paz.", + ], + "jyotish.mithuna.year": [ + "Este ano, comunicação e curiosidade se ampliam; a profundidade complementa.", + ], + "jyotish.tula.year": [ + "Este ano, equilíbrio e parceria estão em foco; a justiça começa em si.", + ], + "jyotish.pushya.year": [ + "Pushya no ano favorece nutrição e proteção; o que você alimenta com presença floresce.", + ], + // ——— Action (geral e por arquétipo) ——— + "action.general": [ + "Prática: antes de responder a alguém, faça uma pausa de três respirações.", + ], + "action.archetype.dissolvente": [ + "Prática: hoje, uma ação que dissolva um hábito que não serve mais.", + ], + "action.archetype.pioneiro": [ + "Prática: dê o primeiro passo em algo que você adia; mesmo pequeno, conta.", + ], + "action.archetype.cuidador": [ + "Prática: uma ação concreta de cuidado consigo ou com outro hoje.", + ], + "action.archetype.sábio": [ + "Prática: reserve 10 minutos em silêncio para escutar o que já sabe.", + ], + "action.archetype.guerreiro": [ + "Prática: defina um limite claro em uma situação que você evita.", + ], + "action.archetype.raiz": [ + "Prática: hoje, ancorar-se no corpo — um gesto sensorial que te traga ao presente.", + ], + "action.archetype.mensageiro": [ + "Prática: faça uma pergunta genuína a alguém e escute a resposta sem interromper.", + ], + "action.archetype.soberano": [ + "Prática: faça um gesto concreto de reconhecimento para alguém do seu convívio.", + ], + "action.archetype.servidor": [ + "Prática: termine uma tarefa pequena que está pendente há dias, a serviço do todo.", + ], + "action.archetype.harmonizador": [ + "Prática: escolha um limite (horário, tema, pessoa) e respeite-o hoje.", + ], + "action.archetype.transformador": [ + "Prática: hoje, uma ação que dissolva um hábito que não serve mais.", + ], + "action.archetype.sabedor": [ + "Prática: reserve 10 minutos em silêncio para escutar o que já sabe.", + ], + "action.archetype.estruturador": [ + "Prática: escreva em uma linha o que você quer que o seu trabalho gere no mundo; releia ao acordar.", + ], + "action.archetype.visionario": [ + "Prática: diga sim a um convite que te puxe para fora da rotina.", + ], + // ——— 30 frases práticas (action.1 … action.30) ——— + "action.1": ["Prática: respire lentamente 7 vezes antes de tomar qualquer decisão hoje."], + "action.2": ["Prática: por 1 minuto, sinta os pés no chão e o ar entrando e saindo; depois escolha o próximo passo."], + "action.3": ["Prática: escreva em uma linha o que você quer que o seu trabalho gere no mundo; releia ao acordar."], + "action.4": ["Prática: hoje, uma ação concreta de cuidado consigo mesmo (um gesto que te nutra)."], + "action.5": ["Prática: antes de responder a alguém, faça uma pausa de três respirações."], + "action.6": ["Prática: diga não a um compromisso que não serve ao que você quer cultivar."], + "action.7": ["Prática: reserve 10 minutos em silêncio para escutar o que já sabe."], + "action.8": ["Prática: defina um limite claro em uma situação que você evita."], + "action.9": ["Prática: dê o primeiro passo em algo que você adia; mesmo pequeno, conta."], + "action.10": ["Prática: agradeça em voz alta uma pessoa que fez diferença no seu dia."], + "action.11": ["Prática: escreva três coisas que você quer liberar este ano; depois rasgue o papel com consciência."], + "action.12": ["Prática: caminhe 5 minutos em silêncio, prestando atenção só ao corpo e ao ar."], + "action.13": ["Prática: faça uma pergunta genuína a alguém e escute a resposta sem interromper."], + "action.14": ["Prática: desligue as notificações por 1 hora e observe o que surge."], + "action.15": ["Prática: escolha uma palavra que represente o que você quer nutrir e repita-a ao acordar."], + "action.16": ["Prática: hoje, não reclame de nada; observe o que muda."], + "action.17": ["Prática: escreva uma frase que você gostaria de ouvir e leia em voz alta para si."], + "action.18": ["Prática: faça um gesto concreto de reconhecimento para alguém do seu convívio."], + "action.19": ["Prática: sente-se 2 minutos com os olhos fechados só observando a respiração."], + "action.20": ["Prática: termine uma tarefa pequena que está pendente há dias."], + "action.21": ["Prática: pergunte a si mesmo: o que em mim já sabe a resposta? e espere em silêncio."], + "action.22": ["Prática: dedique 15 minutos a uma atividade que te coloque em fluxo, sem objetivo de produto."], + "action.23": ["Prática: hoje, evite julgar; apenas observe uma situação difícil sem comentário interno."], + "action.24": ["Prática: envie uma mensagem de gratidão para alguém que você não agradece há tempo."], + "action.25": ["Prática: deixe o telefone em outro cômodo durante uma refeição."], + "action.26": ["Prática: escolha um limite (horário, tema, pessoa) e respeite-o hoje."], + "action.27": ["Prática: faça uma lista de três possibilidades antes de decidir por uma."], + "action.28": ["Prática: diga sim a um convite que te puxe para fora da rotina."], + "action.29": ["Prática: antes de dormir, relembre um momento do dia em que você esteve presente."], + "action.30": ["Prática: amanhã ao acordar, a primeira ação seja uma respiração consciente antes de pegar o celular."], +}; + diff --git a/lib/dictionaries/remedyMatrix.json b/lib/dictionaries/remedyMatrix.json new file mode 100644 index 0000000..fd8efa2 --- /dev/null +++ b/lib/dictionaries/remedyMatrix.json @@ -0,0 +1,302 @@ +[ + { + "state": "anxiety", + "klesha": "raga", + "samkhyaGuna": "rajas", + "qualities": ["chala", "ruksha"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.33", "verse": "Amizade, compaixão, alegria e equanimidade em relação ao que é agradável, doloroso, meritório ou não — acalmam a mente." }, + "practice": "7 respirações longas com mão no coração", + "food": "sopa quente, ghee", + "question": "O que você pode soltar por hoje?" + }, + { + "state": "emotional_attachment", + "klesha": "raga", + "samkhyaGuna": "rajas", + "qualities": ["snigdha", "guru"], + "sacred": { "corpus": "purana", "id": "BP.bhakti", "verse": "A entrega ao divino dissolve o apego; quem oferece o coração encontra leveza." }, + "practice": "escrever 1 gratidão e liberar", + "food": "chá leve digestivo", + "question": "Você ama ou segura?" + }, + { + "state": "existential_fear", + "klesha": "abhinivesha", + "samkhyaGuna": "tamas", + "qualities": ["guru", "sandra"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.2.9", "verse": "O medo da morte habita mesmo no sábio; é inerente à natureza. Observá-lo enfraquece seu poder." }, + "practice": "grounding — pés no chão 2 min", + "food": "raiz assada", + "question": "O que permanece quando você respira?" + }, + { + "state": "anger", + "klesha": "dvesha", + "samkhyaGuna": "rajas", + "qualities": ["tikshna", "ushna"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.33", "verse": "Amizade, compaixão, alegria e equanimidade — em relação a todos os seres — acalmam o coração." }, + "practice": "água fria no rosto + pausa", + "food": "coco, hortelã", + "question": "Contra o que você está lutando?" + }, + { + "state": "resentment", + "klesha": "dvesha", + "samkhyaGuna": "tamas", + "qualities": ["picchila", "sthira"], + "sacred": { "corpus": "purana", "id": "BP.forgiveness", "verse": "O perdão é libertação; quem solta o ressentimento abre a porta do coração." }, + "practice": "repetir \"eu libero\"", + "food": "especiarias leves", + "question": "O que você ganha mantendo isso?" + }, + { + "state": "spiritual_pride", + "klesha": "asmita", + "samkhyaGuna": "rajas", + "qualities": ["tikshna", "kathina"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.2.6", "verse": "O ego é a identificação do que vê com o instrumento do ver; reconhecer isso dissolve o orgulho." }, + "practice": "ato de serviço anônimo", + "food": "simples, sem excessos", + "question": "Quem é você sem sua imagem?" + }, + { + "state": "confusion", + "klesha": "avidya", + "samkhyaGuna": "tamas", + "qualities": ["manda", "guru"], + "sacred": { "corpus": "upanishad", "id": "UP.tat_tvam_asi", "verse": "Isso és tu. O que você busca já é você." }, + "practice": "silêncio 3 minutos", + "food": "gengibre leve", + "question": "O que é verdadeiro agora?" + }, + { + "state": "overthinking", + "klesha": "avidya", + "samkhyaGuna": "rajas", + "qualities": ["sukshma", "chala"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.2", "verse": "Yoga é a cessação das flutuações da mente." }, + "practice": "olhar fixo em vela 1 min", + "food": "comida densa e quente", + "question": "Você está vivendo ou pensando?" + }, + { + "state": "lethargy", + "klesha": "avidya", + "samkhyaGuna": "tamas", + "qualities": ["guru", "manda"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.14", "verse": "A prática torna-se firme quando mantida por muito tempo, sem interrupção, com devoção." }, + "practice": "caminhada curta agora", + "food": "pimenta preta, calor", + "question": "Qual pequeno passo desperta você?" + }, + { + "state": "distraction", + "klesha": "raga", + "samkhyaGuna": "rajas", + "qualities": ["chala", "laghu"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.13", "verse": "O esforço constante para estabilizar a mente é a prática." }, + "practice": "uma tarefa única por 10 min", + "food": "grounding (raiz)", + "question": "Onde está sua atenção?" + }, + { + "state": "deep_sadness", + "klesha": "abhinivesha", + "samkhyaGuna": "tamas", + "qualities": ["guru", "mridu"], + "sacred": { "corpus": "purana", "id": "BP.compassion", "verse": "A compaixão divina acolhe quem sofre; você pode ser sustentado." }, + "practice": "banho quente + descanso", + "food": "leite morno com especiarias", + "question": "Você pode ser acolhido?" + }, + { + "state": "emotional_instability", + "klesha": "raga", + "samkhyaGuna": "rajas", + "qualities": ["sara", "chala"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.33", "verse": "Amizade, compaixão, alegria e equanimidade estabilizam o coração." }, + "practice": "respiração alternada", + "food": "quente e oleoso", + "question": "O que te estabiliza?" + }, + { + "state": "rigidity", + "klesha": "asmita", + "samkhyaGuna": "tamas", + "qualities": ["kathina", "sthira"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.2.1", "verse": "A prática constante e o desapego são os meios." }, + "practice": "soltar um plano hoje", + "food": "suavizante (óleos)", + "question": "Você confia?" + }, + { + "state": "emptiness", + "klesha": "avidya", + "samkhyaGuna": "tamas", + "qualities": ["vishada", "laghu"], + "sacred": { "corpus": "upanishad", "id": "UP.plenitude", "verse": "O Ser que reside no coração é plenitude; o vazio é espaço para Ele." }, + "practice": "presença corporal", + "food": "nutritivo e quente", + "question": "O vazio é falta ou espaço?" + }, + { + "state": "compulsive_desire", + "klesha": "raga", + "samkhyaGuna": "rajas", + "qualities": ["tikshna", "ushna"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.2.7", "verse": "O apego segue o prazer; o desapego nasce do discernimento." }, + "practice": "pausa de 3 respirações antes de agir", + "food": "amargos refrescantes", + "question": "Isso é necessidade ou impulso?" + }, + { + "state": "aversion_to_other", + "klesha": "dvesha", + "samkhyaGuna": "rajas", + "qualities": ["khara", "tikshna"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.33", "verse": "Compaixão em relação ao que dói — em você e no outro — acalma a mente." }, + "practice": "desejar bem internamente", + "food": "cooling", + "question": "O que você vê em espelho?" + }, + { + "state": "oversensitivity", + "klesha": "abhinivesha", + "samkhyaGuna": "rajas", + "qualities": ["sukshma", "chala"], + "sacred": { "corpus": "purana", "id": "BP.protection", "verse": "Proteger o coração não é fechar; é escolher o que entra." }, + "practice": "autoabraço + limite", + "food": "grounding", + "question": "O que é seu, o que é do mundo?" + }, + { + "state": "lack_of_purpose", + "klesha": "avidya", + "samkhyaGuna": "tamas", + "qualities": ["manda", "guru"], + "sacred": { "corpus": "bhagavad_gita", "id": "BG.dharma", "verse": "Faze o que deves fazer; melhor é o próprio dever, mesmo imperfeito, que o dever alheio bem feito." }, + "practice": "uma ação alinhada hoje", + "food": "simples", + "question": "Qual é seu próximo passo real?" + }, + { + "state": "rush", + "klesha": "raga", + "samkhyaGuna": "rajas", + "qualities": ["tikshna", "sara"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.12", "verse": "A prática e o desapego são o par que estabiliza a mente." }, + "practice": "desacelerar fala", + "food": "doce natural", + "question": "Para onde você corre?" + }, + { + "state": "relational_insecurity", + "klesha": "abhinivesha", + "samkhyaGuna": "rajas", + "qualities": ["chala", "ruksha"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.33", "verse": "Equanimidade em relação ao que vem e vai traz paz ao coração." }, + "practice": "escrever \"eu sou suficiente\"", + "food": "oleoso quente", + "question": "Você busca amor ou presença?" + }, + { + "state": "excess_criticism", + "klesha": "asmita", + "samkhyaGuna": "rajas", + "qualities": ["tikshna", "khara"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.2.35", "verse": "Na presença de quem está firme na não-violência, a hostilidade cessa." }, + "practice": "elogiar algo hoje", + "food": "suavizante", + "question": "Você quer estar certo ou em paz?" + }, + { + "state": "lack_of_discipline", + "klesha": "avidya", + "samkhyaGuna": "rajas", + "qualities": ["chala", "laghu"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.14", "verse": "A prática torna-se firme quando mantida por muito tempo, sem interrupção." }, + "practice": "ritual fixo 5 min", + "food": "grounding", + "question": "O que você repete se torna você." + }, + { + "state": "emotional_exhaustion", + "klesha": "abhinivesha", + "samkhyaGuna": "tamas", + "qualities": ["guru", "mridu"], + "sacred": { "corpus": "purana", "id": "BP.rest", "verse": "O descanso é dharma; quem se entrega ao repouso se renova." }, + "practice": "dormir cedo", + "food": "nutritivo", + "question": "Você está se ouvindo?" + }, + { + "state": "creative_agitation", + "klesha": "raga", + "samkhyaGuna": "rajas", + "qualities": ["tikshna", "chala"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.ekagrata", "verse": "Quando a atenção se concentra em um único ponto, a mente se acalma." }, + "practice": "criar 1 coisa pequena", + "food": "refrescante", + "question": "Você cria ou dispersa?" + }, + { + "state": "stagnation", + "klesha": "avidya", + "samkhyaGuna": "tamas", + "qualities": ["sthira", "guru"], + "sacred": { "corpus": "bhagavad_gita", "id": "BG.action", "verse": "A ação correta libera; a inação presa. Age com desapego ao fruto." }, + "practice": "mover corpo agora", + "food": "especiarias", + "question": "O que quer voltar a fluir?" + }, + { + "state": "excess_guilt", + "klesha": "avidya", + "samkhyaGuna": "tamas", + "qualities": ["guru", "picchila"], + "sacred": { "corpus": "upanishad", "id": "UP.forgiveness", "verse": "O perdão interno libera o peso; você pode recomeçar." }, + "practice": "mão no coração", + "food": "leve", + "question": "Você pode recomeçar?" + }, + { + "state": "emotional_dependence", + "klesha": "raga", + "samkhyaGuna": "rajas", + "qualities": ["snigdha", "guru"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.15", "verse": "O desapego é o domínio de quem não deseja nem evita o que vê." }, + "practice": "ficar consigo 10 min", + "food": "leve digestivo", + "question": "Você está inteiro?" + }, + { + "state": "coldness", + "klesha": "dvesha", + "samkhyaGuna": "tamas", + "qualities": ["sita", "kathina"], + "sacred": { "corpus": "purana", "id": "BP.heart", "verse": "Abrir o coração não é fraqueza; é coragem de sentir." }, + "practice": "gesto de bondade", + "food": "quente", + "question": "O que pode derreter?" + }, + { + "state": "overindulgence", + "klesha": "raga", + "samkhyaGuna": "tamas", + "qualities": ["guru", "snigdha"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.2.1", "verse": "Austeridade, autoestudo e entrega ao Absoluto são a prática." }, + "practice": "simplicidade hoje", + "food": "leve", + "question": "O que é suficiente?" + }, + { + "state": "clarity_expansion", + "klesha": null, + "samkhyaGuna": "sattva", + "qualities": ["vishada", "laghu"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.16", "verse": "O ápice do desapego é quando a consciência reconhece sua própria natureza luminosa." }, + "practice": "contemplação silenciosa", + "food": "fresco e leve", + "question": "Como servir a partir da luz?" + } +] diff --git a/lib/dictionaries/sacred/puranas.json b/lib/dictionaries/sacred/puranas.json new file mode 100644 index 0000000..42850ee --- /dev/null +++ b/lib/dictionaries/sacred/puranas.json @@ -0,0 +1,8 @@ +[ + { "id": "BP.bhakti", "text": "A entrega ao divino dissolve o apego; quem oferece o coração encontra leveza.", "kleshaTargets": ["raga"], "qualities": ["snigdha", "guru"] }, + { "id": "BP.forgiveness", "text": "O perdão é libertação; quem solta o ressentimento abre a porta do coração.", "kleshaTargets": ["dvesha"], "qualities": ["picchila", "sthira"] }, + { "id": "BP.compassion", "text": "A compaixão divina acolhe quem sofre; você pode ser sustentado.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru", "mridu"] }, + { "id": "BP.protection", "text": "Proteger o coração não é fechar; é escolher o que entra.", "kleshaTargets": ["abhinivesha"], "qualities": ["sukshma", "chala"] }, + { "id": "BP.rest", "text": "O descanso é dharma; quem se entrega ao repouso se renova.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru", "mridu"] }, + { "id": "BP.heart", "text": "Abrir o coração não é fraqueza; é coragem de sentir.", "kleshaTargets": ["dvesha"], "qualities": ["sita", "kathina"] } +] diff --git a/lib/dictionaries/sacred/upanishads.json b/lib/dictionaries/sacred/upanishads.json new file mode 100644 index 0000000..182b41f --- /dev/null +++ b/lib/dictionaries/sacred/upanishads.json @@ -0,0 +1,10 @@ +[ + { "id": "UP.tat_tvam_asi", "text": "Isso és tu. O que você busca já é você.", "kleshaTargets": ["avidya"], "qualities": ["manda", "guru"] }, + { "id": "UP.plenitude", "text": "O Ser que reside no coração é plenitude; o vazio é espaço para Ele.", "kleshaTargets": ["avidya"], "qualities": ["vishada", "laghu"] }, + { "id": "UP.forgiveness", "text": "O perdão interno libera o peso; você pode recomeçar.", "kleshaTargets": ["avidya"], "qualities": ["guru", "picchila"] }, + { "id": "UP.kena1", "text": "Aquilo que não pode ser pensado pela mente, mas pelo qual a mente pensa — conhece isso como o Absoluto.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "UP.isha_plenum", "text": "Isto é pleno; aquilo é pleno. Do pleno nasce o pleno. Tomando o pleno do pleno, o pleno permanece.", "kleshaTargets": [], "qualities": ["vishada", "laghu"] }, + { "id": "UP.katha_observer", "text": "Conhece o que em você observa como o condutor da carruagem; o corpo é a carruagem.", "kleshaTargets": ["avidya", "asmita"], "qualities": ["vishada"] }, + { "id": "UP.mundaka_birds", "text": "Duas aves, companheiras unidas, habitam a mesma árvore; uma come o fruto, a outra observa sem comer.", "kleshaTargets": ["raga"], "qualities": ["vishada"] }, + { "id": "UP.brihadaranyaka", "text": "Conduz-me do não-ser ao ser; conduz-me da escuridão à luz; conduz-me da morte à imortalidade.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru"] } +] diff --git a/lib/dictionaries/sacred/yoga_sutras.json b/lib/dictionaries/sacred/yoga_sutras.json new file mode 100644 index 0000000..9199970 --- /dev/null +++ b/lib/dictionaries/sacred/yoga_sutras.json @@ -0,0 +1,15 @@ +[ + { "id": "YS.1.1", "text": "Yoga é a cessação das flutuações da mente.", "kleshaTargets": ["avidya"], "qualities": ["chala", "sukshma"] }, + { "id": "YS.1.2", "text": "Quando a mente está em silêncio, o observador repousa em sua própria natureza.", "kleshaTargets": ["avidya"], "qualities": ["vishada", "laghu"] }, + { "id": "YS.1.12", "text": "A prática e o desapego são o par que estabiliza a mente.", "kleshaTargets": ["raga"], "qualities": ["chala", "tikshna"] }, + { "id": "YS.1.13", "text": "O esforço constante para estabilizar a mente é a prática.", "kleshaTargets": ["raga", "avidya"], "qualities": ["chala", "laghu"] }, + { "id": "YS.1.14", "text": "A prática torna-se firme quando mantida por muito tempo, sem interrupção, com devoção.", "kleshaTargets": ["avidya"], "qualities": ["guru", "manda"] }, + { "id": "YS.1.15", "text": "O desapego é o domínio de quem não deseja nem evita o que vê.", "kleshaTargets": ["raga", "dvesha"], "qualities": ["snigdha", "guru"] }, + { "id": "YS.1.16", "text": "O ápice do desapego é quando a consciência reconhece sua própria natureza luminosa.", "kleshaTargets": [], "qualities": ["vishada", "laghu"] }, + { "id": "YS.1.33", "text": "Amizade, compaixão, alegria e equanimidade em relação ao que é agradável, doloroso, meritório ou não — acalmam a mente.", "kleshaTargets": ["raga", "dvesha"], "qualities": ["chala", "ruksha", "tikshna", "ushna", "khara"] }, + { "id": "YS.2.1", "text": "A prática constante e o desapego são os meios.", "kleshaTargets": ["asmita", "raga"], "qualities": ["kathina", "sthira"] }, + { "id": "YS.2.6", "text": "O ego é a identificação do que vê com o instrumento do ver; reconhecer isso dissolve o orgulho.", "kleshaTargets": ["asmita"], "qualities": ["tikshna", "kathina"] }, + { "id": "YS.2.7", "text": "O apego segue o prazer; o desapego nasce do discernimento.", "kleshaTargets": ["raga"], "qualities": ["tikshna", "ushna"] }, + { "id": "YS.2.9", "text": "O medo da morte habita mesmo no sábio; é inerente à natureza. Observá-lo enfraquece seu poder.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru", "sandra"] }, + { "id": "YS.2.35", "text": "Na presença de quem está firme na não-violência, a hostilidade cessa.", "kleshaTargets": ["dvesha", "asmita"], "qualities": ["tikshna", "khara"] } +] diff --git a/lib/engines/buildSymbolicMap.ts b/lib/engines/buildSymbolicMap.ts new file mode 100644 index 0000000..90ddda0 --- /dev/null +++ b/lib/engines/buildSymbolicMap.ts @@ -0,0 +1,34 @@ +/** + * SymbolicMap universal — núcleo do Darshan offline. + * Core → Jyotish + Numerologia + Human Design (só símbolos). + * Inicializa Swiss Ephemeris (se instalado) antes de calcular, para priorizar alta precisão. + */ + +import type { CoreProfile } from "@/lib/core/types"; +import type { AstronomicalCore } from "@/lib/core/types"; +import { computeAstronomicalCore } from "@/lib/core/ephemerisResolver"; +import { initSwissEphemeris } from "@/lib/core/providers/swissProvider"; +import { jyotishEngine } from "./jyotishEngine"; +import { numerologyEngine } from "./numerologyEngine"; +import { humanDesignEngine } from "./humanDesignEngine"; +import type { JyotishEngineResult } from "./jyotishEngine"; +import type { NumerologyEngineResult } from "./numerologyEngine"; +import type { HumanDesignEngineResult } from "./humanDesignEngine"; + +export type SymbolicMap = { + core: AstronomicalCore; + jyotish: JyotishEngineResult; + numerology: NumerologyEngineResult; + humanDesign: HumanDesignEngineResult; +}; + +export async function buildSymbolicMap(profile: CoreProfile): Promise { + await initSwissEphemeris(); + const core = computeAstronomicalCore(profile); + return { + core, + jyotish: jyotishEngine(core), + numerology: numerologyEngine(profile), + humanDesign: humanDesignEngine(core), + }; +} diff --git a/lib/engines/humanDesignEngine.ts b/lib/engines/humanDesignEngine.ts new file mode 100644 index 0000000..7fca58f --- /dev/null +++ b/lib/engines/humanDesignEngine.ts @@ -0,0 +1,22 @@ +/** + * Human Design Engine — plugável; stub por enquanto (cálculo real depois). + * Só produz dados quando core tem planetas (ex.: Swiss Ephemeris). + */ + +import type { AstronomicalCore } from "@/lib/core/types"; + +export type HumanDesignEngineResult = { + type: string; + authority: string; + profile: string; +} | null; + +export function humanDesignEngine(core: AstronomicalCore): HumanDesignEngineResult { + if (!core.planets) return null; + // Stub: cálculo real de BodyGraph (gates, channels, tipo, autoridade) virá depois + return { + type: "Generator", + authority: "Emotional", + profile: "4/6", + }; +} diff --git a/lib/engines/jyotishEngine.ts b/lib/engines/jyotishEngine.ts new file mode 100644 index 0000000..d3a53fe --- /dev/null +++ b/lib/engines/jyotishEngine.ts @@ -0,0 +1,73 @@ +/** + * Jyotish Engine — só símbolos a partir do AstronomicalCore; nunca texto. + * calculate() → resultado bruto; getNakshatraProfile() → perfil interpretativo; detectYogas() → combinações. + */ + +import type { AstronomicalCore } from "@/lib/core/types"; +import type { RashiKey } from "@/lib/knowledge/types"; +import { NAKSHATRA_MEANINGS } from "@/lib/knowledge/jyotishMeanings"; +import { getArchetypeKeysFromChart } from "@/lib/knowledge/archetypes"; + +export type JyotishEngineResult = { + moonRashi: string | null; + nakshatra: string | null; + planets: Record | null; + houses: Record | null; + precisionLevel: string; +}; + +/** Cálculo Jyotish a partir do core (alias: calculate). */ +export function jyotishEngine(core: AstronomicalCore): JyotishEngineResult { + return { + moonRashi: core.moonRashi ?? null, + nakshatra: core.nakshatra ?? null, + planets: core.planets ?? null, + houses: core.houses ?? null, + precisionLevel: core.providerUsed, + }; +} + +/** Alias para API externa: JyotishEngine.calculate(core). */ +export const calculate = jyotishEngine; + +/** Perfil da nakshatra lunar: significados e arquétipos (para Composer/Readings). */ +export type NakshatraProfile = { + nakshatra: string; + moonRashi: string | null; + namePt?: string; + consciousnessThemes?: string[]; + psychologicalEffects?: string[]; + archetypeHints: string[]; +}; + +export function getNakshatraProfile(core: AstronomicalCore): NakshatraProfile | null { + const nakshatra = core.nakshatra ?? null; + const moonRashi = core.moonRashi ?? null; + if (!nakshatra) return null; + const entry = NAKSHATRA_MEANINGS.find((e) => e.key === nakshatra); + const archetypeHints = getArchetypeKeysFromChart({ + moonRashi: (moonRashi ?? undefined) as RashiKey | undefined, + moonNakshatra: nakshatra, + }); + return { + nakshatra, + moonRashi, + namePt: entry?.namePt, + consciousnessThemes: entry?.consciousnessThemes, + psychologicalEffects: entry?.psychologicalEffects, + archetypeHints, + }; +} + +/** Yogas detectados a partir do core (combinações planetárias/signos). Expandir depois com Neecha Bhanga, etc. */ +export function detectYogas(core: AstronomicalCore): string[] { + const yogas: string[] = []; + if (core.nakshatra) yogas.push(`nakshatra-${core.nakshatra}`); + if (core.moonRashi) yogas.push(`moon-in-${core.moonRashi}`); + if (core.planets?.moon != null && core.planets?.sun != null) { + const moonSign = Math.floor((core.planets.moon % 360) / 30); + const sunSign = Math.floor((core.planets.sun % 360) / 30); + if (moonSign === sunSign) yogas.push("chandra-surya-same-sign"); + } + return yogas; +} diff --git a/lib/engines/numerologyEngine.ts b/lib/engines/numerologyEngine.ts new file mode 100644 index 0000000..1f3c992 --- /dev/null +++ b/lib/engines/numerologyEngine.ts @@ -0,0 +1,27 @@ +/** + * Numerologia Engine — refator do conhecimento atual; só símbolos/dados. + */ + +import { getRulingNumberFromName, getNumberTraits } from "@/lib/knowledge/numerology"; +import type { CoreProfile } from "@/lib/core/types"; + +export type NumerologyEngineResult = { + rulingNumber: number; + name: string; + shortTrait: string; + tendencies: string[]; + challenges: string[]; +}; + +export function numerologyEngine(profile: CoreProfile): NumerologyEngineResult { + const fullName = profile.fullName ?? ""; + const rulingNumber = getRulingNumberFromName(fullName); + const traits = getNumberTraits(rulingNumber); + return { + rulingNumber, + name: traits.name, + shortTrait: traits.shortTrait, + tendencies: traits.tendencies, + challenges: traits.challenges, + }; +} diff --git a/lib/historyStorage.ts b/lib/historyStorage.ts new file mode 100644 index 0000000..03d26ce --- /dev/null +++ b/lib/historyStorage.ts @@ -0,0 +1,129 @@ +/** + * Armazenamento de histórico: respostas (orb) e leituras. + * Usa Supabase; se não configurado, as funções não fazem nada (silencioso). + */ + +import { getSupabase, isSupabaseConfigured } from "@/lib/supabase"; + +export type RevelationRow = { + id: string; + user_id: string; + question_text: string | null; + response_text: string; + created_at: string; +}; + +export type ReadingRow = { + id: string; + user_id: string; + content: string; + created_at: string; +}; + +/** Obtém user_id pelo email (apenas busca; não cria usuário). */ +export async function getUserIdByEmail(email: string): Promise { + const supabase = getSupabase(); + if (!supabase || !isSupabaseConfigured()) return null; + const { data } = await supabase + .from("users") + .select("id") + .eq("email", email) + .single(); + return data?.id ?? null; +} + +/** Salva uma revelação (resposta do orb) para o usuário. */ +export async function saveRevelation( + userEmail: string, + payload: { questionText?: string | null; responseText: string } +): Promise { + const supabase = getSupabase(); + if (!supabase || !isSupabaseConfigured()) return null; + const userId = await getUserIdByEmail(userEmail); + if (!userId) return null; + const { data, error } = await supabase + .from("revelations") + .insert({ + user_id: userId, + question_text: payload.questionText?.trim() || null, + response_text: payload.responseText.trim().slice(0, 50000), + }) + .select("id") + .single(); + if (error) return null; + return data?.id ?? null; +} + +/** Salva uma leitura (mapa pessoal) para o usuário. */ +export async function saveReading(userEmail: string, content: string): Promise { + const supabase = getSupabase(); + if (!supabase || !isSupabaseConfigured()) return null; + const userId = await getUserIdByEmail(userEmail); + if (!userId) return null; + const { data, error } = await supabase + .from("readings") + .insert({ + user_id: userId, + content: content.trim().slice(0, 100000), + }) + .select("id") + .single(); + if (error) return null; + return data?.id ?? null; +} + +/** Lista revelações do usuário (mais recentes primeiro). */ +export async function listRevelations( + userEmail: string, + options: { limit?: number; offset?: number } = {} +): Promise { + const supabase = getSupabase(); + if (!supabase || !isSupabaseConfigured()) return []; + const userId = await getUserIdByEmail(userEmail); + if (!userId) return []; + const limit = Math.min(Math.max(1, options.limit ?? 50), 100); + const offset = Math.max(0, options.offset ?? 0); + const { data } = await supabase + .from("revelations") + .select("id, user_id, question_text, response_text, created_at") + .eq("user_id", userId) + .order("created_at", { ascending: false }) + .range(offset, offset + limit - 1); + return (data ?? []) as RevelationRow[]; +} + +/** Lista leituras do usuário (mais recentes primeiro). */ +export async function listReadings( + userEmail: string, + options: { limit?: number; offset?: number } = {} +): Promise { + const supabase = getSupabase(); + if (!supabase || !isSupabaseConfigured()) return []; + const userId = await getUserIdByEmail(userEmail); + if (!userId) return []; + const limit = Math.min(Math.max(1, options.limit ?? 50), 100); + const offset = Math.max(0, options.offset ?? 0); + const { data } = await supabase + .from("readings") + .select("id, user_id, content, created_at") + .eq("user_id", userId) + .order("created_at", { ascending: false }) + .range(offset, offset + limit - 1); + return (data ?? []) as ReadingRow[]; +} + +/** Conta revelações e leituras do usuário (para exibir ícone quando > 0). */ +export async function getHistoryCounts(userEmail: string): Promise<{ revelations: number; readings: number }> { + const supabase = getSupabase(); + if (!supabase || !isSupabaseConfigured()) return { revelations: 0, readings: 0 }; + const userId = await getUserIdByEmail(userEmail); + if (!userId) return { revelations: 0, readings: 0 }; + const [rev, read] = await Promise.all([ + supabase.from("revelations").select("id", { count: "exact", head: true }).eq("user_id", userId), + supabase.from("readings").select("id", { count: "exact", head: true }).eq("user_id", userId), + ]); + return { + revelations: rev.count ?? 0, + readings: read.count ?? 0, + }; +} diff --git a/lib/insights/collectInsights.ts b/lib/insights/collectInsights.ts new file mode 100644 index 0000000..7e9ef39 --- /dev/null +++ b/lib/insights/collectInsights.ts @@ -0,0 +1,17 @@ +/** + * Coleta todos os insights do mapa simbólico (Jyotish + Numerologia + Human Design). + */ + +import type { SymbolicMap } from "@/lib/engines/buildSymbolicMap"; +import type { Insight } from "./types"; +import { jyotishInsights } from "./jyotishInsights"; +import { numerologyInsights } from "./numerologyInsights"; +import { humanDesignInsights } from "./humanDesignInsights"; + +export function collectAllInsights(map: SymbolicMap): Insight[] { + const list: Insight[] = []; + list.push(...jyotishInsights(map.jyotish)); + list.push(...numerologyInsights(map.numerology)); + list.push(...humanDesignInsights(map.humanDesign)); + return list; +} diff --git a/lib/insights/collectInsightsForSymbolic.ts b/lib/insights/collectInsightsForSymbolic.ts new file mode 100644 index 0000000..535bcba --- /dev/null +++ b/lib/insights/collectInsightsForSymbolic.ts @@ -0,0 +1,37 @@ +/** + * Coleta insights do mapa simbólico canônico (lib/symbolic). + * Usado pelo Narrative Composer para leituras determinísticas. + */ + +import type { SymbolicMap } from "@/lib/symbolic/types"; +import type { Insight } from "./types"; +import { jyotishInsightsForSymbolic } from "./jyotishInsightsForSymbolic"; + +export function collectInsightsForSymbolicMap(map: SymbolicMap): Insight[] { + const list: Insight[] = []; + list.push(...jyotishInsightsForSymbolic(map)); + // Action: sempre pelo menos um insight por número regente + geral + const n = map.numerology.rulingNumber; + list.push({ + key: `action.n.${n}`, + topic: "action", + weight: 1, + system: "numerology", + evidence: { rulingNumber: n }, + }); + list.push({ + key: "action.general", + topic: "action", + weight: 0.9, + system: "jyotish", + evidence: {}, + }); + list.push({ + key: `action.archetype.${map.jyotish.archetypeKey}`, + topic: "action", + weight: 0.85, + system: "jyotish", + evidence: { archetypeKey: map.jyotish.archetypeKey }, + }); + return list; +} diff --git a/lib/insights/humanDesignInsights.ts b/lib/insights/humanDesignInsights.ts new file mode 100644 index 0000000..3c55d86 --- /dev/null +++ b/lib/insights/humanDesignInsights.ts @@ -0,0 +1,28 @@ +/** + * Regras determinísticas de insights Human Design — stub; só quando HD estiver disponível. + */ + +import type { Insight } from "./types"; +import type { HumanDesignEngineResult } from "@/lib/engines/humanDesignEngine"; + +export function humanDesignInsights(humanDesign: HumanDesignEngineResult): Insight[] { + const out: Insight[] = []; + if (!humanDesign) return out; + + out.push({ + key: `hd.type.${humanDesign.type.toLowerCase()}`, + weight: 0.85, + system: "humanDesign", + topic: "general", + evidence: humanDesign, + }); + out.push({ + key: `hd.authority.${humanDesign.authority.toLowerCase()}`, + weight: 0.8, + system: "humanDesign", + topic: "action", + evidence: humanDesign, + }); + + return out; +} diff --git a/lib/insights/jyotishInsights.ts b/lib/insights/jyotishInsights.ts new file mode 100644 index 0000000..78ee4b1 --- /dev/null +++ b/lib/insights/jyotishInsights.ts @@ -0,0 +1,71 @@ +/** + * Regras determinísticas de insights Jyotish — só símbolos → keys; nada aleatório. + */ + +import type { Insight } from "./types"; +import type { JyotishEngineResult } from "@/lib/engines/jyotishEngine"; + +export function jyotishInsights(jyotish: JyotishEngineResult): Insight[] { + const out: Insight[] = []; + if (!jyotish) return out; + + if (jyotish.nakshatra) { + out.push({ + key: `jyotish.nakshatra.${jyotish.nakshatra}.general`, + weight: 0.9, + system: "jyotish", + topic: "general", + evidence: { nakshatra: jyotish.nakshatra }, + }); + out.push({ + key: `jyotish.nakshatra.${jyotish.nakshatra}.spiritualTheme`, + weight: 0.85, + system: "jyotish", + topic: "general", + evidence: { nakshatra: jyotish.nakshatra }, + }); + } + + if (jyotish.moonRashi) { + out.push({ + key: `jyotish.rashi.${jyotish.moonRashi}.general`, + weight: 0.8, + system: "jyotish", + topic: "general", + evidence: { moonRashi: jyotish.moonRashi }, + }); + out.push({ + key: `jyotish.rashi.${jyotish.moonRashi}.love`, + weight: 0.7, + system: "jyotish", + topic: "love", + evidence: { moonRashi: jyotish.moonRashi }, + }); + out.push({ + key: `jyotish.rashi.${jyotish.moonRashi}.career`, + weight: 0.7, + system: "jyotish", + topic: "career", + evidence: { moonRashi: jyotish.moonRashi }, + }); + out.push({ + key: `jyotish.rashi.${jyotish.moonRashi}.year`, + weight: 0.7, + system: "jyotish", + topic: "year", + evidence: { moonRashi: jyotish.moonRashi }, + }); + } + + if (jyotish.nakshatra) { + out.push({ + key: `jyotish.nakshatra.${jyotish.nakshatra}.year`, + weight: 0.75, + system: "jyotish", + topic: "year", + evidence: { nakshatra: jyotish.nakshatra }, + }); + } + + return out; +} diff --git a/lib/insights/jyotishInsightsForSymbolic.ts b/lib/insights/jyotishInsightsForSymbolic.ts new file mode 100644 index 0000000..66c74a7 --- /dev/null +++ b/lib/insights/jyotishInsightsForSymbolic.ts @@ -0,0 +1,479 @@ +/** + * Regras determinísticas de insights Jyotish para o mapa simbólico canônico (lib/symbolic). + * 20 general, 10 love, 10 career, 10 year. Tudo ancorado no mapa; nada aleatório. + */ + +import type { SymbolicMap } from "@/lib/symbolic/types"; +import type { Insight } from "./types"; + +export function jyotishInsightsForSymbolic(map: SymbolicMap): Insight[] { + const out: Insight[] = []; + const { nakshatra, moonRashi } = map.jyotish; + + // ——— GENERAL (20+ regras: soulPath, spiritualPath, rashi/nakshatra) ——— + if (nakshatra === "revati") { + out.push({ + key: "jyotish.revati.spiritualPath", + topic: "general", + weight: 0.95, + system: "jyotish", + evidence: { nakshatra: "revati" }, + }); + out.push({ + key: "jyotish.revati.soulPath", + topic: "general", + weight: 0.9, + system: "jyotish", + evidence: { nakshatra: "revati" }, + }); + } + if (nakshatra === "rohini") { + out.push({ + key: "jyotish.rohini.soulPath", + topic: "general", + weight: 0.9, + system: "jyotish", + evidence: { nakshatra: "rohini" }, + }); + } + if (nakshatra === "ashwini") { + out.push({ + key: "jyotish.ashwini.soulPath", + topic: "general", + weight: 0.9, + system: "jyotish", + evidence: { nakshatra: "ashwini" }, + }); + } + if (moonRashi === "karka") { + out.push({ + key: "jyotish.karka.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { moonRashi: "karka" }, + }); + } + if (moonRashi === "mesha") { + out.push({ + key: "jyotish.mesha.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { moonRashi: "mesha" }, + }); + } + if (moonRashi === "mina") { + out.push({ + key: "jyotish.mina.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { moonRashi: "mina" }, + }); + } + if (nakshatra === "pushya") { + out.push({ + key: "jyotish.pushya.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { nakshatra: "pushya" }, + }); + } + if (nakshatra === "magha") { + out.push({ + key: "jyotish.magha.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { nakshatra: "magha" }, + }); + } + if (nakshatra === "hasta") { + out.push({ + key: "jyotish.hasta.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { nakshatra: "hasta" }, + }); + } + if (nakshatra === "chitra") { + out.push({ + key: "jyotish.chitra.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { nakshatra: "chitra" }, + }); + } + if (moonRashi === "vrishabha") { + out.push({ + key: "jyotish.vrishabha.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { moonRashi: "vrishabha" }, + }); + } + if (moonRashi === "mithuna") { + out.push({ + key: "jyotish.mithuna.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { moonRashi: "mithuna" }, + }); + } + if (moonRashi === "simha") { + out.push({ + key: "jyotish.simha.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { moonRashi: "simha" }, + }); + } + if (moonRashi === "kanya") { + out.push({ + key: "jyotish.kanya.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { moonRashi: "kanya" }, + }); + } + if (moonRashi === "tula") { + out.push({ + key: "jyotish.tula.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { moonRashi: "tula" }, + }); + } + if (moonRashi === "vrischika") { + out.push({ + key: "jyotish.vrischika.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { moonRashi: "vrischika" }, + }); + } + if (moonRashi === "dhanu") { + out.push({ + key: "jyotish.dhanu.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { moonRashi: "dhanu" }, + }); + } + if (moonRashi === "makara") { + out.push({ + key: "jyotish.makara.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { moonRashi: "makara" }, + }); + } + if (moonRashi === "kumbha") { + out.push({ + key: "jyotish.kumbha.general", + topic: "general", + weight: 0.85, + system: "jyotish", + evidence: { moonRashi: "kumbha" }, + }); + } + + // ——— LOVE (10 regras) ——— + if (nakshatra === "revati") { + out.push({ + key: "jyotish.revati.love", + topic: "love", + weight: 0.85, + system: "jyotish", + evidence: { nakshatra: "revati" }, + }); + } + if (nakshatra === "rohini") { + out.push({ + key: "jyotish.rohini.love", + topic: "love", + weight: 0.85, + system: "jyotish", + evidence: { nakshatra: "rohini" }, + }); + } + if (moonRashi === "karka") { + out.push({ + key: "jyotish.karka.love", + topic: "love", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "karka" }, + }); + } + if (moonRashi === "mesha") { + out.push({ + key: "jyotish.mesha.love", + topic: "love", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "mesha" }, + }); + } + if (moonRashi === "mina") { + out.push({ + key: "jyotish.mina.love", + topic: "love", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "mina" }, + }); + } + if (moonRashi === "vrishabha") { + out.push({ + key: "jyotish.vrishabha.love", + topic: "love", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "vrishabha" }, + }); + } + if (moonRashi === "mithuna") { + out.push({ + key: "jyotish.mithuna.love", + topic: "love", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "mithuna" }, + }); + } + if (moonRashi === "tula") { + out.push({ + key: "jyotish.tula.love", + topic: "love", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "tula" }, + }); + } + if (moonRashi === "simha") { + out.push({ + key: "jyotish.simha.love", + topic: "love", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "simha" }, + }); + } + if (moonRashi === "kanya") { + out.push({ + key: "jyotish.kanya.love", + topic: "love", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "kanya" }, + }); + } + + // ——— CAREER (10 regras) ——— + if (nakshatra === "revati") { + out.push({ + key: "jyotish.revati.career", + topic: "career", + weight: 0.85, + system: "jyotish", + evidence: { nakshatra: "revati" }, + }); + } + if (moonRashi === "karka") { + out.push({ + key: "jyotish.karka.career", + topic: "career", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "karka" }, + }); + } + if (moonRashi === "mesha") { + out.push({ + key: "jyotish.mesha.career", + topic: "career", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "mesha" }, + }); + } + if (nakshatra === "rohini") { + out.push({ + key: "jyotish.rohini.career", + topic: "career", + weight: 0.85, + system: "jyotish", + evidence: { nakshatra: "rohini" }, + }); + } + if (nakshatra === "ashwini") { + out.push({ + key: "jyotish.ashwini.career", + topic: "career", + weight: 0.85, + system: "jyotish", + evidence: { nakshatra: "ashwini" }, + }); + } + if (moonRashi === "mina") { + out.push({ + key: "jyotish.mina.career", + topic: "career", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "mina" }, + }); + } + if (moonRashi === "vrishabha") { + out.push({ + key: "jyotish.vrishabha.career", + topic: "career", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "vrishabha" }, + }); + } + if (moonRashi === "mithuna") { + out.push({ + key: "jyotish.mithuna.career", + topic: "career", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "mithuna" }, + }); + } + if (moonRashi === "tula") { + out.push({ + key: "jyotish.tula.career", + topic: "career", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "tula" }, + }); + } + if (moonRashi === "dhanu") { + out.push({ + key: "jyotish.dhanu.career", + topic: "career", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "dhanu" }, + }); + } + if (moonRashi === "kanya") { + out.push({ + key: "jyotish.kanya.career", + topic: "career", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "kanya" }, + }); + } + + // ——— YEAR (10 regras) ——— + if (nakshatra === "revati") { + out.push({ + key: "jyotish.revati.year", + topic: "year", + weight: 0.85, + system: "jyotish", + evidence: { nakshatra: "revati" }, + }); + } + if (moonRashi === "karka") { + out.push({ + key: "jyotish.karka.year", + topic: "year", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "karka" }, + }); + } + if (moonRashi === "mesha") { + out.push({ + key: "jyotish.mesha.year", + topic: "year", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "mesha" }, + }); + } + if (nakshatra === "rohini") { + out.push({ + key: "jyotish.rohini.year", + topic: "year", + weight: 0.85, + system: "jyotish", + evidence: { nakshatra: "rohini" }, + }); + } + if (nakshatra === "ashwini") { + out.push({ + key: "jyotish.ashwini.year", + topic: "year", + weight: 0.85, + system: "jyotish", + evidence: { nakshatra: "ashwini" }, + }); + } + if (moonRashi === "mina") { + out.push({ + key: "jyotish.mina.year", + topic: "year", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "mina" }, + }); + } + if (moonRashi === "vrishabha") { + out.push({ + key: "jyotish.vrishabha.year", + topic: "year", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "vrishabha" }, + }); + } + if (moonRashi === "mithuna") { + out.push({ + key: "jyotish.mithuna.year", + topic: "year", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "mithuna" }, + }); + } + if (moonRashi === "tula") { + out.push({ + key: "jyotish.tula.year", + topic: "year", + weight: 0.8, + system: "jyotish", + evidence: { moonRashi: "tula" }, + }); + } + if (nakshatra === "pushya") { + out.push({ + key: "jyotish.pushya.year", + topic: "year", + weight: 0.85, + system: "jyotish", + evidence: { nakshatra: "pushya" }, + }); + } + + return out; +} diff --git a/lib/insights/numerologyInsights.ts b/lib/insights/numerologyInsights.ts new file mode 100644 index 0000000..be1844d --- /dev/null +++ b/lib/insights/numerologyInsights.ts @@ -0,0 +1,42 @@ +/** + * Regras determinísticas de insights Numerologia — número regente → keys. + */ + +import type { Insight } from "./types"; +import type { NumerologyEngineResult } from "@/lib/engines/numerologyEngine"; + +export function numerologyInsights(numerology: NumerologyEngineResult): Insight[] { + const out: Insight[] = []; + if (!numerology) return out; + + out.push({ + key: `numerology.ruling.${numerology.rulingNumber}.general`, + weight: 0.85, + system: "numerology", + topic: "general", + evidence: { rulingNumber: numerology.rulingNumber }, + }); + out.push({ + key: `numerology.ruling.${numerology.rulingNumber}.career`, + weight: 0.75, + system: "numerology", + topic: "career", + evidence: { rulingNumber: numerology.rulingNumber }, + }); + out.push({ + key: `numerology.ruling.${numerology.rulingNumber}.love`, + weight: 0.75, + system: "numerology", + topic: "love", + evidence: { rulingNumber: numerology.rulingNumber }, + }); + out.push({ + key: `numerology.ruling.${numerology.rulingNumber}.year`, + weight: 0.75, + system: "numerology", + topic: "year", + evidence: { rulingNumber: numerology.rulingNumber }, + }); + + return out; +} diff --git a/lib/insights/types.ts b/lib/insights/types.ts new file mode 100644 index 0000000..ec547d7 --- /dev/null +++ b/lib/insights/types.ts @@ -0,0 +1,15 @@ +/** + * Insight — resultado de regras determinísticas por sistema (Jyotish, numerologia, HD). + * Nunca aleatório; sempre justificável pelo mapa. + */ + +export type InsightTopic = "general" | "love" | "career" | "year" | "action"; +export type InsightSystem = "jyotish" | "numerology" | "humanDesign"; + +export type Insight = { + key: string; + weight: number; + system: InsightSystem; + topic: InsightTopic; + evidence?: unknown; +}; diff --git a/lib/instantLight/index.ts b/lib/instantLight/index.ts new file mode 100644 index 0000000..a329499 --- /dev/null +++ b/lib/instantLight/index.ts @@ -0,0 +1,8 @@ +/** + * Instant Light Engine híbrido — Sacred Library + Personal Insight (quando houver mapa). + * Universal Light (sem perfil) = texto sagrado + prática + pergunta. + * Personal Light (com perfil) = texto sagrado + insight do mapa + prática do mapa + pergunta. + */ + +export { composeInstantLight } from "./instantLightComposer"; +export type { InstantLightProfile, ComposeInstantLightOptions } from "./instantLightComposer"; diff --git a/lib/instantLight/instantLightComposer.ts b/lib/instantLight/instantLightComposer.ts new file mode 100644 index 0000000..f89d2da --- /dev/null +++ b/lib/instantLight/instantLightComposer.ts @@ -0,0 +1,76 @@ +/** + * Instant Light Engine — Sacred Remedy Matrix + diagnóstico (Darshan high-end). + * Retorna: verso sagrado + insight (se mapa) + prática + pergunta. Nunca aleatório puro; usa cooldown. + * Sutra/Purana selector usa klesha + samkhyaGunas; Action selector usa ayurvedicQualities. + */ + +import { getDailySeed } from "@/lib/sacred/sacredPicker"; +import { buildSymbolicMap } from "@/lib/symbolic/builder"; +import { getGeneral } from "@/lib/readings/symbolicReadings"; +import { selectRemedy } from "@/lib/diagnosis/diagnosisEngine"; +import type { UserProfileForOracle } from "@/lib/knowledge/types"; + +export type InstantLightProfile = { + fullName?: string; + birthDate?: string; + birthTime?: string; + birthPlace?: string; +}; + +export type ComposeInstantLightOptions = { + /** IDs de textos sagrados recentes (evitar repetição) */ + recentSacredIds?: string[]; + /** Seed para escolha (default: dailySeed + salt) */ + seed?: number; +}; + +function hasProfile(profile: InstantLightProfile | undefined): boolean { + if (!profile) return false; + const name = (profile.fullName ?? "").trim(); + const date = (profile.birthDate ?? "").trim(); + return name.length > 0 || date.length > 0; +} + +/** + * Compõe a mensagem Instant Light a partir da matriz de remédios (30 estados). + * - Sacred verse (da entrada selecionada) + insight do mapa (se perfil) + prática + pergunta. + * - sacredId para cooldown = corpus.id (ex.: yoga_sutras.YS.1.33). + */ +export function composeInstantLight( + profile?: InstantLightProfile, + options: ComposeInstantLightOptions = {} +): { message: string; sacredId: string } { + const { recentSacredIds = [], seed } = options; + const dailySeed = getDailySeed(); + const effectiveSeed = seed ?? dailySeed * 1000 + (Date.now() % 1000); + + const remedy = selectRemedy(profile as UserProfileForOracle | undefined, { + seed: effectiveSeed, + recentSacredIds, + }); + + const verse = remedy.sacred?.verse ?? remedy.sacred?.id ?? ""; + const sacredId = remedy.sacred?.id + ? `${remedy.sacred.corpus ?? "remedy"}.${remedy.sacred.id}` + : remedy.state; + + const parts: string[] = []; + if (verse.trim()) parts.push(verse.trim()); + + if (hasProfile(profile)) { + const map = buildSymbolicMap({ + fullName: profile!.fullName, + birthDate: profile!.birthDate, + birthTime: profile!.birthTime, + birthPlace: profile!.birthPlace, + }); + const insight = getGeneral(map); + if (insight.trim()) parts.push(insight.trim()); + } + + if (remedy.practice?.trim()) parts.push(remedy.practice.trim()); + if (remedy.question?.trim()) parts.push(remedy.question.trim()); + + const message = parts.join("\n\n").trim(); + return { message, sacredId }; +} diff --git a/lib/knowledge/numerology.ts b/lib/knowledge/numerology.ts index d1cca6d..acde26f 100644 --- a/lib/knowledge/numerology.ts +++ b/lib/knowledge/numerology.ts @@ -48,6 +48,28 @@ export function getRulingNumberFromName(fullName: string): RulingNumber { return reduceToDigit(sum) as RulingNumber; } +/** + * Life Path Number (data de nascimento) — soma dos dígitos da data reduzida a 1–9 ou 11/22. + * Ex.: 1990-05-15 → 1+9+9+0+0+5+1+5 = 30 → 3. + */ +export function getLifePathNumber(birthDate: string): RulingNumber { + const digits = (birthDate || "").replace(/\D/g, ""); + if (!digits.length) return 7; + let sum = 0; + for (const d of digits) sum += parseInt(d, 10); + if (sum === 0) return 7; + while (sum > 99) { + sum = String(sum).split("").reduce((s, d) => s + parseInt(d, 10), 0); + } + if (sum === 11 || sum === 22) return sum as 11 | 22; + return reduceToDigit(sum) as RulingNumber; +} + +/** Expression/Destiny Number = número regente do nome (alias semântico). */ +export function getExpressionNumber(fullName: string): RulingNumber { + return getRulingNumberFromName(fullName); +} + /** Entrada do dicionário por número: tendências e frases para o oráculo */ export type NumberTraitsEntry = { number: RulingNumber; diff --git a/lib/narrative/composer.ts b/lib/narrative/composer.ts new file mode 100644 index 0000000..df2255c --- /dev/null +++ b/lib/narrative/composer.ts @@ -0,0 +1,44 @@ +/** + * Narrative Composer universal — map → insights → composer → módulos. + * Monta leitura a partir de insights filtrados por tópico; key → PHRASES[i.key][0]. + * Nada aleatório; tudo ancorado no mapa. + */ + +import type { Insight, InsightTopic } from "@/lib/insights/types"; +import type { SymbolicMap } from "@/lib/symbolic/types"; +import { phraseFor } from "@/lib/dictionaries"; +import { collectInsightsForSymbolicMap } from "@/lib/insights/collectInsightsForSymbolic"; + +/** + * Composer para o mapa simbólico: collectInsights(map) → filter(topic) → sort(weight) → top 3 → phraseFor(key) → join. + */ +export function composeReading(map: SymbolicMap, topic: InsightTopic): string { + const insights = collectInsightsForSymbolicMap(map) + .filter((i) => i.topic === topic) + .sort((a, b) => b.weight - a.weight); + return insights + .slice(0, 3) + .map((i) => phraseFor(i.key)) + .filter(Boolean) + .join("\n\n"); +} + +/** Composer a partir de array de insights (engines flow). */ +export function composeReadingFromInsights(insights: Insight[], topic: InsightTopic): string { + const filtered = insights.filter((i) => i.topic === topic); + const sorted = filtered.sort((a, b) => b.weight - a.weight); + const chosen = sorted.slice(0, 5); + const parts: string[] = []; + for (const i of chosen) { + const phrase = phraseFor(i.key); + if (phrase && !parts.includes(phrase)) parts.push(phrase); + } + return parts.join("\n\n"); +} + +/** + * Alias para compatibilidade: composer por mapa simbólico. + */ +export function composeReadingFromSymbolicMap(map: SymbolicMap, topic: InsightTopic): string { + return composeReading(map, topic); +} diff --git a/lib/narrative/dictionaryLoader.ts b/lib/narrative/dictionaryLoader.ts new file mode 100644 index 0000000..d05d1f1 --- /dev/null +++ b/lib/narrative/dictionaryLoader.ts @@ -0,0 +1,30 @@ +/** + * Carregador de dicionários — agrega todas as fontes de frases (Jyotish, HD, Action, Numerologia, Symbolic). + * Usado pelo Narrative Composer; tudo derivado do mapa, sem aleatoriedade solta. + */ + +import { JYOTISH_PHRASES } from "@/lib/dictionaries/jyotish"; +import { HD_PHRASES } from "@/lib/dictionaries/humanDesign"; +import { ACTION_PHRASES } from "@/lib/dictionaries/action"; +import { NUMEROLOGY_PHRASES } from "@/lib/dictionaries/numerology"; +import { PHRASES_FOR_SYMBOLIC } from "@/lib/dictionaries/phrasesForSymbolic"; + +export type PhraseDictionary = Record; + +/** Retorna o dicionário único mergendo todas as fontes (mesma ordem que phraseFor). */ +export function getMergedDictionaries(): PhraseDictionary { + return { + ...JYOTISH_PHRASES, + ...HD_PHRASES, + ...ACTION_PHRASES, + ...NUMEROLOGY_PHRASES, + ...PHRASES_FOR_SYMBOLIC, + }; +} + +/** Retorna a primeira frase para uma chave, ou string vazia (sem aleatoriedade). */ +export function getPhraseFromDict(dict: PhraseDictionary, key: string): string { + const phrases = dict[key]; + if (phrases && phrases.length > 0) return phrases[0]; + return ""; +} diff --git a/lib/narrative/phraseTemplates.ts b/lib/narrative/phraseTemplates.ts new file mode 100644 index 0000000..25422f7 --- /dev/null +++ b/lib/narrative/phraseTemplates.ts @@ -0,0 +1,36 @@ +/** + * Templates de frase para o Narrative Composer — tudo derivado do mapa. + * placements, yogas, archetypes, theme → frase determinística (sem IA). + */ + +import { phraseFor } from "@/lib/dictionaries"; + +export type TemplateContext = { + placements?: { moonRashi?: string; nakshatra?: string }; + yogas?: string[]; + archetypes?: string[]; + theme: "general" | "love" | "career" | "year" | "action"; +}; + +/** Retorna a frase do dicionário para uma chave de insight (usado pelo Composer). */ +export function getPhraseForInsight(key: string): string { + return phraseFor(key); +} + +/** + * Retorna uma frase para o contexto (placements + theme). + * O Composer principal usa composeReading(map, topic); este helper serve chamadas que já têm só placements/theme. + */ +export function getPhraseForContext(ctx: TemplateContext): string { + if (ctx.placements?.nakshatra) { + const key = `jyotish.${ctx.placements.nakshatra}.${ctx.theme === "general" ? "general" : ctx.theme}`; + const phrase = phraseFor(key); + if (phrase) return phrase; + } + if (ctx.placements?.moonRashi) { + const key = `jyotish.${ctx.placements.moonRashi}.${ctx.theme === "general" ? "general" : ctx.theme}`; + const phrase = phraseFor(key); + if (phrase) return phrase; + } + return phraseFor("jyotish.revati.soulPath") || ""; +} diff --git a/lib/oracleOffline.ts b/lib/oracleOffline.ts index 94a7b81..cb1d63c 100644 --- a/lib/oracleOffline.ts +++ b/lib/oracleOffline.ts @@ -5,15 +5,10 @@ */ import type { UserProfileForOracle } from "./knowledge/types"; -import { computeVedicChartSimplified } from "./knowledge/vedic"; -import { extractKeywords } from "./knowledge/keywordMatch"; -import { getRandomFormulation } from "./knowledge/formulations"; -import { getRandomClassicTextForArchetype } from "./knowledge/classicTexts"; -import { getRandomArchetypePhrase } from "./knowledge/archetypeTraits"; -import { getJyotishPhraseForChart } from "./knowledge/jyotishMeanings"; -import { getRulingNumberFromName, getRandomPhraseForNumber } from "./knowledge/numerology"; - -const MAX_SEED_TRIES = 30; +import { buildSymbolicMap } from "./engines/buildSymbolicMap"; +import { collectAllInsights } from "./insights/collectInsights"; +import { phraseFor } from "./dictionaries"; + /** Número de blocos: na maioria 1, às vezes 2, raramente 3. */ function getTargetBlockCount(seed4: number): number { const r = seed4 % 10; @@ -78,17 +73,13 @@ function getRequestSalt(): number { return (Date.now() * 1000 + Math.floor(Math.random() * 1000)) >>> 0; } -/** Gera uma mensagem de revelação a partir do perfil e do dicionário offline. */ -export function getOfflineRevelation( +/** Gera uma mensagem de revelação a partir do SymbolicMap e insights (coerente com o mapa). */ +export async function getOfflineRevelation( profile: UserProfileForOracle, - userMessage?: string, + _userMessage?: string, recentlyUsedPhrases?: string[] -): string { +): Promise { const salt = getRequestSalt(); - const seedBase = (getSeedFromProfile(profile) + salt) >>> 0; - const seed1 = (getSeedFromProfile(profile, 1) + salt * 31) >>> 0; - const seed2 = (getSeedFromProfile(profile, 2) + salt * 37) >>> 0; - const seed3 = (getSeedFromProfile(profile, 3) + salt * 41) >>> 0; const seed4 = (getSeedFromProfile(profile, 4) + salt * 43) >>> 0; const recentSet = new Set( @@ -97,73 +88,35 @@ export function getOfflineRevelation( .filter((n) => n.length > 0) ); - const keywords = extractKeywords(userMessage); - const blocks: string[] = []; - - const chart = computeVedicChartSimplified({ + const coreProfile = { birthDate: profile.birthDate, birthTime: profile.birthTime, - }); - const archetypeKey = chart.archetypeKeys[0]; - const rulingNumber = getRulingNumberFromName(profile.fullName ?? ""); - const firstName = profile.fullName?.trim().split(/\s+/)[0]; - const closing = firstName && (seed4 % 3 === 0) - ? `${firstName}, o que em você já sabe?` - : "O que em você já sabe?"; - - function pickPhrase( - getPhrase: (seed: number) => string | null, - baseSeed: number - ): string | null { - let phrase = getPhrase(baseSeed); - for (let k = 0; k < MAX_SEED_TRIES && phrase && !canUsePhrase(phrase, blocks, recentSet); k++) { - phrase = getPhrase(baseSeed + k + 1); - } - return phrase && !isDuplicate(phrase, blocks) ? phrase : null; - } - - const formulationText = pickPhrase( - (s) => getRandomFormulation(archetypeKey, s, keywords)?.text ?? null, - seedBase - ); + fullName: profile.fullName, + }; + const map = await buildSymbolicMap(coreProfile); + const insights = collectAllInsights(map).filter((i) => i.topic === "general"); + const sorted = [...insights].sort((a, b) => b.weight - a.weight); - const archetypePhrase = pickPhrase( - (s) => getRandomArchetypePhrase(archetypeKey, s, keywords), - seed1 - ); - const classicText = pickPhrase( - (s) => getRandomClassicTextForArchetype(archetypeKey, s, keywords)?.text ?? null, - seed2 - ); - const jyotishPhrase = pickPhrase( - (s) => getJyotishPhraseForChart(chart, s, keywords), - seed2 + 1 - ); - const numberPhrase = pickPhrase( - (s) => getRandomPhraseForNumber(rulingNumber, s, keywords), - seed3 - ); - - const typedPhrases: { phrase: string }[] = []; + const candidates: string[] = []; const seenNormalized = new Set(); - - function addIfNew(phrase: string | null): void { - if (!phrase) return; + for (const i of sorted) { + const phrase = phraseFor(i.key); + if (!phrase) continue; const n = normalizeForCompare(phrase); - if (!n || seenNormalized.has(n)) return; + if (!n || seenNormalized.has(n) || recentSet.has(n)) continue; seenNormalized.add(n); - typedPhrases.push({ phrase }); + candidates.push(phrase); } - addIfNew(formulationText); - addIfNew(archetypePhrase); - addIfNew(classicText); - addIfNew(jyotishPhrase); - addIfNew(numberPhrase); - if (closing && !isRecentlyUsed(closing, recentSet)) addIfNew(closing); + const firstName = profile.fullName?.trim().split(/\s+/)[0]; + const closing = firstName && seed4 % 3 === 0 + ? `${firstName}, o que em você já sabe?` + : "O que em você já sabe?"; + if (closing && !recentSet.has(normalizeForCompare(closing))) candidates.push(closing); - const shuffled = shuffleBySeed(typedPhrases, seed4); + const shuffled = shuffleBySeed(candidates.map((phrase) => ({ phrase })), seed4); const targetTotal = getTargetBlockCount(seed4); + const blocks: string[] = []; for (const { phrase } of shuffled) { if (phrase && !isDuplicate(phrase, blocks) && !isRecentlyUsed(phrase, recentSet)) { diff --git a/lib/readingOffline.ts b/lib/readingOffline.ts index f56719f..2e547ed 100644 --- a/lib/readingOffline.ts +++ b/lib/readingOffline.ts @@ -1,113 +1,63 @@ /** - * Leitura (mapa pessoal) offline — monta um texto a partir do conhecimento local - * (Jyotish, numerologia, arquétipos), sem chamar a IA. - * Usa interpretação Jyotish para leitura (temas e efeitos narrativos), não as frases do oráculo. + * Leitura (mapa pessoal) offline — SymbolicMap → Insights → Composer → Readings modulares. + * getOfflineReading é apenas um wrapper: monta o mapa e devolve general + love + career + year + action. + * Sem IA; motor determinístico e extensível (pronto para Swiss Ephemeris e Human Design). */ -import { computeVedicChartSimplified } from "@/lib/knowledge/vedic"; -import { getRulingNumberFromName, getNumberTraits } from "@/lib/knowledge/numerology"; -import { getArchetypeTraits } from "@/lib/knowledge/archetypeTraits"; -import { getRandomClassicTextForArchetype } from "@/lib/knowledge/classicTexts"; +import { buildSymbolicMap } from "@/lib/symbolic/builder"; import { - getJyotishReadingInterpretation, - RASHI_MEANINGS, - NAKSHATRA_MEANINGS, -} from "@/lib/knowledge/jyotishMeanings"; -import type { RashiKey, NakshatraKey } from "@/lib/knowledge/types"; + getGeneral, + getLove, + getCareer, + getYear, + getAction, + type StructuredOfflineReading, +} from "@/lib/readings/symbolicReadings"; -function getSeedFromProfile(profile: { fullName?: string; birthDate?: string; birthTime?: string }, offset: number): number { - const str = [ - profile.fullName ?? "", - profile.birthDate ?? "", - profile.birthTime ?? "", - String(offset), - ].join("|"); - let h = 0; - for (let i = 0; i < str.length; i++) { - h = ((h << 5) - h) + str.charCodeAt(i); - h |= 0; - } - return Math.abs(h); -} - -function rashiName(key: RashiKey): string { - const e = RASHI_MEANINGS.find((x) => x.key === key); - return e?.namePt ?? key; -} - -function nakshatraName(key: NakshatraKey): string { - const e = NAKSHATRA_MEANINGS.find((x) => x.key === key); - return e?.namePt ?? key; -} +export type { StructuredOfflineReading }; /** - * Gera uma leitura pessoal (mapa) totalmente offline, sem IA. - * Usa interpretação Jyotish (temas + efeitos), descrição de arquétipo e numerologia, - * e um texto clássico; evita as frases prontas do oráculo Darshan. + * Wrapper: map = buildSymbolicMap(profile) → return { general, love, career, year, action }. */ export function getOfflineReading(profile: { fullName?: string; birthDate?: string; birthPlace?: string; birthTime?: string; -}): string { - const seed3 = (getSeedFromProfile(profile, 3) + Date.now()) >>> 0; - - const chart = computeVedicChartSimplified({ +}): StructuredOfflineReading { + const map = buildSymbolicMap({ + fullName: profile.fullName, birthDate: profile.birthDate, + birthPlace: profile.birthPlace, birthTime: profile.birthTime, }); - const rulingNumber = getRulingNumberFromName(profile.fullName ?? ""); - const numberTraits = getNumberTraits(rulingNumber); - const archetypeKey = chart.archetypeKeys?.[0]; - const archetypeEntry = archetypeKey ? getArchetypeTraits(archetypeKey) : undefined; - - const sections: string[] = []; - - // Introdução - sections.push( - "Esta leitura integra o mapa védico (Lua, signo e estação lunar), numerologia e arquétipos em uma síntese interpretativa." - ); - - // Lua e Jyotish (interpretação narrativa, não frases do oráculo) - if (chart.moonRashi || chart.moonNakshatra) { - const rashi = chart.moonRashi ? rashiName(chart.moonRashi as RashiKey) : ""; - const naks = chart.moonNakshatra ? nakshatraName(chart.moonNakshatra as NakshatraKey) : ""; - const line: string[] = []; - if (rashi) line.push(`Sua Lua está no signo de ${rashi}`); - if (naks) line.push(`na estação lunar ${naks}`); - if (line.length) { - sections.push(line.join(" e ") + "."); - const interpretation = getJyotishReadingInterpretation(chart); - if (interpretation) sections.push(interpretation); - } - } - - // Arquétipo (descrição a partir de traits, sem frase pronta) - if (archetypeKey && archetypeEntry) { - sections.push(""); - sections.push(`Arquétipo: ${archetypeEntry.name} — ${archetypeEntry.shortTrait}`); - const personality = archetypeEntry.personality?.slice(0, 3).join(", ") ?? ""; - const tendencies = archetypeEntry.tendencies?.slice(0, 3).join("; ") ?? ""; - const challenges = archetypeEntry.challenges?.slice(0, 2).join("; ") ?? ""; - if (personality) sections.push(`Traços: ${personality}.`); - if (tendencies) sections.push(`Tendências: ${tendencies}.`); - if (challenges) sections.push(`Desafios possíveis: ${challenges}.`); - } - - // Numerologia (descrição a partir de traits, sem frase pronta) - sections.push(""); - sections.push(`Numerologia (Pitágoras) — Número regente ${rulingNumber}: ${numberTraits.name}.`); - sections.push(numberTraits.shortTrait + "."); - sections.push(`Tendências: ${numberTraits.tendencies.join("; ")}.`); - sections.push(`Desafios: ${numberTraits.challenges.join("; ")}.`); - - // Um texto clássico (reflexão, não oráculo) - const classic = getRandomClassicTextForArchetype(archetypeKey ?? "sábio", seed3); - if (classic?.text) { - sections.push(""); - sections.push(classic.text); - } + return { + general: getGeneral(map), + love: getLove(map), + career: getCareer(map), + year: getYear(map), + action: getAction(map), + }; +} - return sections.join("\n\n").trim(); +/** + * Retorna o texto completo da leitura (todas as seções unidas). + * Útil para compatibilidade com APIs que esperam uma única string (message). + */ +export function getOfflineReadingFullText(profile: { + fullName?: string; + birthDate?: string; + birthPlace?: string; + birthTime?: string; +}): string { + const s = getOfflineReading(profile); + const parts: string[] = []; + const intro = "Esta leitura integra o mapa védico (Lua, signo e estação lunar), numerologia e arquétipos."; + parts.push(intro); + if (s.general.trim()) parts.push(s.general.trim()); + if (s.love.trim()) parts.push(s.love.trim()); + if (s.career.trim()) parts.push(s.career.trim()); + if (s.year.trim()) parts.push(s.year.trim()); + if (s.action.trim()) parts.push(s.action.trim()); + return parts.join("\n\n").trim(); } diff --git a/lib/readings/actionReading.ts b/lib/readings/actionReading.ts new file mode 100644 index 0000000..6b8a950 --- /dev/null +++ b/lib/readings/actionReading.ts @@ -0,0 +1,14 @@ +/** + * Action Engine — prática concreta obrigatória ao final de toda leitura. + */ + +import type { CoreProfile } from "@/lib/core/types"; +import { buildSymbolicMap } from "@/lib/engines/buildSymbolicMap"; +import { collectAllInsights } from "@/lib/insights/collectInsights"; +import { composeReadingFromInsights } from "@/lib/narrative/composer"; + +export async function getActionReading(profile: CoreProfile): Promise { + const map = await buildSymbolicMap(profile); + const insights = collectAllInsights(map); + return composeReadingFromInsights(insights, "action"); +} diff --git a/lib/readings/careerReading.ts b/lib/readings/careerReading.ts new file mode 100644 index 0000000..a4069f3 --- /dev/null +++ b/lib/readings/careerReading.ts @@ -0,0 +1,19 @@ +/** + * Leitura de carreira — mapa + narrativa career + action. + */ + +import type { CoreProfile } from "@/lib/core/types"; +import type { SymbolicMap } from "@/lib/engines/buildSymbolicMap"; +import { buildSymbolicMap } from "@/lib/engines/buildSymbolicMap"; +import { collectAllInsights } from "@/lib/insights/collectInsights"; +import { composeReadingFromInsights } from "@/lib/narrative/composer"; + +export async function getCareerReading(profile: CoreProfile): Promise<{ map: SymbolicMap; reading: string; action: string }> { + const map = await buildSymbolicMap(profile); + const insights = collectAllInsights(map); + return { + map, + reading: composeReadingFromInsights(insights, "career"), + action: composeReadingFromInsights(insights, "action"), + }; +} diff --git a/lib/readings/generalReading.ts b/lib/readings/generalReading.ts new file mode 100644 index 0000000..a099abd --- /dev/null +++ b/lib/readings/generalReading.ts @@ -0,0 +1,19 @@ +/** + * Leitura geral — mapa + narrativa general + action. + */ + +import type { CoreProfile } from "@/lib/core/types"; +import type { SymbolicMap } from "@/lib/engines/buildSymbolicMap"; +import { buildSymbolicMap } from "@/lib/engines/buildSymbolicMap"; +import { collectAllInsights } from "@/lib/insights/collectInsights"; +import { composeReadingFromInsights } from "@/lib/narrative/composer"; + +export async function getGeneralReading(profile: CoreProfile): Promise<{ map: SymbolicMap; reading: string; action: string }> { + const map = await buildSymbolicMap(profile); + const insights = collectAllInsights(map); + return { + map, + reading: composeReadingFromInsights(insights, "general"), + action: composeReadingFromInsights(insights, "action"), + }; +} diff --git a/lib/readings/loveReading.ts b/lib/readings/loveReading.ts new file mode 100644 index 0000000..dadf066 --- /dev/null +++ b/lib/readings/loveReading.ts @@ -0,0 +1,19 @@ +/** + * Leitura de relacionamento (amor) — mapa + narrativa love + action. + */ + +import type { CoreProfile } from "@/lib/core/types"; +import type { SymbolicMap } from "@/lib/engines/buildSymbolicMap"; +import { buildSymbolicMap } from "@/lib/engines/buildSymbolicMap"; +import { collectAllInsights } from "@/lib/insights/collectInsights"; +import { composeReadingFromInsights } from "@/lib/narrative/composer"; + +export async function getLoveReading(profile: CoreProfile): Promise<{ map: SymbolicMap; reading: string; action: string }> { + const map = await buildSymbolicMap(profile); + const insights = collectAllInsights(map); + return { + map, + reading: composeReadingFromInsights(insights, "love"), + action: composeReadingFromInsights(insights, "action"), + }; +} diff --git a/lib/readings/symbolicReadings.ts b/lib/readings/symbolicReadings.ts new file mode 100644 index 0000000..589f1aa --- /dev/null +++ b/lib/readings/symbolicReadings.ts @@ -0,0 +1,117 @@ +/** + * Readings modulares a partir do mapa simbólico canônico (lib/symbolic). + * Cada getX(map) chama composeReading(map, topic). Motor modular e extensível. + */ + +import type { UserProfileForOracle } from "@/lib/knowledge/types"; +import type { SymbolicMap } from "@/lib/symbolic/types"; +import { buildSymbolicMap } from "@/lib/symbolic/builder"; +import { composeReading } from "@/lib/narrative/composer"; +import type { InsightTopic } from "@/lib/insights/types"; + +function toProfile(p: { + fullName?: string; + birthDate?: string; + birthPlace?: string; + birthTime?: string; +}): UserProfileForOracle { + return { + fullName: p.fullName, + birthDate: p.birthDate, + birthPlace: p.birthPlace, + birthTime: p.birthTime, + }; +} + +// ——— Getters por mapa (usados por readingOffline) ——— +export function getGeneral(map: SymbolicMap): string { + return composeReading(map, "general"); +} + +export function getLove(map: SymbolicMap): string { + return composeReading(map, "love"); +} + +export function getCareer(map: SymbolicMap): string { + return composeReading(map, "career"); +} + +export function getYear(map: SymbolicMap): string { + return composeReading(map, "year"); +} + +export function getAction(map: SymbolicMap): string { + return composeReading(map, "action"); +} + +/** Mapa tema externo (API) → tópico interno (composer). */ +const THEME_TO_TOPIC: Record = { + general: "general", + love: "love", + relationship: "love", + career: "career", + work: "career", + year: "year", + yearly: "year", + action: "action", +}; + +/** + * Leitura temática por nome de tema (usado por POST /api/reading?theme=). + * Aceita: general | love | relationship | career | work | year | yearly | action. + */ +export function getReadingByTheme(map: SymbolicMap, theme: string): string { + const topic = THEME_TO_TOPIC[theme?.toLowerCase()] ?? "general"; + return composeReading(map, topic); +} + +// ——— Readings por perfil (conveniência) ——— +export function getGeneralReadingSymbolic(profile: UserProfileForOracle): string { + const map = buildSymbolicMap(profile); + return getGeneral(map); +} + +export function getLoveReadingSymbolic(profile: UserProfileForOracle): string { + const map = buildSymbolicMap(profile); + return getLove(map); +} + +export function getCareerReadingSymbolic(profile: UserProfileForOracle): string { + const map = buildSymbolicMap(profile); + return getCareer(map); +} + +export function getYearReadingSymbolic(profile: UserProfileForOracle): string { + const map = buildSymbolicMap(profile); + return getYear(map); +} + +export function getActionReadingSymbolic(profile: UserProfileForOracle): string { + const map = buildSymbolicMap(profile); + return getAction(map); +} + +export type StructuredOfflineReading = { + general: string; + love: string; + career: string; + year: string; + action: string; +}; + +export function getStructuredOfflineReading(profile: { + fullName?: string; + birthDate?: string; + birthPlace?: string; + birthTime?: string; +}): StructuredOfflineReading { + const p = toProfile(profile); + const map = buildSymbolicMap(p); + return { + general: getGeneral(map), + love: getLove(map), + career: getCareer(map), + year: getYear(map), + action: getAction(map), + }; +} diff --git a/lib/readings/yearReading.ts b/lib/readings/yearReading.ts new file mode 100644 index 0000000..09c8239 --- /dev/null +++ b/lib/readings/yearReading.ts @@ -0,0 +1,19 @@ +/** + * Leitura de ano — mapa + narrativa year + action. + */ + +import type { CoreProfile } from "@/lib/core/types"; +import type { SymbolicMap } from "@/lib/engines/buildSymbolicMap"; +import { buildSymbolicMap } from "@/lib/engines/buildSymbolicMap"; +import { collectAllInsights } from "@/lib/insights/collectInsights"; +import { composeReadingFromInsights } from "@/lib/narrative/composer"; + +export async function getYearReading(profile: CoreProfile): Promise<{ map: SymbolicMap; reading: string; action: string }> { + const map = await buildSymbolicMap(profile); + const insights = collectAllInsights(map); + return { + map, + reading: composeReadingFromInsights(insights, "year"), + action: composeReadingFromInsights(insights, "action"), + }; +} diff --git a/lib/sacred/index.ts b/lib/sacred/index.ts new file mode 100644 index 0000000..ef288a1 --- /dev/null +++ b/lib/sacred/index.ts @@ -0,0 +1,7 @@ +/** + * Sacred Library Engine — textos sagrados (Upanishads, Yoga Sutras, Bhagavad Gita). + * pickSacredText: rotação determinística, cooldown, tags temáticas. + */ + +export { pickSacredText, getDailySeed } from "./sacredPicker"; +export type { SacredTextEntry, PickSacredOptions, SacredSource } from "./types"; diff --git a/lib/sacred/sacredPicker.ts b/lib/sacred/sacredPicker.ts new file mode 100644 index 0000000..75963a2 --- /dev/null +++ b/lib/sacred/sacredPicker.ts @@ -0,0 +1,84 @@ +/** + * Sacred Library Engine — rotação determinística, evitar repetição (cooldown), tags temáticas. + * Textos sagrados (Upanishads, Yoga Sutras, Bhagavad Gita) sempre em primeiro lugar no Instant Light. + */ + +import { + ALL_CLASSIC_TEXTS, + type ClassicTextEntry, +} from "@/lib/knowledge/classicTexts"; +import type { SacredTextEntry, PickSacredOptions } from "./types"; + +/** Converte ClassicTextEntry → SacredTextEntry com tags (source, guna, archetype). */ +function toSacredEntry(e: ClassicTextEntry): SacredTextEntry { + const tags: string[] = [ + e.source, + e.guna ?? "sattva", + ...(e.archetypeHints ?? []), + ]; + return { + id: e.id, + source: e.source, + work: e.work, + verse: e.text, + tags, + }; +} + +let cachedSacredList: SacredTextEntry[] | null = null; + +function getSacredList(): SacredTextEntry[] { + if (cachedSacredList) return cachedSacredList; + cachedSacredList = ALL_CLASSIC_TEXTS.map(toSacredEntry); + return cachedSacredList; +} + +/** + * Escolhe um texto sagrado com rotação determinística e cooldown. + * - themeTags: filtra por pelo menos uma tag (ex.: ["yoga-sutras", "mind"]). + * - avoidLastIds: IDs a não repetir (cooldown). + * - seed: escolha determinística (ex.: dailySeed). + */ +export function pickSacredText(options: PickSacredOptions = {}): SacredTextEntry { + const { + themeTags, + avoidLastIds = [], + avoidLastN = 0, + seed, + } = options; + + const list = getSacredList(); + let pool = list; + + if (themeTags?.length) { + pool = pool.filter((e) => + themeTags.some((t) => e.tags.includes(t.toLowerCase())) + ); + if (pool.length === 0) pool = list; + } + + const avoidSet = new Set(avoidLastIds); + if (avoidLastN > 0 && avoidLastIds.length >= avoidLastN) { + const recent = avoidLastIds.slice(0, avoidLastN); + recent.forEach((id) => avoidSet.add(id)); + } + let candidates = pool.filter((e) => !avoidSet.has(e.id)); + if (candidates.length === 0) candidates = pool; + + const idx = + seed !== undefined + ? Math.abs(Math.floor(seed)) % candidates.length + : Math.floor(Math.random() * candidates.length); + return candidates[idx] ?? candidates[0]; +} + +/** Retorna seed diário (YYYYMMDD) para rotação por dia. */ +export function getDailySeed(): number { + const now = new Date(); + const y = now.getFullYear(); + const m = now.getMonth() + 1; + const d = now.getDate(); + return y * 10000 + m * 100 + d; +} + +export type { SacredTextEntry, PickSacredOptions } from "./types"; diff --git a/lib/sacred/types.ts b/lib/sacred/types.ts new file mode 100644 index 0000000..678b112 --- /dev/null +++ b/lib/sacred/types.ts @@ -0,0 +1,26 @@ +/** + * Tipos da Sacred Library — textos sagrados (Upanishads, Yoga Sutras, Bhagavad Gita, Puranas). + * Cada item tem id, verso, source e tags para filtro temático e rotação. + */ + +export type SacredSource = "upanishad" | "bhagavad-gita" | "yoga-sutras" | "outro"; + +export type SacredTextEntry = { + id: string; + source: SacredSource; + work?: string; + verse: string; + /** Tags para filtro e rotação (mind, silence, discipline, etc.) */ + tags: string[]; +}; + +export type PickSacredOptions = { + /** Filtrar por pelo menos uma tag (ex.: ["mind", "silence"]) */ + themeTags?: string[]; + /** IDs a evitar (cooldown / evitar repetição) */ + avoidLastIds?: string[]; + /** Seed determinístico (ex.: dailySeed) */ + seed?: number; + /** Número de itens recentes a evitar por índice (alternativa a avoidLastIds) */ + avoidLastN?: number; +}; diff --git a/lib/sacredRemedy/diagnosisEngine.ts b/lib/sacredRemedy/diagnosisEngine.ts new file mode 100644 index 0000000..4445ebf --- /dev/null +++ b/lib/sacredRemedy/diagnosisEngine.ts @@ -0,0 +1,118 @@ +/** + * Sacred Remedy — Diagnosis Engine. + * diagnosisUniversal() sem perfil; diagnosisPersonal(map) com perfil. Seleção dirigida + anti-repetição. + */ + +import { buildSymbolicMap } from "@/lib/symbolic/builder"; +import { ARCHETYPE_TO_GUNA } from "@/lib/knowledge/classicTexts"; +import type { UserProfileForOracle } from "@/lib/knowledge/types"; +import type { SymbolicMap } from "@/lib/symbolic/types"; +import type { + ConsciousDiagnosis, + RemedyMatrixEntry, + SamkhyaGuna, + PrakritiFromJyotish, +} from "./types"; + +import remedyMatrixJson from "@/lib/dictionaries/remedyMatrix.json"; + +const REMEDY_MATRIX: RemedyMatrixEntry[] = remedyMatrixJson as RemedyMatrixEntry[]; + +const RASHI_TO_DOSHA: Record = { + mesha: "pitta", vrishabha: "kapha", mithuna: "vata", karka: "kapha", + simha: "pitta", kanya: "vata", tula: "vata", vrischika: "pitta", + dhanu: "pitta", makara: "kapha", kumbha: "vata", mina: "kapha", +}; + +const RASHI_TO_ELEMENT: Record = { + mesha: "fire", vrishabha: "earth", mithuna: "air", karka: "water", + simha: "fire", kanya: "earth", tula: "air", vrischika: "water", + dhanu: "fire", makara: "earth", kumbha: "air", mina: "water", +}; + +export function getRemedyMatrix(): RemedyMatrixEntry[] { + return REMEDY_MATRIX; +} + +function getPrakritiFromMap(map: SymbolicMap): PrakritiFromJyotish { + const rashi = map.jyotish?.moonRashi ?? "mesha"; + return { + dosha: RASHI_TO_DOSHA[rashi] ?? "vata", + element: RASHI_TO_ELEMENT[rashi] ?? "air", + }; +} + +function getDominantSamkhyaGuna(map: SymbolicMap): SamkhyaGuna { + const primary = map.archetypes?.primary ?? map.jyotish?.archetypeKey ?? "dissolvente"; + const guna = ARCHETYPE_TO_GUNA[primary]; + return (guna ?? "tamas") as SamkhyaGuna; +} + +function remedyToDiagnosis(remedy: RemedyMatrixEntry, prakriti?: PrakritiFromJyotish): ConsciousDiagnosis { + const g = remedy.samkhyaGuna; + const sattva = g === "sattva" ? 0.6 : 0.2; + const rajas = g === "rajas" ? 0.6 : 0.2; + const tamas = g === "tamas" ? 0.6 : 0.2; + return { + klesha: remedy.klesha, + samkhyaGunas: { sattva, rajas, tamas }, + ayurvedicQualities: { excess: remedy.qualities ?? [], deficient: [] }, + prakritiFromJyotish: prakriti, + stateKey: remedy.state, + }; +} + +/** + * Diagnóstico universal (sem perfil). Escolhe um estado da matriz por seed; evita recentIds. + */ +export function diagnosisUniversal(options: { seed?: number; recentStateKeys?: string[] } = {}): ConsciousDiagnosis { + const { seed = 0, recentStateKeys = [] } = options; + const avoid = new Set(recentStateKeys); + const pool = avoid.size > 0 + ? REMEDY_MATRIX.filter((e) => !avoid.has(e.state)) + : REMEDY_MATRIX; + const candidates = pool.length > 0 ? pool : REMEDY_MATRIX; + const idx = Math.abs(Math.floor(seed)) % candidates.length; + const remedy = candidates[idx] ?? candidates[0]; + return remedyToDiagnosis(remedy); +} + +/** + * Diagnóstico personalizado (com mapa). Filtra por guna dominante do mapa; escolhe por seed; evita recentIds. + */ +export function diagnosisPersonal( + profile: UserProfileForOracle, + options: { seed?: number; recentStateKeys?: string[] } = {} +): ConsciousDiagnosis { + const { seed = 0, recentStateKeys = [] } = options; + const map = buildSymbolicMap(profile); + const dominantGuna = getDominantSamkhyaGuna(map); + const prakriti = getPrakritiFromMap(map); + const avoid = new Set(recentStateKeys); + const byGuna = REMEDY_MATRIX.filter((e) => e.samkhyaGuna === dominantGuna); + const pool = byGuna.length > 0 ? byGuna : REMEDY_MATRIX; + const preferred = pool.filter((e) => !avoid.has(e.state)); + const candidates = preferred.length > 0 ? preferred : pool; + const idx = Math.abs(Math.floor(seed)) % candidates.length; + const remedy = candidates[idx] ?? candidates[0]; + return remedyToDiagnosis(remedy, prakriti); +} + +/** + * Retorna a entrada da matriz de remédios correspondente ao diagnóstico (por stateKey ou por klesha+guna). + */ +export function getRemedyForDiagnosis( + diagnosis: ConsciousDiagnosis, + options: { seed?: number } = {} +): RemedyMatrixEntry { + if (diagnosis.stateKey) { + const found = REMEDY_MATRIX.find((e) => e.state === diagnosis.stateKey); + if (found) return found; + } + const byKlesha = diagnosis.klesha + ? REMEDY_MATRIX.filter((e) => e.klesha === diagnosis.klesha) + : REMEDY_MATRIX; + const pool = byKlesha.length > 0 ? byKlesha : REMEDY_MATRIX; + const idx = Math.abs(Math.floor(options.seed ?? 0)) % pool.length; + return pool[idx] ?? pool[0]; +} diff --git a/lib/sacredRemedy/index.ts b/lib/sacredRemedy/index.ts new file mode 100644 index 0000000..5e4ca4a --- /dev/null +++ b/lib/sacredRemedy/index.ts @@ -0,0 +1,26 @@ +/** + * Sacred Remedy Engine — núcleo offline medicinal. + * Diagnóstico (universal/personal) + seleção dirigida de texto sagrado + prática + pergunta. + */ + +export { + getRemedyMatrix, + diagnosisUniversal, + diagnosisPersonal, + getRemedyForDiagnosis, +} from "./diagnosisEngine"; +export { selectSacredText, getAllSacredEntries } from "./sacredSelector"; +export type { SelectSacredOptions } from "./sacredSelector"; +export { composeInstantLight } from "./instantLightComposer"; +export type { ComposeInstantLightOptions } from "./instantLightComposer"; +export type { + SamkhyaGuna, + KleshaKey, + AyurvedicQuality, + PrakritiFromJyotish, + SamkhyaGunas, + ConsciousDiagnosis, + SacredCorpusEntry, + RemedyMatrixEntry, + InstantLightResponse, +} from "./types"; diff --git a/lib/sacredRemedy/instantLightComposer.ts b/lib/sacredRemedy/instantLightComposer.ts new file mode 100644 index 0000000..4186b2d --- /dev/null +++ b/lib/sacredRemedy/instantLightComposer.ts @@ -0,0 +1,85 @@ +/** + * Sacred Remedy — Instant Light Composer. + * Texto sagrado dirigido (sutra/purana) + insight (se personal) + prática ayurvédica + pergunta final. + * Motor medicinal offline; não quebra /api/darshan. + */ + +import { buildSymbolicMap } from "@/lib/symbolic/builder"; +import { getGeneral } from "@/lib/readings/symbolicReadings"; +import { + diagnosisUniversal, + diagnosisPersonal, + getRemedyForDiagnosis, +} from "./diagnosisEngine"; +import { selectSacredText } from "./sacredSelector"; +import type { UserProfileForOracle } from "@/lib/knowledge/types"; +import type { InstantLightResponse } from "./types"; + +/** Seed diário para rotação */ +function getDailySeed(): number { + const now = new Date(); + const y = now.getFullYear(); + const m = now.getMonth() + 1; + const d = now.getDate(); + return y * 10000 + m * 100 + d; +} + +export type ComposeInstantLightOptions = { + seed?: number; + recentSacredIds?: string[]; + recentStateKeys?: string[]; +}; + +/** + * Compõe a resposta Instant Light (Sacred Remedy Engine). + * - Com perfil: diagnosisPersonal → sacredSelector(klesha, qualities) → insight do mapa + prática + pergunta. + * - Sem perfil: diagnosisUniversal → sacredSelector → prática + pergunta. + */ +export function composeInstantLight( + profile?: UserProfileForOracle | null, + options: ComposeInstantLightOptions = {} +): InstantLightResponse { + const { + seed, + recentSacredIds = [], + recentStateKeys = [], + } = options; + const dailySeed = getDailySeed(); + const effectiveSeed = seed ?? dailySeed * 1000 + (Date.now() % 1000); + + const hasProfile = Boolean( + profile && ((profile.fullName ?? "").trim() || (profile.birthDate ?? "").trim()) + ); + + const diagnosis = hasProfile && profile + ? diagnosisPersonal(profile, { seed: effectiveSeed, recentStateKeys }) + : diagnosisUniversal({ seed: effectiveSeed, recentStateKeys }); + + const remedy = getRemedyForDiagnosis(diagnosis, { seed: effectiveSeed }); + + const sacredEntry = selectSacredText({ + kleshaTargets: diagnosis.klesha ? [diagnosis.klesha] : [], + qualities: diagnosis.ayurvedicQualities.excess, + avoidIds: recentSacredIds, + seed: effectiveSeed, + }); + + const sacredText = sacredEntry?.text?.trim() || remedy.sacred?.verse?.trim() || remedy.sacred?.id || ""; + const sacredId = sacredEntry ? `${sacredEntry.corpus}.${sacredEntry.id}` : `${remedy.sacred?.corpus ?? "remedy"}.${remedy.sacred?.id ?? remedy.state}`; + + const result: InstantLightResponse = { + sacredText, + practice: remedy.practice?.trim() || "", + question: remedy.question?.trim() || "O que em você já sabe?", + sacredId, + stateKey: diagnosis.stateKey ?? remedy.state, + }; + + if (hasProfile && profile) { + const map = buildSymbolicMap(profile); + const insight = getGeneral(map); + if (insight?.trim()) result.insight = insight.trim(); + } + + return result; +} diff --git a/lib/sacredRemedy/sacredSelector.ts b/lib/sacredRemedy/sacredSelector.ts new file mode 100644 index 0000000..7b0c27e --- /dev/null +++ b/lib/sacredRemedy/sacredSelector.ts @@ -0,0 +1,77 @@ +/** + * Sacred Selector — seleção dirigida de texto sagrado por klesha e qualidades. + * Carrega yoga_sutras, puranas, upanishads (dictionaries/sacred/) com kleshaTargets e qualities. + */ + +import type { SacredCorpusEntry } from "./types"; + +import yogaSutrasJson from "@/lib/dictionaries/sacred/yoga_sutras.json"; +import puranasJson from "@/lib/dictionaries/sacred/puranas.json"; +import upanishadsJson from "@/lib/dictionaries/sacred/upanishads.json"; + +const YOGA_SUTRAS = yogaSutrasJson as SacredCorpusEntry[]; +const PURANAS = puranasJson as SacredCorpusEntry[]; +const UPANISHADS = upanishadsJson as SacredCorpusEntry[]; + +/** Todas as entradas com corpus para id único */ +type TaggedEntry = SacredCorpusEntry & { corpus: string }; + +function tagCorpus(entries: SacredCorpusEntry[], corpus: string): TaggedEntry[] { + return entries.map((e) => ({ ...e, corpus })); +} + +const ALL_SACRED: TaggedEntry[] = [ + ...tagCorpus(YOGA_SUTRAS, "yoga_sutras"), + ...tagCorpus(PURANAS, "puranas"), + ...tagCorpus(UPANISHADS, "upanishads"), +]; + +export type SelectSacredOptions = { + /** Kleśas que o texto deve endereçar (pelo menos um match em kleshaTargets) */ + kleshaTargets?: string[]; + /** Qualidades em excesso (match em qualities do texto) */ + qualities?: string[]; + /** IDs a evitar (anti-repetição) — formato "corpus.id" ou só "id" */ + avoidIds?: string[]; + /** Seed para escolha determinística */ + seed?: number; +}; + +/** + * Seleciona um texto sagrado dirigido por klesha e qualidades. + * Prioriza entradas que batem em kleshaTargets; depois em qualities; evita avoidIds. + */ +export function selectSacredText(options: SelectSacredOptions = {}): SacredCorpusEntry & { corpus: string } { + const { kleshaTargets = [], qualities = [], avoidIds = [], seed = 0 } = options; + const avoidSet = new Set(avoidIds); + const kleshaSet = new Set(kleshaTargets.filter(Boolean)); + const qualitySet = new Set(qualities.filter(Boolean)); + + const score = (e: TaggedEntry): number => { + let s = 0; + const targets = e.kleshaTargets ?? []; + const quals = e.qualities ?? []; + if (kleshaSet.size && targets.some((t) => kleshaSet.has(t))) s += 2; + if (qualitySet.size && quals.some((q) => qualitySet.has(q))) s += 1; + return s; + }; + + const byAvoid = ALL_SACRED.filter((e) => { + const fullId = `${e.corpus}.${e.id}`; + return !avoidSet.has(fullId) && !avoidSet.has(e.id); + }); + const pool = byAvoid.length > 0 ? byAvoid : ALL_SACRED; + + const scored = pool.map((e) => ({ e, s: score(e) })); + scored.sort((a, b) => b.s - a.s); + const bestScore = scored[0]?.s ?? 0; + const candidates = bestScore > 0 ? scored.filter((x) => x.s === bestScore).map((x) => x.e) : pool; + + const idx = Math.abs(Math.floor(seed)) % candidates.length; + return candidates[idx] ?? candidates[0]; +} + +/** Retorna lista completa para fallback ou inspeção */ +export function getAllSacredEntries(): TaggedEntry[] { + return ALL_SACRED; +} diff --git a/lib/sacredRemedy/types.ts b/lib/sacredRemedy/types.ts new file mode 100644 index 0000000..c5e8bf5 --- /dev/null +++ b/lib/sacredRemedy/types.ts @@ -0,0 +1,67 @@ +/** + * Sacred Remedy Engine — tipos canônicos. + * Diagnóstico consciente (klesha, samkhya, qualidades) + corpus sagrado taggeado + matriz de remédios. + */ + +/** Guṇas Sāṃkhya — estado global da consciência */ +export type SamkhyaGuna = "sattva" | "rajas" | "tamas"; + +/** Kleśas — obstáculos da mente (Yoga Sutras) */ +export type KleshaKey = "avidya" | "asmita" | "raga" | "dvesha" | "abhinivesha" | null; + +/** Qualidades ayurvédicas (20) — fenomenológicas */ +export type AyurvedicQuality = string; + +/** Prakṛti simbólica (Jyotish) */ +export type PrakritiFromJyotish = { + dosha?: string; + element?: string; +}; + +export type SamkhyaGunas = { + sattva: number; + rajas: number; + tamas: number; +}; + +/** Diagnóstico consciente — entrada do motor de remédio */ +export type ConsciousDiagnosis = { + klesha: KleshaKey; + samkhyaGunas: SamkhyaGunas; + ayurvedicQualities: { excess: string[]; deficient: string[] }; + prakritiFromJyotish?: PrakritiFromJyotish; + /** Estado da matriz usado (ex.: anxiety, lethargy) */ + stateKey?: string; +}; + +/** Entrada do corpus sagrado (dictionaries/sacred/*.json) — taggeada por klesha e qualidades */ +export type SacredCorpusEntry = { + id: string; + text: string; + /** Kleśas que este texto ajuda a equilibrar */ + kleshaTargets?: string[]; + /** Qualidades ayurvédicas associadas */ + qualities?: string[]; +}; + +/** Entrada da matriz de remédios (30 estados) */ +export type RemedyMatrixEntry = { + state: string; + klesha: KleshaKey; + samkhyaGuna: SamkhyaGuna; + qualities: string[]; + sacred: { corpus: string; id: string; verse?: string }; + practice: string; + food: string; + question: string; +}; + +/** Resposta do Instant Light (GET /api/instant-light) */ +export type InstantLightResponse = { + sacredText: string; + insight?: string; + practice: string; + question: string; + sacredId?: string; + stateKey?: string; +}; diff --git a/lib/symbolic/SymbolicMap.ts b/lib/symbolic/SymbolicMap.ts new file mode 100644 index 0000000..adbed1d --- /dev/null +++ b/lib/symbolic/SymbolicMap.ts @@ -0,0 +1,8 @@ +/** + * Entrada canônica do mapa simbólico — Engine 2.0. + * Estrutura: jyotish, numerology, themes, traits, evidence. + * Use buildSymbolicMap(profile) para obter o mapa; depois composeReading(map, topic) para leituras temáticas. + */ + +export type { SymbolicMap } from "./types"; +export { buildSymbolicMap } from "./builder"; diff --git a/lib/symbolic/builder.ts b/lib/symbolic/builder.ts new file mode 100644 index 0000000..7aa1a70 --- /dev/null +++ b/lib/symbolic/builder.ts @@ -0,0 +1,46 @@ +/** + * Constrói o mapa simbólico canônico a partir do perfil (Jyotish + Numerologia). + * Determinístico; sem IA. evidence guarda o chart para auditoria e extensão. + */ + +import type { UserProfileForOracle } from "@/lib/knowledge/types"; +import type { VedicChartSimplified } from "@/lib/knowledge/types"; +import { computeVedicChartSimplified } from "@/lib/knowledge/vedic"; +import { + getRulingNumberFromName, + getLifePathNumber, + getExpressionNumber, +} from "@/lib/knowledge/numerology"; +import type { SymbolicMap } from "./types"; + +export function buildSymbolicMap(profile: UserProfileForOracle): SymbolicMap { + const chart: VedicChartSimplified = computeVedicChartSimplified({ + birthDate: profile.birthDate, + birthTime: profile.birthTime, + }); + const rulingNumber = getRulingNumberFromName(profile.fullName ?? ""); + const lifePathNumber = getLifePathNumber(profile.birthDate ?? ""); + const expressionNumber = getExpressionNumber(profile.fullName ?? ""); + + const archetypeKeys = chart.archetypeKeys ?? ["dissolvente"]; + const primary = archetypeKeys[0] ?? "dissolvente"; + + return { + jyotish: { + moonRashi: chart.moonRashi ?? "mesha", + nakshatra: chart.moonNakshatra ?? "ashwini", + archetypeKey: primary, + }, + numerology: { + rulingNumber: rulingNumber as number, + lifePathNumber: lifePathNumber as number, + expressionNumber: expressionNumber as number, + }, + archetypes: { + primary, + keys: archetypeKeys, + }, + themes: [], + evidence: { chart }, + }; +} diff --git a/lib/symbolic/types.ts b/lib/symbolic/types.ts new file mode 100644 index 0000000..0e37d16 --- /dev/null +++ b/lib/symbolic/types.ts @@ -0,0 +1,28 @@ +/** + * Mapa simbólico canônico universal — base do Engine 2.0. + * Estrutura: jyotish, numerology, archetypes, themes, evidence. + * Objeto canônico do Darshan; reutilizável por Oracle e leituras premium. + */ + +export type SymbolicMap = { + jyotish: { + moonRashi: string; + nakshatra: string; + archetypeKey: string; + }; + numerology: { + /** Número regente do nome (Pitágoras); mantido para compatibilidade. */ + rulingNumber: number; + /** Life Path — data de nascimento (1–9, 11, 22). */ + lifePathNumber?: number; + /** Expression/Destiny — nome completo (1–9, 11, 22). */ + expressionNumber?: number; + }; + /** Arquétipos derivados do chart (primary = primeiro; keys = lista completa). */ + archetypes: { + primary: string; + keys: string[]; + }; + themes: string[]; + evidence: Record; +}; diff --git a/package-lock.json b/package-lock.json index d445aa2..1bc5fd1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,14 +1,15 @@ { "name": "darshan", - "version": "0.1.0", + "version": "0.1.3", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "darshan", - "version": "0.1.0", + "version": "0.1.3", "dependencies": { "@anthropic-ai/sdk": "^0.32.1", + "@fusionstrings/swiss-eph": "^0.1.1", "@google/generative-ai": "^0.21.0", "@supabase/supabase-js": "^2.93.3", "framer-motion": "^11.11.17", @@ -595,6 +596,46 @@ "dev": true, "license": "MIT" }, + "node_modules/@deno/shim-deno": { + "version": "0.18.2", + "resolved": "https://registry.npmjs.org/@deno/shim-deno/-/shim-deno-0.18.2.tgz", + "integrity": "sha512-oQ0CVmOio63wlhwQF75zA4ioolPvOwAoK0yuzcS5bDC1JUvH3y1GS8xPh8EOpcoDQRU4FTG8OQfxhpR+c6DrzA==", + "license": "MIT", + "dependencies": { + "@deno/shim-deno-test": "^0.5.0", + "which": "^4.0.0" + } + }, + "node_modules/@deno/shim-deno-test": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/@deno/shim-deno-test/-/shim-deno-test-0.5.0.tgz", + "integrity": "sha512-4nMhecpGlPi0cSzT67L+Tm+GOJqvuk8gqHBziqcUQOarnuIax1z96/gJHCSIz2Z0zhxE6Rzwb3IZXPtFh51j+w==", + "license": "MIT" + }, + "node_modules/@deno/shim-deno/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@deno/shim-deno/node_modules/which": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-4.0.0.tgz", + "integrity": "sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==", + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^16.13.0 || >=18.0.0" + } + }, "node_modules/@emnapi/core": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.8.1.tgz", @@ -711,6 +752,16 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fusionstrings/swiss-eph": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@fusionstrings/swiss-eph/-/swiss-eph-0.1.1.tgz", + "integrity": "sha512-UGKCfVh5TUygShCNKnh7iauJ109QYgV+e3+8PACOsiIFyiX8z3PIw7etbYDqF0egsJfIArRdDjOwrliAOFGNgA==", + "license": "AGPL-3.0", + "dependencies": { + "@deno/shim-deno": "~0.18.0", + "undici": "^6.0.0" + } + }, "node_modules/@google/generative-ai": { "version": "0.21.0", "resolved": "https://registry.npmjs.org/@google/generative-ai/-/generative-ai-0.21.0.tgz", @@ -9272,6 +9323,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/undici": { + "version": "6.23.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.23.0.tgz", + "integrity": "sha512-VfQPToRA5FZs/qJxLIinmU59u0r7LXqoJkCzinq3ckNJp3vKEh7jTWN589YQ5+aoAC/TGRLyJLCPKcLQbM8r9g==", + "license": "MIT", + "engines": { + "node": ">=18.17" + } + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", diff --git a/package.json b/package.json index 562fc93..09e8af7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "private": true, "scripts": { "dev": "next dev", - "prebuild": "node -e \"try { require('fs').rmSync('build', { recursive: true }) } catch (e) { }\"", + "prebuild": "node -e \"try { require('fs').rmSync('build', { recursive: true, maxRetries: 3 }) } catch (e) { if (e.code !== 'EPERM' && e.code !== 'EBUSY') throw e; }\"", "build": "next build", "start": "next start", "lint": "next lint", @@ -21,15 +21,16 @@ }, "dependencies": { "@anthropic-ai/sdk": "^0.32.1", + "@fusionstrings/swiss-eph": "^0.1.1", "@google/generative-ai": "^0.21.0", "@supabase/supabase-js": "^2.93.3", "framer-motion": "^11.11.17", + "mercadopago": "^2.0.0", "mhah-panchang": "^1.2.0", "next": "15.5.11", "openai": "^4.73.0", "react": "^19.0.0", "react-dom": "^19.0.0", - "mercadopago": "^2.0.0", "stripe": "^17.4.0", "suncalc": "^1.9.0" }, @@ -40,10 +41,10 @@ "@types/react-dom": "^19.0.1", "@types/suncalc": "^1.9.2", "autoprefixer": "^10.4.20", - "postcss": "^8.4.49", "eslint": "^8.57.0", "eslint-config-next": "15.5.11", "jest": "^29.7.0", + "postcss": "^8.4.49", "tailwindcss": "^3.4.15", "typescript": "^5.6.3" } diff --git a/supabase/migrations/20250129100000_history_tables.sql b/supabase/migrations/20250129100000_history_tables.sql new file mode 100644 index 0000000..86533dd --- /dev/null +++ b/supabase/migrations/20250129100000_history_tables.sql @@ -0,0 +1,40 @@ +-- Darshan: histórico de respostas (orb) e leituras — por usuário. +-- Também estende credit_ledger.reason para incluir personal_map (leitura completa). +-- Executar no Supabase (SQL Editor ou CLI: supabase db push). + +-- Permitir reason 'personal_map' no credit_ledger (se a constraint existir) +DO $$ +BEGIN + ALTER TABLE credit_ledger DROP CONSTRAINT IF EXISTS credit_ledger_reason_check; + ALTER TABLE credit_ledger ADD CONSTRAINT credit_ledger_reason_check + CHECK (reason IN ('purchase', 'darshan_call', 'personal_map', 'admin_adjust')); +EXCEPTION + WHEN undefined_object THEN NULL; + WHEN others THEN NULL; +END $$; + +-- 1. revelations (respostas da interação com o orb — pergunta opcional + resposta da IA) +CREATE TABLE IF NOT EXISTS revelations ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users (id) ON DELETE CASCADE, + question_text TEXT, + response_text TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS idx_revelations_user_id ON revelations (user_id); +CREATE INDEX IF NOT EXISTS idx_revelations_created_at ON revelations (created_at DESC); + +-- 2. readings (resultado das leituras completas — mapa pessoal) +CREATE TABLE IF NOT EXISTS readings ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users (id) ON DELETE CASCADE, + content TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS idx_readings_user_id ON readings (user_id); +CREATE INDEX IF NOT EXISTS idx_readings_created_at ON readings (created_at DESC); + +COMMENT ON TABLE revelations IS 'Histórico de respostas da IA na interação com o orb (pergunta + revelação).'; +COMMENT ON TABLE readings IS 'Histórico de leituras completas (mapa pessoal) por usuário.'; From 6f39a32846cc789737167f317fa7f786b207288e Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 31 Jan 2026 01:22:48 -0300 Subject: [PATCH 2/4] feat(Engine 2.1): Ayurvedic qualities, action selector, 50 estados, corpus expandido MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - types: AyurvedicQuality completo (20 gunas), diagnosis excess/deficient - diagnosisEngine: dosha → qualities (vata/pitta/kapha), prakriti no diagnóstico - ayurvedaActionSelector: prática e alimento por qualidade (ruksha→oleação, chala→grounding) - instantLightComposer: usa getActionsForQualities, retorna food na resposta - remedyMatrix: 50 estados (burnout, solitude, grief, jealousy, numbness, hypercontrol, etc.) - yoga_sutras.json: 60 entradas; puranas.json: 40; upanishads.json: 30 (kleshaTargets + qualities) - docs: seção Engine 2.1 em SACRED_REMEDY_ENGINE.md Co-authored-by: Cursor --- docs/SACRED_REMEDY_ENGINE.md | 13 +- lib/dictionaries/remedyMatrix.json | 200 +++++++++++++++++++++ lib/dictionaries/sacred/puranas.json | 38 +++- lib/dictionaries/sacred/upanishads.json | 24 ++- lib/dictionaries/sacred/yoga_sutras.json | 79 +++++++- lib/sacredRemedy/ayurvedaActionSelector.ts | 84 +++++++++ lib/sacredRemedy/diagnosisEngine.ts | 13 +- lib/sacredRemedy/index.ts | 5 + lib/sacredRemedy/instantLightComposer.ts | 11 +- lib/sacredRemedy/types.ts | 22 ++- 10 files changed, 476 insertions(+), 13 deletions(-) create mode 100644 lib/sacredRemedy/ayurvedaActionSelector.ts diff --git a/docs/SACRED_REMEDY_ENGINE.md b/docs/SACRED_REMEDY_ENGINE.md index c85eb6b..aab4024 100644 --- a/docs/SACRED_REMEDY_ENGINE.md +++ b/docs/SACRED_REMEDY_ENGINE.md @@ -92,8 +92,15 @@ Cada entrada em `yoga_sutras.json`, `puranas.json`, `upanishads.json`: --- -## 7. Próximos passos (editorial) +## 7. Engine 2.1 — Ayurveda + corpus expandido + +- **AyurvedicQuality (20 gunas)** em `types.ts`: guru/laghu, snigdha/ruksha, sita/ushna, manda/tikshna, sthira/chala, mridu/kathina, vishada/picchila, shlakshna/khara, sukshma/sthula, sandra/drava, sara. +- **Diagnosis** retorna `ayurvedicQualities.excess` e `deficient`; **prakriti/dosha** do mapa enriquece o diagnóstico (dosha → qualidades típicas em excesso). +- **Ayurveda Action Selector** (`ayurvedaActionSelector.ts`): prática e alimento concretos por qualidade (ruksha → oleação, chala → grounding, tikshna → cooling, etc.). +- **remedyMatrix.json**: 50 estados (incl. burnout, solitude, grief, jealousy, numbness, hypercontrol, shame, impatience, despair, envy, restlessness, boredom, overwhelm, isolation, perfectionism, avoidance, irritability, self_doubt, longing, acceptance). +- **Corpus sagrado**: yoga_sutras ~60 entradas, puranas ~40, upanishads ~30, todas com `kleshaTargets` e `qualities`. + +## 8. Próximos passos (editorial) -- Ampliar entradas em yoga_sutras, puranas, upanishads (mais versos + tags). - Refinar mapeamento Nakshatra → tendência → klesha provável (para diagnóstico personal mais fino). -- Testes automatizados para `diagnosisEngine`, `sacredSelector`, `composeInstantLight` e GET `/api/instant-light`. +- Testes automatizados para `diagnosisEngine`, `sacredSelector`, `ayurvedaActionSelector`, `composeInstantLight` e GET `/api/instant-light`. diff --git a/lib/dictionaries/remedyMatrix.json b/lib/dictionaries/remedyMatrix.json index fd8efa2..0b76668 100644 --- a/lib/dictionaries/remedyMatrix.json +++ b/lib/dictionaries/remedyMatrix.json @@ -298,5 +298,205 @@ "practice": "contemplação silenciosa", "food": "fresco e leve", "question": "Como servir a partir da luz?" + }, + { + "state": "burnout", + "klesha": "abhinivesha", + "samkhyaGuna": "tamas", + "qualities": ["guru", "mridu"], + "sacred": { "corpus": "purana", "id": "BP.rest", "verse": "O descanso é dharma; quem se entrega ao repouso se renova." }, + "practice": "dormir cedo, sem telas", + "food": "nutritivo, quente", + "question": "O que você pode não fazer hoje?" + }, + { + "state": "solitude", + "klesha": "raga", + "samkhyaGuna": "tamas", + "qualities": ["vishada", "laghu"], + "sacred": { "corpus": "upanishad", "id": "UP.isha_plenum", "verse": "Isto é pleno; aquilo é pleno. Do pleno nasce o pleno." }, + "practice": "uma conexão genuína hoje (uma pessoa)", + "food": "quente e reconfortante", + "question": "Você está só ou em solidão?" + }, + { + "state": "grief", + "klesha": "abhinivesha", + "samkhyaGuna": "tamas", + "qualities": ["guru", "mridu", "sandra"], + "sacred": { "corpus": "purana", "id": "BP.compassion", "verse": "A compaixão divina acolhe quem sofre; você pode ser sustentado." }, + "practice": "banho quente, mão no coração", + "food": "leite morno com especiarias", + "question": "O que precisa ser chorado?" + }, + { + "state": "jealousy", + "klesha": "dvesha", + "samkhyaGuna": "rajas", + "qualities": ["tikshna", "ushna"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.33", "verse": "Alegria pelo bem do outro acalma o coração; a comparação dissolve-se." }, + "practice": "respiração refrescante, pausa", + "food": "coco, hortelã", + "question": "O que você tem que o outro não vê?" + }, + { + "state": "numbness", + "klesha": "dvesha", + "samkhyaGuna": "tamas", + "qualities": ["sita", "sthira"], + "sacred": { "corpus": "purana", "id": "BP.heart", "verse": "Abrir o coração não é fraqueza; é coragem de sentir." }, + "practice": "gesto de bondade, água morna nas mãos", + "food": "quente, especiarias leves", + "question": "O que pode derreter um pouco hoje?" + }, + { + "state": "hypercontrol", + "klesha": "asmita", + "samkhyaGuna": "rajas", + "qualities": ["kathina", "sthira"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.2.1", "verse": "A prática constante e o desapego são os meios." }, + "practice": "soltar um plano hoje, uma só decisão", + "food": "suavizante (óleos)", + "question": "O que acontece se você não controlar?" + }, + { + "state": "shame", + "klesha": "asmita", + "samkhyaGuna": "tamas", + "qualities": ["guru", "picchila"], + "sacred": { "corpus": "upanishad", "id": "UP.forgiveness", "verse": "O perdão interno libera o peso; você pode recomeçar." }, + "practice": "mão no coração, \"eu mereço paz\"", + "food": "leve, digestivo", + "question": "Você pode recomeçar?" + }, + { + "state": "impatience", + "klesha": "raga", + "samkhyaGuna": "rajas", + "qualities": ["tikshna", "chala"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.12", "verse": "A prática e o desapego são o par que estabiliza a mente." }, + "practice": "desacelerar a fala, 3 respirações antes de agir", + "food": "doce natural, quente", + "question": "Para onde você corre?" + }, + { + "state": "despair", + "klesha": "abhinivesha", + "samkhyaGuna": "tamas", + "qualities": ["guru", "manda"], + "sacred": { "corpus": "upanishad", "id": "UP.brihadaranyaka", "verse": "Conduz-me do não-ser ao ser; conduz-me da escuridão à luz." }, + "practice": "caminhada curta, luz do sol", + "food": "pimenta preta, calor", + "question": "Qual é o menor passo possível agora?" + }, + { + "state": "envy", + "klesha": "dvesha", + "samkhyaGuna": "rajas", + "qualities": ["tikshna", "ushna"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.2.35", "verse": "Na presença de quem está firme na não-violência, a hostilidade cessa." }, + "practice": "desejar bem a alguém em silêncio", + "food": "cooling", + "question": "O que você celebra no outro?" + }, + { + "state": "restlessness", + "klesha": "raga", + "samkhyaGuna": "rajas", + "qualities": ["chala", "laghu"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.13", "verse": "O esforço constante para estabilizar a mente é a prática." }, + "practice": "uma tarefa única por 10 min, pés no chão", + "food": "grounding (raiz)", + "question": "Onde está sua atenção?" + }, + { + "state": "boredom", + "klesha": "avidya", + "samkhyaGuna": "tamas", + "qualities": ["manda", "guru"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.14", "verse": "A prática torna-se firme quando mantida por muito tempo, sem interrupção." }, + "practice": "uma ação nova (mesmo pequena) agora", + "food": "pimenta preta, calor", + "question": "O que está pedindo sua atenção?" + }, + { + "state": "overwhelm", + "klesha": "raga", + "samkhyaGuna": "rajas", + "qualities": ["chala", "sukshma"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.2", "verse": "Yoga é a cessação das flutuações da mente." }, + "practice": "olhar fixo em um ponto 1 min, depois uma só tarefa", + "food": "comida densa e quente", + "question": "Qual é a única coisa agora?" + }, + { + "state": "isolation", + "klesha": "dvesha", + "samkhyaGuna": "tamas", + "qualities": ["sita", "sthira"], + "sacred": { "corpus": "purana", "id": "BP.heart", "verse": "Abrir o coração não é fraqueza; é coragem de sentir." }, + "practice": "um gesto de bondade (mesmo pequeno)", + "food": "quente", + "question": "Quem você pode deixar entrar um pouco?" + }, + { + "state": "perfectionism", + "klesha": "asmita", + "samkhyaGuna": "rajas", + "qualities": ["tikshna", "kathina"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.2.6", "verse": "O ego é a identificação do que vê com o instrumento do ver." }, + "practice": "fazer algo \"imperfeito\" de propósito", + "food": "simples, sem excessos", + "question": "Quem é você sem o resultado?" + }, + { + "state": "avoidance", + "klesha": "abhinivesha", + "samkhyaGuna": "tamas", + "qualities": ["chala", "ruksha"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.2.9", "verse": "Observar o que se evita enfraquece seu poder." }, + "practice": "um passo mínimo em direção ao que evita", + "food": "oleação, quente", + "question": "O que você está evitando sentir?" + }, + { + "state": "irritability", + "klesha": "dvesha", + "samkhyaGuna": "rajas", + "qualities": ["tikshna", "ushna"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.33", "verse": "Compaixão em relação ao que dói acalma o coração." }, + "practice": "água fria no rosto, pausa", + "food": "coco, hortelã", + "question": "O que está por baixo da irritação?" + }, + { + "state": "self_doubt", + "klesha": "avidya", + "samkhyaGuna": "tamas", + "qualities": ["sukshma", "guru"], + "sacred": { "corpus": "upanishad", "id": "UP.tat_tvam_asi", "verse": "Isso és tu. O que você busca já é você." }, + "practice": "escrever uma qualidade sua que é verdadeira", + "food": "nutritivo e quente", + "question": "O que você já sabe que é verdade?" + }, + { + "state": "longing", + "klesha": "raga", + "samkhyaGuna": "rajas", + "qualities": ["snigdha", "sara"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.2.7", "verse": "O apego segue o prazer; o desapego nasce do discernimento." }, + "practice": "ficar consigo 10 min, sem preencher", + "food": "leve digestivo", + "question": "O que você tem agora que é suficiente?" + }, + { + "state": "acceptance", + "klesha": null, + "samkhyaGuna": "sattva", + "qualities": ["vishada", "mridu"], + "sacred": { "corpus": "yoga_sutras", "id": "YS.1.16", "verse": "Quando a consciência reconhece sua própria natureza, a aceitação nasce." }, + "practice": "contemplação silenciosa", + "food": "fresco e leve", + "question": "O que você pode aceitar neste instante?" } ] diff --git a/lib/dictionaries/sacred/puranas.json b/lib/dictionaries/sacred/puranas.json index 42850ee..160b1be 100644 --- a/lib/dictionaries/sacred/puranas.json +++ b/lib/dictionaries/sacred/puranas.json @@ -4,5 +4,41 @@ { "id": "BP.compassion", "text": "A compaixão divina acolhe quem sofre; você pode ser sustentado.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru", "mridu"] }, { "id": "BP.protection", "text": "Proteger o coração não é fechar; é escolher o que entra.", "kleshaTargets": ["abhinivesha"], "qualities": ["sukshma", "chala"] }, { "id": "BP.rest", "text": "O descanso é dharma; quem se entrega ao repouso se renova.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru", "mridu"] }, - { "id": "BP.heart", "text": "Abrir o coração não é fraqueza; é coragem de sentir.", "kleshaTargets": ["dvesha"], "qualities": ["sita", "kathina"] } + { "id": "BP.heart", "text": "Abrir o coração não é fraqueza; é coragem de sentir.", "kleshaTargets": ["dvesha"], "qualities": ["sita", "kathina"] }, + { "id": "BP.surrender1", "text": "Quem entrega o fruto da ação ao divino não carrega o peso do resultado.", "kleshaTargets": ["raga"], "qualities": ["guru", "snigdha"] }, + { "id": "BP.surrender2", "text": "A entrega dissolve o medo; o que é oferecido não pode ser perdido.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru"] }, + { "id": "BP.peace1", "text": "A paz que nasce da devoção não depende do mundo.", "kleshaTargets": ["raga", "dvesha"], "qualities": ["vishada", "laghu"] }, + { "id": "BP.peace2", "text": "Quem canta o nome do divino encontra refúgio no próprio coração.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru", "mridu"] }, + { "id": "BP.grace1", "text": "A graça não escolhe merecimento; ela escolhe o momento.", "kleshaTargets": ["asmita"], "qualities": ["kathina"] }, + { "id": "BP.grace2", "text": "Quando o coração está quebrado, a luz entra por onde não havia porta.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru", "mridu"] }, + { "id": "BP.service1", "text": "Servir sem esperar retorno libera o coração do peso do resultado.", "kleshaTargets": ["raga", "asmita"], "qualities": ["snigdha", "guru"] }, + { "id": "BP.service2", "text": "O serviço ao outro é oração em movimento.", "kleshaTargets": ["asmita"], "qualities": ["vishada"] }, + { "id": "BP.trust1", "text": "Confiar no fluxo da vida não é passividade; é coragem.", "kleshaTargets": ["abhinivesha"], "qualities": ["chala", "ruksha"] }, + { "id": "BP.trust2", "text": "O divino habita no coração de quem confia.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru"] }, + { "id": "BP.silence1", "text": "No silêncio da devoção, a resposta já está.", "kleshaTargets": ["avidya"], "qualities": ["chala", "sukshma"] }, + { "id": "BP.silence2", "text": "Quem cala a mente ouve o que as palavras não dizem.", "kleshaTargets": ["raga"], "qualities": ["chala"] }, + { "id": "BP.love1", "text": "Amar sem posse é a única forma de não perder.", "kleshaTargets": ["raga"], "qualities": ["snigdha", "guru"] }, + { "id": "BP.love2", "text": "O amor divino não exige reciprocidade; ele simplesmente flui.", "kleshaTargets": ["dvesha"], "qualities": ["sita", "kathina"] }, + { "id": "BP.release1", "text": "Soltar não é desistir; é deixar o rio correr.", "kleshaTargets": ["raga"], "qualities": ["picchila", "sthira"] }, + { "id": "BP.release2", "text": "O que você libera libera você.", "kleshaTargets": ["dvesha"], "qualities": ["guru", "picchila"] }, + { "id": "BP.presence1", "text": "O divino está no agora; quem habita o passado ou o futuro o perde.", "kleshaTargets": ["abhinivesha"], "qualities": ["chala"] }, + { "id": "BP.presence2", "text": "Uma só respiração consciente é uma oração.", "kleshaTargets": ["avidya"], "qualities": ["chala", "laghu"] }, + { "id": "BP.witness1", "text": "Quem observa sem julgar encontra paz.", "kleshaTargets": ["dvesha", "asmita"], "qualities": ["tikshna", "khara"] }, + { "id": "BP.witness2", "text": "O testemunho compassivo dissolve a raiva.", "kleshaTargets": ["dvesha"], "qualities": ["ushna", "tikshna"] }, + { "id": "BP.humility1", "text": "A humildade não diminui; ela abre espaço para o infinito.", "kleshaTargets": ["asmita"], "qualities": ["tikshna", "kathina"] }, + { "id": "BP.humility2", "text": "Quem se esvazia é preenchido.", "kleshaTargets": ["raga"], "qualities": ["vishada", "laghu"] }, + { "id": "BP.grief1", "text": "Chorar é permitir que o rio do coração encontre o mar.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru", "mridu"] }, + { "id": "BP.grief2", "text": "A tristeza honrada se transforma em compaixão.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru", "sandra"] }, + { "id": "BP.joy1", "text": "A alegria que não depende do externo é estável.", "kleshaTargets": ["raga"], "qualities": ["chala", "laghu"] }, + { "id": "BP.joy2", "text": "Celebrar o bem do outro é antídoto para a inveja.", "kleshaTargets": ["dvesha"], "qualities": ["tikshna", "ushna"] }, + { "id": "BP.patience1", "text": "A paciência é força; a pressa é medo disfarçado.", "kleshaTargets": ["raga", "abhinivesha"], "qualities": ["tikshna", "chala"] }, + { "id": "BP.patience2", "text": "O que amadurece no tempo certo não apodrece.", "kleshaTargets": ["raga"], "qualities": ["chala"] }, + { "id": "BP.simplicity1", "text": "A simplicidade desfaz o nó da cobiça.", "kleshaTargets": ["raga"], "qualities": ["guru", "snigdha"] }, + { "id": "BP.simplicity2", "text": "Menos é o caminho para mais.", "kleshaTargets": ["raga"], "qualities": ["vishada"] }, + { "id": "BP.truth1", "text": "A verdade libera; a mentira aprisiona.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "BP.truth2", "text": "Falar a verdade com amor é arte.", "kleshaTargets": ["dvesha"], "qualities": ["khara", "tikshna"] }, + { "id": "BP.rebirth1", "text": "Cada respiração é chance de recomeçar.", "kleshaTargets": ["avidya"], "qualities": ["guru", "picchila"] }, + { "id": "BP.rebirth2", "text": "O perdão é o único modo de nascer de novo sem morrer.", "kleshaTargets": ["dvesha"], "qualities": ["picchila"] }, + { "id": "BP.union1", "text": "Quem vê o divino em todos não está nunca só.", "kleshaTargets": ["abhinivesha"], "qualities": ["sita", "sthira"] }, + { "id": "BP.union2", "text": "A separação é ilusão; a união é a natureza do ser.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] } ] diff --git a/lib/dictionaries/sacred/upanishads.json b/lib/dictionaries/sacred/upanishads.json index 182b41f..b6acbd2 100644 --- a/lib/dictionaries/sacred/upanishads.json +++ b/lib/dictionaries/sacred/upanishads.json @@ -6,5 +6,27 @@ { "id": "UP.isha_plenum", "text": "Isto é pleno; aquilo é pleno. Do pleno nasce o pleno. Tomando o pleno do pleno, o pleno permanece.", "kleshaTargets": [], "qualities": ["vishada", "laghu"] }, { "id": "UP.katha_observer", "text": "Conhece o que em você observa como o condutor da carruagem; o corpo é a carruagem.", "kleshaTargets": ["avidya", "asmita"], "qualities": ["vishada"] }, { "id": "UP.mundaka_birds", "text": "Duas aves, companheiras unidas, habitam a mesma árvore; uma come o fruto, a outra observa sem comer.", "kleshaTargets": ["raga"], "qualities": ["vishada"] }, - { "id": "UP.brihadaranyaka", "text": "Conduz-me do não-ser ao ser; conduz-me da escuridão à luz; conduz-me da morte à imortalidade.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru"] } + { "id": "UP.brihadaranyaka", "text": "Conduz-me do não-ser ao ser; conduz-me da escuridão à luz; conduz-me da morte à imortalidade.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru"] }, + { "id": "UP.kena2", "text": "O que não pode ser visto pelo olho, mas pelo qual o olho vê — conhece isso como o Absoluto.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "UP.isha_interior", "text": "No interior do ser reside o Ser; quem o vê alcança a paz.", "kleshaTargets": ["avidya"], "qualities": ["vishada", "laghu"] }, + { "id": "UP.mundaka_cord", "text": "Como pássaro preso à corda, o homem preso ao corpo não vê a liberdade.", "kleshaTargets": ["raga"], "qualities": ["guru", "sthira"] }, + { "id": "UP.katha_here_there", "text": "O que está aqui está ali; o que está ali está aqui. Quem vê só a multiplicidade, da morte à morte vai.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "UP.katha_awake", "text": "Levanta-te, desperta, aproxima-te dos mestres e conhece. Afiada como o fio da navalha é a senda.", "kleshaTargets": ["avidya", "raga"], "qualities": ["manda", "guru"] }, + { "id": "UP.kena_speech", "text": "Não pelo discurso, não pela mente, não pelo olho se alcança o Ser. Só quem diz 'É Ele' o alcança.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "UP.chandogya_mustard", "text": "O Ser que reside no coração é menor que um grão de mostarda e maior que os céus.", "kleshaTargets": ["avidya"], "qualities": ["vishada", "sukshma"] }, + { "id": "UP.mundaka_becomes", "text": "Aquele que conhece o Absoluto torna-se o Absoluto.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "UP.kena_peace", "text": "A paz que está além do entendimento — assim é quem conhece o Absoluto.", "kleshaTargets": ["abhinivesha"], "qualities": ["vishada", "laghu"] }, + { "id": "UP.taittiriya_fearless", "text": "Conhecendo a bem-aventurança do Absoluto, o sábio não treme diante de nada.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru", "mridu"] }, + { "id": "UP.mandukya_om", "text": "O som que é o todo: o passado, o presente e o futuro.", "kleshaTargets": ["avidya"], "qualities": ["guru", "sandra"] }, + { "id": "UP.shvetashvatara_peace", "text": "Conhecendo o que habita no coração de todos os seres, o sábio entra na paz eterna.", "kleshaTargets": ["dvesha"], "qualities": ["vishada"] }, + { "id": "UP.katha_eternal", "text": "O Ser não nasce nem morre; não nasceu, não nascerá; eterno é.", "kleshaTargets": ["abhinivesha"], "qualities": ["vishada"] }, + { "id": "UP.isha_all_in_all", "text": "Quem vê todos os seres no Ser e o Ser em todos os seres não mais se oculta.", "kleshaTargets": ["asmita", "dvesha"], "qualities": ["vishada"] }, + { "id": "UP.katha_driver", "text": "O corpo é a carruagem; os sentidos são os cavalos; a mente é as rédeas. Quem conduz é o Ser.", "kleshaTargets": ["asmita"], "qualities": ["vishada"] }, + { "id": "UP.chandogya_heart", "text": "No coração está a morada do Ser; quem o conhece não teme.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru"] }, + { "id": "UP.brihadaranyaka_light", "text": "Conduz-me da escuridão à luz.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru", "manda"] }, + { "id": "UP.isha_plenum_short", "text": "Do pleno nasce o pleno; o pleno permanece.", "kleshaTargets": ["avidya"], "qualities": ["vishada", "laghu"] }, + { "id": "UP.katha_sharp", "text": "A senda é afiada como o fio da navalha; os sábios a atravessam.", "kleshaTargets": ["avidya", "raga"], "qualities": ["tikshna", "vishada"] }, + { "id": "UP.mundaka_two", "text": "Duas aves na mesma árvore; uma come, a outra observa. Quem observa é livre.", "kleshaTargets": ["raga"], "qualities": ["vishada", "snigdha"] }, + { "id": "UP.taittiriya_joy", "text": "O Ser é feito de bem-aventurança; quem o conhece é preenchido.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "UP.shvetashvatara_one", "text": "Um só respira em todos; quem vê a unidade entra na paz.", "kleshaTargets": ["dvesha"], "qualities": ["vishada"] } ] diff --git a/lib/dictionaries/sacred/yoga_sutras.json b/lib/dictionaries/sacred/yoga_sutras.json index 9199970..22a298f 100644 --- a/lib/dictionaries/sacred/yoga_sutras.json +++ b/lib/dictionaries/sacred/yoga_sutras.json @@ -1,15 +1,92 @@ [ { "id": "YS.1.1", "text": "Yoga é a cessação das flutuações da mente.", "kleshaTargets": ["avidya"], "qualities": ["chala", "sukshma"] }, { "id": "YS.1.2", "text": "Quando a mente está em silêncio, o observador repousa em sua própria natureza.", "kleshaTargets": ["avidya"], "qualities": ["vishada", "laghu"] }, + { "id": "YS.1.3", "text": "Então o observador repousa em sua própria natureza.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.1.4", "text": "No resto do tempo, o observador se identifica com as flutuações.", "kleshaTargets": ["asmita"], "qualities": ["chala"] }, + { "id": "YS.1.5", "text": "As flutuações são cinco: dolorosas e não dolorosas.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.1.6", "text": "São: conhecimento correto, erro, imaginação, sono e memória.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.1.7", "text": "O conhecimento correto é direto, inferência ou testemunho.", "kleshaTargets": [], "qualities": ["vishada"] }, + { "id": "YS.1.8", "text": "O erro é conhecimento falso, sem base no real.", "kleshaTargets": ["avidya"], "qualities": ["manda"] }, + { "id": "YS.1.9", "text": "A imaginação segue a palavra, sem objeto real.", "kleshaTargets": ["avidya"], "qualities": ["sukshma", "chala"] }, + { "id": "YS.1.10", "text": "O sono é a flutuação sustentada pela ausência.", "kleshaTargets": ["avidya"], "qualities": ["guru", "tamas"] }, + { "id": "YS.1.11", "text": "A memória é a retenção do experimentado.", "kleshaTargets": ["raga"], "qualities": ["picchila"] }, { "id": "YS.1.12", "text": "A prática e o desapego são o par que estabiliza a mente.", "kleshaTargets": ["raga"], "qualities": ["chala", "tikshna"] }, { "id": "YS.1.13", "text": "O esforço constante para estabilizar a mente é a prática.", "kleshaTargets": ["raga", "avidya"], "qualities": ["chala", "laghu"] }, { "id": "YS.1.14", "text": "A prática torna-se firme quando mantida por muito tempo, sem interrupção, com devoção.", "kleshaTargets": ["avidya"], "qualities": ["guru", "manda"] }, { "id": "YS.1.15", "text": "O desapego é o domínio de quem não deseja nem evita o que vê.", "kleshaTargets": ["raga", "dvesha"], "qualities": ["snigdha", "guru"] }, { "id": "YS.1.16", "text": "O ápice do desapego é quando a consciência reconhece sua própria natureza luminosa.", "kleshaTargets": [], "qualities": ["vishada", "laghu"] }, + { "id": "YS.1.17", "text": "A consciência contemplativa acompanha raciocínio, reflexão, alegria e senso de ser.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.1.18", "text": "A outra é o resíduo de prática; apenas sementes em repouso.", "kleshaTargets": ["raga"], "qualities": ["laghu"] }, + { "id": "YS.1.19", "text": "Para alguns, o estado sem nascimento vem da natureza; para outros, da entrega ao Absoluto.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru"] }, + { "id": "YS.1.20", "text": "Para os outros, a prática é fé, energia, memória, absorção e sabedoria.", "kleshaTargets": ["avidya"], "qualities": ["manda"] }, + { "id": "YS.1.21", "text": "Para os intensos, o resultado é próximo.", "kleshaTargets": ["raga"], "qualities": ["tikshna"] }, + { "id": "YS.1.22", "text": "Há diferença: suave, moderado ou intenso.", "kleshaTargets": [], "qualities": ["vishada"] }, + { "id": "YS.1.23", "text": "Ou pela entrega ao Absoluto.", "kleshaTargets": ["raga", "abhinivesha"], "qualities": ["guru"] }, + { "id": "YS.1.24", "text": "O Absoluto é um ser especial, intacto pelo sofrimento, ato ou fruto.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.1.25", "text": "Nele está a semente da onisciência.", "kleshaTargets": [], "qualities": ["vishada", "laghu"] }, + { "id": "YS.1.26", "text": "Ele é o mestre dos antigos, não limitado pelo tempo.", "kleshaTargets": ["abhinivesha"], "qualities": ["vishada"] }, + { "id": "YS.1.27", "text": "Sua expressão é o som sagrado.", "kleshaTargets": [], "qualities": ["vishada"] }, + { "id": "YS.1.28", "text": "A repetição e a contemplação desse som conduzem.", "kleshaTargets": ["chala"], "qualities": ["chala", "laghu"] }, + { "id": "YS.1.29", "text": "Daí surge o voltar-se para dentro e o desaparecimento dos obstáculos.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.1.30", "text": "Os obstáculos são: doença, apatia, dúvida, negligência, preguiça, apego ao prazer, erro, não-estabilidade.", "kleshaTargets": ["avidya", "raga"], "qualities": ["guru", "manda"] }, + { "id": "YS.1.31", "text": "Sofrimento, tristeza, tremor do corpo e respiração irregular acompanham.", "kleshaTargets": ["abhinivesha"], "qualities": ["chala", "ruksha"] }, + { "id": "YS.1.32", "text": "Para removê-los, a prática sobre um único princípio.", "kleshaTargets": ["avidya"], "qualities": ["chala"] }, { "id": "YS.1.33", "text": "Amizade, compaixão, alegria e equanimidade em relação ao que é agradável, doloroso, meritório ou não — acalmam a mente.", "kleshaTargets": ["raga", "dvesha"], "qualities": ["chala", "ruksha", "tikshna", "ushna", "khara"] }, + { "id": "YS.1.34", "text": "Ou pela expiração e retenção do sopro.", "kleshaTargets": ["chala"], "qualities": ["chala"] }, + { "id": "YS.1.35", "text": "A atividade sensorial que fixa a mente surge e estabiliza.", "kleshaTargets": ["raga"], "qualities": ["chala", "laghu"] }, + { "id": "YS.1.36", "text": "Ou a luz interior, livre de tristeza.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru", "mridu"] }, + { "id": "YS.1.37", "text": "Ou a mente que abandonou o apego.", "kleshaTargets": ["raga"], "qualities": ["snigdha", "guru"] }, + { "id": "YS.1.38", "text": "Ou o conhecimento que surge no sono e no sonho.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.1.39", "text": "Ou pela contemplação do que se deseja.", "kleshaTargets": ["raga"], "qualities": ["chala"] }, + { "id": "YS.1.40", "text": "O domínio vai do infinitamente pequeno ao infinitamente grande.", "kleshaTargets": ["asmita"], "qualities": ["vishada"] }, { "id": "YS.2.1", "text": "A prática constante e o desapego são os meios.", "kleshaTargets": ["asmita", "raga"], "qualities": ["kathina", "sthira"] }, + { "id": "YS.2.2", "text": "Eles reduzem as impurezas e permitem a luz do discernimento.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.2.3", "text": "As impurezas são: ignorância, ego, apego, aversão e medo da morte.", "kleshaTargets": ["avidya", "asmita", "raga", "dvesha", "abhinivesha"], "qualities": ["guru"] }, + { "id": "YS.2.4", "text": "A ignorância é o campo dos outros; eles podem estar adormecidos, enfraquecidos, interrompidos ou ativos.", "kleshaTargets": ["avidya"], "qualities": ["manda", "guru"] }, + { "id": "YS.2.5", "text": "A ignorância é tomar o impermanente pelo permanente, o impuro pelo puro, a dor pelo prazer, o não-eu pelo eu.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, { "id": "YS.2.6", "text": "O ego é a identificação do que vê com o instrumento do ver; reconhecer isso dissolve o orgulho.", "kleshaTargets": ["asmita"], "qualities": ["tikshna", "kathina"] }, { "id": "YS.2.7", "text": "O apego segue o prazer; o desapego nasce do discernimento.", "kleshaTargets": ["raga"], "qualities": ["tikshna", "ushna"] }, + { "id": "YS.2.8", "text": "A aversão segue a dor.", "kleshaTargets": ["dvesha"], "qualities": ["tikshna", "ushna", "khara"] }, { "id": "YS.2.9", "text": "O medo da morte habita mesmo no sábio; é inerente à natureza. Observá-lo enfraquece seu poder.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru", "sandra"] }, - { "id": "YS.2.35", "text": "Na presença de quem está firme na não-violência, a hostilidade cessa.", "kleshaTargets": ["dvesha", "asmita"], "qualities": ["tikshna", "khara"] } + { "id": "YS.2.10", "text": "Em estado sutil, eles se dissolvem pela volta à origem.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.2.11", "text": "Em estado ativo, eles se dissolvem pela meditação.", "kleshaTargets": ["raga", "dvesha"], "qualities": ["chala", "tikshna"] }, + { "id": "YS.2.12", "text": "O reservatório do karma tem a raiz nas impurezas e é experimentado no nascimento, na vida e no fruto.", "kleshaTargets": ["avidya"], "qualities": ["guru"] }, + { "id": "YS.2.13", "text": "Enquanto a raiz existir, ele amadurece em nascimento, vida e experiência.", "kleshaTargets": ["abhinivesha"], "qualities": ["guru"] }, + { "id": "YS.2.14", "text": "Eles têm como fruto o prazer ou a dor, conforme a causa: virtude ou vício.", "kleshaTargets": ["raga", "dvesha"], "qualities": ["vishada"] }, + { "id": "YS.2.15", "text": "Para quem discerne, tudo é sofrimento por causa da mudança, da ansiedade e do conflito dos gunas.", "kleshaTargets": ["raga", "dvesha"], "qualities": ["chala", "tikshna"] }, + { "id": "YS.2.16", "text": "O sofrimento que ainda não veio pode ser evitado.", "kleshaTargets": ["abhinivesha"], "qualities": ["vishada"] }, + { "id": "YS.2.17", "text": "A causa do evitável é a união do que vê com o visto.", "kleshaTargets": ["asmita"], "qualities": ["picchila"] }, + { "id": "YS.2.18", "text": "O visto tem a natureza da luz, da atividade e da inércia; é feito de elemento e órgão; existe para experiência e liberação.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.2.19", "text": "Os estágios dos gunas são o específico, o inespecífico, o diferenciado e o indiferenciado.", "kleshaTargets": [], "qualities": ["vishada"] }, + { "id": "YS.2.20", "text": "O que vê é apenas ver; puro, mas vê as imagens da mente.", "kleshaTargets": ["asmita"], "qualities": ["vishada"] }, + { "id": "YS.2.21", "text": "O visto existe apenas para ele.", "kleshaTargets": ["raga"], "qualities": ["vishada"] }, + { "id": "YS.2.22", "text": "Para quem alcançou o fim, o visto desaparece; para outros permanece por ser comum a todos.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.2.23", "text": "A união é para conhecer a natureza e o poder de ambos.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.2.24", "text": "Sua causa é a ignorância.", "kleshaTargets": ["avidya"], "qualities": ["manda"] }, + { "id": "YS.2.25", "text": "Com o desaparecimento da ignorância, a união desaparece; isso é liberação para o que vê.", "kleshaTargets": ["avidya"], "qualities": ["vishada", "laghu"] }, + { "id": "YS.2.26", "text": "O discernimento ininterrupto é o meio de liberação.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.2.27", "text": "Para ele surge a sabedoria em sete estágios.", "kleshaTargets": [], "qualities": ["vishada"] }, + { "id": "YS.2.28", "text": "Pela prática dos membros do yoga, as impurezas se reduzem; surge a luz do discernimento.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.2.29", "text": "Os membros são: contenção, disciplina, postura, controle do sopro, retração dos sentidos, concentração, meditação e absorção.", "kleshaTargets": ["raga"], "qualities": ["chala"] }, + { "id": "YS.2.30", "text": "A contenção é não violência, verdade, não roubo, continência e não cobiça.", "kleshaTargets": ["dvesha", "raga"], "qualities": ["tikshna", "khara"] }, + { "id": "YS.2.31", "text": "Esses são universais, não limitados por condição, lugar, tempo ou voto.", "kleshaTargets": [], "qualities": ["vishada"] }, + { "id": "YS.2.32", "text": "A disciplina é pureza, contentamento, austeridade, autoestudo e entrega ao Absoluto.", "kleshaTargets": ["asmita", "raga"], "qualities": ["kathina", "sthira"] }, + { "id": "YS.2.33", "text": "Quando perturbado por pensamentos contrários, cultivar os contrários.", "kleshaTargets": ["dvesha", "raga"], "qualities": ["tikshna", "ushna"] }, + { "id": "YS.2.34", "text": "Pensamentos contrários são violência etc., feitos, provocados ou aprovados por cobiça, raiva ou ilusão.", "kleshaTargets": ["dvesha", "raga"], "qualities": ["tikshna", "khara"] }, + { "id": "YS.2.35", "text": "Na presença de quem está firme na não-violência, a hostilidade cessa.", "kleshaTargets": ["dvesha", "asmita"], "qualities": ["tikshna", "khara"] }, + { "id": "YS.2.36", "text": "Para quem está firme na verdade, ação e fruto dependem dele.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.2.37", "text": "Para quem está firme no não roubo, as joias vêm.", "kleshaTargets": ["raga"], "qualities": ["vishada"] }, + { "id": "YS.2.38", "text": "Para quem está firme na continência, vigor surge.", "kleshaTargets": ["raga"], "qualities": ["snigdha", "guru"] }, + { "id": "YS.2.39", "text": "Para quem está firme no não cobiçar, surge o conhecimento do nascimento.", "kleshaTargets": ["raga"], "qualities": ["vishada"] }, + { "id": "YS.2.40", "text": "Pela pureza, surge o desgosto pelo próprio corpo e a não união com outros.", "kleshaTargets": ["raga"], "qualities": ["vishada"] }, + { "id": "YS.2.41", "text": "E também pureza da mente, contentamento, concentração, domínio dos sentidos e aptidão para ver o Ser.", "kleshaTargets": [], "qualities": ["vishada", "laghu"] }, + { "id": "YS.2.42", "text": "Do contentamento surge felicidade suprema.", "kleshaTargets": ["raga"], "qualities": ["snigdha", "guru"] }, + { "id": "YS.2.43", "text": "Pela austeridade, as impurezas do corpo se reduzem; surgem poderes dos sentidos.", "kleshaTargets": ["avidya"], "qualities": ["guru", "manda"] }, + { "id": "YS.2.44", "text": "Pelo autoestudo, união com a divindade desejada.", "kleshaTargets": ["avidya"], "qualities": ["vishada"] }, + { "id": "YS.2.45", "text": "Pela entrega ao Absoluto, a absorção.", "kleshaTargets": ["raga", "abhinivesha"], "qualities": ["guru"] }, + { "id": "YS.2.46", "text": "A postura é estável e confortável.", "kleshaTargets": ["chala"], "qualities": ["chala", "sthira"] }, + { "id": "YS.2.47", "text": "Pelo relaxamento do esforço e pela contemplação do infinito.", "kleshaTargets": ["asmita"], "qualities": ["kathina", "sthira"] }, + { "id": "YS.2.48", "text": "Daí não ser perturbado pelos pares de opostos.", "kleshaTargets": ["raga", "dvesha"], "qualities": ["vishada"] }, + { "id": "YS.2.49", "text": "A partir disso, o controle da inspiração e da expiração.", "kleshaTargets": ["chala"], "qualities": ["chala"] }, + { "id": "YS.2.50", "text": "O sopro é externo, interno ou suspenso; regulado por lugar, tempo e número; longo ou sutil.", "kleshaTargets": ["avidya"], "qualities": ["chala"] } ] diff --git a/lib/sacredRemedy/ayurvedaActionSelector.ts b/lib/sacredRemedy/ayurvedaActionSelector.ts new file mode 100644 index 0000000..5de3c33 --- /dev/null +++ b/lib/sacredRemedy/ayurvedaActionSelector.ts @@ -0,0 +1,84 @@ +/** + * Ayurveda Action Selector — práticas e alimentos concretos por qualidade ayurvédica. + * Texto cura mente; prática cura qualidade; alimento ancora no corpo. + */ + +import type { AyurvedicQuality } from "./types"; + +/** Qualidade em excesso → prática mínima recomendada (antídoto) */ +export const QUALITY_TO_PRACTICE: Record = { + ruksha: "oleação: óleo na pele ou nas narinas (2 gotas)", + chala: "grounding: pés no chão 2 min ou caminhada lenta", + tikshna: "cooling: respiração lunar ou água fria no rosto", + ushna: "refrescar: bebida à temperatura ambiente, sombra", + guru: "movimento leve: caminhada curta ou alongamento suave", + manda: "estimular: pimenta preta, calor, movimento agora", + sthira: "soltar: alongar, soltar um plano hoje", + picchila: "limpar: gargarejo, escovar língua, leveza na comida", + kathina: "suavizar: óleos, calor úmido, compaixão", + khara: "suavizar: hidratar, palavras gentis", + sukshma: "ancorar: comida densa e quente, corpo no presente", + laghu: "grounding: raiz, sopa, ritual fixo 5 min", + snigdha: "leveza digestiva: chá digestivo, não exceder", + sita: "aquecer: bebida quente, gesto de bondade", + mridu: "contenção suave: descanso, não forçar", + vishada: "nutrir: comida quente e nutritiva", + sandra: "fluir: especiarias, movimento, fluido", + drava: "estabilizar: raiz assada, rotina", + sara: "estabilizar: quente e oleoso, respiração alternada", + shlakshna: "—", + sthula: "—", +}; + +/** Qualidade em excesso → sugestão alimentar simples (antídoto) */ +export const QUALITY_TO_FOOD: Record = { + ruksha: "ghee, sopa quente, oleação", + chala: "raiz, sopa, comida quente", + tikshna: "coco, hortelã, cooling", + ushna: "coco, hortelã, doce natural", + guru: "pimenta preta, gengibre, calor", + manda: "pimenta preta, calor, leve", + sthira: "óleos, suavizante", + picchila: "especiarias leves, digestivo", + kathina: "óleos, quente", + khara: "suavizante, hidratante", + sukshma: "comida densa e quente", + laghu: "grounding (raiz), quente", + snigdha: "chá leve digestivo", + sita: "quente, reconfortante", + mridu: "nutritivo, descanso", + vishada: "nutritivo e quente", + sandra: "especiarias, fluido", + drava: "raiz assada, estável", + sara: "quente e oleoso", + shlakshna: "—", + sthula: "—", +}; + +/** + * Retorna prática sugerida para a qualidade em excesso (primeira da lista ou fallback). + */ +export function getPracticeForQuality(quality: AyurvedicQuality | string): string { + return QUALITY_TO_PRACTICE[quality] ?? ""; +} + +/** + * Retorna sugestão alimentar para a qualidade em excesso. + */ +export function getFoodForQuality(quality: AyurvedicQuality | string): string { + return QUALITY_TO_FOOD[quality] ?? ""; +} + +/** + * Dado uma lista de qualidades em excesso, retorna práticas e alimentos concretos. + * Prioriza a primeira qualidade; pode combinar até 2 sugestões. + */ +export function getActionsForQualities(qualities: (AyurvedicQuality | string)[]): { + practice: string; + food: string; +} { + const q = qualities.filter(Boolean); + const practice = q.map((x) => QUALITY_TO_PRACTICE[x]).filter(Boolean)[0] ?? ""; + const food = q.map((x) => QUALITY_TO_FOOD[x]).filter(Boolean)[0] ?? ""; + return { practice, food }; +} diff --git a/lib/sacredRemedy/diagnosisEngine.ts b/lib/sacredRemedy/diagnosisEngine.ts index 4445ebf..2942a9b 100644 --- a/lib/sacredRemedy/diagnosisEngine.ts +++ b/lib/sacredRemedy/diagnosisEngine.ts @@ -12,6 +12,7 @@ import type { RemedyMatrixEntry, SamkhyaGuna, PrakritiFromJyotish, + AyurvedicQuality, } from "./types"; import remedyMatrixJson from "@/lib/dictionaries/remedyMatrix.json"; @@ -30,6 +31,13 @@ const RASHI_TO_ELEMENT: Record = { dhanu: "fire", makara: "earth", kumbha: "air", mina: "water", }; +/** Dosha → qualidades tipicamente em excesso (Ayurveda); usadas para enriquecer diagnóstico a partir do mapa */ +const DOSHA_TO_QUALITIES_EXCESS: Record = { + vata: ["ruksha", "laghu", "chala", "sukshma", "sara"], + pitta: ["ushna", "tikshna", "drava"], + kapha: ["guru", "snigdha", "manda", "sthira", "sandra"], +}; + export function getRemedyMatrix(): RemedyMatrixEntry[] { return REMEDY_MATRIX; } @@ -53,10 +61,13 @@ function remedyToDiagnosis(remedy: RemedyMatrixEntry, prakriti?: PrakritiFromJyo const sattva = g === "sattva" ? 0.6 : 0.2; const rajas = g === "rajas" ? 0.6 : 0.2; const tamas = g === "tamas" ? 0.6 : 0.2; + const excessFromRemedy = (remedy.qualities ?? []) as AyurvedicQuality[]; + const excessFromDosha = prakriti?.dosha ? DOSHA_TO_QUALITIES_EXCESS[prakriti.dosha] ?? [] : []; + const excess = [...new Set([...excessFromRemedy, ...excessFromDosha])]; return { klesha: remedy.klesha, samkhyaGunas: { sattva, rajas, tamas }, - ayurvedicQualities: { excess: remedy.qualities ?? [], deficient: [] }, + ayurvedicQualities: { excess, deficient: [] }, prakritiFromJyotish: prakriti, stateKey: remedy.state, }; diff --git a/lib/sacredRemedy/index.ts b/lib/sacredRemedy/index.ts index 5e4ca4a..6e9714d 100644 --- a/lib/sacredRemedy/index.ts +++ b/lib/sacredRemedy/index.ts @@ -11,6 +11,11 @@ export { } from "./diagnosisEngine"; export { selectSacredText, getAllSacredEntries } from "./sacredSelector"; export type { SelectSacredOptions } from "./sacredSelector"; +export { + getPracticeForQuality, + getFoodForQuality, + getActionsForQualities, +} from "./ayurvedaActionSelector"; export { composeInstantLight } from "./instantLightComposer"; export type { ComposeInstantLightOptions } from "./instantLightComposer"; export type { diff --git a/lib/sacredRemedy/instantLightComposer.ts b/lib/sacredRemedy/instantLightComposer.ts index 4186b2d..0ef6287 100644 --- a/lib/sacredRemedy/instantLightComposer.ts +++ b/lib/sacredRemedy/instantLightComposer.ts @@ -12,6 +12,7 @@ import { getRemedyForDiagnosis, } from "./diagnosisEngine"; import { selectSacredText } from "./sacredSelector"; +import { getActionsForQualities } from "./ayurvedaActionSelector"; import type { UserProfileForOracle } from "@/lib/knowledge/types"; import type { InstantLightResponse } from "./types"; @@ -67,13 +68,19 @@ export function composeInstantLight( const sacredText = sacredEntry?.text?.trim() || remedy.sacred?.verse?.trim() || remedy.sacred?.id || ""; const sacredId = sacredEntry ? `${sacredEntry.corpus}.${sacredEntry.id}` : `${remedy.sacred?.corpus ?? "remedy"}.${remedy.sacred?.id ?? remedy.state}`; + const ayurvedaActions = getActionsForQualities(diagnosis.ayurvedicQualities.excess); + const practice = (ayurvedaActions.practice || remedy.practice || "").trim(); + const food = (ayurvedaActions.food || remedy.food || "").trim(); + const question = (remedy.question || "O que em você já sabe?").trim(); + const result: InstantLightResponse = { sacredText, - practice: remedy.practice?.trim() || "", - question: remedy.question?.trim() || "O que em você já sabe?", + practice, + question, sacredId, stateKey: diagnosis.stateKey ?? remedy.state, }; + if (food) result.food = food; if (hasProfile && profile) { const map = buildSymbolicMap(profile); diff --git a/lib/sacredRemedy/types.ts b/lib/sacredRemedy/types.ts index c5e8bf5..481f984 100644 --- a/lib/sacredRemedy/types.ts +++ b/lib/sacredRemedy/types.ts @@ -9,8 +9,19 @@ export type SamkhyaGuna = "sattva" | "rajas" | "tamas"; /** Kleśas — obstáculos da mente (Yoga Sutras) */ export type KleshaKey = "avidya" | "asmita" | "raga" | "dvesha" | "abhinivesha" | null; -/** Qualidades ayurvédicas (20) — fenomenológicas */ -export type AyurvedicQuality = string; +/** As 20 qualidades ayurvédicas (10 pares) — mente, corpo, alimento, clima, emoções */ +export type AyurvedicQuality = + | "guru" | "laghu" // pesado | leve + | "snigdha"| "ruksha" // oleoso | seco + | "sita" | "ushna" // frio | quente (usna) + | "manda" | "tikshna" // lento/obtuso | agudo + | "sthira" | "chala" // estável | móvel + | "mridu" | "kathina" // suave | duro + | "vishada"| "picchila"// claro/limpo | pegajoso + | "shlakshna"| "khara" // liso | áspero + | "sukshma"| "sthula" // sutil | grosso + | "sandra" | "drava" // denso | fluido + | "sara"; // fluido/móvel (complementar) /** Prakṛti simbólica (Jyotish) */ export type PrakritiFromJyotish = { @@ -28,7 +39,8 @@ export type SamkhyaGunas = { export type ConsciousDiagnosis = { klesha: KleshaKey; samkhyaGunas: SamkhyaGunas; - ayurvedicQualities: { excess: string[]; deficient: string[] }; + /** Qualidades em excesso e em deficiência (20 gunas ayurvédicas) */ + ayurvedicQualities: { excess: AyurvedicQuality[]; deficient: AyurvedicQuality[] }; prakritiFromJyotish?: PrakritiFromJyotish; /** Estado da matriz usado (ex.: anxiety, lethargy) */ stateKey?: string; @@ -44,7 +56,7 @@ export type SacredCorpusEntry = { qualities?: string[]; }; -/** Entrada da matriz de remédios (30 estados) */ +/** Entrada da matriz de remédios (50 estados) */ export type RemedyMatrixEntry = { state: string; klesha: KleshaKey; @@ -61,6 +73,8 @@ export type InstantLightResponse = { sacredText: string; insight?: string; practice: string; + /** Recomendação alimentar simples (Ayurveda) */ + food?: string; question: string; sacredId?: string; stateKey?: string; From 63b0f312fba864380fe28f838c29080e4858be76 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 31 Jan 2026 01:58:06 -0300 Subject: [PATCH 3/4] feat(Engine 2.1): P0 unificar engines, P1 cooldown server-side, P2 numerologia completa MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P0 — Engine único: - /api/darshan (mock) usa apenas @/lib/sacredRemedy - lib/instantLight vira re-export/adaptador (compatibilidade) P1 — Cooldown autônomo: - getRecentInstantLightIds(userEmail), recordInstantLightUse(userEmail, { sacredId, stateKey }) - Tabela instant_light_uses (migração) - Mock: se usuário logado, busca ids no servidor e registra uso P2 — Numerologia completa: - getSoulUrgeNumber(fullName) (vogais), getPersonalityNumber(fullName) (consoantes) - SymbolicMap: soulUrgeNumber, personalityNumber Docs: SACRED_REMEDY_ENGINE.md seção 8 e 9 Co-authored-by: Cursor --- app/api/darshan/route.ts | 43 +++++++++--- docs/SACRED_REMEDY_ENGINE.md | 10 ++- lib/historyStorage.ts | 39 +++++++++++ lib/instantLight/instantLightComposer.ts | 65 ++++--------------- lib/knowledge/numerology.ts | 47 ++++++++++++++ lib/symbolic/builder.ts | 6 ++ lib/symbolic/types.ts | 4 ++ .../20250129110000_instant_light_uses.sql | 15 +++++ 8 files changed, 168 insertions(+), 61 deletions(-) create mode 100644 supabase/migrations/20250129110000_instant_light_uses.sql diff --git a/app/api/darshan/route.ts b/app/api/darshan/route.ts index c0e07b9..f1f6b4e 100644 --- a/app/api/darshan/route.ts +++ b/app/api/darshan/route.ts @@ -4,7 +4,7 @@ import { getConnector } from "@/lib/ai"; import { loadMasterPrompt } from "@/lib/darshanPrompt"; import { getConfig } from "@/lib/configStore"; import { PHASE_NAMES } from "@/lib/darshan"; -import { composeInstantLight } from "@/lib/instantLight/instantLightComposer"; +import { composeInstantLight } from "@/lib/sacredRemedy"; import { getSessionFromCookie } from "@/lib/auth"; import { getCreditsFromCookie, @@ -22,7 +22,7 @@ import { import { logger } from "@/lib/logger"; import { checkAndRecordRateLimit, checkDailyLimit, recordDailyRequest } from "@/lib/usageLimits"; import { isSupabaseConfigured } from "@/lib/supabase"; -import { saveRevelation } from "@/lib/historyStorage"; +import { saveRevelation, getRecentInstantLightIds, recordInstantLightUse } from "@/lib/historyStorage"; export const dynamic = "force-dynamic"; @@ -120,17 +120,42 @@ export async function POST(req: Request) { : [...MOCK_MESSAGES, ...config.mockMessagesOverride] : MOCK_MESSAGES; - // IA desativada (mock): Instant Light híbrido — Sacred Library + Personal Insight (se houver mapa). - // Sem perfil → Universal Light (texto sagrado + prática + pergunta). Com perfil → Personal Light (+ insight e prática do mapa). + // IA desativada (mock): Sacred Remedy Engine (único composer). + // Cooldown autônomo: se usuário logado, buscar recentSacredIds/recentStateKeys no servidor e registrar uso. if (useMock) { - const { message, sacredId } = composeInstantLight(userProfile, { - recentSacredIds, + const cookieStore = await cookies(); + const session = getSessionFromCookie(cookieStore.toString()); + let recentSacredIdsRes = recentSacredIds; + let recentStateKeysRes = Array.isArray(body.recentStateKeys) + ? body.recentStateKeys.filter((k: unknown) => typeof k === "string") + : []; + if (session?.email) { + const recent = await getRecentInstantLightIds(session.email, 20); + if (recent.sacredIds.length || recent.stateKeys.length) { + recentSacredIdsRes = recent.sacredIds.length ? recent.sacredIds : recentSacredIdsRes; + recentStateKeysRes = recent.stateKeys.length ? recent.stateKeys : recentStateKeysRes; + } + } + const res = composeInstantLight(userProfile, { + recentSacredIds: recentSacredIdsRes, + recentStateKeys: recentStateKeysRes, }); + if (session?.email && res.sacredId) { + recordInstantLightUse(session.email, { sacredId: res.sacredId, stateKey: res.stateKey ?? undefined }).catch(() => {}); + } + const parts: string[] = []; + if (res.sacredText?.trim()) parts.push(res.sacredText.trim()); + if (res.insight?.trim()) parts.push(res.insight.trim()); + if (res.practice?.trim()) parts.push(res.practice.trim()); + if (res.food?.trim()) parts.push(res.food.trim()); + if (res.question?.trim()) parts.push(res.question.trim()); + const message = parts.length > 0 ? parts.join("\n\n") : getMockMessage(mockMessages); return NextResponse.json({ - message: message || getMockMessage(mockMessages), + message, phase: 1, - sacredId, - } satisfies { message: string; phase: number; sacredId?: string }); + sacredId: res.sacredId, + stateKey: res.stateKey, + } satisfies { message: string; phase: number; sacredId?: string; stateKey?: string }); } const cookieStore = await cookies(); diff --git a/docs/SACRED_REMEDY_ENGINE.md b/docs/SACRED_REMEDY_ENGINE.md index aab4024..64223c2 100644 --- a/docs/SACRED_REMEDY_ENGINE.md +++ b/docs/SACRED_REMEDY_ENGINE.md @@ -100,7 +100,15 @@ Cada entrada em `yoga_sutras.json`, `puranas.json`, `upanishads.json`: - **remedyMatrix.json**: 50 estados (incl. burnout, solitude, grief, jealousy, numbness, hypercontrol, shame, impatience, despair, envy, restlessness, boredom, overwhelm, isolation, perfectionism, avoidance, irritability, self_doubt, longing, acceptance). - **Corpus sagrado**: yoga_sutras ~60 entradas, puranas ~40, upanishads ~30, todas com `kleshaTargets` e `qualities`. -## 8. Próximos passos (editorial) +## 8. Engine 2.1 — Unificação e cooldown (pós-PR #3) + +- **P0 — Engine único:** `/api/darshan` (mock) e GET `/api/instant-light` usam apenas `lib/sacredRemedy`. `lib/instantLight` virou re-export/adaptador para compatibilidade. +- **P1 — Cooldown autônomo:** Quando o usuário está logado, o servidor busca `recentSacredIds` e `recentStateKeys` em `getRecentInstantLightIds(userEmail)` e registra uso em `recordInstantLightUse(userEmail, { sacredId, stateKey })`. Tabela `instant_light_uses` (migração `20250129110000_instant_light_uses.sql`). +- **P2 — Numerologia completa:** `getSoulUrgeNumber(fullName)` (vogais) e `getPersonalityNumber(fullName)` (consoantes) em `lib/knowledge/numerology.ts`. SymbolicMap inclui `soulUrgeNumber` e `personalityNumber`. + +## 9. Próximos passos (editorial) - Refinar mapeamento Nakshatra → tendência → klesha provável (para diagnóstico personal mais fino). +- P3: Ayurveda action selector high-end (múltiplas qualities, prioridade por dosha). +- P4: Corpus premium (Yoga Sutras 100+, Puranas 80+, Upanishads 50+). - Testes automatizados para `diagnosisEngine`, `sacredSelector`, `ayurvedaActionSelector`, `composeInstantLight` e GET `/api/instant-light`. diff --git a/lib/historyStorage.ts b/lib/historyStorage.ts index 03d26ce..756cf40 100644 --- a/lib/historyStorage.ts +++ b/lib/historyStorage.ts @@ -127,3 +127,42 @@ export async function getHistoryCounts(userEmail: string): Promise<{ revelations readings: read.count ?? 0, }; } + +/** Cooldown server-side: retorna sacredIds e stateKeys recentes do usuário (evitar repetição). */ +export async function getRecentInstantLightIds( + userEmail: string, + limit: number = 20 +): Promise<{ sacredIds: string[]; stateKeys: string[] }> { + const supabase = getSupabase(); + if (!supabase || !isSupabaseConfigured()) return { sacredIds: [], stateKeys: [] }; + const userId = await getUserIdByEmail(userEmail); + if (!userId) return { sacredIds: [], stateKeys: [] }; + const cap = Math.min(Math.max(1, limit), 50); + const { data } = await supabase + .from("instant_light_uses") + .select("sacred_id, state_key") + .eq("user_id", userId) + .order("created_at", { ascending: false }) + .limit(cap); + const rows = (data ?? []) as { sacred_id: string; state_key: string | null }[]; + return { + sacredIds: rows.map((r) => r.sacred_id).filter(Boolean), + stateKeys: rows.map((r) => r.state_key).filter((k): k is string => Boolean(k)), + }; +} + +/** Cooldown server-side: registra uso de sacredId/stateKey para o usuário (recordUse automático). */ +export async function recordInstantLightUse( + userEmail: string, + payload: { sacredId: string; stateKey?: string } +): Promise { + const supabase = getSupabase(); + if (!supabase || !isSupabaseConfigured()) return; + const userId = await getUserIdByEmail(userEmail); + if (!userId) return; + await supabase.from("instant_light_uses").insert({ + user_id: userId, + sacred_id: payload.sacredId?.trim() || "", + state_key: payload.stateKey?.trim() || null, + }); +} diff --git a/lib/instantLight/instantLightComposer.ts b/lib/instantLight/instantLightComposer.ts index f89d2da..b7af75f 100644 --- a/lib/instantLight/instantLightComposer.ts +++ b/lib/instantLight/instantLightComposer.ts @@ -1,13 +1,9 @@ /** - * Instant Light Engine — Sacred Remedy Matrix + diagnóstico (Darshan high-end). - * Retorna: verso sagrado + insight (se mapa) + prática + pergunta. Nunca aleatório puro; usa cooldown. - * Sutra/Purana selector usa klesha + samkhyaGunas; Action selector usa ayurvedicQualities. + * Instant Light — re-export do Sacred Remedy Engine (único composer). + * Mantido para compatibilidade; novo código deve usar @/lib/sacredRemedy. */ -import { getDailySeed } from "@/lib/sacred/sacredPicker"; -import { buildSymbolicMap } from "@/lib/symbolic/builder"; -import { getGeneral } from "@/lib/readings/symbolicReadings"; -import { selectRemedy } from "@/lib/diagnosis/diagnosisEngine"; +import { composeInstantLight as composeSacredRemedy } from "@/lib/sacredRemedy"; import type { UserProfileForOracle } from "@/lib/knowledge/types"; export type InstantLightProfile = { @@ -18,59 +14,26 @@ export type InstantLightProfile = { }; export type ComposeInstantLightOptions = { - /** IDs de textos sagrados recentes (evitar repetição) */ recentSacredIds?: string[]; - /** Seed para escolha (default: dailySeed + salt) */ + recentStateKeys?: string[]; seed?: number; }; -function hasProfile(profile: InstantLightProfile | undefined): boolean { - if (!profile) return false; - const name = (profile.fullName ?? "").trim(); - const date = (profile.birthDate ?? "").trim(); - return name.length > 0 || date.length > 0; -} - /** - * Compõe a mensagem Instant Light a partir da matriz de remédios (30 estados). - * - Sacred verse (da entrada selecionada) + insight do mapa (se perfil) + prática + pergunta. - * - sacredId para cooldown = corpus.id (ex.: yoga_sutras.YS.1.33). + * Compõe a mensagem Instant Light (delega ao Sacred Remedy e monta message única). + * Retorno legado: { message, sacredId } para compatibilidade com quem ainda importa daqui. */ export function composeInstantLight( - profile?: InstantLightProfile, + profile?: InstantLightProfile | null, options: ComposeInstantLightOptions = {} ): { message: string; sacredId: string } { - const { recentSacredIds = [], seed } = options; - const dailySeed = getDailySeed(); - const effectiveSeed = seed ?? dailySeed * 1000 + (Date.now() % 1000); - - const remedy = selectRemedy(profile as UserProfileForOracle | undefined, { - seed: effectiveSeed, - recentSacredIds, - }); - - const verse = remedy.sacred?.verse ?? remedy.sacred?.id ?? ""; - const sacredId = remedy.sacred?.id - ? `${remedy.sacred.corpus ?? "remedy"}.${remedy.sacred.id}` - : remedy.state; - + const res = composeSacredRemedy(profile as UserProfileForOracle | null, options); const parts: string[] = []; - if (verse.trim()) parts.push(verse.trim()); - - if (hasProfile(profile)) { - const map = buildSymbolicMap({ - fullName: profile!.fullName, - birthDate: profile!.birthDate, - birthTime: profile!.birthTime, - birthPlace: profile!.birthPlace, - }); - const insight = getGeneral(map); - if (insight.trim()) parts.push(insight.trim()); - } - - if (remedy.practice?.trim()) parts.push(remedy.practice.trim()); - if (remedy.question?.trim()) parts.push(remedy.question.trim()); - + if (res.sacredText?.trim()) parts.push(res.sacredText.trim()); + if (res.insight?.trim()) parts.push(res.insight.trim()); + if (res.practice?.trim()) parts.push(res.practice.trim()); + if (res.food?.trim()) parts.push(res.food.trim()); + if (res.question?.trim()) parts.push(res.question.trim()); const message = parts.join("\n\n").trim(); - return { message, sacredId }; + return { message, sacredId: res.sacredId ?? "" }; } diff --git a/lib/knowledge/numerology.ts b/lib/knowledge/numerology.ts index acde26f..8de9bcd 100644 --- a/lib/knowledge/numerology.ts +++ b/lib/knowledge/numerology.ts @@ -70,6 +70,53 @@ export function getExpressionNumber(fullName: string): RulingNumber { return getRulingNumberFromName(fullName); } +/** Vogais (Pitágoras): A, E, I, O, U (e equivalentes acentuados). */ +const VOWELS = new Set(["A", "E", "I", "O", "U"]); + +/** + * Soul Urge Number (vogais do nome) — desejo interior, motivação profunda. + * Soma apenas as vogais do nome completo; reduz a 1–9 ou 11/22. + */ +export function getSoulUrgeNumber(fullName: string): RulingNumber { + const name = (fullName || "").toUpperCase().replace(/\s+/g, "").replace(/[^A-ZÀ-Ú]/g, ""); + if (!name.length) return 7; + let sum = 0; + for (const char of name) { + const n = char.normalize("NFD").replace(/\p{Diacritic}/gu, "").toUpperCase(); + if (!VOWELS.has(n)) continue; + const v = LETTER_VALUES[n] ?? LETTER_VALUES[char.toUpperCase()]; + if (v) sum += v; + } + if (sum === 0) return 7; + while (sum > 99) { + sum = String(sum).split("").reduce((s, d) => s + parseInt(d, 10), 0); + } + if (sum === 11 || sum === 22) return sum as 11 | 22; + return reduceToDigit(sum) as RulingNumber; +} + +/** + * Personality Number (consoantes do nome) — como os outros te veem, máscara social. + * Soma apenas as consoantes do nome completo; reduz a 1–9 ou 11/22. + */ +export function getPersonalityNumber(fullName: string): RulingNumber { + const name = (fullName || "").toUpperCase().replace(/\s+/g, "").replace(/[^A-ZÀ-Ú]/g, ""); + if (!name.length) return 7; + let sum = 0; + for (const char of name) { + const n = char.normalize("NFD").replace(/\p{Diacritic}/gu, "").toUpperCase(); + if (VOWELS.has(n)) continue; + const v = LETTER_VALUES[n] ?? LETTER_VALUES[char.toUpperCase()]; + if (v) sum += v; + } + if (sum === 0) return 7; + while (sum > 99) { + sum = String(sum).split("").reduce((s, d) => s + parseInt(d, 10), 0); + } + if (sum === 11 || sum === 22) return sum as 11 | 22; + return reduceToDigit(sum) as RulingNumber; +} + /** Entrada do dicionário por número: tendências e frases para o oráculo */ export type NumberTraitsEntry = { number: RulingNumber; diff --git a/lib/symbolic/builder.ts b/lib/symbolic/builder.ts index 7aa1a70..25928bb 100644 --- a/lib/symbolic/builder.ts +++ b/lib/symbolic/builder.ts @@ -10,6 +10,8 @@ import { getRulingNumberFromName, getLifePathNumber, getExpressionNumber, + getSoulUrgeNumber, + getPersonalityNumber, } from "@/lib/knowledge/numerology"; import type { SymbolicMap } from "./types"; @@ -21,6 +23,8 @@ export function buildSymbolicMap(profile: UserProfileForOracle): SymbolicMap { const rulingNumber = getRulingNumberFromName(profile.fullName ?? ""); const lifePathNumber = getLifePathNumber(profile.birthDate ?? ""); const expressionNumber = getExpressionNumber(profile.fullName ?? ""); + const soulUrgeNumber = getSoulUrgeNumber(profile.fullName ?? ""); + const personalityNumber = getPersonalityNumber(profile.fullName ?? ""); const archetypeKeys = chart.archetypeKeys ?? ["dissolvente"]; const primary = archetypeKeys[0] ?? "dissolvente"; @@ -35,6 +39,8 @@ export function buildSymbolicMap(profile: UserProfileForOracle): SymbolicMap { rulingNumber: rulingNumber as number, lifePathNumber: lifePathNumber as number, expressionNumber: expressionNumber as number, + soulUrgeNumber: soulUrgeNumber as number, + personalityNumber: personalityNumber as number, }, archetypes: { primary, diff --git a/lib/symbolic/types.ts b/lib/symbolic/types.ts index 0e37d16..4fa4441 100644 --- a/lib/symbolic/types.ts +++ b/lib/symbolic/types.ts @@ -17,6 +17,10 @@ export type SymbolicMap = { lifePathNumber?: number; /** Expression/Destiny — nome completo (1–9, 11, 22). */ expressionNumber?: number; + /** Soul Urge — vogais do nome (desejo interior). */ + soulUrgeNumber?: number; + /** Personality — consoantes do nome (máscara social). */ + personalityNumber?: number; }; /** Arquétipos derivados do chart (primary = primeiro; keys = lista completa). */ archetypes: { diff --git a/supabase/migrations/20250129110000_instant_light_uses.sql b/supabase/migrations/20250129110000_instant_light_uses.sql new file mode 100644 index 0000000..a401d77 --- /dev/null +++ b/supabase/migrations/20250129110000_instant_light_uses.sql @@ -0,0 +1,15 @@ +-- Cooldown server-side para Instant Light: registra uso de sacredId/stateKey por usuário. +-- Usado por getRecentInstantLightIds e recordInstantLightUse (historyStorage). + +CREATE TABLE IF NOT EXISTS instant_light_uses ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users (id) ON DELETE CASCADE, + sacred_id TEXT NOT NULL, + state_key TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT now() +); + +CREATE INDEX IF NOT EXISTS idx_instant_light_uses_user_id ON instant_light_uses (user_id); +CREATE INDEX IF NOT EXISTS idx_instant_light_uses_created_at ON instant_light_uses (created_at DESC); + +COMMENT ON TABLE instant_light_uses IS 'Uso recente de textos/estados do Instant Light por usuário (cooldown server-side).'; From c4b83aba3f9b3605d700e4f0fcb7d7688c1d30b4 Mon Sep 17 00:00:00 2001 From: Raphael Date: Sat, 31 Jan 2026 02:03:52 -0300 Subject: [PATCH 4/4] =?UTF-8?q?docs:=20valida=C3=A7=C3=A3o=20Engine=202.1?= =?UTF-8?q?=20+=20deprecia=C3=A7=C3=A3o=20lib/diagnosis=20e=20lib/sacred?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ENGINE_2.1_VALIDATION.md: status P0/P1/P2 implementados, módulos legados - SACRED_REMEDY_ENGINE: matriz 50 estados, darshan mock usa sacredRemedy + cooldown - lib/diagnosis e lib/sacred: comentários apontando engine oficial sacredRemedy Co-authored-by: Cursor --- docs/ENGINE_2.1_VALIDATION.md | 51 +++++++++++++++++++++++++++++++++++ docs/SACRED_REMEDY_ENGINE.md | 4 +-- lib/diagnosis/index.ts | 3 +++ lib/sacred/index.ts | 3 +++ 4 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 docs/ENGINE_2.1_VALIDATION.md diff --git a/docs/ENGINE_2.1_VALIDATION.md b/docs/ENGINE_2.1_VALIDATION.md new file mode 100644 index 0000000..111c21b --- /dev/null +++ b/docs/ENGINE_2.1_VALIDATION.md @@ -0,0 +1,51 @@ +# Validação Engine 2.1 — status pós-PR #3 + +Estado atual do repositório após os commits do PR #3 (incl. unificação, cooldown e numerologia). + +--- + +## ✅ Implementado + +| Módulo | Status | Observação | +|--------|--------|------------| +| Symbolic Map Engine | ✅ | `lib/symbolic/`, `lib/engines/buildSymbolicMap.ts` | +| Sacred Remedy Engine | ✅ | **Engine oficial único:** `lib/sacredRemedy/` | +| Remedy Matrix (50 estados) | ✅ | `lib/dictionaries/remedyMatrix.json` | +| Corpus sagrado expandido | ✅ | yoga_sutras ~60, puranas ~40, upanishads ~30 (kleshaTargets + qualities) | +| Ayurveda Qualities (20 gunas) | ✅ | `lib/sacredRemedy/types.ts` + ayurvedaActionSelector | +| Instant Light Universal + Personal | ✅ | GET `/api/instant-light`; POST `/api/darshan` (mock) usa sacredRemedy | +| Readings modulares | ✅ | `/api/reading/*` (general, love, career, year) | +| Histórico e anti-repetição | ✅ | historyStorage, instant_light_uses, modal UI | +| Swiss Provider scaffold | ✅ | `lib/core/providers/swissProvider.ts` | +| **Unificação sem duplicação** | ✅ | **P0:** Apenas `lib/sacredRemedy` é o motor; `/api/darshan` e `/api/instant-light` importam só daqui. `lib/instantLight` é wrapper de compatibilidade. | +| **Numerologia profunda** | ✅ | **P1:** lifePathNumber (data), expressionNumber (nome), soulUrgeNumber (vogais), personalityNumber (consoantes) em `lib/knowledge/numerology.ts`; SymbolicMap inclui os quatro. | +| **Cooldown autônomo server-side** | ✅ | **P2:** Em `/api/darshan` (mock), se usuário logado: servidor chama `getRecentInstantLightIds(session.email)` e `recordInstantLightUse(session.email, { sacredId, stateKey })`. Tabela `instant_light_uses`. | + +--- + +## Módulos legados (não usados pelo fluxo Instant Light) + +- **`lib/diagnosis/`** — Cópia antiga do diagnosis; o fluxo usa `lib/sacredRemedy/diagnosisEngine.ts`. Mantido por referência; engine oficial é sacredRemedy. +- **`lib/sacred/`** — Sacred Picker (classicTexts); o fluxo Instant Light usa `lib/sacredRemedy/sacredSelector.ts` + `dictionaries/sacred/*.json`. Mantido por referência. +- **`lib/instantLight/`** — Re-export do sacredRemedy; retorna `{ message, sacredId }` para compatibilidade. Novo código deve usar `@/lib/sacredRemedy`. + +--- + +## ⚠️ Parcial / próximo passo + +| Item | Status | Próximo passo | +|------|--------|--------------| +| Human Design | ⚠️ Scaffold | humanDesignEngine.ts e humanDesignInsights existem; aprofundar quando integrar HD de fato. | +| Ayurveda action selector high-end | ⚠️ MVP | P3: antídotos para as 20 qualities completos, múltiplas qualities, prioridade por dosha. | +| Corpus premium | ⚠️ ~60/40/30 | P4: expandir para Sutras 100+, Puranas 80+, Upanishads 50+. | +| GET /api/instant-light com cooldown server-side | ⚠️ Cliente opcional | Endpoint não exige auth; cooldown server-side só em `/api/darshan` (mock) quando há sessão. Para instant-light autônomo seria preciso auth. | + +--- + +## Resumo + +- **Engine único:** `composeInstantLight()` está apenas em `lib/sacredRemedy`; chamado por `/api/darshan` (mock) e GET `/api/instant-light`. +- **Numerologia:** life path, expression, soul urge, personality no SymbolicMap. +- **Cooldown:** server-side em `/api/darshan` (mock) quando o usuário está logado; `instant_light_uses` + `getRecentInstantLightIds` / `recordInstantLightUse`. + +Engine 2.1 está fechado para P0, P1 e P2; P3 (Ayurveda high-end) e P4 (corpus premium) são evolução editorial. diff --git a/docs/SACRED_REMEDY_ENGINE.md b/docs/SACRED_REMEDY_ENGINE.md index 64223c2..1cc97c0 100644 --- a/docs/SACRED_REMEDY_ENGINE.md +++ b/docs/SACRED_REMEDY_ENGINE.md @@ -9,7 +9,7 @@ Motor **paralelo** ao fluxo atual do Darshan. Não substitui `/api/darshan`; adi - **Diagnóstico consciente:** klesha + samkhya guna + qualidades ayurvédicas (excesso/deficiência). - **Corpus sagrado taggeado:** yoga_sutras, puranas, upanishads com `kleshaTargets` e `qualities`. - **Seleção dirigida:** texto sagrado escolhido por klesha e qualidades (anti-repetição por `avoidIds`). -- **Matriz de remédios (30 estados):** cada estado mapeia klesha, samkhyaGuna, qualidades, prática, alimento, pergunta. +- **Matriz de remédios (50 estados):** cada estado mapeia klesha, samkhyaGuna, qualidades, prática, alimento, pergunta. --- @@ -86,7 +86,7 @@ Cada entrada em `yoga_sutras.json`, `puranas.json`, `upanishads.json`: ## 6. Relação com o resto do produto -- **`/api/darshan` (POST, mock/IA):** continua como está (IA + fallback; 7 fases; `getOfflineRevelation` etc.). **Não foi alterado.** +- **`/api/darshan` (POST, mock/IA):** no mock usa **apenas** Sacred Remedy (`composeInstantLight` de `lib/sacredRemedy`); cooldown server-side quando usuário logado. - **`/api/instant-light` (GET):** motor novo, só offline, só Sacred Remedy (diagnóstico + corpus taggeado + matriz + prática + pergunta). - **Leitura offline narrativa (`readingOffline`, `oracleOffline`):** segue separada; o Instant Light do Sacred Remedy é outra camada (resposta estruturada: sacredText, insight, practice, question). diff --git a/lib/diagnosis/index.ts b/lib/diagnosis/index.ts index 26f6000..25079ce 100644 --- a/lib/diagnosis/index.ts +++ b/lib/diagnosis/index.ts @@ -1,6 +1,9 @@ /** * Diagnosis Engine — diagnóstico consciente e matriz de remédios (Sacred Remedy Matrix). * Camada 1: Prakṛti (Jyotish). Camada 2: Sāṃkhya Guṇas. Camada 3: Ayurvedic Qualities. + * + * @deprecated Engine oficial: lib/sacredRemedy (diagnosisEngine.ts). Este módulo é legado; + * o fluxo Instant Light usa apenas sacredRemedy. Mantido por referência. */ export { diff --git a/lib/sacred/index.ts b/lib/sacred/index.ts index ef288a1..f13a377 100644 --- a/lib/sacred/index.ts +++ b/lib/sacred/index.ts @@ -1,6 +1,9 @@ /** * Sacred Library Engine — textos sagrados (Upanishads, Yoga Sutras, Bhagavad Gita). * pickSacredText: rotação determinística, cooldown, tags temáticas. + * + * Para Instant Light medicinal use lib/sacredRemedy (sacredSelector + dictionaries/sacred/*.json). + * Este módulo usa classicTexts; mantido para outros fluxos. */ export { pickSacredText, getDailySeed } from "./sacredPicker";