1 import Base 2 import ../store.bend as Store 3 import ../policy/types.bend as T 4 import ../policy/usage.bend as PU 5 import ./gather.bend as G 6 import ./view.bend as V 7 8 # Which accounts a scheduled auto tick fetches (autoswitch.py 9 # _collect_scheduled_usage, contract 4.2 step 3). No network for a fresh row. 10 11 def sel_e(+c: Bool, +x: List<&2, G.Ent>, +y: List<&2, G.Ent>) -> List<&2, G.Ent>: 12 match c: 13 case True{}: 14 x 15 case False{}: 16 y 17 18 def is_active(+e: G.Ent, +active: Maybe<&2, String>) -> Bool: 19 V.is_slot(active, V.ent_slot(e)) 20 21 def acct_disabled(+a: T.Account) -> Bool: 22 match a: 23 case T.Account{s, em, k, d}: 24 d 25 26 # A candidate can be fetched: not disabled/quarantined/unswitchable. 27 def eligible(+e: G.Ent) -> Bool: 28 match e: 29 case G.Ent{s, em, o, c, a}: 30 Bool.not(acct_disabled(a)) 31 32 def due2(+m: Maybe<&2, T.Usage>, +now: Nat, +cfg: T.Config) -> Bool: 33 match m: 34 case Some{u}: 35 PU.poll_due(u, now, cfg) 36 case None{}: 37 False{} 38 39 def due(+e: G.Ent, +us: List<&2, T.Usage>, +now: Nat, +cfg: T.Config) -> Bool: 40 due2(V.usage_for(us, V.slot_nat_of(V.ent_slot(e))), now, cfg) 41 42 def stale2(+m: Maybe<&2, T.Usage>, +now: Nat, +ms: List<&2, String>) -> Bool: 43 match m: 44 case Some{u}: 45 Bool.not(PU.fresh(u, now, ms)) 46 case None{}: 47 False{} 48 49 def stale(+e: G.Ent, +us: List<&2, T.Usage>, +now: Nat, +ms: List<&2, String>) -> Bool: 50 stale2(V.usage_for(us, V.slot_nat_of(V.ent_slot(e))), now, ms) 51 52 def fa2(+m: Maybe<&2, T.Usage>) -> Nat: 53 match m: 54 case Some{T.Usage{s, five, fr, seven, sr, ss, fa, bo, er, fl, np, pi, ad, hu, cu, se}}: 55 Maybe.default(&2, Nat, fa, 0n) 56 case _: 57 0n 58 59 def fetched_at(+e: G.Ent, +us: List<&2, T.Usage>) -> Nat: 60 fa2(V.usage_for(us, V.slot_nat_of(V.ent_slot(e)))) 61 62 # The active account, when it is due. 63 def active_due(+es: List<&2, G.Ent>, +us: List<&2, T.Usage>, +active: Maybe<&2, String>, +now: Nat, +cfg: T.Config) -> List<&2, G.Ent>: 64 match es: 65 case Nil{}: 66 [] 67 case Con{e, t}: 68 sel_e(is_active(e, active) && eligible(e) && due(e, us, now, cfg), [e], active_due(t, us, active, now, cfg)) 69 70 # Due candidates, in roster order. 71 def due_cands(+es: List<&2, G.Ent>, +us: List<&2, T.Usage>, +active: Maybe<&2, String>, +now: Nat, +cfg: T.Config) -> List<&2, G.Ent>: 72 match es: 73 case Nil{}: 74 [] 75 case Con{e, t}: 76 sel_e(Bool.not(is_active(e, active)) && eligible(e) && due(e, us, now, cfg), e <> due_cands(t, us, active, now, cfg), due_cands(t, us, active, now, cfg)) 77 78 def sel_m(+c: Bool, +x: Maybe<&2, G.Ent>, +y: Maybe<&2, G.Ent>) -> Maybe<&2, G.Ent>: 79 match c: 80 case True{}: 81 x 82 case False{}: 83 y 84 85 def older(+e: G.Ent, +best: Maybe<&2, G.Ent>, +us: List<&2, T.Usage>) -> Maybe<&2, G.Ent>: 86 match best: 87 case None{}: 88 Some{e} 89 case Some{b}: 90 sel_m(Nat.is_lt(fetched_at(e, us), fetched_at(b, us)), Some{e}, best) 91 92 # The due candidate with the oldest measurement (the first wins ties). 93 def oldest(+es: List<&2, G.Ent>, +us: List<&2, T.Usage>, +best: Maybe<&2, G.Ent>) -> Maybe<&2, G.Ent>: 94 match es: 95 case Nil{}: 96 best 97 case Con{e, t}: 98 oldest(t, us, older(e, best, us)) 99 100 def one_of(+m: Maybe<&2, G.Ent>) -> List<&2, G.Ent>: 101 match m: 102 case Some{e}: 103 [e] 104 case None{}: 105 [] 106 107 # Phase 1: the active account if due, plus at most one due candidate. 108 def first_pass(+es: List<&2, G.Ent>, +us: List<&2, T.Usage>, +active: Maybe<&2, String>, +now: Nat, +cfg: T.Config) -> List<&2, G.Ent>: 109 List.append(&2, G.Ent, active_due(es, us, active, now, cfg), one_of(oldest(due_cands(es, us, active, now, cfg), us, None{}))) 110 111 def same_slot(+a: G.Ent, +b: G.Ent) -> Bool: 112 String.eq(V.ent_slot(a), V.ent_slot(b)) 113 114 def in_list(+e: G.Ent, +xs: List<&2, G.Ent>) -> Bool: 115 match xs: 116 case Nil{}: 117 False{} 118 case Con{h, t}: 119 same_slot(e, h) || in_list(e, t) 120 121 def exempt3(+u: T.Usage, +h: Maybe<&2, Nat>) -> Bool: 122 match u h: 123 case T.Usage{s, five, fr, seven, sr, ss, fa, bo, er, fl, Some{np}, pi, ad, hu, cu, se} Some{x}: 124 Nat.is_eq(x, 0n) && Nat.is_gt(pi, 600n) 125 case _ _: 126 False{} 127 128 def exempt2(+m: Maybe<&2, T.Usage>, +now: Nat, +ms: List<&2, String>) -> Bool: 129 match m: 130 case Some{u}: 131 exempt3(u, PU.headroom(u, now, ms)) 132 case None{}: 133 False{} 134 135 # Rows planned past a long wait and exhausted are left alone. 136 def exempt(+e: G.Ent, +us: List<&2, T.Usage>, +now: Nat, +ms: List<&2, String>) -> Bool: 137 exempt2(V.usage_for(us, V.slot_nat_of(V.ent_slot(e))), now, ms) 138 139 # Phase 2 (escalation): every other candidate that is due or stale. 140 def escalated(+es: List<&2, G.Ent>, +us: List<&2, T.Usage>, +active: Maybe<&2, String>, +skip: List<&2, G.Ent>, +now: Nat, +cfg: T.Config, +ms: List<&2, String>) -> List<&2, G.Ent>: 141 match es: 142 case Nil{}: 143 [] 144 case Con{e, t}: 145 sel_e(Bool.not(is_active(e, active)) && eligible(e) && Bool.not(in_list(e, skip)) && (due(e, us, now, cfg) || stale(e, us, now, ms)) && Bool.not(exempt(e, us, now, ms)), 146 e <> escalated(t, us, active, skip, now, cfg, ms), escalated(t, us, active, skip, now, cfg, ms))