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
10 changes: 10 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Toggle demo providers (ephemeris, Chinese calendar, Zi Wei Dou Shu, etc.)
# Default: enabled in development, disabled in production builds.
NEXT_PUBLIC_ENABLE_DEMO_PROVIDERS=true

# Optional Swiss Ephemeris configuration
SWISSEPH_DATA_PATH=""
SWISSEPH_JPL_FILE=""
SWISSEPH_ENGINE="swiss"
SWISSEPH_LICENSE_KEY=""
SWISSEPH_LICENSE_FILE=""
33 changes: 33 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: CI

on:
push:
branches: [main]
pull_request:

jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4

- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: 20
cache: npm

- name: Install dependencies
run: npm ci

- name: Run lint
run: npm run lint

- name: Run unit tests
run: npm test -- --run

- name: Build application
run: npm run build
env:
NEXT_PUBLIC_ENABLE_DEMO_PROVIDERS: "false"
8 changes: 4 additions & 4 deletions IMPLEMENTATION_PLAN.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ This document captures the implementation roadmap for finishing the MetaMap Next
- Remove already-generated `.next/` and `test-results/` directories from the repo history/worktree (delete locally; do not commit generated artefacts).

4. **Enrich sample data**
- Populate `public/sample.csv` with 3–5 representative rows covering different systems, directions, timing windows, and `privacy:paid` note usage.
- Populate `public/sample.csv` with 3–5 representative rows covering different systems, directions, timing windows, and `privacy` column usage (public/internal/paid).
- Update README import instructions to reference the richer sample.

5. **Add basic smoke tests**
Expand All @@ -42,7 +42,7 @@ Estimated effort: ~1 day.
## 3. Core Goals for Completion

- Integrate real calculator providers for each `UNKNOWN` system (ephemeris, Chinese calendar, Zi Wei Dou Shu, Qi Men Dun Jia, Feng Shui, Human Design, Gene Keys).
- Persist calculator outputs into the dataset via a consistent interface (with provenance, subsystem tagging, `privacy:paid` annotations).
- Persist calculator outputs into the dataset via a consistent interface (with provenance, subsystem tagging, and `privacy` annotations).
- Provide UI feedback states (loading, error, variant indicators) once real data is produced.
- Expand testing to cover calculators, normalisation edge cases, and E2E flows.
- Prepare deployment artefacts (Docker image, CI pipeline) and documentation for operators/users.
Expand Down Expand Up @@ -112,7 +112,7 @@ Estimated effort: ~1 day.
3. Align Life Gua calculation with provider results to avoid duplication.

#### 2.6 Human Design (HD)
1. Integrate BodyGraph provider (likely requires 3rd-party API: Jovian Archive, Genetic Matrix). Respect licensing; add `privacy:paid`.
1. Integrate BodyGraph provider (likely requires 3rd-party API: Jovian Archive, Genetic Matrix). Respect licensing; set `privacy` to `paid`.
2. Update UI to show defined centres, type, authority. Consider dynamic SVG.
3. Since data may be sensitive/licensed, gate behind configuration flag.

Expand Down Expand Up @@ -191,7 +191,7 @@ For each provider:

## 5. Implementation Notes & Risks

