Files
order-bak/docs/superpowers/plans/2026-04-21-backup-days-param.md
2026-04-21 22:31:05 +08:00

8.6 KiB

Backup --days Parameter Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Replace --start/--end date range params on the backup command with a single --days N param that backs up all orders older than N days.

Architecture: The backup_orders function currently takes start/end datetime pair and filters [start, end). We change it to accept a single cutoff datetime and filter OrderTime < cutoff. The CLI computes cutoff = today_00_utc - timedelta(days=N).

Tech Stack: Python, Click, pytest


Task 1: Update backup.py — change function signature and filter logic

Files:

  • Modify: src/order_bak/backup.py

  • Step 1: Write the failing test

In tests/test_backup.py, add a new test that uses the cutoff signature:

def test_backup_filters_by_cutoff(tmp_path):
    scan_dir = tmp_path / "scan"
    backup_dir = tmp_path / "backups"
    scan_dir.mkdir()
    backup_dir.mkdir()

    entities = [
        _make_entity("u1", "o1", datetime(2025, 1, 1, 10, tzinfo=timezone.utc), 1),
        _make_entity("u2", "o2", datetime(2025, 1, 2, 8, tzinfo=timezone.utc), 1),
        _make_entity("u3", "o3", datetime(2025, 1, 3, 8, tzinfo=timezone.utc), 1),  # at/after cutoff
    ]
    scan_csv = scan_dir / "scan-2025-01-03.csv"
    write_csv(scan_csv, entities)

    cutoff = datetime(2025, 1, 3, tzinfo=timezone.utc)
    result = backup_orders(
        scan_path=scan_csv,
        backup_dir=backup_dir,
        cutoff=cutoff,
    )

    assert result == 2
    assert (backup_dir / "2025-01-01.csv").exists()
    assert (backup_dir / "2025-01-02.csv").exists()
    assert not (backup_dir / "2025-01-03.csv").exists()
  • Step 2: Run test to verify it fails

Run: uv run pytest tests/test_backup.py::test_backup_filters_by_cutoff -v Expected: FAIL — backup_orders() got unexpected keyword cutoff

  • Step 3: Update backup_orders signature and filter

In src/order_bak/backup.py, replace the function:

def backup_orders(
    scan_path: Path,
    backup_dir: Path,
    cutoff: datetime,
) -> int:
    backup_dir.mkdir(parents=True, exist_ok=True)

    by_date: dict[str, list[dict]] = defaultdict(list)

    with open(scan_path, newline="") as f:
        reader = csv.DictReader(f)
        for raw_row in reader:
            entity = parse_row(raw_row)
            if entity["state"] != 1:
                continue
            order_time = entity["OrderTime"]
            if order_time is None:
                continue
            if order_time >= cutoff:
                continue
            date_key = order_time.strftime("%Y-%m-%d")
            by_date[date_key].append(entity)

    total = 0
    for date_key, rows in sorted(by_date.items()):
        csv_path = backup_dir / f"{date_key}.csv"
        with open(csv_path, "w", newline="") as f:
            writer = csv.DictWriter(f, fieldnames=CSV_COLUMNS)
            writer.writeheader()
            writer.writerows(entity_to_row(r) for r in rows)
        total += len(rows)
        print(f"  {date_key}: {len(rows)} orders")

    print(f"Backup complete: {total} orders across {len(by_date)} days")
    return total
  • Step 4: Run test to verify it passes

Run: uv run pytest tests/test_backup.py::test_backup_filters_by_cutoff -v Expected: PASS

  • Step 5: Update the old test to use cutoff

In tests/test_backup.py, replace test_backup_filters_by_state_and_date:

def test_backup_filters_by_state_and_date(tmp_path):
    scan_dir = tmp_path / "scan"
    backup_dir = tmp_path / "backups"
    scan_dir.mkdir()
    backup_dir.mkdir()

    entities = [
        _make_entity("u1", "o1", datetime(2025, 1, 1, 10, tzinfo=timezone.utc), 1),
        _make_entity("u2", "o2", datetime(2025, 1, 1, 12, tzinfo=timezone.utc), 0),  # Payed, skip
        _make_entity("u3", "o3", datetime(2025, 1, 2, 8, tzinfo=timezone.utc), 1),
        _make_entity("u4", "o4", datetime(2025, 1, 3, 8, tzinfo=timezone.utc), 1),  # at cutoff, skip
    ]
    scan_csv = scan_dir / "scan-2025-01-03.csv"
    write_csv(scan_csv, entities)

    result = backup_orders(
        scan_path=scan_csv,
        backup_dir=backup_dir,
        cutoff=datetime(2025, 1, 3, tzinfo=timezone.utc),
    )

    assert result == 2
    jan1 = backup_dir / "2025-01-01.csv"
    jan2 = backup_dir / "2025-01-02.csv"
    assert jan1.exists()
    assert jan2.exists()
    assert not (backup_dir / "2025-01-03.csv").exists()

    from order_bak.csv_utils import read_csv
    jan1_rows = list(read_csv(jan1))
    assert len(jan1_rows) == 1
    assert jan1_rows[0]["RowKey"] == "o1"
  • Step 6: Run all backup tests

