Development Specification

The Nag

Refinements, bug fixes, and new capabilities to take the system from functional to production-ready.
April 21, 2026 Gordon → Mustajab v1.0
Mustajab — first off, thank you for everything you've built here. The Nag is working, messages are going out, Twilio is stable, and the whole scheduling + Plepper pipeline is solid infrastructure. That's real. What follows isn't about problems with your work — it's about refinements that will let us confidently roll this out to more offices. I've organized everything so you can work through it item by item. Budget is set aside for all of this, so just let me know your timeline estimates and we'll get moving.

Contents

Issue 01

Message Tone & Content System

High Priority Bug / Enhancement

The Problem

What's happening

The current message system is binary — the entire nag is either "on track" or "behind." Both metrics (photos + reviews) are lumped together into a single status, and there's no awareness of when in the week the message fires.

Real impact: A business owner can be crushing it on reviews but missing one image upload — and the message they get says "You are behind this week" in a flat, critical tone. On Monday morning, before anyone has even had a chance to act, the first touch of the week is negative. That's killing the vibe.

Current Code

The messages are hardcoded in four places inside worker.py:

# "On track" message — worker.py:413-417 413 msg_body = ( 414 f"Hi {nag['site_name']}:\n" 415 f"You are on track this week:\n" 416 f"• Photos: {nag['photos_delta']} / {nag['photos_target']}\n" 417 f"• Reviews: {nag['reviews_delta']} / {nag['reviews_target']}\n" 418 )
# "Behind" message — worker.py:538-547 538 msg_body = ( 539 f"Hi {nag['site_name']}:\n" 540 f"You are behind this week:\n" 541 f"• Photos: {nag['photos_delta']} / {nag['photos_target']}\n" 542 f"• Reviews: {nag['reviews_delta']} / {nag['reviews_target']}\n" 543 f"Key Action items:\n" 544 )

Duplicated again at lines 718-720 and 843-845 for the single-metric code path.

What We Need

Configurable message templates with 3 dimensions

Each metric gets its own evaluation and its own message line. The tone adapts to three things:

  1. Per Metric — Photos and reviews are evaluated independently. One can be exceeding while the other is behind, and the message reflects both accurately.
  2. Per Progress Level — Three tiers:
    • Below Expectation — current < target
    • Meeting Expectation — current = target
    • Exceeding Expectation — current > target
  3. Per Time-of-Week — Different tone early vs. late:
    • Beginning — First scheduled message of the interval
    • Mid-week — Middle messages
    • End of week — Final message before interval closes

This creates a 2 × 3 × 3 = 18 template slots per nag. Each slot is a configurable string with variable interpolation.

Template Variables

{current} — Current count for this metric {target} — Goal for this metric {metric} — "photos" or "reviews" {site_name} — Business name {remaining} — target - current (if behind)

Tone Examples

Time of Week Below Expectation Meeting Exceeding
Beginning "It's early! Let's aim for {target} {metric} this week. You've got this." "Great start — already at {current}/{target} {metric}! Keep it rolling." "Incredible start! {current}/{target} {metric} already. Let's see how high we can push this!"
Mid-week "Halfway check-in: {current}/{target} {metric}. {remaining} more to hit the goal — we can close this gap." "Right on pace with {current}/{target} {metric}. Solid work this week." "You're ahead of pace! {current}/{target} {metric}. Outstanding effort."
End of week "Final stretch: {current}/{target} {metric}. Let's push to close the gap before the week ends." "Goal met! {current}/{target} {metric}. Great week — let's keep this momentum." "Amazing week! {current}/{target} {metric} — you blew it out of the water."
Implementation notes
  • Store templates in a new message_templates table keyed by nag_id, metric, progress_level, time_of_week
  • Provide default templates that ship out of the box — per-nag customization is optional
  • The worker determines time_of_week by checking the message's position among the nag's scheduled sends for that interval
  • A UI for managing these can come later — database-level config is fine for now
Issue 02

Image Counting from Reviews

Medium Priority Investigation

The Problem

When a customer leaves a review with attached photos, those images are not counted toward the image goal. Only images from the dedicated google/by-profile/images Plepper endpoint are counted.

Current Code

Image counting at worker.py:244 only pulls from the images endpoint:

244 count_pictures = json_body["results"]["google/by-profile/images"][0]["results"]

Meanwhile, review processing at worker.py:348-353 only extracts rating and time — any image data in the review response is ignored.

What We Need

