HomeDocsHouses & Cusps

Houses & Cusps

House-system policy, cusp calculation, high-latitude behavior, effective fallback, and admitted spatial boundary geometry.

Moira Houses Backend Standard

Governing Principle

The Moira houses backend is a sovereign computational subsystem. Its definitions, layer boundaries, invariants, failure doctrine, and determinism rules are stated here and are frozen until explicitly superseded by a revision to this document.

This document reflects current implementation truth for the public house-system surface. It does not describe aspirational future capabilities.

For the clean-room sovereignty remediation path beyond current implementation truth, see HOUSES_SOVEREIGNTY_REMEDIATION_ROADMAP.md. For the house-system derivation and discretionary divergence record, see HOUSE_SYSTEM_DIVERGENCE.md.


Part I — Architecture Standard

1. Authoritative Computational Definitions

1.1 House cusp

A house cusp in Moira is:

An ecliptic longitude in degrees [0, 360) that marks the opening boundary of one of the twelve astrological houses for a given observer location, Julian date, and house system.

ElementDefinition
ecliptic longitudeDegrees along the ecliptic, normalised to [0, 360) by normalize_degrees
observer locationGeographic latitude [-90, 90] and longitude [-180, 180] in decimal degrees
Julian dateUT1-based Julian day number
house systemOne of the 22 recognised HouseSystem codes

Twelve cusps are always produced. No system produces fewer or more than 12.

1.2 House

A house in Moira is:

The half-open ecliptic arc [cusps[n-1], cusps[n % 12]) for house number n (1–12), where arcs are measured as forward arcs on the circle.

RuleDefinition
intervalHouse n owns [cusps[n-1], cusps[n % 12]) — opening cusp included, next cusp excluded
forward arc(end - start) % 360° — always non-negative, never assumes monotonic cusps
membership test(longitude - cusps[n-1]) % 360° < span_n
opening cusp ownershipA longitude coinciding with a cusp belongs to the house that opens at that cusp

1.3 Ecliptic longitude placement

A placement is:

The result of assigning one normalised ecliptic longitude to exactly one house under the interval rule using a specific set of 12 cusps.

GuaranteeDescription
completenessEvery longitude maps to exactly one house — no gaps, no overlaps
determinismThe same longitude and the same cusps always yield the same house
normalisationInput is normalised to [0, 360) before membership evaluation
exact-on-cuspWhen the distance to the opening cusp is < 1e-9°, exact_on_cusp is True; the point is still in that house

1.4 Angularity category

An angularity category is:

A structural label derived from the assigned house number alone, independent of cusp positions, system family, or latitude.

CategoryHouses
ANGULAR1, 4, 7, 10
SUCCEDENT2, 5, 8, 11
CADENT3, 6, 9, 12

1.5 Cusp proximity

Cusp proximity is:

The forward-arc distance from a placed longitude to its nearest bracketing cusp, evaluated against an explicit caller-declared threshold.

DistanceFormula
dist_to_opening(longitude - opening_cusp) % 360°
dist_to_closing(closing_cusp - longitude) % 360°
house_spandist_to_opening + dist_to_closing (identity to < 1e-9°)
nearest_cusp_distancemin(dist_to_opening, dist_to_closing); tie-break to opening cusp
is_near_cuspnearest_cusp_distance < near_cusp_threshold

1.6 Signed cusp delta

A signed cusp delta between two systems is:

(right.cusps[i] - left.cusps[i]) % 360° - 360° when > 180°, else (right.cusps[i] - left.cusps[i]) % 360°.

Result is always in the range (-180°, 180°]. Positive means the right cusp is counter-clockwise ahead of the left; negative means behind.


2. Layer Structure

The backend is organised into ten phases. Each phase operates only on outputs produced by phases below it. No phase reaches upward.

