Class: Insika::FunnelFold

Inherits:
Object
  • Object
show all
Defined in:
lib/insika/funnel_fold.rb

Overview

the tick-driven fold of WS7 outcome records into the pack-declared stages (the tick's cadence). One pass per claim window; the fold is idempotent across crashes via the per-pair ids cursor (D3) and per-pair transactions. Pairs whose declaration is absent or malformed are skipped (D8), as are outcome kinds the declaration does not map — the funnel shows the hole. Nothing else: no attribution (D4), no stage vocabulary of its own (D1).

Constant Summary collapse

SCOPE =
"funnel_fold"
KEY =
"claim"
DEFAULT_WINDOW =

seconds; one folding worker per window

300

Instance Method Summary collapse

Constructor Details

#initialize(outcome_store:, funnel_store:, profiles:, store:, window: DEFAULT_WINDOW, now: nil) ⇒ FunnelFold

Returns a new instance of FunnelFold.



19
20
21
22
23
24
25
26
27
# File 'lib/insika/funnel_fold.rb', line 19

def initialize(outcome_store:, funnel_store:, profiles:, store:,
               window: DEFAULT_WINDOW, now: nil)
  @outcome_store = outcome_store
  @funnel_store = funnel_store
  @profiles = profiles
  @store = store
  @window = window
  @now = now
end

Instance Method Details

#recompute(tenant:, agent:, declaration:) ⇒ Object

From scratch (E1's "recompute matches the incremental fold", and the repair path for backfilled outcomes): wipes the pair's day cells and rebuilds them from every outcome record. tenant: takes the store's spellings alike (nil/""/"platform" = the no-tenant pair) — for_pair and delete_days normalize at their own key boundaries, so no tenant is ever mixed with another (a "platform" recompute never folds "acme"'s records). -> folded count.



66
67
68
69
# File 'lib/insika/funnel_fold.rb', line 66

def recompute(tenant:, agent:, declaration:)
  @funnel_store.delete_days(tenant: tenant, agent: agent)
  fold_pair(tenant: tenant, agent: agent, declaration: declaration, full: true)
end

#runObject

One pass. -> { claimed: true, folded: Integer, skipped: Integer, pairs: Integer } | { claimed: false } (another worker holds the window — the retention.rb:74 shape, so the tick summary reads uniformly)



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
# File 'lib/insika/funnel_fold.rb', line 32

def run
  return { claimed: false } unless claim

  folded = 0
  skipped = 0
  pairs = 0
  # The pairs come from the OUTCOME keys (prefix scan, no record reads);
  # each pair's records are read by `for_pair`, so a pair without a
  # declaration is never even read (a full `all` would read every record
  # of every pair — the O(n) the key shape is there to avoid).
  @outcome_store.pairs.each do |pair|
    begin
      declaration = declaration_for(pair[:agent])
      next unless declaration

      folded += fold_pair(tenant: pair[:tenant], agent: pair[:agent],
                          declaration: declaration, skipped: -> { skipped += 1 })
      pairs += 1
    rescue StandardError
      # a broken pair must not hold every other store's funnel hostage —
      # its own transaction already rolled back; the pass keeps folding.
      next
    end
  end
  { claimed: true, folded: folded, skipped: skipped, pairs: pairs }
end