From 3b950cf886966a6443def9a2bc6f0c02e7b1a4a2 Mon Sep 17 00:00:00 2001 From: dat972 Date: Mon, 20 Jul 2026 19:44:34 -0500 Subject: [PATCH] updated the report so that we will flag drawers that had errors and also crashed --- README.md | 4 +- __pycache__/fccs_report.cpython-313.pyc | Bin 9016 -> 10918 bytes __pycache__/fccs_verify.cpython-313.pyc | Bin 4975 -> 4980 bytes fccs_report.py | 54 ++++++++++++++++++------ 4 files changed, 45 insertions(+), 13 deletions(-) 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 97fd8a970a2beda7eebfe7b6f60a6287046878bc..77e4cc089d835c6cb51e3406160f7de6287790fa 100644 GIT binary patch delta 4383 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 diff --git a/__pycache__/fccs_verify.cpython-313.pyc b/__pycache__/fccs_verify.cpython-313.pyc index a41e3e0f174d340d1bbc26369a507a9c4bfee762..b1e1494ad5634177431bce0df3928f501e8e66ae 100644 GIT binary patch delta 55 zcmaE__C<~NGcPX}0}yD}24z`qav*8WiI2*N&-R5 E0G^%?>;M1& 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}
Generated {date} · Lists documents not yet present in the - export. Drawers not shown are fully migrated.
+ 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__":