Phase  1 — Truth preservation           (HouseCusps: system / effective_system / fallback)
Phase  2 — Classification               (HouseSystemFamily, HouseSystemCuspBasis, HouseSystemClassification)
Phase  3 — Inspectability               (HouseCusps.__post_init__, _POLAR_SYSTEMS, _KNOWN_SYSTEMS)
Phase  4 — Policy                       (UnknownSystemPolicy, PolarFallbackPolicy, HousePolicy)
Phase  5 — Point-to-house membership    (HousePlacement, assign_house)
Phase  6 — Cusp proximity               (HouseBoundaryProfile, describe_boundary)
Phase  7 — Angularity                   (HouseAngularity, HouseAngularityProfile, describe_angularity)
Phase  8 — System comparison            (HouseSystemComparison, HousePlacementComparison, compare_systems, compare_placements)
Phase  9 — Chart-wide distribution      (HouseOccupancy, HouseDistributionProfile, distribute_points)
Phase 10 — Subsystem hardening         (invariant register, failure-behavior freeze, terminology alignment)

Layer boundary rules

A function in phase N:

  • may consume any result vessel from phases 1 through N−1
  • may not re-run cusp arithmetic
  • may not re-perform house membership independently of assign_house
  • may not mutate a vessel produced by an earlier phase
  • may not introduce new doctrine inputs that are not explicit parameters

3. Supported Systems

22 house system codes are recognised. _KNOWN_SYSTEMS is the authoritative frozenset.

CodeNameFamilyCusp basisLat-sensitivePolar-capable
WWhole SignWHOLE_SIGNECLIPTICNoYes
EEqualEQUALECLIPTICNoYes
EMEqual from MCEQUALECLIPTICNoYes
VVehlowEQUALECLIPTICNoYes
MMorinusEQUALEQUATORIALNoYes
XMeridianEQUALEQUATORIALNoYes
ZZarielEQUALEQUATORIALNoYes
PSDPullen SDQUADRANTSINUSOIDALYesYes
PSRPullen SRQUADRANTSINUSOIDALYesYes
SSolar SignSOLARECLIPTICNoYes
OPorphyryQUADRANTQUADRANT_TRISECTIONYesYes
PPlacidusQUADRANTSEMI_ARCYesYes
BAlcabitiusQUADRANTSEMI_ARCYesYes
KKochQUADRANTOBLIQUE_ASCENSIONYesNo
CCampanusQUADRANTPRIME_VERTICALYesNo
HAzimuthalQUADRANTHORIZONYesYes
RRegiomontanusQUADRANTPOLAR_PROJECTIONYesNo
TTopocentricQUADRANTPOLAR_PROJECTIONYesNo
CTCarterQUADRANTEQUATORIALYesYes
UKrusinskiQUADRANTGREAT_CIRCLEYesYes
YAPCQUADRANTAPC_FORMULAYesYes
NSunshineSOLARSOLAR_POSITIONNoYes

Polar-incapable systems (_POLAR_SYSTEMS): K, C, R, and T. Placidus (P) has integrated branch-search doctrine for high latitudes (unique ordered semi-arc cycles when they exist for the position); it is no longer blanket pre-empted and is marked polar_capable=True. When no unique ordered solution exists for a given high-lat ARMC, policy still governs fallback/raise. Alcabitius (B) likewise has integrated direct zero-pole ordered-figure doctrine and is marked polar_capable=True. APC remains directly computable at high latitude only when its projected cusps form one strictly ordered ecliptic cycle; otherwise its declared polar policy governs fallback or raise. The remaining listed systems fall back (or raise) under default policy above critical latitude (90° − obliquity ≈ 66.56° at J2000).

QUADRANT H1 exception

For QUADRANT family systems with cusp_basis == HORIZON (Azimuthal, code H), cusps[0] is a horizon-derived cusp and legitimately differs from the geographic Ascendant. The __post_init__ guard that asserts cusps[0] == asc is skipped for this basis. All non-quadrant families also legitimately place H1 ≠ ASC.


