feat(tahuantinsuyu): "Guardar como…" en Tránsito y Progresada
Extiende el patrón de F4 a dos módulos más:
- **Tránsito**: nuevo `Control::Action "💾 Guardar tránsito como
carta libre"`. Captura el momento actual (UTC `now()`) anclado
a las coordenadas del natal. Label `{natal} transito · YYYY-MM-DD
HH:MM UTC`. Útil para "qué pasaba en el cielo de Pedro ahora
mismo, pegado como carta".
- **Progresada secundaria**: análogo, sufijo `prog-{N}a`. El
birth_data del Chart resultante es REAL (natal_instant + N días
simbólicos × 86400 s), así que cuando se computa de nuevo como
natal produce las posiciones progresadas correctas. El usuario
edita el slider, click → la carta queda guardada como libre
para después persistir.
Backend:
- Dos funciones nuevas en `tahuantinsuyu-engine`:
`compute_transit_chart(chart)` y
`compute_progression_chart(chart, age)`. Reusan
`parse_iso8601_components` (introducido en el commit del PR).
- En el shell: nuevo helper `insert_derived_free_chart(source,
birth, label)` que el callsite de PR ahora reusa (refactor
pequeño).
Sobre solar_arc y primary_directions:
- NO se agrega el botón. SA y PD son transformaciones matemáticas
puras — un Chart natal computado en el "momento dirigido" daría
posiciones distintas a las dirigidas (porque SA rota uniforme y
PD es función de RA, no de longitud eclíptica). Para guardarlas
haría falta extender `Chart` con un kind
`Derived { source, transform, params }` que el engine sepa
rehidratar al render. TODO en otra fase.
Tests: 10 verdes (sin cambios en los paths probados).
This commit is contained in:
@@ -1093,6 +1093,74 @@ pub fn compute_planetary_return_chart(
|
||||
Ok((stored, label))
|
||||
}
|
||||
|
||||
/// Computa la **carta de tránsito** del momento actual sobre las
|
||||
/// coordenadas del natal — birth_data = "ahora" UTC, mismo
|
||||
/// observer/lat/lon/TZ que el natal. Útil para snapshot del cielo
|
||||
/// en este instante anclado al lugar de nacimiento del sujeto.
|
||||
pub fn compute_transit_chart(
|
||||
chart: &Chart,
|
||||
) -> Result<(tahuantinsuyu_model::StoredBirthData, String), EngineError> {
|
||||
let now_iso = ESInstant::now().utc().to_iso8601();
|
||||
let (year, month, day, hour, minute, second) =
|
||||
parse_iso8601_components(&now_iso).ok_or_else(|| {
|
||||
EngineError::Eternal(format!("iso8601 inválido para now(): {}", now_iso))
|
||||
})?;
|
||||
let stored = tahuantinsuyu_model::StoredBirthData {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
tz_offset_minutes: chart.birth_data.tz_offset_minutes,
|
||||
latitude_deg: chart.birth_data.latitude_deg,
|
||||
longitude_deg: chart.birth_data.longitude_deg,
|
||||
altitude_m: chart.birth_data.altitude_m,
|
||||
time_certainty: Default::default(),
|
||||
subject_name: chart.birth_data.subject_name.clone(),
|
||||
birthplace_label: chart.birth_data.birthplace_label.clone(),
|
||||
};
|
||||
let label = format!("{:04}-{:02}-{:02} {:02}:{:02} UTC", year, month, day, hour, minute);
|
||||
Ok((stored, label))
|
||||
}
|
||||
|
||||
/// Computa la **carta progresada secundaria** a la edad dada como
|
||||
/// `StoredBirthData` standalone. Método clásico: el instante de la
|
||||
/// progresada es `natal_instant + target_age_years * 1 día`
|
||||
/// (un día simbólico = un año de vida). Las coordenadas del
|
||||
/// observador se heredan del natal — la progresada es una proyección
|
||||
/// simbólica sobre el lugar de nacimiento, no un evento real ahí.
|
||||
pub fn compute_progression_chart(
|
||||
chart: &Chart,
|
||||
target_age_years: f64,
|
||||
) -> Result<(tahuantinsuyu_model::StoredBirthData, String), EngineError> {
|
||||
let (birth_e, _config_e, _observer) = build_eternal_inputs(chart, 0)?;
|
||||
let advance_seconds = target_age_years * 86400.0; // 1 día / año
|
||||
let advanced_utc = birth_e.instant.utc().add_seconds(advance_seconds);
|
||||
let iso = advanced_utc.to_iso8601();
|
||||
let (year, month, day, hour, minute, second) =
|
||||
parse_iso8601_components(&iso).ok_or_else(|| {
|
||||
EngineError::Eternal(format!("iso8601 inválido: {}", iso))
|
||||
})?;
|
||||
let stored = tahuantinsuyu_model::StoredBirthData {
|
||||
year,
|
||||
month,
|
||||
day,
|
||||
hour,
|
||||
minute,
|
||||
second,
|
||||
tz_offset_minutes: chart.birth_data.tz_offset_minutes,
|
||||
latitude_deg: chart.birth_data.latitude_deg,
|
||||
longitude_deg: chart.birth_data.longitude_deg,
|
||||
altitude_m: chart.birth_data.altitude_m,
|
||||
time_certainty: Default::default(),
|
||||
subject_name: chart.birth_data.subject_name.clone(),
|
||||
birthplace_label: chart.birth_data.birthplace_label.clone(),
|
||||
};
|
||||
let label = format!("{:04}-{:02}-{:02} {:02}:{:02} UTC", year, month, day, hour, minute);
|
||||
Ok((stored, label))
|
||||
}
|
||||
|
||||
/// Parsea "YYYY-MM-DDTHH:MM:SS[.fff]" a `(year, month, day, hour,
|
||||
/// minute, second_float)`. Retorna `None` si el formato no encaja.
|
||||
fn parse_iso8601_components(s: &str) -> Option<(i32, u32, u32, u32, u32, f64)> {
|
||||
|
||||
@@ -437,6 +437,26 @@ pub fn compute_planetary_return_chart(
|
||||
bridge::compute_planetary_return_chart(chart, body, target_age_years, shift_days)
|
||||
}
|
||||
|
||||
/// Helper análogo para tránsito — birth_data = `ahora` UTC + lugar
|
||||
/// del natal. Útil para snapshotear el cielo en este instante anclado
|
||||
/// a las coordenadas del sujeto.
|
||||
#[cfg(feature = "eternal-bridge")]
|
||||
pub fn compute_transit_chart(
|
||||
chart: &Chart,
|
||||
) -> Result<(tahuantinsuyu_model::StoredBirthData, String), EngineError> {
|
||||
bridge::compute_transit_chart(chart)
|
||||
}
|
||||
|
||||
/// Helper análogo para progresión secundaria — birth_data = natal +
|
||||
/// target_age_years × 1 día simbólico.
|
||||
#[cfg(feature = "eternal-bridge")]
|
||||
pub fn compute_progression_chart(
|
||||
chart: &Chart,
|
||||
target_age_years: f64,
|
||||
) -> Result<(tahuantinsuyu_model::StoredBirthData, String), EngineError> {
|
||||
bridge::compute_progression_chart(chart, target_age_years)
|
||||
}
|
||||
|
||||
/// Helper retrocompatible: construye un `PlanetaryReturn` con
|
||||
/// `shift_days = 0`. Útil para llamadores que no necesitan ajuste
|
||||
/// fino (todos los Solar return y muchos casos básicos).
|
||||
|
||||
Reference in New Issue
Block a user