proactive-gate 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +86 -4
- package/README.tr.md +76 -1
- package/dist/src/checks.d.ts +52 -5
- package/dist/src/checks.js +52 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -112,6 +112,22 @@ consumed in check order at commit, so when a weekly check passes and the daily o
|
|
|
112
112
|
refuses, that weekly unit is spent without a delivery. It only happens when two commits
|
|
113
113
|
race after a shared evaluate.
|
|
114
114
|
|
|
115
|
+
### Two limits you should know before you adopt this
|
|
116
|
+
|
|
117
|
+
Neither is a bug, and both are pinned by tests so a future change has to be deliberate.
|
|
118
|
+
|
|
119
|
+
**The week is the ISO week, so the weekly budget refills on Monday.** Where the working
|
|
120
|
+
week runs Sunday to Thursday, that refill lands one day in: a user who spends the budget on
|
|
121
|
+
Sunday has it back on Monday, with four working days still to run. Changing the key would
|
|
122
|
+
move every counter already in your store, so it is documented rather than quietly altered.
|
|
123
|
+
Pass your own budget check keyed how you like if the ISO week is wrong for your users.
|
|
124
|
+
|
|
125
|
+
**Quiet hours are a single window, the same on every day of the week.** A user carries one
|
|
126
|
+
`start` and one `end`, so a Friday window, a Shabbat window or a public holiday cannot be
|
|
127
|
+
expressed. The day of the week is never read. If you need one, write a check: it is an
|
|
128
|
+
object with an `id` and a `run`, it composes in the order you choose, and the trace will
|
|
129
|
+
show it firing beside the built-in ones.
|
|
130
|
+
|
|
115
131
|
Order is a design decision and it should be visible. Consent has to come before
|
|
116
132
|
everything. Quiet hours have to come before the budget, or a rejected candidate
|
|
117
133
|
consumes a delivery it never made. Reorder freely; the trace will show what you did.
|
|
@@ -208,20 +224,67 @@ Both ship off. They read numbers the caller puts on the candidate.
|
|
|
208
224
|
|
|
209
225
|
- `utilityFloor({ costFalseAlarm, costMissedHelp })` acts only when `candidate.pAccept` clears
|
|
210
226
|
`tau = cFA / (cFA + pNeed * cFN)` (`pNeed` defaults to 1) and skips when there is no
|
|
211
|
-
`pAccept`.
|
|
227
|
+
`pAccept`. That threshold is the classical Bayes decision boundary: alerting costs
|
|
228
|
+
`(1 - p) * cFA`, silence costs `p * cFN`, so you alert when the first is the smaller.
|
|
229
|
+
The alerting application is [Horvitz, Jacobs and Hovel, "Attention-Sensitive Alerting",
|
|
230
|
+
UAI 1999](https://arxiv.org/abs/1301.6707), whose system is named Priorities.
|
|
212
231
|
- `boundedDeferral({ lambda, interruptCost, staleness, boundSeconds })` never rejects. When
|
|
213
232
|
`candidate.busy` is true it moves `deliverAt` to `now + t*`, with
|
|
214
233
|
`t* = min(bound, lambda * interruptCost / (2 * staleness))`; the defaults give 116 seconds.
|
|
215
234
|
|
|
216
235
|
Neither check ships a model, a cost or a probability. `costFalseAlarm`, `costMissedHelp`,
|
|
217
236
|
`interruptCost` and `staleness` are yours to measure, and the package has no opinion about
|
|
218
|
-
what an interruption costs your users. The
|
|
219
|
-
|
|
220
|
-
reach for is [Iqbal and Horvitz, "Disruption and recovery of computing tasks", CHI
|
|
237
|
+
what an interruption costs your users. The field measurement people usually reach for is
|
|
238
|
+
[Iqbal and Horvitz, "Disruption and recovery of computing tasks", CHI
|
|
221
239
|
2007](https://erichorvitz.com/CHI_2007_Iqbal_Horvitz.pdf), which logged real users and put
|
|
222
240
|
the return to a suspended task in the region of 11 to 16 minutes. The widely repeated "23
|
|
223
241
|
minutes 15 seconds" figure is not from a peer-reviewed paper and is not used here.
|
|
224
242
|
|
|
243
|
+
`boundedDeferral` implements the derivation in [Achlioptas and Horvitz, "Principles of
|
|
244
|
+
Bounded Deferral for Balancing Information Awareness with
|
|
245
|
+
Interruption"](http://erichorvitz.com/Bounded_Deferral.pdf): expected cost is stationary
|
|
246
|
+
where `f'(t0) = lambda * c`, so a quadratic staleness `f(t) = s * t²` gives
|
|
247
|
+
`t* = lambda * c / (2 * s)`.
|
|
248
|
+
|
|
249
|
+
## Which defaults are measured and which are ours
|
|
250
|
+
|
|
251
|
+
Every default here is either taken from a study, which is then named, or chosen by
|
|
252
|
+
judgement, which is then admitted. There is one of the first kind.
|
|
253
|
+
|
|
254
|
+
| default | where it comes from |
|
|
255
|
+
|---|---|
|
|
256
|
+
| `lambda = 1/43` in `boundedDeferral` | Measured. Achlioptas and Horvitz above: 113 Microsoft employees (42 program managers, 25 developers, 19 testers, 10 administrators, 9 managers, 4 in sales and marketing, 4 research scientists), three sequential business days between 10am and 4pm, 4,803 busy situations, mean busy session 43.12 s, standard deviation 51.79 s |
|
|
257
|
+
| `staleness = 0.0001`, `boundSeconds = 240` | Scale choices. Only the ratio `interruptCost / staleness` changes `t*`, so this pair is one way to write "a few minutes". Nothing fixes either number |
|
|
258
|
+
| `trustRamp` 7 days | Ours. No study sets it |
|
|
259
|
+
| `dismissalCooldown` 3 in 30 days buying 7 days | Ours. A dismissal is the clearest signal a user gives, so the shape is defensible; the three numbers are not from anywhere |
|
|
260
|
+
| `dailyBudget` 5 | Ours, in a supported direction. Pielot and Rello (below) cite an in-situ log study where participants received a median of 63.5 notifications a day, so a handful sits far below the ambient load. Nothing in that work says five |
|
|
261
|
+
|
|
262
|
+
The spread inside the one measured number is worth more than the number. The same paper's
|
|
263
|
+
two-subject analysis puts the mean time to a lower-cost state after an alert at 11 seconds
|
|
264
|
+
for one person and 101 seconds for the other, so the variation between two people is larger
|
|
265
|
+
than the default itself. Measure your own users before you trust it.
|
|
266
|
+
|
|
267
|
+
### Deferring is supported; silence is not free
|
|
268
|
+
|
|
269
|
+
The strongest evidence that deferral works at all is [Okoshi, Tsubouchi and Tokuda,
|
|
270
|
+
"Real-world large-scale study on adaptive notification scheduling on smartphones",
|
|
271
|
+
*Pervasive and Mobile Computing* 50:1-24
|
|
272
|
+
(2018)](https://keio.elsevierpure.com/en/publications/real-world-large-scale-study-on-adaptive-notification-scheduling-/):
|
|
273
|
+
the Yahoo! JAPAN Android app, more than 680,000 users over three weeks, where holding a
|
|
274
|
+
notification until an interruptible moment was detected cut response time by 49.7 percent
|
|
275
|
+
against immediate delivery. That supports the direction. It says nothing about any window,
|
|
276
|
+
budget or cooldown in this package.
|
|
277
|
+
|
|
278
|
+
The counterweight belongs here too, because a gate that suppresses is not free. In [Pielot
|
|
279
|
+
and Rello, "Productive, Anxious, Lonely: 24 Hours Without Push Notifications", MobileHCI
|
|
280
|
+
2017](https://arxiv.org/abs/1612.02314), 30 volunteers switched notifications off for a day.
|
|
281
|
+
They were less distracted, and they also worried about missing information, checked their
|
|
282
|
+
phones more often, and felt less connected to the people around them. Fifteen of the thirty
|
|
283
|
+
agreed they were afraid of missing something urgent. Three people approached for the study
|
|
284
|
+
refused outright, because their workplace expected them to be reachable. A silence your user
|
|
285
|
+
did not choose costs them something, and that cost does not appear in any trace this library
|
|
286
|
+
prints.
|
|
287
|
+
|
|
225
288
|
## Presets: platform quotas and legal limits, with sources
|
|
226
289
|
|
|
227
290
|
```ts
|
|
@@ -250,6 +313,25 @@ Each preset carries `sources` (the pages the numbers come from) and a `note` on
|
|
|
250
313
|
out. Reviewable defaults, not legal advice: several official sources disagree with each other,
|
|
251
314
|
and the note says which value was chosen and why.
|
|
252
315
|
|
|
316
|
+
**Read the scope before you reach for a legal preset.** Every instrument above regulates
|
|
317
|
+
*commercial* communication. `usTcpa`, `euEprivacy`, `krNetworkAct50` and `jpAntiSpamLaw` are
|
|
318
|
+
marketing rules, so they bind your message only when the message itself is commercial. A
|
|
319
|
+
reminder your user asked for is not advertising, and pulling in a marketing preset for it
|
|
320
|
+
imports a restriction the law never placed on you, which is its own kind of wrong answer.
|
|
321
|
+
Use them when the candidate is promotional; when it is not, the platform quotas and your own
|
|
322
|
+
quiet hours are the honest constraints.
|
|
323
|
+
|
|
324
|
+
That scope test is also why some jurisdictions people ask for are missing. Canada's CASL and
|
|
325
|
+
Australia's Spam Act 2003 set consent, identification and unsubscribe duties, and neither
|
|
326
|
+
carries a time-of-day rule at all. The Brazilian window quoted around the web comes from bill
|
|
327
|
+
PLS 48/2018, a proposal rather than enacted law, and it covers telemarketing calls. India is
|
|
328
|
+
the interesting one: the widely repeated "9am to 9pm" is not what the primary text says. The
|
|
329
|
+
Telecom Commercial Communications Customer Preference Regulations make time bands a
|
|
330
|
+
*preference the subscriber registers* with their access provider, alongside content category
|
|
331
|
+
and day type, not a fixed statutory quiet window, and the secondary sources that quote a
|
|
332
|
+
window disagree with each other about whether it starts at 09:00 or 10:00. A preset built on
|
|
333
|
+
that would encode a number no primary source states, so there is none.
|
|
334
|
+
|
|
253
335
|
## The budget is enforced at commit, not at evaluate
|
|
254
336
|
|
|
255
337
|
Two instances can both evaluate a candidate for the same user, both see four of
|
package/README.tr.md
CHANGED
|
@@ -182,10 +182,67 @@ bir birim tüketmez.
|
|
|
182
182
|
|
|
183
183
|
- `utilityFloor({ costFalseAlarm, costMissedHelp })` yalnızca `candidate.pAccept` değeri
|
|
184
184
|
`tau = cFA / (cFA + pNeed * cFN)` eşiğini geçtiğinde konuşur (`pNeed` varsayılanı 1);
|
|
185
|
-
`pAccept` yoksa atlar. Bu
|
|
185
|
+
`pAccept` yoksa atlar. Bu eşik klasik Bayes karar sınırıdır: konuşmanın maliyeti
|
|
186
|
+
`(1 - p) * cFA`, susmanın maliyeti `p * cFN`, hangisi küçükse o seçilir. Uyarı alanındaki
|
|
187
|
+
karşılığı [Horvitz, Jacobs ve Hovel, "Attention-Sensitive Alerting", UAI
|
|
188
|
+
1999](https://arxiv.org/abs/1301.6707); o makaledeki sistemin adı Priorities.
|
|
186
189
|
- `boundedDeferral({ lambda, interruptCost, staleness, boundSeconds })` asla reddetmez.
|
|
187
190
|
`candidate.busy` doğruysa `deliverAt` değerini `now + t*` yapar;
|
|
188
191
|
`t* = min(bound, lambda * interruptCost / (2 * staleness))`, varsayılanlar 116 saniye verir.
|
|
192
|
+
Türetim [Achlioptas ve Horvitz, "Principles of Bounded
|
|
193
|
+
Deferral"](http://erichorvitz.com/Bounded_Deferral.pdf) makalesinden.
|
|
194
|
+
|
|
195
|
+
## Hangi varsayılan ölçüldü, hangisi bizim tercihimiz
|
|
196
|
+
|
|
197
|
+
Buradaki her varsayılan ya bir çalışmadan geliyor ve kaynağı yazılıyor, ya da bir kanaat ve
|
|
198
|
+
bunu söylüyoruz. Birinci türden tek bir tane var.
|
|
199
|
+
|
|
200
|
+
| varsayılan | nereden geliyor |
|
|
201
|
+
|---|---|
|
|
202
|
+
| `boundedDeferral` içindeki `lambda = 1/43` | Ölçüm. Yukarıdaki makale: 113 çalışan, üç ardışık iş günü, 10.00 ile 16.00 arası, 4.803 meşgul durum, ortalama meşguliyet süresi 43,12 saniye, standart sapma 51,79 saniye |
|
|
203
|
+
| `staleness = 0.0001`, `boundSeconds = 240` | Ölçek tercihi. `t*` yalnızca `interruptCost / staleness` oranına bağlı; bu çift "birkaç dakika" demenin bir yolu, iki sayıyı da sabitleyen bir bulgu yok |
|
|
204
|
+
| `trustRamp` 7 gün | Bizim. Hiçbir çalışma bu sayıyı vermiyor |
|
|
205
|
+
| `dismissalCooldown` 30 günde 3 kapatma, 7 gün sessizlik | Bizim. Kapatma, kullanıcının verdiği en net sinyal olduğu için biçim savunulabilir; üç sayı bize ait |
|
|
206
|
+
| `dailyBudget` 5 | Bizim, ama yönü destekli. Pielot ve Rello'nun aktardığı yerinde günlük kayıt çalışmasında katılımcılar günde ortanca 63,5 bildirim alıyor; bir avuç mesaj bunun çok altında. O çalışma "beş" demiyor |
|
|
207
|
+
|
|
208
|
+
Ölçülen tek sayının içindeki dağılım, sayının kendisinden değerli: aynı makalenin iki kişilik
|
|
209
|
+
çözümlemesinde uyarı sonrası düşük maliyetli duruma geçiş ortalaması birinde 11, diğerinde
|
|
210
|
+
101 saniye. İki kişi arasındaki fark varsayılanın kendisinden büyük.
|
|
211
|
+
|
|
212
|
+
### Ertelemenin dayanağı var, susmanın bedeli de var
|
|
213
|
+
|
|
214
|
+
Ertelemenin işe yaradığına dair en güçlü kanıt [Okoshi, Tsubouchi ve Tokuda, *Pervasive and
|
|
215
|
+
Mobile Computing* 50:1-24
|
|
216
|
+
(2018)](https://keio.elsevierpure.com/en/publications/real-world-large-scale-study-on-adaptive-notification-scheduling-/):
|
|
217
|
+
Yahoo! JAPAN Android uygulaması, 680.000'den fazla kullanıcı, üç hafta; bildirimi uygun ana
|
|
218
|
+
kadar bekletmek yanıt süresini yüzde 49,7 kısaltmış. Bu, yönü destekler; bu paketteki
|
|
219
|
+
hiçbir pencereyi, bütçeyi veya bekleme süresini desteklemez.
|
|
220
|
+
|
|
221
|
+
Karşı ağırlık da burada durmalı, çünkü susturan bir kapı bedelsiz değil. [Pielot ve Rello,
|
|
222
|
+
MobileHCI 2017](https://arxiv.org/abs/1612.02314) çalışmasında 30 gönüllü bir gün boyunca
|
|
223
|
+
bildirimleri kapatmış. Daha az dağılmışlar, ama aynı zamanda bir şeyi kaçırmaktan
|
|
224
|
+
endişelenmiş, telefonlarına daha sık bakmış ve çevrelerinden kopuk hissetmişler. Otuz kişiden
|
|
225
|
+
on beşi acil bir şeyi kaçırmaktan korktuğunu söylemiş. Çalışma için görüşülen üç kişi,
|
|
226
|
+
işyerinde sürekli ulaşılabilir olmaları beklendiği için katılmayı reddetmiş. Kullanıcının
|
|
227
|
+
seçmediği bir sessizliğin bir bedeli var ve o bedel bu kütüphanenin yazdığı hiçbir izde
|
|
228
|
+
görünmüyor.
|
|
229
|
+
|
|
230
|
+
## Benimsemeden önce bilmeniz gereken iki sınır
|
|
231
|
+
|
|
232
|
+
İkisi de hata değil ve ikisi de testle sabitlendi, böylece ileride değişecekse bilerek değişir.
|
|
233
|
+
|
|
234
|
+
**Hafta, ISO haftasıdır; haftalık bütçe pazartesi yenilenir.** Çalışma haftası pazardan
|
|
235
|
+
perşembeye uzanan yerlerde bu yenilenme haftanın birinci gününe denk gelir: pazar günü
|
|
236
|
+
bütçesini harcayan bir kullanıcı pazartesi sabahı bütçesini geri alır ve önünde hâlâ dört
|
|
237
|
+
iş günü vardır. Anahtarı değiştirmek deponuzdaki bütün sayaçları kaydıracağı için bunu
|
|
238
|
+
sessizce değiştirmek yerine yazıyoruz. ISO haftası sizin kullanıcılarınız için yanlışsa
|
|
239
|
+
kendi bütçe kontrolünüzü istediğiniz anahtarla yazabilirsiniz.
|
|
240
|
+
|
|
241
|
+
**Sessiz saatler tek bir penceredir ve haftanın her günü aynıdır.** Kullanıcıda tek bir
|
|
242
|
+
`start` ve tek bir `end` vardır; cuma penceresi, Şabat penceresi veya resmî tatil
|
|
243
|
+
tanımlanamaz. Haftanın günü hiç okunmaz. Böyle bir kurala ihtiyacınız varsa kendi
|
|
244
|
+
kontrolünüzü yazın: `id` ve `run` taşıyan bir nesnedir, istediğiniz sırada dizilir ve izde
|
|
245
|
+
yerleşik kontrollerin yanında görünür.
|
|
189
246
|
|
|
190
247
|
## Hazır paketler: platform kotaları ve yasal sınırlar, kaynaklarıyla
|
|
191
248
|
|
|
@@ -215,6 +272,24 @@ Her paket `sources` (sayıların geldiği sayfalar) ve neyi dışarıda bırakt
|
|
|
215
272
|
`note` taşır. Gözden geçirilebilir varsayılanlar, hukuki tavsiye değil: birkaç resmi kaynak
|
|
216
273
|
birbiriyle çelişir ve not hangi değerin neden seçildiğini söyler.
|
|
217
274
|
|
|
275
|
+
**Yasal bir pakete uzanmadan önce kapsamını okuyun.** Yukarıdaki bütün düzenlemeler *ticari*
|
|
276
|
+
iletişimi düzenler. `usTcpa`, `euEprivacy`, `krNetworkAct50` ve `jpAntiSpamLaw` birer pazarlama
|
|
277
|
+
kuralıdır; yani mesajınızı ancak mesajın kendisi ticari olduğunda bağlar. Kullanıcının kendi
|
|
278
|
+
istediği bir hatırlatma reklam değildir ve onun için pazarlama paketi kullanmak, yasanın size
|
|
279
|
+
hiç koymadığı bir kısıtı kendi elinizle içeri almak olur. Aday promosyon niteliğindeyse
|
|
280
|
+
kullanın; değilse dürüst sınırlar platform kotaları ve kendi sessiz saatlerinizdir.
|
|
281
|
+
|
|
282
|
+
Bazı ülkelerin neden burada olmadığı da aynı kapsam sınavıyla açıklanır. Kanada'nın CASL'i ve
|
|
283
|
+
Avustralya'nın 2003 tarihli Spam Act'i rıza, gönderen kimliği ve abonelikten çıkma
|
|
284
|
+
yükümlülükleri getirir; ikisinde de saat kısıtı yoktur. İnternette dolaşan Brezilya penceresi
|
|
285
|
+
PLS 48/2018 sayılı kanun *teklifinden* gelir, yürürlükteki bir kanundan değil, ve
|
|
286
|
+
telefonla pazarlama aramalarını kapsar. Hindistan ilginç olanı: sıkça tekrarlanan "09.00-21.00"
|
|
287
|
+
birincil metinde yazmaz. TRAI düzenlemesi zaman bantlarını, içerik kategorisi ve gün tipiyle
|
|
288
|
+
birlikte, abonenin operatörüne *kaydettirdiği bir tercih* yapar; sabit bir yasal sessizlik
|
|
289
|
+
penceresi değildir. Üstelik pencereyi aktaran ikincil kaynaklar başlangıcın 09.00 mı 10.00 mı
|
|
290
|
+
olduğunda birbiriyle çelişir. Bunun üzerine kurulacak bir paket, hiçbir birincil kaynağın
|
|
291
|
+
yazmadığı bir sayıyı kodlardı; o yüzden yok.
|
|
292
|
+
|
|
218
293
|
## Adaptörler
|
|
219
294
|
|
|
220
295
|
| alt yol | framework | kapı nerede durur |
|
package/dist/src/checks.d.ts
CHANGED
|
@@ -36,6 +36,9 @@ export declare function quietHours(options?: {
|
|
|
36
36
|
* For the first `days` after sign-up the user hears from the system only at
|
|
37
37
|
* or above `minPriority`. A proactive assistant is least calibrated exactly
|
|
38
38
|
* when the user is least forgiving.
|
|
39
|
+
*
|
|
40
|
+
* Seven days is a judgement, not a finding. No study sets this number, and
|
|
41
|
+
* none of the literature the package cites speaks to it.
|
|
39
42
|
*/
|
|
40
43
|
export declare function trustRamp(options?: {
|
|
41
44
|
days?: number;
|
|
@@ -45,6 +48,10 @@ export declare function trustRamp(options?: {
|
|
|
45
48
|
* When the user has dismissed `dismissals` candidates of a type within
|
|
46
49
|
* `withinDays`, that type stays silent for `silenceDays`. Fed by
|
|
47
50
|
* gate.record(userId, candidate, "dismissed").
|
|
51
|
+
*
|
|
52
|
+
* Three in thirty buying seven days is a judgement, not a finding. The shape
|
|
53
|
+
* is defensible, since a dismissal is the clearest signal a user gives; the
|
|
54
|
+
* three numbers are ours and no study sets them.
|
|
48
55
|
*/
|
|
49
56
|
export declare function dismissalCooldown(options?: {
|
|
50
57
|
dismissals?: number;
|
|
@@ -74,15 +81,36 @@ export interface BudgetOptions {
|
|
|
74
81
|
export declare const budgetKey: (userId: string, now: Date, timezone?: string) => string;
|
|
75
82
|
export declare const weeklyBudgetKey: (userId: string, now: Date, timezone?: string) => string;
|
|
76
83
|
export declare const monthlyBudgetKey: (userId: string, now: Date, timezone?: string) => string;
|
|
77
|
-
/**
|
|
84
|
+
/**
|
|
85
|
+
* At most `limit` deliveries per user per local day.
|
|
86
|
+
*
|
|
87
|
+
* Five is a judgement, not a finding. The direction has support: Pielot and
|
|
88
|
+
* Rello, "Productive, Anxious, Lonely: 24 Hours Without Push Notifications",
|
|
89
|
+
* MobileHCI 2017 (https://arxiv.org/abs/1612.02314), cite an in-situ log study
|
|
90
|
+
* (Pielot, Church and de Oliveira, MobileHCI 2014) in which participants
|
|
91
|
+
* received a median of 63.5 notifications a day, so a handful is far below the
|
|
92
|
+
* ambient load. Nothing in that work says five.
|
|
93
|
+
*/
|
|
78
94
|
export declare function dailyBudget(options?: BudgetOptions): BudgetCheck;
|
|
79
|
-
/**
|
|
95
|
+
/**
|
|
96
|
+
* At most `limit` deliveries per user per local ISO week.
|
|
97
|
+
*
|
|
98
|
+
* The week is the ISO week, so the counter resets on Monday morning in the
|
|
99
|
+
* user's zone. For a Sunday-to-Thursday working week that reset lands
|
|
100
|
+
* mid-week. Documented rather than fixed; changing it would move every
|
|
101
|
+
* existing key.
|
|
102
|
+
*/
|
|
80
103
|
export declare function weeklyBudget(options?: BudgetOptions): BudgetCheck;
|
|
81
104
|
/** At most `limit` deliveries per user per local calendar month. */
|
|
82
105
|
export declare function monthlyBudget(options?: BudgetOptions): BudgetCheck;
|
|
83
106
|
/**
|
|
84
|
-
*
|
|
85
|
-
*
|
|
107
|
+
* Expected-utility alerting: act only when the caller's estimate of acceptance
|
|
108
|
+
* clears tau = cFA / (cFA + pNeed * cFN). That threshold is the classical Bayes
|
|
109
|
+
* decision boundary between the cost of alerting when the user did not want it,
|
|
110
|
+
* (1 - p) * cFA, and the cost of staying silent when they did, p * cFN.
|
|
111
|
+
* The alerting application is Horvitz, Jacobs and Hovel, "Attention-Sensitive
|
|
112
|
+
* Alerting", UAI 1999 (https://arxiv.org/abs/1301.6707); the system in that
|
|
113
|
+
* paper is named Priorities.
|
|
86
114
|
* `candidate.pAccept` and `candidate.pNeed` come from the caller's own model.
|
|
87
115
|
*/
|
|
88
116
|
export declare function utilityFloor(options: {
|
|
@@ -90,10 +118,29 @@ export declare function utilityFloor(options: {
|
|
|
90
118
|
costMissedHelp: number;
|
|
91
119
|
}): Check;
|
|
92
120
|
/**
|
|
93
|
-
* Bounded deferral
|
|
121
|
+
* Bounded deferral: when the user is busy, wait t* = min(bound,
|
|
94
122
|
* lambda * interruptCost / (2 * staleness)), the optimum of a quadratic
|
|
95
123
|
* staleness loss against the cost of interrupting a busy person, with the
|
|
96
124
|
* user becoming free at rate lambda. Never rejects; only moves deliverAt.
|
|
125
|
+
*
|
|
126
|
+
* The derivation is Achlioptas and Horvitz, "Principles of Bounded Deferral
|
|
127
|
+
* for Balancing Information Awareness with Interruption", Microsoft Research
|
|
128
|
+
* (http://erichorvitz.com/Bounded_Deferral.pdf): the expected cost is
|
|
129
|
+
* stationary where f'(t0) = lambda * c with f''(t0) > 0, so a quadratic
|
|
130
|
+
* staleness f(t) = s * t^2 gives t* = lambda * c / (2 * s).
|
|
131
|
+
*
|
|
132
|
+
* `lambda` defaults to 1/43 from the same paper's field study: 113 Microsoft
|
|
133
|
+
* employees (42 program managers, 25 developers, 19 testers, 10 administrators,
|
|
134
|
+
* 9 managers, 4 in sales and marketing, 4 research scientists), three
|
|
135
|
+
* sequential business days between 10am and 4pm, 4,803 busy situations, mean
|
|
136
|
+
* busy-session duration 43.12 s with a standard deviation of 51.79 s. That
|
|
137
|
+
* spread matters: the same paper's two-subject Interruption Workbench analysis
|
|
138
|
+
* puts the mean time to a lower-cost state after an alert at 11 s for one
|
|
139
|
+
* person and 101 s for the other. Measure your own users before trusting it.
|
|
140
|
+
*
|
|
141
|
+
* `staleness` and `boundSeconds` are scale choices, not findings. Only the
|
|
142
|
+
* ratio interruptCost / staleness affects t*, so the pair below is one way to
|
|
143
|
+
* express "a few minutes"; nothing in the literature fixes either number.
|
|
97
144
|
*/
|
|
98
145
|
export declare function boundedDeferral(options?: {
|
|
99
146
|
lambda?: number;
|
package/dist/src/checks.js
CHANGED
|
@@ -132,6 +132,9 @@ export function quietHours(options = {}) {
|
|
|
132
132
|
* For the first `days` after sign-up the user hears from the system only at
|
|
133
133
|
* or above `minPriority`. A proactive assistant is least calibrated exactly
|
|
134
134
|
* when the user is least forgiving.
|
|
135
|
+
*
|
|
136
|
+
* Seven days is a judgement, not a finding. No study sets this number, and
|
|
137
|
+
* none of the literature the package cites speaks to it.
|
|
135
138
|
*/
|
|
136
139
|
export function trustRamp(options = {}) {
|
|
137
140
|
const days = options.days ?? 7;
|
|
@@ -153,6 +156,10 @@ export function trustRamp(options = {}) {
|
|
|
153
156
|
* When the user has dismissed `dismissals` candidates of a type within
|
|
154
157
|
* `withinDays`, that type stays silent for `silenceDays`. Fed by
|
|
155
158
|
* gate.record(userId, candidate, "dismissed").
|
|
159
|
+
*
|
|
160
|
+
* Three in thirty buying seven days is a judgement, not a finding. The shape
|
|
161
|
+
* is defensible, since a dismissal is the clearest signal a user gives; the
|
|
162
|
+
* three numbers are ours and no study sets them.
|
|
156
163
|
*/
|
|
157
164
|
export function dismissalCooldown(options = {}) {
|
|
158
165
|
const n = options.dismissals ?? 3;
|
|
@@ -235,11 +242,27 @@ const isoWeekKey = (day) => {
|
|
|
235
242
|
};
|
|
236
243
|
export const weeklyBudgetKey = (userId, now, timezone) => `weeklyBudget:${userId}:${isoWeekKey(localDay(now, timezone))}`;
|
|
237
244
|
export const monthlyBudgetKey = (userId, now, timezone) => `monthlyBudget:${userId}:${localDay(now, timezone).slice(0, 7)}`;
|
|
238
|
-
/**
|
|
245
|
+
/**
|
|
246
|
+
* At most `limit` deliveries per user per local day.
|
|
247
|
+
*
|
|
248
|
+
* Five is a judgement, not a finding. The direction has support: Pielot and
|
|
249
|
+
* Rello, "Productive, Anxious, Lonely: 24 Hours Without Push Notifications",
|
|
250
|
+
* MobileHCI 2017 (https://arxiv.org/abs/1612.02314), cite an in-situ log study
|
|
251
|
+
* (Pielot, Church and de Oliveira, MobileHCI 2014) in which participants
|
|
252
|
+
* received a median of 63.5 notifications a day, so a handful is far below the
|
|
253
|
+
* ambient load. Nothing in that work says five.
|
|
254
|
+
*/
|
|
239
255
|
export function dailyBudget(options = {}) {
|
|
240
256
|
return budget({ id: "dailyBudget", label: "daily budget", defaultLimit: 5, keyFor: ({ user, now }) => budgetKey(user.id, now, user.timezone), ttlSeconds: 2 * DAY_SECONDS }, options);
|
|
241
257
|
}
|
|
242
|
-
/**
|
|
258
|
+
/**
|
|
259
|
+
* At most `limit` deliveries per user per local ISO week.
|
|
260
|
+
*
|
|
261
|
+
* The week is the ISO week, so the counter resets on Monday morning in the
|
|
262
|
+
* user's zone. For a Sunday-to-Thursday working week that reset lands
|
|
263
|
+
* mid-week. Documented rather than fixed; changing it would move every
|
|
264
|
+
* existing key.
|
|
265
|
+
*/
|
|
243
266
|
export function weeklyBudget(options = {}) {
|
|
244
267
|
return budget({ id: "weeklyBudget", label: "weekly budget", defaultLimit: 20, keyFor: ({ user, now }) => weeklyBudgetKey(user.id, now, user.timezone), ttlSeconds: 8 * DAY_SECONDS }, options);
|
|
245
268
|
}
|
|
@@ -251,8 +274,13 @@ export function monthlyBudget(options = {}) {
|
|
|
251
274
|
/* Optional, caller-fed checks. Off by default; the package ships no model. */
|
|
252
275
|
/* ------------------------------------------------------------------------ */
|
|
253
276
|
/**
|
|
254
|
-
*
|
|
255
|
-
*
|
|
277
|
+
* Expected-utility alerting: act only when the caller's estimate of acceptance
|
|
278
|
+
* clears tau = cFA / (cFA + pNeed * cFN). That threshold is the classical Bayes
|
|
279
|
+
* decision boundary between the cost of alerting when the user did not want it,
|
|
280
|
+
* (1 - p) * cFA, and the cost of staying silent when they did, p * cFN.
|
|
281
|
+
* The alerting application is Horvitz, Jacobs and Hovel, "Attention-Sensitive
|
|
282
|
+
* Alerting", UAI 1999 (https://arxiv.org/abs/1301.6707); the system in that
|
|
283
|
+
* paper is named Priorities.
|
|
256
284
|
* `candidate.pAccept` and `candidate.pNeed` come from the caller's own model.
|
|
257
285
|
*/
|
|
258
286
|
export function utilityFloor(options) {
|
|
@@ -270,10 +298,29 @@ export function utilityFloor(options) {
|
|
|
270
298
|
}
|
|
271
299
|
const round3 = (n) => Math.round(n * 1000) / 1000;
|
|
272
300
|
/**
|
|
273
|
-
* Bounded deferral
|
|
301
|
+
* Bounded deferral: when the user is busy, wait t* = min(bound,
|
|
274
302
|
* lambda * interruptCost / (2 * staleness)), the optimum of a quadratic
|
|
275
303
|
* staleness loss against the cost of interrupting a busy person, with the
|
|
276
304
|
* user becoming free at rate lambda. Never rejects; only moves deliverAt.
|
|
305
|
+
*
|
|
306
|
+
* The derivation is Achlioptas and Horvitz, "Principles of Bounded Deferral
|
|
307
|
+
* for Balancing Information Awareness with Interruption", Microsoft Research
|
|
308
|
+
* (http://erichorvitz.com/Bounded_Deferral.pdf): the expected cost is
|
|
309
|
+
* stationary where f'(t0) = lambda * c with f''(t0) > 0, so a quadratic
|
|
310
|
+
* staleness f(t) = s * t^2 gives t* = lambda * c / (2 * s).
|
|
311
|
+
*
|
|
312
|
+
* `lambda` defaults to 1/43 from the same paper's field study: 113 Microsoft
|
|
313
|
+
* employees (42 program managers, 25 developers, 19 testers, 10 administrators,
|
|
314
|
+
* 9 managers, 4 in sales and marketing, 4 research scientists), three
|
|
315
|
+
* sequential business days between 10am and 4pm, 4,803 busy situations, mean
|
|
316
|
+
* busy-session duration 43.12 s with a standard deviation of 51.79 s. That
|
|
317
|
+
* spread matters: the same paper's two-subject Interruption Workbench analysis
|
|
318
|
+
* puts the mean time to a lower-cost state after an alert at 11 s for one
|
|
319
|
+
* person and 101 s for the other. Measure your own users before trusting it.
|
|
320
|
+
*
|
|
321
|
+
* `staleness` and `boundSeconds` are scale choices, not findings. Only the
|
|
322
|
+
* ratio interruptCost / staleness affects t*, so the pair below is one way to
|
|
323
|
+
* express "a few minutes"; nothing in the literature fixes either number.
|
|
277
324
|
*/
|
|
278
325
|
export function boundedDeferral(options = {}) {
|
|
279
326
|
const lambda = options.lambda ?? 1 / 43;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "proactive-gate",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Decide whether a proactive AI agent may reach a user right now, and log why not. Ordered checks as code or JSON, a conformance spec, presets for platform and legal limits, adapters for AI SDK, Mastra, LangChain and OpenAI Agents, and a Python sibling.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/src/index.js",
|