4. Delegated Assumptions

The houses backend delegates to external modules without redefining them.

ConcernDelegated toConvention
Longitude normalisationmoira.coordinates.normalize_degreesReturns [0, 360)
Time conversion (UT1 → TT)moira.julian.ut_to_ttJulian days
True obliquitymoira.obliquity.true_obliquityDegrees
Nutationmoira.obliquity.nutation(dpsi, deps) in degrees
Local sidereal timemoira.julian.local_sidereal_timeARMC in degrees
Sign labellingmoira.constants.sign_of(name, symbol, degree_within_sign)
Sun longitude (Sunshine / Solar Sign)moira.planets.sun_longitudeDegrees; lazily imported

The backend does not redefine any of these. Changes to those modules propagate automatically to all cusp computations.


5. Public Surface

All public names are declared in the module moira/houses.py.

Enumerations

NameMembers
HouseSystemFamilyEQUAL, QUADRANT, WHOLE_SIGN, SOLAR
HouseSystemCuspBasisECLIPTIC, EQUATORIAL, SEMI_ARC, OBLIQUE_ASCENSION, QUADRANT_TRISECTION, PRIME_VERTICAL, HORIZON, POLAR_PROJECTION, SINUSOIDAL, GREAT_CIRCLE, APC_FORMULA, SOLAR_POSITION
HouseAngularityANGULAR, SUCCEDENT, CADENT
UnknownSystemPolicyFALLBACK_TO_PLACIDUS, RAISE
PolarFallbackPolicyFALLBACK_TO_PORPHYRY, FALLBACK_TO_EQUAL, FALLBACK_TO_WHOLE_SIGN, RAISE, EXPERIMENTAL_SEARCH

Frozen dataclass vessels

VesselPhasePrimary fields
HouseSystemClassification2family, cusp_basis, latitude_sensitive, polar_capable
HousePolicy4unknown_system, polar_fallback
HouseCusps1–4system, cusps (immutable tuple), asc, mc, armc, vertex, anti_vertex, effective_system, fallback, fallback_reason, classification, policy
HousePlacement5house, longitude, house_cusps, exact_on_cusp, cusp_longitude
HouseBoundaryProfile6placement, opening_cusp, closing_cusp, dist_to_opening, dist_to_closing, house_span, nearest_cusp, nearest_cusp_distance, near_cusp_threshold, is_near_cusp
HouseAngularityProfile7placement, category, house
HouseSystemComparison8left, right, cusp_deltas, systems_agree, fallback_differs, families_differ
HousePlacementComparison8longitude, placements, houses, all_agree, angularity_agrees
HouseOccupancy9house, count, longitudes, placements, is_empty
HouseDistributionProfile9house_cusps, point_count, occupancies, counts, empty_houses, dominant_houses, angular_count, succedent_count, cadent_count

Computation functions

FunctionSignaturePhase
classify_house_system(code: str) -> HouseSystemClassification2
calculate_houses(jd_ut, latitude, longitude, system='P', *, policy=None) -> HouseCusps1–4
assign_house(longitude, house_cusps) -> HousePlacement5
describe_boundary(placement, *, near_cusp_threshold=3.0) -> HouseBoundaryProfile6
describe_angularity(placement) -> HouseAngularityProfile7
compare_systems(left, right) -> HouseSystemComparison8
compare_placements(longitude, *house_cusps_seq) -> HousePlacementComparison8
distribute_points(longitudes, house_cusps) -> HouseDistributionProfile9

Module-level constants

NameValueMeaning
_MEMBERSHIP_CUSP_TOLERANCE1e-9Degrees; threshold for exact_on_cusp detection
_NEAR_CUSP_DEFAULT_THRESHOLD3.0Degrees; default for describe_boundary
_POLAR_SYSTEMSfrozenset{'K','C','R','T'}Systems still outer-guarded above the critical latitude
_KNOWN_SYSTEMSfrozenset of 22 codesAll recognised HouseSystem values
_ANGULARITY_MAPdict[int, HouseAngularity]Static 12-entry lookup; never recomputed

