NINE_PARTS_BACKEND_STANDARD - TheDaniel166/moira GitHub Wiki

Nine Parts Backend Standard

Subsystem: moira/nine_parts.py Computational Domain: Abu Ma'shar's Nine Hermetic Lots Constitutional Phase: 11 — Architecture Freeze and Validation Codex Status: Constitutional


Part I — Architecture Standard

§1. Computational Definition

§1.1 What the Nine Parts Are

The Nine Parts are Abu Ma'shar's expansion of the classical Hermetic Lots into a system of nine: seven lots associated with the seven classical planets, plus two nodal lots (the Part of the Sword and the Part of the Node). Each lot is computed as a directed arc from one chart body to another, projected from the Ascendant.

Doctrine basis: Abu Ma'shar, Kitāb taḥāwil sinī al-mawālīd. Confirmed formulas and reversal rule: Benjamin N. Dykes, Introductions to Traditional Astrology (Cazimi Press, 2010) and Persian Nativities Vol. II (Cazimi Press, 2010).

This subsystem is not the Hellenistic nonomoiria (9-fold sign subdivision) and is not the Vedic Navamsha (D9 chart). The Vedic Navamsha is handled by moira/varga.py. The Hellenistic nonomoiria is not currently implemented.

§1.2 Formula Table

All nine parts use the formula: Longitude = (Asc + Add − Sub) mod 360°

# Part Planet Day Add Day Sub
1 Fortune Moon Moon Sun
2 Spirit Sun Sun Moon
3 Love Venus Spirit Fortune
4 Necessity Mercury Fortune Spirit
5 Courage Mars Fortune Mars
6 Victory Jupiter Jupiter Spirit
7 Nemesis Saturn Fortune Saturn
8 Sword Mars Saturn
9 Node North Node Moon

Parts 3 (Love), 4 (Necessity), and 6 (Victory) are derived: their formula ingredients include other computed lots (Fortune and/or Spirit), not only raw planet longitudes. The engine computes Fortune and Spirit first; derived parts are computed after.

§1.3 Night Reversal Rule

Abu Ma'shar applies full reversal for all nine parts for night charts. For night charts, the Add and Sub operands are swapped:

Night formula: Longitude = (Asc + Day_Sub − Day_Add) mod 360°

Night definition: the chart is nocturnal when the Sun is in houses 1–6 (below the horizon). The caller supplies is_night_chart: bool; the engine does not determine sect independently.

No per-lot exceptions. The full reversal applies to all nine parts uniformly. This is the Abu Ma'shar standard confirmed by Dykes. Some medieval editors stopped reversing the Part of Fortune following a misreading of Ptolemy; this engine does not follow that error.

§1.4 Dependency Order

Step 1: Fortune (raw: Moon, Sun)
        Spirit  (raw: Sun, Moon)
Step 2: Love       (derived: Spirit, Fortune)
        Necessity  (derived: Fortune, Spirit)
        Courage    (raw: Fortune [computed], Mars)
        Victory    (derived: Spirit [computed], Jupiter)
        Nemesis    (raw: Fortune [computed], Saturn)
        Sword      (raw: Mars, Saturn)
        Node       (raw: North Node, Moon)

The engine enforces this order internally. Callers do not control computation order.


§2. Public API

§2.1 Primary Entry Point

def nine_parts_abu_mashar(
    asc: float,
    planets: dict[str, float],
    is_night_chart: bool,
    policy: NinePartsPolicy = DEFAULT_NINE_PARTS_POLICY,
) -> NinePartsAggregate

Parameters:

Parameter Type Description
asc float Ascendant longitude in degrees [0, 360)
planets dict[str, float] Planet name → longitude mapping
is_night_chart bool True when Sun is in houses 1–6
policy NinePartsPolicy Doctrinal configuration

Required planet keys: Sun, Moon, Mars, Jupiter, Saturn, North Node.

Returns: NinePartsAggregate — complete result with all parts, dependency relations, per-part condition profiles, and aggregate intelligence.

Raises: KeyError if a required planet key is missing; ValueError if any longitude is non-finite.

§2.2 Validation Entry Point

def validate_nine_parts_output(aggregate: NinePartsAggregate) -> list[str]

Returns a list of failure strings. An empty list confirms full consistency. See §4 (Validation Codex) for the complete check list.


§3. Type Surface

§3.1 Classification Namespaces

