Class: Insika::Commands::FreezeFunnelBaseline
- Inherits:
-
Object
- Object
- Insika::Commands::FreezeFunnelBaseline
- Defined in:
- lib/insika/commands/freeze_funnel_baseline.rb
Overview
the operator's FREEZE — turn the folded counts over a declared span into the baseline snapshot (A/B follow-up) and (promotion gate) read. One command, dispatched from the Studio and available to any operator-grade caller. Synchronous control command — no task, no actor, same discipline as RecordOutcome (WS7).
The ≥ 4-week rule ("no 1.0 target before a remeasure") is
enforced HERE, not by convention: a frozen span shorter than 28 days is a
ValidationError. frozen_at stamps when; the baseline OVERWRITES (D5) —
the freeze event is the audit surface.
Constant Summary collapse
- MIN_SPAN_DAYS =
28
Instance Method Summary collapse
-
#call(command) ⇒ Object
payload: { agent:, tenant:, from: "YYYY-MM-DD", to: "YYYY-MM-DD", operator: } agent REQUIRED (ValidationError); tenant from payload || meta.
-
#initialize(funnel_store:, profiles:, event_stream:) ⇒ FreezeFunnelBaseline
constructor
A new instance of FreezeFunnelBaseline.
Constructor Details
#initialize(funnel_store:, profiles:, event_stream:) ⇒ FreezeFunnelBaseline
Returns a new instance of FreezeFunnelBaseline.
21 22 23 24 25 |
# File 'lib/insika/commands/freeze_funnel_baseline.rb', line 21 def initialize(funnel_store:, profiles:, event_stream:) @funnel_store = funnel_store @profiles = profiles @event_stream = event_stream end |
Instance Method Details
#call(command) ⇒ Object
payload: { agent:, tenant:, from: "YYYY-MM-DD", to: "YYYY-MM-DD", operator: } agent REQUIRED (ValidationError); tenant from payload || meta. from/to optional — default to the pair's folded span (first/last day cell); explicit from/to are inclusive bounds. -> the baseline record.
32 33 34 35 36 37 38 39 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 67 68 |
# File 'lib/insika/commands/freeze_funnel_baseline.rb', line 32 def call(command) payload = command.payload agent = Coercion.presence(payload[:agent] || payload["agent"]) raise ValidationError, "agent is required" if agent.nil? declaration = declaration_for(agent) tenant = payload[:tenant] || payload["tenant"] || command.[:tenant] from, to = bounds(payload, tenant, agent) validate_span!(from, to) stages = @funnel_store.days(tenant: tenant, agent: agent, from: from, to: to) .each_with_object(Hash.new(0)) do |(_, counts), acc| counts.each { |stage, n| acc[stage] += n } end primary = declaration.primary primary_count = stages[primary].to_i first_count = stages[declaration.first_stage].to_i conversion = first_count.zero? ? nil : primary_count.to_f / first_count baseline = { "from" => from, "to" => to, "stages" => stages, "primary" => primary, "primary_count" => primary_count, "conversion" => conversion, "window" => declaration.attribution_window, "frozen_at" => Time.now.utc.iso8601 } @funnel_store.set_baseline(tenant: tenant, agent: agent, record: baseline) @event_stream.emit(Insika::Event.new( type: :funnel_baseline_frozen, data: { agent: agent, from: from, to: to, primary: primary, primary_count: primary_count, conversion: conversion }, meta: { at: Time.now.utc.iso8601 } )) baseline end |