Investigation + conditional fix
  1. Check whether Plepper's google/by-profile/reviews response includes image/photo data within each review record
  2. If yes — count those images and add them to the image total alongside the dedicated images endpoint count
  3. If the data surface is unreliable (images aren't consistently present in review records) — document this limitation and skip it. Don't over-engineer.
Approach

Pull a sample google/by-profile/reviews response and inspect the JSON structure. If there's an images array or photo_url field on review records, it's straightforward. If not, we move on.

Issue 03

Interval / Counting Bug

High Priority Bug Fix

The Problem

Real-world incident

A business owner received 3 reviews during the interval. The system reported only 1. They got a message saying they were failing when they should have been congratulated. This damaged trust in the system — the person who was actually doing great work got chewed out by their group.

I verified the reviews were visible on the GBP profile and within the interval dates. Something in the pipeline is dropping or miscounting them.

Suspect Areas in Code

There are several places where this could go wrong. Here's what I need audited end-to-end:

1. Date formatting in image counting

At worker.py:274, the image date is constructed as:

274 image_time = "{}-{}-{}".format(r["date_year"], r["date_month"], r["date_day"])

This can produce 2024-1-5 instead of 2024-01-05. The is_date_in_range() function at worker.py:60-72 parses with %Y-%m-%d, which should handle single digits — but this is worth verifying.

2. Review date parsing

At worker.py:352, review time is split differently:

350 review_time = r["time"].split(" ")[0] 352 if (int(r.get("rating", 0)) in (4, 5)) and (is_date_in_range(review_time, interval_start_at, interval_end_at)) == True: 353 review_counter += 1

If the Plepper time field format changes or has timezone information appended, the .split(" ")[0] could produce something is_date_in_range() can't parse — and the bare except: pass would silently skip the review.

3. Stale Plepper results

When the scheduler creates a batch job, Plepper may not have finished scraping by the time the worker polls. If the worker picks up a "Finished" response that contains stale cached data from a previous scrape, it marks the batch "completed" and moves on — the new reviews never get counted.

4. run_track deduplication

At job_scheduler.py:216:

216 if check_last_exec_date.data[0]["last_exec_date"] == now_utc.strftime("%Y-%m-%d"): 217 execution_check = 0 # Skip — already ran today

This prevents re-evaluation when a nag has multiple scheduled sends per day. If the first send fires with stale Plepper data, the second send (which might have fresh data) gets skipped entirely.

What We Need

Full-chain audit + fix
Issue 04

Discord Integration for Debugging

Medium Priority New Feature

The Problem

Right now, you can't see how the system is performing without checking Heroku logs. I can't easily correlate the text messages I receive with what the system actually computed. When something seems off, our feedback loop is slow — I describe a symptom, you dig through logs, we go back and forth.

What We Need

A Discord webhook integration in our shared server with 2–3 channels:

nag-testing
Mirror of actual nag messages — fire the same SMS content here so we can both see test output in real time without burning Twilio credits. Useful for validating message tone changes (Issue 1) and count accuracy (Issue 3).
[2026-04-21 09:02 EST] NAG: Detroit Downtown
Hi Detroit Downtown:
• Photos: 2/1 — Amazing week! You blew it out of the water.
• Reviews: 1/2 — Final stretch. 1 more to hit the goal.
Sent to: 3 recipients (primary)
nag-operations-log
Verbose, mutable log of every significant operation. Timestamps, nag names, what happened. This is the channel we'll both glance at when something seems off.
[09:00:01] SCHEDULER • Schedule matched: "Detroit Downtown" (Mon 09:00 EST)
[09:00:02] SCHEDULER • Plepper batch created: images (batch_abc123)
[09:00:02] SCHEDULER • Plepper batch created: reviews (batch_def456)
[09:01:45] WORKER • Batch abc123 finished: 3 images found, 2 in interval
[09:01:46] WORKER • Batch def456 finished: 5 reviews found, 1 is 4-5 star in interval
[09:01:46] WORKER • Status: photos ON TRACK (2/1), reviews BEHIND (1/2)
[09:01:47] WORKER • SMS sent to 3 recipients via conversation sid_xyz
nag-alerts
Optional. Errors and anomalies only — Plepper failures, Twilio errors, unexpected data formats. So we don't have to watch the full log to catch problems.
[09:01:45] ERROR • Plepper batch abc123 returned status "Failed" for Detroit Downtown images
[09:01:46] ERROR • Review date parse failed: "2024-1-5T09:00:00Z" — skipped
Implementation suggestion

Discord webhooks are the simplest path — no bot framework needed. Just store webhook URLs as env vars (DISCORD_WEBHOOK_TESTING, DISCORD_WEBHOOK_LOG, DISCORD_WEBHOOK_ALERTS) and POST to them from the worker and scheduler. A simple helper function is all it takes:

import requests def discord_log(webhook_url, message): requests.post(webhook_url, json={"content": message}, timeout=5)
Issue 05

Future: API / MCP Layer

Low Priority Roadmap

This isn't urgent — just sharing where things are headed so it's on your radar.

The nudge_server already has a solid REST API. The next evolution would be:

The practical effect: instead of clicking through the admin UI to adjust a nag's targets or add a contact to a group, I could just say it and the agent handles the API calls. Makes managing multiple offices much faster.

We can scope this as a separate project once Issues 1–4 are solid. No rush.

Next Steps

Mustajab, please take a look through this and come back with:

Budget is ready for all of this. I'm excited to get The Nag to where it needs to be so we can start rolling it out to more offices. Let's make it happen.

— Gordon