Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ MetaMap is a TypeScript + React (Next.js 14 App Router) web application for expl
- ✅ **Visualisations** (timeline, compass, network, heatmap) powered by D3 and CSS variable palettes.
- ✅ **Adjustable weights** (default HD 0.6, GK 0.5, others 1.0) stored in localStorage.
- ✅ **RNG tools** for I Ching, Tarot, and Geomancy using `crypto.getRandomValues`.
- ✅ **Live calculator demos** for ephemeris, Chinese calendar, Zi Wei Dou Shu, Qi Men Dun Jia, Feng Shui, Human Design, and Gene Keys.
- ✅ **Plug-in ready calculators** with TypeScript interfaces under `src/calculators`.
- ✅ **Accessibility & responsiveness** (WCAG AA focus styles, prefers-reduced-motion support).

Expand Down Expand Up @@ -40,7 +41,7 @@ Docker users can run `docker compose up --build` for a Node 20 Alpine environmen

### Provider registry

The app ships with demo providers for Ephemeris, Chinese calendar, Zi Wei Dou Shu, and Qi Men Dun Jia when `NEXT_PUBLIC_ENABLE_DEMO_PROVIDERS=true` (enabled by default in non-production). Register licensed providers by calling `registerProvider` in `src/providers/bootstrap.ts` or supplying your own bootstrap module. Production builds should disable the demo flag.
The app ships with demo providers for Ephemeris, Chinese calendar, Zi Wei Dou Shu, Qi Men Dun Jia, Feng Shui, Human Design, and Gene Keys when `NEXT_PUBLIC_ENABLE_DEMO_PROVIDERS=true` (enabled by default in non-production). Register licensed providers by calling `registerProvider` in `src/providers/bootstrap.ts` or supplying your own bootstrap module. Production builds should disable the demo flag.

### Testing in CI

Expand Down Expand Up @@ -84,7 +85,7 @@ Utility helpers (`src/lib`) cover intervals, direction mapping, CSV serializatio
2. Use the **Import data** panel on the overview (`/`) to append or replace rows. Zod validates every line and surfaces row-level errors.
3. **Export data** downloads the currently filtered dataset to CSV/JSON, maintaining schema ordering and ISO timestamps.

Sample starter file lives at `public/sample.csv` with representative rows spanning astrology, Feng Shui, BaZi, numerology, and Tarot (including a `privacy:paid` note example).
Sample starter file lives at `public/sample.csv` with representative rows spanning natal astrology, Jyotiṣa, Feng Shui, BaZi, Qi Men Dun Jia, Human Design, Gene Keys, numerology, and Tarot (including a `privacy:paid` note example).

---

Expand Down
19 changes: 18 additions & 1 deletion next.config.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
/* config options here */
output: "standalone",
experimental: {
serverComponentsExternalPackages: ["swisseph"],
},
webpack: (config, { isServer }) => {
if (isServer) {
const existing = config.externals ?? [];
const externals = Array.isArray(existing) ? [...existing] : [existing];

if (!externals.includes("swisseph")) {
externals.push("swisseph");
}

config.externals = externals;
}

return config;
},
};