Type Values Purpose
NinePartName FORTUNENODE Canonical Abu Ma'shar part names
NinePartFormulaVariant DAY, NIGHT Which formula column was applied
NinePartDependencyKind DIRECT, DERIVED Whether the formula uses other lots
NinePartsReversalRule FULL_REVERSAL Only admitted reversal doctrine

§3.2 Policy Surface

@dataclass(frozen=True)
class NinePartsPolicy:
    reversal_rule: NinePartsReversalRule = NinePartsReversalRule.FULL_REVERSAL

DEFAULT_NINE_PARTS_POLICY is the module-level default instance.

The only admitted reversal rule is FULL_REVERSAL. No other option is historically supported for the Abu Ma'shar Nine Parts system.

§3.3 Truth-Preservation Vessel — NinePartComputationTruth

Preserved per part. Records every operand and decision made during formula evaluation.

Field Type Description
asc_longitude float Ascendant used for projection
add_key str Add operand key (after reversal)
sub_key str Sub operand key (after reversal)
add_longitude float Resolved longitude of add operand
sub_longitude float Resolved longitude of sub operand
is_night_chart bool Whether chart was nocturnal
formula_reversed bool Whether night reversal was applied
formula_variant NinePartFormulaVariant DAY or NIGHT
formula str Human-readable formula string

Invariant: formula_reversed implies is_night_chart. formula_variant is NIGHT iff formula_reversed is True. formula must equal f"Asc + {add_key} − {sub_key}".

§3.4 Primary Result Vessel — NinePart

Field Type Description
name NinePartName Canonical name
planet_association str | None Associated planet; None for Sword and Node
meaning str Brief interpretive domain
longitude float Computed longitude [0, 360)
sign str Sign name
sign_degree float Degree within sign [0, 30)
sign_symbol str Glyph character
dependency_kind NinePartDependencyKind DIRECT or DERIVED
computation NinePartComputationTruth Full computation audit trail

Invariants: longitude in [0, 360); sign_degree in [0, 30); sign matches sign_of(longitude).

Phase 3 properties:

Property Returns Description
is_derived bool True when dependency_kind is DERIVED
is_nocturnal_formula bool True when night formula was used
has_planet_association bool False only for Sword and Node
degrees_in_sign int Whole degrees in sign
minutes_in_sign int Arc minutes within whole degree

§3.5 Relational Vessel — NinePartsDependencyRelation

Field Type Description
part NinePartName The part whose dependency is described
lot_dependencies tuple[NinePartName, ...] Lots this formula depends on

Invariant: lot_dependencies must match the canonical dependency list for the named part. Empty for direct parts.

Properties: is_direct (no lot dependencies), dependency_count.

§3.6 Relational Group Vessel — NinePartsSet

Field Type Description
parts list[NinePart] Nine parts in canonical order
is_night_chart bool Sect of the chart
policy NinePartsPolicy Policy used
dependency_relations list[NinePartsDependencyRelation] One per part

Invariants: exactly 9 parts; exactly 9 dependency relations; all parts share the same is_night_chart.

Methods: get(name), get_dependency_relation(name).

Phase 6 properties: direct_parts, derived_parts, nocturnal_formula_count, planetary_parts, nodal_parts.

§3.7 Condition Vessel — NinePartConditionProfile

Field Type Description
part NinePart The part
dependency_relation NinePartsDependencyRelation Dependency relation
lord str Traditional domicile ruler of the part's sign
lord_is_part_planet bool True when lord == part's planet association

Invariant: lord must match _SIGN_RULER[part.sign].

Properties: is_in_own_sign, is_derived.

§3.8 Aggregate Vessel — NinePartsAggregate

Field Type Description
parts_set NinePartsSet Complete parts set
condition_profiles list[NinePartConditionProfile] 9 profiles
policy NinePartsPolicy Policy used

Invariant: exactly 9 condition profiles.

Properties: parts_in_own_sign, unique_lords, dominant_lord.

Methods: get_profile(name).


§4. Validation Codex

validate_nine_parts_output(aggregate) checks:

