diff --git a/README.md b/README.md index bc8c673..aacbf0e 100644 --- a/README.md +++ b/README.md @@ -21,6 +21,7 @@ Extracts and organizes documents from FileCabinet CS (Thomson Reuters) using GUI | `fccs_reorganize.py` | Step 3: Parse filenames and rebuild folder structure | | `fccs_verify.py` | Step 4 (optional): Compare manifests against exported files | | `fccs_check.py` | Utility: Interactively check if specific drawers exported completely | +| `fccs_report.py` | Utility: Generate a clean client-facing HTML progress report (issues only) | | `fccs_dump_controls.py` | Utility: Dump control identifiers of an on-screen dialog | ## Setup Per Engagement @@ -115,10 +116,18 @@ Drawer ID(s): 08097 18430 > Note: because page-splitting means the number of files can't be mapped one-to-one to documents, completeness is judged by document *presence* (is each manifest document represented by at least one exported file), not by exact file counts. +**Client-facing progress report:** For a clean summary to share with the client, generate an HTML report of outstanding work only: + +``` +python fccs_report.py +``` + +This runs the same document-level check as `fccs_verify.py` but writes a self-contained, print-friendly HTML file (`report_html`, default `C:\Migration\progress_report.html`) that lists **only** the drawers with missing documents, each with the client name and the specific documents still outstanding. Fully-migrated drawers are omitted (a headline shows how many are done). Drawers with no manifest or in `crashed.txt` are excluded since they reflect internal tooling state, not client-facing progress. Open it in any browser and print to PDF to send. + ## Config Reference All scripts read from `config.ini` (or specify `--config path\to\config.ini`). -- **`[paths]`** -- backup_dir, export_dir, output_dir, drawer_id_file, completed_file, ignore_file, crashed_file, log_file, verify_report, screenshot_dir, manifest_dir, folder_list +- **`[paths]`** -- backup_dir, export_dir, output_dir, drawer_id_file, completed_file, ignore_file, crashed_file, log_file, verify_report, report_html, screenshot_dir, manifest_dir, folder_list - **`[timeouts]`** -- nav_timeout, dialog_timeout, progress_appear, progress_finish, settle, confirm_timeout - **`[controls]`** -- FCCS window class names and button titles (rarely need changing) diff --git a/__pycache__/fccs_config.cpython-313.pyc b/__pycache__/fccs_config.cpython-313.pyc index c2e53a7..4c11bb3 100644 Binary files a/__pycache__/fccs_config.cpython-313.pyc and b/__pycache__/fccs_config.cpython-313.pyc differ diff --git a/__pycache__/fccs_report.cpython-313.pyc b/__pycache__/fccs_report.cpython-313.pyc new file mode 100644 index 0000000..97fd8a9 Binary files /dev/null and b/__pycache__/fccs_report.cpython-313.pyc differ diff --git a/config.ini b/config.ini index d2e3ecf..6adb962 100644 --- a/config.ini +++ b/config.ini @@ -9,6 +9,7 @@ ignore_file = C:\Migration\ignore.txt crashed_file = C:\Migration\crashed.txt log_file = C:\Migration\run_log.txt verify_report = C:\Migration\verify_report.txt +report_html = C:\Migration\progress_report.html screenshot_dir = C:\Migration\screenshots manifest_dir = C:\Migration\manifests folder_list = fccs_folders.txt diff --git a/fccs_config.py b/fccs_config.py index 1a29108..a8921b9 100644 --- a/fccs_config.py +++ b/fccs_config.py @@ -229,6 +229,25 @@ def exported_doc_name(filename): return stem[anchors[-1].end():] +def client_name_from_files(files): + """Best-effort client name from a drawer's export filenames, or None. + + Filenames are '{drawer}_{client}_{folder}_{MM-DD-YYYY}_{doc}.ext', so the + client name is the second underscore-delimited token. Client names carry + commas/spaces but not underscores; the most common value across the drawer's + files is returned to shrug off any oddball filename. + """ + counts = {} + for f in files: + parts = f.split("_") + if len(parts) >= 2 and parts[1].strip(): + name = parts[1].strip() + counts[name] = counts.get(name, 0) + 1 + if not counts: + return None + return max(counts, key=counts.get) + + def index_files_by_drawer(export_dir): """Map each export file to its leading drawer-ID token (before first '_'). diff --git a/fccs_report.py b/fccs_report.py new file mode 100644 index 0000000..121430c --- /dev/null +++ b/fccs_report.py @@ -0,0 +1,209 @@ +""" +Utility: Generate a client-facing progress report (HTML). + +Runs the same document-level completeness check as fccs_verify.py, but produces +a clean, self-contained HTML file listing ONLY the drawers with outstanding +items and, for each, the specific documents still missing from the export. It's +meant as a "check-in progress" document you can send to the client. + +Drawers that are fully exported, that have no manifest, or that only appear in +crashed.txt are intentionally left out — the report shows outstanding work, not +internal tooling state. A headline shows how many drawers are fully migrated so +the overall progress is clear. + +Output goes to `report_html` (default C:\\Migration\\progress_report.html). + +USAGE +----- + python fccs_report.py +""" + +import os +import sys +import html +from datetime import datetime + +from fccs_config import ( + parse_args, load_config, + evaluate_drawer, index_files_by_drawer, client_name_from_files, +) + + +PAGE_TEMPLATE = """ + + + + +Migration Progress Report + + + +
+
+

