updates to pyswiss aspect & aspect application data served over API, incl. seminal invocation of Space parades & Time stellia
All checks were successful
ci/woodpecker/push/main Pipeline was successful
ci/woodpecker/push/pyswiss Pipeline was successful

This commit is contained in:
Disco DeDisco
2026-04-21 00:37:33 -04:00
parent 4761d3f939
commit 9c7d58f0b3
4 changed files with 297 additions and 36 deletions

View File

@@ -21,10 +21,14 @@ SIGN_ELEMENT = {
ASPECTS = [
('Conjunction', 0, 8.0),
('Semisextile', 30, 4.0),
('Sextile', 60, 6.0),
('Square', 90, 8.0),
('Trine', 120, 8.0),
('Quincunx', 150, 5.0),
('Opposition', 180, 10.0),
# ('Semisquare', 45, 4.0),
# ('Sesquiquadrate', 135, 4.0),
]
PLANET_CODES = {
@@ -67,6 +71,7 @@ def get_planet_positions(jd):
planets[name] = {
'sign': get_sign(degree),
'degree': degree,
'speed': pos[3],
'retrograde': pos[3] < 0,
}
return planets
@@ -74,30 +79,69 @@ def get_planet_positions(jd):
def get_element_counts(planets):
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']
counts[SIGN_ELEMENT[sign]] += 1
el = SIGN_ELEMENT[sign]
classic[el].append({'planet': name, 'sign': sign})
sign_counts[sign] += 1
sign_planets[sign].append({'planet': name, 'sign': sign})
# Time: highest planet concentration in a single sign, minus 1
counts['Time'] = max(sign_counts.values()) - 1
result = {
el: {'count': len(contribs), 'contributors': contribs}
for el, contribs in classic.items()
}
# Space: longest consecutive run of occupied signs (circular), minus 1
indices = [i for i, s in enumerate(SIGNS) if sign_counts[s] > 0]
# Time: stellium — highest concentration in one sign, bonus = size - 1.
# 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
for start in range(len(indices)):
seq_len = 1
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
else:
break
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):
@@ -119,12 +163,16 @@ def calculate_aspects(planets):
for aspect_name, target, max_orb in ASPECTS:
orb = abs(angle - target)
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({
'planet1': name1,
'planet2': name2,
'type': aspect_name,
'angle': round(angle, 2),
'orb': round(orb, 2),
'applying_planet': applying,
})
break
return aspects