6. Fallback and Policy Doctrine

6.1 Fallback triggers

Two conditions can redirect the computation away from the requested system. Unknown-system policy is resolved before polar admissibility; projected-cycle quality is evaluated after the affected cusp arithmetic.

TriggerConditionDefault behaviourStrict behaviour
Critical latitude or unordered projected cycleabs(latitude) >= 90° − obliquity and either the effective system is in _POLAR_SYSTEMS, integrated high-latitude search for Placidus/Alcabitius finds no unique ordered figure, or APC produces an unordered ecliptic cycleSubstitute according to PolarFallbackPolicyRaise ValueError
Unknown systemsystem not in _KNOWN_SYSTEMSSubstitute PlacidusRaise ValueError

The critical latitude is computed from the chart's actual obliquity at call time. At J2000 obliquity (23.4377°) this is ≈ 66.56° — the geometric Arctic Circle, above which some ecliptic degrees become circumpolar and the standard fixed-point semi-arc iteration can produce geometrically invalid cusp orderings. The old fixed 75.0° threshold was incorrect: it silently returned invalid cusp sets from ≈66.6° to 74.9°.

This does not mean Placidus is mathematically impossible above the critical latitude. The 77°N branch-search experiment showed that valid, ordered Placidus solutions can exist in narrow ARMC regimes. Moira therefore distinguishes between:

  • globally supported behavior (default production path)
  • conditionally solvable high-latitude cases (experimental search path)
  • unsupported cases where the current production solver cannot recover a valid branch

Critical latitude takes precedence over unknown system when both conditions are true.

6.2 Fallback truth preservation

When a fallback occurs:

FieldValue
HouseCusps.systemThe requested code — never modified
HouseCusps.effective_systemThe substituted code actually used
HouseCusps.fallbackTrue
HouseCusps.fallback_reasonHuman-readable string (see §6.3)

When no fallback occurs: fallback = False, fallback_reason = None.

6.3 Fallback reason strings (pattern)

TriggerMessage pattern
Critical latitude + default policy"|lat| <value>° >= critical latitude <threshold>° (90° − obliquity); '<code>' produces invalid cusps above this threshold; fell back to Porphyry"
Unknown + default policy"unknown system code '<code>'; fell back to Placidus"

6.3a Experimental high-latitude search

PolarFallbackPolicy.EXPERIMENTAL_SEARCH is an explicit opt-in research mode that loads the separate experimental_<system>.py module for a requested polar system when such a module is registered. The per-system module owns the definition of an admissible high-latitude figure for that geometry.

  • The engine calls the registered moira.experimental_<system> module for the requested system.
  • The search accepts the result only when the module's own doctrine identifies exactly one admissible ordered cusp cycle.
  • If no ordered cycle exists, or more than one ordered cycle exists, the call raises ValueError rather than silently falling back.
  • Successful experimental search returns effective_system == system and fallback == False; the experimental nature of the computation remains visible through HouseCusps.policy.

6.4 Policy factory methods

Methodunknown_systempolar_fallback
HousePolicy.default()FALLBACK_TO_PLACIDUSFALLBACK_TO_PORPHYRY
HousePolicy.strict()RAISERAISE
HousePolicy.experimental()FALLBACK_TO_PLACIDUSEXPERIMENTAL_SEARCH

HousePolicy.default() exactly replicates all pre-Phase-4 behaviour.


7. Invariant Register

7.1 HouseCusps invariants (enforced by __post_init__)

#InvariantViolation raises
C1len(cusps) == 12ValueError
C2For QUADRANT family with cusp_basis != HORIZON: abs(cusps[0] - asc) < 1e-9°ValueError
C3fallback == (system != effective_system) when effective_system is setValueError
C4(fallback_reason is None) == (not fallback)ValueError
C5classification is not None when effective_system is non-emptyValueError
C6policy is a HousePolicyTypeError

