updates to pyswiss aspect & aspect application data served over API, incl. seminal invocation of Space parades & Time stellia
This commit is contained in:
@@ -21,10 +21,14 @@ SIGN_ELEMENT = {
|
|||||||
|
|
||||||
ASPECTS = [
|
ASPECTS = [
|
||||||
('Conjunction', 0, 8.0),
|
('Conjunction', 0, 8.0),
|
||||||
|
('Semisextile', 30, 4.0),
|
||||||
('Sextile', 60, 6.0),
|
('Sextile', 60, 6.0),
|
||||||
('Square', 90, 8.0),
|
('Square', 90, 8.0),
|
||||||
('Trine', 120, 8.0),
|
('Trine', 120, 8.0),
|
||||||
|
('Quincunx', 150, 5.0),
|
||||||
('Opposition', 180, 10.0),
|
('Opposition', 180, 10.0),
|
||||||
|
# ('Semisquare', 45, 4.0),
|
||||||
|
# ('Sesquiquadrate', 135, 4.0),
|
||||||
]
|
]
|
||||||
|
|
||||||
PLANET_CODES = {
|
PLANET_CODES = {
|
||||||
@@ -67,6 +71,7 @@ def get_planet_positions(jd):
|
|||||||
planets[name] = {
|
planets[name] = {
|
||||||
'sign': get_sign(degree),
|
'sign': get_sign(degree),
|
||||||
'degree': degree,
|
'degree': degree,
|
||||||
|
'speed': pos[3],
|
||||||
'retrograde': pos[3] < 0,
|
'retrograde': pos[3] < 0,
|
||||||
}
|
}
|
||||||
return planets
|
return planets
|
||||||
@@ -74,30 +79,69 @@ def get_planet_positions(jd):
|
|||||||
|
|
||||||
def get_element_counts(planets):
|
def get_element_counts(planets):
|
||||||
sign_counts = {s: 0 for s in SIGNS}
|
sign_counts = {s: 0 for s in SIGNS}
|
||||||
counts = {'Fire': 0, 'Water': 0, 'Earth': 0, 'Air': 0}
|
sign_planets = {s: [] for s in SIGNS}
|
||||||
|
classic = {'Fire': [], 'Water': [], 'Earth': [], 'Air': []}
|
||||||
|
|
||||||
for data in planets.values():
|
for name, data in planets.items():
|
||||||
sign = data['sign']
|
sign = data['sign']
|
||||||
counts[SIGN_ELEMENT[sign]] += 1
|
el = SIGN_ELEMENT[sign]
|
||||||
|
classic[el].append({'planet': name, 'sign': sign})
|
||||||
sign_counts[sign] += 1
|
sign_counts[sign] += 1
|
||||||
|
sign_planets[sign].append({'planet': name, 'sign': sign})
|
||||||
|
|
||||||
# Time: highest planet concentration in a single sign, minus 1
|
result = {
|
||||||
counts['Time'] = max(sign_counts.values()) - 1
|
el: {'count': len(contribs), 'contributors': contribs}
|
||||||
|
for el, contribs in classic.items()
|
||||||
|
}
|
||||||
|
|
||||||
# Space: longest consecutive run of occupied signs (circular), minus 1
|
# Time: stellium — highest concentration in one sign, bonus = size - 1.
|
||||||
indices = [i for i, s in enumerate(SIGNS) if sign_counts[s] > 0]
|
# Collect all signs tied at the maximum.
|
||||||
|
max_in_sign = max(sign_counts.values())
|
||||||
|
stellia = [
|
||||||
|
{'sign': s, 'planets': sign_planets[s]}
|
||||||
|
for s in SIGNS
|
||||||
|
if sign_counts[s] == max_in_sign and max_in_sign > 1
|
||||||
|
]
|
||||||
|
result['Time'] = {
|
||||||
|
'count': max_in_sign - 1,
|
||||||
|
'stellia': stellia,
|
||||||
|
}
|
||||||
|
|
||||||
|
# Space: parade — longest consecutive run of occupied signs (circular),
|
||||||
|
# bonus = run length - 1. Collect all runs tied at the maximum.
|
||||||
|
index_set = {i for i, s in enumerate(SIGNS) if sign_counts[s] > 0}
|
||||||
|
indices = sorted(index_set)
|
||||||
max_seq = 0
|
max_seq = 0
|
||||||
for start in range(len(indices)):
|
for start in range(len(indices)):
|
||||||
seq_len = 1
|
seq_len = 1
|
||||||
for offset in range(1, len(indices)):
|
for offset in range(1, len(indices)):
|
||||||
if (indices[start] + offset) % len(SIGNS) in indices:
|
if (indices[start] + offset) % len(SIGNS) in index_set:
|
||||||
seq_len += 1
|
seq_len += 1
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
max_seq = max(max_seq, seq_len)
|
max_seq = max(max_seq, seq_len)
|
||||||
counts['Space'] = max_seq - 1
|
|
||||||
|
|
||||||
return counts
|
parades = []
|
||||||
|
for start in range(len(indices)):
|
||||||
|
run = []
|
||||||
|
for offset in range(max_seq):
|
||||||
|
idx = (indices[start] + offset) % len(SIGNS)
|
||||||
|
if idx not in index_set:
|
||||||
|
break
|
||||||
|
run.append(idx)
|
||||||
|
else:
|
||||||
|
sign_run = [SIGNS[i] for i in run]
|
||||||
|
parade_planets = [
|
||||||
|
p for s in sign_run for p in sign_planets[s]
|
||||||
|
]
|
||||||
|
parades.append({'signs': sign_run, 'planets': parade_planets})
|
||||||
|
|
||||||
|
result['Space'] = {
|
||||||
|
'count': max_seq - 1,
|
||||||
|
'parades': parades,
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
def calculate_aspects(planets):
|
def calculate_aspects(planets):
|
||||||
@@ -119,12 +163,16 @@ def calculate_aspects(planets):
|
|||||||
for aspect_name, target, max_orb in ASPECTS:
|
for aspect_name, target, max_orb in ASPECTS:
|
||||||
orb = abs(angle - target)
|
orb = abs(angle - target)
|
||||||
if orb <= max_orb:
|
if orb <= max_orb:
|
||||||
|
s1 = abs(planets[name1].get('speed', 0))
|
||||||
|
s2 = abs(planets[name2].get('speed', 0))
|
||||||
|
applying = name1 if s1 >= s2 else name2
|
||||||
aspects.append({
|
aspects.append({
|
||||||
'planet1': name1,
|
'planet1': name1,
|
||||||
'planet2': name2,
|
'planet2': name2,
|
||||||
'type': aspect_name,
|
'type': aspect_name,
|
||||||
'angle': round(angle, 2),
|
'angle': round(angle, 2),
|
||||||
'orb': round(orb, 2),
|
'orb': round(orb, 2),
|
||||||
|
'applying_planet': applying,
|
||||||
})
|
})
|
||||||
break
|
break
|
||||||
return aspects
|
return aspects
|
||||||
|
|||||||
@@ -31,12 +31,12 @@ class Command(BaseCommand):
|
|||||||
EphemerisSnapshot.objects.update_or_create(
|
EphemerisSnapshot.objects.update_or_create(
|
||||||
dt=dt,
|
dt=dt,
|
||||||
defaults={
|
defaults={
|
||||||
'fire': elements['Fire'],
|
'fire': elements['Fire']['count'],
|
||||||
'water': elements['Water'],
|
'water': elements['Water']['count'],
|
||||||
'earth': elements['Earth'],
|
'earth': elements['Earth']['count'],
|
||||||
'air': elements['Air'],
|
'air': elements['Air']['count'],
|
||||||
'time_el': elements['Time'],
|
'time_el': elements['Time']['count'],
|
||||||
'space_el': elements['Space'],
|
'space_el': elements['Space']['count'],
|
||||||
'chart_data': {'planets': planets},
|
'chart_data': {'planets': planets},
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -114,10 +114,42 @@ class ChartApiTest(TestCase):
|
|||||||
"""All 10 planets are assigned to exactly one classical element."""
|
"""All 10 planets are assigned to exactly one classical element."""
|
||||||
data = self._get({'dt': J2000, **LONDON}).json()
|
data = self._get({'dt': J2000, **LONDON}).json()
|
||||||
classical = sum(
|
classical = sum(
|
||||||
data['elements'][e] for e in ('Fire', 'Water', 'Earth', 'Air')
|
data['elements'][e]['count'] for e in ('Fire', 'Water', 'Earth', 'Air')
|
||||||
)
|
)
|
||||||
self.assertEqual(classical, 10)
|
self.assertEqual(classical, 10)
|
||||||
|
|
||||||
|
def test_each_element_has_count_key(self):
|
||||||
|
data = self._get({'dt': J2000, **LONDON}).json()
|
||||||
|
for key in ('Fire', 'Water', 'Earth', 'Air', 'Time', 'Space'):
|
||||||
|
with self.subTest(element=key):
|
||||||
|
self.assertIn('count', data['elements'][key])
|
||||||
|
|
||||||
|
def test_classic_elements_have_contributors(self):
|
||||||
|
data = self._get({'dt': J2000, **LONDON}).json()
|
||||||
|
for key in ('Fire', 'Water', 'Earth', 'Air'):
|
||||||
|
with self.subTest(element=key):
|
||||||
|
self.assertIn('contributors', data['elements'][key])
|
||||||
|
|
||||||
|
def test_time_has_stellia(self):
|
||||||
|
data = self._get({'dt': J2000, **LONDON}).json()
|
||||||
|
self.assertIn('stellia', data['elements']['Time'])
|
||||||
|
|
||||||
|
def test_space_has_parades(self):
|
||||||
|
data = self._get({'dt': J2000, **LONDON}).json()
|
||||||
|
self.assertIn('parades', data['elements']['Space'])
|
||||||
|
|
||||||
|
def test_each_planet_has_speed(self):
|
||||||
|
data = self._get({'dt': J2000, **LONDON}).json()
|
||||||
|
for name, planet in data['planets'].items():
|
||||||
|
with self.subTest(planet=name):
|
||||||
|
self.assertIn('speed', planet)
|
||||||
|
|
||||||
|
def test_each_aspect_has_applying_planet(self):
|
||||||
|
data = self._get({'dt': J2000, **LONDON}).json()
|
||||||
|
for aspect in data['aspects']:
|
||||||
|
with self.subTest(aspect=aspect):
|
||||||
|
self.assertIn('applying_planet', aspect)
|
||||||
|
|
||||||
# ── house system ──────────────────────────────────────────────────────
|
# ── house system ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
def test_default_house_system_is_porphyry(self):
|
def test_default_house_system_is_porphyry(self):
|
||||||
|
|||||||
@@ -9,25 +9,55 @@ Run:
|
|||||||
"""
|
"""
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
|
|
||||||
from apps.charts.calc import calculate_aspects
|
from apps.charts.calc import calculate_aspects, get_element_counts
|
||||||
|
|
||||||
|
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Synthetic planet data — degrees chosen for predictable aspects
|
# FAKE_PLANETS_ASPECTS — degrees only; used by calculate_aspects tests.
|
||||||
# Matches FAKE_PLANETS in test_populate_ephemeris.py
|
# Each planet also carries a speed (deg/day) for applying_planet tests.
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
FAKE_PLANETS = {
|
FAKE_PLANETS = {
|
||||||
'Sun': {'degree': 10.0}, # Aries
|
'Sun': {'degree': 10.0, 'speed': 1.00}, # Aries
|
||||||
'Moon': {'degree': 130.0}, # Leo — 120° from Sun → Trine
|
'Moon': {'degree': 130.0, 'speed': 13.00}, # Leo — 120° from Sun → Trine
|
||||||
'Mercury': {'degree': 250.0}, # Sagittarius — 120° from Sun → Trine
|
'Mercury': {'degree': 250.0, 'speed': 1.50}, # Sagittarius — 120° from Sun → Trine
|
||||||
'Venus': {'degree': 40.0}, # Taurus — 90° from Moon → Square
|
'Venus': {'degree': 40.0, 'speed': 1.10}, # Taurus — 90° from Moon → Square
|
||||||
'Mars': {'degree': 160.0}, # Virgo — 60° from Neptune → Sextile
|
'Mars': {'degree': 160.0, 'speed': 0.50}, # Virgo — 60° from Neptune → Sextile
|
||||||
'Jupiter': {'degree': 280.0}, # Capricorn — 120° from Mars → Trine
|
'Jupiter': {'degree': 280.0, 'speed': 0.08}, # Capricorn — 120° from Mars → Trine
|
||||||
'Saturn': {'degree': 70.0}, # Gemini — 120° from Uranus → Trine
|
'Saturn': {'degree': 70.0, 'speed': 0.03}, # Gemini — 120° from Uranus → Trine
|
||||||
'Uranus': {'degree': 310.0}, # Aquarius — 60° from Sun (wrap) → Sextile
|
'Uranus': {'degree': 310.0, 'speed': 0.01}, # Aquarius — 60° from Sun (wrap) → Sextile
|
||||||
'Neptune': {'degree': 100.0}, # Cancer
|
'Neptune': {'degree': 100.0, 'speed': 0.006}, # Cancer
|
||||||
'Pluto': {'degree': 340.0}, # Pisces
|
'Pluto': {'degree': 340.0, 'speed': 0.003}, # Pisces
|
||||||
|
}
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
# FAKE_PLANETS_ELEMENTS — sign + degree + speed; used by get_element_counts.
|
||||||
|
# Designed to produce a known stellium and parade.
|
||||||
|
#
|
||||||
|
# Occupied signs: Aries(0), Taurus(1), Gemini(2), Leo(4), Virgo(5),
|
||||||
|
# Scorpio(7), Capricorn(9), Aquarius(10)
|
||||||
|
# Gaps at Cancer(3), Libra(6), Sagittarius(8), Pisces(11) prevent wrap-around.
|
||||||
|
#
|
||||||
|
# Consecutive runs: Aries→Taurus→Gemini = 3 ← parade (Space = 2)
|
||||||
|
# Leo→Virgo = 2
|
||||||
|
# Capricorn→Aquarius = 2
|
||||||
|
#
|
||||||
|
# Time = 2 (Aries has Sun+Mercury+Venus → stellium of 3, bonus = 2)
|
||||||
|
# Space = 2 (Aries→Taurus→Gemini = 3-sign parade, bonus = 2)
|
||||||
|
# Classic: Fire=4, Earth=3, Air=2, Water=1
|
||||||
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
FAKE_PLANETS_ELEMENTS = {
|
||||||
|
'Sun': {'sign': 'Aries', 'degree': 10.0, 'speed': 1.00}, # Fire, stellium
|
||||||
|
'Moon': {'sign': 'Taurus', 'degree': 40.0, 'speed': 13.00}, # Earth, parade
|
||||||
|
'Mercury': {'sign': 'Aries', 'degree': 20.0, 'speed': 1.50}, # Fire, stellium
|
||||||
|
'Venus': {'sign': 'Aries', 'degree': 25.0, 'speed': 1.10}, # Fire, stellium
|
||||||
|
'Mars': {'sign': 'Leo', 'degree': 130.0, 'speed': 0.50}, # Fire
|
||||||
|
'Jupiter': {'sign': 'Scorpio', 'degree': 220.0, 'speed': 0.08}, # Water
|
||||||
|
'Saturn': {'sign': 'Gemini', 'degree': 70.0, 'speed': 0.03}, # Air, parade
|
||||||
|
'Uranus': {'sign': 'Aquarius', 'degree': 310.0, 'speed': 0.01}, # Air
|
||||||
|
'Neptune': {'sign': 'Capricorn', 'degree': 270.0, 'speed': 0.006}, # Earth
|
||||||
|
'Pluto': {'sign': 'Virgo', 'degree': 160.0, 'speed': 0.003}, # Earth
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -36,6 +66,131 @@ def _aspect_pairs(aspects):
|
|||||||
return {(a['planet1'], a['planet2'], a['type']) for a in aspects}
|
return {(a['planet1'], a['planet2'], a['type']) for a in aspects}
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# get_element_counts — enriched shape
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
|
class GetElementCountsTest(SimpleTestCase):
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
self.counts = get_element_counts(FAKE_PLANETS_ELEMENTS)
|
||||||
|
|
||||||
|
# ── top-level keys ───────────────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_returns_all_six_elements(self):
|
||||||
|
for key in ('Fire', 'Earth', 'Air', 'Water', 'Time', 'Space'):
|
||||||
|
with self.subTest(key=key):
|
||||||
|
self.assertIn(key, self.counts)
|
||||||
|
|
||||||
|
# ── classic four — count + contributors ──────────────────────────────────
|
||||||
|
|
||||||
|
def test_classic_element_has_count_key(self):
|
||||||
|
self.assertIn('count', self.counts['Fire'])
|
||||||
|
|
||||||
|
def test_classic_element_has_contributors_key(self):
|
||||||
|
self.assertIn('contributors', self.counts['Fire'])
|
||||||
|
|
||||||
|
def test_fire_count_is_correct(self):
|
||||||
|
# Sun + Mercury + Venus (Aries) + Mars (Leo) = 4
|
||||||
|
self.assertEqual(self.counts['Fire']['count'], 4)
|
||||||
|
|
||||||
|
def test_earth_count_is_correct(self):
|
||||||
|
# Moon (Taurus) + Neptune (Capricorn) + Pluto (Virgo) = 3
|
||||||
|
self.assertEqual(self.counts['Earth']['count'], 3)
|
||||||
|
|
||||||
|
def test_air_count_is_correct(self):
|
||||||
|
# Saturn (Gemini) + Uranus (Aquarius) = 2
|
||||||
|
self.assertEqual(self.counts['Air']['count'], 2)
|
||||||
|
|
||||||
|
def test_water_count_is_correct(self):
|
||||||
|
# Jupiter (Scorpio) = 1
|
||||||
|
self.assertEqual(self.counts['Water']['count'], 1)
|
||||||
|
|
||||||
|
def test_fire_contributors_contains_expected_planets(self):
|
||||||
|
planets = {c['planet'] for c in self.counts['Fire']['contributors']}
|
||||||
|
self.assertEqual(planets, {'Sun', 'Mercury', 'Venus', 'Mars'})
|
||||||
|
|
||||||
|
def test_contributor_has_planet_and_sign_keys(self):
|
||||||
|
contrib = self.counts['Fire']['contributors'][0]
|
||||||
|
self.assertIn('planet', contrib)
|
||||||
|
self.assertIn('sign', contrib)
|
||||||
|
|
||||||
|
def test_fire_contributor_signs_are_correct(self):
|
||||||
|
sign_map = {c['planet']: c['sign'] for c in self.counts['Fire']['contributors']}
|
||||||
|
self.assertEqual(sign_map['Sun'], 'Aries')
|
||||||
|
self.assertEqual(sign_map['Mercury'], 'Aries')
|
||||||
|
self.assertEqual(sign_map['Venus'], 'Aries')
|
||||||
|
self.assertEqual(sign_map['Mars'], 'Leo')
|
||||||
|
|
||||||
|
# ── Time — count + stellia ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_time_has_count_key(self):
|
||||||
|
self.assertIn('count', self.counts['Time'])
|
||||||
|
|
||||||
|
def test_time_has_stellia_key(self):
|
||||||
|
self.assertIn('stellia', self.counts['Time'])
|
||||||
|
|
||||||
|
def test_time_count_is_correct(self):
|
||||||
|
# Aries has 3 planets → bonus = 2
|
||||||
|
self.assertEqual(self.counts['Time']['count'], 2)
|
||||||
|
|
||||||
|
def test_time_stellia_is_a_list(self):
|
||||||
|
self.assertIsInstance(self.counts['Time']['stellia'], list)
|
||||||
|
|
||||||
|
def test_time_stellia_contains_one_entry(self):
|
||||||
|
self.assertEqual(len(self.counts['Time']['stellia']), 1)
|
||||||
|
|
||||||
|
def test_time_stellium_sign_is_aries(self):
|
||||||
|
self.assertEqual(self.counts['Time']['stellia'][0]['sign'], 'Aries')
|
||||||
|
|
||||||
|
def test_time_stellium_planets_are_correct(self):
|
||||||
|
planet_names = {p['planet'] for p in self.counts['Time']['stellia'][0]['planets']}
|
||||||
|
self.assertEqual(planet_names, {'Sun', 'Mercury', 'Venus'})
|
||||||
|
|
||||||
|
def test_time_stellium_planet_entries_have_sign(self):
|
||||||
|
for entry in self.counts['Time']['stellia'][0]['planets']:
|
||||||
|
with self.subTest(planet=entry['planet']):
|
||||||
|
self.assertEqual(entry['sign'], 'Aries')
|
||||||
|
|
||||||
|
# ── Space — count + parades ──────────────────────────────────────────────
|
||||||
|
|
||||||
|
def test_space_has_count_key(self):
|
||||||
|
self.assertIn('count', self.counts['Space'])
|
||||||
|
|
||||||
|
def test_space_has_parades_key(self):
|
||||||
|
self.assertIn('parades', self.counts['Space'])
|
||||||
|
|
||||||
|
def test_space_count_is_correct(self):
|
||||||
|
# Aries→Taurus→Gemini = 3 consecutive → bonus = 2
|
||||||
|
self.assertEqual(self.counts['Space']['count'], 2)
|
||||||
|
|
||||||
|
def test_space_parades_is_a_list(self):
|
||||||
|
self.assertIsInstance(self.counts['Space']['parades'], list)
|
||||||
|
|
||||||
|
def test_space_parades_contains_one_entry(self):
|
||||||
|
self.assertEqual(len(self.counts['Space']['parades']), 1)
|
||||||
|
|
||||||
|
def test_space_parade_signs_are_correct(self):
|
||||||
|
self.assertEqual(
|
||||||
|
self.counts['Space']['parades'][0]['signs'],
|
||||||
|
['Aries', 'Taurus', 'Gemini'],
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_space_parade_planets_are_correct(self):
|
||||||
|
planet_names = {p['planet'] for p in self.counts['Space']['parades'][0]['planets']}
|
||||||
|
self.assertEqual(planet_names, {'Sun', 'Mercury', 'Venus', 'Moon', 'Saturn'})
|
||||||
|
|
||||||
|
def test_space_parade_planet_entries_have_planet_and_sign(self):
|
||||||
|
for entry in self.counts['Space']['parades'][0]['planets']:
|
||||||
|
with self.subTest(planet=entry['planet']):
|
||||||
|
self.assertIn('planet', entry)
|
||||||
|
self.assertIn('sign', entry)
|
||||||
|
|
||||||
|
|
||||||
|
# ===========================================================================
|
||||||
|
# calculate_aspects
|
||||||
|
# ===========================================================================
|
||||||
|
|
||||||
class CalculateAspectsTest(SimpleTestCase):
|
class CalculateAspectsTest(SimpleTestCase):
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@@ -55,8 +210,32 @@ class CalculateAspectsTest(SimpleTestCase):
|
|||||||
self.assertIn('angle', aspect)
|
self.assertIn('angle', aspect)
|
||||||
self.assertIn('orb', aspect)
|
self.assertIn('orb', aspect)
|
||||||
|
|
||||||
|
def test_each_aspect_has_applying_planet_key(self):
|
||||||
|
for aspect in self.aspects:
|
||||||
|
with self.subTest(aspect=aspect):
|
||||||
|
self.assertIn('applying_planet', aspect)
|
||||||
|
|
||||||
|
def test_applying_planet_is_one_of_the_pair(self):
|
||||||
|
for aspect in self.aspects:
|
||||||
|
with self.subTest(aspect=aspect):
|
||||||
|
self.assertIn(
|
||||||
|
aspect['applying_planet'],
|
||||||
|
(aspect['planet1'], aspect['planet2']),
|
||||||
|
)
|
||||||
|
|
||||||
|
def test_applying_planet_is_the_faster_body(self):
|
||||||
|
"""Moon (13.0°/day) applies to Sun (1.0°/day) in their Trine."""
|
||||||
|
sun_moon = next(
|
||||||
|
a for a in self.aspects
|
||||||
|
if {a['planet1'], a['planet2']} == {'Sun', 'Moon'}
|
||||||
|
)
|
||||||
|
self.assertEqual(sun_moon['applying_planet'], 'Moon')
|
||||||
|
|
||||||
def test_each_aspect_type_is_a_known_name(self):
|
def test_each_aspect_type_is_a_known_name(self):
|
||||||
known = {'Conjunction', 'Sextile', 'Square', 'Trine', 'Opposition'}
|
known = {
|
||||||
|
'Conjunction', 'Semisextile', 'Sextile', 'Square',
|
||||||
|
'Trine', 'Quincunx', 'Opposition',
|
||||||
|
}
|
||||||
for aspect in self.aspects:
|
for aspect in self.aspects:
|
||||||
with self.subTest(aspect=aspect):
|
with self.subTest(aspect=aspect):
|
||||||
self.assertIn(aspect['type'], known)
|
self.assertIn(aspect['type'], known)
|
||||||
@@ -126,11 +305,13 @@ class CalculateAspectsTest(SimpleTestCase):
|
|||||||
|
|
||||||
def test_orb_is_within_allowed_maximum(self):
|
def test_orb_is_within_allowed_maximum(self):
|
||||||
max_orbs = {
|
max_orbs = {
|
||||||
'Conjunction': 8.0,
|
'Conjunction': 8.0,
|
||||||
'Sextile': 6.0,
|
'Semisextile': 4.0,
|
||||||
'Square': 8.0,
|
'Sextile': 6.0,
|
||||||
'Trine': 8.0,
|
'Square': 8.0,
|
||||||
'Opposition': 10.0,
|
'Trine': 8.0,
|
||||||
|
'Quincunx': 5.0,
|
||||||
|
'Opposition': 10.0,
|
||||||
}
|
}
|
||||||
for aspect in self.aspects:
|
for aspect in self.aspects:
|
||||||
with self.subTest(aspect=aspect):
|
with self.subTest(aspect=aspect):
|
||||||
|
|||||||
Reference in New Issue
Block a user