Files
deck_in_a_dash/docs/input-output-format.md
T
ml 41422adf98 Add narrative text and a day-significance rank to the interpreter
Each of the day's top significant transits now gets a short sentence
describing what it classically means and which area of life its house
governs, condensed from Sepharial's Transits and Planetary Periods
(1920, public domain) plus a small table of traditional house
significations. The day-level line also shows its rank out of the 4
defined levels, e.g. "Notable (2/4)". Docs (CLAUDE.md, README,
docs/input-output-format.md, docs/reading.md) updated to match,
including a full worked example in docs/reading.md.
2026-07-05 21:05:53 +02:00

12 KiB

Input & output formats

See architecture.md for how these fit together. There are two ways to provide input (direct CLI flags, or run-engine.sh

  • 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

Direct CLI flags (dist/deck-engine)

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|json]
Flag Required Format Meaning
--seed yes any string Tarot seed. The same seed always produces the same 10-card spread, positions, and orientations (see rng.c/tarot.c).
--birth-date yes YYYY-MM-DD Birth date, local calendar.
--birth-time yes HH:MM (24h) Birth time, local clock.
--birth-utc-offset yes decimal hours, e.g. 2 or -5.5 Hours to subtract from local birth time to get UTC (e.g. 2 for CEST).
--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 | 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).

Birth latitude/longitude only affect the Ascendant/houses — planet signs are geocentric and location-independent.

run-engine.sh + user.properties

