#!/usr/bin/env python3 """ Foundation Accounts pipeline — Step 3: convert bulk_runner responses → CSV. Reads `foundation_vendors_responses.json`, and (optionally) merges the Main Client Contact block (columns M–S) from the source spreadsheet by account name. That contact/address block has no ows-account endpoint, so it is carried here for manual follow-up rather than sent during account creation. Basic (vendor_id/uuid/name/link only): uv run python src/pipelines/foundation_accounts/3_postprocess.py With contact block: uv run python src/pipelines/foundation_accounts/3_postprocess.py \\ --runner-csv data/input/foundation_vendors.csv \\ --source-csv "data/prepare/Foundation Accounts - Filled in Orchard Spreadsheet.csv" """ import argparse import csv import json import sys # Source column indices for the Main Client Contact block (M–S). CONTACT_COLS: dict[str, int] = { 'main_client_contact_name': 12, 'main_client_contact_email': 13, 'street_address': 14, 'city': 15, 'contact_country': 16, 'zipcode': 17, 'phone': 18, } OUTPUT_COLS_BASE = ['name', 'vendor_id', 'vendor_uuid', 'link'] OUTPUT_COLS_FULL = OUTPUT_COLS_BASE + list(CONTACT_COLS.keys()) def load_runner_csv(path: str) -> dict[int, str]: """Row num (1-based) → account name.""" with open(path, newline='', encoding='utf-8-sig') as f: rows = list(csv.DictReader(f)) return {i + 1: row['name'].strip() for i, row in enumerate(rows)} def load_source_contacts(path: str) -> dict[str, dict[str, str]]: """Normalized account name → Main Client Contact block (columns M–S).""" with open(path, newline='', encoding='utf-8-sig') as f: rows = list(csv.reader(f)) result: dict[str, dict[str, str]] = {} for row in rows[1:]: # row 0 is the header if not row or not row[0].strip(): continue name = row[0].strip() result[name.lower()] = {field: (row[idx].strip() if len(row) > idx else '') for field, idx in CONTACT_COLS.items()} return result def main() -> None: parser = argparse.ArgumentParser(description='Foundation Accounts Step 3: responses JSON → CSV') parser.add_argument('responses', help='Path to foundation_vendors_responses.json from bulk_runner.py') parser.add_argument('--runner-csv', help='foundation_vendors.csv used for the run (row → name mapping)') parser.add_argument('--source-csv', help='Source spreadsheet (for the Main Client Contact block, columns M–S)') args = parser.parse_args() full_mode = bool(args.runner_csv or args.source_csv) if full_mode and not (args.runner_csv and args.source_csv): print('Error: --runner-csv and --source-csv must be used together', file=sys.stderr) sys.exit(1) output_path = args.responses.replace('.json', '_ids.csv') with open(args.responses) as f: data = json.load(f) row_to_name = load_runner_csv(args.runner_csv) if full_mode else {} contacts = load_source_contacts(args.source_csv) if full_mode else {} output_cols = OUTPUT_COLS_FULL if full_mode else OUTPUT_COLS_BASE unmatched: list[str] = [] with open(output_path, 'w', newline='') as f: writer = csv.DictWriter(f, fieldnames=output_cols) writer.writeheader() for entry in data: r = entry.get('response') or {} row_num = entry.get('row') vendor_id = r.get('vendor_id') # Prefer the runner CSV row → name mapping over the response name (handles drift). name = row_to_name.get(row_num, r.get('name', '')).strip() out_row: dict[str, object] = { 'name': name, 'vendor_id': vendor_id, 'vendor_uuid': r.get('vendor_uuid'), 'link': f'https://oa.theorchard.com/cont_mgmt/view_vendor.php?vendor_id={vendor_id}', } if full_mode: contact = contacts.get(name.lower()) if contact is None: unmatched.append(f'row={row_num} name={name!r}') contact = {field: '' for field in CONTACT_COLS} out_row.update(contact) writer.writerow(out_row) print(f'Written {len(data)} rows to {output_path}') if unmatched: print(f'WARNING: {len(unmatched)} rows not matched in source CSV:') for m in unmatched: print(f' {m}') if __name__ == '__main__': main()