Run: uv run pytest tests/test_backup.py -v Expected: All PASS

  • Step 7: Commit
git add src/order_bak/backup.py tests/test_backup.py
git commit -m "refactor: change backup_orders to use cutoff param instead of start/end"

Task 2: Update cli.py — replace --start/--end with --days

Files:

  • Modify: src/order_bak/cli.py

  • Step 1: Update the backup command in cli.py

Replace the backup command function in src/order_bak/cli.py:

@main.command()
@click.option("--days", required=True, type=int, help="Backup orders older than N days (min 1)")
@click.option("--scan", "scan_path", default=None, type=click.Path(), help="Path to scan CSV (default: latest in scan_dir)")
@click.pass_context
def backup(ctx, days, scan_path):
    if days < 1:
        click.echo("Error: --days must be at least 1")
        raise SystemExit(1)

    cfg = load_config(ctx.obj["config_path"])
    from order_bak.backup import backup_orders

    if scan_path:
        scan_file = Path(scan_path)
    else:
        scan_files = sorted(cfg.scan_dir.glob("scan-*.csv"))
        if not scan_files:
            click.echo("No scan files found in scan_dir")
            raise SystemExit(1)
        scan_file = scan_files[-1]

    from datetime import timedelta
    today_utc = datetime.now(timezone.utc).replace(hour=0, minute=0, second=0, microsecond=0)
    cutoff = today_utc - timedelta(days=days)

    click.echo(f"Backing up orders before {cutoff.strftime('%Y-%m-%d')} UTC")
    backup_orders(
        scan_path=scan_file,
        backup_dir=Path("./backups"),
        cutoff=cutoff,
    )

Add from datetime import datetime, timedelta, timezone to the top-level imports (replace the existing from datetime import timezone).

  • Step 2: Run all tests

Run: uv run pytest tests/ -v Expected: All PASS (backup tests already updated in Task 1)

  • Step 3: Commit
git add src/order_bak/cli.py
git commit -m "feat: replace --start/--end with --days param on backup command"

Task 3: Add edge case tests

Files:

  • Modify: tests/test_backup.py

  • Step 1: Add edge case tests

Append to tests/test_backup.py:

def test_backup_cutoff_at_midnight(tmp_path):
    """Order exactly at cutoff midnight should be excluded."""
    scan_dir = tmp_path / "scan"
    backup_dir = tmp_path / "backups"
    scan_dir.mkdir()
    backup_dir.mkdir()

    entities = [
        _make_entity("u1", "o1", datetime(2025, 1, 3, 0, 0, 0, tzinfo=timezone.utc), 1),  # exactly at cutoff
        _make_entity("u2", "o2", datetime(2025, 1, 2, 23, 59, 59, tzinfo=timezone.utc), 1),  # just before
    ]
    scan_csv = scan_dir / "scan.csv"
    write_csv(scan_csv, entities)

    result = backup_orders(
        scan_path=scan_csv,
        backup_dir=backup_dir,
        cutoff=datetime(2025, 1, 3, tzinfo=timezone.utc),
    )

    assert result == 1
    assert (backup_dir / "2025-01-02.csv").exists()
    assert not (backup_dir / "2025-01-03.csv").exists()


def test_backup_empty_result(tmp_path):
    """No orders before cutoff returns 0 and no files."""
    scan_dir = tmp_path / "scan"
    backup_dir = tmp_path / "backups"
    scan_dir.mkdir()
    backup_dir.mkdir()

    entities = [
        _make_entity("u1", "o1", datetime(2025, 1, 5, 10, tzinfo=timezone.utc), 1),
    ]
    scan_csv = scan_dir / "scan.csv"
    write_csv(scan_csv, entities)

    result = backup_orders(
        scan_path=scan_csv,
        backup_dir=backup_dir,
        cutoff=datetime(2025, 1, 1, tzinfo=timezone.utc),
    )

    assert result == 0
    assert list(backup_dir.glob("*.csv")) == []
  • Step 2: Run all tests

Run: uv run pytest tests/test_backup.py -v Expected: All 4 tests PASS

  • Step 3: Commit
git add tests/test_backup.py
git commit -m "test: add edge cases for backup cutoff filtering"