#!/usr/bin/env python3
"""
Project 33: Planetary Dignities & Character
Does Essential Dignity (Domicile/Exaltation) correlate with Career Success/Field?
"""
import sys
import pandas as pd
import numpy as np
import swisseph as swe
from scipy import stats
import matplotlib.pyplot as plt
import seaborn as sns
from pathlib import Path
from datetime import datetime
# Import the data extracted from Project 16
from source_data import CREATIVE_GENIUSES
OUTPUT_DIR = Path(__file__).parent
swe.set_ephe_path(None)
SIGNS = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo',
'Libra', 'Scorpio', 'Sagittarius', 'Capricorn', 'Aquarius', 'Pisces']
PLANETS = {
'Sun': swe.SUN,
'Moon': swe.MOON,
'Mercury': swe.MERCURY,
'Venus': swe.VENUS,
'Mars': swe.MARS,
'Jupiter': swe.JUPITER,
'Saturn': swe.SATURN
}
# Dignity Map (Sign Indices 0-11)
# Sign: 0=Ari, 1=Tau, 2=Gem, 3=Can, 4=Leo, 5=Vir, 6=Lib, 7=Sco, 8=Sag, 9=Cap, 10=Aqu, 11=Pis
DIGNITIES = {
'Sun': {'Dom': [4], 'Exalt': [0], 'Det': [10], 'Fall': [6]},
'Moon': {'Dom': [3], 'Exalt': [1], 'Det': [9], 'Fall': [7]},
'Mercury':{'Dom': [2, 5], 'Exalt': [5], 'Det': [8, 11], 'Fall': [11]}, # Vir is Dom+Exalt
'Venus': {'Dom': [1, 6], 'Exalt': [11], 'Det': [0, 7], 'Fall': [5]},
'Mars': {'Dom': [0, 7], 'Exalt': [9], 'Det': [6, 1], 'Fall': [3]},
'Jupiter':{'Dom': [8, 11], 'Exalt': [3], 'Det': [2, 5], 'Fall': [9]},
'Saturn': {'Dom': [9, 10], 'Exalt': [6], 'Det': [3, 4], 'Fall': [0]},
}
def get_dignity_score(planet, sign_idx):
"""
Calculate essential dignity score.
Simple weighted model:
Domicile = +5
Exaltation = +4
Detriment = -5
Fall = -4
Peregrine = 0
(Note: If sign is both Dom and Exalt (e.g. Mercury in Virgo), we sum them?
Tradition says Virgo is own domicile and exaltation. Usually considered very strong.
Let's sum them: 5+4=9.
Same for debilities? Mercury in Pisces is Detriment and Fall. -5 + -4 = -9.)
"""
rules = DIGNITIES[planet]
score = 0
if sign_idx in rules['Dom']: score += 5
if sign_idx in rules['Exalt']: score += 4
if sign_idx in rules['Det']: score -= 5
if sign_idx in rules['Fall']: score -= 4
return score
def get_dignity_label(planet, sign_idx):
rules = DIGNITIES[planet]
labels = []
if sign_idx in rules['Dom']: labels.append("Domicile")
if sign_idx in rules['Exalt']: labels.append("Exaltation")
if sign_idx in rules['Det']: labels.append("Detriment")
if sign_idx in rules['Fall']: labels.append("Fall")
if not labels: return "Peregrine"
return "/".join(labels)
def get_julian_day(date_str, time_str):
try:
dt = datetime.strptime(f"{date_str} {time_str}", "%Y-%m-%d %H:%M")
except ValueError:
# Handle cases with seconds or weird formats if any
try:
dt = datetime.strptime(f"{date_str} {time_str}", "%Y-%m-%d %H:%M:%S")
except:
# Fallback to noon
dt = datetime.strptime(date_str, "%Y-%m-%d")
dt = dt.replace(hour=12)
return swe.julday(dt.year, dt.month, dt.day, dt.hour + dt.minute/60.0)
def analyze_chart(name, date, time, field):
jd = get_julian_day(date, time)
row = {
'name': name,
'occupation': field,
}
total_score = 0
for planet, pid in PLANETS.items():
res = swe.calc_ut(jd, pid)[0][0]
sign_idx = int(res / 30) % 12
score = get_dignity_score(planet, sign_idx)
label = get_dignity_label(planet, sign_idx)
row[f'{planet}_Sign'] = SIGNS[sign_idx]
row[f'{planet}_Score'] = score
row[f'{planet}_Dignity'] = label
total_score += score
row['Total_Dignity'] = total_score
return row
def main():
print("Project 33: Planetary Dignities Analysis")
print("-" * 50)
data = []
for entry in CREATIVE_GENIUSES:
# Entry: (Name, Date, Time, Field, Achievement)
data.append(analyze_chart(entry[0], entry[1], entry[2], entry[3]))
df = pd.DataFrame(data)
# Save processed data
df.to_csv(OUTPUT_DIR / 'dignities_analyzed.csv', index=False)
print(f"Analyzed {len(df)} charts.")
# --- Statistical Analysis ---
# Hypotheses:
# 1. Scientists have strong Saturn or Mercury?
# 2. Artists have strong Venus?
# 3. Writers have strong Mercury?
# 4. Leaders/Filmmakers have strong Sun?
print("\n--- Average Dignity Scores by Occupation ---")
score_cols = [f'{p}_Score' for p in PLANETS.keys()]
grouped = df.groupby('occupation')[score_cols].mean()
print(grouped.round(2))
# Test Significance
print("\n--- Significance Tests (T-Test vs Rest of Population) ---")
results = []
occupations = df['occupation'].unique()
for occ in occupations:
occ_df = df[df['occupation'] == occ]
rest_df = df[df['occupation'] != occ]
print(f"\nOccupation: {occ.upper()} (n={len(occ_df)})")
for planet in PLANETS.keys():
col = f'{planet}_Score'
t_stat, p_val = stats.ttest_ind(occ_df[col], rest_df[col], equal_var=False)
diff = occ_df[col].mean() - rest_df[col].mean()
if p_val < 0.10: # Loose threshold for initial scan
sig = "**" if p_val < 0.05 else "*"
print(f" {planet}: Diff={diff:+.2f}, p={p_val:.4f} {sig}")
results.append({
'Occupation': occ,
'Planet': planet,
'Mean_Occ': occ_df[col].mean(),
'Mean_Rest': rest_df[col].mean(),
'Diff': diff,
'P_Value': p_val
})
# --- Visualization ---
create_plots(df, results)
# --- Generate Report ---
generate_report(df, results)
def create_plots(df, results_list):
# 1. Heatmap of Average Scores
score_cols = [f'{p}_Score' for p in PLANETS.keys()]
global_mean = df[score_cols].mean()
# Calculate difference from global mean for each occupation
grouped = df.groupby('occupation')[score_cols].mean()
diff_matrix = grouped - global_mean
plt.figure(figsize=(10, 6))
sns.heatmap(diff_matrix, cmap='RdBu_r', center=0, annot=True, fmt='.2f')
plt.title('Dignity Score Deviation by Occupation\n(Positive = Higher Dignity than Average)')
plt.tight_layout()
plt.savefig(OUTPUT_DIR / 'dignity_heatmap.png')
plt.close()
# 2. Distro of Total Dignity
plt.figure(figsize=(10, 6))
sns.kdeplot(data=df, x='Total_Dignity', hue='occupation', fill=False)
plt.title('Distribution of Total Chart Dignity by Occupation')
plt.savefig(OUTPUT_DIR / 'total_dignity_dist.png')
plt.close()
def generate_report(df, results):
with open(OUTPUT_DIR / 'RESULTS.md', 'w') as f:
f.write("# Project 33: Planetary Dignities & Character\n\n")
f.write("## Overview\n")
f.write(f"Analyzed **{len(df)}** charts of historical figures for Essential Dignity (Domicile/Exaltation vs Detriment/Fall).\n")
f.write("Does the 'strength' of a planet correlate with career choice?\n\n")
f.write("## Scoring System\n")
f.write("- **Domicile**: +5\n")
f.write("- **Exaltation**: +4\n")
f.write("- **Peregrine**: 0\n")
f.write("- **Detriment**: -5\n")
f.write("- **Fall**: -4\n\n")
f.write("## Significant Findings (P < 0.10)\n")
f.write("| Occupation | Planet | Mean Score | Vs Rest | Diff | P-Value |\n")
f.write("|---|---|---|---|---|---|\n")
results.sort(key=lambda x: x['P_Value'])
for r in results:
sig = "**" if r['P_Value'] < 0.05 else ""
f.write(f"| {r['Occupation']} | {r['Planet']} | {r['Mean_Occ']:.2f} | {r['Mean_Rest']:.2f} | {r['Diff']:+.2f} | {sig}{r['P_Value']:.4f}{sig} |\n")
f.write("\n## Interpretation\n")
# Add dynamic interpretation
strongest = results[0] if results else None
if strongest and strongest['P_Value'] < 0.05:
f.write(f"The most significant finding is **{strongest['Occupation']} and {strongest['Planet']}** (Diff: {strongest['Diff']:.2f}). ")
if strongest['Diff'] > 0:
f.write(f"This group has significantly stronger {strongest['Planet']} placement than average.\n")
else:
f.write(f"This group actually has WEAKER {strongest['Planet']} placement than average (Debility?).\n")
else:
f.write("No extremely strong correlations (p<0.05) were found, suggesting that classical dignity scores alone may not determine career field.\n")
if __name__ == "__main__":
main()