feat(cosmobiologia): rectificador automático — UI de entrada y disparo
Segundo incremento: el rectificador ya es usable de punta a punta desde el panel, sin infraestructura de UI nueva. - cosmobiologia-panel: Control::TextInput pasa a renderizarse desde string_state — deja de ser un display estático y se vuelve un campo de sólo-lectura que el shell escribe vía set_string (resultados, etiquetas). - cosmobiologia-modules: el módulo primary_directions gana 3 sliders «Evento N · edad» (0 = ranura sin usar), un Action «Rectificar hora» y un TextInput «Resultado». - shell: run_rectificacion lee las edades de los sliders, llama a engine::rectificar (ventana ±15 min, paso 1) y escribe la hora rectificada + el puntaje en el campo Resultado del panel. El rectificador queda funcional: activar GR → fijar edades de eventos → «Rectificar hora» → leer el resultado. Falta sólo la curva del perfil del barrido como visualización (incremento opcional). Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -32,8 +32,8 @@ use cosmobiologia_canvas::{
|
|||||||
AstrologyCanvas, CanvasEvent, CanvasMode, ThumbnailItem, ThumbnailScope,
|
AstrologyCanvas, CanvasEvent, CanvasMode, ThumbnailItem, ThumbnailScope,
|
||||||
};
|
};
|
||||||
use cosmobiologia_engine::{
|
use cosmobiologia_engine::{
|
||||||
LayerKind, NatalOptions, OUTER_RING_MODULES, PipelineRequest, compose_with_options,
|
EventoConocido, LayerKind, NatalOptions, OUTER_RING_MODULES, PipelineRequest,
|
||||||
svg_export,
|
compose_with_options, svg_export,
|
||||||
};
|
};
|
||||||
use cosmobiologia_model::{
|
use cosmobiologia_model::{
|
||||||
Chart, ChartId, ChartKind, ContactId, FreeChartId, ModuleState, StoredBirthData,
|
Chart, ChartId, ChartKind, ContactId, FreeChartId, ModuleState, StoredBirthData,
|
||||||
@@ -1365,24 +1365,64 @@ impl Shell {
|
|||||||
/// Otros módulos overlay (progression, solar_arc, primary_directions)
|
/// Otros módulos overlay (progression, solar_arc, primary_directions)
|
||||||
/// son extensión natural — TODO.
|
/// son extensión natural — TODO.
|
||||||
fn on_panel_action(&mut self, module_id: String, key: String, cx: &mut Context<Self>) {
|
fn on_panel_action(&mut self, module_id: String, key: String, cx: &mut Context<Self>) {
|
||||||
if key != "save_as_free" {
|
match key.as_str() {
|
||||||
return;
|
"save_as_free" => match module_id.as_str() {
|
||||||
}
|
"planetary_return" => self.save_planetary_return_as_free(cx),
|
||||||
match module_id.as_str() {
|
"transit" => self.save_transit_as_free(cx),
|
||||||
"planetary_return" => self.save_planetary_return_as_free(cx),
|
"progression" => self.save_progression_as_free(cx),
|
||||||
"transit" => self.save_transit_as_free(cx),
|
// Solar arc y direcciones primarias son transformaciones
|
||||||
"progression" => self.save_progression_as_free(cx),
|
// matemáticas puras (no tienen un birth_data real
|
||||||
// Solar arc y direcciones primarias son transformaciones
|
// equivalente). Guardarlas exigiría un `ChartKind`
|
||||||
// matemáticas puras (no tienen un birth_data real
|
// `Derived { source, transform, params }`. TODO.
|
||||||
// equivalente — un Chart natal computado en el "momento
|
_ => {}
|
||||||
// SA" daría posiciones distintas a las dirigidas). Para
|
},
|
||||||
// guardarlas haría falta extender Chart con un kind
|
"rectificar" => self.run_rectificacion(cx),
|
||||||
// `Derived { source, transform, params }` que el engine
|
|
||||||
// sepa rehidratar. TODO.
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lanza el rectificador automático (Sistema GR): lee las edades de
|
||||||
|
/// los eventos conocidos de los sliders del módulo, barre las horas
|
||||||
|
/// candidatas y escribe el resultado en el campo «Resultado» del
|
||||||
|
/// panel. El barrido es síncrono — para ±15 min son ~31 cartas.
|
||||||
|
fn run_rectificacion(&mut self, cx: &mut Context<Self>) {
|
||||||
|
// Clonamos la carta: `rectificar` necesita `&Chart` y luego
|
||||||
|
// `panel.update` toma `&mut self` — no pueden solaparse.
|
||||||
|
let Some(chart) = self.current_chart.clone() else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
let cfg = self.module_configs.get("primary_directions");
|
||||||
|
let read_age = |key: &str| -> f64 {
|
||||||
|
cfg.and_then(|c| c.get(key))
|
||||||
|
.and_then(|v| v.as_f64())
|
||||||
|
.unwrap_or(0.0)
|
||||||
|
};
|
||||||
|
// Edades > 0 — una ranura en 0 es "sin usar".
|
||||||
|
let eventos: Vec<EventoConocido> = ["evento_1", "evento_2", "evento_3"]
|
||||||
|
.iter()
|
||||||
|
.map(|k| read_age(k))
|
||||||
|
.filter(|edad| *edad > 0.5)
|
||||||
|
.map(|edad| EventoConocido { edad_years: edad })
|
||||||
|
.collect();
|
||||||
|
let key_gr = cfg
|
||||||
|
.and_then(|c| c.get("key"))
|
||||||
|
.and_then(|v| v.as_str())
|
||||||
|
.unwrap_or("naibod")
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// Ventana ±15 min, paso 1 min — el barrido GR estándar.
|
||||||
|
let resultado = match cosmobiologia_engine::rectificar(&chart, &eventos, 15, 1, &key_gr) {
|
||||||
|
Ok(r) => format!(
|
||||||
|
"{:+} min · puntaje {:.2}",
|
||||||
|
r.mejor_offset_minutos, r.mejor_puntaje
|
||||||
|
),
|
||||||
|
Err(_) => "define al menos un evento (edad > 0)".to_string(),
|
||||||
|
};
|
||||||
|
self.panel.update(cx, |p, cx| {
|
||||||
|
p.set_string("primary_directions", "resultado", Some(resultado), cx)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/// Snapshot del cielo en este instante anclado al lugar del
|
/// Snapshot del cielo en este instante anclado al lugar del
|
||||||
/// natal. Sufijo `transito-{fecha}`. Útil para guardar "qué
|
/// natal. Sufijo `transito-{fecha}`. Útil para guardar "qué
|
||||||
/// estaba pasando ahora en la carta de Pedro".
|
/// estaba pasando ahora en la carta de Pedro".
|
||||||
|
|||||||
@@ -947,6 +947,43 @@ pub mod primary_directions {
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
// --- Rectificador automático ---
|
||||||
|
// Tres edades de eventos conocidos de la vida del
|
||||||
|
// sujeto; `0` = ranura sin usar. El barrido GR busca la
|
||||||
|
// hora de nacimiento que mejor las explica.
|
||||||
|
Control::Slider {
|
||||||
|
key: "evento_1".into(),
|
||||||
|
label: "Evento 1 · edad".into(),
|
||||||
|
min: 0.0,
|
||||||
|
max: 90.0,
|
||||||
|
step: 1.0,
|
||||||
|
default: 0.0,
|
||||||
|
},
|
||||||
|
Control::Slider {
|
||||||
|
key: "evento_2".into(),
|
||||||
|
label: "Evento 2 · edad".into(),
|
||||||
|
min: 0.0,
|
||||||
|
max: 90.0,
|
||||||
|
step: 1.0,
|
||||||
|
default: 0.0,
|
||||||
|
},
|
||||||
|
Control::Slider {
|
||||||
|
key: "evento_3".into(),
|
||||||
|
label: "Evento 3 · edad".into(),
|
||||||
|
min: 0.0,
|
||||||
|
max: 90.0,
|
||||||
|
step: 1.0,
|
||||||
|
default: 0.0,
|
||||||
|
},
|
||||||
|
Control::Action {
|
||||||
|
key: "rectificar".into(),
|
||||||
|
label: "Rectificar hora".into(),
|
||||||
|
},
|
||||||
|
Control::TextInput {
|
||||||
|
key: "resultado".into(),
|
||||||
|
label: "Resultado".into(),
|
||||||
|
default: "—".into(),
|
||||||
|
},
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
fn compute_layers(&self, _chart: &Chart, _cfg: &serde_json::Value) -> Vec<Layer> {
|
||||||
|
|||||||
@@ -173,6 +173,14 @@ impl ControlPanel {
|
|||||||
.entry((m.id().to_string(), key))
|
.entry((m.id().to_string(), key))
|
||||||
.or_insert(Some(default));
|
.or_insert(Some(default));
|
||||||
}
|
}
|
||||||
|
// `TextInput` es un campo de sólo-display que el
|
||||||
|
// shell escribe (resultados, etiquetas) vía
|
||||||
|
// `set_string`; su estado vive en `string_state`.
|
||||||
|
Control::TextInput { key, default, .. } => {
|
||||||
|
self.string_state
|
||||||
|
.entry((m.id().to_string(), key))
|
||||||
|
.or_insert(Some(default));
|
||||||
|
}
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -550,7 +558,16 @@ impl ControlPanel {
|
|||||||
options,
|
options,
|
||||||
default,
|
default,
|
||||||
} => self.render_select(theme, module_id, key, label, options, default, cx),
|
} => self.render_select(theme, module_id, key, label, options, default, cx),
|
||||||
Control::TextInput { label, default, .. } => display_row(theme, label, default),
|
Control::TextInput { key, label, default } => {
|
||||||
|
// Sólo-display: muestra lo último que el shell escribió
|
||||||
|
// con `set_string`, o el `default` si nada se escribió.
|
||||||
|
let valor = self
|
||||||
|
.string_state
|
||||||
|
.get(&(module_id.to_string(), key.to_string()))
|
||||||
|
.and_then(|o| o.clone())
|
||||||
|
.unwrap_or_else(|| default.clone());
|
||||||
|
display_row(theme, label, &valor)
|
||||||
|
}
|
||||||
Control::Action { key, label } => {
|
Control::Action { key, label } => {
|
||||||
self.render_action(theme, module_id, key, label, cx)
|
self.render_action(theme, module_id, key, label, cx)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user