"""Convert nonferrous raw reports for an approved batch.
|
|
This script is intentionally narrow: it reads a raw inventory CSV, converts
|
PDF files with pypdf, extracts basic text from Office zip containers when
|
possible, and writes conversion status rows. It does not create conclusions.
|
"""
|
|
from __future__ import annotations
|
|
import argparse
|
import csv
|
import re
|
import zipfile
|
from pathlib import Path
|
|
from pypdf import PdfReader
|
|
|
def clean_text(text: str) -> str:
|
text = text.replace("\x00", " ")
|
text = re.sub(r"[ \t]+", " ", text)
|
text = re.sub(r"\n{3,}", "\n\n", text)
|
return text.strip()
|
|
|
def convert_pdf(path: Path) -> tuple[str, int, str]:
|
reader = PdfReader(str(path))
|
parts: list[str] = []
|
for idx, page in enumerate(reader.pages, start=1):
|
page_text = page.extract_text() or ""
|
parts.append(f"\n\n[[PAGE {idx}]]\n{page_text}")
|
text = clean_text("\n".join(parts))
|
return text, len(reader.pages), ""
|
|
|
def convert_office_zip(path: Path) -> tuple[str, int, str]:
|
parts: list[str] = []
|
with zipfile.ZipFile(path) as zf:
|
names = zf.namelist()
|
for name in names:
|
lower = name.lower()
|
if not (
|
lower.endswith(".xml")
|
and (
|
lower.startswith("word/")
|
or lower.startswith("ppt/")
|
or lower.startswith("xl/sharedstrings")
|
)
|
):
|
continue
|
raw = zf.read(name).decode("utf-8", errors="ignore")
|
raw = re.sub(r"<[^>]+>", " ", raw)
|
raw = re.sub(r"\s+", " ", raw)
|
if raw.strip():
|
parts.append(f"[[{name}]]\n{raw.strip()}")
|
if not parts:
|
return "", 0, "no readable Office XML text found"
|
return clean_text("\n\n".join(parts)), 0, ""
|
|
|
def main() -> int:
|
parser = argparse.ArgumentParser()
|
parser.add_argument("--project-root", default=".")
|
parser.add_argument("--inventory", required=True)
|
parser.add_argument("--converted-dir", required=True)
|
parser.add_argument("--status-out", required=True)
|
parser.add_argument("--case-id", required=True)
|
parser.add_argument("--batch-id", required=True)
|
parser.add_argument("--run-id", required=True)
|
parser.add_argument("--only-unregistered", action="store_true")
|
args = parser.parse_args()
|
|
root = Path(args.project_root).resolve()
|
inventory = root / args.inventory
|
converted_dir = root / args.converted_dir
|
converted_dir.mkdir(parents=True, exist_ok=True)
|
status_out = root / args.status_out
|
status_out.parent.mkdir(parents=True, exist_ok=True)
|
|
with inventory.open("r", encoding="utf-8-sig", newline="") as f:
|
rows = list(csv.DictReader(f))
|
|
status_rows: list[dict[str, str | int]] = []
|
for idx, row in enumerate(rows, start=1):
|
if args.only_unregistered and row.get("already_in_source_document") == "YES":
|
continue
|
raw_path = root / row["raw_file_path"]
|
stem = raw_path.name
|
out_name = f"{stem}.txt" if not stem.lower().endswith(".txt") else stem
|
out_path = converted_dir / out_name
|
error = ""
|
page_count = 0
|
char_count = 0
|
status = "FAILED"
|
try:
|
detected = row.get("detected_type", "")
|
if detected == "PDF":
|
text, page_count, error = convert_pdf(raw_path)
|
elif detected == "ZIP_OR_OFFICE":
|
text, page_count, error = convert_office_zip(raw_path)
|
else:
|
text, page_count, error = "", 0, f"unsupported detected_type={detected}"
|
if text and not error:
|
out_path.write_text(text, encoding="utf-8")
|
char_count = len(text)
|
status = "TEXT_CONVERTED"
|
elif not error:
|
error = "empty converted text"
|
except Exception as exc: # keep batch running and record the failure
|
error = f"{type(exc).__name__}: {exc}"
|
|
status_rows.append(
|
{
|
"conversion_id": f"YS-B3-CONV-{idx:04d}",
|
"source_doc_id": row.get("inventory_id", ""),
|
"case_id": args.case_id,
|
"batch_id": args.batch_id,
|
"run_id": args.run_id,
|
"raw_file_path": row.get("raw_file_path", ""),
|
"raw_sha256": row.get("sha256", ""),
|
"detected_type": row.get("detected_type", ""),
|
"conversion_tool": "pypdf_or_office_zip_xml",
|
"converted_text_path": out_path.relative_to(root).as_posix() if status == "TEXT_CONVERTED" else "",
|
"page_count": page_count,
|
"char_count": char_count,
|
"conversion_status": status,
|
"error_message": error,
|
}
|
)
|
|
if status_rows:
|
with status_out.open("w", encoding="utf-8-sig", newline="") as f:
|
writer = csv.DictWriter(f, fieldnames=list(status_rows[0].keys()))
|
writer.writeheader()
|
writer.writerows(status_rows)
|
print(f"status_rows={len(status_rows)}")
|
print(status_out.relative_to(root))
|
return 0
|
|
|
if __name__ == "__main__":
|
raise SystemExit(main())
|