File size: 14,514 Bytes
1c93f85
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
"""
HERMES Voice Agent Integration
Using ElevenLabs Conversational AI for interactive astrology teaching
"""

import os
from typing import Dict, Optional, List
from elevenlabs import VoiceSettings
from elevenlabs.client import ElevenLabs

# Initialize ElevenLabs client
ELEVENLABS_API_KEY = os.getenv("ELEVENLABS_API_KEY", "")

class AstrologyVoiceAgent:
    """Voice-enabled teaching assistant for Hellenistic astrology"""
    
    def __init__(self, agent_type: str = "teaching"):
        """
        Initialize voice agent
        
        Args:
            agent_type: Either 'teaching' (Thrasyllus) or 'consultation' (Valentia)
        """
        self.client = ElevenLabs(api_key=ELEVENLABS_API_KEY) if ELEVENLABS_API_KEY else None
        self.agent_type = agent_type
        
        # Voice configurations
        self.voices = {
            "teaching": {
                "name": "Thrasyllus",
                "description": "Professional pedagogical voice for explanations and historical context",
                "voice_id": None,  # Will be set when voice is created
                "settings": VoiceSettings(
                    stability=0.7,
                    similarity_boost=0.8,
                    style=0.5,
                    use_speaker_boost=True
                )
            },
            "consultation": {
                "name": "Valentia", 
                "description": "Warm interpretive voice for delineations and practice",
                "voice_id": None,
                "settings": VoiceSettings(
                    stability=0.6,
                    similarity_boost=0.75,
                    style=0.6,
                    use_speaker_boost=True
                )
            }
        }
    
    def create_teaching_prompt(self) -> str:
        """System prompt for teaching mode (Thrasyllus)"""
        return """You are Thrasyllus, a Hellenistic astrology teacher in the tradition of the ancient Greek astrologers.

Your teaching style:
- Explain techniques step-by-step with historical context
- Cite specific ancient sources (Valens, Ptolemy, Dorotheus)
- Compare different authors' approaches when they disagree
- Use concrete examples from practice
- Encourage critical thinking about methods
- Speak clearly and pedagogically

When teaching:
1. Start with the fundamental concept
2. Provide historical background
3. Walk through calculation methods
4. Give practical examples
5. Note variations between authors

Always consider sect (day/night chart) in delineations.
Reference whole sign houses unless specified otherwise.
Distinguish between essential dignities and accidental dignities.

You are patient, thorough, and passionate about preserving traditional methods."""
    
    def create_consultation_prompt(self) -> str:
        """System prompt for consultation mode (Valentia)"""
        return """You are Valentia, a Hellenistic astrology consultant offering interpretive insights.

Your consultation style:
- Warm and empathetic while maintaining traditional rigor
- Focus on practical life applications
- Consider timing techniques (profections, ZR, firdaria)
- Emphasize planetary condition and sect
- Offer nuanced interpretations, not fatalistic pronouncements

When analyzing charts:
1. Assess overall chart condition (sect, dignities)
2. Identify the most important planets
3. Consider time-lord activations
4. Synthesize traditional significations with timing
5. Provide actionable guidance

Remember: "The stars incline, they do not compel."
Traditional astrology empowers through knowledge, not fear.

You speak conversationally but with depth and accuracy."""
    
    def explain_technique(self, technique: str, context: Dict = None) -> str:
        """
        Generate audio explanation of a Hellenistic technique
        
        Args:
            technique: Name of technique (e.g., "annual profections", "sect theory")
            context: Optional context (chart data, specific question)
        
        Returns:
            Text response (will be converted to audio)
        """
        explanations = {
            "annual_profections": """
            Let me walk you through annual profections, one of the most practical time-lord 
            techniques in Hellenistic astrology.
            
            The method comes from Valens, Book 4 of his Anthology. It's beautifully simple: 
            each year of life corresponds to one house, counted from the Ascendant.
            
            At birth, you're in your 1st house year. At age 1, you move to the 2nd house. 
            At 12, you return to the 1st house - your first profection return. This continues 
            throughout life.
            
            The ruler of the profected house becomes your "Lord of the Year." This planet 
            is activated for that entire year. Pay attention to transits to it, where it's 
            located in your natal chart, and its essential condition.
            
            For example, if you're 25 years old: 25 divided by 12 is 2 with remainder 1. 
            So you add 1, giving you the 2nd house. The ruler of your natal 2nd house is 
            your Lord of the Year at age 25.
            
            Paulus Alexandrinus adds that any planets in the profected house are also 
            activated that year. This is brilliant for prediction - it tells you which 
            parts of your chart are "turned on."
            """,
            
            "sect_theory": """
            Sect - or hairesis in Greek - is absolutely fundamental to Hellenistic astrology. 
            It distinguishes between day and night charts.
            
            Valens is quite explicit about this in Book 3. If the Sun is above the horizon 
            at birth, you have a diurnal or day chart. Sun below the horizon? Nocturnal or 
            night chart.
            
            This matters enormously for delineation. In a day chart, the benefics are 
            Jupiter and Saturn. The malefics are Mars (worse) and Saturn (less difficult). 
            In a night chart, it reverses: Venus and the Moon are the benefics, while 
            Saturn becomes the worse malefic.
            
            Ptolemy in the Tetrabiblos emphasizes that planets in their proper sect are 
            strengthened. A day chart with Jupiter angular is far more favorable than the 
            same position for Saturn.
            
            When you assess planetary condition, you must always consider sect. A planet 
            in its proper sect and well-dignified can mitigate many difficulties. One 
            contrary to sect and poorly placed? That's when you see the harder significations 
            manifest.
            """,
            
            "zodiacal_releasing": """
            Zodiacal Releasing is perhaps the most sophisticated time-lord system we have 
            from the Hellenistic tradition. Valens calls it "Loosing of the Bond."
            
            It uses a fixed planetary order through the signs: Cancer, Leo, Virgo, and so on. 
            Each sign has a set number of years - Cancer gives 25 years, Leo 19, Virgo 20, 
            and so forth.
            
            You usually start from the Lot of Fortune for general life circumstances, or the 
            Lot of Spirit for career and reputation. The sign where your lot falls begins 
            the first period.
            
            What makes this powerful is the layering - you have major periods, minor periods 
            within those, and even sub-minor periods. When multiple levels activate the same 
            sign, that's when significant events tend to manifest.
            
            Peak periods - when you shift into the sign opposite your Fortune or Spirit - 
            often mark major life turning points. Schmidt's work on this through Project 
            Hindsight really illuminated how to use it in practice.
            """
        }
        
        return explanations.get(
            technique.lower().replace(" ", "_"),
            f"I'd be happy to explain {technique}. This is a {self.agent_type} mode explanation."
        )
    
    def delineate_planetary_condition(self, planet_data: Dict) -> str:
        """
        Provide voice delineation of a planet's condition
        
        Args:
            planet_data: Dict with planet, sign, dignities, sect info
        """
        planet = planet_data.get("planet", "")
        sign = planet_data.get("sign", "")
        dignities = planet_data.get("dignities", [])
        sect = planet_data.get("sect", "day")
        
        response = f"Let's look at {planet} in {sign}. "
        
        if "Domicile" in str(dignities):
            response += f"{planet} is in its domicile here - this is its home sign. "
            response += "The planet has maximum autonomy and can express its nature freely. "
        elif "Exaltation" in str(dignities):
            response += f"{planet} is exalted in {sign}. "
            response += "This is an honored guest position, able to act at its best. "
        elif dignities:
            response += f"{planet} has some dignity through {', '.join(dignities)}. "
        else:
            response += f"{planet} is peregrine here - no essential dignity. "
            response += "The planet is like a stranger in a foreign land. "
        
        # Sect consideration
        if sect == "day":
            response += "In this day chart, "
        else:
            response += "In this night chart, "
        
        if planet in ["Jupiter", "Sun", "Saturn"] and sect == "day":
            response += f"{planet} is in its proper sect, which strengthens it. "
        elif planet in ["Venus", "Moon", "Mars"] and sect == "night":
            response += f"{planet} is in its proper sect, which strengthens it. "
        else:
            response += f"{planet} is contrary to sect, which weakens its expression. "
        
        return response
    
    def create_quiz_interaction(self, topic: str) -> List[Dict]:
        """
        Generate interactive quiz for learning
        
        Args:
            topic: Quiz topic (e.g., "bounds", "triplicities", "lots")
        
        Returns:
            List of Q&A pairs for interactive learning
        """
        quizzes = {
            "bounds": [
                {
                    "question": "In the Egyptian system, which planet rules the first 6 degrees of Aries?",
                    "answer": "Jupiter",
                    "explanation": "Jupiter rules 0-6 degrees of Aries in the Egyptian bounds. This is interesting because Mars rules Aries by domicile, but Jupiter - the greater benefic - gets the first term."
                },
                {
                    "question": "How many dignity points do bounds contribute to a planet's essential dignity?",
                    "answer": "2 points",
                    "explanation": "Bounds give 2 points in the 5-point dignity system. It's less than domicile (5) or exaltation (4), but more than decan (1)."
                }
            ],
            "triplicities": [
                {
                    "question": "Which planet is the day ruler of the fire triplicity?",
                    "answer": "The Sun",
                    "explanation": "The Sun rules fire by day, Jupiter by night, with Saturn as the participating ruler. This makes sense - fire is solar in nature."
                },
                {
                    "question": "What are the three modes of triplicity rulership?",
                    "answer": "Day ruler, night ruler, and participating ruler",
                    "explanation": "Triplicities have three rulers: one for day charts, one for night charts, and a participating ruler that works in both. This tri-partite system is unique to triplicities."
                }
            ]
        }
        
        return quizzes.get(topic, [
            {
                "question": f"Tell me about {topic} in Hellenistic astrology",
                "answer": "This is a fundamental concept worth exploring.",
                "explanation": f"Let's dive deep into {topic} together."
            }
        ])
    
    def generate_audio(self, text: str, voice_type: str = None) -> bytes:
        """
        Convert text to speech using ElevenLabs
        
        Args:
            text: Text to convert
            voice_type: Override default voice type
        
        Returns:
            Audio bytes
        """
        if not self.client:
            return b""  # Return empty bytes if no API key
        
        voice_config = self.voices.get(voice_type or self.agent_type)
        
        # This will be implemented with actual ElevenLabs API calls
        # For now, placeholder
        try:
            audio = self.client.generate(
                text=text,
                voice=voice_config["voice_id"],
                model="eleven_monolingual_v1",
                voice_settings=voice_config["settings"]
            )
            return audio
        except Exception as e:
            print(f"Voice generation error: {e}")
            return b""


