updated our verify logic

This commit is contained in:
2026-07-11 12:27:40 -05:00
parent 2b04210a23
commit fec7745092
7 changed files with 230 additions and 183 deletions

View File

@@ -157,3 +157,135 @@ def check_for_clashes(drawer_ids):
if matches:
clashes.append((short, matches))
return clashes
# ---------------------------------------------------------------------------
# MANIFEST / EXPORT-COMPLETENESS HELPERS
# ---------------------------------------------------------------------------
# Shared by fccs_verify.py (batch) and fccs_check.py (interactive) so both
# judge completeness identically.
# Trailing " Page N" (optionally "Page N of M") appended to multi-page exports.
_PAGE_RE = re.compile(r"\s*Page\s+\d+(?:\s+of\s+\d+)?\s*$", re.IGNORECASE)
# Creation-date field ("_MM-DD-YYYY_") that precedes the document name.
_DATE_ANCHOR = re.compile(r"_\d{2}-\d{2}-\d{4}_")
def load_manifest(path):
"""Load a manifest file and return one row (list of cells) per document.
Handles both formats:
- New: one document per line (tab-separated columns).
- Old: a raw ListView dump led by 'List1', then row-major cells with
3 columns per document (Drawer ID, Page Title, Application), reshaped
so the document count is correct.
"""
with open(path, "r", encoding="utf-8") as f:
lines = [line.rstrip("\n") for line in f if line.strip()]
if lines and lines[0].strip() == "List1":
cells = lines[1:]
rows = [cells[i:i + 3] for i in range(0, len(cells), 3)]
return [r for r in rows if len(r) == 3]
return [line.split("\t") for line in lines]
def manifest_doc_names(path, drawer_id):
"""Return the expected document (Page Title) names from a manifest file."""
rows = load_manifest(path)
names = []
for row in rows:
if len(row) >= 3 and row[0].strip() == drawer_id:
names.append(row[1]) # old format: DrawerID, PageTitle, Application
elif row:
names.append(row[0]) # new format: PageTitle, Application
return names
def match_key(name):
"""Normalize a document name for tolerant comparison.
Strips a trailing 'Page N' page-split suffix, then reduces to lowercase
alphanumerics so punctuation and filename-sanitization differences (FCCS
strips characters illegal in Windows filenames) don't cause false
mismatches.
"""
base = _PAGE_RE.sub("", name)
return re.sub(r"[^a-z0-9]+", "", base.lower())
def exported_doc_name(filename):
"""Extract the document-name portion from an exported filename, or None.
Format: {drawer}_{client}_{folder}_{MM-DD-YYYY}_{docname}.ext
The creation-date field is a reliable anchor; the doc name follows the last
one (client/folder fields don't carry an "_MM-DD-YYYY_" pattern).
"""
stem = os.path.splitext(filename)[0]
anchors = list(_DATE_ANCHOR.finditer(stem))
if not anchors:
return None
return stem[anchors[-1].end():]
def index_files_by_drawer(export_dir):
"""Map each export file to its leading drawer-ID token (before first '_').
The underscore boundary keeps clashing IDs separate (04289 vs 04289TS).
Returns {drawer_id: [filenames]}.
"""
index = {}
for f in os.listdir(export_dir):
if not os.path.isfile(os.path.join(export_dir, f)):
continue
token = f.split("_", 1)[0]
index.setdefault(token, []).append(f)
return index
def evaluate_drawer(drawer_id, files, manifest_dir):
"""Compare a drawer's manifest against its exported files.
`files` is the list of export filenames belonging to this drawer. Accounts
for page-splitting (a document exported as 'Name Page 1/2/...' counts as
present) and filename sanitization (via match_key).
Returns a dict:
has_manifest, manifest_path, expected (list), missing (list),
extras (list), unparsed (int), file_count (int)
"""
manifest_path = os.path.join(manifest_dir, drawer_id + ".txt")
result = {
"has_manifest": os.path.exists(manifest_path),
"manifest_path": manifest_path,
"expected": [],
"missing": [],
"extras": [],
"unparsed": 0,
"file_count": len(files),
}
if not result["has_manifest"]:
return result
expected = manifest_doc_names(manifest_path, drawer_id)
result["expected"] = expected
# Group exported files by normalized doc name; page-splits collapse together.
exported = {} # key -> list of full doc names (one entry per file/page)
unparsed = 0
for f in files:
doc = exported_doc_name(f)
if doc is None:
unparsed += 1
continue
exported.setdefault(match_key(doc), []).append(doc)
result["missing"] = [name for name in expected
if match_key(name) not in exported]
expected_keys = {match_key(n) for n in expected}
result["extras"] = [names[0] for k, names in exported.items()
if k not in expected_keys]
result["unparsed"] = unparsed
return result