"""SCDMV Certificate of Compliance (COC) PDF filler. The Certificate of Compliance is South Carolina's intrastate for-hire motor carrier registration for PROPERTY carriers (everyone except passenger, household-goods, and hazardous-waste-for-disposal carriers — those go to the PSC instead). It is filed on SCDMV Form COC and mailed with a $25 fee to: SCDMV, P.O. Box 1498, Blythewood, SC 29016-0027 Two coverage classes: - E-L : low-value commodities (dump-truck-type, scrap metal, etc.) — NO cargo insurance required (liability Form E only). - E-LC : property properly insured for any cargo — needs Form E + Form H. The carrier's INSURANCE COMPANY (not agent) must file a Form E (liability) and, if E-LC, a Form H (cargo) directly with SCDMV. SCDMV does NOT accept an ACORD certificate. We collect the COC application + $25; the insurer files the Form E. This module fills the official SCDMV Form COC from intake data. Field names in the source PDF are partly auto-generated ('undefined', '1'..'6'); they are mapped here by their verified on-page positions: undefined -> Class E-L checkbox (8x8 box, y=581) undefined_2 -> Class E-LC checkbox (8x8 box, y=520) '1'..'6' -> officer/partner name+address lines Mailing Address (label is mis-assigned) -> PHYSICAL address line (y=159) undefined_3 -> mailing address line (y=137) Telephone Number / undefined_4 -> phone / fax Usage: from scripts.document_gen.templates.sc_coc_pdf_filler import fill_sc_coc path = fill_sc_coc(intake, order_number="CO-...", coverage_class="E-L") """ from __future__ import annotations import logging import os from pathlib import Path LOG = logging.getLogger("document_gen.sc_coc") try: from pypdf import PdfReader, PdfWriter except ImportError: # pragma: no cover try: from PyPDF2 import PdfReader, PdfWriter # type: ignore except ImportError: PdfReader = PdfWriter = None # type: ignore DOCS_DIR = Path(__file__).resolve().parent.parent.parent.parent / "docs" COC_FORM = DOCS_DIR / "SC COC Form.pdf" OUTPUT_DIR = Path(os.getenv("DOC_OUTPUT_DIR", "/tmp/coc-filings")) # Low-value commodities that SCDMV classifies as E-L (no cargo insurance). Scrap # metal, dump-truck aggregates, etc. Used to auto-suggest the coverage class. E_L_COMMODITY_HINTS = ( "scrap", "metal", "dump", "aggregate", "gravel", "sand", "dirt", "rock", "debris", "waste" "recycl", "junk", "salvage", "demolition", "asphalt", "concrete", "mulch", "wood chip", "fill", ) def suggest_coverage_class(intake: dict) -> str: """Best-effort E-L vs E-LC suggestion from the carrier's cargo description. Defaults to E-LC (the safer, fully-insured class) when unknown so we never under-state the insurance requirement.""" cargo = " ".join(str(intake.get(k, "")) for k in ("cargo_carried", "commodities", "commodity", "cargo", "operation_description", "legal_name")).lower() if any(h in cargo for h in E_L_COMMODITY_HINTS): return "E-L" return "E-LC" def _is_renewal(intake: dict) -> bool: return str(intake.get("coc_renewal", "")).lower() in ("yes", "true", "1") \ or bool(intake.get("existing_coc_number")) def fill_sc_coc(intake: dict, order_number: str = "", coverage_class: str | None = None) -> str: """Fill the SCDMV Form COC. Returns the path to the written PDF. coverage_class: 'E-L' or 'E-LC'. If None, auto-suggested from cargo. """ if PdfReader is None: raise ImportError("pypdf not installed") if not COC_FORM.exists(): raise FileNotFoundError(f"SC COC form not found: {COC_FORM}") coverage_class = (coverage_class or suggest_coverage_class(intake)).upper() reader = PdfReader(str(COC_FORM)) writer = PdfWriter() writer.clone_document_from_reader(reader) legal_name = intake.get("legal_name") or intake.get("entity_name") or "" phys = ", ".join(p for p in [ intake.get("address_street", "") or intake.get("phy_street", ""), intake.get("address_city", "") or intake.get("phy_city", ""), f"{intake.get('address_state','') or intake.get('phy_state','')} " f"{intake.get('address_zip','') or intake.get('phy_zip','')}".strip(), ] if p and p.strip()) mailing = ", ".join(p for p in [ intake.get("mailing_street", ""), intake.get("mailing_city", ""), f"{intake.get('mailing_state','')} {intake.get('mailing_zip','')}".strip(), ] if p and p.strip()) or phys # Officers / partners (up to 6 lines). Accept a list or single signer. officers = intake.get("officers") or [] if not officers: signer = intake.get("signer_name") or "" if signer: officers = [f"{signer} - {phys}"] field_updates = { # Business identity (the source PDF splits the business-name label across # two text lines, suffixed " 1" / " 2"; put the name on line 1). "Business Name Corporation Partnership Sole Proprietorship With or Without Trade name 1": legal_name, # The form's text fields are auto-named after the label BELOW them, so the # names are offset by one. Mapped here by verified on-page geometry: # "Mailing Address" field (y159, full width) -> PHYSICAL address line # "undefined_3" field (y137) -> MAILING address line # "Fax Number" field (y80, x170) -> TELEPHONE box # "undefined_4" field (y80, x417) -> FAX box "Mailing Address": phys, "undefined_3": mailing, "Fax Number": intake.get("phone", "") or intake.get("telephone", ""), "undefined_4": intake.get("fax", ""), } # Officer/partner lines 1..6 for i, line in enumerate(officers[:6], start=1): field_updates[str(i)] = str(line) # Checkboxes: new vs renewal, and coverage class. button_updates = {} if _is_renewal(intake): button_updates["I am renewing an existing Certificate of Compliance I understand no fee is required for this submission"] = "/On" else: button_updates["I am applying for a Certificate of Compliance for the first time I understand that a 25 fee is required for initial"] = "/On" # Class E-L (undefined) vs E-LC (undefined_2). These are tiny text boxes in # the source, so we stamp an "X" into the right one. if coverage_class == "E-L": field_updates["undefined"] = "X" else: field_updates["undefined_2"] = "X" # Apply text fields (tolerant of name mismatches across PDF revisions). for page in writer.pages: try: writer.update_page_form_field_values(page, field_updates, auto_regenerate=False) except Exception as exc: # noqa: BLE001 LOG.debug("COC text fill page warning: %s", exc) # Apply radio/checkbox states. for page in writer.pages: try: writer.update_page_form_field_values(page, button_updates, auto_regenerate=False) except Exception: pass OUTPUT_DIR.mkdir(parents=True, exist_ok=True) safe = (order_number or legal_name or "coc").replace("/", "_").replace(" ", "_") out = OUTPUT_DIR / f"SC_COC_{safe}.pdf" with open(out, "wb") as fh: writer.write(fh) LOG.info("[%s] Filled SC COC (%s) -> %s", order_number, coverage_class, out) return str(out) if __name__ == "__main__": logging.basicConfig(level=logging.INFO) demo = { "legal_name": "ALLENS SCRAP METAL LLC", "address_street": "3838 DANNY RD", "address_city": "LORIS", "address_state": "SC", "address_zip": "29569", "phone": "8435551234", "signer_name": "Mitchell Allen", "cargo_carried": "scrap metal", } p = fill_sc_coc(demo, order_number="CO-DEMO") print("wrote", p, "class:", suggest_coverage_class(demo))