portal: 90% threshold for budget notices; sweep 3x a day
Trigger becomes proportional (90% of the balance used) instead of a $2 floor, so a member on $15 and one on $35 are treated the same. The once-per-crossing latch keys on the same fraction. membership_sweep moves from '0 3 * * *' to '0 3,11,19 * * *' so the notice arrives within hours of crossing rather than up to a day later. BUDGET_NOTICES remains off.
This commit is contained in:
1 parent
26be6156ae
commit
7d2507a927
2 files changed
+9
-8
No files matched your search
@@ -15,7 +15,7 @@
|
|||||||
"sendmail": {},
|
"sendmail": {},
|
||||||
"scheduler": {
|
"scheduler": {
|
||||||
"membership_sweep": {
|
"membership_sweep": {
|
||||||
"schedule": "0 3 * * *",
|
"schedule": "0 3,11,19 * * *",
|
||||||
"command": "/app/code/sweep.sh"
|
"command": "/app/code/sweep.sh"
|
||||||
},
|
},
|
||||||
"rag_refresh": {
|
"rag_refresh": {
|
||||||
|
|||||||
+8
-7
@@ -3973,9 +3973,10 @@ async def index():
|
|||||||
|
|
||||||
# Generic on purpose: the balance can be short because the monthly allowance is spent,
|
# Generic on purpose: the balance can be short because the monthly allowance is spent,
|
||||||
# because purchased credit is spent, or a mix — the letter says "balance" so it is true
|
# because purchased credit is spent, or a mix — the letter says "balance" so it is true
|
||||||
# either way. The trigger is an absolute floor, not a percentage, so it means the same
|
# either way. The trigger is proportional — 90% of the balance used — so it means the same
|
||||||
# thing to a member on a $15 allowance and a member on $35.
|
# thing to a member on $15 and one on $35: the letter arrives when roughly a tenth is left,
|
||||||
BUDGET_LOW_WATER = float(os.environ.get("BUDGET_LOW_WATER", "2.00"))
|
# on whichever sweep follows, rather than at an arbitrary absolute amount.
|
||||||
|
BUDGET_NOTICE_AT = float(os.environ.get("BUDGET_NOTICE_AT", "0.90"))
|
||||||
|
|
||||||
PINE_LOGO_SVG = """<svg width="56" height="56" viewBox="0 0 130 130" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
PINE_LOGO_SVG = """<svg width="56" height="56" viewBox="0 0 130 130" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
||||||
<circle cx="65" cy="14" r="5" fill="#5b8c5a"/>
|
<circle cx="65" cy="14" r="5" fill="#5b8c5a"/>
|
||||||
@@ -4131,10 +4132,10 @@ async def budget_notice_sweep(send: bool = False) -> dict:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
remaining = float(budget) - spend
|
remaining = float(budget) - spend
|
||||||
if remaining >= BUDGET_LOW_WATER:
|
if spend < BUDGET_NOTICE_AT * float(budget):
|
||||||
# Back above the line — they added funds, or the allowance reset. Clear the latch so
|
# Above the line — they added funds, or the allowance reset. Clear the latch so the
|
||||||
# the NEXT crossing sends a fresh notice. That is what makes it once each time, and
|
# NEXT crossing sends a fresh notice. That is what makes it once each time, and it
|
||||||
# it is why this is a latch rather than a key on the budget cycle.
|
# is why this is a latch rather than a key on the budget cycle.
|
||||||
conn.execute("DELETE FROM budget_notices WHERE email = ?", (email,))
|
conn.execute("DELETE FROM budget_notices WHERE email = ?", (email,))
|
||||||
conn.commit()
|
conn.commit()
|
||||||
continue
|
continue
|
||||||
|
|||||||
Reference in new issue
Block a user