# Check Failure example
1 Exactly 9 parts "Expected 9 parts, found 8"
2 Parts in canonical order "Part 2: expected LOVE, got COURAGE"
3 Night consistency across parts "Fortune: is_night_chart mismatch"
4 formula_reversed matches night flag "Fortune: formula_reversed=False but is_night_chart=True"
5 Derived parts follow their dependencies "Love appears before its dependency Spirit"
6 DERIVED classification on derived parts "Love: dependency_kind should be DERIVED"
7 DIRECT classification on direct parts "Fortune: dependency_kind should be DIRECT"
8 Longitudes finite and in [0, 360) "Fortune: longitude 361.0 not in [0, 360)"
9 sign_degree in [0, 30) "Fortune: sign_degree 30.0 not in [0, 30)"
10 Sign names match longitudes "Fortune: sign 'Aries' does not match sign_of(…)"
11 Lord assignments match sign rulers "Fortune: lord 'Sun' does not match sign ruler for 'Cancer'"
12 Exactly 9 condition profiles "Expected 9 condition profiles, found 8"
13 Exactly 9 dependency relations "Expected 9 dependency relations, found 7"

§5. Doctrine Boundaries

§5.1 What This Module Owns

  • The Nine Parts formula catalogue for the Abu Ma'shar tradition
  • Night reversal logic (FULL_REVERSAL)
  • Dependency ordering enforcement (Fortune/Spirit before derived lots)
  • Result vessels through all constitutional process layers
  • Lord-of-the-part assignment (traditional domicile rulers only)

§5.2 What This Module Does Not Own

  • Night determination (caller supplies is_night_chart)
  • House calculation or Ascendant computation
  • Solar return chart construction
  • Al-Sijzi Transfer of Management mechanics (Phase 2, not yet implemented)
  • Vedic Navamsha (handled by moira/varga.py)
  • Hellenistic nonomoiria (not yet implemented)
  • The Part of Fortune used elsewhere in Moira (e.g. in lots.py or longevity.py) is not governed by this module; each subsystem owns its own Fortune computation

§5.3 Admitted Doctrine

  • Abu Ma'shar, full-reversal variant only
  • Traditional (pre-modern) domicile rulers (no outer planets)
  • Egyptian bounds and Dorothaean triplicity rulers are not used in this module; the lord-of-the-part assignment uses domicile only

§5.4 Deferred Doctrine (Not Yet Implemented)

  • Al-Sijzi Transfer of Management: itissāl chain of the lot lord, tasyīr profection of the part through bounds (Phase 2)
  • The intersection with longevity.py for the Part-based Hyleg refinement (Phase 2 interface design required before implementation)

Part II — Implementation Notes

§6. Module Location

moira/nine_parts.py — standalone module, not part of lots.py.

Rationale: the Nine Parts are a specific named doctrinal system (Abu Ma'shar), not a generic lot catalogue entry. Keeping them separate prevents confusion with the ~430-entry PARTS_DEFINITIONS catalogue and makes the doctrinal scope explicit.

§7. Relation to lots.py

The nine_parts_abu_mashar() engine does not call lots.py internally. It reimplements the arc formula (Asc + A − B mod 360°) directly for the nine parts, ensuring that the reversal rule, dependency order, and result vessels are governed entirely by this module.

The formulas for Fortune and Spirit in nine_parts.py are mathematically identical to the "Fortune" and "Spirit" entries in lots.py. They are not the same objects; callers should not mix results from the two modules.

§8. Relation to varga.py

The Vedic Navamsha (D9) is fully implemented in moira/varga.py via calculate_varga(longitude, 9) and the navamsa() convenience function. The segment_idx % 12 formula in varga.py naturally encodes the Vedic elemental starting-point rule and produces correct navamsha sign assignments. No additional navamsha work is needed in nine_parts.py.


Part III — Change Policy

§9. Stability Guarantees

The following are frozen and require a doctrine review before changing:

  • _NINE_PARTS_CATALOGUE — the nine formula definitions
  • NinePartName enum values and their order
  • NinePartsReversalRule.FULL_REVERSAL semantics
  • The dependency order (Fortune/Spirit before derived lots)

The following are internal and may change without API notice:

  • _SIGN_RULER, _DERIVED_PARTS, _LOT_DEPENDENCIES
  • _ALL_DEPENDENCY_RELATIONS, _DEPENDENCY_RELATION_BY_NAME
  • _validate_inputs, _resolve_key

§10. Extension Points

To add Al-Sijzi Transfer of Management (Phase 2):

  1. Define LotManagement vessel in this module
  2. Add lot_management(part, chart, date) -> LotManagement
  3. Design the longevity.py intersection interface in a separate doctrinal note before touching either module
  4. Do not modify any existing Phase 1–8 vessels or the primary engine