- **Licensing:** Several providers (Swiss Ephemeris, HD, GK) may require paid licenses. Honour `privacy:paid` flags and document limitations.
- **Licensing:** Several providers (Swiss Ephemeris, HD, GK) may require paid licenses. Honour `privacy=paid` flags and document limitations.
- **Timezones:** Ensure providers operate with consistent timezone conversions. Prefer using Luxon `DateTime` objects across server and client.
- **Performance:** Heavy libraries (ephemeris) should stay server-side; client components should fetch via API to avoid bloating bundles.
- **Security:** Sanitise inputs coming from dataset (notes may contain user text). When exporting, guard against CSV injection (prefix `'` if first char is `=`, `+`, etc.).
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,8 @@ Schema lives in `src/schema.ts` (Zod + inferred TypeScript types). CSV column or
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
timing_window_end,polarity,strength,confidence,weight_system,privacy,
provenance,notes
```

Core enum sets: `System`, `Category`, `DirectionCardinal`, `Polarity`. Validation rules:
Expand All @@ -73,6 +74,7 @@ Core enum sets: `System`, `Category`, `DirectionCardinal`, `Polarity`. Validatio
- `direction_degrees` integer 0–359; auto-derives cardinal if missing.
- `strength` integer −2…+2, `confidence` between 0…1.
- `weight_system > 0` (defaults: HD 0.6, GK 0.5, others 1.0).
- `privacy` values: `public`, `internal`, `paid`. Use `provenance` to track provider+timestamp metadata.
- Timezone must be an IANA tzdb identifier.

Utility helpers (`src/lib`) cover intervals, direction mapping, CSV serialization, deduplication, and numerology math.
Expand All @@ -85,7 +87,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 natal astrology, Jyotiṣa, Feng Shui, BaZi, Qi Men Dun Jia, Human Design, Gene Keys, 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 Tarotincluding entries flagged with `privacy=paid` and `privacy=internal` for filter testing.

---

Expand All @@ -97,7 +99,7 @@ Interfaces live in `src/calculators/`:
- `ChineseCalendarProvider` – BaZi pillars, luck cycles, sexagenary conversions.
- `ZWDSProvider`, `QMDJProvider`, `FSProvider`, `HDProvider`, `GKProvider`.

Inject your implementation into the relevant system route (under `app/systems/**`). When a provider is absent, UI components surface `UNKNOWN` banners. Mark paid or private sources with `notes:"privacy:paid"` so users can filter them out.
Inject your implementation into the relevant system route (under `app/systems/**`). When a provider is absent, UI components surface `UNKNOWN` banners. Mark paid or private sources by setting the `privacy` field to `paid` or `internal` so collaborators can filter them out.

Until a calculator is integrated, MetaMap never invents WA/HA/JA/BaZi/ZWDS/QMDJ/FS/HD/GK results—only deterministic math (e.g., numerology) is pre-filled.

Expand Down
16 changes: 11 additions & 5 deletions next.config.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
import { resolve } from "node:path";
import type { NextConfig } from "next";

const swissephStub = "@/server/providers/ephemeris/swissephStub";

const nextConfig: NextConfig = {
experimental: {
turbopackUseSystemTlsCerts: true,
},
turbopack: {
resolveAlias: {
swisseph: swissephStub,
},
},
webpack: (config) => {
config.resolve = config.resolve ?? {};
config.resolve.alias = config.resolve.alias ?? {};
if (!config.resolve.alias.swisseph) {
config.resolve.alias.swisseph = resolve(
__dirname,
"src/server/providers/ephemeris/swissephStub",
);
config.resolve.alias.swisseph = swissephStub;
}
return config;
},
Expand Down
28 changes: 28 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 @@ -38,6 +38,8 @@
"@testing-library/user-event": "^14.6.1",
"@types/d3": "^7.4.3",
"@types/jest": "^29.5.12",
"@types/jsdom": "^27.0.0",
"@types/luxon": "^3.7.1",
"@types/node": "^20",
"@types/papaparse": "^5.3.15",
"@types/react": "^19",
Expand Down
22 changes: 12 additions & 10 deletions public/sample.csv
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
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
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,privacy,provenance,notes
default-person,1992-09-01T06:03:00,Australia/Sydney,WA,Sidereal · Lahiri,Swiss Ephemeris,,Sun 08° Virgo,Sun at 08° Virgo in 1st house,Personality,Core Self,,,,,+,2,0.9,1,paid,provider:ephemeris:swiss,house_system=P; ayanamsa=lahiri
default-person,1992-09-01T06:03:00,Australia/Sydney,JA,Lahiri,Swiss Ephemeris,,Nakṣatra Chitrā,Moon longitude 186.49° (sidereal),Timing,Nakṣatra,,,,,+,0,0.85,1,public,provider:ephemeris:lahiri,sequence:vimshottari
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.75,1,public,provider:fs: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,Pillar,,,,,+,1,0.88,1,public,provider:chineseCalendar:standard,gender:unspecified
default-person,1992-09-01T06:03:00,Australia/Sydney,QMDJ,Zhi Run · yang,Lo Shu QMDJ,,Centre palace,Wu | Open | Chief,Guidance,Palace,,,,,+,0,0.72,1,public,provider:qmdj:zhi-run:yang,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,paid,provider:hd,authority:Sacral
default-person,1992-09-01T06:03:00,Australia/Sydney,GK,Activation Sequence,Gene Keys Profile,,Life's Work,Gene Key 29 · Line 2,Guidance,Sphere,,,,,+,0,0.65,0.5,paid,provider:gk,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,public,internal:numerology,auto-calculated:numerology
default-person,1992-09-01T06:03:00,Australia/Sydney,Numerology_Chaldean,,MetaMap numerology,,Birth number 4,Birth number 4,Learning,Birth Number,,,,,+,1,0.6,1,public,internal:numerology,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,0.5,1,internal,rng:tarot,spread:celtic; position:Outcome
default-person,1992-09-01T06:03:00,Australia/Sydney,Geomancy,Judge,MetaMap RNG,,Judge 15,UNKNOWN,Guidance,Judge,,,2024-03-01T09:00:00+11:00,2024-03-01T09:00:00+11:00,0,0,0.5,1,internal,rng:geomancy,figure:● ○ ● ○
6 changes: 4 additions & 2 deletions src/app/api/providers/[provider]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,15 @@ import type {
} from "@/calculators";

type ProviderParams = {
params: { provider: string };
params: { provider: string } | Promise<{ provider: string }>;
};

const isProviderKey = (key: string): key is ProviderKey => {
return ["ephemeris", "chineseCalendar", "zwds", "qmdj", "fs", "hd", "gk"].includes(key);
};

export async function POST(request: Request, { params }: ProviderParams) {
export async function POST(request: Request, context: ProviderParams) {
const params = await context.params;
if (!isProviderKey(params.provider)) {
return NextResponse.json(
{ error: `Unknown provider "${params.provider}"` },
Expand Down Expand Up @@ -118,6 +119,7 @@ export async function POST(request: Request, { params }: ProviderParams) {
provider.luckPillars({
dateTime: birth,
zone,
gender: payload.gender,
variant: payload.variant,
}),
]);
Expand Down
14 changes: 6 additions & 8 deletions src/app/compass/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,22 +2,20 @@

import Link from "next/link";
import { useMemo } from "react";
import { shallow } from "zustand/shallow";
import { useStore } from "@/store/useStore";
import type { MetaMapStore } from "@/store/useStore";
import { useStoreHydration } from "@/hooks/useStoreHydration";
import { applyFilters } from "@/lib/filters";
import { Compass } from "@/components/Compass";
import { FilterBar } from "@/components/FilterBar";

const CompassPage = () => {
const hydrated = useStoreHydration();
const { dataset, filters } = useStore(
(state) => ({
dataset: state.dataset,
filters: state.filters,
}),
shallow,
);
const selection: Pick<MetaMapStore, "dataset" | "filters"> = useStore((state) => ({
dataset: state.dataset,
filters: state.filters,
}));
const { dataset, filters } = selection;

if (!hydrated) {
return null;
Expand Down
21 changes: 11 additions & 10 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import Link from "next/link";
import { useMemo } from "react";
import { useStore } from "@/store/useStore";
import type { MetaMapStore } from "@/store/useStore";
import { useStoreHydration } from "@/hooks/useStoreHydration";
import { applyFilters } from "@/lib/filters";
import { conflictCount, systemCount, totalRows, unknownShare } from "@/lib/stats";
Expand All @@ -17,21 +18,21 @@ import { Timeline } from "@/components/Timeline";
import { Compass } from "@/components/Compass";
import { HowItWorksModal } from "@/components/modals/HowItWorksModal";
import { WeightsPanel } from "@/components/WeightsPanel";
import { shallow } from "zustand/shallow";
import { DatasetList } from "@/components/DatasetList";
import { ProviderStatusPanel } from "@/components/ProviderStatusPanel";

export default function Home() {
const hydrated = useStoreHydration();
const { dataset, filters, tzdbVersion, birthDetails } = useStore(
(state) => ({
dataset: state.dataset,
filters: state.filters,
tzdbVersion: state.tzdbVersion,
birthDetails: state.birthDetails,
}),
shallow,
);
const selection: Pick<
MetaMapStore,
"dataset" | "filters" | "tzdbVersion" | "birthDetails"
> = useStore((state) => ({
dataset: state.dataset,
filters: state.filters,
tzdbVersion: state.tzdbVersion,
birthDetails: state.birthDetails,
}));
const { dataset, filters, tzdbVersion, birthDetails } = selection;

if (!hydrated) {
return null;
Expand Down
4 changes: 4 additions & 0 deletions src/app/systems/bazi/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,8 @@ const BaZiPage = () => {
strength: 0,
confidence: 0.85,
weight_system: 1,
privacy: "public",
provenance: `provider:chineseCalendar:${requestPayload.variant}`,
notes: `pillar=${pillar.pillar}`,
});
});
Expand All @@ -110,6 +112,8 @@ const BaZiPage = () => {
strength: 0,
confidence: 0.8,
weight_system: 1,
privacy: "public",
provenance: `provider:chineseCalendar:${requestPayload.variant}`,
notes: `duration=${luck.durationYears}y`,
});
});
Expand Down
Loading