feat(mbw-v4): support current_date param for historical follow-up rate computation#199
feat(mbw-v4): support current_date param for historical follow-up rate computation#199aliflaming wants to merge 3 commits into
Conversation
…e computation Allows the auditing job to compute metrics as of a past date by overriding `now` in the follow-up rate calculation. Used by the dashboard to fetch each FLW's baseline follow-up rate at the time their task was triggered. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🤖 AI Code ReviewPR Diff Review🔵 SuggestionsDate injection patternLocation: Line 269 The change allows overriding "current date" via now = date.fromisoformat(job_config["current_date"]) if job_config.get("current_date") else datetime.now(tz=timezone.utc).date()Suggestions:
Improved version: # Allow current_date override for testing
if current_date_str := job_config.get("current_date"):
try:
now = date.fromisoformat(current_date_str)
except (ValueError, TypeError) as e:
raise ValueError(f"Invalid current_date format: {current_date_str}") from e
else:
now = datetime.now(tz=timezone.utc).date()Overall Assessment✅ Code looks functionally correct for its intended purpose. The change enables date injection for testing time-dependent logic, which is a good practice. The main concern is lack of input validation, but this may be acceptable depending on how Powered by Claude — auto-generated review |
- Adds time_start field to pipeline 3050 (form.meta.timeStart) - Computes minute_per_visit as median(timeEnd - timeStart) per FLW - Computes travel_time as median gap between end of one visit and start of the next for the same FLW (cross-day gaps >8h excluded) - Both metrics respect task_filters for Tab 2 post-task computation Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🤖 AI Code ReviewPR Review: MBW Auditing V4 - Visit Duration & Travel Time MetricsSummaryThis PR adds calculation of visit duration and inter-visit travel time metrics. The implementation is generally solid but has a few issues that need attention. 🔴 Critical Issues1. Potential KeyError on Missing FieldsLocation: Lines 222-223, 235 ts_start = row.get("time_start") or ""
ts_end = row.get("visit_datetime") or ""Issue: If Impact: Fix: Only append to if 0 < mins < 300:
visit_durations.setdefault(username, []).append(mins)
visit_times.setdefault(username, []).append((ts_start, ts_end))🟡 Warnings1. Hardcoded Magic NumbersLocation: Lines 229, 254 if 0 < mins < 300: # 5 hours
if 0 < gap_mins < 480: # 8 hoursIssue: Magic numbers scattered in code without constants. Recommendation: Define constants at module level: MAX_VISIT_DURATION_MINUTES = 300 # 5 hours
MAX_INTER_VISIT_GAP_MINUTES = 480 # 8 hours2. Assumption About Data OrderingLocation: Lines 234-235, comment # visits_sorted is chronological so appending here is already ordered per FLW.
visit_times.setdefault(username, []).append((ts_start, ts_end))Issue: Code assumes Recommendation: Either verify the sort order guarantees per-FLW chronological ordering, or add explicit sorting: for u, vt_list in visit_times.items():
vt_sorted = sorted(vt_list, key=lambda x: x[0]) # sort by start time
for i in range(1, len(vt_sorted)):
# ... gap calculation3. Configuration Change Without Migration PathLocation: Line 301 now = date.fromisoformat(job_config["current_date"]) if job_config.get("current_date") else datetime.now(tz=timezone.utc).date()Issue: Introduces new optional config parameter. If not documented, existing jobs may behave unexpectedly. Recommendation: Ensure 🔵 Suggestions1. Median Calculation DuplicationLocation: Lines 424-426, 432-434 sorted_dur = sorted(durations)
minute_per_visit = round(sorted_dur[len(sorted_dur) // 2])Suggestion: Extract to helper function to avoid duplication: def median_rounded(values: list[float]) -> int | None:
if not values:
return None
sorted_vals = sorted(values)
return round(sorted_vals[len(sorted_vals) // 2])2. Type Annotation for visit_timesLocation: Line 161 visit_times: dict[str, list[tuple]] = {} # username → [(time_start, time_end)]Suggestion: Make tuple type explicit: visit_times: dict[str, list[tuple[str, str]]] = {}3. Edge Case: Zero-Length ListsLocation: Lines 424, 432 ✅ Good Practices Observed
RecommendationFix the critical issue (#1) before merging to prevent runtime exceptions. Address warnings for production robustness. Powered by Claude — auto-generated review |
…-metric denominators - Counts visits_completed per FLW (all forms passing task_filters) - Tracks eligible_mothers_visited (mothers with eligible_full_intervention_bonus=1 who were visited in the task window) - Exposes per-metric denominators (followup_rate_denom, ebf_denom, gps_denom, duration_denom, travel_time_denom) to support frontend "Not enough data" guard Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
🤖 AI Code ReviewCode ReviewOverall, this PR adds new metrics for visit duration and inter-visit travel time calculation. The implementation is generally sound, but there are some issues to address. 🔴 Critical Issues1. Potential Timezone Mismatch now = date.fromisoformat(job_config["current_date"]) if job_config.get("current_date") else datetime.now(tz=timezone.utc).date()
🟡 Warnings1. Inconsistent Variable Naming flw_summaries.append({
"username": u,
"display_name": flw_names.get(u) or flw_names.get(username) or u,
...
})
2. Magic Numbers Without Constants if 0 < mins < 300: # sanity: 0–5 hours
if 0 < gap_mins < 480: # sanity: 0–8 hours
3. No Validation of Time Ordering mins = (t1 - t0).total_seconds() / 60
if 0 < mins < 300:
🔵 Suggestions1. Potential Performance Issue eligible_mothers_visited = sum(1 for mid in mothers_visited if mother_eligibility.get(mid, False))
2. Median Calculation Readability minute_per_visit = round(sorted_dur[len(sorted_dur) // 2])
3. Type Safety visit_times: dict[str, list[tuple]] = {}
✅ Good Practices Observed
SummaryThe code is functional but has one potential bug (the Powered by Claude — auto-generated review |
Product Description
Adds
current_dateparameter support to the MBW Auditing V4 job handler, enabling the dashboard to compute follow-up rate metrics as of a historical date. This powers the new "baseline at task trigger" feature on Tab 2 (Improvements within Audit): each FLW's follow-up rate is shown with a parenthetical(↑ from X%)/(↓ from X%)/(= X%)indicating change since their task was first triggered.Technical Summary
One-line change in
handle_mbw_auditing_v4_job: whenjob_config["current_date"]is provided,nowis set to that date instead ofdatetime.now(). This lets the frontend run historical job instances withcurrent_dateset to each FLW's task trigger date to get the baseline follow-up rate.Safety Assurance
current_datedefaults todate.today()when not supplied, identical to previous behaviour🤖 Generated with Claude Code