diff --git a/app/bluebeam_bci.py b/app/bluebeam_bci.py index 2984dc3..031ad7f 100644 --- a/app/bluebeam_bci.py +++ b/app/bluebeam_bci.py @@ -2,14 +2,14 @@ from pathlib import Path import argparse -import pandas as pd +import csv BASE_DIR = Path(__file__).resolve().parents[1] DATA_DIR = BASE_DIR / "data" OUTPUT_DIR = BASE_DIR / "output" def clean_value(v): - if pd.isna(v): + if v is None: return "" return str(v).strip() @@ -20,89 +20,80 @@ def bci_escape(v): v = v.replace("\n", "\\n") return v -def build_column_dict(row, exclude_cols): - data = {} - for col in row.index: - if col in exclude_cols: - continue - val = clean_value(row[col]) - if val != "": - data[col] = val - return data +def read_csv(path): + with open(path, newline="", encoding="utf-8-sig") as f: + return list(csv.DictReader(f)) def dict_to_bci_payload(d): parts = [] for k, v in d.items(): - parts.append(f"'{bci_escape(k)}':'{bci_escape(v)}'") + if clean_value(v) != "": + parts.append(f"'{bci_escape(k)}':'{bci_escape(v)}'") return "{" + ",".join(parts) + "}" def main(): parser = argparse.ArgumentParser(description="Generate Bluebeam BCI script from Markups CSV + update CSV.") - parser.add_argument("--bb-csv", default=str(DATA_DIR / "bluebeam_markups.csv"), help="Bluebeam Markups List CSV export") - parser.add_argument("--updates-csv", default=str(DATA_DIR / "bluebeam_updates.csv"), help="Your update CSV") - parser.add_argument("--out", default=str(OUTPUT_DIR / "update_columns.bci"), help="Output BCI file") - parser.add_argument("--pdf-path", default=r"C:\PATH\TO\TARGET.pdf", help="Windows path to target PDF for the BCI Open() command") - parser.add_argument("--match-column", default="Comment", help="Column in Bluebeam CSV to match against match_value") - parser.add_argument("--page-column", default="Page Index", help="Bluebeam page index column") - parser.add_argument("--id-column", default="ID", help="Bluebeam markup ID column") - parser.add_argument("--contains", action="store_true", help="Use contains match instead of exact match") + parser.add_argument("--bb-csv", default=str(DATA_DIR / "bluebeam_markups.csv")) + parser.add_argument("--updates-csv", default=str(DATA_DIR / "bluebeam_updates.csv")) + parser.add_argument("--out", default=str(OUTPUT_DIR / "update_columns.bci")) + parser.add_argument("--pdf-path", default=r"C:\PATH\TO\TARGET.pdf") + parser.add_argument("--match-column", default="Comment") + parser.add_argument("--page-column", default="Page Index") + parser.add_argument("--id-column", default="ID") + parser.add_argument("--contains", action="store_true") args = parser.parse_args() - bb_csv = Path(args.bb_csv) - updates_csv = Path(args.updates_csv) - out_path = Path(args.out) - OUTPUT_DIR.mkdir(exist_ok=True) - bb = pd.read_csv(bb_csv, dtype=str).fillna("") - upd = pd.read_csv(updates_csv, dtype=str).fillna("") + bb_rows = read_csv(args.bb_csv) + update_rows = read_csv(args.updates_csv) + + if not bb_rows: + raise ValueError("Bluebeam CSV is empty.") + if not update_rows: + raise ValueError("Updates CSV is empty.") required_bb = {args.page_column, args.id_column, args.match_column} - missing_bb = required_bb - set(bb.columns) + missing_bb = required_bb - set(bb_rows[0].keys()) if missing_bb: raise ValueError(f"Bluebeam CSV missing required columns: {sorted(missing_bb)}") - required_upd = {"match_value"} - missing_upd = required_upd - set(upd.columns) - if missing_upd: - raise ValueError(f"Updates CSV missing required columns: {sorted(missing_upd)}") - - lines = [] - lines.append(f"Open('{args.pdf_path}', '')") + if "match_value" not in update_rows[0]: + raise ValueError("Updates CSV missing required column: match_value") + lines = [f"Open('{args.pdf_path}', '')"] total_matches = 0 - for _, urow in upd.iterrows(): - match_value = clean_value(urow["match_value"]) + for urow in update_rows: + match_value = clean_value(urow.get("match_value")) if not match_value: continue - if args.contains: - mask = bb[args.match_column].astype(str).str.contains(match_value, na=False, regex=False) - else: - mask = bb[args.match_column].astype(str).str.strip() == match_value - - matches = bb[mask] - - col_data = build_column_dict(urow, exclude_cols={"match_value"}) + col_data = {k: v for k, v in urow.items() if k != "match_value" and clean_value(v) != ""} if not col_data: continue payload = dict_to_bci_payload(col_data) - for _, brow in matches.iterrows(): - page_index = clean_value(brow[args.page_column]) - markup_id = clean_value(brow[args.id_column]) + for brow in bb_rows: + target = clean_value(brow.get(args.match_column)) - if page_index == "" or markup_id == "": + matched = match_value in target if args.contains else target == match_value + if not matched: + continue + + page_index = clean_value(brow.get(args.page_column)) + markup_id = clean_value(brow.get(args.id_column)) + + if not page_index or not markup_id: continue lines.append(f'ColumnDataSet({page_index},"{markup_id}","{payload}")') total_matches += 1 - lines.append("Save()") - lines.append("Close()") + lines += ["Save()", "Close()"] + out_path = Path(args.out) out_path.write_text("\n".join(lines) + "\n", encoding="utf-8") print(f"WROTE: {out_path}")