Migration Progress Report

+
{date}
+
+
+
{complete}
+
Drawers migrated
+
{incomplete}
+
Drawers outstanding
+
{total}
+
Total drawers
+
+
+{content} +
+ +
+ + +""" + + +def build_drawer_block(drawer_id, client, missing): + """Return the HTML for one outstanding drawer.""" + head = f'{html.escape(drawer_id)}' + if client: + head += f' — {html.escape(client)}' + n = len(missing) + docs = "\n".join( + f"
  • {html.escape(m)}
  • " for m in missing + ) + return ( + '
    \n' + f'

    {head}

    \n' + f'
    {n} document{"s" if n != 1 else ""} ' + 'outstanding
    \n' + f' \n' + '
    ' + ) + + +def main(): + args = parse_args() + cfg = load_config(args.config) + + export_dir = cfg.get("paths", "export_dir") + manifest_dir = cfg.get("paths", "manifest_dir") + report_html = cfg.get("paths", "report_html") + + if not os.path.isdir(manifest_dir): + print(f"ERROR: manifest directory not found: {manifest_dir}") + print("Run fccs_export.py first to generate manifests.") + sys.exit(1) + if not os.path.isdir(export_dir): + print(f"ERROR: export directory not found: {export_dir}") + sys.exit(1) + + drawer_ids = sorted( + os.path.splitext(f)[0] + for f in os.listdir(manifest_dir) + if f.endswith(".txt") + ) + if not drawer_ids: + print("No manifest files found.") + sys.exit(1) + + files_by_drawer = index_files_by_drawer(export_dir) + + complete = 0 + outstanding = [] # (drawer_id, client, missing_list) + for drawer_id in drawer_ids: + files = files_by_drawer.get(drawer_id, []) + r = evaluate_drawer(drawer_id, files, manifest_dir) + if r["missing"]: + client = client_name_from_files(files) + outstanding.append((drawer_id, client, r["missing"])) + else: + complete += 1 + + total = len(drawer_ids) + date_str = f"{datetime.now():%B %d, %Y}" + + if outstanding: + content = "\n".join( + build_drawer_block(d, c, m) for d, c, m in outstanding + ) + else: + content = ('
    All drawers are fully ' + "migrated — no outstanding documents.
    ") + + page = PAGE_TEMPLATE.format( + date=html.escape(date_str), + complete=complete, + incomplete=len(outstanding), + total=total, + content=content, + ) + + with open(report_html, "w", encoding="utf-8") as f: + f.write(page) + + print(f"Progress report written to {report_html}") + print(f" {complete}/{total} drawers fully migrated, " + f"{len(outstanding)} outstanding.") + if outstanding: + print(" Outstanding: " + ", ".join(d for d, _, _ in outstanding)) + + +if __name__ == "__main__": + main()