export default nextConfig;
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@
"dependencies": {
"@tanstack/react-virtual": "^3.10.8",
"@vvo/tzdb": "^6.128.0",
"astronomy-engine": "^2.1.19",
"clsx": "^2.1.1",
"d3": "^7.9.0",
"luxon": "^3.5.0",
"next": "16.0.0",
"papaparse": "^5.4.1",
"react": "19.2.0",
"react-dom": "19.2.0",
"solarlunar": "^2.0.7",
"swisseph": "^0.5.17",
"zod": "^3.23.8",
"zustand": "^5.0.1"
Expand Down
16 changes: 10 additions & 6 deletions public/sample.csv
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
person_id,birth_datetime_local,birth_timezone,system,subsystem,source_tool,source_url_or_ref,data_point,verbatim_text,category,subcategory,direction_cardinal,direction_degrees,timing_window_start,timing_window_end,polarity,strength,confidence,weight_system,notes
default-person,1992-09-01T06:03:00,Australia/Sydney,WA,"Tropical · Placidus","Swiss Ephemeris",https://example.com/swiss-ephemeris,"Sun 08° Virgo","Sun at 08° Virgo in 1st house",Personality,"Core Self",,,,,+,2,0.9,1.0,"privacy:paid; source:Swiss Ephemeris"
default-person,1992-09-01T06:03:00,Australia/Sydney,FS,"Period 8 Flying Stars","Feng Shui Toolkit",,Facing North prosperity sector,UNKNOWN,Property,"Wealth Sector",N,0,,,+,1,0.7,1.0,"period:8; facing:0"
default-person,1992-09-01T06:03:00,Australia/Sydney,BaZi,"Standard HKO","Lunisolar Calculator",https://example.com/hko,"BaZi Day Pillar Ji-Si (己巳)","Ji-Si (己巳) day stem/branch",Timing,"Day Master",,,,,+,1,0.8,1.0,"variant:solar-lunar"
default-person,1992-09-01T06:03:00,Australia/Sydney,Numerology_Pythagorean,,"MetaMap numerology",,Life Path 13/4,Life Path 13/4,Personality,"Life Path",,,,,+,1,0.6,1.0,"auto-calculated:numerology"
default-person,1992-09-01T06:03:00,Australia/Sydney,Tarot,"Celtic Cross","MetaMap RNG",,The Sun,UNKNOWN,Guidance,Outcome,,,2024-03-01T10:15:00+11:00,2024-03-01T10:15:00+11:00,+,0,0.5,1.0,"spread:celtic; position:Outcome"
person_id,birth_datetime_local,birth_timezone,system,subsystem,source_tool,source_url_or_ref,data_point,verbatim_text,category,subcategory,direction_cardinal,direction_degrees,timing_window_start,timing_window_end,polarity,strength,confidence,weight_system,notes
default-person,1992-09-01T06:03:00,Australia/Sydney,WA,Sidereal · Lahiri,Astronomy Engine Ephemeris,,Sun 08° Virgo,Sun at 08° Virgo in 1st house,Personality,Core Self,,,,,+,2,0.9,1.0,source:astronomy-engine
default-person,1992-09-01T06:03:00,Australia/Sydney,JA,Lahiri,Astronomy Engine Ephemeris,,Nakṣatra Chitrā,Moon longitude 186.49° (sidereal),Timing,Nakṣatra,,,,,+,0,0.8,1.0,ayanamsa:lahiri
default-person,1992-09-01T06:03:00,Australia/Sydney,FS,Period 8,Traditional Flying Star,,North palace prosperity,Star 9 (base 1 / period 8),Direction,Flying Star,N,0,,,+,1,0.7,1.0,period:8; facing:0
default-person,1992-09-01T06:03:00,Australia/Sydney,BaZi,Standard,SolarLunar Chinese Calendar,,Day pillar Ji-Si (己巳),Ji-Si (己巳) stem/branch,Timing,Day Master,,,,,+,1,0.85,1.0,gender:unspecified
default-person,1992-09-01T06:03:00,Australia/Sydney,QMDJ,Zhi Run · yang,Lo Shu QMDJ,,Center palace,Wu | Open | Chief,Guidance,Palace,,,,,+,0,0.7,1.0,arrangement:yang
default-person,1992-09-01T06:03:00,Australia/Sydney,HD,BodyGraph,Human Design Gate Mapper,,Type,Manifesting Generator,Personality,Type,,,,,+,0,0.7,0.6,authority:Sacral
default-person,1992-09-01T06:03:00,Australia/Sydney,GK,Life's Work,Gene Keys Profile,,Gene Key 29,Line 2,Guidance,Sphere,,,,,+,0,0.6,0.5,sequence:Activation
default-person,1992-09-01T06:03:00,Australia/Sydney,Numerology_Pythagorean,,MetaMap numerology,,Life Path 13/4,Life Path 13/4,Personality,Life Path,,,,,+,1,0.6,1.0,auto-calculated:numerology
default-person,1992-09-01T06:03:00,Australia/Sydney,Tarot,Celtic Cross,MetaMap RNG,,The Sun,UNKNOWN,Guidance,Outcome,,,2024-03-01T10:15:00+11:00,2024-03-01T10:15:00+11:00,+,0,0.5,1.0,spread:celtic; position:Outcome
83 changes: 82 additions & 1 deletion src/app/api/providers/[provider]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,14 @@ import { NextResponse } from "next/server";
import { DateTime } from "luxon";
import { getProvider, ProviderUnavailableError, type ProviderKey } from "@/providers";
import { ensureProvidersBootstrapped } from "@/providers/bootstrap";
import type { ChineseCalendarProvider, QMDJProvider, ZWDSProvider } from "@/calculators";
import type {
ChineseCalendarProvider,
FSProvider,
GKProvider,
HDProvider,
QMDJProvider,
ZWDSProvider,
} from "@/calculators";

type ProviderParams = {
params: { provider: string };
Expand Down Expand Up @@ -177,6 +184,80 @@ export async function POST(request: Request, { params }: ProviderParams) {
return NextResponse.json({ board });
}

if (params.provider === "fs") {
const payload = await request.json().catch(() => null) as {
sittingDegrees?: number;
facingDegrees?: number;
period?: number;
birthYear?: number;
gender?: "female" | "male" | "unspecified";
} | null;

if (
payload == null ||
typeof payload.sittingDegrees !== "number" ||
typeof payload.facingDegrees !== "number" ||
typeof payload.period !== "number" ||
typeof payload.birthYear !== "number" ||
(payload.gender && !["female", "male", "unspecified"].includes(payload.gender))
) {
return NextResponse.json({ error: "Missing or invalid Feng Shui payload." }, { status: 400 });
}

const provider = getProvider("fs") as FSProvider;
const [flyingStars, eightMansions] = await Promise.all([
provider.computeFlyingStars({
sittingDegrees: payload.sittingDegrees,
facingDegrees: payload.facingDegrees,
period: payload.period,
}),
provider.computeEightMansions({
birthYear: payload.birthYear,
gender: payload.gender ?? "unspecified",
}),
]);

return NextResponse.json({ flyingStars, eightMansions });
}

if (params.provider === "hd") {
const payload = await request.json().catch(() => null) as {
birthIso?: string;
timezone?: string;
} | null;

if (!payload?.birthIso || !payload.timezone) {
return NextResponse.json({ error: "Missing birthIso or timezone for Human Design request." }, { status: 400 });
}

const provider = getProvider("hd") as HDProvider;
const bodyGraph = await provider.computeBodyGraph({
birthDateTime: payload.birthIso,
timezone: payload.timezone,
});

return NextResponse.json(bodyGraph);
}

if (params.provider === "gk") {
const payload = await request.json().catch(() => null) as {
birthIso?: string;
timezone?: string;
} | null;

if (!payload?.birthIso || !payload.timezone) {
return NextResponse.json({ error: "Missing birthIso or timezone for Gene Keys request." }, { status: 400 });
}

const provider = getProvider("gk") as GKProvider;
const profile = await provider.computeProfile({
birthDateTime: payload.birthIso,
timezone: payload.timezone,
});

return NextResponse.json(profile);
}

return NextResponse.json(
{
status: "not_implemented",
Expand Down
60 changes: 57 additions & 3 deletions src/app/systems/bazi/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,16 @@ const BaZiPage = () => {
providerLoading,
providerErrors,
clearProviderError,
appendRow,
pruneRows,
} = useStore((state) => ({
birthDetails: state.birthDetails,
invokeProvider: state.invokeProvider,
providerLoading: state.providerLoading,
providerErrors: state.providerErrors,
clearProviderError: state.clearProviderError,
appendRow: state.appendRow,
pruneRows: state.pruneRows,
}));

const [pillars, setPillars] = useState<BaZiPillar[] | null>(null);
Expand All @@ -59,6 +63,56 @@ const BaZiPage = () => {
if (response.status === 200 && response.data) {
setPillars(response.data.pillars);
setLuckPillars(response.data.luckPillars);
const birthIso = `${birthDetails.birthDate}T${birthDetails.birthTime}`;
pruneRows((row) => row.system === "BaZi");
response.data.pillars.forEach((pillar) => {
appendRow({
person_id: "default-person",
birth_datetime_local: birthIso,
birth_timezone: birthDetails.timezone,
system: "BaZi",
subsystem: requestPayload.variant,
source_tool: "chineseCalendar",
source_url_or_ref: "",
data_point: `${pillarLabel(pillar.pillar)} pillar`,
verbatim_text: `${pillar.heavenlyStem} · ${pillar.earthlyBranch} (Hidden: ${pillar.hiddenStems.join(", ")})`,
category: "Timing",
subcategory: "Pillar",
direction_cardinal: "",
direction_degrees: null,
timing_window_start: null,
timing_window_end: null,
polarity: "+",
strength: 0,
confidence: 0.85,
weight_system: 1,
notes: `pillar=${pillar.pillar}`,
});
});
response.data.luckPillars.forEach((luck) => {
appendRow({
person_id: "default-person",
birth_datetime_local: birthIso,
birth_timezone: birthDetails.timezone,
system: "BaZi",
subsystem: `${requestPayload.variant} luck`,
source_tool: "chineseCalendar",
source_url_or_ref: "",
data_point: `Luck pillar ${luck.index + 1}`,
verbatim_text: `${luck.pillar.heavenlyStem} · ${luck.pillar.earthlyBranch} (start age ${luck.startingAge})`,
category: "Timing",
subcategory: "Luck",
direction_cardinal: "",
direction_degrees: null,
timing_window_start: null,
timing_window_end: null,
polarity: "+",
strength: 0,
confidence: 0.8,
weight_system: 1,
notes: `duration=${luck.durationYears}y`,
});
});
}
};

Expand All @@ -68,11 +122,11 @@ const BaZiPage = () => {
description="Compute Heavenly Stems and Earthly Branches using the configured Chinese calendar provider."
>
<WarningBanner
title={pillars ? "Demo output" : "Provider required"}
title={pillars ? "BaZi pillars computed" : "Awaiting provider"}
description={
pillars
? "Demo data provided for development environments. Replace with licensed provider for production use."
: "Connect a ChineseCalendarProvider to derive sexagenary stems, branches, and luck cycles."
? "Sexagenary stems and branches are derived from the configured Chinese calendar provider."
: "Provide accurate birth details then compute to populate the pillars and decadal cycles."
}
/>
<section className="grid gap-6 md:grid-cols-[1.2fr_1fr]">
Expand Down
Loading