mirror of
https://github.com/warmbly/warmbly.git
synced 2026-09-05 16:02:48 +00:00
* feat: let a warmup recipient answer the mailbox that just wrote to it, so a thread reads as a conversation rather than two mailboxes monologuing on their own ramps: warmup_tasks.target_account_id was written as nil and never read by anything, so a reply only happened when the recipient's own ramp fired AND the draw happened to land on that partner; a verified receipt now sometimes re-points the recipient's pending warmup task at the sender 25 minutes to 5 hours later inside its own warmup hours, which is a re-pointing rather than new work because only one warmup task may be pending per mailbox, it can never delay a send the mailbox had planned sooner, and it stops before the thread cap so replies cannot answer replies forever; the clock parser also moves into models.ClockMinutes so a second copy of the HH:MM parsing that silently disabled every sending window cannot drift back in * feat: stop the reply-back drawing the reply rate twice, and stop its jitter escaping a short warmup window: the scheduler drew the recipient's reply rate to decide whether to answer at all, then the task handler drew it again to decide reply-versus-new, so a 30 percent reply rate produced a 9 percent answer rate and a directed task could send a fresh message to the mailbox it was meant to be answering; a directed task now IS the reply, and the opening-time jitter is capped to the window width so a mailbox warming 09:00 to 09:20 is not scheduled past its own close
31 lines
930 B
Go
31 lines
930 B
Go
package models
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// ClockMinutes parses "HH:MM" into minutes since midnight, tolerating trailing
|
|
// seconds and a fraction. Returns fallback for anything it cannot read.
|
|
//
|
|
// The tolerance is the point: start_time, end_time, warmup_start_time and
|
|
// warmup_end_time are Postgres `time` columns that arrive as "09:00:00.000000",
|
|
// while the app writes "09:00". A parser that accepted only the second form
|
|
// silently disabled every campaign's sending window. One definition, so a
|
|
// second caller cannot reintroduce that.
|
|
func ClockMinutes(v string, fallback int) int {
|
|
parts := strings.Split(strings.TrimSpace(v), ":")
|
|
if len(parts) < 2 {
|
|
return fallback
|
|
}
|
|
hour, err := strconv.Atoi(parts[0])
|
|
if err != nil || hour < 0 || hour > 23 {
|
|
return fallback
|
|
}
|
|
minute, err := strconv.Atoi(parts[1])
|
|
if err != nil || minute < 0 || minute > 59 {
|
|
return fallback
|
|
}
|
|
return hour*60 + minute
|
|
}
|