Module: AstroChart::Progressions

Defined in:
lib/astro_chart/progressions.rb

Overview

Secondary progressions (二次推運): "a day for a year".

For a target date N years after birth, the progressed chart is the sky N days after birth: jd_prog = jd_natal + years_elapsed, where years_elapsed = (jd_target - jd_natal) / 365.2425.

Typical usage with a Chart#generate result:

AstroChart::Progressions.secondary(natal_chart, "2026-07-24")

Constant Summary collapse

DAYS_PER_YEAR =

Mean Gregorian year length in days (matches the calendar's 400-year cycle).

365.2425

Class Method Summary collapse

Class Method Details

.secondary(natal_chart, target_date, orb_limit: 1.0) ⇒ Object

Secondary-progressed positions for a natal chart at a target date.

natal_chart: a Chart#generate result hash (its "input" block supplies birth date/time/timezone, so jd_natal is reconstructed exactly). target_date: "YYYY-MM-DD". The target moment is taken at the same local birth time + timezone, so whole calendar years elapse cleanly. orb_limit: keep only progressed-to-natal aspects with orb <= limit (default 1.0 — progressions move slowly, only exact contacts matter).

Returns:

{
"progressed_jd" => 2447926.72,
"years_elapsed" => 30.0,
"planets" => [ { "planet" => "太陽", "zodiac" => "獅子座",
                 "degree" => 9.87, "total_degree" => 129.87,
                 "natal_house" => 3 }, ... 12 entries ],
"aspects_to_natal" => [ { "progressed_planet" => "月亮",
                          "natal_planet" => "金星",
                          "aspect_type" => "三分相", "orb" => 0.4 }, ... ]
}

aspects_to_natal are sorted by orb (tightest first).



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
# File 'lib/astro_chart/progressions.rb', line 40

def self.secondary(natal_chart, target_date, orb_limit: 1.0)
  input = natal_chart&.dig("input")
  if input.nil? || input["birth_date"].nil? || input["birth_time"].nil? || input["timezone"].nil?
    raise ArgumentError, "chart has no input data (birth_date/birth_time/timezone required)"
  end

  jd_natal  = TimeConversion.to_julian_day(input["birth_date"], input["birth_time"], input["timezone"])
  jd_target = TimeConversion.to_julian_day(target_date, input["birth_time"], input["timezone"])

  years_elapsed = (jd_target - jd_natal) / DAYS_PER_YEAR
  jd_prog = jd_natal + years_elapsed # a day for a year

  progressed_positions = Planets.calculate_positions(jd_prog)
  natal_positions      = Synastry.positions_from_chart(natal_chart)
  natal_cusps          = Synastry.cusps_from_chart(natal_chart)

  {
    "progressed_jd" => jd_prog,
    "years_elapsed" => years_elapsed.round(2),
    "planets" => Transits.planet_details(progressed_positions, natal_cusps),
    "aspects_to_natal" => Transits.aspects_to_natal(
      progressed_positions, natal_positions,
      orb_limit: orb_limit,
      keys: %w[progressed_planet natal_planet]
    ),
  }
end