Function bodies 37 total
getSpreadsheet function · javascript · L22-L26 (5 LOC)apps_script_drip.js
function getSpreadsheet() {
return CONFIG.SPREADSHEET_ID
? SpreadsheetApp.openById(CONFIG.SPREADSHEET_ID)
: SpreadsheetApp.getActiveSpreadsheet();
}getColumnMap function · javascript · L29-L37 (9 LOC)apps_script_drip.js
function getColumnMap(sheet) {
const headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
const map = {};
headers.forEach((h, i) => {
const key = h.toString().trim().toLowerCase();
if (key) map[key] = i;
});
return map;
}sendNextEmail function · javascript · L40-L149 (110 LOC)apps_script_drip.js
function sendNextEmail() {
const ss = getSpreadsheet();
const sheet = ss.getSheetByName(CONFIG.SHEET_NAME);
const props = PropertiesService.getScriptProperties();
// Check daily send count
const today = new Date().toDateString();
const dailyKey = "sent_" + today;
const sentToday = parseInt(props.getProperty(dailyKey) || "0");
if (sentToday >= CONFIG.DAILY_LIMIT) {
Logger.log("Daily limit reached: " + sentToday);
return;
}
// Get template
const template = getEmailTemplate();
if (!template) {
Logger.log("ERROR: Could not find template file: " + CONFIG.TEMPLATE_FILE);
return;
}
// Get column positions from headers
const colMap = getColumnMap(sheet);
const firstNameCol = colMap["first name"];
const emailCol = colMap["email"];
const statusCol = colMap["status"];
const timestampCol = colMap["timestamp"];
if (firstNameCol === undefined || emailCol === undefined || statusCol === undefined || timestampCol === undefined) {
Logger.logetEmailTemplate function · javascript · L152-L175 (24 LOC)apps_script_drip.js
function getEmailTemplate() {
const files = DriveApp.getFilesByName(CONFIG.TEMPLATE_FILE);
if (!files.hasNext()) return null;
const content = files.next().getBlob().getDataAsString();
const parts = content.split("---");
let subject = "Hello";
let senderName = CONFIG.SENDER_NAME;
let body = content;
if (parts.length >= 2) {
const header = parts[0].trim();
body = parts.slice(1).join("---").trim();
const subjectMatch = header.match(/subject:\s*(.+)/i);
if (subjectMatch) subject = subjectMatch[1].trim();
const nameMatch = header.match(/from_name:\s*(.+)/i);
if (nameMatch) senderName = nameMatch[1].trim();
}
return { subject, senderName, body };
}markdownToHtml function · javascript · L178-L277 (100 LOC)apps_script_drip.js
function markdownToHtml(md) {
const lines = md.split("\n");
let html = "";
let inTable = false;
let inList = false;
let inOrderedList = false;
let tableHeaderDone = false;
for (let i = 0; i < lines.length; i++) {
let line = lines[i];
if (line.startsWith("######")) {
html += "<h6>" + inlineFormat(line.slice(6).trim()) + "</h6>";
continue;
} else if (line.startsWith("#####")) {
html += "<h5>" + inlineFormat(line.slice(5).trim()) + "</h5>";
continue;
} else if (line.startsWith("####")) {
html += "<h4>" + inlineFormat(line.slice(4).trim()) + "</h4>";
continue;
} else if (line.startsWith("###")) {
html += "<h3>" + inlineFormat(line.slice(3).trim()) + "</h3>";
continue;
} else if (line.startsWith("##")) {
html += "<h2>" + inlineFormat(line.slice(2).trim()) + "</h2>";
continue;
} else if (line.startsWith("#")) {
html += "<h1>" + inlineFormat(line.slice(1).trim()) + "</h1>";
continlineFormat function · javascript · L280-L285 (6 LOC)apps_script_drip.js
function inlineFormat(text) {
return text
.replace(/\*\*(.+?)\*\*/g, "<b>$1</b>")
.replace(/\*(.+?)\*/g, "<i>$1</i>")
.replace(/\[(.+?)\]\((.+?)\)/g, '<a href="$2">$1</a>');
}deleteExistingTriggers function · javascript · L288-L294 (7 LOC)apps_script_drip.js
function deleteExistingTriggers(functionName) {
ScriptApp.getProjectTriggers().forEach(trigger => {
if (trigger.getHandlerFunction() === functionName) {
ScriptApp.deleteTrigger(trigger);
}
});
}Repobility · open methodology · https://repobility.com/research/
sendTextViaWebhook function · javascript · L297-L337 (41 LOC)apps_script_drip.js
function sendTextViaWebhook(firstName, email, phone) {
if (!phone || phone.toString().trim() === "") {
Logger.log("No phone number for " + email + ", skipping text.");
return;
}
if (!CONFIG.MAKE_WEBHOOK_URL) {
Logger.log("No webhook URL configured, skipping text for " + email);
return;
}
// Strip to digits only, then normalize to 10-digit US number
var digits = phone.toString().replace(/\D/g, "");
if (digits.length === 11 && digits.charAt(0) === "1") {
digits = digits.substring(1); // remove leading country code
}
if (digits.length !== 10) {
Logger.log("Invalid phone number for " + email + ": " + phone + " (" + digits.length + " digits), skipping text.");
return;
}
const payload = {
firstName: firstName,
email: email,
phone: digits,
message: firstName + ", pls remember to complete CE & renewal by 4/30 - info here -> https://www.kalehuddle.com/post/illinois-real-estate-license-renewal-2026-complete-guide",
sentAt: startCampaign function · javascript · L341-L345 (5 LOC)apps_script_drip.js
function startCampaign() {
deleteExistingTriggers("sendNextEmail");
Logger.log("Campaign started.");
sendNextEmail();
}pauseCampaign function · javascript · L347-L350 (4 LOC)apps_script_drip.js
function pauseCampaign() {
deleteExistingTriggers("sendNextEmail");
Logger.log("Campaign paused. Run startCampaign() to resume.");
}checkStatus function · javascript · L352-L377 (26 LOC)apps_script_drip.js
function checkStatus() {
const ss = getSpreadsheet();
const sheet = ss.getSheetByName(CONFIG.SHEET_NAME);
const data = sheet.getDataRange().getValues();
const colMap = getColumnMap(sheet);
const props = PropertiesService.getScriptProperties();
const statusCol = colMap["status"];
const today = new Date().toDateString();
const sentToday = parseInt(props.getProperty("sent_" + today) || "0");
let sent = 0, unsent = 0, errors = 0;
for (let i = 1; i < data.length; i++) {
const s = data[i][statusCol];
if (s === "SENT") sent++;
else if (s === "ERROR" || s === "INVALID") errors++;
else unsent++;
}
Logger.log("=== CAMPAIGN STATUS ===");
Logger.log("Sent: " + sent + " | Unsent: " + unsent + " | Errors: " + errors);
Logger.log("Sent today: " + sentToday + " / " + CONFIG.DAILY_LIMIT);
Logger.log("Send interval: every " + CONFIG.SEND_INTERVAL_DAYS + " days per person");
Logger.log("Active triggers: " + ScriptApp.getProjectTriggers().length);
LoggefullReset function · javascript · L379-L399 (21 LOC)apps_script_drip.js
function fullReset() {
const props = PropertiesService.getScriptProperties();
props.deleteAllProperties();
deleteExistingTriggers("sendNextEmail");
const ss = getSpreadsheet();
const sheet = ss.getSheetByName(CONFIG.SHEET_NAME);
const colMap = getColumnMap(sheet);
const statusCol = colMap["status"];
const timestampCol = colMap["timestamp"];
const totalRows = sheet.getLastRow();
if (totalRows > 1 && statusCol !== undefined) {
sheet.getRange(2, statusCol + 1, totalRows - 1, 1).clearContent();
}
if (totalRows > 1 && timestampCol !== undefined) {
sheet.getRange(2, timestampCol + 1, totalRows - 1, 1).clearContent();
}
Logger.log("Full reset complete.");
}onOpen function · javascript · L402-L413 (12 LOC)apps_script_drip.js
function onOpen() {
SpreadsheetApp.getUi()
.createMenu("Email Campaign")
.addItem("Start Campaign", "startCampaign")
.addItem("Pause Campaign", "pauseCampaign")
.addItem("Check Status", "checkStatus")
.addItem("Full Reset", "fullReset")
.addSeparator()
.addItem("Sync Active Roster (Monday.com)", "syncActiveRoster")
.addItem("Email Renewal Report", "emailRenewalReport")
.addToUi();
}testWebhookOnly function · javascript · L415-L418 (4 LOC)apps_script_drip.js
function testWebhookOnly() {
sendTextViaWebhook("Test", "[email protected]", "1234567890");
Logger.log("Test webhook fired.");
}mondayQuery function · javascript · L21-L36 (16 LOC)apps_script_sync.js
function mondayQuery(query, variables) {
const token = PropertiesService.getScriptProperties().getProperty("MONDAY_API_TOKEN");
if (!token) throw new Error("Set MONDAY_API_TOKEN in Script Properties (Project Settings > Script Properties)");
const resp = UrlFetchApp.fetch("https://api.monday.com/v2", {
method: "post",
contentType: "application/json",
headers: { Authorization: token, "API-Version": "2024-10" },
payload: JSON.stringify({ query: query, variables: variables || {} }),
muteHttpExceptions: true,
});
const data = JSON.parse(resp.getContentText());
if (data.errors) throw new Error("Monday.com API error: " + JSON.stringify(data.errors));
return data.data;
}Powered by Repobility — scan your code at https://repobility.com
getActiveAgents function · javascript · L38-L85 (48 LOC)apps_script_sync.js
function getActiveAgents() {
const agents = [];
let cursor = null;
let isFirstPage = true;
while (true) {
let data;
if (isFirstPage) {
data = mondayQuery(
`query ($boardId: [ID!]!) {
boards(ids: $boardId) {
items_page(limit: 500) {
cursor
items { name, group { id }, column_values { id text } }
}
}
}`,
{ boardId: [String(MONDAY_BOARD_ID)] }
);
const page = data.boards[0].items_page;
cursor = page.cursor;
page.items.forEach(item => {
if (item.group.id === MONDAY_ACTIVE_GROUP_ID) agents.push(parseMonday(item));
});
isFirstPage = false;
} else {
data = mondayQuery(
`query ($cursor: String!) {
next_items_page(limit: 500, cursor: $cursor) {
cursor
items { name, group { id }, column_values { id text } }
}
}`,
{ cursor: cursor }
);
const page =parseMonday function · javascript · L87-L125 (39 LOC)apps_script_sync.js
function parseMonday(item) {
const cols = {};
item.column_values.forEach(c => { cols[c.id.toLowerCase()] = c.text; });
// Log column IDs on first item to help discover HC's column mapping
if (!parseMonday._logged) {
var colIds = item.column_values.map(function(c) { return c.id + "=" + (c.text || "").substring(0, 30); });
Logger.log("Column IDs: " + colIds.join(", "));
parseMonday._logged = true;
}
// First name / Last name: try known column IDs
let first = cols["text95"] || cols["first_name"] || cols["first name"] || cols["firstname"] || "";
let last = cols["text_19"] || cols["last_name"] || cols["last name"] || cols["lastname"] || "";
if (!first && !last) {
const parts = item.name.trim().split(/\s+/);
first = parts[0] || "";
last = parts.slice(1).join(" ") || "";
}
// License number: license_number3 for HC, license_number for KHA
var licenseNum = cols["license_number3"] || cols["license_number"] || "";
// Email: try common column dfprLookup function · javascript · L129-L158 (30 LOC)apps_script_sync.js
function dfprLookup(licenseNumbers) {
// Query DFPR by license numbers in batches of 100
var results = {};
var BATCH = 100;
for (var i = 0; i < licenseNumbers.length; i += BATCH) {
var batch = licenseNumbers.slice(i, i + BATCH);
var inList = batch.map(function(n) { return "'" + n + "'"; }).join(", ");
var where = "license_number IN (" + inList + ")";
var url = "https://data.illinois.gov/resource/pzzh-kp68.json"
+ "?$where=" + encodeURIComponent(where)
+ "&$select=" + encodeURIComponent("license_number,expiration_date,license_status")
+ "&$limit=50000";
var resp = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
if (resp.getResponseCode() === 200) {
var records = JSON.parse(resp.getContentText());
records.forEach(function(r) {
// Keep the latest expiration per license number
var num = r.license_number;
var exp = r.expiration_date || "";
if (!results[num] || exp > (results[num].expirationsyncActiveRoster function · javascript · L162-L321 (160 LOC)apps_script_sync.js
function syncActiveRoster() {
Logger.log("[" + MONDAY_ENTITY + "] Active roster sync starting...");
var activeAgents = getActiveAgents();
Logger.log("Active agents on Monday.com: " + activeAgents.length);
if (activeAgents.length === 0) {
Logger.log("WARNING: No active agents found. Skipping sync to prevent accidental wipe.");
return;
}
// Build lookup of active agents by email
var activeByEmail = {};
activeAgents.forEach(function(agent) {
if (agent.email) {
activeByEmail[agent.email.toLowerCase()] = agent;
}
});
Logger.log("Active agents with email: " + Object.keys(activeByEmail).length);
// Open spreadsheet
var ss = CONFIG.SPREADSHEET_ID
? SpreadsheetApp.openById(CONFIG.SPREADSHEET_ID)
: SpreadsheetApp.getActiveSpreadsheet();
var sheet = ss.getSheetByName(CONFIG.SHEET_NAME);
var headers = sheet.getRange(1, 1, 1, sheet.getLastColumn()).getValues()[0];
var colMap = {};
headers.forEach(function(h, i) { colMap[h.toString().emailRenewalReport function · javascript · L325-L362 (38 LOC)apps_script_sync.js
function emailRenewalReport() {
Logger.log("[" + MONDAY_ENTITY + "] Fetching renewal report from GitHub...");
var url = "https://raw.githubusercontent.com/" + GITHUB_REPO_OWNER + "/" + GITHUB_REPO_NAME + "/main/latest_report.txt";
try {
var resp = UrlFetchApp.fetch(url, { muteHttpExceptions: true });
if (resp.getResponseCode() !== 200) {
Logger.log("No report found at " + url + " (HTTP " + resp.getResponseCode() + "). Skipping email.");
return;
}
var reportText = resp.getContentText();
if (!reportText || reportText.trim().length === 0) {
Logger.log("Report is empty. Skipping email.");
return;
}
var today = Utilities.formatDate(new Date(), Session.getScriptTimeZone(), "yyyy-MM-dd");
var subject = "[" + MONDAY_ENTITY + "] License Renewal Report — " + today;
var htmlBody = "<h2>" + subject + "</h2>"
+ "<pre style='font-family: Consolas, monospace; font-size: 13px; line-height: 1.4;'>"
+ reportText.replace(setupSyncTrigger function · javascript · L367-L391 (25 LOC)apps_script_sync.js
function setupSyncTrigger() {
// Remove any existing sync triggers
ScriptApp.getProjectTriggers().forEach(trigger => {
var fn = trigger.getHandlerFunction();
if (fn === "syncActiveRoster" || fn === "syncTerminatedAgents" || fn === "emailRenewalReport") {
ScriptApp.deleteTrigger(trigger);
}
});
// Daily roster sync at 5am (runs before the email campaign)
ScriptApp.newTrigger("syncActiveRoster")
.timeBased()
.everyDays(1)
.atHour(5)
.create();
// Daily report email at 10am (after GitHub Actions runs at ~2am CT)
ScriptApp.newTrigger("emailRenewalReport")
.timeBased()
.everyDays(1)
.atHour(10)
.create();
Logger.log("Daily triggers created: syncActiveRoster (5am), emailRenewalReport (10am).");
}fetch_dfpr_records function · python · L23-L75 (53 LOC)fetch_dfpr_data.py
def fetch_dfpr_records():
"""
Query DFPR data filtered by business DBA and real estate license type.
Returns a DataFrame with columns matching the existing script's expectations.
"""
where_clause = (
f"businessdba LIKE '%{DFPR_BUSINESS_DBA}%' "
f"AND description LIKE '%Real Estate%'"
)
all_rows = []
offset = 0
while True:
params = {
"$where": where_clause,
"$select": "first_name,middle,last_name,license_number,license_status,"
"expiration_date,business_name,businessdba,description",
"$limit": PAGE_SIZE,
"$offset": offset,
"$order": "last_name,first_name",
}
print(f" Querying data.illinois.gov (offset={offset})...")
resp = requests.get(SODA_BASE_URL, params=params, timeout=60)
resp.raise_for_status()
chunk = pd.read_csv(io.StringIO(resp.text))
if chunk.empty:
break
all_row_get_headers function · python · L22-L31 (10 LOC)fetch_monday_agents.py
def _get_headers():
token = os.environ.get("MONDAY_API_TOKEN")
if not token:
print("ERROR: Set MONDAY_API_TOKEN environment variable")
sys.exit(1)
return {
"Authorization": token,
"Content-Type": "application/json",
"API-Version": "2024-10",
}Repobility · severity-and-effort ranking · https://repobility.com
_run_query function · python · L34-L47 (14 LOC)fetch_monday_agents.py
def _run_query(query, variables=None):
payload = {"query": query}
if variables:
payload["variables"] = variables
resp = requests.post(MONDAY_API_URL, json=payload, headers=_get_headers(), timeout=120)
resp.raise_for_status()
data = resp.json()
if "errors" in data:
print(f"Monday.com API errors: {json.dumps(data['errors'], indent=2)}")
sys.exit(1)
return data["data"]fetch_board_items function · python · L50-L100 (51 LOC)fetch_monday_agents.py
def fetch_board_items(board_id):
"""Fetch all items from a board using cursor-based pagination."""
first_query = """
query ($boardId: [ID!]!, $limit: Int!) {
boards(ids: $boardId) {
name
items_page(limit: $limit) {
cursor
items {
id
name
group { id title }
column_values { id text }
}
}
}
}
"""
data = _run_query(first_query, {"boardId": [str(board_id)], "limit": PAGE_LIMIT})
board = data["boards"][0]
print(f"Board: {board['name']}")
page = board["items_page"]
all_items = list(page["items"])
cursor = page["cursor"]
next_query = """
query ($limit: Int!, $cursor: String!) {
next_items_page(limit: $limit, cursor: $cursor) {
cursor
items {
id
name
group { id title }
column_values { id text }
}
}
}
"""
page_num = 1
while cursor is not extract_agent_info function · python · L103-L141 (39 LOC)fetch_monday_agents.py
def extract_agent_info(item):
"""Extract agent first/last name and license number from a Monday.com item."""
col_lookup = {}
for col in item["column_values"]:
col_lookup[col["id"].lower()] = col["text"]
first_name = None
last_name = None
for key in ["text95", "first name", "first_name", "firstname", "first"]:
if key in col_lookup and col_lookup[key]:
first_name = col_lookup[key].strip()
break
for key in ["text_19", "last name", "last_name", "lastname", "last"]:
if key in col_lookup and col_lookup[key]:
last_name = col_lookup[key].strip()
break
# Fallback: parse item name as "First Last"
if not first_name and not last_name:
parts = item["name"].strip().split(None, 1)
first_name = parts[0] if len(parts) >= 1 else ""
last_name = parts[1] if len(parts) >= 2 else ""
# License number: "license_number3" for HC, "license_number" for KHA
license_num = fetch_active_agents function · python · L144-L154 (11 LOC)fetch_monday_agents.py
def fetch_active_agents():
"""Fetch active (non-terminated) agents and return as a DataFrame."""
items = fetch_board_items(MONDAY_BOARD_ID)
active = [i for i in items if i["group"]["id"] != MONDAY_TERMINATED_GROUP_ID]
terminated = len(items) - len(active)
print(f" Active: {len(active)}, Terminated (filtered out): {terminated}")
agents = [extract_agent_info(item) for item in active]
df = pd.DataFrame(agents)
return dfnormalize_name function · python · L42-L51 (10 LOC)update_license_renewals.py
def normalize_name(name):
"""Normalize a name for comparison."""
name = name.lower().strip()
# Remove common suffixes
for suffix in [' jr', ' sr', ' ii', ' iii', ' iv', ' md']:
name = name.rstrip('.').replace(suffix, '')
# Normalize punctuation and whitespace
name = name.replace("'", "").replace("-", " ").replace(".", "").replace(",", " ")
name = ' '.join(name.split())
return namebuild_agents_csv_name_variants function · python · L54-L87 (34 LOC)update_license_renewals.py
def build_agents_csv_name_variants(row):
"""Generate multiple name variants from the agents CSV row for matching."""
first = str(row.get('First Name', '')).strip()
lasts = []
for col in ['Last Name', 'Last Name 2', 'Last Name 3']:
val = str(row.get(col, '')).strip()
if val and val.lower() != 'nan' and val != '':
lasts.append(val)
variants = set()
# Full name: "First Last Last2 Last3"
full = ' '.join([first] + lasts)
variants.add(normalize_name(full))
if lasts:
# "Last First" format
variants.add(normalize_name(f"{' '.join(lasts)} {first}"))
variants.add(normalize_name(f"{lasts[0]} {first}"))
variants.add(normalize_name(f"{first} {lasts[0]}"))
# With hyphenated last names
if len(lasts) >= 2:
variants.add(normalize_name(f"{first} {lasts[0]}-{lasts[1]}"))
variants.add(normalize_name(f"{first} {lasts[0]} {lasts[1]}"))
variants.add(normalize_name(f"{lasts[0]}-{lmatch_dfpr_to_agents function · python · L90-L159 (70 LOC)update_license_renewals.py
def match_dfpr_to_agents(dfpr_df, agents_df, threshold=DEFAULT_THRESHOLD):
"""Match DFPR records to agents CSV using fuzzy name matching."""
from rapidfuzz import fuzz, process
# Build agents CSV lookup: {normalized_variant: csv_index}
agents_name_map = {}
for idx, row in agents_df.iterrows():
variants = build_agents_csv_name_variants(row)
for variant in variants:
if variant:
agents_name_map[variant] = idx
agents_variant_list = list(agents_name_map.keys())
matches = []
unmatched_dfpr = []
matched_agents_indices = set()
for _, dfpr_row in dfpr_df.iterrows():
dfpr_name_raw = str(dfpr_row.get('Supverisee', '')).strip()
if not dfpr_name_raw or dfpr_name_raw.lower() == 'nan':
continue
dfpr_name = normalize_name(dfpr_name_raw)
if not dfpr_name:
continue
exp_text = str(dfpr_row.get('Expiration Date', '')).strip()
# Pass 1: Exact matparse_expiration_date function · python · L166-L178 (13 LOC)update_license_renewals.py
def parse_expiration_date(raw_text):
"""Parse expiration date from DFPR CSV (M/D/YY or M/D/YYYY format)."""
text = str(raw_text).strip()
if not text or text.lower() == 'nan':
return None
for fmt in ['%m/%d/%y', '%m/%d/%Y', '%m-%d-%y', '%m-%d-%Y']:
try:
return datetime.strptime(text, fmt).date()
except ValueError:
continue
return NoneRepobility · code-quality intelligence platform · https://repobility.com
determine_action function · python · L185-L195 (11 LOC)update_license_renewals.py
def determine_action(match):
"""Determine whether to keep or remove an agent."""
exp_date = parse_expiration_date(match['exp_date'])
if exp_date is None:
return 'keep', f'unparseable date: "{match["exp_date"]}"'
if exp_date <= RENEWAL_CUTOFF:
return 'keep', f'expires {exp_date.strftime("%m/%d/%Y")} (needs renewal)'
else:
return 'remove', f'expires {exp_date.strftime("%m/%d/%Y")} (already renewed)'load_csv function · python · L202-L212 (11 LOC)update_license_renewals.py
def load_csv(csv_path):
"""Load a CSV with encoding detection."""
import pandas as pd
for encoding in ['utf-8', 'latin-1', 'cp1252']:
try:
df = pd.read_csv(csv_path, encoding=encoding)
return df
except (UnicodeDecodeError, Exception):
continue
print(f"ERROR: Could not read CSV: {csv_path}")
sys.exit(1)backup_csv function · python · L215-L220 (6 LOC)update_license_renewals.py
def backup_csv(csv_path):
"""Create timestamped backup of original CSV."""
timestamp = datetime.now().strftime('%Y-%m-%d_%H%M%S')
backup_path = f"{csv_path}.bak.{timestamp}"
shutil.copy2(csv_path, backup_path)
return backup_pathrun_pipeline function · python · L227-L375 (149 LOC)update_license_renewals.py
def run_pipeline(dfpr_path=None, agents_path=None, output_path=None,
dry_run=False, threshold=DEFAULT_THRESHOLD,
dfpr_df=None, agents_df=None, source_label="manual"):
"""Main processing pipeline. Accepts CSVs paths or pre-loaded DataFrames."""
import pandas as pd
from collections import Counter
print("=" * 60)
print(f"[{ENTITY_NAME}] License Renewal Update — {datetime.now().strftime('%Y-%m-%d')}")
print("=" * 60)
print(f"Source: {source_label}")
print(f"Cutoff: {RENEWAL_CUTOFF.strftime('%m/%d/%Y')}")
print(f"Threshold: {threshold}")
print(f"Dry run: {dry_run}")
print()
# --- Step 1: Load data ---
if dfpr_df is None:
print(f"Loading DFPR CSV: {dfpr_path}")
dfpr_df = load_csv(dfpr_path)
if agents_df is None:
print(f"Loading Agents CSV: {agents_path}")
agents_df = load_csv(agents_path)
print(f" DFPR records: {len(dfpr_df)}")
print(f" Agents irun_auto function · python · L381-L416 (36 LOC)update_license_renewals.py
def run_auto(dry_run=False, threshold=DEFAULT_THRESHOLD):
"""Automated pipeline: pull from Monday.com + data.illinois.gov."""
from fetch_monday_agents import fetch_active_agents
from fetch_dfpr_data import fetch_dfpr_records
print(f"[{ENTITY_NAME}] Automated renewal check starting...\n")
# Step 1: Get active agents from Monday.com
print("--- Monday.com ---")
agents_df = fetch_active_agents()
# Step 2: Get DFPR license data
print("\n--- DFPR (data.illinois.gov) ---")
dfpr_df = fetch_dfpr_records()
if dfpr_df.empty:
print("No DFPR records found. Exiting.")
return
# Step 3: Run the matching pipeline
report_text = run_pipeline(
dfpr_df=dfpr_df,
agents_df=agents_df,
output_path=AUTO_OUTPUT_CSV,
dry_run=dry_run,
threshold=threshold,
source_label="Monday.com + data.illinois.gov",
)
print("\n" + report_text)
# Step 4: Save report for Apps Script to email
ifmain function · python · L419-L470 (52 LOC)update_license_renewals.py
def main():
parser = argparse.ArgumentParser(
description='License renewal tracker — automated or manual',
formatter_class=argparse.RawDescriptionHelpFormatter,
epilog="""
Examples:
# Automated (Monday.com + DFPR API):
python update_license_renewals.py --auto --dry-run
python update_license_renewals.py --auto --dry-run
python update_license_renewals.py --auto
# Manual (local CSVs):
python update_license_renewals.py --dfpr dfpr_export.csv --agents agents.csv --dry-run
python update_license_renewals.py --dfpr dfpr_export.csv --agents agents.csv
"""
)
parser.add_argument('--auto', action='store_true',
help='Automated mode: pull agents from Monday.com, DFPR from data.illinois.gov')
parser.add_argument('--dfpr', default=None, help='Path to DFPR eLicense CSV export (manual mode)')
parser.add_argument('--agents', default=None, help='Path to agents CSV (manual mode)')
parser.add_argument('--output',