updated the organizer logic so that unparased files at least still get put in the correct client folder
This commit is contained in:
@@ -84,12 +84,16 @@ output/
|
||||
2008 Form 1040 Filing Instructions.doc
|
||||
Permanent File/
|
||||
Driver's License.pdf
|
||||
_unparsed/
|
||||
(files for this client that couldn't be fully parsed, kept for review)
|
||||
_unparsed/
|
||||
(files that couldn't be parsed go here for manual review)
|
||||
(only files whose client couldn't be recovered from the filename)
|
||||
```
|
||||
|
||||
Exported filenames follow the format `{drawer_id}_{client_name}_{folder_name}_{creation_date}_{document_name}.ext`. The parser uses folder templates from `fccs_folders.txt` (with `YYYY` expanded via regex) and the creation date (`MM-DD-YYYY`) as anchors to reliably split the underscore-delimited fields. Folder names are decomposed into nested paths that match the FCCS UI structure (e.g. `2025 Tax Documents` becomes `Tax Documents/2025/`). UltraTax CS folders are matched by a built-in pattern.
|
||||
|
||||
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/`.
|
||||
|
||||
### 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.
|
||||
|
||||
Binary file not shown.
BIN
__pycache__/fccs_reorganize.cpython-313.pyc
Normal file
BIN
__pycache__/fccs_reorganize.cpython-313.pyc
Normal file
Binary file not shown.
@@ -229,19 +229,30 @@ 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.
|
||||
def client_name_from_filename(filename):
|
||||
"""Best-effort client name from a single export filename, 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.
|
||||
commas/spaces but not underscores, so this token is reliable even when the
|
||||
fuller parse (folder/date matching) fails.
|
||||
"""
|
||||
parts = filename.split("_")
|
||||
if len(parts) >= 2 and parts[1].strip():
|
||||
return parts[1].strip()
|
||||
return None
|
||||
|
||||
|
||||
def client_name_from_files(files):
|
||||
"""Best-effort client name from a drawer's export filenames, or None.
|
||||
|
||||
The most common per-file client name 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()
|
||||
name = client_name_from_filename(f)
|
||||
if name:
|
||||
counts[name] = counts.get(name, 0) + 1
|
||||
if not counts:
|
||||
return None
|
||||
|
||||
@@ -13,11 +13,14 @@ Examples:
|
||||
01069_ABRAHAM, REBEKAH L._UltraTax CS 12-31-2008_02-12-2009_2008 Form 1040 Filing Instructions.doc
|
||||
|
||||
Output structure (recreates FCCS UI nesting):
|
||||
output/{drawer_id}_{client_name}/Tax Documents/2025/{original_filename}
|
||||
output/{drawer_id}_{client_name}/UltraTax CS/12-31-2008/{original_filename}
|
||||
output/{drawer_id}_{client_name}/Permanent File/{original_filename}
|
||||
output/{client_name}/Tax Documents/2025/{original_filename}
|
||||
output/{client_name}/UltraTax CS/12-31-2008/{original_filename}
|
||||
output/{client_name}/Permanent File/{original_filename}
|
||||
|
||||
Files that cannot be parsed go to output/_unparsed/ for manual review.
|
||||
Files that cannot be parsed are still filed under their client:
|
||||
output/{client_name}/_unparsed/{original_filename}
|
||||
Only files whose client can't be recovered from the leading tokens fall back to
|
||||
the top-level output/_unparsed/ for manual review.
|
||||
"""
|
||||
|
||||
import os
|
||||
@@ -28,6 +31,7 @@ import sys
|
||||
from fccs_config import (
|
||||
parse_args, load_config, make_logger,
|
||||
load_folder_list, build_folder_patterns, decompose_folder_path,
|
||||
client_name_from_filename,
|
||||
)
|
||||
|
||||
# Regex for the creation date field (MM-DD-YYYY) bounded by underscores
|
||||
@@ -126,8 +130,17 @@ def main():
|
||||
result = parse_filename(filename, folder_patterns)
|
||||
|
||||
if result is None:
|
||||
log(f" UNPARSED: {filename}")
|
||||
dest_dir = os.path.join(output_dir, "_unparsed")
|
||||
# Still recover the client from the leading tokens so the file lands
|
||||
# in that client's folder (in an _unparsed subfolder to flag it),
|
||||
# rather than a single top-level bucket. Fall back to the top-level
|
||||
# _unparsed only when even the client can't be determined.
|
||||
client_name = client_name_from_filename(filename)
|
||||
if client_name:
|
||||
dest_dir = os.path.join(output_dir, client_name, "_unparsed")
|
||||
log(f" UNPARSED (filed under {client_name}): {filename}")
|
||||
else:
|
||||
dest_dir = os.path.join(output_dir, "_unparsed")
|
||||
log(f" UNPARSED (no client): {filename}")
|
||||
failed += 1
|
||||
else:
|
||||
drawer_id, client_name, folder_parts, date, doc_name = result
|
||||
@@ -158,7 +171,9 @@ def main():
|
||||
log("=" * 60)
|
||||
log(f"Reorganization complete: {success} organized, {failed} unparsed")
|
||||
if failed:
|
||||
log(f"Review unparsed files in: {os.path.join(output_dir, '_unparsed')}")
|
||||
log("Review unparsed files in each client's _unparsed subfolder "
|
||||
f"(and {os.path.join(output_dir, '_unparsed')} for any without a "
|
||||
"detectable client).")
|
||||
log("=" * 60)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user