7.2 HousePlacement invariants

#Invariant
P11 <= house <= 12
P20.0 <= longitude < 360.0
P30.0 <= cusp_longitude < 360.0
P4cusp_longitude == house_cusps.cusps[house - 1] (within 1e-9°)

7.3 HouseBoundaryProfile invariants

#Invariant
B1dist_to_opening >= 0.0
B2dist_to_closing > 0.0
B3abs(dist_to_opening + dist_to_closing - house_span) < 1e-9
B4house_span > 0.0
B5near_cusp_threshold > 0.0
B6nearest_cusp_distance >= 0.0
B7is_near_cusp == (nearest_cusp_distance < near_cusp_threshold)

7.4 HouseAngularityProfile invariants

#Invariant
A11 <= house <= 12
A2house == placement.house
A3category == _ANGULARITY_MAP[house]

7.5 HouseSystemComparison invariants

#Invariant
SC1len(cusp_deltas) == 12
SC2All d in cusp_deltas satisfy -180.0 < d <= 180.0
SC3systems_agree == (left.effective_system == right.effective_system)
SC4fallback_differs == (left.fallback != right.fallback)

7.6 HousePlacementComparison invariants

#Invariant
PC10.0 <= longitude < 360.0
PC2len(placements) >= 2
PC3len(houses) == len(placements)
PC4houses[i] == placements[i].house for all i
PC5all_agree == (len(set(houses)) == 1)
PC6placements[i].longitude == longitude for all i

7.7 HouseOccupancy invariants

#Invariant
O11 <= house <= 12
O2count == len(longitudes) == len(placements)
O3is_empty == (count == 0)
O4pl.house == house for all pl in placements

7.8 HouseDistributionProfile invariants

#Invariant
D1len(occupancies) == 12
D2len(counts) == 12
D3point_count == sum(counts)
D4angular_count + succedent_count + cadent_count == point_count
D5occupancies[i].house == i + 1 for all i
D6counts[i] == occupancies[i].count for all i
D7dominant_houses == () when point_count == 0
D8counts[h-1] == max(counts) for all h in dominant_houses when point_count > 0

8. Determinism and Ordering Rules

The following ordering guarantees are frozen.

ContextOrdering rule
HouseCusps.cuspsHouse 1 at index 0, House 12 at index 11; indices never reordered
HousePlacementComparison.placementsSame order as systems passed to compare_placements
HousePlacementComparison.housesParallel to placements
HouseDistributionProfile.occupanciesHouse 1 at index 0, House 12 at index 11
HouseDistributionProfile.dominant_housesAscending house number
HouseOccupancy.longitudes / .placementsInput order of distribute_points sequence
assign_house on equal inputIdentical output — no state, no randomness
distribute_points on equal inputIdentical output — deterministic via assign_house
compare_placements on equal inputIdentical output

9. Non-Goals and Excluded Concerns

The following are explicitly outside the scope of moira/houses.py and all Phase 1–10 layers:

Excluded concernNotes
Planet position computationDelegated to moira.planets
Aspect detectionSeparate subsystem (moira/aspects.py)
Dignity scoringSeparate subsystem
Interpretation (weak/strong, benefic/malefic)Never in the backend
Chart assemblyHigher-level orchestration
Hemisphere / quadrant totalsDeferred; doctrine not yet frozen
Harmonic house overlaysDeferred
Cross-system distribution comparisonDeferred
UI rendering or formattingExcluded permanently from this file
Public API exposure (__init__)Phase 12

Part II — Validation Codex

10. Validation Environment

Authoritative runtime: project .venv (Python 3.14, Windows/cmd).

All validation commands must be run as:

.venv\Scripts\python.exe -m pytest <target>

