Private
Public Access
0
0
Files
manual_slop/scripts/organize_reports.py
T
ed 423f260aba chore(scripts): organize_reports emits subdirs-skipped list
Self-documents that subdirectories (existing week folders + category
folders like code_path_audit/ and license_cve_audit/) are skipped
non-recursively. Surfaces in both human-readable and --json output.
2026-06-26 23:06:42 -04:00

164 lines
5.3 KiB
Python

"""Organize docs/reports/ files into week folders named <YYYY>-<MM>-<DD>.
Scheme: ShareX-style <YYYY>-<MM>-<W> where <W> resolves to the day-of-month
(zero-padded) of the week's Monday. Example: files from the week of Mon
June 22, 2026 land in docs/reports/2026-06-22/.
Only moves files from OLD weeks; the current week's files stay in place
so in-flight work isn't buried.
Date resolution per file:
1. Parse an 8-digit YYYYMMDD or YYYY-MM-DD substring from the filename
(most reports embed one, e.g. TRACK_COMPLETION_..._20260627.md).
2. Fall back to the file's mtime. NOTE: git checkout resets mtime, so
undated files may be mis-classified as 'current week' and left in place.
Idempotent: subdirectories (existing week folders) are skipped; files
already at their computed destination are skipped.
Non-recursive: only immediate children of the reports dir are scanned.
Subdirectories (existing week folders, category folders like
code_path_audit/ + license_cve_audit/) are never entered; their contents
are left untouched. Only loose .md (or other) files in the immediate
reports dir are candidates for moving.
Usage:
python scripts/organize_reports.py # dry-run; print plan
python scripts/organize_reports.py --apply # actually move files
python scripts/organize_reports.py --json # dry-run; JSON output
python scripts/organize_reports.py --dir docs/reports
"""
from __future__ import annotations
import argparse
import json
import re
import shutil
import sys
from datetime import date, timedelta
from pathlib import Path
_DATE_REGEX = re.compile(r"(\d{4})[-_]?(\d{2})[-_]?(\d{2})")
def _week_monday(d: date) -> date:
return d - timedelta(days=d.weekday())
def _parse_filename_date(name: str) -> date | None:
m = _DATE_REGEX.search(name)
if not m:
return None
y, mo, dy = int(m.group(1)), int(m.group(2)), int(m.group(3))
if not (2000 <= y < 2100 and 1 <= mo <= 12 and 1 <= dy <= 31):
return None
try:
return date(y, mo, dy)
except ValueError:
return None
def _file_week_monday(path: Path) -> date | None:
fd = _parse_filename_date(path.name)
if fd is not None:
return _week_monday(fd)
try:
mt = path.stat().st_mtime
except OSError:
return None
return _week_monday(date.fromtimestamp(mt))
def main() -> int:
parser = argparse.ArgumentParser(
description="Organize docs/reports/ files into week folders named <YYYY>-<MM>-<DD> (Monday of the file's week). Old weeks only; current week's files stay in place."
)
parser.add_argument("--apply", action="store_true", help="Actually move files (default: dry-run)")
parser.add_argument("--json", action="store_true", help="Emit JSON instead of human-readable output")
parser.add_argument("--dir", default="docs/reports", help="Reports directory (default: docs/reports)")
args = parser.parse_args()
reports = Path(args.dir)
if not reports.is_dir():
print(f"error: {reports} is not a directory", file=sys.stderr)
return 1
subdirs_skipped = [e.name for e in sorted(reports.iterdir(), key=lambda e: e.name) if e.is_dir()]
current_monday = _week_monday(date.today())
moves: list[dict] = []
skipped_current_week: list[str] = []
skipped_no_date: list[str] = []
skipped_already_in_place: list[str] = []
for entry in sorted(reports.iterdir(), key=lambda e: e.name):
if entry.is_dir():
continue
if not entry.is_file():
continue
fm = _file_week_monday(entry)
if fm is None:
skipped_no_date.append(entry.name)
continue
if fm >= current_monday:
skipped_current_week.append(entry.name)
continue
dest_dir = reports / fm.isoformat()
dest = dest_dir / entry.name
if dest.exists():
skipped_already_in_place.append(entry.name)
continue
moves.append({"src": str(entry), "dest": str(dest), "week": fm.isoformat()})
if args.json:
print(json.dumps({
"current_week_monday": current_monday.isoformat(),
"reports_dir": str(reports),
"moves": moves,
"skipped_current_week": skipped_current_week,
"skipped_no_date": skipped_no_date,
"skipped_already_in_place": skipped_already_in_place,
"subdirs_skipped": subdirs_skipped,
}, indent=2))
return 0
print(f"Current week Monday: {current_monday.isoformat()} (files from this week stay put)")
print(f"Reports dir: {reports}")
print()
if not moves:
print("Nothing to move.")
else:
tag = "MOVE" if args.apply else "DRY "
print(f"{tag}: {len(moves)} file(s)")
for mv in moves:
print(f" {tag} {mv['src']} -> {mv['dest']}")
if skipped_current_week:
print(f"\nSkipped (current week): {len(skipped_current_week)}")
for n in skipped_current_week:
print(f" - {n}")
if skipped_already_in_place:
print(f"\nSkipped (already in destination): {len(skipped_already_in_place)}")
for n in skipped_already_in_place:
print(f" - {n}")
if skipped_no_date:
print(f"\nSkipped (no parseable date): {len(skipped_no_date)}")
for n in skipped_no_date:
print(f" - {n}")
if subdirs_skipped:
print(f"\nSubdirectories skipped (non-recursive; left untouched): {len(subdirs_skipped)}")
for n in subdirs_skipped:
print(f" - {n}")
if args.apply and moves:
for mv in moves:
dest_path = Path(mv["dest"])
dest_path.parent.mkdir(parents=True, exist_ok=True)
shutil.move(mv["src"], mv["dest"])
print(f"\nApplied: moved {len(moves)} file(s).")
elif moves:
print("\nDry run. Pass --apply to move.")
return 0
if __name__ == "__main__":
sys.exit(main())