feat(cli): TapChannel::Net — the net_r_equity tap routes and persists (#234 task 6)
TapChannel gains Net; tap_channel routes the full closed vocabulary (producibility moved to the caller); the trace path drains the sixth channel and persists the cost-adjusted net curve. A cost-less document requesting net_r_equity keeps a skip notice, reworded to name the remedy (add a cost block) — not a refusal. Two deliberate pin moves: the absence pin re-pins to routed, the skip-notice e2e re-pins to the reworded form. Post-review: positive write-path e2e added (a costed GER40 campaign lands net_r_equity.json with the curve differing from r_equity by the accrued costs). Verified: routing pin + positive e2e green (real archive), full workspace suite green, clippy -D warnings clean. refs #234
This commit is contained in:
@@ -636,36 +636,35 @@ fn present_campaign(
|
||||
/// campaign trace re-run. The routing mirrors `persist_traces_r` (main.rs):
|
||||
/// equity <- the SimBroker equity recorder (tx_eq), exposure <- the Bias
|
||||
/// recorder (tx_ex), r_equity <- the cum_realized_r + unrealized_r recorder
|
||||
/// (tx_req). `net_r_equity` has no channel here: `wrap_r` no longer builds a
|
||||
/// cost leg at all (#221), so a net curve is not produced by this run's
|
||||
/// configuration.
|
||||
/// (tx_req), net_r_equity <- the cost leg's LinComb(4) net-curve recorder
|
||||
/// (tx_net, #234) — produced only when the campaign document carries a cost
|
||||
/// block (the caller's producibility check).
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum TapChannel {
|
||||
Equity,
|
||||
Exposure,
|
||||
REquity,
|
||||
Net,
|
||||
}
|
||||
|
||||
/// Route one requested tap of the closed vocabulary to its drained channel;
|
||||
/// `None` marks a tap this run cannot produce (the caller skips it loudly).
|
||||
/// An unknown name cannot reach here (`validate_campaign` refuses it) and
|
||||
/// maps to `None` all the same rather than guessing.
|
||||
/// `None` marks a name outside the vocabulary (unreachable — `validate_campaign`
|
||||
/// refuses it — and mapped defensively rather than guessed at).
|
||||
fn tap_channel(tap: &str) -> Option<TapChannel> {
|
||||
// The emit_vocabulary debug_assert's twin: every vocabulary tap must be
|
||||
// either routable here or the one known cost-only tap — a future fifth
|
||||
// vocabulary entry fails loudly instead of silently skipping with the
|
||||
// hardcoded needs-a-cost-run message.
|
||||
// routable here — a future fifth vocabulary entry fails loudly instead of
|
||||
// silently skipping.
|
||||
debug_assert!(
|
||||
aura_research::tap_vocabulary()
|
||||
.iter()
|
||||
.all(|t| *t == "net_r_equity"
|
||||
|| matches!(*t, "equity" | "exposure" | "r_equity")),
|
||||
.all(|t| matches!(*t, "equity" | "exposure" | "r_equity" | "net_r_equity")),
|
||||
"tap_vocabulary drifted from the channels campaign_run routes"
|
||||
);
|
||||
match tap {
|
||||
"equity" => Some(TapChannel::Equity),
|
||||
"exposure" => Some(TapChannel::Exposure),
|
||||
"r_equity" => Some(TapChannel::REquity),
|
||||
"net_r_equity" => Some(TapChannel::Net),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -771,16 +770,19 @@ pub(crate) fn persist_campaign_traces(
|
||||
.ensure_name_free(trace_name, WriteKind::Family)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// Requested ∩ producible, in request order. When nothing producible was
|
||||
// requested (net_r_equity only), no member dir is written and the
|
||||
// Requested ∩ producible, in request order. `net_r_equity` is producible
|
||||
// exactly when the document carries a cost block (#234); the skip notice
|
||||
// names the remedy — the tap is optional presentation, not a refusal.
|
||||
// When nothing producible was requested, no member dir is written and the
|
||||
// summary honestly reports 0 tap(s).
|
||||
let mut routed: Vec<(&str, TapChannel)> = Vec::new();
|
||||
for tap in taps {
|
||||
match tap_channel(tap) {
|
||||
Some(ch) => routed.push((tap.as_str(), ch)),
|
||||
None => eprintln!(
|
||||
"aura: tap \"{tap}\" is not produced by this run (needs a cost run); skipped"
|
||||
Some(TapChannel::Net) if campaign.cost.is_empty() => eprintln!(
|
||||
"aura: tap \"{tap}\" needs a cost model; add a cost block to the campaign document; skipped"
|
||||
),
|
||||
Some(ch) => routed.push((tap.as_str(), ch)),
|
||||
None => eprintln!("aura: tap \"{tap}\" is not produced by this run; skipped"),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -910,7 +912,7 @@ pub(crate) fn persist_campaign_traces(
|
||||
// re-run gross would trip the C1 drift alarm below on every
|
||||
// legitimate costed campaign, not just on a real divergence.
|
||||
let (tx_cost, rx_cost) = mpsc::channel();
|
||||
let (tx_net, _rx_net) = mpsc::channel();
|
||||
let (tx_net, rx_net) = mpsc::channel();
|
||||
let cost_leg = (!campaign.cost.is_empty()).then(|| crate::CostLeg {
|
||||
nodes: cost_nodes_for(&campaign.cost),
|
||||
tx_cost,
|
||||
@@ -921,13 +923,15 @@ pub(crate) fn persist_campaign_traces(
|
||||
.bootstrap_with_cells(&point)
|
||||
.expect("the member's point re-bootstraps (it already ran this realization)");
|
||||
h.run(sources);
|
||||
// Drain ALL FIVE channels (`run_signal_r` leaves req undrained; the
|
||||
// trace path must not): eq/ex/req feed the taps, r+cost feed the metrics.
|
||||
// Drain ALL SIX channels (`run_signal_r` leaves req undrained; the
|
||||
// trace path must not): eq/ex/req/net feed the taps, r+cost feed
|
||||
// the metrics.
|
||||
let eq_rows: Vec<(Timestamp, Vec<Scalar>)> = rx_eq.try_iter().collect();
|
||||
let ex_rows: Vec<(Timestamp, Vec<Scalar>)> = rx_ex.try_iter().collect();
|
||||
let r_rows: Vec<(Timestamp, Vec<Scalar>)> = rx_r.try_iter().collect();
|
||||
let req_rows: Vec<(Timestamp, Vec<Scalar>)> = rx_req.try_iter().collect();
|
||||
let cost_rows: Vec<(Timestamp, Vec<Scalar>)> = rx_cost.try_iter().collect();
|
||||
let net_rows: Vec<(Timestamp, Vec<Scalar>)> = rx_net.try_iter().collect();
|
||||
|
||||
// The C1 drift alarm: metrics equality against the recorded
|
||||
// member. The reduce-mode fold shares its arithmetic with this
|
||||
@@ -951,6 +955,7 @@ pub(crate) fn persist_campaign_traces(
|
||||
TapChannel::Equity => &eq_rows,
|
||||
TapChannel::Exposure => &ex_rows,
|
||||
TapChannel::REquity => &req_rows,
|
||||
TapChannel::Net => &net_rows,
|
||||
};
|
||||
ColumnarTrace::from_rows(tap, &[ScalarKind::F64], rows)
|
||||
})
|
||||
@@ -1272,14 +1277,17 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
/// The tap->channel routing mirrors `persist_traces_r` (equity<-eq,
|
||||
/// exposure<-ex, r_equity<-req); `net_r_equity` is unproducible on the
|
||||
/// campaign runner's cost-free wrap and routes to no channel.
|
||||
fn tap_channel_routes_the_producible_vocabulary_only() {
|
||||
/// #234 — a DELIBERATE pin move (was: `net_r_equity` routes to `None` on
|
||||
/// the cost-free wrap): the full closed tap vocabulary now routes,
|
||||
/// `net_r_equity` to the cost leg's net-curve channel. Producibility
|
||||
/// (does THIS run carry a cost model?) is the caller's check, not the
|
||||
/// routing's.
|
||||
fn tap_channel_routes_the_full_vocabulary() {
|
||||
assert_eq!(tap_channel("equity"), Some(TapChannel::Equity));
|
||||
assert_eq!(tap_channel("exposure"), Some(TapChannel::Exposure));
|
||||
assert_eq!(tap_channel("r_equity"), Some(TapChannel::REquity));
|
||||
assert_eq!(tap_channel("net_r_equity"), None);
|
||||
assert_eq!(tap_channel("net_r_equity"), Some(TapChannel::Net));
|
||||
assert_eq!(tap_channel("bogus"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
Reference in New Issue
Block a user