astrolith 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0d189aaf9ea9a6f0c78bd37ef2db4d5c466ed2dfb6ddaa167d57a7a66ee1704d
4
+ data.tar.gz: fd4a37d35802c4c6646dc37e805921d0095d3802c7e162fcce553ca94ef5468b
5
+ SHA512:
6
+ metadata.gz: b66c0604a9c413c15fefd9054a93e97baab4ed63940fdc20ce7bcc50d2ad0c53c3fb727d79a9a4ba617ddead19e1461a5a19912e0cdbda69dafa16238bc6f2cb
7
+ data.tar.gz: 61f675bb33986226be7385fa20f6cd0cc58574add760ade7bc91f5ed363f7a127f642ed1fa596c0829a0f898e1db90d0b91e3b1f427f0627f854323f6a5580e4
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Go-pye
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,317 @@
1
+ # Astrolith
2
+
3
+ A Ruby port of the [immanuel-python](https://github.com/theriftlab/immanuel-python) astrology library, functioning as a decoupled data calculation engine with structured JSON output.
4
+
5
+ [![Tests](https://img.shields.io/badge/tests-passing-brightgreen)]()
6
+ [![Ruby](https://img.shields.io/badge/ruby-3.1%2B-red)]()
7
+
8
+ ## Features
9
+
10
+ - **Multiple Chart Types**: Natal, Solar Return, Progressed, Composite, Transit
11
+ - **Comprehensive Data**: Planets, houses, aspects, dignities, decans, moon phase, chart shape
12
+ - **Swiss Ephemeris**: High-precision astronomical calculations using built-in Moshier ephemeris
13
+ - **No External Files Required**: Uses analytical ephemeris (precision: ~0.1 arc seconds for planets, ~3" for Moon)
14
+ - **JSON Output**: Structured data output for easy integration
15
+ - **Flexible Input**: Multiple formats for coordinates, datetime, and timezone
16
+ - **7 House Systems**: Placidus, Koch, Whole Sign, Equal, Porphyrius, Regiomontanus, Campanus
17
+
18
+ ## Installation
19
+
20
+ Add this line to your application's Gemfile:
21
+
22
+ ```ruby
23
+ gem 'astrolith'
24
+ ```
25
+
26
+ And then execute:
27
+
28
+ ```bash
29
+ bundle install
30
+ ```
31
+
32
+ Or install it yourself as:
33
+
34
+ ```bash
35
+ gem install astrolith
36
+ ```
37
+
38
+ ## Quick Start
39
+
40
+ ```ruby
41
+ require 'ruby_chart_engine'
42
+
43
+ # Create a natal chart
44
+ chart = RubyChartEngine::Charts::Natal.new(
45
+ datetime: '1990-05-15T14:30:00',
46
+ latitude: 40.7128,
47
+ longitude: -74.0060,
48
+ timezone: 'America/New_York',
49
+ house_system: :placidus
50
+ )
51
+
52
+ # Access chart data
53
+ puts chart.planets[:sun][:sign][:name] # => "Taurus"
54
+ puts chart.planets[:sun][:house] # => 10
55
+ puts chart.moon_phase # => "Waxing Gibbous"
56
+ puts chart.chart_shape # => "bowl"
57
+
58
+ # Export to JSON
59
+ json = chart.to_json
60
+ puts json
61
+ ```
62
+
63
+ ## Usage Examples
64
+
65
+ ### Natal Chart
66
+
67
+ ```ruby
68
+ chart = RubyChartEngine::Charts::Natal.new(
69
+ datetime: '1990-05-15T14:30:00',
70
+ latitude: 40.7128,
71
+ longitude: -74.0060,
72
+ timezone: 'America/New_York'
73
+ )
74
+
75
+ # Access planetary data
76
+ sun = chart.planets[:sun]
77
+ # => {
78
+ # longitude: 54.123,
79
+ # sign_longitude: 24.123,
80
+ # sign: { name: "Taurus", element: "Earth", modality: "Fixed" },
81
+ # house: 10,
82
+ # decan: 3,
83
+ # movement: "direct",
84
+ # dignities: { domicile: false, exaltation: false, ... }
85
+ # }
86
+
87
+ # Check aspects
88
+ chart.aspects.each do |aspect|
89
+ puts "#{aspect[:planet1]} #{aspect[:type]} #{aspect[:planet2]} (orb: #{aspect[:orb]}�)"
90
+ end
91
+ ```
92
+
93
+ ### Solar Return
94
+
95
+ ```ruby
96
+ solar_return = RubyChartEngine::Charts::SolarReturn.new(
97
+ natal_datetime: '1990-05-15T14:30:00',
98
+ return_year: 2024,
99
+ latitude: 40.7128,
100
+ longitude: -74.0060,
101
+ timezone: 'America/New_York'
102
+ )
103
+ ```
104
+
105
+ ### Progressed Chart
106
+
107
+ ```ruby
108
+ progressed = RubyChartEngine::Charts::Progressed.new(
109
+ natal_datetime: '1990-05-15T14:30:00',
110
+ progression_date: '2024-03-15',
111
+ latitude: 40.7128,
112
+ longitude: -74.0060,
113
+ timezone: 'America/New_York'
114
+ )
115
+ ```
116
+
117
+ ### Composite Chart
118
+
119
+ ```ruby
120
+ composite = RubyChartEngine::Charts::Composite.new(
121
+ chart1_params: {
122
+ datetime: '1990-05-15T14:30:00',
123
+ latitude: 40.7128,
124
+ longitude: -74.0060,
125
+ timezone: 'America/New_York'
126
+ },
127
+ chart2_params: {
128
+ datetime: '1992-08-20T10:00:00',
129
+ latitude: 34.0522,
130
+ longitude: -118.2437,
131
+ timezone: 'America/Los_Angeles'
132
+ }
133
+ )
134
+ ```
135
+
136
+ ### Transit Chart
137
+
138
+ ```ruby
139
+ natal = RubyChartEngine::Charts::Natal.new(
140
+ datetime: '1990-05-15T14:30:00',
141
+ latitude: 40.7128,
142
+ longitude: -74.0060,
143
+ timezone: 'America/New_York'
144
+ )
145
+
146
+ transit = RubyChartEngine::Charts::Transit.new(
147
+ natal_chart_params: natal,
148
+ transit_datetime: Time.now.iso8601
149
+ )
150
+
151
+ # View transit aspects
152
+ transit.transit_aspects.each do |aspect|
153
+ puts "#{aspect[:planet1]} #{aspect[:type]} #{aspect[:planet2]}"
154
+ end
155
+ ```
156
+
157
+ ## Input Formats
158
+
159
+ ### Coordinates
160
+
161
+ ```ruby
162
+ # Decimal degrees
163
+ latitude: 40.7128, longitude: -74.0060
164
+
165
+ # Text format (degrees and minutes)
166
+ latitude: '40n43', longitude: '74w00'
167
+
168
+ # Mixed formats work too
169
+ latitude: 40.7128, longitude: '74w00'
170
+ ```
171
+
172
+ ### DateTime
173
+
174
+ ```ruby
175
+ # ISO 8601 string
176
+ datetime: '1990-05-15T14:30:00'
177
+
178
+ # Hash
179
+ datetime: { year: 1990, month: 5, day: 15, hour: 14, minute: 30 }
180
+
181
+ # Ruby DateTime object
182
+ datetime: DateTime.new(1990, 5, 15, 14, 30, 0)
183
+ ```
184
+
185
+ ### House Systems
186
+
187
+ ```ruby
188
+ # Available systems:
189
+ house_system: :placidus # Default
190
+ house_system: :koch
191
+ house_system: :whole_sign
192
+ house_system: :equal
193
+ house_system: :porphyrius
194
+ house_system: :regiomontanus
195
+ house_system: :campanus
196
+ ```
197
+
198
+ ## Output Structure
199
+
200
+ ```json
201
+ {
202
+ "metadata": {
203
+ "datetime": "1990-05-15T14:30:00+00:00",
204
+ "julian_day": 2448025.1041666665,
205
+ "latitude": 40.7128,
206
+ "longitude": -74.006,
207
+ "timezone": "America/New_York",
208
+ "house_system": "placidus"
209
+ },
210
+ "planets": {
211
+ "sun": {
212
+ "longitude": 54.123,
213
+ "sign_longitude": 24.123,
214
+ "sign": { "name": "Taurus", "element": "Earth", "modality": "Fixed" },
215
+ "house": 10,
216
+ "decan": 3,
217
+ "movement": "direct",
218
+ "dignities": { "domicile": false, "exaltation": false, ... }
219
+ }
220
+ },
221
+ "houses": { "cusps": [...] },
222
+ "angles": { "ascendant": {...}, "midheaven": {...}, ... },
223
+ "aspects": [...],
224
+ "moon_phase": "Waxing Gibbous",
225
+ "chart_shape": "bowl"
226
+ }
227
+ ```
228
+
229
+ ## Celestial Objects
230
+
231
+ **Planets**: Sun, Moon, Mercury, Venus, Mars, Jupiter, Saturn, Uranus, Neptune, Pluto
232
+
233
+ **Points**: North Node, South Node *(Note: Chiron is not available when using the built-in Moshier ephemeris)*
234
+
235
+ **Angles**: Ascendant, Midheaven, Descendant, Imum Coeli
236
+
237
+ ## Aspects
238
+
239
+ Supported aspects with configurable orbs:
240
+ - Conjunction (0�)
241
+ - Opposition (180�)
242
+ - Trine (120�)
243
+ - Square (90�)
244
+ - Sextile (60�)
245
+ - Quincunx (150�)
246
+ - Semi-Sextile (30�)
247
+ - Semi-Square (45�)
248
+ - Sesquiquadrate (135�)
249
+
250
+ ## Testing
251
+
252
+ Run the test suite:
253
+
254
+ ```bash
255
+ bundle exec rspec
256
+
257
+ # With documentation format
258
+ bundle exec rspec --format documentation
259
+
260
+ # Run specific test file
261
+ bundle exec rspec spec/charts/natal_spec.rb
262
+ ```
263
+
264
+ ## Dependencies
265
+
266
+ - **swe4r**: Swiss Ephemeris C extension for astronomical calculations
267
+ - **ephemeris**: Higher-level astrological logic built on swe4r
268
+ - **daru**: Data Analysis in Ruby for data structuring
269
+ - **tzinfo**: Timezone handling
270
+
271
+ ### Optional Visualization
272
+
273
+ - **apexcharts**: For analytical charts
274
+ - **prawn**: For PDF and geometric drawing (chart wheels)
275
+
276
+ ## Documentation
277
+
278
+ - [Quick Start Guide](docs/QUICK_START.md) - Get started quickly with common use cases
279
+ - [Project Overview](docs/PROJECT_OVERVIEW.md) - Architecture and implementation details
280
+ - [Implementation Summary](docs/IMPLEMENTATION_SUMMARY.md) - Technical implementation notes
281
+
282
+ ## Development
283
+
284
+ After checking out the repo:
285
+
286
+ ```bash
287
+ # Install dependencies
288
+ bundle install
289
+
290
+ # Run tests
291
+ bundle exec rspec
292
+
293
+ # Run console for experimentation
294
+ bundle console
295
+ ```
296
+
297
+ ## Contributing
298
+
299
+ 1. Fork it
300
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
301
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
302
+ 4. Push to the branch (`git push origin my-new-feature`)
303
+ 5. Create new Pull Request
304
+
305
+ ## License
306
+
307
+ MIT License
308
+
309
+ ## Acknowledgments
310
+
311
+ This library is a Ruby port of [immanuel-python](https://github.com/theriftlab/immanuel-python), maintaining compatibility with its JSON output structure while leveraging Ruby's ecosystem.
312
+
313
+ ## Notes
314
+
315
+ - This implementation uses the **built-in Moshier ephemeris** (analytical calculations) which provides excellent precision without requiring external ephemeris files
316
+ - Chiron is not available with the Moshier ephemeris (would require external `.se1` files)
317
+ - For production use with extended date ranges or maximum precision, consider using the full Swiss Ephemeris with external data files
data/astrolith.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ Gem::Specification.new do |spec|
2
+ spec.name = "astrolith"
3
+ spec.version = "0.1.0"
4
+ spec.authors = ["Go-pye"]
5
+ spec.email = ["Go-pye@users.noreply.github.com"]
6
+
7
+ spec.summary = "A Ruby port of immanuel-python astrology library"
8
+ spec.description = "Astrolith is a decoupled astrological data calculation engine that generates structured JSON output. It supports multiple chart types including Natal, Solar Return, Progressed, Composite, and Transit charts."
9
+ spec.homepage = "https://github.com/Go-pye/astrolith"
10
+ spec.license = "MIT"
11
+
12
+ spec.required_ruby_version = ">= 2.7.0"
13
+
14
+ spec.files = Dir.glob("{lib,examples}/**/*") + %w[
15
+ README.md
16
+ LICENSE
17
+ astrolith.gemspec
18
+ ]
19
+
20
+ spec.require_paths = ["lib"]
21
+
22
+ # Core dependencies
23
+ spec.add_dependency "swe4r", "~> 0.0.2"
24
+ spec.add_dependency "ephemeris", "~> 0.1"
25
+ spec.add_dependency "daru", "~> 0.3"
26
+ spec.add_dependency "tzinfo", "~> 2.0"
27
+
28
+ # Optional visualization dependencies
29
+ spec.add_dependency "apexcharts", "~> 0.1"
30
+ spec.add_dependency "prawn", "~> 2.4"
31
+
32
+ # Development dependencies
33
+ spec.add_development_dependency "rspec", "~> 3.12"
34
+ spec.add_development_dependency "pry", "~> 0.14"
35
+ end
@@ -0,0 +1,154 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require_relative '../lib/ruby_chart_engine'
4
+
5
+ # Example 1: Basic Natal Chart
6
+ puts "=" * 80
7
+ puts "Example 1: Basic Natal Chart"
8
+ puts "=" * 80
9
+
10
+ natal_chart = RubyChartEngine::Charts::Natal.new(
11
+ datetime: '1990-05-15T14:30:00',
12
+ latitude: 40.7128,
13
+ longitude: -74.0060,
14
+ timezone: 'America/New_York',
15
+ house_system: :placidus
16
+ )
17
+
18
+ puts "\nSun Position:"
19
+ sun = natal_chart.planets[:sun]
20
+ puts " Longitude: #{sun[:longitude].round(2)}°"
21
+ puts " Sign: #{sun[:sign][:name]}"
22
+ puts " House: #{sun[:house]}"
23
+ puts " Movement: #{sun[:movement]}"
24
+
25
+ puts "\nMoon Position:"
26
+ moon = natal_chart.planets[:moon]
27
+ puts " Longitude: #{moon[:longitude].round(2)}°"
28
+ puts " Sign: #{moon[:sign][:name]}"
29
+ puts " House: #{moon[:house]}"
30
+
31
+ puts "\nMoon Phase: #{natal_chart.moon_phase}"
32
+ puts "Chart Shape: #{natal_chart.chart_shape}"
33
+
34
+ puts "\nAspects (first 3):"
35
+ natal_chart.aspects.take(3).each do |aspect|
36
+ puts " #{aspect[:planet1]} #{aspect[:type]} #{aspect[:planet2]} (orb: #{aspect[:orb].round(2)}°)"
37
+ end
38
+
39
+ # Example 2: Solar Return Chart
40
+ puts "\n" + "=" * 80
41
+ puts "Example 2: Solar Return Chart for 2024"
42
+ puts "=" * 80
43
+
44
+ solar_return = RubyChartEngine::Charts::SolarReturn.new(
45
+ natal_datetime: '1990-05-15T14:30:00',
46
+ return_year: 2024,
47
+ latitude: 40.7128,
48
+ longitude: -74.0060,
49
+ timezone: 'America/New_York'
50
+ )
51
+
52
+ puts "\nSolar Return Date: #{solar_return.datetime.datetime}"
53
+ puts "Natal Sun Longitude: #{solar_return.natal_sun_longitude.round(2)}°"
54
+ puts "Solar Return Sun: #{solar_return.planets[:sun][:longitude].round(2)}°"
55
+
56
+ # Example 3: Composite Chart
57
+ puts "\n" + "=" * 80
58
+ puts "Example 3: Composite Chart"
59
+ puts "=" * 80
60
+
61
+ composite = RubyChartEngine::Charts::Composite.new(
62
+ chart1_params: {
63
+ datetime: '1990-05-15T14:30:00',
64
+ latitude: 40.7128,
65
+ longitude: -74.0060,
66
+ timezone: 'America/New_York'
67
+ },
68
+ chart2_params: {
69
+ datetime: '1992-08-20T10:00:00',
70
+ latitude: 34.0522,
71
+ longitude: -118.2437,
72
+ timezone: 'America/Los_Angeles'
73
+ }
74
+ )
75
+
76
+ puts "\nComposite Sun:"
77
+ comp_sun = composite.planets[:sun]
78
+ puts " Longitude: #{comp_sun[:longitude].round(2)}°"
79
+ puts " Sign: #{comp_sun[:sign][:name]}"
80
+ puts " House: #{comp_sun[:house]}"
81
+
82
+ # Example 4: Transit Chart
83
+ puts "\n" + "=" * 80
84
+ puts "Example 4: Transit Chart"
85
+ puts "=" * 80
86
+
87
+ transit = RubyChartEngine::Charts::Transit.new(
88
+ natal_chart_params: natal_chart,
89
+ transit_datetime: '2024-03-15T12:00:00'
90
+ )
91
+
92
+ puts "\nTransit Aspects to Natal Chart (first 5):"
93
+ transit.transit_aspects.take(5).each do |aspect|
94
+ puts " Transit #{aspect[:planet1]} #{aspect[:type]} Natal #{aspect[:planet2]} (orb: #{aspect[:orb].round(2)}°)"
95
+ end
96
+
97
+ # Example 5: JSON Export
98
+ puts "\n" + "=" * 80
99
+ puts "Example 5: JSON Export"
100
+ puts "=" * 80
101
+
102
+ puts "\nNatal Chart as JSON (pretty):"
103
+ json = RubyChartEngine::Serializers::JsonSerializer.serialize_pretty(natal_chart)
104
+ puts json[0..500] + "..."
105
+
106
+ puts "\nChart with filtered output (metadata and planets only):"
107
+ filtered_json = RubyChartEngine::Serializers::JsonSerializer.serialize_with_options(
108
+ natal_chart,
109
+ include: [:metadata, :planets],
110
+ pretty: true
111
+ )
112
+ puts filtered_json[0..300] + "..."
113
+
114
+ # Example 6: Different Coordinate Formats
115
+ puts "\n" + "=" * 80
116
+ puts "Example 6: Different Coordinate Formats"
117
+ puts "=" * 80
118
+
119
+ # Standard text format
120
+ chart_text = RubyChartEngine::Charts::Natal.new(
121
+ datetime: '1990-05-15T14:30:00',
122
+ latitude: '40n43',
123
+ longitude: '74w00',
124
+ timezone: 'America/New_York'
125
+ )
126
+
127
+ puts "\nChart with text coordinates:"
128
+ puts " Latitude: #{chart_text.coordinates.latitude.round(4)}°"
129
+ puts " Longitude: #{chart_text.coordinates.longitude.round(4)}°"
130
+
131
+ # Example 7: Different House Systems
132
+ puts "\n" + "=" * 80
133
+ puts "Example 7: Different House Systems"
134
+ puts "=" * 80
135
+
136
+ house_systems = [:placidus, :koch, :whole_sign, :equal]
137
+
138
+ house_systems.each do |system|
139
+ chart = RubyChartEngine::Charts::Natal.new(
140
+ datetime: '1990-05-15T14:30:00',
141
+ latitude: 40.7128,
142
+ longitude: -74.0060,
143
+ timezone: 'America/New_York',
144
+ house_system: system
145
+ )
146
+
147
+ puts "\n#{system.to_s.capitalize} House System:"
148
+ puts " 1st House Cusp: #{chart.houses[:cusps][0].round(2)}°"
149
+ puts " 10th House Cusp: #{chart.houses[:cusps][9].round(2)}°"
150
+ end
151
+
152
+ puts "\n" + "=" * 80
153
+ puts "Examples Complete!"
154
+ puts "=" * 80
@@ -0,0 +1,88 @@
1
+ module RubyChartEngine
2
+ module Calculations
3
+ class Aspects
4
+ # Standard aspect definitions with orbs
5
+ ASPECT_TYPES = {
6
+ conjunction: { angle: 0, orb: 8 },
7
+ opposition: { angle: 180, orb: 8 },
8
+ trine: { angle: 120, orb: 8 },
9
+ square: { angle: 90, orb: 8 },
10
+ sextile: { angle: 60, orb: 6 },
11
+ quincunx: { angle: 150, orb: 3 },
12
+ semi_sextile: { angle: 30, orb: 3 },
13
+ semi_square: { angle: 45, orb: 3 },
14
+ sesquiquadrate: { angle: 135, orb: 3 }
15
+ }.freeze
16
+
17
+ # Calculate all aspects between two sets of planets
18
+ def self.calculate(planets1, planets2 = nil)
19
+ planets2 ||= planets1
20
+ aspects = []
21
+
22
+ planets1.each do |name1, data1|
23
+ planets2.each do |name2, data2|
24
+ # Skip if comparing planet to itself
25
+ next if planets2.equal?(planets1) && name1 == name2
26
+
27
+ # Skip if we've already calculated this pair (for same chart)
28
+ next if planets2.equal?(planets1) && planets1.keys.index(name1) >= planets1.keys.index(name2)
29
+
30
+ aspect = find_aspect(
31
+ data1[:longitude],
32
+ data2[:longitude],
33
+ name1,
34
+ name2
35
+ )
36
+
37
+ aspects << aspect if aspect
38
+ end
39
+ end
40
+
41
+ aspects
42
+ end
43
+
44
+ # Find aspect between two longitudes
45
+ def self.find_aspect(lon1, lon2, name1, name2)
46
+ angle = calculate_angle(lon1, lon2)
47
+
48
+ ASPECT_TYPES.each do |type, config|
49
+ diff = (angle - config[:angle]).abs
50
+
51
+ if diff <= config[:orb]
52
+ return {
53
+ type: type,
54
+ planet1: name1,
55
+ planet2: name2,
56
+ angle: config[:angle],
57
+ orb: diff,
58
+ applying: false # TODO: Calculate if aspect is applying or separating
59
+ }
60
+ end
61
+ end
62
+
63
+ nil
64
+ end
65
+
66
+ # Calculate the angle between two longitudes
67
+ def self.calculate_angle(lon1, lon2)
68
+ diff = (lon2 - lon1).abs
69
+ diff = 360 - diff if diff > 180
70
+ diff
71
+ end
72
+
73
+ # Determine if aspect is applying or separating based on speeds
74
+ def self.applying?(planet1_data, planet2_data, aspect_angle)
75
+ # Get speeds
76
+ speed1 = planet1_data[:speed_longitude]
77
+ speed2 = planet2_data[:speed_longitude]
78
+
79
+ # If speeds are moving toward exact aspect, it's applying
80
+ # This is a simplified calculation
81
+ relative_speed = speed2 - speed1
82
+
83
+ # If faster planet is behind slower planet, aspect is applying
84
+ relative_speed > 0
85
+ end
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,71 @@
1
+ module RubyChartEngine
2
+ module Calculations
3
+ class Dignities
4
+ # Essential dignities table
5
+ RULERSHIPS = {
6
+ aries: { ruler: :mars, detriment: :venus, exaltation: :sun, fall: :saturn },
7
+ taurus: { ruler: :venus, detriment: :mars, exaltation: :moon, fall: nil },
8
+ gemini: { ruler: :mercury, detriment: :jupiter, exaltation: nil, fall: nil },
9
+ cancer: { ruler: :moon, detriment: :saturn, exaltation: :jupiter, fall: :mars },
10
+ leo: { ruler: :sun, detriment: :saturn, exaltation: nil, fall: nil },
11
+ virgo: { ruler: :mercury, detriment: :jupiter, exaltation: :mercury, fall: :venus },
12
+ libra: { ruler: :venus, detriment: :mars, exaltation: :saturn, fall: :sun },
13
+ scorpio: { ruler: :mars, detriment: :venus, exaltation: nil, fall: :moon },
14
+ sagittarius: { ruler: :jupiter, detriment: :mercury, exaltation: nil, fall: nil },
15
+ capricorn: { ruler: :saturn, detriment: :moon, exaltation: :mars, fall: :jupiter },
16
+ aquarius: { ruler: :saturn, detriment: :sun, exaltation: nil, fall: nil },
17
+ pisces: { ruler: :jupiter, detriment: :mercury, exaltation: :venus, fall: :mercury }
18
+ }.freeze
19
+
20
+ # Modern rulerships (for outer planets)
21
+ MODERN_RULERSHIPS = {
22
+ scorpio: :pluto,
23
+ aquarius: :uranus,
24
+ pisces: :neptune
25
+ }.freeze
26
+
27
+ # Calculate dignities for a planet in a sign
28
+ def self.calculate(planet_name, sign_index)
29
+ sign_name = SIGNS[sign_index][:name].downcase.to_sym
30
+ dignities_for_sign = RULERSHIPS[sign_name]
31
+
32
+ return default_dignities unless dignities_for_sign
33
+
34
+ {
35
+ domicile: dignities_for_sign[:ruler] == planet_name,
36
+ detriment: dignities_for_sign[:detriment] == planet_name,
37
+ exaltation: dignities_for_sign[:exaltation] == planet_name,
38
+ fall: dignities_for_sign[:fall] == planet_name,
39
+ peregrine: !has_any_dignity?(planet_name, dignities_for_sign)
40
+ }
41
+ end
42
+
43
+ # Calculate dignity score
44
+ def self.score(dignities)
45
+ score = 0
46
+ score += 5 if dignities[:domicile]
47
+ score += 4 if dignities[:exaltation]
48
+ score -= 5 if dignities[:detriment]
49
+ score -= 4 if dignities[:fall]
50
+ score
51
+ end
52
+
53
+ private
54
+
55
+ def self.has_any_dignity?(planet_name, dignities_for_sign)
56
+ dignities_for_sign[:ruler] == planet_name ||
57
+ dignities_for_sign[:exaltation] == planet_name
58
+ end
59
+
60
+ def self.default_dignities
61
+ {
62
+ domicile: false,
63
+ detriment: false,
64
+ exaltation: false,
65
+ fall: false,
66
+ peregrine: true
67
+ }
68
+ end
69
+ end
70
+ end
71
+ end