updated our verify logic
This commit is contained in:
132
fccs_config.py
132
fccs_config.py
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user