dist/run-engine.sh [text|html|json] [lang] wraps the binary for daily use:

  • Seed and --date come from the OS clock: date +%Y-%m-%d (stable all day) and date -u +%Y-%m-%dT%H:%M.

  • Birth data and language are read from dist/user.properties, a key=value properties file next to the script (#-prefixed lines and blank lines are comments; whitespace around = is trimmed):

    birth_date=1990-05-14
    birth_time=14:32
    birth_utc_offset=2
    birth_lat=52.5200
    birth_lon=13.4050
    lang=en
    

    The file is seeded once from engine/scripts/user.properties.template with placeholder values (YOUR_BIRTH_DATE_HERE, etc.). run-engine.sh checks every birth field for an empty value or a leftover YOUR_ placeholder and refuses to run with a clear error until the file is filled in; lang has no such check since it always has a usable default (en). Since dist/ itself can be wiped (make clean, or deleting the directory), the build also keeps a durable backup at engine/scripts/user.properties.local (gitignored) once the file looks filled in, and restores from it if dist/user.properties is ever missing — see CLAUDE.md's "Build & run".

  • The optional second argument overrides lang= for a single run without editing the file, e.g. dist/run-engine.sh html de.

  • dist/run-interpreter.sh (no arguments) is the equivalent wrapper for the interpreter: same OS clock + user.properties inputs, but calls deck-engine --format json and pipes it straight into dist/interpreter-cli — see "The interpreter" below.

Translations (--lang, dist/i18n/)

Every piece of display text — planet/sign/moon-phase/aspect names, tarot card names and meanings, Celtic Cross position names, and the section headers in the text/html output — is looked up in a translation catalog with the built-in English text as the fallback for any key the catalog doesn't have. See CLAUDE.md's "Translations" section for the key-naming convention and how to add a new language.

engine/i18n/en.lang and de.lang are the shipped translation files; make copies engine/i18n/*.lang to dist/i18n/ (refreshed on every build, like dist/img/). Adding a language is just dropping another <code>.lang file into engine/i18n/ — no code changes needed.

Output

DailyReading (the real output — reading.h)

reading_generate() is the actual API; text/html below are just two ways of rendering the struct it fills in. This is what the watchapp will read directly once it exists.

typedef struct {
  NatalChart natal;
  DailyTransits transits;
  CelticCrossSpread spread;
} DailyReading;
  • NatalChart (astro.h): bodies[10] (Sun..Pluto, each a sign + degree-in-sign + raw ecliptic longitude + whole-sign house 1-12), ascendant_longitude, houses[12] (whole-sign: houses[0] is the Ascendant's sign).
  • DailyTransits (astro.h): bodies[10] (today's positions, same shape as above — house here is the natal chart's house the transiting planet currently occupies, not a fresh "houses for right now" chart), moon_phase (one of 8 named phases), aspects[] + aspect_count (each aspect names a transiting planet, a natal planet, an AspectType — conjunction/sextile/square/trine/opposition — and the orb in degrees).
  • CelticCrossSpread (tarot.h): positions[10], indexed by CelticCrossPosition (Present, Challenge, Crown, Foundation, Recent Past, Near Future, Attitude, Environment, Hopes and Fears, Outcome — Waite's own drawing order), each holding a TarotCard and a reversed bool.

--format text

Four =====-delimited sections, in order: natal chart (each body's sign

  • degree + house, then the Ascendant), today's sky (Moon phase, then each body's transiting position + the natal house it currently occupies), today's aspects to the natal chart (one line per aspect, with orb), and the Celtic Cross (one block per position: name, card, (Reversed) if applicable, the position's meaning, then the card's meaning).
===== Natal Chart =====
Sun       23.5° Taurus      (House 3)
Moon      15.2° Capricorn   (House 11)
...
Ascendant  18.4° Pisces

===== Today's Sky =====
Moon phase: Waning Gibbous
Sun       11.5° Cancer      (House 5)
...

===== Today's Aspects to Natal Chart =====
Transiting Sun Opposition natal Moon (orb 3.7°)
...

===== Celtic Cross =====
The Present      The High Priestess (Reversed)
  This covers him: the general influence affecting the matter.
  Passion, moral or physical ardour, conceit, surface knowledge.
...

--format html

A single self-contained HTML page (inline <style>, no JS, no external requests) with the same four sections, plus each of the 10 Celtic Cross cards rendered as an image:

<img class="reversed" src="img/RWS_Tarot_02_High_Priestess.jpeg" alt="The High Priestess">
  • src="img/<file>" is relative to the HTML file's own location — it expects to sit next to an img/ directory, exactly like dist/reading.html next to dist/img/ (which make images populates from res/img/). Saving the HTML output anywhere else breaks the 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.

{
  "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.

dist/deck-engine ... --format json | dist/interpreter-cli
# or:
dist/deck-engine ... --format json > reading.json
dist/interpreter-cli reading.json
# or, for daily use (OS clock + user.properties, same inputs as run-engine.sh):
dist/run-interpreter.sh
  • Input: a file path argument, or stdin if no argument (or -) is given. "transits"."aspects" is required (an empty array is valid and means "no notable transits today"; a missing key is treated as invalid input) and drives scoring. "natal"."bodies"/"transits"."bodies" are read too, for the sign/house context printed alongside each event below (not for scoring) — an entry naming an unrecognized body, or a missing house, is silently skipped rather than failing the load (see parse_bodies() in reading_io.c); spread is always ignored.

  • Output: a plain-text report — the day's overall significance level (Quiet/Notable/Significant/Major, plus its rank out of the 4 defined levels, e.g. Notable (2/4)), a "worth a deeper Celtic Cross look" line on Major days only, and up to 5 of today's aspects ranked by score, each naming the sign and natal house the transiting planet currently occupies and the sign and house of the natal planet it's aspecting, followed by a short narrative sentence about what that transiting planet classically signifies and which area of life its current house governs (see CLAUDE.md's narrative.c bullet) — omitted only if the source material has nothing to say for that planet/aspect combination:

    Day significance: Significant (3/4)
    (a major transit today - worth a deeper Celtic Cross look)
    
    Top 3 significant transits:
      1. Transiting Saturn in Aries (house 2) Square natal Sun in Taurus (house 3) (orb 0.5°, score 8.10)
         In matters of money, possessions, and personal values (house 2). Brings depression, stagnation, hindrances and obstacles, and some deprivation of the usual benefits.
      2. Transiting Pluto in Aquarius (house 12) Opposition natal Moon in Capricorn (house 11) (orb 0.2°, score 7.92)
         In matters of solitude, the subconscious, and hidden matters (house 12). Brings deep, often hidden transformation - the surfacing or dismantling of something that has outgrown its old form.
      3. Transiting Jupiter in Leo (house 5) Trine natal Venus in Aries (house 2) (orb 2.1°, score 3.40)
         In matters of romance, creativity, and children (house 5). Brings increase and expansion - fullness of fortune and health, and a generally fortunate time.