blackmoon 0.1.1

Command-line astrology data-format converter built on astrogram.
# Astrogram

[![Crates.io](https://img.shields.io/crates/v/astrogram.svg)](https://crates.io/crates/astrogram)
[![Documentation](https://docs.rs/astrogram/badge.svg)](https://docs.rs/astrogram)
[![License](https://img.shields.io/crates/l/astrogram.svg)](https://github.com/lucidaeon/mediumcoeli#license)

The chart-format conversion engine behind [`Blackmoon`](blackmoon.md). A pure-Rust library for reading, writing, merging, and normalizing astrology chart data across every documented platform format.

## What it does

Every reader produces, and every writer consumes, the single canonical type `chart::Chart` — a flat struct of name, place, datetime, zone, event type, source rating, house system, zodiac, coordinate system, sub-charts, and notes. Coordinates are always ISO 6709 (East-positive longitude, North-positive latitude) regardless of source format.

Modules currently shipping:

- **`sfcht`** — Solar Fire `.SFcht` binary chart-collection format (cp1252, fixed-record layout, `+West` longitude/timezone). Round-trip read and write, verified by golden tests against 22 specimen files / 3,918 records.
- **`adbxml`** — Astrodatabank XML reader (`export_format` 160715). Parses `{DD}{n|s}{MM}` coordinates and derives the UT offset from `jd_ut` or `stmerid`.
- **`aaf`** — Astrolog ASCII Format `#A93:`/`#B93:` paired records.
- **`zeus`** — Zeus `.zdb` UTF-8 semicolon-delimited records.
- **`luna`** — lunaastrology.com HTTP session: listing pagination, `cast.json` metadata, sidebar HTML parsing, form-token-aware writes.
- **`astro`** — astro.com HTTP session: credential login, full CRUD against the AWD endpoint with `nhor`-keyed identity.
- **`consolidate`** — duplicate detection (exact name + date, time within ±2h, lat/lon within 0.1°) and batch merge.
- **`normalize`** — strips characters not representable in cp1252 and collapses whitespace, the mandatory pre-step before any `.SFcht` write.

## Why it exists

Chart data lives in a dozen incompatible formats — each tool's "native" container plus several web platforms behind login. Every conversion path between them is currently a manual copy-paste from one UI to another, with the usual transcription risk on birth times, longitudes, and Rodden ratings. `astrogram` is the substrate that makes every format addressable in code, and makes round-tripping between them a function call.

## How it works

**Canonical type at the center.** `chart::Chart` is the only shape that crosses module boundaries. Sign-convention mismatches (e.g. Solar Fire's `+West` longitude vs. ISO 6709's `+East`) are resolved at the reader/writer boundary — never inside `Chart`. House systems and zodiacs use exhaustive named variants for everything observed in the wild plus an `Other(u8)` escape hatch for unknown ids.

**Specifications drive the parsers.** Every format module is backed by a written spec (under `docs/` and the gitignored `research/`) and, where possible, a Python oracle. The `.SFcht` reader is verified by a Kaitai Struct definition and golden JSON fixtures generated by the oracle; web modules are verified by the round-trip from a known listing.

**Web sessions are first-class.** `luna::LunaSession` and `astro::AstroSession` are constructable from a cookie or — for astro.com — from credentials, and expose `fetch_charts`, `write_charts`, `create_one`, `delete_charts` directly. HTTP orchestration (pagination, rate limiting, per-chart progress callbacks, form-token discovery) lives in the library, not the CLI.

**Errors are typed.** Each format has its own `thiserror`-derived enum; the top-level `error::ParseError` lifts everything into a single result type. Reqwest, encoding, and XML errors flow through as wrapped variants for clean caller-side matching.