From ca4a89864c0745deb293de2a2bf8b4d5c4059b51 Mon Sep 17 00:00:00 2001 From: claude Date: Tue, 14 Jul 2026 00:02:50 +0200 Subject: [PATCH] =?UTF-8?q?feat(std,engine):=20SessionFrankfurt=20?= =?UTF-8?q?=E2=80=94=20the=20Session=20node=20enters=20the=20closed=20rost?= =?UTF-8?q?er?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A Frankfurt-baked zero-arg Session preset (open 09:00 Europe/Berlin as literals) joins the std vocabulary as SessionFrankfurt, declaring period_minutes: I64 (default 15) as its one scalar knob — the single-knob roster pattern the cost nodes established. A data-only blueprint can now reach a session/time-of-day stream (bars_since_open), unblocking time-of-day conditioners for the confirm/refute research model. Roster count pins bump 28 -> 29 in both conscious-act sites. Fork decision on the issue: the preset variant over a param-vocabulary extension — a timezone param would need a string kind, breaching the four-scalar discipline; the preset keeps the vocabulary closed and is the additive extension the roster-exclusion comment anticipates. The 4-arg Session::builder stays untouched; in_session/session_open_ts stay deferred (#154). closes #261 --- crates/aura-cli/tests/graph_construct.rs | 4 +-- crates/aura-std/src/lib.rs | 2 +- crates/aura-std/src/session.rs | 31 +++++++++++++++++++++++- crates/aura-std/src/vocabulary.rs | 7 +++--- 4 files changed, 37 insertions(+), 7 deletions(-) diff --git a/crates/aura-cli/tests/graph_construct.rs b/crates/aura-cli/tests/graph_construct.rs index ea02399..e5844d6 100644 --- a/crates/aura-cli/tests/graph_construct.rs +++ b/crates/aura-cli/tests/graph_construct.rs @@ -121,8 +121,8 @@ fn graph_introspect_vocabulary_lists_exactly_the_closed_roster_count() { let lines: Vec<&str> = stdout.lines().filter(|l| !l.is_empty()).collect(); assert_eq!( lines.len(), - 28, - "the std-only (no project) vocabulary has exactly the roster's 28 entries: {stdout}" + 29, + "the std-only (no project) vocabulary has exactly the roster's 29 entries: {stdout}" ); } diff --git a/crates/aura-std/src/lib.rs b/crates/aura-std/src/lib.rs index 9dcbb08..8429824 100644 --- a/crates/aura-std/src/lib.rs +++ b/crates/aura-std/src/lib.rs @@ -88,7 +88,7 @@ pub use rolling_max::RollingMax; pub use rolling_min::RollingMin; pub use scale::Scale; pub use series_reducer::SeriesReducer; -pub use session::Session; +pub use session::{Session, SessionFrankfurt}; pub use sim_broker::SimBroker; pub use sizer::Sizer; pub use sma::Sma; diff --git a/crates/aura-std/src/session.rs b/crates/aura-std/src/session.rs index 83102d2..c1c1a2e 100644 --- a/crates/aura-std/src/session.rs +++ b/crates/aura-std/src/session.rs @@ -32,7 +32,9 @@ //! //! **Cold guard.** If the trigger column is empty (not yet fired) → `None`. -use aura_core::{Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, PortSpec, PrimitiveBuilder, ScalarKind}; +use aura_core::{ + Cell, Ctx, FieldSpec, Firing, Node, NodeSchema, ParamSpec, PortSpec, PrimitiveBuilder, ScalarKind, +}; use chrono::{TimeZone, Timelike}; /// Frankfurt-session bar indexer. Holds the open offset (minutes past local @@ -76,6 +78,33 @@ impl Session { } } +/// The zero-arg `SessionFrankfurt` roster preset (#261): the Frankfurt open +/// (09:00 `Europe/Berlin`) baked as a literal, so the closed `std_vocabulary` +/// roster can reach a `Session` without a structural construction argument +/// (`Session::builder` itself stays absent from the roster — #155 scope). +/// The one remaining knob, `period_minutes` (i64, conventionally 15 — the +/// resampler's bar width), is declared as the node's sole `ParamSpec` and +/// bound through it via the single-knob `CarryCost`/`ConstantCost`/ +/// `VolSlippageCost` pattern. This is additive: `Session::builder` is +/// untouched and stays the vehicle for other timezones/opens. +pub struct SessionFrankfurt; + +impl SessionFrankfurt { + /// The param-generic recipe: one `period_minutes` I64 knob, the Frankfurt + /// open/timezone baked in the closure. + pub fn builder() -> PrimitiveBuilder { + PrimitiveBuilder::new( + "SessionFrankfurt", + NodeSchema { + inputs: vec![PortSpec { kind: ScalarKind::F64, firing: Firing::Any, name: "trigger".into() }], + output: vec![FieldSpec { name: "bars_since_open".into(), kind: ScalarKind::I64 }], + params: vec![ParamSpec { name: "period_minutes".into(), kind: ScalarKind::I64 }], + }, + |p| Box::new(Session::new(9, 0, chrono_tz::Europe::Berlin, p[0].i64())), + ) + } +} + impl Node for Session { fn lookbacks(&self) -> Vec { vec![1] diff --git a/crates/aura-std/src/vocabulary.rs b/crates/aura-std/src/vocabulary.rs index 6f888e8..355ef0d 100644 --- a/crates/aura-std/src/vocabulary.rs +++ b/crates/aura-std/src/vocabulary.rs @@ -20,7 +20,7 @@ use crate::{ Abs, Add, And, Bias, CarryCost, Const, ConstantCost, Delay, Div, Ema, EqConst, FixedStop, Gt, Latch, LongOnly, Max, Min, Mul, PositionManagement, Resample, RollingMax, RollingMin, Scale, - Sizer, Sma, Sqrt, Sub, VolSlippageCost, + SessionFrankfurt, Sizer, Sma, Sqrt, Sub, VolSlippageCost, }; use aura_core::PrimitiveBuilder; @@ -81,6 +81,7 @@ std_vocabulary_roster! { "RollingMax" => RollingMax, "RollingMin" => RollingMin, "Scale" => Scale, + "SessionFrankfurt" => SessionFrankfurt, "Sizer" => Sizer, "SMA" => Sma, "Sqrt" => Sqrt, @@ -129,7 +130,7 @@ mod tests { assert!(!std_vocabulary_types().contains(&"LinComb")); // construction-arg node assert!(!std_vocabulary_types().contains(&"Recorder")); // sink assert!(!std_vocabulary_types().contains(&"nope")); - // count guard: pins the roster at exactly 28 entries - assert_eq!(std_vocabulary_types().len(), 28); + // count guard: pins the roster at exactly 29 entries + assert_eq!(std_vocabulary_types().len(), 29); } }