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
+57
View File
@@ -0,0 +1,57 @@
#ifndef DECK_SIGNIFICANCE_H
#define DECK_SIGNIFICANCE_H
/* Header-only dependency on the engine: this module never compiles or
* links the engine's own .c files (or the vendored Astronomy Engine),
* only shares its struct/enum definitions. That's what makes it testable
* with a hand-built DailyReading instead of a real reading_generate()
* call - see interpreter/tests/significance_test.c. */
#include "../../engine/src/reading.h"
#include <stdbool.h>
#define MAX_SIGNIFICANT_ITEMS 5
/* Only aspects compete for the top-5 today. Kept as an explicit tag
* (rather than assuming "item == aspect") so a future kind - e.g. a
* notable moon phase or a house ingress - can be added without
* reshaping this struct. */
typedef enum {
ITEM_ASPECT = 0
} SignificantItemKind;
typedef struct {
SignificantItemKind kind;
Aspect aspect; /* valid when kind == ITEM_ASPECT */
double score; /* higher = more significant; no fixed upper bound */
} SignificantItem;
/* Overall classification of the day, derived from the top item's score.
* A small enum, not a raw number, so callers (rendering, i18n, "does
* this deserve Celtic Cross framing") don't each have to invent their
* own thresholds. */
typedef enum {
DAY_QUIET = 0,
DAY_NOTABLE,
DAY_SIGNIFICANT,
DAY_MAJOR
} DaySignificance;
typedef struct {
SignificantItem top_items[MAX_SIGNIFICANT_ITEMS];
int top_item_count; /* <= MAX_SIGNIFICANT_ITEMS; fewer if the day had
* fewer than 5 aspects in orb */
DaySignificance day_level;
} DailyInterpretation;
/* Reads reading->transits.aspects[] only - never touches natal/spread,
* and never calls back into the engine, so a hand-built DailyReading is
* a complete, valid input. */
void interpret_daily_reading(const DailyReading *reading, DailyInterpretation *out);
/* True once day_level reaches the threshold at which the Celtic Cross
* reading should get a "this matters" framing sentence naming
* interp->top_items[0]. */
bool interpretation_deserves_framing(const DailyInterpretation *interp);
#endif