# Example usage functions
def demo_teaching_session():
    """Demonstrate teaching mode"""
    agent = AstrologyVoiceAgent(agent_type="teaching")
    
    # Explain annual profections
    explanation = agent.explain_technique("annual profections")
    print("Teaching Explanation:")
    print(explanation)
    
    # Quiz on bounds
    quiz = agent.create_quiz_interaction("bounds")
    print("\nInteractive Quiz:")
    for q in quiz:
        print(f"Q: {q['question']}")
        print(f"A: {q['answer']}")
        print(f"Explanation: {q['explanation']}\n")

def demo_consultation_session():
    """Demonstrate consultation mode"""
    agent = AstrologyVoiceAgent(agent_type="consultation")
    
    # Delineate a planetary condition
    planet_data = {
        "planet": "Jupiter",
        "sign": "Cancer",
        "dignities": ["Exaltation (+4)", "Triplicity Ruler - Day (+3)"],
        "sect": "day"
    }
    
    delineation = agent.delineate_planetary_condition(planet_data)
    print("Consultation Delineation:")
    print(delineation)


if __name__ == "__main__":
    print("HERMES Voice Agent Demo\n")
    print("=" * 50)
    print("\n1. Teaching Mode (Thrasyllus):\n")
    demo_teaching_session()
    print("\n" + "=" * 50)
    print("\n2. Consultation Mode (Valentia):\n")
    demo_consultation_session()