Add JSON output format and a separate interpreter binary

deck-engine gains --format json (language-independent, slug-based)
so a new dist/interpreter-cli can score how significant a day's
transits are without linking the engine itself — it depends only on
the JSON shape, via its own minimal parser. Also documents the full
reading pipeline end to end (CLAUDE.md, README, docs/) and adds
docs/reading.md, a non-technical explanation of what a daily reading
contains and means.
This commit is contained in:
ml
2026-07-05 15:55:43 +02:00
parent 57deb62b51
commit bd739ccfdf
25 changed files with 1497 additions and 11 deletions
+97 -5
View File
@@ -2,9 +2,11 @@
See [`architecture.md`](architecture.md) for how these fit together.
There are two ways to provide input (direct CLI flags, or `run.sh` +
`user.properties`) and two output formats (`text`, `html`); the
`DailyReading` struct is the programmatic form both outputs are rendered
from, and the one the future watchapp will consume directly.
`user.properties`) and three output formats (`text`, `html`, `json`); the
`DailyReading` struct is the programmatic form all three are rendered
from, and the one the future watchapp will consume directly. `json` is
also `interpreter-cli`'s input format — see "The interpreter" at the
bottom of this file.
## Input
@@ -13,7 +15,7 @@ from, and the one the future watchapp will consume directly.
```
deck-engine --seed <string> --birth-date YYYY-MM-DD --birth-time HH:MM
--birth-utc-offset <hours> --birth-lat <deg> --birth-lon <deg>
[--date YYYY-MM-DDTHH:MM] [--format text|html]
[--date YYYY-MM-DDTHH:MM] [--format text|html|json]
```
| Flag | Required | Format | Meaning |
@@ -25,7 +27,7 @@ deck-engine --seed <string> --birth-date YYYY-MM-DD --birth-time HH:MM
| `--birth-lat` | yes | decimal degrees | Birth latitude, north positive. |
| `--birth-lon` | yes | decimal degrees | Birth longitude, east positive. |
| `--date` | no | `YYYY-MM-DDTHH:MM[:SS]` | UTC moment used for today's transits and as the moment the tarot seed is drawn against. Defaults to the current system time. |
| `--format` | no | `text` \| `html` | Output format, defaults to `text`. |
| `--format` | no | `text` \| `html` \| `json` | Output format, defaults to `text`. `json` always uses language-independent identifiers, ignoring `--lang`. |
| `--lang` | no | language code, e.g. `en`, `de` | Reading language, defaults to `en`. Matches a file named `<code>.lang` in `--i18n-dir` (see "Translations" below). Unknown codes or missing files fall back to English with a warning on stderr. |
| `--i18n-dir` | no | path | Directory to look for `<lang>.lang` in. Defaults to an `i18n/` directory next to the binary itself (resolved from `argv[0]`, so `dist/deck-engine` finds `dist/i18n/` regardless of the caller's working directory). |
@@ -156,3 +158,93 @@ cards rendered as an image:
image links.
- Reversed cards get `class="reversed"`, styled with `transform:
rotate(180deg)` in the page's inline CSS.
### `--format json`
A single JSON object mirroring `DailyReading` exactly — `natal`,
`transits`, `spread` — using the **slug** accessors
(`astro_body_slug()`, `astro_sign_slug()`, `astro_moon_phase_slug()`,
`astro_aspect_slug()`, `tarot_card_slug()`, `tarot_position_slug()`)
rather than the display-name ones, so the output is identical regardless
of `--lang` — this is the one format meant for another program to parse
(`dist/interpreter-cli`, see "The interpreter" below), not for a human to
read.
```json
{
"natal": {
"bodies": [
{"body": "sun", "sign": "taurus", "degree_in_sign": 23.4926, "ecliptic_longitude": 53.4926, "house": 3},
...
],
"ascendant_longitude": 348.4123,
"houses": ["pisces", "aries", ...]
},
"transits": {
"bodies": [
{"body": "sun", "sign": "cancer", "degree_in_sign": 11.5012, "ecliptic_longitude": 101.5012, "house": 5},
...
],
"moon_phase": "waning_gibbous",
"aspects": [
{"transiting_planet": "sun", "natal_planet": "moon", "type": "opposition", "orb": 3.7021},
...
]
},
"spread": {
"positions": [
{"position": "present", "card": "high_priestess", "reversed": true},
...
]
}
}
```
- `bodies` is always `NUM_BODIES` (10) entries, Sun..Pluto in `Body` enum
order; `spread.positions` is always `TAROT_SPREAD_SIZE` (10) entries in
`CelticCrossPosition` enum order (Present, Challenge, Crown,
Foundation, Recent Past, Near Future, Attitude, Environment, Hopes and
Fears, Outcome — see `CLAUDE.md`'s "Tarot" section for why that's not
the order most tutorials use).
- `natal.houses[i]` is the sign occupying whole-sign house `i + 1`;
`houses[0]` is the Ascendant's own sign. `transits.bodies[*].house` is
a house number (1-12) into the **natal** chart, not a fresh "houses for
right now" chart — same rule as the `text`/`html` output.
- All angles are decimal degrees, `%.4f`. `aspects` may be an empty array
(a valid "no aspects today"), but the key itself is always present.
## The interpreter (`interpreter/`, `dist/interpreter-cli`)
`dist/interpreter-cli` is a separate binary — see `CLAUDE.md`'s
"Interpretation" section for the full architecture — that reads a
`--format json` document and scores how significant today's transits
are. It never links the engine; it only depends on the JSON shape
above.
```bash
dist/deck-engine ... --format json | dist/interpreter-cli
# or:
dist/deck-engine ... --format json > reading.json
dist/interpreter-cli reading.json
```
- **Input**: a file path argument, or stdin if no argument (or `-`) is
given. Only `"transits"."aspects"` is read — `natal` and `spread` may
be present (and are ignored) or omitted entirely, except that
`"transits"."aspects"` itself must be present (an empty array is valid
and means "no notable transits today"; a missing key is treated as
invalid input).
- **Output**: a plain-text report — the day's overall significance level
(`Quiet`/`Notable`/`Significant`/`Major`), a "worth a deeper Celtic
Cross look" line on `Major` days only, and up to 5 of today's aspects
ranked by score:
```
Day significance: Significant
(a major transit today - worth a deeper Celtic Cross look)
Top 3 significant transits:
1. Transiting Saturn Square natal Sun (orb 0.5°, score 8.10)
2. Transiting Pluto Opposition natal Moon (orb 0.2°, score 7.92)
3. Transiting Jupiter Trine natal Venus (orb 2.1°, score 3.40)
```
+100
View File
@@ -0,0 +1,100 @@
# Your daily reading, explained
This page describes what actually happens when you get a reading —
no code, no jargon you haven't already agreed to. If you just want to
know "what am I looking at", start here.
## The one-time setup: telling it who you are
Before you can get a personal reading, you tell it a few things about
your birth, once:
- the **date** and **time** you were born, and which time zone that
clock time was in
- **where** you were born (latitude/longitude)
That's it. This information never changes and is only used to work out
exactly where the planets were, and which zodiac sign was rising, at the
moment you were born — your **natal chart**. Everything else the app
does is compared against that one fixed reference point.
## What happens every day
Each day, the app does two independent things and then combines them:
1. **It looks at the sky right now** and compares it to your natal
chart from step one — this tells you what's changing today,
relative to your own personal baseline (not a generic horoscope).
2. **It shuffles a tarot deck**, using today's date as the shuffle
"seed". This means: if you check your reading three times on the
same day, you get the exact same 10 cards, in the same order, every
time — it only changes when the date changes. There's no randomness
involved beyond that; today's date fully determines today's spread.
## The three parts of a reading
### 1. Your natal chart
A snapshot of the sky at the moment you were born: where the Sun,
Moon, and each planet were, which zodiac sign was rising on the
eastern horizon at that moment (your **Ascendant**), and which of the
12 astrological "houses" each planet falls into as a result. Think of
this as your personal baseline — it doesn't change day to day, and
every other part of the reading is measured against it.
### 2. Today's sky (your "transits")
Where the Sun, Moon, and planets are positioned *today*, plus:
- the current **Moon phase** (New, Waxing Crescent, Full, and so on)
- any **aspects** — meaningful angles formed between where a planet is
today and where a planet sat in your natal chart. For example, "the
Sun is opposite your natal Moon today" is one aspect. These are the
traditional signals astrologers use to say a day is emotionally
charged, a good day for decisions, a day of tension, etc. Not every
day has aspects; some days are quiet.
Each planet's position today is also placed into one of *your* 12
houses (not a fresh chart for right now) — that's the standard way
astrologers read "what house is this transit happening in for me".
### 3. Your tarot spread (the Celtic Cross)
A classic 10-card tarot layout, drawn from the 22 Major Arcana cards
(The Fool, The Magician, and so on through The World). Each of the 10
positions asks a different question about your day or situation — for
example, "what covers you right now", "what crosses/challenges you",
"your hopes and fears", "the likely outcome". Every card can land
**upright** or **reversed**, which traditionally shades or inverts its
meaning. The order and meanings of the 10 positions, and the meaning
of each card, come from A. E. Waite's original 1911 description of the
spread — the same source most modern tarot decks trace back to.
## How "big" is today?
Not every day is equally eventful astrologically. A separate,
optional step can look at today's aspects (part 2, above) and works
out an overall **significance level** for the day — Quiet, Notable,
Significant, or Major — based on which planets are involved, how exact
the angle is, and whether it touches your Sun or Moon (the two most
personally weighted points in a chart). On a genuinely Major day, it
also calls out the single most important thing happening, as a hint
that it's worth paying closer attention to your tarot spread that day
rather than treating it as routine. Quiet days still get a full
reading — this step only adds a "pay attention today" flag, it never
removes anything.
## What a day's reading actually gives you
Put together, one day's reading contains: your natal chart (for
reference), today's sky and how it's speaking to your chart, how
significant today is overall, and your 10-card Celtic Cross spread for
the day. Right now these sit side by side — the astrology tells you
*how eventful* today is and *where* to pay attention, and the tarot
spread gives you the actual guidance to sit with. They don't yet get
woven into a single narrative (e.g. a spread explained *in light of*
today's significant transit) — that combined storytelling is the next
piece to build, not something you get today.
*(This page, like that piece, is still a work in progress — expect it
to grow as the reading itself does.)*