YEAR_SEMANTICS_AUDIT - TheDaniel166/moira GitHub Wiki
Year Semantics Audit
Date: 2026-03-30 Corrected: 2026-07-25
Profection correction. The earlier audit classified a fixed
elapsed_days / 365.25age as plausibly correct. That classification was stale.profection_schedule()now advances completed age on the natal civil anniversary in the natal timezone. February 29 nativities require an explicitfebruary_28ormarch_1anniversary policy.
Purpose
Audit every live use of 365.25, 36525.0, JULIAN_YEAR, TROPICAL_YEAR,
and SIDEREAL_YEAR in the moira/ codebase so the project distinguishes:
- exact Julian-year doctrine
- tropical-year timing
- sidereal-year/orbital timing
- standard Julian-date algorithm constants
- sites that remain candidates for future refinement
Summary Verdict
Most remaining 365.25 uses are correct and should remain.
The codebase currently contains seven distinct semantic classes:
Julian yearby doctrine or convention: keep.Tropical yearfor seasonal/solar timing: useTROPICAL_YEAR.Sidereal yearfor orbital/stellar period timing: useSIDEREAL_YEAR.Julian century(36525.0) in standard astronomical polynomials: keep.365.25inside canonical calendar/JD conversion algorithms: keep.- Delta-T annual-epoch integration: use actual source-epoch spacing.
- Civil-anniversary chronology for completed ages: do not replace with a fixed-length astronomical year.
After the latest correction pass, the main live upgrade targets that remain are
small in number. The largest genuinely ambiguous site is the annual-resolution
integration in delta_t_physical.py.
Class A: Keep As Exact Julian-Year Doctrine
These are not loose approximations. They are explicit Julian-year doctrine, historical astrological convention, or standard astronomical convention.
moira/constants.pyJULIAN_YEAR = 365.25- Correct. Exact by definition.
moira/dasha.pyjulian_365.25basis and allJULIAN_YEARuses- Correct. This is an explicit doctrinal year basis, contrasted with
savana_360.
moira/timelords.py_JULIAN_YEAR = 365.25- Correct. The module explicitly says JD arithmetic uses Julian years.
moira/primary_directions/keys.py_NAIBOD_RATE = 360.0 / 365.25- Correct. Naibod is a doctrinal key, not a generic solar-year approximation.
moira/primary_directions/__init__.py_DEFAULT_SOLAR_RATE = 360.0 / 365.25- Likely correct within primary-direction key doctrine unless the subsystem explicitly adopts a different key basis.
moira/stars.py- proper-motion propagation:
dt_years = (jd_tt - _J2000) / 365.25 - light-time conversion:
_DAYS_PER_YEAR = 365.25 - Correct by astronomical convention. Proper motion is normally expressed per Julian year; the light-year is conventionally tied to the Julian year.
- proper-motion propagation:
moira/multiple_stars.pyOrbitalElements.period_yris documented as Julian yearst_yr = (jd - orb.epoch_jd) / 365.25- Correct because the module explicitly defines orbital periods in Julian years.
moira/sothic.py1460 × 365.25 = 533265cycle_pos = ((jd_rise - epoch_jd) / 365.25) % _SOTHIC_CYCLE_YEARS- Correct. The Sothic cycle identity is specifically Julian-year based.
Class B: Keep As Tropical-Year Timing
These are places where the code means seasonal or solar-cycle timing rather than a generic Julian year.
moira/constants.pyTROPICAL_YEAR = 365.24219
moira/harmonics.py- age harmonic timing uses
TROPICAL_YEAR - Correct.
- age harmonic timing uses
moira/progressions.py- progression age conversion uses
TROPICAL_YEAR - Correct.
- progression age conversion uses
moira/transits.pysolar_return()search seed now usesTROPICAL_YEAR- Correct. The solar return is seasonal/solar, not Julian-year doctrine.
Class C: Keep As Sidereal-Year / Orbital Timing
These are places where the code means orbital period or sidereal cycle timing.
moira/constants.pySIDEREAL_YEAR = 365.256363Body.SIDEREAL_PERIODS[EARTH] = SIDEREAL_YEAR- Correct.
moira/phenomena.py- perihelion/aphelion fallback search windows now use
SIDEREAL_YEAR - Correct. These are orbital search heuristics, not seasonal year lengths.
- perihelion/aphelion fallback search windows now use
Class D: Keep As Julian-Century Polynomial Time
These are standard astronomical polynomial scales. They are not “approximate years” in the sense at issue here.
moira/constants.pyJULIAN_CENTURY = 36525.0
moira/coordinates.pyT = (jd_tt - 2451545.0) / 36525.0
moira/eclipse.pycenturies = delta_days / 36525.0
moira/hermetic_decans.pyT = (jd - 2451545.0) / 36525.0
moira/nodes.py- rates divided by
36525.0
- rates divided by
moira/precession.pyT * 36525.0
These should remain exactly as they are unless the underlying polynomial model itself changes.
Class E: Keep As Canonical JD/Calendar Algorithm Terms
These uses occur inside standard Meeus-style or related calendar/JD conversion formulae. They are algorithm constants, not physical year approximations.
moira/julian.pymath.floor(365.25 * (year + 4716))(B - 122.1) / 365.25D = math.floor(365.25 * C)
moira/planets.py- inverse calendar/JD helper with the same constants
These should not be rewritten as tropical or sidereal years.
Class F: Delta-T Annual-Epoch Integration — Resolved
moira/delta_t_physical.py no longer treats every annual-series interval as
an undifferentiated 365.25-day span. Calendar mid-year epochs use their
actual Julian-day spacing, including leap-year structure. A JULIAN_YEAR
fallback remains only for an explicitly uniform decimal-year axis whose input
does not carry calendar mid-year semantics.
The candidate C04, AAM/OAM, and GRACE component series are also quarantined from the admitted public Delta-T mean. Their epoch-spacing helpers remain testable research infrastructure; they do not establish causal component truth merely by integrating a proxy.
Class G: Civil-Anniversary Chronology
moira/profections.pyprofection_schedule()accepts timezone-awarenatal_dtandcurrent_dt.- Completed age changes at the natal local date and time, after converting the current instant into the natal timezone.
- A February 29 nativity requires an explicit
LeapDayAnniversaryPolicy.FEBRUARY_28orLeapDayAnniversaryPolicy.MARCH_1. - The result carries
age_basis="civil_anniversary"and the selected leap-day policy.
This is a calendar rule, not a Julian-, tropical-, or sidereal-year constant.
Using int(elapsed_days / 365.25) can advance the profection before the civil
anniversary and is forbidden for the schedule API.
Low-Priority Cosmetic Improvements
These are not correctness bugs, but could be made more explicit over time.
moira/stars.py- could import
JULIAN_YEARinstead of local_DAYS_PER_YEAR = 365.25
- could import
moira/timelords.py- could import
JULIAN_YEARinstead of local_JULIAN_YEAR = 365.25
- could import
moira/multiple_stars.py- could import
JULIAN_YEARfor consistency with its “Julian years” doctrine
- could import
These are semantic-clarity refactors, not accuracy fixes.
Recommended Next Steps
- Leave all admitted Class A-G semantics intact.
- Optionally normalize explicit Julian-year imports in Class A for consistency.
- Preserve the explicit source-epoch policy and quarantine status when Delta-T data artifacts are refreshed or re-derived.
- If desired, annotate key modules with “Julian year”, “tropical year”, or “sidereal year” comments at the constant declaration points to prevent future drift.