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
+52
View File
@@ -142,3 +142,55 @@ void reading_print_html(const DailyReading *r, FILE *out) {
}
fprintf(out, "</div>\n</body></html>\n");
}
static void print_body_position_json(FILE *out, const char *indent, Body body,
const PlanetPosition *p) {
fprintf(out,
"%s{\"body\": \"%s\", \"sign\": \"%s\", \"degree_in_sign\": %.4f, "
"\"ecliptic_longitude\": %.4f, \"house\": %d}",
indent, astro_body_slug(body), astro_sign_slug(p->sign),
p->degree_in_sign, p->ecliptic_longitude, p->house);
}
void reading_print_json(const DailyReading *r, FILE *out) {
fprintf(out, "{\n");
fprintf(out, " \"natal\": {\n \"bodies\": [\n");
for (int b = 0; b < NUM_BODIES; b++) {
print_body_position_json(out, " ", (Body)b, &r->natal.bodies[b]);
fprintf(out, "%s\n", b + 1 < NUM_BODIES ? "," : "");
}
fprintf(out, " ],\n \"ascendant_longitude\": %.4f,\n \"houses\": [",
r->natal.ascendant_longitude);
for (int h = 0; h < 12; h++) {
fprintf(out, "\"%s\"%s", astro_sign_slug(r->natal.houses[h]), h + 1 < 12 ? ", " : "");
}
fprintf(out, "]\n },\n");
fprintf(out, " \"transits\": {\n \"bodies\": [\n");
for (int b = 0; b < NUM_BODIES; b++) {
print_body_position_json(out, " ", (Body)b, &r->transits.bodies[b]);
fprintf(out, "%s\n", b + 1 < NUM_BODIES ? "," : "");
}
fprintf(out, " ],\n \"moon_phase\": \"%s\",\n \"aspects\": [\n",
astro_moon_phase_slug(r->transits.moon_phase));
for (int i = 0; i < r->transits.aspect_count; i++) {
const Aspect *a = &r->transits.aspects[i];
fprintf(out,
" {\"transiting_planet\": \"%s\", \"natal_planet\": \"%s\", "
"\"type\": \"%s\", \"orb\": %.4f}%s\n",
astro_body_slug(a->transiting_planet), astro_body_slug(a->natal_planet),
astro_aspect_slug(a->type), a->orb,
i + 1 < r->transits.aspect_count ? "," : "");
}
fprintf(out, " ]\n },\n");
fprintf(out, " \"spread\": {\n \"positions\": [\n");
for (int i = 0; i < TAROT_SPREAD_SIZE; i++) {
const TarotDraw *draw = &r->spread.positions[i];
fprintf(out, " {\"position\": \"%s\", \"card\": \"%s\", \"reversed\": %s}%s\n",
tarot_position_slug((CelticCrossPosition)i), tarot_card_slug(draw->card),
draw->reversed ? "true" : "false", i + 1 < TAROT_SPREAD_SIZE ? "," : "");
}
fprintf(out, " ]\n }\n}\n");
}