diff --git a/README.md b/README.md index aacbf0e..eea5737 100644 --- a/README.md +++ b/README.md @@ -122,7 +122,9 @@ Drawer ID(s): 08097 18430 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. +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 and drawers with no manifest are omitted (a headline shows how many are done). Open it in any browser and print to PDF to send. + +Any outstanding drawer that also appears in `crashed.txt` (its export was aborted by an FCCS converter crash) is badged **CRASHED** and sorted to the top, and counted in the header. These are the genuine failures worth spot-checking first — as opposed to benign false positives, where a collapsed "container" document's children exported fine but the container's name lands in the filename's folder field rather than the document field, so it reads as missing. ## Config Reference diff --git a/__pycache__/fccs_report.cpython-313.pyc b/__pycache__/fccs_report.cpython-313.pyc index 97fd8a9..77e4cc0 100644 Binary files a/__pycache__/fccs_report.cpython-313.pyc and b/__pycache__/fccs_report.cpython-313.pyc differ diff --git a/__pycache__/fccs_verify.cpython-313.pyc b/__pycache__/fccs_verify.cpython-313.pyc index a41e3e0..b1e1494 100644 Binary files a/__pycache__/fccs_verify.cpython-313.pyc and b/__pycache__/fccs_verify.cpython-313.pyc differ diff --git a/fccs_report.py b/fccs_report.py index 121430c..5f0460a 100644 --- a/fccs_report.py +++ b/fccs_report.py @@ -6,10 +6,15 @@ 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. +Drawers that are fully exported or that have no manifest are left out — the +report shows outstanding work. A headline shows how many drawers are fully +migrated so overall progress is clear. + +Any outstanding drawer that also appears in crashed.txt (an FCCS converter crash +aborted its export) is badged CRASHED and sorted to the top: those are genuine +failures worth attention, as opposed to benign name-match false positives (e.g. +a collapsed "container" document whose children exported fine but whose name +lands in the filename's folder field rather than the document field). Output goes to `report_html` (default C:\\Migration\\progress_report.html). @@ -24,7 +29,7 @@ import html from datetime import datetime from fccs_config import ( - parse_args, load_config, + parse_args, load_config, load_lines, evaluate_drawer, index_files_by_drawer, client_name_from_files, ) @@ -61,6 +66,7 @@ PAGE_TEMPLATE = """ letter-spacing: .03em; margin-top: .35rem; }} .stat.good .num {{ color: #2e7d32; }} .stat.flag .num {{ color: #c0392b; }} + .stat.crash .num {{ color: #b9770e; }} .body {{ padding: 1.5rem 2.25rem 2.25rem; }} .all-clear {{ text-align: center; padding: 2.5rem 1rem; color: #2e7d32; font-size: 1.1rem; @@ -70,10 +76,17 @@ PAGE_TEMPLATE = """ margin-bottom: 1rem; }} .drawer:last-child {{ margin-bottom: 0; }} + .drawer.crashed {{ border-left: 4px solid #c0392b; background: #fdf6f5; }} .drawer h2 {{ margin: 0 0 .1rem; font-size: 1.05rem; font-weight: 620; }} .drawer .id {{ color: #486581; font-variant-numeric: tabular-nums; }} + .badge {{ + display: inline-block; margin-left: .5rem; padding: .1rem .5rem; + font-size: .72rem; font-weight: 700; letter-spacing: .04em; + text-transform: uppercase; color: #fff; background: #c0392b; + border-radius: 4px; vertical-align: middle; + }} .drawer .meta {{ color: #7b8794; font-size: .82rem; margin-bottom: .6rem; }} ul.docs {{ margin: 0; padding: 0; list-style: none; }} ul.docs li {{ @@ -105,6 +118,8 @@ PAGE_TEMPLATE = """
Drawers migrated
{incomplete}
Drawers outstanding
+
{crashed}
+
Crashed in export
{total}
Total drawers
@@ -112,24 +127,29 @@ PAGE_TEMPLATE = """ {content} + export. Drawers not shown are fully migrated. A + Crashed badge marks drawers whose export was + interrupted by an error. """ -def build_drawer_block(drawer_id, client, missing): +def build_drawer_block(drawer_id, client, missing, crashed=False): """Return the HTML for one outstanding drawer.""" head = f'{html.escape(drawer_id)}' if client: head += f' — {html.escape(client)}' + if crashed: + head += ' Crashed' n = len(missing) docs = "\n".join( f"
  • {html.escape(m)}
  • " for m in missing ) + cls = "drawer crashed" if crashed else "drawer" return ( - '
    \n' + f'
    \n' f'

    {head}

    \n' f'
    {n} document{"s" if n != 1 else ""} ' 'outstanding
    \n' @@ -145,6 +165,7 @@ def main(): export_dir = cfg.get("paths", "export_dir") manifest_dir = cfg.get("paths", "manifest_dir") report_html = cfg.get("paths", "report_html") + crashed_set = set(load_lines(cfg.get("paths", "crashed_file"))) if not os.path.isdir(manifest_dir): print(f"ERROR: manifest directory not found: {manifest_dir}") @@ -166,22 +187,27 @@ def main(): files_by_drawer = index_files_by_drawer(export_dir) complete = 0 - outstanding = [] # (drawer_id, client, missing_list) + outstanding = [] # (drawer_id, client, missing_list, crashed) 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"])) + outstanding.append((drawer_id, client, r["missing"], + drawer_id in crashed_set)) else: complete += 1 + # Crashed drawers are the genuine failures — surface them first. + outstanding.sort(key=lambda o: (not o[3], o[0])) + crashed_ids = [d for d, _, _, cr in outstanding if cr] + 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 + build_drawer_block(d, c, m, cr) for d, c, m, cr in outstanding ) else: content = ('
    All drawers are fully ' @@ -191,6 +217,7 @@ def main(): date=html.escape(date_str), complete=complete, incomplete=len(outstanding), + crashed=len(crashed_ids), total=total, content=content, ) @@ -202,7 +229,10 @@ def main(): print(f" {complete}/{total} drawers fully migrated, " f"{len(outstanding)} outstanding.") if outstanding: - print(" Outstanding: " + ", ".join(d for d, _, _ in outstanding)) + print(" Outstanding: " + ", ".join(d for d, _, _, _ in outstanding)) + if crashed_ids: + print(f" Outstanding AND crashed ({len(crashed_ids)}) " + "— spot-check these first: " + ", ".join(crashed_ids)) if __name__ == "__main__":