[
  {
    "title": "Project Overview",
    "url": "readme/",
    "content": "Function layer for Kefer Astrology Quickstart Get up and running in 3 steps:\n# 1. Create and activate virtual environment python3 -m venv venv # alternatively use makefile source ./venv/bin/activate # Linux/Mac venv\\Scripts\\activate # Windows # 2. Install dependencies (choose your UI) pip install -r requirements/base.txt # for base package pip install -r requirements/streamlit.txt # For Streamlit UI (browser) pip install -r requirements/kivy.txt # For Kivy UI (desktop) # Or install by package extras pip install . # core computation/storage package pip install .[api] # FastAPI backend for Tauri/web frontends pip install .[streamlit] # Streamlit UI + chart rendering pip install .[kivy] # Kivy desktop UI + chart rendering # 3. Run the application python3 -m module --streamlit # Streamlit UI (browser) python3 -m module --kivy # Kivy UI (desktop) python3 -m module --tui # Text UI (terminal) python3 -m module # Default: TUI For detailed setup instructions, troubleshooting, and advanced options, see the Installation and Venvs.\nSuggested Runtime Split core: install pip install . for computation, workspace, and storage helpers api: install pip install .[api] for a FastAPI sidecar/backend without UI dependencies streamlit: install pip install .[streamlit] for the browser UI kivy: install pip install .[kivy] for the native Python desktop UI For a React frontend from Figma + FastAPI backend + Tauri packaging setup, prefer core + api and keep the ui_* modules as optional developer-only frontends. See FastAPI + React + Tauri.\nHow it works All the heavy lifting is made by g-battaglia/kerykeion, a manipulation layer over astrorigin/pyswisseph (also known as Swiss Ephemerides), thus the license is inherited (GNU Affero GPL v3.0). Other dependencies are tracked in requirements/base.in.\nFor NASA JPL ephemerides we use skyfielders/python-skyfield, which is MIT-licensed.\nHigh Level System Overview ┌─────────────────────────────────────────────────────────────────┐ │ UI Layer │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ Streamlit UI │ │ Kivy UI │ │ CLI/TUI │ │ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │ │ │ │ │ │ └──────────────────┼──────────────────┘ │ │ │ │ │ ┌───────▼────────┐ │ │ │ module.* APIs │ │ │ │ - services │ │ │ │ - workspace │ │ │ │ - storage │ │ │ └───────┬────────┘ │ └────────────────────────────┼─────────────────────────────────────┘ │ │ Python calls (in-process) │ ┌────────────────────────────▼─────────────────────────────────────┐ │ Backend (Python) │ │ ┌────────────────────────────────────────────────────────────┐ │ │ │ Core Modules │ │ │ │ - module.services │ │ │ │ - module.workspace │ │ │ │ - module.storage (optional) │ │ │ └────────────────────────────────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────┼──────────────────────────┐ │ │ │ │ │ │ │ │ ┌──────────────┐ │ ┌──────────────┐ │ │ │ │ │ DuckDB │ │ │ YAML │ │ │ │ │ │ Storage │ │ │ Workspace │ │ │ │ │ └──────┬───────┘ │ └──────┬───────┘ │ │ │ │ │ │ │ │ │ │ │ ┌──────▼──────────┐ │ ┌───────▼────────┐ │ │ │ │ │ workspace.db │ │ │ workspace.yaml │ │ │ │ │ │ - positions │ │ │ charts/*.yaml │ │ │ │ │ │ - metadata │ │ │ subjects/*.yaml│ │ │ │ │ └─────────────────┘ │ └────────────────┘ │ │ │ │ │ │ │ │ │ │ ┌──────▼──────────┐ │ │ │ │ │ │ parquet/ │ │ │ │ │ │ │ - chart_*.parquet│ │ │ │ │ │ └─────────────────┘ │ │ │ │ └──────────────────────────┼───────────────────────────┘ │ └─────────────────────────────┼─────────────────────────────────────┘ │ │ Files on disk │ ┌─────────────────────────────▼─────────────────────────────────────┐ │ Local File System │ │ │ │ workspace/ │ │ ├── workspace.yaml (Metadata) │ │ ├── charts/ (Chart definitions) │ │ │ ├── chart_001.yaml │ │ │ └── chart_002.yaml │ │ ├── subjects/ (Subject definitions) │ │ │ └── subject_001.yaml │ │ └── data/ (Computed data) │ │ ├── workspace.db (DuckDB) │ │ └── parquet/ (Parquet files) │ │ ├── chart_001.parquet │ │ └── chart_002.parquet │ └───────────────────────────────────────────────────────────────────┘ Data Flow: Chart Creation User Input │ ▼ ┌─────────────────┐ │ UI/CLI Input │ (Name, Date, Location, Settings) └────────┬────────┘ │ │ build chart + workspace ▼ ┌──────────────────┐ │ module.workspace │ │ - load/save │ │ - chart config │ └────────┬─────────┘ │ ├──► module.services.compute_positions │ └──► module.storage (optional) │ ▼ ┌─────────────────┐ │ DuckDB/Parquet │ │ - positions │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ Return to UI │ │ - chart_id │ │ - positions │ └─────────────────┘ Data Flow: Transit Computation User Input │ ▼ ┌─────────────────┐ │ Transit Panel │ (Source chart, Time range, Objects) └────────┬────────┘ │ │ compute series ▼ ┌─────────────────┐ │ module.services │ │ - series loop │ │ - positions │ │ - aspects (on │ │ demand) │ └────────┬────────┘ │ │ batch store (optional) ▼ ┌─────────────────┐ │ DuckDB/Parquet │ │ - positions │ └────────┬────────┘ │ │ query results ▼ ┌─────────────────┐ │ UI Updates │ │ - Progress │ │ - Results │ └─────────────────┘ Storage Schema Relationships ┌──────────────────────────────┐ │ computed_positions │ │ │ │ - chart_id │ │ - datetime │ │ - object_id │ │ - longitude/latitude │ │ - speed/retrograde │ │ - engine/ephemeris_file │ │ - ... (optional fields) │ └──────────────────────────────┘ Aspects are computed on demand from positions (not stored by default). "
  },
  {
    "title": "Streamlit UI",
    "url": "ui_streamlit/",
    "content": "Streamlit UI The Streamlit UI provides a web-based interface for creating and visualizing astrological charts.\nHow to Run Prerequisites Install Streamlit and required dependencies:\npip install -r requirements/streamlit.txt Starting the Application Run the Streamlit interface:\npython -m module --streamlit Or directly:\nstreamlit run module/ui_streamlit.py Features Interactive chart creation Real-time position calculations Aspect visualization Workspace management Chart export capabilities Configuration The Streamlit UI uses the workspace configuration from workspace.yaml. You can customize settings through the UI\u0026rsquo;s advanced options panel.\n"
  },
  {
    "title": "Kivy UI",
    "url": "ui_kivy/",
    "content": "Kivy UI The Kivy UI provides a native desktop application for astrological chart visualization with embedded interactive charts.\nHow to Run Prerequisites Install Kivy and required dependencies:\npip install -r requirements/kivy.txt For embedded interactive Plotly charts, install the WebView widget:\npip install kivy-garden garden install kivy_garden.webview Starting the Application Run the Kivy interface:\npython -m module --kivy Features Native desktop application Embedded interactive Plotly charts (with WebView) JPL engine toggle Standardized Radix chart renderer Cross-platform support (Windows, macOS, Linux) Configuration Default JPL ephemeris file: source/de421.bsp Chart files are saved to the workspace directory If WebView is unavailable, charts open in the default browser Troubleshooting Unknown class WebView: Install via garden install kivy_garden.webview JPL not active: Ensure skyfield is installed and source/de421.bsp exists Chart opens in browser: WebView widget not installed (this is a fallback behavior) "
  },
  {
    "title": "cli Module",
    "url": "auto/cli/",
    "content": "cli module CLI interface for Tauri frontend integration.\nThis module provides a JSON-based command interface that can be called from the Tauri application via subprocess. All commands read JSON from stdin or command-line arguments and output JSON to stdout.\nUsage: python -m module.cli \u0026lt;command\u0026gt; [args_json]\nCommands:\ncompute_chart: Compute positions and aspects for a chart compute_transit_series: Compute transit series for a time range get_workspace_settings: Get workspace settings and defaults list_charts: List all charts in workspace get_chart: Get chart details by ID sync_workspace: Synchronize workspace manifest with files on disk export_parquet: Export stored positions to Parquet files Storage:\nDuckDB database: workspace_dir/data/workspace.db Parquet files: workspace_dir/data/parquet/*.parquet Enable storage: Set store_in_db=True in compute_chart args Functions cmd_compute_chart cmd_compute_chart(args: Dict[str, Any]) -\u0026gt; Dict[str, Any] Compute positions and aspects for a chart.\nParameters workspace_path: Path to workspace.yaml\nchart_id: Chart ID to compute\ninclude_physical: Include extended physical properties (JPL only, default: False)\ninclude_topocentric: Include altitude/azimuth (JPL with location, default: False)\nstore_in_db: If True, store results in DuckDB (default: False)\nReturns Dict with keys: - positions: Dict mapping object_id -\u0026gt; position data - aspects: List of aspect dictionaries - chart_id: Chart ID - stored: True if stored in DuckDB, False otherwise\ncmd_compute_transit_series cmd_compute_transit_series(args: Dict[str, Any]) -\u0026gt; Dict[str, Any] Compute transit series for a time range.\nParameters workspace_path: Path to workspace.yaml\nsource_chart_id: Base chart ID\nstart_datetime: Start time (ISO format)\nend_datetime: End time (ISO format)\ntime_step: Step size (e.g., \u0026lsquo;1 second\u0026rsquo;, \u0026lsquo;1 minute\u0026rsquo;, \u0026lsquo;1 hour\u0026rsquo;, \u0026lsquo;1 day\u0026rsquo;)\ntransiting_objects: Optional list of object IDs to compute\ntransited_objects: Optional list of object IDs in source chart\naspect_types: Optional list of aspect types to compute\ninclude_physical: Include extended physical properties (default: False)\ninclude_topocentric: Include altitude/azimuth (default: False)\nReturns Dict with transit series results\ncmd_export_parquet cmd_export_parquet(args: Dict[str, Any]) -\u0026gt; Dict[str, Any] Export stored positions to Parquet files.\nParameters workspace_path: Path to workspace.yaml\nchart_id: Optional chart ID to export (if not provided, exports all)\noutput_dir: Optional output directory (defaults to workspace/data/parquet)\npartition_by_date: If True, partition by date (default: True)\nReturns Dict with list of created Parquet file paths\ncmd_get_chart cmd_get_chart(args: Dict[str, Any]) -\u0026gt; Dict[str, Any] Get chart details by ID.\nParameters workspace_path: Path to workspace.yaml\nchart_id: Chart ID\nReturns Dict with chart details\ncmd_get_workspace_settings cmd_get_workspace_settings(args: Dict[str, Any]) -\u0026gt; Dict[str, Any] Get workspace settings and defaults.\nParameters workspace_path: Path to workspace.yaml Returns Dict with workspace settings\ncmd_list_charts cmd_list_charts(args: Dict[str, Any]) -\u0026gt; Dict[str, Any] List all charts in workspace.\nParameters workspace_path: Path to workspace.yaml Returns Dict with list of chart summaries\ncmd_sync_workspace cmd_sync_workspace(args: Dict[str, Any]) -\u0026gt; Dict[str, Any] Synchronize workspace manifest with files on disk.\nParameters workspace_path: Path to workspace.yaml\nauto_import: If True, import new charts/subjects found on disk (default: True)\nauto_remove: If True, remove references to missing files (default: False)\nReturns Dict with sync results\nmain main() Main CLI entry point.\n"
  },
  {
    "title": "models Module",
    "url": "auto/models/",
    "content": "models module Classes class Annotation Annotation(title: str, content: str, created: datetime.datetime | None, author: str)\nDataclass fields title: str content: str created: Union author: str class Aspect Aspect(type: str, source_id: str, target_id: str, angle: float, orb: float)\nDataclass fields type: str source_id: str target_id: str angle: float orb: float class AspectContext (str, Enum) Contexts where an aspect can be used.\nclass AspectDefinition AspectDefinition(id: str, glyph: str, angle: float, default_orb: float, i18n: Dict[str, str], color: str | None = None, importance: int | None = None, line_style: str | None = None, line_width: float | None = None, show_label: bool | None = None, valid_contexts: List[module.models.AspectContext] | None = None)\nDataclass fields id: str glyph: str angle: float default_orb: float i18n: Dict color: Union importance: Union line_style: Union line_width: Union show_label: Union valid_contexts: Union class AspectSettings Settings for a single aspect definition, including display properties.\nDataclass fields id: str enabled: bool orb: Union color: Union importance: Union line_style: Union line_width: Union show_label: Union class AstroModel AstroModel(name: str, body_definitions: List[module.models.BodyDefinition], aspect_definitions: List[module.models.AspectDefinition], signs: List[module.models.Sign], settings: module.models.ModelSettings, engine: module.models.EngineType | None = None, zodiac_type: module.models.ZodiacType | None = None, ayanamsa: module.models.Ayanamsa | None = None)\nDataclass fields name: str body_definitions: List aspect_definitions: List signs: List settings: ModelSettings engine: Union zodiac_type: Union ayanamsa: Union class Attachment Attachment(filename: str, url: str, type: str)\nDataclass fields filename: str url: str type: str class Ayanamsa (str, Enum) class BodyDefinition BodyDefinition(id: str, glyph: str, formula: str, element: module.models.Element | None, avg_speed: float, max_orb: float, i18n: Dict[str, str], object_type: module.models.ObjectType | None = None, computation_map: Dict[str, str | None] = \u0026lt;factory\u0026gt;, requires_location: bool = False, requires_house_system: bool = False)\nDataclass fields id: str glyph: str formula: str element: Union avg_speed: float max_orb: float i18n: Dict object_type: Union computation_map: Dict requires_location: bool requires_house_system: bool class CelestialBody CelestialBody(id: str, definition_id: str, degree: float, sign: str, retrograde: bool, speed: float)\nDataclass fields id: str definition_id: str degree: float sign: str retrograde: bool speed: float class ChartConfig ChartConfig(mode: module.models.ChartMode, house_system: module.models.HouseSystem, zodiac_type: module.models.ZodiacType, included_points: List[str], aspect_orbs: Dict[str, float], display_style: str, color_theme: str, override_ephemeris: str | None = None, model: str | None = None, engine: module.models.EngineType | None = None, ayanamsa: module.models.Ayanamsa | None = None, observable_objects: List[str] | None = None, time_system: module.models.TimeSystem | None = None)\nDataclass fields mode: ChartMode house_system: HouseSystem zodiac_type: ZodiacType included_points: List aspect_orbs: Dict display_style: str color_theme: str override_ephemeris: Union model: Union engine: Union ayanamsa: Union observable_objects: Union time_system: Union class ChartInstance ChartInstance(id: str, subject: module.models.ChartSubject, config: module.models.ChartConfig, computed_chart: ForwardRef(\u0026lsquo;Horoscope\u0026rsquo;) | None = None, tags: List[str] = \u0026lt;factory\u0026gt;)\nDataclass fields id: str subject: ChartSubject config: ChartConfig computed_chart: Union tags: List class ChartMode (str, Enum) class ChartPreset ChartPreset(name: str, config: module.models.ChartConfig)\nDataclass fields name: str config: ChartConfig class ChartRelation ChartRelation(type: module.models.RelationType, source: str, target: str, method: str, time_span: module.models.DateRange | None = None)\nDataclass fields type: RelationType source: str target: str method: str time_span: Union class ChartSubject ChartSubject(id: str, name: str, event_time: datetime.datetime, location: module.models.Location)\nDataclass fields id: str name: str event_time: datetime location: Location class DateRange DateRange(start: datetime.datetime, end: datetime.datetime)\nDataclass fields start: datetime end: datetime class Element (str, Enum) The four classical elements.\nclass ElementColorSettings Color settings for the four elements.\nDataclass fields fire: str earth: str air: str water: str class EngineType (str, Enum) class EphemerisSource EphemerisSource(name: str, backend: str)\nDataclass fields name: str backend: str class Horoscope Horoscope(for_time: datetime.datetime, location: module.models.Location, bodies: List[module.models.CelestialBody], houses: List[module.models.House], aspects: List[module.models.Aspect])\nDataclass fields for_time: datetime location: Location bodies: List houses: List aspects: List class House House(number: int, cusp_degree: float, sign: str)\nDataclass fields number: int cusp_degree: float sign: str class HouseSystem (str, Enum) class LayoutStyle (str, Enum) class Location Location(name: str, latitude: float, longitude: float, timezone: str)\nDataclass fields name: str latitude: float longitude: float timezone: str class ModelOverrides ModelOverrides(points: List[module.models.OverrideEntry] = \u0026lt;factory\u0026gt;, aspects: List[module.models.OverrideEntry] = \u0026lt;factory\u0026gt;, override_orbs: Dict[str, float] = \u0026lt;factory\u0026gt;)\nDataclass fields points: List aspects: List override_orbs: Dict class ModelSettings ModelSettings(default_house_system: module.models.HouseSystem, default_aspects: List[str], default_bodies: List[str], standard_orb: float, default_transit_aspects: List[str] | None = None, default_direction_aspects: List[str] | None = None, default_transit_bodies: List[str] | None = None, default_direction_bodies: List[str] | None = None, degrees_in_circle: float = 360.0, obliquity_j2000: float = 23.4392911, coordinate_tolerance: float = 0.0001)\nDataclass fields default_house_system: HouseSystem default_aspects: List default_bodies: List standard_orb: float default_transit_aspects: Union default_direction_aspects: Union default_transit_bodies: Union default_direction_bodies: Union degrees_in_circle: float obliquity_j2000: float coordinate_tolerance: float class ObjectType (str, Enum) Type of observable object in the chart.\nclass OverrideEntry OverrideEntry(id: str, glyph: str | None = None, angle: float | None = None, default_orb: float | None = None, only_for: List[str] | None = None, i18n: Dict[str, str] | None = None, computed: bool | None = None)\nDataclass fields id: str glyph: Union angle: Union default_orb: Union only_for: Union i18n: Union computed: Union class RadixPointColorSettings Color settings for radix (natal chart) points/planets.\nMaps object IDs to color hex codes. Common objects:\nsun, moon, mercury, venus, mars, jupiter, saturn, uranus, neptune, pluto asc, mc, ic, desc (angles) north_node, south_node lilith, chiron, etc. Dataclass fields colors: Dict class RelationType (str, Enum) class Sign Sign(name: str, glyph: str, abbreviation: str, element: module.models.Element, i18n: Dict[str, str])\nDataclass fields name: str glyph: str abbreviation: str element: Element i18n: Dict class TimeSystem (str, Enum) Time representation systems.\nclass ViewLayout ViewLayout(name: str, layout_style: module.models.LayoutStyle, chart_instances: List[str], relations: List[module.models.ChartRelation] = \u0026lt;factory\u0026gt;, modules: List[module.models.ViewModule] = \u0026lt;factory\u0026gt;)\nDataclass fields name: str layout_style: LayoutStyle chart_instances: List relations: List modules: List class ViewModule ViewModule(type: module.models.ViewModuleType, config: Dict)\nDataclass fields type: ViewModuleType config: Dict class ViewModuleType (str, Enum) class Workspace Complete workspace container for astrological chart analysis.\nA Workspace represents a project or collection of astrological work, containing all the data, settings, and configurations needed for chart computation and analysis. It serves as the top-level organizational unit for managing charts, subjects, and their associated metadata.\nStructure: - Identity \u0026amp; Configuration: - owner: Workspace owner/creator identifier - active_model: Currently active astrological model (e.g., \u0026ldquo;western\u0026rdquo;, \u0026ldquo;vedic\u0026rdquo;) - default: Default settings (ephemeris, location, house system, language, theme)\n- **Astrological Models**: - models: Available astrological model catalogs (planet/aspect definitions, zodiac systems) - model_overrides: Custom modifications to model definitions - **Core Data Collections**: - subjects: People or events for which charts can be created - charts: Computed chart instances (actual charts with planetary positions) - chart_presets: Reusable configuration templates (house system, display settings) - **Organization \u0026amp; Presentation**: - layouts: View configurations for displaying charts (single, dual-wheel, comparison) - annotations: Notes, interpretations, and commentary - aspects: List of aspect IDs enabled for this workspace Typical Usage: 1. Load or create a workspace 2. Add subjects (people/events with birth data) 3. Create charts using subjects and presets 4. Apply layouts to visualize charts 5. Add annotations for interpretation\nExample: python ws = Workspace( owner=\u0026quot;astrologer@example.com\u0026quot;, active_model=\u0026quot;western\u0026quot;, default=WorkspaceDefaults( ephemeris_engine=EngineType.SWISSEPH, ephemeris_backend=None, default_house_system=HouseSystem.PLACIDUS ), subjects=[...], charts=[...] ) Dataclass fields owner: str subjects: List charts: List chart_presets: List layouts: List annotations: List active_model: Union default: WorkspaceDefaults aspects: List bodies: List models: Dict model_overrides: Union class WorkspaceDefaults Aggregated default settings for a workspace (preferred YAML shape).\nThis mirrors the desired manifest structure under the top-level key \u0026lsquo;default\u0026rsquo;. Provides workspace-wide defaults that can be overridden at the workspace level.\nDataclass fields default_house_system: Union default_bodies: Union default_aspects: Union ephemeris_engine: Union ephemeris_backend: Union element_colors: Union radix_point_colors: Union default_location: Union language: Union theme: Union time_system: Union class ZodiacType (str, Enum) "
  },
  {
    "title": "services Module",
    "url": "auto/services/",
    "content": "services module Functions build_chart_instance build_chart_instance(name: str, dt_str: str, loc_text: str, mode: module.models.ChartMode, ws: Optional[module.models.Workspace] = None, ephemeris_path: Optional[str] = None) -\u0026gt; module.models.ChartInstance Build a ChartInstance using workspace defaults when provided.\nResolves engine and house system from ws if available. Uses utils.prepare_horoscope to produce a fully-typed ChartInstance. build_radix_figure_for_chart build_radix_figure_for_chart(chart: module.models.ChartInstance, engine_override: Optional[module.models.EngineType] = None, ephemeris_path_override: Optional[str] = None, ws: Optional[ForwardRef(\u0026#39;Workspace\u0026#39;)] = None) -\u0026gt; Any Extract positions from a ChartInstance\u0026rsquo;s computed_chart and return a Plotly Figure ready to render.\nParameters chart: ChartInstance to compute positions for\nengine_override: Optional engine to use instead of chart\u0026rsquo;s stored engine\nephemeris_path_override: Optional ephemeris path to use instead of chart\u0026rsquo;s stored path\nws: Optional workspace for resolving observable objects defaults\nReturns Plotly Figure object ready for rendering\ncompute_aspects compute_aspects(bodies: List[module.models.CelestialBody], aspect_defs: List[module.models.AspectDefinition]) -\u0026gt; List[module.models.Aspect] Compute aspects between celestial bodies using provided definitions.\nParameters bodies: List of celestial bodies to compute aspects for\naspect_defs: List of aspect definitions to use for detection\nReturns List of Aspect objects representing detected aspects\ncompute_aspects_for_chart compute_aspects_for_chart(chart: module.models.ChartInstance, aspect_definitions: Optional[List[module.models.AspectDefinition]] = None, ws: Optional[ForwardRef(\u0026#39;Workspace\u0026#39;)] = None) -\u0026gt; List[Dict[str, Any]] Compute aspects between celestial bodies in a chart.\nParameters chart: ChartInstance to compute aspects for\naspect_definitions: List of aspect definitions (orbs, types) If None, uses chart.config.aspect_orbs or workspace defaults\nws: Optional workspace for default aspect definitions\nReturns List of aspect dictionaries, each with: { \u0026lsquo;from\u0026rsquo;: str, # Source object ID (e.g., \u0026lsquo;sun\u0026rsquo;) \u0026rsquo;to\u0026rsquo;: str, # Target object ID (e.g., \u0026lsquo;moon\u0026rsquo;) \u0026rsquo;type\u0026rsquo;: str, # Aspect type: \u0026lsquo;conjunction\u0026rsquo;, \u0026lsquo;sextile\u0026rsquo;, \u0026lsquo;square\u0026rsquo;, \u0026rsquo;trine\u0026rsquo;, \u0026lsquo;opposition\u0026rsquo; \u0026lsquo;angle\u0026rsquo;: float, # Actual angle between objects (degrees) \u0026lsquo;orb\u0026rsquo;: float, # Orb (deviation from exact aspect, degrees) \u0026rsquo;exact_angle\u0026rsquo;: float, # Exact aspect angle (0, 60, 90, 120, 180) \u0026lsquo;applying\u0026rsquo;: bool, # True if aspect is applying (getting closer) \u0026lsquo;separating\u0026rsquo;: bool # True if aspect is separating }\ncompute_jpl_positions compute_jpl_positions(name: str, dt_str: str, loc_str: str, ephemeris_path: Optional[str] = None, requested_objects: Optional[List[str]] = None, include_physical: bool = False, include_topocentric: bool = False, extended: bool = False) -\u0026gt; Dict[str, Union[float, Dict[str, float]]] Compute planetary positions using Skyfield JPL ephemerides.\nParameters name: subject name (human-readable; not used in computation)\ndt_str: datetime string (parsed by utils.Actual)\nloc_str: location string (parsed by utils.Actual)\nephemeris_path: optional path to a local BSP file; falls back to default\nrequested_objects: optional list of object IDs to compute\ninclude_physical: if True, include magnitude/phase/elongation (extended mode only)\ninclude_topocentric: if True, include altitude/azimuth (extended mode only)\nextended: if True, return extended format with distance/declination/RA\nReturns Mapping planet -\u0026gt; ecliptic longitude (float) or extended dict Empty dict if computation is unavailable compute_positions compute_positions(engine: Optional[module.models.EngineType], name: str, dt_str: str, loc_str: str, ephemeris_path: Optional[str] = None, requested_objects: Optional[List[str]] = None) -\u0026gt; Dict[str, Union[float, Dict[str, float]]] Dispatch position computation based on engine.\nFor EngineType.JPL, returns a dict of ecliptic longitudes using Skyfield and a local ephemeris file. For other or None, returns Kerykeion observable object longitudes (degrees) as a dict. Parameters engine: Computation engine to use\nname: Subject name\ndt_str: Datetime string\nloc_str: Location string\nephemeris_path: Optional path to ephemeris file\nrequested_objects: Optional list of object IDs to compute (filters results)\nReturns Dict mapping object_id -\u0026gt; ecliptic_longitude (degrees) or extended dict. Empty dict on error or if no positions found.\nWarnings ⚠️ ValueError: If datetime or location cannot be parsed FileNotFoundError: If ephemeris file is specified but not found\ncompute_positions_for_chart compute_positions_for_chart(chart: module.models.ChartInstance, ws: Optional[ForwardRef(\u0026#39;Workspace\u0026#39;)] = None, include_physical: bool = False, include_topocentric: bool = False) -\u0026gt; Dict[str, Union[float, Dict[str, float]]] Compute positions using a ChartInstance\u0026rsquo;s engine and ephemeris settings. Uses chart.subject.event_time and chart.subject.location.name for location lookup. Handles both ChartInstance objects and dict-like structures safely.\nParameters chart: ChartInstance to compute positions for\nws: Optional workspace for resolving observable objects defaults\ninclude_physical: If True, include magnitude/phase/elongation (JPL only)\ninclude_topocentric: If True, include altitude/azimuth (JPL with location)\nReturns Dict mapping object_id -\u0026gt; position data. Empty dict on error or if no positions found: - For non-JPL engines: float (longitude in degrees) - For JPL engine: dict with keys: - \u0026rsquo;longitude\u0026rsquo;: float (degrees) - always present - \u0026rsquo;latitude\u0026rsquo;: float (degrees) - if available - \u0026lsquo;distance\u0026rsquo;: float (AU) - always present for JPL - \u0026lsquo;declination\u0026rsquo;: float (degrees) - always present for JPL - \u0026lsquo;right_ascension\u0026rsquo;: float (degrees) - always present for JPL - \u0026lsquo;altitude\u0026rsquo;: float (degrees) - if include_topocentric and location available - \u0026lsquo;azimuth\u0026rsquo;: float (degrees) - if include_topocentric and location available - \u0026lsquo;apparent_magnitude\u0026rsquo;: float - if include_physical - \u0026lsquo;phase_angle\u0026rsquo;: float (degrees) - if include_physical - \u0026rsquo;elongation\u0026rsquo;: float (degrees) - if include_physical - \u0026rsquo;light_time\u0026rsquo;: float (seconds) - if include_physical - \u0026lsquo;speed\u0026rsquo;: float (degrees/day) - if available - \u0026lsquo;retrograde\u0026rsquo;: bool - if available\nWarnings ⚠️ ValueError: If chart is missing required subject or location data\ncompute_positions_for_inputs compute_positions_for_inputs(engine: Optional[module.models.EngineType], name: str, dt_str: str, loc_text: str, ephemeris_path: Optional[str] = None, requested_objects: Optional[List[str]] = None) -\u0026gt; Dict[str, float] Thin wrapper over compute_positions to normalize/forward parameters from UI layers.\ncompute_subject compute_subject(name: str, dt_str: str, loc_str: str, zodiac: str = \u0026#39;Tropic\u0026#39;) -\u0026gt; kerykeion.backword.AstrologicalSubject Construct a Kerykeion AstrologicalSubject from strings.\nParameters name: Subject name (human-readable identifier)\ndt_str: Datetime string (parsed by utils.Actual)\nloc_str: Location string (parsed by utils.Actual)\nzodiac: Zodiac type, defaults to \u0026ldquo;Tropic\u0026rdquo;\nReturns AstrologicalSubject instance with computed positions\ncreate_relation_svg create_relation_svg(subject1: kerykeion.backword.AstrologicalSubject, subject2: kerykeion.backword.AstrologicalSubject, chart_type: str = \u0026#39;Synastry\u0026#39;) -\u0026gt; kerykeion.backword.KerykeionChartSVG Create a Kerykeion SVG chart for relation/composite types.\nParameters subject1: First astrological subject\nsubject2: Second astrological subject\nchart_type: Type of relation chart (e.g., \u0026ldquo;Synastry\u0026rdquo;, \u0026ldquo;Composite\u0026rdquo;), defaults to \u0026ldquo;Synastry\u0026rdquo;\nReturns KerykeionChartSVG instance with generated SVG chart\nextract_kerykeion_points extract_kerykeion_points(obj: Any) -\u0026gt; pandas.core.frame.DataFrame Extract KerykeionPointModel attributes from an object into a DataFrame.\nParameters obj: Object containing KerykeionPointModel attributes Returns DataFrame with one row per KerykeionPointModel attribute found\nfind_chart_by_name_or_id find_chart_by_name_or_id(ws: Optional[module.models.Workspace], name_or_id: str) -\u0026gt; Optional[module.models.ChartInstance] Find a chart in the workspace by subject name or chart ID.\nParameters ws: Workspace to search in\nname_or_id: Subject name or chart ID to search for\nReturns ChartInstance if found, None otherwise\nget_active_model get_active_model(ws: Optional[ForwardRef(\u0026#39;Workspace\u0026#39;)]) -\u0026gt; Optional[module.models.AstroModel] Resolve the currently active AstroModel instance from a Workspace, if available.\nParameters ws: Workspace instance to get active model from Returns Active AstroModel instance, or None if no models available\nlist_open_view_rows list_open_view_rows(ws: Optional[module.models.Workspace]) -\u0026gt; List[Dict[str, str]] Produce table rows for Open view display.\nParameters ws: Workspace containing charts Returns List of dictionaries with keys: name, event_time, location, tags, search_text\nmerge_model_with_overrides merge_model_with_overrides(model: module.models.AstroModel, overrides: Optional[module.models.ModelOverrides]) -\u0026gt; module.models.AstroModel Return a new AstroModel with selective overrides applied.\nParameters model: Base AstroModel to apply overrides to\noverrides: Optional ModelOverrides containing override definitions\nReturns New AstroModel instance with overrides applied\nresolve_effective_defaults resolve_effective_defaults(ws: \u0026#39;Workspace\u0026#39;, model: Optional[module.models.AstroModel]) -\u0026gt; Dict[str, object] Resolve effective defaults merging workspace overrides on top of AstroModel settings.\nParameters ws: Workspace containing default overrides\nmodel: Optional AstroModel with base settings\nReturns Dictionary with keys: house_system, bodies, aspects, standard_orb, engine, zodiac_type, ayanamsa, aspect_orbs, observable_objects\nsearch_charts search_charts(ws: Optional[module.models.Workspace], query: str) -\u0026gt; List[module.models.ChartInstance] Search charts in workspace using case-insensitive text matching.\nParameters ws: Workspace to search in\nquery: Search query string\nReturns List of ChartInstance objects matching the query\nClasses class Subject Lightweight wrapper around Kerykeion\u0026rsquo;s AstrologicalSubject builder.\nUsage:\nCall at_place() then at_time() to prepare self.computed. Use data() to extract names, degrees, and labels for plotting. Methods at_place(self, location: object) -\u0026gt; None\nSet place from a free-text location or coordinates string.\nat_time(self, time: str) -\u0026gt; None\nSet event time from a free-text datetime string and build computed subject.\ndata(self)\nReturn (object_names, degrees, labels) extracted from computed planets list.\nreport(self)\nBuild a Kerykeion textual Report for the computed subject.\n"
  },
  {
    "title": "storage Module",
    "url": "auto/storage/",
    "content": "storage module DuckDB and Parquet storage helpers for computed astrological data.\nThis module provides optional storage functionality for Python to write computed positions and aspects directly to DuckDB/Parquet, avoiding large JSON transfers for batch operations like transit series.\nUsage: from module.storage import DuckDBStorage\nstorage = DuckDBStorage('/path/to/workspace/data/workspace.db') storage.store_positions(chart_id, datetime, positions, engine='jpl') # Optimized batch computation storage.compute_and_store_series( chart_id='transit_base', start_datetime=start_dt, end_datetime=end_dt, time_step=timedelta(minutes=1), location=location, engine='jpl', requested_objects=['sun', 'moon', ...], include_physical=True, include_topocentric=True ) Functions get_storage_path get_storage_path(workspace_path: str | pathlib.Path) -\u0026gt; pathlib.Path Get DuckDB storage path for a workspace.\nParameters workspace_path: Path to workspace.yaml Returns Path to workspace.db in data/ directory\nClasses class DuckDBStorage DuckDB storage for computed astrological data.\nProvides methods to store positions and aspects directly in DuckDB, avoiding large JSON transfers for batch operations.\nMethods close(self)\nClose database connection.\ncompute_and_store_series(self, chart_id: str, start_datetime: datetime.datetime, end_datetime: datetime.datetime, time_step: datetime.timedelta, location: 'Location', engine: str = 'swisseph', ephemeris_file: str | None = None, requested_objects: List[str] | None = None, include_physical: bool = False, include_topocentric: bool = False, batch_size: int = 1000, radix_chart_id: str | None = None) -\u0026gt; int\nOptimized: Compute and store time series with pre-initialized engines.\ncompute_aspects_from_positions(self, chart_id: str, datetime_str: str | None = None, aspect_definitions: List[Dict[str, float]] | None = None, max_orb: float = 10.0)\nCompute aspects from stored positions using SQL.\nexport_to_parquet(self, output_dir: str | pathlib.Path, chart_id: str | None = None, partition_by_date: bool = True, partition_by_hour: bool = False, compression: str = 'snappy') -\u0026gt; List[pathlib.Path]\nExport positions to Parquet files.\nquery_positions(self, chart_id: str | None = None, start_datetime: str | datetime.datetime | None = None, end_datetime: str | datetime.datetime | None = None, object_id: str | None = None, use_parquet: bool | None = None, parquet_dir: str | pathlib.Path | None = None, auto_route: bool = True)\nQuery positions with optional Parquet fallback and smart routing.\nquery_radix_relative_positions(self, transit_chart_id: str, radix_chart_id: str, datetime_str: str | None = None, start_datetime: str | datetime.datetime | None = None, end_datetime: str | datetime.datetime | None = None)\nQuery transit positions relative to radix positions.\nstore_positions(self, chart_id: str, datetime_str: str, positions: Dict[str, float | Dict[str, float]], engine: str | None = None, ephemeris_file: str | None = None, radix_chart_id: str | None = None) -\u0026gt; None\nStore computed positions in DuckDB.\nstore_positions_batch(self, chart_id: str, positions_list: List[tuple], engine: str | None = None, ephemeris_file: str | None = None, auto_export_parquet: bool = True, parquet_threshold: int = 100, parquet_dir: str | pathlib.Path | None = None) -\u0026gt; List[pathlib.Path] | None\nStore multiple positions in batch (for transit series).\nstore_radix_positions(self, radix_chart_id: str, datetime_str: str, positions: Dict[str, float | Dict[str, float]], engine: str | None = None, ephemeris_file: str | None = None) -\u0026gt; None\nStore radix (base chart) positions.\n"
  },
  {
    "title": "ui_streamlit Module",
    "url": "auto/ui_streamlit/",
    "content": "ui_streamlit module Functions main main() render_toolbar render_toolbar() How the UI is invoked Entry point: main()\n"
  },
  {
    "title": "utils Module",
    "url": "auto/utils/",
    "content": "utils module Functions combine_date_time combine_date_time(input_date: datetime.date, input_time: datetime.time) -\u0026gt; datetime.datetime Combine a date and time into a naive datetime (no timezone).\nParameters input_date: Date object\ninput_time: Time object\nReturns Naive datetime combining date and time\ncompute_vernal_equinox_offset compute_vernal_equinox_offset(year: int, eph, observer, ts) -\u0026gt; float Compute the vernal equinox offset for tropical astrology adjustment.\nParameters year: The year to compute the vernal equinox for\neph: Skyfield ephemeris object\nobserver: Skyfield Topos observer object\nts: Skyfield timescale object\nReturns The ecliptic longitude offset in degrees [0, 360)\ndefault_ephemeris_path default_ephemeris_path() -\u0026gt; str Return the default path to the local JPL ephemeris file.\nReturns Absolute path to de421.bsp file in source/ directory\nensure_aware ensure_aware(dt: datetime.datetime, tz_name: Optional[str] = None) -\u0026gt; datetime.datetime Return a timezone-aware datetime.\nParameters dt: Datetime to make timezone-aware\ntz_name: Optional timezone name for localization, defaults to UTC\nReturns Timezone-aware datetime. If dt is already aware, returns unchanged. If tz_name provided, localizes to that timezone. Otherwise uses UTC.\nexpand_range expand_range(center: datetime.datetime, days: int) -\u0026gt; module.models.DateRange Create a DateRange centered on a datetime extending days on both sides.\nParameters center: Center datetime for the range\ndays: Number of days to extend on each side\nReturns DateRange from (center - days) to (center + days)\nexport_chart_yaml export_chart_yaml(chart: module.models.ChartInstance, dest_dir: str) -\u0026gt; str Export a ChartInstance as YAML into dest_dir.\nParameters chart: ChartInstance to export\ndest_dir: Destination directory for YAML file\nReturns Absolute file path to exported YAML file\nexport_workspace_yaml export_workspace_yaml(ws: module.models.Workspace, dest_path: Union[str, pathlib._local.Path]) -\u0026gt; pathlib._local.Path Export a Workspace as YAML to dest_path.\nParameters ws: Workspace instance to export\ndest_path: Destination file path\nReturns Resolved Path to exported YAML file\nNotes Uses the local serializer to convert dataclasses, enums, and datetimes to primitives. Ensures parent directory exists.\nfind_vernal_equinox_datetime find_vernal_equinox_datetime(year: int) -\u0026gt; datetime.datetime Find the approximate datetime of the vernal equinox for a given year.\nParameters year: The year to find the vernal equinox for Returns A timezone-aware UTC datetime for the approximate vernal equinox\nimport_chart_yaml import_chart_yaml(path: str) -\u0026gt; module.models.ChartInstance Read a chart YAML file from disk and parse into a ChartInstance.\nParameters path: Path to chart YAML file Returns ChartInstance parsed from YAML file\nin_range in_range(dt: datetime.datetime, dr: module.models.DateRange) -\u0026gt; bool Check if datetime lies within the inclusive DateRange.\nParameters dt: Datetime to check\ndr: DateRange with start and end datetimes\nReturns True if dt is within [start, end] (inclusive), False otherwise\nload_sfs_models_from_dir load_sfs_models_from_dir(dir_path: Union[str, pathlib._local.Path]) -\u0026gt; Dict[str, module.models.AstroModel] Scan a directory for StarFisher .sfs files and build AstroModel catalogs.\nParameters dir_path: Directory path to scan for .sfs files Returns Dictionary mapping model name (from file stem or parsed model.name) to AstroModel. Files that cannot be decoded or parsed are skipped.\nlocation_equals location_equals(loc1: module.models.Location, loc2: module.models.Location) -\u0026gt; bool Check approximate equality of two Location objects.\nParameters loc1: First location to compare\nloc2: Second location to compare\nReturns True if locations are approximately equal, False otherwise\nlocation_from_coords location_from_coords(lat: float, lon: float, name: str = \u0026#39;\u0026#39;) -\u0026gt; module.models.Location Build a Location from raw coordinates, inferring timezone via TimezoneFinder.\nParameters lat: Latitude in degrees\nlon: Longitude in degrees\nname: Optional location name, defaults to coordinate string\nReturns Location instance with inferred timezone\nnow_utc now_utc() -\u0026gt; datetime.datetime Return current time as a timezone-aware UTC datetime.\nparse_chart_yaml parse_chart_yaml(data: dict) -\u0026gt; module.models.ChartInstance Construct a ChartInstance from a YAML-mapped dict with safe coercions.\nParameters data: Dictionary containing chart data (subject, config, id, tags) Returns ChartInstance with parsed subject and config\nNotes Removes \u0026lsquo;computed_chart\u0026rsquo; if present (it\u0026rsquo;s recomputable and shouldn\u0026rsquo;t be loaded from YAML)\nWarnings ⚠️ ValueError: If subject data is invalid\nparse_sfs_content parse_sfs_content(content: str) -\u0026gt; Tuple[module.models.AstroModel, Dict[str, Any]] Parse the content of a StarFisher .sfs file and map to AstroModel.\nParameters content: String content of the .sfs file Returns Tuple of (AstroModel, display_config_dict) where display_config_dict contains display-related settings from the .sfs file\nparse_yaml_content parse_yaml_content(data: Union[str, bytes]) -\u0026gt; dict Parse YAML from a string or bytes and return a dict.\nParameters data: YAML content as string or bytes Returns Parsed YAML as dictionary, or empty dict if empty/invalid\nNotes Useful for handling uploaded files or in-memory YAML content uniformly.\nprepare_horoscope prepare_horoscope(name: str = \u0026#39;\u0026#39;, dt: datetime.datetime = None, loc: module.models.Location = None, engine: Optional[module.models.EngineType] = None, ephemeris_path: Optional[str] = None, zodiac: module.models.ZodiacType = \u0026lt;ZodiacType.TROPICAL: \u0026#39;Tropical\u0026#39;\u0026gt;, house: module.models.HouseSystem = \u0026lt;HouseSystem.PLACIDUS: \u0026#39;Placidus\u0026#39;\u0026gt;) -\u0026gt; module.models.ChartInstance Create a ChartInstance with basic configuration.\nParameters name: Chart subject name\ndt: Event datetime\nloc: Location for the chart\nengine: Optional computation engine\nephemeris_path: Optional path to ephemeris file\nzodiac: Zodiac type, defaults to TROPICAL\nhouse: House system, defaults to PLACIDUS\nReturns ChartInstance with configured ChartSubject and ChartConfig\nread_yaml_file read_yaml_file(path: Union[str, pathlib._local.Path]) -\u0026gt; dict Read a YAML file and return a dict.\nParameters path: Path to YAML file Returns Parsed YAML content as dictionary, or empty dict if file is empty\nNotes This is a thin wrapper around yaml.safe_load that always returns a dict.\nresolve_under_base resolve_under_base(base: Union[str, pathlib._local.Path], rel_path: Union[str, pathlib._local.Path]) -\u0026gt; pathlib._local.Path Resolve rel_path against base and ensure the result stays within base.\nParameters base: Base directory path\nrel_path: Relative path to resolve\nReturns Resolved Path that is contained within base\nWarnings ⚠️ ValueError: If path is absolute or attempts directory traversal outside base\nto_timezone resolve_user_path(path: Union[str, pathlib._local.Path], *, base_dir: Union[str, pathlib._local.Path, NoneType] = None) -\u0026gt; pathlib._local.Path Resolve a user-provided path safely.\nto_timezone to_timezone(dt: datetime.datetime, tz_name: str) -\u0026gt; datetime.datetime Convert a timezone-aware datetime to the target timezone by name.\nParameters dt: Timezone-aware datetime to convert\ntz_name: Target timezone name (e.g., \u0026ldquo;UTC\u0026rdquo;, \u0026ldquo;Europe/Prague\u0026rdquo;)\nReturns Datetime converted to target timezone\nwrite_json_file write_json_file(path: Union[str, pathlib._local.Path], data: dict, *, indent: int = 2) -\u0026gt; None Write a dict to a JSON file.\nParameters path: Destination file path\ndata: JSON-serializable data\nindent: Indentation level, defaults to 2\nNotes Ensures parent directory exists before writing.\nwrite_yaml_file write_yaml_file(path: Union[str, pathlib._local.Path], data: dict, *, sort_keys: bool = False, allow_unicode: bool = True) -\u0026gt; None Write a dict to a YAML file using yaml.safe_dump.\nParameters path: Destination file path\ndata: Dictionary to write\nsort_keys: Whether to sort keys in output, defaults to False\nallow_unicode: Whether to allow unicode characters, defaults to True\nNotes Ensures parent directory exists. Callers should pass already-serialized primitives (e.g., via a to_primitive function) if the input data contains dataclasses, enums, or datetime objects.\nClasses class Actual Universal holder for either a place or time object. Useful for normalizing user input and controlling shiftable dimensions in astrology.\nMethods add_time(self, delta: Union[int, datetime.timedelta, str]) -\u0026gt; None\nassign_timezone(self, tz: Optional[str] = None) -\u0026gt; None\nto_model_location(self) -\u0026gt; Optional[module.models.Location]\n"
  },
  {
    "title": "workspace Module",
    "url": "auto/workspace/",
    "content": "workspace module Functions add_chart add_chart(ws: module.models.Workspace, chart: module.models.ChartInstance, base_dir: str | pathlib.Path) -\u0026gt; str Add a chart to a Workspace and write its YAML file.\nParameters ws: Workspace to add chart to\nchart: ChartInstance to add\nbase_dir: Base directory for workspace files\nReturns Relative path to chart YAML file\nNotes Prevents duplicates by checking existing charts. Removes computed_chart and ephemeral overrides before serialization.\nadd_or_update_chart add_or_update_chart(ws: module.models.Workspace, chart: module.models.ChartInstance, base_dir: str | pathlib.Path) -\u0026gt; str Add a new chart or update an existing one by id or subject name; persist YAML and manifest.\nadd_subject add_subject(ws: module.models.Workspace, subject: module.models.ChartSubject, base_dir: str | pathlib.Path) -\u0026gt; str Add a subject to a Workspace and write its YAML file.\nParameters ws: Workspace to add subject to\nsubject: ChartSubject instance to add\nbase_dir: Base directory for workspace files\nReturns Relative path (e.g., subjects/john-doe.yml) written to disk\nNotes Caller should re-save the manifest via save_workspace_modular.\nbuild_workspace_from_sfs build_workspace_from_sfs(dir_path: str | pathlib.Path, owner: str = \u0026#39;local\u0026#39;, ephemeris_name: str = \u0026#39;local\u0026#39;, ephemeris_backend: str = \u0026#39;local\u0026#39;) -\u0026gt; module.models.Workspace Create a new Workspace from a directory of .sfs model files.\nParameters dir_path: Directory path containing .sfs files\nowner: Workspace owner identifier, defaults to \u0026ldquo;local\u0026rdquo;\nephemeris_name: Ephemeris name, defaults to \u0026ldquo;local\u0026rdquo;\nephemeris_backend: Ephemeris backend, defaults to \u0026ldquo;local\u0026rdquo;\nReturns Workspace instance with loaded models and empty collections\nNotes Loads all .sfs files into AstroModel catalogs, selects the first model as active (if any), and initializes empty collections for charts, presets, layouts, and annotations.\nbuild_workspace_from_sfs_to_yaml build_workspace_from_sfs_to_yaml(dir_path: str | pathlib.Path, out_path: str | pathlib.Path, owner: str = \u0026#39;local\u0026#39;, ephemeris_name: str = \u0026#39;local\u0026#39;, ephemeris_backend: str = \u0026#39;local\u0026#39;) -\u0026gt; pathlib.Path Build a Workspace from .sfs files and save it as YAML.\nParameters dir_path: Directory containing .sfs files\nout_path: Output YAML file path\nowner: Workspace owner identifier, defaults to \u0026ldquo;local\u0026rdquo;\nephemeris_name: Ephemeris name, defaults to \u0026ldquo;local\u0026rdquo;\nephemeris_backend: Ephemeris backend, defaults to \u0026ldquo;local\u0026rdquo;\nReturns Path to written YAML file\nget_all_aspect_definitions get_all_aspect_definitions(ws: ForwardRef(\u0026#39;Workspace\u0026#39;) | None = None, model: module.models.AstroModel | None = None) -\u0026gt; Dict[str, module.models.AspectDefinition] Get all aspect definitions (defaults + custom from workspace/model YAML).\nParameters ws: Optional Workspace to load custom aspects from\nmodel: Optional AstroModel to get aspect definitions from\nReturns Dictionary mapping aspect_id -\u0026gt; AspectDefinition. Custom aspects from workspace/model override defaults with the same id.\nget_all_observable_objects get_all_observable_objects(ws: ForwardRef(\u0026#39;Workspace\u0026#39;) | None = None, model: module.models.AstroModel | None = None) -\u0026gt; Dict[str, module.models.BodyDefinition] Get all observable objects (defaults + custom from workspace/model YAML).\nParameters ws: Optional Workspace to load custom objects from\nmodel: Optional AstroModel to get body definitions from\nReturns Dictionary mapping object_id -\u0026gt; BodyDefinition. Custom objects from workspace/model override defaults with the same id.\nget_default_aspect_definitions get_default_aspect_definitions() -\u0026gt; Dict[str, module.models.AspectDefinition] Get default aspect definitions.\nReturns Dictionary mapping aspect_id -\u0026gt; AspectDefinition for standard aspects (conjunction, opposition, trine, square, sextile, etc.)\nget_default_observable_objects get_default_observable_objects() -\u0026gt; Dict[str, module.models.BodyDefinition] Get default observable object definitions.\nReturns Dictionary mapping object_id -\u0026gt; BodyDefinition for standard objects (planets, angles, lunar nodes, calculated points) that are commonly available in kerykeion and can be computed.\ninit_workspace init_workspace(base_dir: str | pathlib.Path, owner: str, active_model: str, default_ephemeris: Dict[str, str]) -\u0026gt; pathlib.Path Initialize a new workspace directory structure and manifest.\nReturns Absolute path to the created workspace.yaml file. iter_charts iter_charts(ws: module.models.Workspace) -\u0026gt; Iterator[module.models.ChartInstance] Yield charts from the Workspace (safe for None).\nParameters ws: Workspace to iterate charts from load_workspace load_workspace(workspace_path: str) -\u0026gt; module.models.Workspace Load a modular workspace from a manifest YAML file.\nParameters workspace_path: Absolute or relative path to workspace.yaml. Returns Workspace dataclass assembled from the manifest and referenced parts.\nNotes Paths referenced in the manifest (e.g., charts/*.yml) are resolved relative to the manifest directory and validated for containment.\nWarnings ⚠️ FileNotFoundError: If workspace file does not exist\nload_workspace_from_dir load_workspace_from_dir(base_dir: str | pathlib.Path) -\u0026gt; module.models.Workspace Load a workspace given its base directory.\nParameters base_dir: Directory containing workspace.yaml manifest Returns Workspace instance loaded from manifest\nWarnings ⚠️ FileNotFoundError: If directory or workspace.yaml does not exist\npopulate_workspace_models populate_workspace_models(ws: Any, dir_path: str | pathlib.Path) -\u0026gt; Dict[str, module.models.AstroModel] Load .sfs models from a directory and assign them to workspace.\nParameters ws: Workspace instance to populate models into\ndir_path: Directory path to scan for .sfs files\nReturns Dictionary of loaded models\nNotes If ws.active_model is not set and any models were loaded, sets it to the first key.\nprune_workspace_manifest prune_workspace_manifest(base_dir: str | pathlib.Path) -\u0026gt; Dict[str, List[str]] Prune workspace.yaml to remove references to modular files that no longer exist.\nrecompute_all recompute_all(ws: module.models.Workspace) -\u0026gt; Dict[str, Dict[str, float]] Compute positions for all charts in a workspace.\nParameters ws: Workspace containing charts to recompute Returns Dictionary mapping chart_id -\u0026gt; positions dict. Charts that fail to compute will have empty dict as value.\nremove_chart remove_chart(ws: module.models.Workspace, chart_id: str) -\u0026gt; bool Remove a chart from the Workspace in-memory by its id.\nParameters ws: Workspace containing the chart\nchart_id: ID of chart to remove\nReturns True if chart was found and removed, False otherwise\nremove_chart_by_id remove_chart_by_id(ws: module.models.Workspace, chart_id: str, base_dir: str | pathlib.Path) -\u0026gt; bool Remove a chart by id and persist the manifest. Returns True if removed.\nsave_workspace_flat save_workspace_flat(workspace: module.models.Workspace, path: str, format: str = \u0026#39;yaml\u0026#39;) -\u0026gt; None Save the entire Workspace as a single flat file (debug/export use).\nParameters workspace: Workspace instance to serialize\npath: Destination file path (YAML or JSON)\nformat: \u0026ldquo;yaml\u0026rdquo; or \u0026ldquo;json\u0026rdquo;, defaults to \u0026ldquo;yaml\u0026rdquo;\nWarnings ⚠️ ValueError: If format is not \u0026ldquo;yaml\u0026rdquo; or \u0026ldquo;json\u0026rdquo;\nsave_workspace_modular save_workspace_modular(workspace: module.models.Workspace, base_dir: str | pathlib.Path) -\u0026gt; pathlib.Path Persist a Workspace into modular YAML files and update workspace.yaml.\nReturns Absolute path to the updated workspace.yaml. scan_workspace_changes scan_workspace_changes(base_dir: str | pathlib.Path) -\u0026gt; Dict[str, Dict[str, List[str]]] Scan the workspace directory for drift relative to the manifest.\nReturns { \u0026lsquo;charts\u0026rsquo;: {\u0026rsquo;new_on_disk\u0026rsquo;: [\u0026hellip;], \u0026lsquo;missing_on_disk\u0026rsquo;: [\u0026hellip;]}, \u0026lsquo;subjects\u0026rsquo;: {\u0026rsquo;new_on_disk\u0026rsquo;: [\u0026hellip;], \u0026lsquo;missing_on_disk\u0026rsquo;: [\u0026hellip;]} } where item names are basenames (e.g., john-doe.yml). summarize_chart summarize_chart(chart: module.models.ChartInstance) -\u0026gt; Dict[str, str | List[str]] Return a lightweight summary dict for a chart.\nParameters chart: ChartInstance to summarize Returns Dictionary with keys: id, name, event_time, location, engine, zodiac_type, house_system, tags\nsync_workspace sync_workspace(workspace_path: str | pathlib.Path, auto_import: bool = True, auto_remove: bool = False) -\u0026gt; Dict[str, Any] Synchronize workspace manifest with files on disk.\nParameters workspace_path: Path to workspace.yaml\nauto_import: If True, automatically import new charts/subjects found on disk\nauto_remove: If True, remove references to missing files from manifest\nReturns Dict with sync results: - \u0026lsquo;changes\u0026rsquo;: Dict from scan_workspace_changes() - \u0026lsquo;imported_charts\u0026rsquo;: List of chart IDs imported - \u0026lsquo;imported_subjects\u0026rsquo;: List of subject IDs imported - \u0026lsquo;removed_charts\u0026rsquo;: List of chart references removed (if auto_remove=True) - \u0026lsquo;removed_subjects\u0026rsquo;: List of subject references removed (if auto_remove=True)\nupdate_chart update_chart(ws: module.models.Workspace, chart_id: str, updater: Callable[[module.models.ChartInstance], module.models.ChartInstance]) -\u0026gt; bool Update a chart in-memory by id using a caller-provided updater.\nParameters ws: Workspace containing the chart\nchart_id: ID of chart to update\nupdater: Function that takes ChartInstance and returns updated ChartInstance\nReturns True if chart was found and updated, False otherwise\nvalidate_workspace validate_workspace(ws: Any) -\u0026gt; List[str] Validate workspace consistency and return a list of human-readable issues.\nParameters ws: Workspace instance to validate Returns List of human-readable issue strings (empty if no issues found)\nNotes Checks: - Active model presence and resolution - WorkspaceDefaults default_bodies/default_aspects exist in the active model - Top-level ws.aspects exist in the active model - Each ChartInstance.config included_points exist in the active model bodies - Each ChartInstance.config aspect_orbs keys exist in the active model aspects - Layout chart references resolve to existing chart IDs\nvalidation_report validation_report(ws: Any) -\u0026gt; str Return a multi-line text report from validate_workspace().\nParameters ws: Workspace instance to validate Returns Multi-line text report of validation issues, or success message\n"
  },
  {
    "title": "z_visual Module",
    "url": "auto/z_visual/",
    "content": "z_visual module Functions build_radix_figure build_radix_figure(positions: dict) -\u0026gt; plotly.graph_objs._figure.Figure Build a standardized polar (radix) chart figure from planet positions in degrees [0,360). positions: mapping of planet name (lowercase or mixed) -\u0026gt; ecliptic longitude in degrees\ndisplay_3d display_3d(categories: list, values_degrees: list, labels: list) -\u0026gt; None Render a simple Matplotlib 3D scatter plot for objects at given angles.\ndisplay_radial display_radial(categories: list, values_degrees: list, labels: list) -\u0026gt; None Quick Matplotlib radial scatter demo for given categories and degrees.\nParameters categories: labels for angular ticks\nvalues_degrees: list of angles in degrees\nlabels: point labels to show as markers\nfigure_3d figure_3d(objects: object) Build a Plotly polar figure for planetary positions and zodiac labels.\ngenerate_moon_dec generate_moon_dec(o: object) -\u0026gt; None Plot moon declination time series from DataFrame-like o.\ngenerate_planets_dec generate_planets_dec(o: object) -\u0026gt; None Plot declination time series for all non-moon bodies in the DataFrame-like o.\ngenerate_skyfield_data generate_skyfield_data(sky_set: object) Render a polar plot from a mapping of right ascension/declination tuples.\nwrite_plotly_html write_plotly_html(fig: plotly.graph_objs._figure.Figure, tmpname: str = \u0026#39;radix_chart.html\u0026#39;) -\u0026gt; str Write a Plotly figure to a temporary HTML file and return its absolute path.\nParameters fig: Plotly Figure to serialize to HTML\ntmpname: filename to use within the system temporary directory\n"
  },
  {
    "title": "",
    "url": "architecture/",
    "content": "Overview The architecture separates UI, computation, workspace management, and storage so multiple frontends can share the same backend behavior.\nRepository Structure (top level) cache: temporary files for computation module: all the program logic scripts: generators for documentation tests: scenario and functionality testing venv: python environment with libraries Core Layers UI layer: Streamlit, Kivy, or Tauri/Svelte call Python services directly. Services layer: module.services computes positions/aspects and builds charts. Workspace layer: module.workspace loads/saves modular YAML workspaces. Storage layer: module.storage persists positions to DuckDB and Parquet (positions-only). Data Flow UI collects inputs and opens a workspace. Services compute positions (and aspects on demand). Storage writes positions to DuckDB and/or Parquet. UI renders charts from computed positions. Storage Model Positions-only: aspects are computed on demand. Dual engines: Kerykeion (fast) and JPL/Skyfield (extended properties). Radix support: radix_chart_id + is_radix for transit comparisons. Hybrid storage: DuckDB for active queries, Parquet for time series. Storage (Backend) Strategy (Python vs Rust) Python storage (batch):\nTransit series and large time ranges Background jobs and data migrations Avoids JSON transfer overhead Rust storage (interactive):\nSingle chart computations User-driven queries Small datasets and quick updates Both Python and Rust use the same DuckDB schema.\nSchema (Positions-Only) computed_positions stores:\nchart_id, datetime, object_id longitude, latitude declination, right_ascension, distance altitude, azimuth apparent_magnitude, phase_angle, elongation, light_time speed, retrograde engine, ephemeris_file radix_chart_id, is_radix has_equatorial, has_topocentric, has_physical Aspects are computed on demand from positions.\nCurrent behavior Dual engine support\nDefault: Kerykeion (Swisseph) Optional: JPL (extended properties) Radix-relative storage\nradix_chart_id links transits to base charts is_radix marks base vs transit rows Performance optimization\nPre-initialized engine for batch series No aspect storage (computed on demand) 3D-ready fields\ndeclination and distance stored for JPL Storage paths workspace/ ├── workspace.yaml └── data/ ├── workspace.db └── parquet/ └── chart_id_YYYY-MM-DD.parquet Layers by responsibility 🔹 Category 🌞 Horoscope 📊 Chart 🗂️ Workspace 🧱 Key Objects HoroscopeCelestialBodyAspectHouse ChartSubjectChartInstanceChartConfigChartPreset WorkspaceEphemerisSourceModelOverridesAnnotation ⚙️ Core Functions compute_positions(...)compute_aspects(...)compute_positions_for_chart(...) build_chart_instance(...)prepare_horoscope(...) load_workspace(...)save_workspace_modular(...)get_all_aspect_definitions(...) ✨ Features Immutable snapshotEngine-specific (Western, Vedic, etc.)Supports derived charts (e.g., progressed)Custom points via overrides Preset-driven configSupports ChartMode (NATAL, TRANSIT, etc.)Custom display and ephemeris override Modular YAML structurePer-user model customizationAnnotations, media, layouts Key entry points module.services.compute_positions(...) module.services.compute_positions_for_chart(...) module.services.compute_aspects_for_chart(...) module.services.build_chart_instance(...) module.workspace.load_workspace(...) module.workspace.save_workspace_modular(...) Schéma editor\n"
  },
  {
    "title": "",
    "url": "cli_reference/",
    "content": "CLI Reference Overview The Python sidecar provides a JSON-based CLI interface for the Tauri frontend. All commands accept JSON arguments and return JSON results.\nUsage python -m module.cli \u0026lt;command\u0026gt; [args_json] Or with JSON from stdin:\necho \u0026#39;{\u0026#34;workspace_path\u0026#34;: \u0026#34;/path/to/workspace.yaml\u0026#34;, \u0026#34;chart_id\u0026#34;: \u0026#34;my-chart\u0026#34;}\u0026#39; | python -m module.cli compute_chart Commands 1. compute_chart Compute positions and aspects for a chart.\nArguments:\n{ \u0026#34;workspace_path\u0026#34;: \u0026#34;/path/to/workspace.yaml\u0026#34;, \u0026#34;chart_id\u0026#34;: \u0026#34;chart-id\u0026#34;, \u0026#34;include_physical\u0026#34;: false, // Optional: Include extended physical properties (JPL only) \u0026#34;include_topocentric\u0026#34;: false // Optional: Include altitude/azimuth (JPL with location) } Response:\n{ \u0026#34;positions\u0026#34;: { \u0026#34;sun\u0026#34;: 45.5, // Simple format (non-JPL) or \u0026#34;moon\u0026#34;: { // Extended format (JPL) \u0026#34;longitude\u0026#34;: 120.3, \u0026#34;distance\u0026#34;: 0.0025, \u0026#34;declination\u0026#34;: 18.5, \u0026#34;right_ascension\u0026#34;: 122.1, \u0026#34;latitude\u0026#34;: 2.5, \u0026#34;speed\u0026#34;: 13.2, \u0026#34;retrograde\u0026#34;: false } }, \u0026#34;aspects\u0026#34;: [ { \u0026#34;from\u0026#34;: \u0026#34;sun\u0026#34;, \u0026#34;to\u0026#34;: \u0026#34;moon\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;trine\u0026#34;, \u0026#34;angle\u0026#34;: 119.5, \u0026#34;orb\u0026#34;: 0.5, \u0026#34;exact_angle\u0026#34;: 120.0, \u0026#34;applying\u0026#34;: false, \u0026#34;separating\u0026#34;: false } ], \u0026#34;chart_id\u0026#34;: \u0026#34;chart-id\u0026#34; } Error Response:\n{ \u0026#34;error\u0026#34;: \u0026#34;Chart chart-id not found\u0026#34;, \u0026#34;type\u0026#34;: \u0026#34;ChartNotFound\u0026#34; } 2. compute_transit_series Compute transit series for a time range.\nArguments:\n{ \u0026#34;workspace_path\u0026#34;: \u0026#34;/path/to/workspace.yaml\u0026#34;, \u0026#34;source_chart_id\u0026#34;: \u0026#34;source-chart-id\u0026#34;, \u0026#34;start_datetime\u0026#34;: \u0026#34;2024-01-01T00:00:00+01:00\u0026#34;, \u0026#34;end_datetime\u0026#34;: \u0026#34;2024-01-01T12:00:00+01:00\u0026#34;, \u0026#34;time_step\u0026#34;: \u0026#34;1 hour\u0026#34;, // e.g., \u0026#34;1 second\u0026#34;, \u0026#34;30 seconds\u0026#34;, \u0026#34;1 minute\u0026#34;, \u0026#34;1 hour\u0026#34;, \u0026#34;1 day\u0026#34; \u0026#34;transiting_objects\u0026#34;: [\u0026#34;sun\u0026#34;, \u0026#34;moon\u0026#34;], // Optional \u0026#34;transited_objects\u0026#34;: [\u0026#34;sun\u0026#34;, \u0026#34;moon\u0026#34;], // Optional \u0026#34;aspect_types\u0026#34;: [\u0026#34;conjunction\u0026#34;, \u0026#34;square\u0026#34;], // Optional \u0026#34;include_physical\u0026#34;: false, // Optional \u0026#34;include_topocentric\u0026#34;: false // Optional } Response:\n{ \u0026#34;source_chart_id\u0026#34;: \u0026#34;source-chart-id\u0026#34;, \u0026#34;time_range\u0026#34;: { \u0026#34;start\u0026#34;: \u0026#34;2024-01-01T00:00:00+01:00\u0026#34;, \u0026#34;end\u0026#34;: \u0026#34;2024-01-01T12:00:00+01:00\u0026#34; }, \u0026#34;time_step\u0026#34;: \u0026#34;1 hour\u0026#34;, \u0026#34;results\u0026#34;: [ { \u0026#34;datetime\u0026#34;: \u0026#34;2024-01-01T00:00:00+01:00\u0026#34;, \u0026#34;transit_positions\u0026#34;: { /* positions dict */ }, \u0026#34;aspects\u0026#34;: [ /* aspects list */ ] }, // ... more time points ] } 3. get_workspace_settings Get workspace settings and defaults.\nArguments:\n{ \u0026#34;workspace_path\u0026#34;: \u0026#34;/path/to/workspace.yaml\u0026#34; } Response:\n{ \u0026#34;owner\u0026#34;: \u0026#34;user@example.com\u0026#34;, \u0026#34;active_model\u0026#34;: \u0026#34;western\u0026#34;, \u0026#34;default\u0026#34;: { \u0026#34;ephemeris_engine\u0026#34;: \u0026#34;jpl\u0026#34;, \u0026#34;ephemeris_backend\u0026#34;: \u0026#34;de421\u0026#34;, \u0026#34;default_house_system\u0026#34;: \u0026#34;Placidus\u0026#34;, \u0026#34;default_bodies\u0026#34;: [\u0026#34;sun\u0026#34;, \u0026#34;moon\u0026#34;, \u0026#34;mercury\u0026#34;, \u0026#34;venus\u0026#34;, \u0026#34;mars\u0026#34;], \u0026#34;default_aspects\u0026#34;: [\u0026#34;conjunction\u0026#34;, \u0026#34;opposition\u0026#34;, \u0026#34;trine\u0026#34;, \u0026#34;square\u0026#34;, \u0026#34;sextile\u0026#34;], \u0026#34;language\u0026#34;: \u0026#34;en\u0026#34;, \u0026#34;theme\u0026#34;: \u0026#34;default\u0026#34; }, \u0026#34;aspects\u0026#34;: [\u0026#34;conjunction\u0026#34;, \u0026#34;opposition\u0026#34;, \u0026#34;trine\u0026#34;, \u0026#34;square\u0026#34;] } 4. list_charts List all charts in workspace.\nArguments:\n{ \u0026#34;workspace_path\u0026#34;: \u0026#34;/path/to/workspace.yaml\u0026#34; } Response:\n{ \u0026#34;charts\u0026#34;: [ { \u0026#34;id\u0026#34;: \u0026#34;chart-1\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;John Doe\u0026#34;, \u0026#34;event_time\u0026#34;: \u0026#34;2024-01-01T12:00:00+01:00\u0026#34;, \u0026#34;location\u0026#34;: \u0026#34;Prague, CZ\u0026#34;, \u0026#34;engine\u0026#34;: \u0026#34;jpl\u0026#34;, \u0026#34;house_system\u0026#34;: \u0026#34;Placidus\u0026#34;, \u0026#34;tags\u0026#34;: [\u0026#34;natal\u0026#34;, \u0026#34;person\u0026#34;] }, // ... more charts ] } 5. get_chart Get chart details by ID.\nArguments:\n{ \u0026#34;workspace_path\u0026#34;: \u0026#34;/path/to/workspace.yaml\u0026#34;, \u0026#34;chart_id\u0026#34;: \u0026#34;chart-id\u0026#34; } Response:\n{ \u0026#34;id\u0026#34;: \u0026#34;chart-id\u0026#34;, \u0026#34;subject\u0026#34;: { \u0026#34;id\u0026#34;: \u0026#34;subject-id\u0026#34;, \u0026#34;name\u0026#34;: \u0026#34;John Doe\u0026#34;, \u0026#34;event_time\u0026#34;: \u0026#34;2024-01-01T12:00:00+01:00\u0026#34;, \u0026#34;location\u0026#34;: { \u0026#34;name\u0026#34;: \u0026#34;Prague, CZ\u0026#34;, \u0026#34;latitude\u0026#34;: 50.0875, \u0026#34;longitude\u0026#34;: 14.4214, \u0026#34;timezone\u0026#34;: \u0026#34;Europe/Prague\u0026#34; } }, \u0026#34;config\u0026#34;: { \u0026#34;mode\u0026#34;: \u0026#34;NATAL\u0026#34;, \u0026#34;house_system\u0026#34;: \u0026#34;Placidus\u0026#34;, \u0026#34;zodiac_type\u0026#34;: \u0026#34;Tropical\u0026#34;, \u0026#34;engine\u0026#34;: \u0026#34;jpl\u0026#34;, \u0026#34;override_ephemeris\u0026#34;: null }, \u0026#34;tags\u0026#34;: [\u0026#34;natal\u0026#34;, \u0026#34;person\u0026#34;] } Error Types InvalidArgument: Missing or invalid arguments ChartNotFound: Chart ID not found in workspace ComputationError: Error during computation LoadError: Error loading workspace InvalidCommand: Unknown command InternalError: Unexpected internal error Testing Run tests:\npython -m pytest tests/test_cli.py -v Or with unittest:\npython -m unittest tests.test_cli Examples Compute chart positions python -m module.cli compute_chart \u0026#39;{\u0026#34;workspace_path\u0026#34;: \u0026#34;/path/to/workspace.yaml\u0026#34;, \u0026#34;chart_id\u0026#34;: \u0026#34;my-chart\u0026#34;}\u0026#39; Get workspace settings python -m module.cli get_workspace_settings \u0026#39;{\u0026#34;workspace_path\u0026#34;: \u0026#34;/path/to/workspace.yaml\u0026#34;}\u0026#39; List all charts python -m module.cli list_charts \u0026#39;{\u0026#34;workspace_path\u0026#34;: \u0026#34;/path/to/workspace.yaml\u0026#34;}\u0026#39; "
  },
  {
    "title": "",
    "url": "enums/",
    "content": "Enum Overview This document lists all enumerations defined in module.models.\nEnum Description Members AspectContext Contexts where an aspect can be used. CHART, TRANSIT, DIRECTION Ayanamsa Ayanamsa enumeration LAHIRI, RAMAN, KRISHNAMURTI, FAGAN_BRADLEY, DE_LUCE, USER_DEFINED ChartMode ChartMode enumeration NATAL, EVENT, HORARY, COMPOSITE Element The four classical elements. FIRE, EARTH, AIR, WATER EngineType Engine enumeration SWISSEPH, JYOTISH, JPL, CUSTOM HouseSystem House enumeration PLACIDUS, WHOLE_SIGN, CAMPANUS, KOCH, EQUAL, REGIOMONTANUS, VEHL, PORPHYRY, ALCABITIUS LayoutStyle Layout enumeration SINGLE, TIMELINE_OVERLAY, DUAL_WHEEL, COMPARISON ObjectType Type of observable object in the chart. PLANET, ASTEROID, ANGLE, HOUSE_CUSP, CALCULATED_POINT, LUNAR_NODE, PART RelationType Relation enumeration TRANSIT, SYNASTRY, PROGRESSION, COMPOSITE TimeSystem Time representation systems. GREGORIAN, JULIAN_DAY, JULIAN_CALENDAR, UNIX_TIMESTAMP, ORDINAL_DATE, ISO_WEEK_DATE, COMPACT_DATE ViewModuleType ViewModule enumeration WHEEL, TIMELINE, GRID, TABLE, TEXT ZodiacType Zodiac enumeration TROPICAL, SIDEREAL "
  },
  {
    "title": "",
    "url": "ephemeris/",
    "content": "Ephemeris files Default file: de440s.bsp The default JPL ephemeris was upgraded from de421.bsp to de440s.bsp.\ndefault_ephemeris_path() in module/utils.py resolves the file in this order:\nsource/de440s.bsp # preferred source/de421.bsp # legacy fallback The old de421.bsp remains in source/ as a fallback for environments that have not yet received de440s.bsp. Once de440s.bsp is present, it is used automatically — no configuration change needed.\nWhat de440s actually provides de440s.bsp contains the same set of queryable bodies as de421.bsp:\nBody NAIF ID Sun 10 Moon 301 Mercury 199 Venus 299 Earth 399 Mars 499 Jupiter barycenter 5 Saturn barycenter 6 Uranus barycenter 7 Neptune barycenter 8 Pluto barycenter 9 The 343 asteroids integrated in DE440/441 are integration perturbers only. Their gravitational effects are baked into the planetary positions, but their own trajectories are not stored as queryable SPK segments in any of the DE planetary BSP files. Querying Ceres (NAIF 2000001) from de440s.bsp will raise a Skyfield ValueError — a dedicated asteroid SPK kernel is required (see below).\nWhy upgrade then? DE440 is a more accurate, more recent numerical solution (Park et al. 2021). For modern astrological dates it produces sub-arcsecond improvements over DE421, and it extends the reliable date range.\nAvailable BSP files and date ranges File Size Date range Queryable bodies de440s.bsp 32 MB 1900 – 2050 10 planets + Moon de440.bsp 115 MB 1550 – 2650 10 planets + Moon de441_part-1.bsp ~1.5 GB −13 200 – 0 AD 10 planets + Moon de441_part-2.bsp ~1.5 GB 0 – 17 191 AD 10 planets + Moon de421.bsp 17 MB ~1899 – 2053 10 planets + Moon All files are publicly available from the NASA/JPL NAIF server: https://naif.jpl.nasa.gov/pub/naif/generic_kernels/spk/planets/\nDate range overlaps Some files cover the same epochs. This matters when choosing which file to load and when mixing files.\nCoverage map -13200 0 1550 1900 2050 2650 17191 │ │ │ │ │ │ │ de441_part-1 ───────────────┤ │ │ │ │ │ de441_part-2 │ ├───────────────────────────────────────┤ de440 │ │ ├─────────────────────┤ │ de440s │ │ │ ├────────┤ │ │ de421 (~) │ │ │ ├────────┤ │ │ Overlap summary Pair Overlapping range Notes de440s ∩ de441_part-2 1900 – 2050 de440s is entirely inside de441_part-2 de440 ∩ de441_part-2 1550 – 2650 de440 is entirely inside de441_part-2 de440s ∩ de440 1900 – 2050 de440s is entirely inside de440 de441_part-1 ∩ de440 none part-1 ends at year 0; de440 starts at 1550 de441_part-1 ∩ de440s none same de441_part-1 ∩ de421 none de421 starts ~1899 Behaviour when overlapping files are loaded together Skyfield (and the underlying SPICE convention) applies last-loaded wins: if two BSP files both contain a segment for the same body at the same time, the most recently loaded file takes effect.\nplanets = load.open(\u0026#39;de440s.bsp\u0026#39;) # loaded first extended = load.open(\u0026#39;de441_part-2.bsp\u0026#39;) # loaded last → wins for 1900-2050 For normal astrological work (1900–2050) this is acceptable: DE441 was derived from the same initial conditions as DE440 and the accuracy difference in the modern era is sub-arcsecond.\nWhen you need each file Use case File Modern charts (1900–2050) de440s.bsp — bundled, no download Extended modern (1550–2650) de440.bsp Ancient / BC charts de441_part-1.bsp Far-future charts (after 2650) de441_part-2.bsp You do not need de441_part-2.bsp for charts in the 1900–2050 window.\nis_de421 flag in services.py services.py inspects the loaded filename to decide whether to use barycenters for outer planets:\nis_de421 = eph_file and \u0026#34;de421\u0026#34; in Path(eph_file).name.lower() de440s.bsp does not match this check, so it follows the non-de421 path: direct planet names are tried first (jupiter, saturn, etc.) rather than barycenters. This is correct for DE440-series files, which expose individual planet objects directly.\nNo code change is needed in services.py when switching from de421 to de440s.\nAsteroid bodies Current status Asteroids (Ceres, Pallas, Juno, Vesta, Chiron) are not available from any of the DE planetary files. A dedicated asteroid SPK kernel is required:\nOption Bodies Source Individual files (ceres_1900_2100.bsp etc.) one body per file NAIF archive codes_300ast_20100725.bsp (59 MB) 300 main-belt asteroids NAIF archive JPL Horizons REST API any numbered asteroid on-demand query NAIF IDs for the main astrological asteroids:\nBody NAIF ID Ceres 2 000 001 Pallas 2 000 002 Juno 2 000 003 Vesta 2 000 004 Chiron 2 000 060 These are not in de440s.bsp despite the 343-asteroid integration — those asteroids improve planetary accuracy as perturbers but are not output as SPK segments.\nWorkaround for computed points Part of Fortune — pure formula: ASC + Moon - Sun (day chart) or ASC + Sun - Moon (night chart); no BSP needed Black Moon Lilith — mean lunar apogee; derivable from Moon orbital elements South Node — North Node + 180°; already computed from the Mean Node formula Overriding the ephemeris file Pass ephemeris_path explicitly to compute_jpl_positions() or set chart.config.override_ephemeris:\nfrom module.services import compute_jpl_positions result = compute_jpl_positions( name=\u0026#34;Test\u0026#34;, dt_str=\u0026#34;2000-01-01 12:00:00\u0026#34;, loc_str=\u0026#34;51.4779,0.0\u0026#34;, ephemeris_path=\u0026#34;/path/to/de441_part-2.bsp\u0026#34; ) If ephemeris_path is None, default_ephemeris_path() is called, which returns de440s.bsp if present, otherwise de421.bsp.\n"
  },
  {
    "title": "",
    "url": "fastapi_tauri/",
    "content": "FastAPI + React + Tauri Integration Overview This guide describes the recommended way to use the Python package in a desktop application built from:\nReact (or Svelte) frontend FastAPI backend Tauri packaging The key idea is to treat the Python package as a reusable computation library, and to keep UI-specific modules optional.\nRecommended runtime flow:\nTauri shell -\u0026gt; React frontend -\u0026gt; HTTP calls to FastAPI on localhost -\u0026gt; FastAPI calls module.services / module.workspace / module.storage Do not build the Tauri app around module/ui_streamlit.py or module/ui_kivy.py. Those files are alternative Python frontends, not the ideal integration surface for Tauri.\nRecommended package split Use the package in layers:\nmodule.models dataclasses and enums shared by the backend module.services chart computation and domain logic module.workspace workspace loading and saving module.storage optional DuckDB and Parquet support module.cli existing JSON command contract, useful as a reference module.ui_* optional developer-facing UIs, not required by FastAPI or Tauri For the target architecture, prefer:\ncore package install for shared astrology logic api extra for FastAPI optional visuals extra only if the backend needs Plotly/Matplotlib output Installation options Lean backend for Tauri Install only the backend runtime:\npip install . pip install .[api] This is the preferred setup when:\nReact renders the UI FastAPI exposes JSON endpoints the backend does not need Streamlit or Kivy Backend with chart rendering support If the Python backend must generate Plotly figures or export images:\npip install . pip install .[api] pip install .[visuals] Optional developer UIs Install only when those frontends are actually used:\npip install .[streamlit] pip install .[kivy] Why this split matters The repository contains multiple frontend styles:\nStreamlit Kivy CLI/TUI sidecar-style JSON commands For a Tauri desktop app, shipping unnecessary frontend dependencies makes the Python environment larger and harder to maintain.\nIn particular:\nStreamlit should not be required by the FastAPI sidecar Kivy should not be required by the FastAPI sidecar visualization libraries should be optional unless the backend renders charts itself Existing integration surface The current repo already has a useful contract in module.cli.\nSee:\ncli_reference.md module/cli.py That CLI already exposes sidecar-style operations such as:\ncompute_chart compute_transit_series get_workspace_settings list_charts get_chart You can either:\nKeep using the CLI as a subprocess contract from Tauri, or Move that same contract behind FastAPI routes For new work, FastAPI is usually the cleaner option because it gives you:\ntyped request and response models easier debugging better error handling simpler integration from React cleaner long-term API evolution Recommended FastAPI structure Create a dedicated API layer instead of putting HTTP concerns into module.services.\nSuggested layout:\nmodule/ api/ __init__.py app.py schemas.py routes/ charts.py workspace.py transits.py Suggested responsibility split:\nmodule.services pure domain logic module.workspace file and workspace orchestration module.storage persistence concerns module.api.schemas Pydantic request and response models module.api.routes.* HTTP request parsing and response shaping Example FastAPI adapter Minimal example:\nfrom fastapi import FastAPI, HTTPException from pydantic import BaseModel from module.workspace import load_workspace from module.services import ( compute_positions_for_chart, compute_aspects_for_chart, find_chart_by_name_or_id, ) app = FastAPI() class ComputeChartRequest(BaseModel): workspace_path: str chart_id: str include_physical: bool = False include_topocentric: bool = False @app.post(\u0026#34;/charts/compute\u0026#34;) def compute_chart(payload: ComputeChartRequest): ws = load_workspace(payload.workspace_path) chart = find_chart_by_name_or_id(ws, payload.chart_id) if not chart: raise HTTPException(status_code=404, detail=\u0026#34;Chart not found\u0026#34;) positions = compute_positions_for_chart( chart, ws=ws, include_physical=payload.include_physical, include_topocentric=payload.include_topocentric, ) aspects = compute_aspects_for_chart(chart, ws=ws) return { \u0026#34;chart_id\u0026#34;: payload.chart_id, \u0026#34;positions\u0026#34;: positions, \u0026#34;aspects\u0026#34;: aspects, } This keeps the package reusable while giving the React app a clean HTTP API.\nThe repository now includes a scaffolded FastAPI adapter in:\nmodule/api/app.py module/api/schemas.py module/api/__main__.py Run it locally with:\npip install .[api] python -m module.api --host 127.0.0.1 --port 8765 --reload Or:\nmake run-api Suggested React and Tauri responsibilities React frontend The React app should handle:\nrouting forms state management chart selection API calls to FastAPI rendering backend results Tauri shell Tauri should handle:\npackaging starting and stopping the FastAPI sidecar native file dialogs if needed desktop-only capabilities Connecting the pieces together The clean connection model is:\nTauri -\u0026gt; starts Python sidecar -\u0026gt; loads React frontend -\u0026gt; React waits for GET /health -\u0026gt; React calls FastAPI routes on localhost Step 1: start the backend locally During development, run:\npip install .[api] python -m module.api --host 127.0.0.1 --port 8765 --reload Step 2: point React at the local API Use a frontend API base URL such as:\nhttp://127.0.0.1:8765 Typical frontend calls:\nGET /health GET /workspace/settings?workspace_path=... GET /charts?workspace_path=... GET /charts/{chart_id}?workspace_path=... POST /charts/compute POST /transits/compute-series Example fetch:\nconst API_BASE = \u0026#34;http://127.0.0.1:8765\u0026#34;; export async function computeChart(workspacePath: string, chartId: string) { const response = await fetch(`${API_BASE}/charts/compute`, { method: \u0026#34;POST\u0026#34;, headers: { \u0026#34;Content-Type\u0026#34;: \u0026#34;application/json\u0026#34; }, body: JSON.stringify({ workspace_path: workspacePath, chart_id: chartId, include_physical: false, include_topocentric: false, store_in_db: false, }), }); if (!response.ok) { throw new Error(`API error: ${response.status}`); } return response.json(); } Step 3: let Tauri launch the sidecar In the packaged desktop app, Tauri should launch the bundled backend executable on startup.\nThe frontend should not assume the backend is instantly ready.\nRecommended flow:\nTauri starts the sidecar. React polls GET /health. When health is ready, the app unlocks the main UI. Step 4: keep the backend address configurable In development:\nuse a fixed localhost URL such as http://127.0.0.1:8765 In production:\nkeep the same default if possible or inject the backend URL into the frontend on startup Step 5: pass workspace paths explicitly The current API scaffold keeps workspace selection explicit.\nThat means React or Tauri should provide the workspace_path when calling endpoints that need it.\nThis is a good starting point because it avoids hidden global backend state.\nExact Tauri integration checklist Use this sequence when you open the Tauri project.\nHandoff checklist for the Tauri project Use one of these two handoff modes.\nOption A: source-based handoff Use this if the Tauri project or CI will build the Python sidecar itself.\nPass these:\nmodule/ pyproject.toml source/ if runtime assets such as ephemeris files are needed this document: docs/site/content/fastapi_tauri.md Do not pass these:\nbuild/ .venv-build/ PyInstaller temporary caches Optional:\nkefer-backend.spec if you want the Tauri project to reuse the same PyInstaller config Option B: artifact-based handoff Use this if the Python backend is built outside the Tauri project and Tauri only bundles the finished sidecar.\nPass these:\ndist/kefer-backend or dist/kefer-backend.exe this document: docs/site/content/fastapi_tauri.md runtime asset files if the backend expects them beside the executable Do not pass these:\nbuild/ .venv-build/ the full backend source tree, unless the Tauri project also needs to rebuild it Recommendation If you want the cleanest Tauri repository, prefer:\nartifact-based handoff for packaging source-based handoff only if the Tauri repo is also responsible for backend builds That keeps the Tauri app smaller and avoids mixing Python build machinery into the frontend project unless you really want one repository to own everything.\nRecommended folder strategy You do not need to flatten everything into one folder.\nIn fact, that usually becomes harder to maintain.\nThe cleaner approach is to keep clear boundaries inside the Tauri repository.\nSuggested shape:\ntauri-app/ frontend/ src-tauri/ backend-python/ module/ pyproject.toml source/ Or, if the backend is only consumed as a built artifact:\ntauri-app/ frontend/ src-tauri/ binaries/ kefer-backend-... When to keep a separate backend folder Prefer a separate backend folder when:\nPython is still evolving you want backend builds in CI you want to keep Python dependencies isolated you do not want Tauri frontend folders cluttered with Python files When artifact-only is better Prefer artifact-only handoff when:\nthe backend is built elsewhere the Tauri project should stay mostly frontend plus Rust you want the smallest possible mental overhead in the Tauri repo Recommendation for you Based on your concern about clutter, I would not merge everything into one giant folder.\nI would use one of these:\na dedicated backend-python/ folder inside the Tauri repo no backend source in the Tauri repo at all, only the packaged sidecar in src-tauri/binaries/ Both are cleaner than flattening the whole Python project into the Tauri app root.\nRecommended Tauri repo layout If you plan to pass the backend source and sort it out inside the Tauri project, this is the layout I recommend.\nOption 1: keep backend source in the Tauri repo tauri-app/ frontend/ src/ package.json vite.config.ts src-tauri/ Cargo.toml tauri.conf.json binaries/ backend-python/ module/ source/ pyproject.toml Makefile docs/ Why this is good:\nfrontend stays frontend Rust and Tauri stay in src-tauri/ Python stays isolated in backend-python/ CI can build the sidecar from backend-python/ and copy the result into src-tauri/binaries/ Option 2: keep only built backend artifacts in the Tauri repo tauri-app/ frontend/ src/ package.json src-tauri/ Cargo.toml tauri.conf.json binaries/ kefer-backend-x86_64-pc-windows-msvc.exe kefer-backend-x86_64-unknown-linux-gnu kefer-backend-aarch64-apple-darwin kefer-backend-x86_64-apple-darwin docs/ backend-handoff.md Why this is good:\nsmallest Tauri repo footprint no Python build tooling mixed into the desktop app Tauri only bundles and launches the sidecar Which option I recommend If you are still actively changing the Python backend:\nuse Option 1 first If the backend becomes stable or is built in separate CI:\nmove toward Option 2 What I would do in your place For now, since you plan to pass all of these things to the Tauri project:\ncreate backend-python/ put module/, source/, pyproject.toml, Makefile, and this doc there build the sidecar from backend-python/ copy the built executable into src-tauri/binaries/ let Tauri bundle only the executable, not the whole Python folder That gives you a clean transition path:\neasy to work on now easy to slim down later Files to ignore in the Tauri project Even if you pass everything over initially, I recommend not keeping these in the long-term packaged project:\nbackend-python/build/ backend-python/dist/ except for the final sidecar you actually bundle backend-python/.venv-build/ any temporary PyInstaller cache directories Minimal copy set into backend-python/ Best practical handoff:\nmodule/ source/ pyproject.toml Makefile docs/site/content/fastapi_tauri.md Optional:\nkefer-backend.spec Step 1: copy or add the Python backend package Make sure the Tauri app can build this Python project as part of the desktop build.\nYou need these pieces available to the desktop app build:\nmodule/ pyproject.toml source/ if ephemeris assets are needed at runtime any workspace/example data you want to ship If the Tauri app lives in a different repository, either:\ninclude this repo as a submodule, or copy the backend into a dedicated backend/ folder, or publish the backend package and install it during CI Step 2: create a backend build environment For local development:\npython -m venv .venv-build .venv-build/bin/python -m pip install --upgrade pip .venv-build/bin/python -m pip install -e \u0026#34;.[api]\u0026#34; pyinstaller On Windows, use:\npython -m venv .venv-build .venv-build\\Scripts\\python -m pip install --upgrade pip .venv-build\\Scripts\\python -m pip install -e \u0026#34;.[api]\u0026#34; pyinstaller Step 3: verify the backend runs before packaging Run the backend directly first:\n.venv-build/bin/python -m module.api --host 127.0.0.1 --port 8765 Check:\nhttp://127.0.0.1:8765/health http://127.0.0.1:8765/docs Do this before touching Tauri packaging.\nStep 4: build a backend sidecar executable Start with PyInstaller.\nExample command on Linux or macOS:\n.venv-build/bin/pyinstaller \\ --name kefer-backend \\ --onefile \\ --collect-all kerykeion \\ --hidden-import module.api.app \\ --hidden-import module.api.schemas \\ -m module.api On Windows:\n.venv-build\\Scripts\\pyinstaller ` --name kefer-backend ` --onefile ` --collect-all kerykeion ` --hidden-import module.api.app ` --hidden-import module.api.schemas ` -m module.api Expected output:\ndist/kefer-backend on Linux or macOS dist/kefer-backend.exe on Windows Step 5: add the sidecar binary to the Tauri project Inside the Tauri project, place the built backend into the sidecar/binaries area used by Tauri.\nTypical layout:\nsrc-tauri/ binaries/ kefer-backend-x86_64-pc-windows-msvc.exe kefer-backend-x86_64-unknown-linux-gnu kefer-backend-aarch64-apple-darwin kefer-backend-x86_64-apple-darwin The exact file names depend on your Tauri target configuration, but the important part is:\none backend executable per platform target copied into the Tauri packaging inputs before the app build runs Step 6: start the sidecar from Tauri In the Tauri app:\nstart the sidecar on app startup pass --host 127.0.0.1 --port 8765 capture stdout and stderr for debugging The Tauri side should:\nlaunch the bundled kefer-backend wait until GET /health returns success then allow the React app to use the backend Step 7: point React to the local API In the React app, use:\nhttp://127.0.0.1:8765 Recommended pattern:\ncentralize the base URL in one API client module wait for backend readiness before loading workspace-dependent screens show a loading or recovery state if the sidecar fails to start Step 8: wire workspace selection Because the scaffolded backend is stateless with respect to workspace selection, React or Tauri must provide:\na workspace file path Good first implementation:\nuse a Tauri file dialog to select workspace.yaml store that path in frontend state send it on every API request that needs it Step 9: package the desktop installers After the sidecar is wired in, build Tauri normally for each platform.\nExpected final artifacts:\nWindows .msi or .exe Linux .AppImage macOS .dmg Each installer should already contain the backend sidecar.\nStep 10: CI artifact strategy Recommended CI pipeline:\nbuild backend sidecar per platform upload sidecar binaries as CI artifacts build Tauri installers using those sidecars upload installers as release artifacts This gives you both:\nbackend artifacts for debugging final app artifacts for users FastAPI backend FastAPI should handle:\nworkspace loading chart computation transit computation persistence requests normalization of Python exceptions into stable API responses Suggested endpoint groups Useful endpoint groups for this project:\nGET /health GET /workspace/settings GET /charts GET /charts/{chart_id} POST /charts/compute POST /transits/compute-series POST /storage/export-parquet These map naturally to the existing logic in module.cli.\nCurrent scaffolded routes:\nGET /health GET /workspace/settings GET /charts GET /charts/{chart_id} POST /charts/compute POST /transits/compute-series POST /workspace/sync POST /storage/export-parquet Data model guidance Avoid returning internal dataclasses directly over HTTP.\nInstead:\nkeep dataclasses in module.models map them to Pydantic schemas in module.api.schemas use JSON-friendly response shapes This helps with:\nversioning frontend stability better OpenAPI docs clearer validation errors What to keep out of the backend core Do not make the core computation modules depend on:\nStreamlit Kivy Tauri-specific code frontend rendering details If you need chart visualization, keep it behind an optional boundary.\nFor example:\npure position/aspect computation stays in services Plotly figure creation stays optional UI presentation logic stays in React or separate Python UI modules CLI vs FastAPI Both are valid, but they serve different goals.\nUse CLI when you want the fastest possible integration Tauri launches Python subprocesses directly request volume is small Use FastAPI when you want a stable backend contract React should call normal HTTP endpoints you expect the integration to grow you want easier debugging and testing For this project, FastAPI is the better long-term fit.\nMigration path from current repo Recommended path:\nKeep module.services, module.workspace, and module.storage as the core. Treat module.cli as the source contract for the first API routes. Add module/api/ with Pydantic schemas and FastAPI routes. Keep module/ui_streamlit.py and module/ui_kivy.py as optional tools for manual testing or demos. Let Tauri start the FastAPI sidecar and point the React app at it. Bundling Python into the Tauri app For shipped desktop builds, do not depend on a user-installed Python.\nInstead, bundle the backend privately inside the application package.\nThere are two main approaches.\nOption A: bundle a Python runtime In this approach, the packaged app contains:\na Python interpreter your installed package your dependencies a small startup command such as python -m module.api.app This works, but it is usually more operationally complex because you need to assemble a platform-specific embedded runtime for:\nWindows macOS Linux It is better than requiring a system Python, but it is not the simplest Tauri integration path.\nOption B: freeze the Python backend into a standalone executable In this approach, you build the backend into a self-contained binary with a tool such as:\nPyInstaller Nuitka Tauri then ships that backend binary as a sidecar and starts it on launch.\nFor this project, this is the recommended approach.\nBenefits:\nno dependency on system Python simpler installer behavior easier Tauri integration cleaner artifact story in CI Recommended approach for this repository The most practical packaging model here is:\nReact frontend inside Tauri FastAPI backend as a sidecar executable Python package kept normal during development frozen backend executable produced per platform during release builds In development, use venv.\nIn release packaging, do not ship the development virtual environment directly.\nWhy not ship the raw virtualenv A virtual environment is mainly a development convenience.\nIt is not the best final artifact because:\nit is platform-specific it often contains unnecessary development files paths inside it can be brittle copying it between machines is unreliable it usually produces a messier installer than a real sidecar binary So the typical workflow is:\ndevelop in a venv build a frozen backend executable for each platform let Tauri bundle that executable into the final installer Suggested build artifacts You will usually end up with two layers of artifacts.\nBackend artifacts These are produced first, one per platform:\nbackend-windows-x64.zip contains kefer-backend.exe backend-linux-x64.tar.gz contains kefer-backend backend-macos-x64.tar.gz contains kefer-backend backend-macos-aarch64.tar.gz contains kefer-backend These are CI-friendly artifacts and can be reused by the desktop packaging job.\nDesktop installer artifacts These are the final Tauri deliverables:\nWindows: .msi and/or .exe Linux: .AppImage and optionally .deb macOS: .dmg These final installers should already contain the backend sidecar.\nSuggested CI flow Recommended release pipeline:\nBuild frontend assets. Build Python backend sidecar for each target platform. Upload backend sidecars as CI artifacts. Build Tauri app for each target platform. Bundle the matching backend sidecar into the Tauri package. Upload final installers as release artifacts. This is a good fit if you want \u0026ldquo;artifacts will be available as artifacts\u0026rdquo; in CI, because it gives you:\nintermediate backend artifacts for debugging final installer artifacts for users Suggested repository layout for packaging Once you add the Tauri app, a practical structure looks like this:\nfunction-wrapper/ module/ frontend/ src-tauri/ tauri.conf.json Cargo.toml binaries/ kefer-backend-x86_64-pc-windows-msvc.exe kefer-backend-x86_64-unknown-linux-gnu kefer-backend-aarch64-apple-darwin kefer-backend-x86_64-apple-darwin The exact sidecar naming depends on the Tauri version and configuration, but the main idea is the same:\none backend binary per target checked into build outputs or copied there during CI bundled by Tauri into the final installer Runtime behavior inside the packaged app At runtime:\nTauri launches the bundled backend sidecar. The backend starts FastAPI on 127.0.0.1 and a fixed or negotiated port. React calls the local HTTP API. On app exit, Tauri stops the sidecar. Good practice:\nwait for backend readiness before loading API-dependent screens expose a small GET /health endpoint log sidecar startup failures clearly Backend entrypoint recommendation Create a tiny dedicated backend entrypoint for packaging.\nFor example:\nmodule/ api/ app.py __main__.py That entrypoint should do one job:\nstart the FastAPI app with the correct host and port Keep packaging logic outside the domain modules.\nThis repository now provides that entrypoint through:\npython -m module.api kefer-api Tooling recommendation For this repository, start with:\nFastAPI + Uvicorn for the backend PyInstaller for the first packaged sidecar Tauri sidecar bundling for the desktop app PyInstaller is not perfect, but it is usually the fastest way to get to real artifacts.\nIf startup speed or binary size later becomes a problem, evaluate Nuitka after the integration is already working.\nPractical release plan The simplest path to production-like artifacts is:\nAdd module/api/app.py Add a minimal backend launcher Build that backend with PyInstaller for each target platform Add the Tauri app and configure it to start the sidecar Produce installer artifacts per platform That gets you to:\nreusable Python package in development private bundled backend in release builds downloadable CI artifacts for both sidecars and installers Practical recommendation If you are building the new application now, the most maintainable setup is:\nPython package for business logic FastAPI as the backend adapter React as the only application UI Tauri as the desktop shell That gives you one real UI, one real backend contract, and keeps the old Python UI modules optional instead of foundational.\n"
  },
  {
    "title": "",
    "url": "installation/",
    "content": "Installation and Venvs This project uses multiple virtual environments so the core backend stays lean while UI wrappers remain optional.\nRecommended venvs venv (base): core backend only venv-streamlit: Streamlit UI venv-kivy: Kivy UI venv-docs: documentation export (imports UI modules) Makefile targets Use the Makefile to create isolated environments with pinned dependencies:\nmake venv (creates venv with requirements/base.txt) make venv-streamlit (creates venv-streamlit with requirements/streamlit.txt) make venv-kivy (creates venv-kivy with requirements/kivy.txt) make venv-docs (creates venv-docs with .[docs] extras) Notes A single venv cannot switch environments without uninstalling packages. Separate venvs avoid heavy or conflicting UI dependencies. Environment preparation Create virtual environment: python3 -m venv venv Activate virtual environment: Windows: venv\\Scripts\\activate Linux: source ./venv/bin/activate Install libraries: Using precompiled lockfiles (recommended): Base only: pip install -r requirements/base.txt Streamlit UI: pip install -r requirements/streamlit.txt Kivy UI: pip install -r requirements/kivy.txt Dev all‑in: pip install -r requirements/dev.txt (or top‑level requirements.txt if present) To (re)compile lockfiles with pip‑tools: Base: pip-compile requirements/base.in -o requirements/base.txt Streamlit: pip-compile requirements/streamlit.in -o requirements/streamlit.txt Kivy: pip-compile requirements/kivy.in -o requirements/kivy.txt Dev all‑in: create requirements/dev.in that includes -r requirements/streamlit.in and -r requirements/kivy.in, then: pip-compile requirements/dev.in -o requirements/dev.txt Run the application: Default: python3 -m module (runs TUI) Streamlit UI: python3 -m module --streamlit Kivy UI: python3 -m module --kivy TUI explicitly: python3 -m module --tui Run tests: See docs/TESTING_GUIDE.md for detailed instructions Quick start: python3 -m unittest discover tests -v Or use Makefile: make run_tests Project structure (core modules): services.py — chart creation/selection and compute orchestration utils.py — utilities for date and location parsing workspace.py — workspace, chart and settings management z_visual.py — visualization Dependency management Dependency definitions live in requirements/ as .in files: requirements/base.in contains core runtime deps. requirements/streamlit.in and requirements/kivy.in should start with -r base.in. Reproducible installs use compiled lockfiles in requirements/*.txt. Dev/all‑in: keep requirements/dev.txt under requirements/. Optionally provide a root requirements.txt that contains -r requirements/dev.txt. Environment strategy (one venv vs multiple) A single venv cannot \u0026ldquo;switch\u0026rdquo; environments without uninstalling packages; once installed, they persist. Recommended: use separate venvs per UI target (base, streamlit, kivy, docs) to avoid heavy or conflicting deps. If you still want one venv, install the base first, then add extras: pip install -e . (core only) pip install -e \u0026quot;.[streamlit]\u0026quot; or pip install -e \u0026quot;.[kivy]\u0026quot; pip install -e \u0026quot;.[docs]\u0026quot; (includes UI deps so make docs can import UI modules) Sync pyproject from requirements pyproject.toml is derived from requirements/*.in (not from the compiled .txt lockfiles).\nTo regenerate the dependency sections after updating .in files:\nmake sync-pyproject UI Quickstart Streamlit UI (interactive, in browser) Install dependencies (choose one):\nOption A: using pip‑tools pip-compile requirements/streamlit.in -o requirements/streamlit.txt pip install -r requirements/streamlit.txt # contains base via -r base.in Option B: directly from the precompiled lockfile pip install -r requirements/streamlit.txt Run Streamlit:\npython3 -m module --streamlit Features:\nEngine switcher (Kerykeion default vs JPL/Skyfield) in Advanced settings. Default JPL ephemeris file: source/de421.bsp (override via the ephemeris path field). Standardized Radix chart rendered with Plotly interactively. Kivy UI (desktop app) Install dependencies (choose one):\nOption A: using pip‑tools pip-compile requirements/kivy.in -o requirements/kivy.txt pip install -r requirements/kivy.txt # contains base via -r base.in Option B: directly from the precompiled lockfile pip install -r requirements/kivy.txt Enable embedded interactive Plotly inside Kivy (recommended):\nInstall Kivy Garden tooling and the WebView widget: pip install kivy-garden garden install kivy_garden.webview If WebView is unavailable, the app falls back to opening the chart in the browser. Run Kivy:\npython3 -m module --kivy Features:\nJPL engine toggle in the top toolbar (JPL on/off). Standardized Radix chart renderer. Default JPL ephemeris file path: source/de421.bsp. Troubleshooting Unknown class WebView in KV:\nInstall the widget via Kivy Garden: pip install kivy-garden then garden install kivy_garden.webview. Alternatively, rely on the browser fallback. Kerykeion Geonames warning:\nSet a custom geonames username to avoid default shared rate limits. See the Kerykeion docs. JPL not active / ephemeris file not found:\nEnsure skyfield is installed and that source/de421.bsp is present. You can change the path in Advanced settings (Streamlit) or in Kivy code. Streamlit not updating:\nClear the browser cache or stop/restart the Streamlit server. Generating Documentation The Hugo site sources live under docs/site/ and the build output is written to docs/.\n1) Generate auto-docs # Create docs venv (includes extras needed for doc export) make venv-docs source venv-docs/bin/activate # Generate docs/site/content/auto, docs/site/content/models.mmd, docs/site/content/enums.md make docs If you created venv-docs before syncing pyproject.toml, re-run make venv-docs to force-reinstall dependencies (this installs new base deps like duckdb).\n2) Run Hugo locally # From repo root hugo server --source docs/site --config hugo.toml --baseURL http://localhost:1313/ --appendPort=false This serves the docs site locally at http://localhost:1313. If you skip the override, Hugo uses the GitHub Pages baseURL and you\u0026rsquo;ll need to open http://localhost:1313/function-wrapper/ instead.\n3) Build static site hugo --source docs/site --config hugo.toml --destination docs The static site is generated in docs/ (the GitHub Pages publishing root).\n4) GitHub Pages checklist (docs folder hosting) Repository Settings → Pages → Source: Deploy from a branch Branch: main (or your default branch) Folder: /docs Save and wait for the Pages deployment to finish Notes:\ndocs/ contains the built site; Hugo sources live in docs/site/. Because the source tree is under docs/, the raw source files are also publicly visible. "
  },
  {
    "title": "",
    "url": "testing_guide/",
    "content": "Testing Guide Overview This guide covers testing strategies for the Kefer Astrology sidecar module, including CLI handlers, storage functionality, and core computation services.\nTest Structure tests/ ├── __init__.py ├── test_cli.py # CLI command handlers ├── test_storage.py # DuckDB storage (NEW) ├── test_workspace_flow.py # Workspace operations ├── test_dates_places.py # Date/location parsing ├── test_locations.py # Location handling ├── test_starfisher.py # Starfisher integration ├── test_polar.py # Polar coordinates └── test_docs.py # Documentation generation Running Tests All Tests python -m unittest discover tests Specific Test Suite python -m unittest tests.test_cli python -m unittest tests.test_storage python -m unittest tests.test_workspace_flow With Coverage pip install coverage coverage run -m unittest discover tests coverage report coverage html # Generate HTML report in htmlcov/ Test Coverage Status ✅ Covered CLI handlers (test_cli.py)\ncompute_chart command get_workspace_settings command list_charts command get_chart command compute_transit_series command Error handling Storage (test_storage.py) ⭐ NEW\nSchema creation Simple position storage Extended position storage Aspect storage Position replacement Parquet export Context manager Workspace operations (test_workspace_flow.py)\nWorkspace initialization Chart creation Workspace settings Date/location parsing (test_dates_places.py, test_locations.py)\n⚠️ Partially Covered Aspect computation - Basic tests needed Extended position format - JPL format tests needed CLI + storage integration - Combined tests needed ❌ Missing Tests test_services_aspects.py - Aspect computation edge cases test_services_extended_positions.py - JPL extended format test_cli_storage_integration.py - CLI with storage enabled Writing New Tests Test Template import unittest import tempfile from pathlib import Path from module.services import compute_positions_for_chart from module.models import EngineType class TestMyFeature(unittest.TestCase): def setUp(self): \u0026#34;\u0026#34;\u0026#34;Set up test fixtures.\u0026#34;\u0026#34;\u0026#34; self.tmpdir = tempfile.TemporaryDirectory() # ... setup code def tearDown(self): \u0026#34;\u0026#34;\u0026#34;Clean up.\u0026#34;\u0026#34;\u0026#34; self.tmpdir.cleanup() def test_feature_basic(self): \u0026#34;\u0026#34;\u0026#34;Test basic functionality.\u0026#34;\u0026#34;\u0026#34; # Arrange # Act # Assert pass CLI Test Pattern def test_cli_command(self): \u0026#34;\u0026#34;\u0026#34;Test CLI command via subprocess.\u0026#34;\u0026#34;\u0026#34; args = { \u0026#34;workspace_path\u0026#34;: self.workspace_path, \u0026#34;chart_id\u0026#34;: \u0026#34;test-chart\u0026#34;, } result = self._run_cli(\u0026#34;compute_chart\u0026#34;, args) self.assertNotIn(\u0026#34;error\u0026#34;, result) self.assertIn(\u0026#34;positions\u0026#34;, result) Storage Test Pattern @unittest.skipUnless(STORAGE_AVAILABLE, \u0026#34;duckdb not available\u0026#34;) def test_storage_operation(self): \u0026#34;\u0026#34;\u0026#34;Test storage operation.\u0026#34;\u0026#34;\u0026#34; with DuckDBStorage(self.db_path) as storage: storage.store_positions(...) # Verify data storage2 = DuckDBStorage(self.db_path, create_schema=False) result = storage2.conn.execute(\u0026#34;SELECT ...\u0026#34;).fetchall() self.assertEqual(len(result), expected_count) Test Requirements Core Tests (Always Run) CLI handlers Workspace operations Date/location parsing Optional Tests (Skip if Dependencies Missing) Storage tests (requires duckdb) Parquet export (requires pyarrow) UI tests (requires kivy/streamlit) Test Markers @unittest.skipUnless(STORAGE_AVAILABLE, \u0026#34;duckdb not available\u0026#34;) class TestStorage(unittest.TestCase): pass Continuous Integration GitHub Actions Example name: Tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 with: python-version: \u0026#39;3.10\u0026#39; - run: pip install -r requirements/base.txt - run: pip install duckdb pyarrow # Optional deps - run: python -m unittest discover tests Test Data Sample Charts Use _make_sample_chart() helper from test_workspace_flow.py:\nfrom tests.test_workspace_flow import _make_sample_chart chart = _make_sample_chart(name=\u0026#34;Test Chart\u0026#34;) Sample Workspace from module.workspace import init_workspace with tempfile.TemporaryDirectory() as tmpdir: base = Path(tmpdir) / \u0026#34;ws\u0026#34; manifest_path = init_workspace( base_dir=base, owner=\u0026#34;Tester\u0026#34;, active_model=\u0026#34;default\u0026#34;, default_ephemeris={\u0026#34;name\u0026#34;: \u0026#34;de421\u0026#34;, \u0026#34;backend\u0026#34;: \u0026#34;jpl\u0026#34;}, ) Best Practices Use temporary directories for file operations Clean up resources in tearDown() Skip optional tests if dependencies missing Test error cases as well as success Use descriptive test names that explain what\u0026rsquo;s tested Keep tests independent - no shared state between tests Debugging Failed Tests Run Single Test python -m unittest tests.test_cli.TestCLI.test_compute_chart Verbose Output python -m unittest tests.test_cli -v Debug Mode import pdb; pdb.set_trace() # Add breakpoint Performance Testing For large batch operations (transit series):\ndef test_transit_series_performance(self): \u0026#34;\u0026#34;\u0026#34;Test transit series with many time points.\u0026#34;\u0026#34;\u0026#34; import time start = time.time() result = cmd_compute_transit_series({ \u0026#34;workspace_path\u0026#34;: self.workspace_path, \u0026#34;source_chart_id\u0026#34;: \u0026#34;test-chart\u0026#34;, \u0026#34;start_datetime\u0026#34;: \u0026#34;2024-01-01T00:00:00\u0026#34;, \u0026#34;end_datetime\u0026#34;: \u0026#34;2024-01-31T23:59:59\u0026#34;, \u0026#34;time_step\u0026#34;: \u0026#34;1 hour\u0026#34;, # ~744 time points }) elapsed = time.time() - start self.assertLess(elapsed, 60.0) # Should complete in \u0026lt; 60s Next Steps ✅ Add test_storage.py - Done Add aspect computation tests Add extended position format tests Add CLI + storage integration tests Add performance benchmarks Set up CI/CD with test automation Unittest Quick Start # From project root python -m unittest discover tests -v # Or using unittest directly python -m unittest discover . -v # Or run specific test file python -m unittest tests.test_comprehensive -v Specific Test Suites # Run comprehensive test suite python -m unittest tests.test_comprehensive.TestComprehensive -v # Run workspace flow tests python -m unittest tests.test_workspace_flow -v # Run CLI tests python -m unittest tests.test_cli -v # Run engine tests python -m unittest tests.test_engines -v Specific Test Methods # Run a single test method python -m unittest tests.test_comprehensive.TestComprehensive.test_02_workspace_initialization -v Test Files Core Functionality Tests test_comprehensive.py: Comprehensive test suite covering all functionality\nDate, place, location parsing Engine functionality Workspace flow (including StarFisher import) CLI accessibility Chart computation with timestamps Sample workspace creation (creates tests/sample/ directory) test_workspace_flow.py: Workspace initialization and management\nWorkspace creation Chart addition and saving Workspace settings test_cli.py: Command-line interface tests\nChart computation via CLI Workspace settings retrieval Chart listing and retrieval Transit series computation test_engines.py: Astrological computation engine tests\nJPL engine Kerykeion engine Engine comparison test_storage.py: Storage integration tests\nPosition storage Aspect storage Parquet export Storage Stress Test test_storage_stress.py: High-frequency storage stress test 5 days at 1-minute intervals (~7,200 timestamps) 10 standard planets (~72,000 rows) All extended attributes enabled (physical + topocentric) Run with unittest:\npython -m unittest tests.test_storage_stress.TestStorageStress.test_minute_interval_5_days_max_attributes -v Expected runtime:\nComputation: ~5-15 minutes Storage: ~1-2 minutes Parquet export: ~10-30 seconds Total: ~10-20 minutes Output location:\ntests/sample_stress/ Workspace contents (high level):\nworkspace.yaml charts/ data/workspace.db data/parquet/ (partitioned by date) Clean up:\nrm -rf tests/sample_stress/ Utility Tests test_dates_places.py: Date and place parsing utilities test_locations.py: Location parsing and geocoding test_starfisher.py: StarFisher file import test_polar.py: Polar coordinate calculations Test Sample Workspace The comprehensive test suite creates a sample workspace in tests/sample/ that you can inspect after running tests. This directory is automatically added to .gitignore.\nNote: The sample workspace is created during test execution and contains:\nworkspace.yaml - Workspace manifest charts/ - Chart YAML files subjects/ - Subject definitions layouts/, annotations/, presets/ - Other workspace components Test Requirements Required Dependencies Python 3.7+ Standard library unittest Test Output Verbose Output Use -v flag for verbose output showing each test as it runs:\npython -m unittest discover tests -v Quiet Output Omit -v for minimal output (only failures):\npython -m unittest discover tests Stop on First Failure python -m unittest discover tests -v --failfast Common Issues Import Errors If you get import errors, make sure you\u0026rsquo;re running from the project root:\ncd /path/to/function-wrapper python -m unittest discover tests -v Missing Dependencies Some tests are skipped if optional dependencies are missing. This is expected behavior.\nSample Workspace Already Exists If tests/sample/ already exists from a previous test run, the comprehensive tests will use it. To start fresh:\nrm -rf tests/sample/ python -m unittest tests.test_comprehensive -v "
  }
]
