From fd3ceb3efc99e14c9ac23f9b66733b1d9cc34620 Mon Sep 17 00:00:00 2001 From: justin Date: Tue, 2 Jun 2026 12:30:08 -0500 Subject: [PATCH] build_trucking_campaigns: add --max-per-segment / --only-segment / --send-hour for fresh-IP warmup sends Lets us fire small, controlled batches (e.g. MCS-150 only, 100/tz, sent today) while the new sending IPs warm up, instead of the full multi-segment schedule. --- scripts/build_trucking_campaigns.py | 40 +++++++++++++++++++++++++---- 1 file changed, 35 insertions(+), 5 deletions(-) diff --git a/scripts/build_trucking_campaigns.py b/scripts/build_trucking_campaigns.py index 1a07df7..043166e 100644 --- a/scripts/build_trucking_campaigns.py +++ b/scripts/build_trucking_campaigns.py @@ -365,7 +365,9 @@ def list_segments(send_date: date) -> None: conn.close() -def run(send_date: date, dry_run: bool = False, preview: bool = False) -> None: +def run(send_date: date, dry_run: bool = False, preview: bool = False, + max_per_segment: int | None = None, only_segments: set[str] | None = None, + send_hour_override: int | None = None, send_minute: int = 0) -> None: conn = psycopg2.connect(DB_URL) base_mcs150 = get_base_campaign(CAMPAIGN_MCS150_ID) @@ -391,16 +393,30 @@ def run(send_date: date, dry_run: bool = False, preview: bool = False) -> None: continue campaign_specs.append((ctype, base, seg["limit"], seg["label"])) + # Optional warmup controls: restrict to specific segments and/or cap the + # per-segment audience so a fresh-IP day-0 send stays small. + if only_segments: + campaign_specs = [s for s in campaign_specs if s[0] in only_segments] + if not campaign_specs: + LOG.error("No campaign specs match --only-segment %s", sorted(only_segments)) + conn.close() + return + if max_per_segment is not None: + campaign_specs = [ + (ct, base, min(lim, max_per_segment), label) + for (ct, base, lim, label) in campaign_specs + ] + if preview: LOG.info("PREVIEW MODE — 1 sample carrier per campaign, drafts only, " "test sends to %s, no real schedule, no mark-sent.", TEST_EMAIL) for tz, tz_cfg in TIMEZONE_CONFIG.items(): states = tz_cfg["states"] - send_hour = tz_cfg["send_hour_utc"] + send_hour = send_hour_override if send_hour_override is not None else tz_cfg["send_hour_utc"] send_at = datetime( send_date.year, send_date.month, send_date.day, - send_hour, 0, 0, tzinfo=timezone.utc + send_hour, send_minute, 0, tzinfo=timezone.utc ) for campaign_type, base, limit, label in campaign_specs: @@ -478,6 +494,14 @@ def main(): parser.add_argument("--preview", action="store_true", help="1 sample carrier/campaign, drafts only, test sends to owner, no real schedule/mark-sent") parser.add_argument("--list-segments", action="store_true", help="Report deduped audience size per deficiency segment, then exit (no writes)") parser.add_argument("--date", type=str, help="Target send date YYYY-MM-DD (default: tomorrow)") + parser.add_argument("--max-per-segment", type=int, default=None, + help="Cap audience per (segment x timezone). Use for fresh-IP warmup small sends.") + parser.add_argument("--only-segment", action="append", default=None, metavar="SEG", + help="Restrict to specific segment(s), e.g. mcs150, inactive, for_hire_boc3. Repeatable.") + parser.add_argument("--send-hour", type=int, default=None, + help="Override send hour (UTC) for ALL timezones instead of per-tz defaults.") + parser.add_argument("--send-minute", type=int, default=0, + help="Send minute (UTC), used with --send-hour. Default 0.") args = parser.parse_args() if args.date: @@ -489,8 +513,14 @@ def main(): list_segments(send_date) return - LOG.info("Building campaigns for send date %s (dry_run=%s, preview=%s)", send_date, args.dry_run, args.preview) - run(send_date, dry_run=args.dry_run, preview=args.preview) + only = set(args.only_segment) if args.only_segment else None + LOG.info("Building campaigns for send date %s (dry_run=%s, preview=%s, " + "max_per_segment=%s, only=%s, send_hour=%s)", + send_date, args.dry_run, args.preview, args.max_per_segment, + sorted(only) if only else None, args.send_hour) + run(send_date, dry_run=args.dry_run, preview=args.preview, + max_per_segment=args.max_per_segment, only_segments=only, + send_hour_override=args.send_hour, send_minute=args.send_minute) if __name__ == "__main__":