No test may be marked passing unless it passes in .venv with no modifications to the test file. Tests may not be silenced, skipped without a registered marker, or monkey-patched to hide real failures.


11. Test File Register

FilePhase(s)Focus
tests/unit/test_house_truth_preservation.py1system / effective_system / fallback field integrity
tests/unit/test_house_classification.py2HouseSystemFamily, HouseSystemCuspBasis, classify_house_system
tests/unit/test_house_inspectability.py3__post_init__ guard paths, is_quadrant_system, is_latitude_sensitive
tests/unit/test_house_policy.py4HousePolicy, UnknownSystemPolicy, PolarFallbackPolicy, strict raises
tests/unit/test_house_membership.py5assign_house, boundary interval, wraparound, exact-on-cusp
tests/unit/test_house_boundary.py6describe_boundary, distance doctrine, span-sum identity, threshold
tests/unit/test_house_angularity.py7describe_angularity, _ANGULARITY_MAP, all 12 houses
tests/unit/test_house_comparison.py8compare_systems, compare_placements, delta range, agreement flags
tests/unit/test_house_distribution.py9distribute_points, occupancy counts, empty/dominant, angularity totals
tests/unit/test_house_hardening.py10Cross-layer consistency, failure behavior, determinism, invariant preservation
tests/unit/test_polar_houses.py3-4Polar fallback at extreme latitudes
tests/integration/test_houses_external_reference.py1Placidus cusps vs external reference values

Exact test counts belong to live pytest output, not this doctrine file.


12. Validation Doctrine

12.1 What must be validated per layer

LayerMust test
Truth preservationsystem unchanged after fallback; effective_system matches what ran; fallback is True iff they differ; fallback_reason is None iff fallback is False
Classificationclassify_house_system returns correct family and cusp_basis for all 18 recognised codes and raises on unknown codes
Inspectability__post_init__ raises concrete runtime exceptions (ValueError / TypeError) for violated invariants; properties are consistent with classification
PolicyDefault policy produces no raise; strict policy raises ValueError on both polar and unknown triggers; error messages match §6.3 patterns
MembershipEvery longitude in [0, 360) maps to exactly one house; opening cusp belongs to its house; exact_on_cusp fires within 1e-9°; wraparound cusps are handled correctly
Boundarydist_to_opening + dist_to_closing == house_span to < 1e-9°; dist_to_closing > 0 always; is_near_cusp consistent with nearest_cusp_distance; zero/negative threshold raises ValueError
Angularity_ANGULARITY_MAP covers all 12 houses; category == _ANGULARITY_MAP[house]; house == placement.house
Comparisoncusp_deltas all in (-180, 180]; systems_agree consistent with effective_system; all placement.longitude == longitude in HousePlacementComparison
Distribution12 occupancies always; point_count == sum(counts); angularity sum == point_count; input order preserved; dominant_houses sorted ascending
HardeningCross-layer seam consistency; all failure paths; same input → same output across all public functions

12.2 Conftest fixture usage (Phase 10 onward)

New tests added from Phase 10 onward must use the session-scoped conftest fixtures rather than constructing inline HouseCusps:

FixtureProvidesScope
natal_housesHouseCusps (Placidus, London 51.5°N / 0.1°W, 2000-01-01 12:00 UTC)session
moira_engineMoira() engine instancesession
natal_chartChart for the same reference momentsession
jd_j20002451545.0session

Tests that require a second system for comparison may construct it inline via calculate_houses with the same reference coordinates.

12.3 What tests must NOT do

  • Modify moira/houses.py constants or vessel definitions to make a test pass
  • Skip a failing test without a registered KNOWN_ISSUES.yml entry
  • Assert on internal private names (_circular_diff, _porphyry, etc.) unless testing the specific private behaviour is the stated purpose of that test class
  • Use monkeypatch to suppress a ValueError or TypeError that the implementation is meant to raise

13. Guaranteed Failure Conditions

The following inputs must always produce the stated error. This table is frozen.

