From 07472711e7663dcc37ff2005c75030c1da8ef949 Mon Sep 17 00:00:00 2001 From: dat972 Date: Tue, 21 Jul 2026 07:24:50 -0500 Subject: [PATCH] had to error handle the reog script to add safe handling of weird folder paths with odd characaters --- README.md | 2 + __pycache__/fccs_reorganize.cpython-313.pyc | Bin 7875 -> 9235 bytes fccs_reorganize.py | 109 +++++++++++++------- 3 files changed, 73 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index e8109ae..4f681ca 100644 --- a/README.md +++ b/README.md @@ -94,6 +94,8 @@ Exported filenames follow the format `{drawer_id}_{client_name}_{folder_name}_{c Files that can't be fully parsed still keep their client: the drawer ID and client name are the first two underscore-delimited tokens and stay recoverable even when the folder/date parse fails, so those files are filed under `{client_name}/_unparsed/` (retaining their original filename). Only files whose client can't be recovered at all fall back to the top-level `_unparsed/`. +Folder names are sanitized for Windows before use — trailing spaces and periods are stripped from each path component, since Windows can't create a directory ending in a space or dot (e.g. a client named `FARR GROUP, P.L.` becomes `FARR GROUP, P.L`). Reorganization is also resilient per file: if one file can't be placed for any reason, the error is logged and counted (reported as `errored` in the summary) and the run continues with the rest rather than aborting. + ### Step 4 (optional): Verify Export Completeness During export, each drawer's document list is captured from the FCCS dialog and saved as a manifest (in `manifest_dir`). These tools compare the manifests against the files actually in the export folder to confirm nothing was missed. diff --git a/__pycache__/fccs_reorganize.cpython-313.pyc b/__pycache__/fccs_reorganize.cpython-313.pyc index dc228e4a9a260e86513350bab19d761ca4ad9c09..ec4b55aeac18cffb064dc451bf02f801b31bd9b6 100644 GIT binary patch delta 2832 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 delta 1368 zcmZ8hOH3PA6n!)P4ff1HrRa&!wN4gRaG6Kszi#~^ru!`nVJe>SrH4AWeX{rY^pwkq0lG2tNZRf_ug~w zdwQ_%$1eJBiqZnKzJB%oZPA{jt=L-9{h8~QA*g8|2OvxUaEqx1EhP+EE)c;ZxQBT3 z+dmX!3>^N0X47qWfe#54Mi9XJSO7$F^0iYi0OP;3;HSDs3DlGXz+}ytPas>cvX2(% z*URQ`0|0Q_)a)34D@eB}$^gB9SA(ElplJ|f1QLF48u6Xl3<605AAXj~1q5ACQ%@lp zx{(HfT2dj65oysNvZ4{*GfVib7w@DHI85cDRPLmkJ1a=InInMRhSzdnT(hj#?3ysXf|=&8P_Eg zD_TTrP$Tk}kh~>Orxz@!SChcgK(L|?mH%ZG_%h|oQI9Q^8cyI))Ja|V#B4%U4OWZp zYI(L`%v)-#!z7r`vboTu4RZkK(9NFA*mB0Me6|W&l#v&7NZNcV_{ds`*%8}rFETiM zivPhcghxOO%`S$E#4PYsZhpgG!{8V8MywNJjz-8hNZ91KLR7ex#E-*L3@$jLxW@`d zD?ftYRyOh;TR|C?BC&#EYBo|(L>D4b=?zM)kaY&&hfaq_sw$nmNEDRQ;l&xrT~ZY> zX{ta?&%_o(vvbiIF+4v5zjE5}m<|3#TH(G^1^;xC+6jc6MmyHKsjO#A87Eq?dC-EUUa6o8Rbq{rkzVzbn z#ML6rtVZII)o47r)}E&uWyOoyE9*p-kg1EVw)A^>+9xZH)qtc|{c->5N_V__t!7u} z+}1gZynVy6ZrSBM+q@@JpXb}+1M(Y*+C8&H4is5a%AItlyh(4`xXadUvvozYH5Exl zQqg2I-Ii5m?qo-^%h|c7%B?%O`rf=bkWlO~mbIoNna~`ofRVeDAkWqvsX+(DPVQQ0f@^4!}>uP1T4)`LjWdWPu2U=EHMhDCA)a znowwZVJZ}o=+Xnir5Wi>q)1(`xh8;3z?GU>Z5SC!pH*xEep%Dw9L6yA@B {os.path.basename(dst)}") + + shutil.copy2(src, dst) + except Exception as e: + log(f" ERROR processing {filename}: {e}") + errored += 1 + continue + + # Count only after a successful copy so the totals stay accurate. + if parsed: success += 1 - - os.makedirs(dest_dir, exist_ok=True) - src = os.path.join(export_dir, filename) - # For parsed files, use just the document name; for unparsed, keep original - dest_filename = doc_name if result else filename - dst = os.path.join(dest_dir, dest_filename) - - # Handle duplicate filenames - if os.path.exists(dst): - base, fext = os.path.splitext(dest_filename) - counter = 1 - while os.path.exists(dst): - dst = os.path.join(dest_dir, f"{base}_{counter}{fext}") - counter += 1 - log(f" DUPLICATE renamed: {dest_filename} -> {os.path.basename(dst)}") - - shutil.copy2(src, dst) + else: + failed += 1 log("=" * 60) - log(f"Reorganization complete: {success} organized, {failed} unparsed") + log(f"Reorganization complete: {success} organized, {failed} unparsed, " + f"{errored} errored") if failed: log("Review unparsed files in each client's _unparsed subfolder " f"(and {os.path.join(output_dir, '_unparsed')} for any without a " "detectable client).") + if errored: + log(f"{errored} file(s) could not be placed due to errors — see the " + "ERROR lines above; these were left in the export folder.") log("=" * 60)