Spaces:
Running
Running
Download scripts/check_callback_attempts.py from Shurem/mz_chatbot: direct link, hf CLI and curl.
- Browser
- Download file 6.89 kB
-
https://huggingface.co/spaces/Shurem/mz_chatbot/resolve/main/scripts/check_callback_attempts.py
- Command line
-
hf download hf://spaces/Shurem/mz_chatbot/scripts/check_callback_attempts.py
-
curl -L -o check_callback_attempts.py https://huggingface.co/spaces/Shurem/mz_chatbot/resolve/main/scripts/check_callback_attempts.py
6.89 kB
| """ | |
| check_callback_attempts.py -- offline: the 3-checkbox re-contact tracker (crud/callback_attempts.py, | |
| 2026-09-29). No database, no network -- throwaway in-memory SQLite. | |
| uv run python scripts/check_callback_attempts.py | |
| Covers: | |
| * record_attempt() appends a UTC timestamp per call, caps at 3 (a 4th call is a no-op) | |
| * undo_last_attempt() pops the most recently checked box, is a no-op on an empty list | |
| * next_attempt_due() is None with 0 or 3 attempts recorded, and gives the right gap otherwise | |
| (48h after the 1st, 72h after the 2nd) | |
| * the weekend push: a due date that would land on Saturday or Sunday (PKT) rolls forward to Monday, | |
| same time -- checked by picking a 1st-attempt timestamp that lands the 48h-later due date ON a | |
| Saturday with NO push, to prove the push is what moves it | |
| * the three contexts (callback / no_show / proposal) are genuinely independent -- checking one does not | |
| touch the other two on the same lead | |
| * LeadOut.from_model() serializes the raw history with a "Z" suffix (matching every other | |
| backend-generated timestamp) and exposes the same due date computed above | |
| """ | |
| from __future__ import annotations | |
| import sys | |
| from datetime import datetime, timedelta | |
| from pathlib import Path | |
| sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) | |
| from sqlalchemy import create_engine # noqa: E402 | |
| from sqlalchemy.orm import sessionmaker # noqa: E402 | |
| from sqlalchemy.pool import StaticPool # noqa: E402 | |
| import app.models # noqa: E402,F401 | |
| from app.crud import callback_attempts as crud # noqa: E402 | |
| from app.crud import leads as crud_leads # noqa: E402 | |
| from app.database import Base # noqa: E402 | |
| from app.schemas.lead import LeadOut # noqa: E402 | |
| FAILED = 0 | |
| def check(label: str, ok: bool, detail: str = "") -> None: | |
| global FAILED | |
| FAILED += 0 if ok else 1 | |
| print(f" [{'ok ' if ok else 'FAIL'}] {label}" + ("" if ok else f" {detail}")) | |
| def main() -> int: | |
| engine = create_engine("sqlite://", connect_args={"check_same_thread": False}, poolclass=StaticPool) | |
| Base.metadata.create_all(engine) | |
| db = sessionmaker(bind=engine)() | |
| lead = crud_leads.add_lead_dict(db, {"company_name": "Ancient Cherry Yachts"}) | |
| print("\nrecord_attempt: appends, caps at 3") | |
| check("starts empty", (lead.callback_attempts or []) == []) | |
| crud.record_attempt(db, lead, "callback") | |
| check("1 recorded", len(lead.callback_attempts) == 1) | |
| crud.record_attempt(db, lead, "callback") | |
| crud.record_attempt(db, lead, "callback") | |
| check("3 recorded", len(lead.callback_attempts) == 3) | |
| third = list(lead.callback_attempts) | |
| crud.record_attempt(db, lead, "callback") | |
| check("4th call is a no-op, still 3", lead.callback_attempts == third) | |
| print("\nundo_last_attempt: pops the most recent, no-op on empty") | |
| crud.undo_last_attempt(db, lead, "callback") | |
| check("back to 2 after undo", len(lead.callback_attempts) == 2) | |
| crud.undo_last_attempt(db, lead, "callback") | |
| crud.undo_last_attempt(db, lead, "callback") | |
| check("back to 0 after 2 more undos", lead.callback_attempts == []) | |
| crud.undo_last_attempt(db, lead, "callback") | |
| check("undo on empty is a no-op, no crash", lead.callback_attempts == []) | |
| print("\ncontexts are independent") | |
| lead2 = crud_leads.add_lead_dict(db, {"company_name": "Raspy Darkness Marine"}) | |
| crud.record_attempt(db, lead2, "callback") | |
| crud.record_attempt(db, lead2, "no_show") | |
| crud.record_attempt(db, lead2, "no_show") | |
| check("callback has 1", len(lead2.callback_attempts) == 1) | |
| check("no_show has 2, not shared with callback's count", len(lead2.no_show_attempts) == 2) | |
| check("proposal untouched", (lead2.proposal_attempts or []) == []) | |
| print("\nnext_attempt_due: gap math") | |
| check("0 attempts -> None (nothing to schedule yet)", crud.next_attempt_due([]) is None) | |
| check("3 attempts -> None (confirm point, not a reschedule point)", crud.next_attempt_due(["a", "b", "c"]) is None) | |
| # A Wednesday timestamp: 48h later is Friday -- no weekend push needed, isolates the gap math itself | |
| # from the push logic (checked separately below). | |
| wed = datetime(2026, 9, 30, 10, 0, 0) # 2026-09-30 is a Wednesday | |
| due_after_1 = crud.next_attempt_due([wed.isoformat()]) | |
| check( | |
| "after 1 attempt, due is +48h (no weekend in the way)", | |
| due_after_1 == (wed + timedelta(hours=48)).isoformat(), | |
| str(due_after_1), | |
| ) | |
| due_after_2 = crud.next_attempt_due([wed.isoformat(), (wed + timedelta(hours=48)).isoformat()]) | |
| check( | |
| "after 2 attempts, due is +72h from the 2nd (not the 1st)", | |
| due_after_2 == (wed + timedelta(hours=48 + 72)).isoformat(), | |
| str(due_after_2), | |
| ) | |
| print("\nweekend push: a due date landing on Sat/Sun (PKT) rolls to Monday same time") | |
| # Thursday 2026-10-01, 10:00 PKT +48h (raw, no push) = Saturday 2026-10-03, 10:00 PKT -- lands ON a | |
| # weekend with NO push, so this specific timestamp proves the push actually moved it, not that it | |
| # happened to land on a weekday anyway. Stored as naive UTC (matching _utcnow()), so PKT = UTC + 5h -- | |
| # pick the UTC instant whose PKT wall-clock is Thursday 10:00 (i.e. UTC 05:00). | |
| thu_utc = datetime(2026, 10, 1, 5, 0, 0) # PKT: Thu 2026-10-01 10:00 | |
| raw_due = thu_utc + timedelta(hours=48) # PKT: Sat 2026-10-03 10:00 -- the weekend case | |
| check( | |
| "(context) the raw +48h WOULD land on a Saturday in PKT, proving a push is actually needed here", | |
| (raw_due + timedelta(hours=5)).weekday() == 5, | |
| str((raw_due + timedelta(hours=5)).weekday()), | |
| ) | |
| pushed = crud.next_attempt_due([thu_utc.isoformat()]) | |
| pushed_dt = datetime.fromisoformat(pushed) | |
| pushed_pkt_weekday = (pushed_dt + timedelta(hours=5)).weekday() | |
| check("pushed date is no longer a weekend day in PKT", pushed_pkt_weekday < 5, str(pushed_pkt_weekday)) | |
| check( | |
| "pushed to the following Monday, same wall-clock time (not shifted)", | |
| pushed_dt == raw_due + timedelta(days=2) and pushed_dt.time() == raw_due.time(), | |
| str(pushed_dt), | |
| ) | |
| print("\nLeadOut.from_model: 'Z' suffix, due date exposed") | |
| lead3 = crud_leads.add_lead_dict(db, {"company_name": "Z Suffix Check"}) | |
| crud.record_attempt(db, lead3, "proposal") | |
| out = LeadOut.from_model(lead3) | |
| check("proposal_attempts has exactly 1 entry, 'Z'-suffixed", len(out.proposal_attempts) == 1 and out.proposal_attempts[0].endswith("Z")) | |
| check("proposal_next_attempt_due is set and 'Z'-suffixed", out.proposal_next_attempt_due.endswith("Z"), out.proposal_next_attempt_due) | |
| check("callback_attempts empty on this lead", out.callback_attempts == []) | |
| check("callback_next_attempt_due empty (0 attempts)", out.callback_next_attempt_due == "") | |
| print("\nFAILED: %d" % FAILED) | |
| return 1 if FAILED else 0 | |
| if __name__ == "__main__": | |
| sys.exit(main()) | |