FunctionBad inputError raisedMessage contains
calculate_housessystem not in _KNOWN_SYSTEMS + HousePolicy.strict()ValueError"unknown house system code"
calculate_housesabs(latitude) >= 90° − obliquity + system in _POLAR_SYSTEMS + HousePolicy.strict()ValueError"critical latitude"
calculate_houses / houses_from_armcpolicy is not a HousePolicyTypeError"policy must be a HousePolicy"
assign_houselen(house_cusps.cusps) != 12ValueError"exactly 12 cusps"
describe_boundarynear_cusp_threshold <= 0.0ValueError"near_cusp_threshold must be positive"
compare_placementsfewer than 2 HouseCusps suppliedValueError"at least 2"

The following inputs must always produce the stated runtime exception at construction time:

VesselViolated invariantRaises
HouseCuspslen(cusps) != 12ValueError
HouseCuspsfallback != (system != effective_system)ValueError
HouseCuspsfallback_reason present when fallback is FalseValueError
HouseCuspspolicy is not a HousePolicyTypeError
HousePlacementhouse outside [1, 12]ValueError
HousePlacementcusp_longitude does not match house_cusps.cusps[house-1]ValueError
HouseBoundaryProfiledist_to_opening + dist_to_closing != house_spanValueError
HouseBoundaryProfileis_near_cusp inconsistent with distancesValueError
HouseAngularityProfilecategory != _ANGULARITY_MAP[house]ValueError
HouseSystemComparisonany delta outside (-180, 180]ValueError
HousePlacementComparisonlen(placements) < 2ValueError
HousePlacementComparisonplacement.longitude != longitudeValueError
HouseOccupancycount != len(longitudes)ValueError
HouseDistributionProfilepoint_count != sum(counts)ValueError
HouseDistributionProfileangularity sum != point_countValueError

14. Cross-Layer Consistency Requirements

The following cross-layer relationships are required to hold at all times.

RelationshipRequirement
HousePlacement.cusp_longitudeMust equal placement.house_cusps.cusps[placement.house - 1]
HouseBoundaryProfile.opening_cuspMust equal placement.cusp_longitude
HouseBoundaryProfile.closing_cuspMust equal placement.house_cusps.cusps[placement.house % 12]
HouseAngularityProfile.houseMust equal placement.house
HouseAngularityProfile.categoryMust equal _ANGULARITY_MAP[placement.house]
HousePlacementComparison.longitudeMust equal pl.longitude for every pl in placements
HouseOccupancy.placements[i].houseMust equal occupancy.house for all i
HouseDistributionProfile.counts[i]Must equal occupancies[i].count for all i
HouseDistributionProfile.angular_countMust equal sum(counts[h-1] for h in (1,4,7,10))
HouseCusps.classificationMust equal classify_house_system(effective_system)
HouseCusps.is_quadrant_systemMust equal classification.family == HouseSystemFamily.QUADRANT
HouseCusps.is_latitude_sensitiveMust equal classification.latitude_sensitive

15. Scope-Freeze Statement

The Moira houses backend is hereby frozen at Phase 10. The following changes require an explicit revision to this document before implementation:

  • Adding a new public vessel or computation function
  • Adding a field to any existing vessel
  • Changing the default value of any function parameter
  • Changing the boundary condition of any interval rule or distance formula
  • Adding a new house system code to _KNOWN_SYSTEMS
  • Changing the critical-latitude formula (90° − obliquity)
  • Changing the cusp tolerance (1e-9°)
  • Changing the default near-cusp threshold (3.0°)
  • Changing the angularity map (_ANGULARITY_MAP)
  • Exposing any name through __init__ (Phase 12)

The following changes do not require a revision:

  • Adding new tests within an existing test class
  • Adding docstring clarifications that do not change stated doctrine
  • Performance improvements that produce identical outputs
  • Fixing a defect where the implementation violates a stated invariant in this document