select_cohort.py
Language Python3.9 KB95 lines
#!/usr/bin/env python3"""select_cohort.py: pick a cohort from the vault's client stubs by tier fieldsand emit the cohort mapping XML.Usage:python3 select_cohort.py \--vault "/path/to/03-Clients/client-stubs" \--user-group Retail \--user-group-correct "Palladium Partner" \--pipeline partner_outreach \--out cohort-mapping.xmlMatch rule: a stub is in the cohort if its frontmatter user_group == --user-group(when given) AND user_group_correct == --user-group-correct (when given). Eitherfilter may be omitted. Prints the matching stubs and writes the XML.Notes-----* Stubs live flat in `03-Clients/client-stubs/`, one .md per client.* `user_group_correct` is the computed, true tier and is the one to filter on.Raw `user_group` is blank on most stubs.* Tier values are the full labels "Palladium Partner", "Platinum Partner", NOT thebare tier ("Palladium"). The Bitrix User Group enum matches these.* The output is a THROWAWAY working file for one push. It is NOT the vault census(`03-Clients/partner-client-master-list.xml`). Never write one over the other.* `bitrix_id` is copied through from the stub. Where populated it is a verifiedBitrix contact id, but coverage is partial: link contacts by EMAIL, not by this."""import argparse, os, re, htmlSTUB_DIR_RELATIVE = "/03-Clients/client-stubs"def fm(text):m = re.match(r'^---\n(.*?)\n---', text, re.S)return m.group(1) if m else ''def g(f, k):m = re.search(r'(?m)^' + re.escape(k) + r':\s*(.*)$', f)return (m.group(1).strip().strip('"').strip("'").strip() if m else '')def main():ap = argparse.ArgumentParser()ap.add_argument("--vault", required=True,help="path to 03-Clients/client-stubs")ap.add_argument("--user-group", default="")ap.add_argument("--user-group-correct", default="",help='e.g. "Palladium Partner" (full label, not the bare tier)')ap.add_argument("--pipeline", default="partner_outreach",help="vault pipeline key: partner_outreach | quarterly_checkin | winback")ap.add_argument("--out", default="cohort-mapping.xml")a = ap.parse_args()rows = []for fn in sorted(os.listdir(a.vault)):if not fn.endswith('.md') or fn.startswith('CLAUDE'):continuewith open(os.path.join(a.vault, fn), encoding='utf-8') as fh:F = fm(fh.read())ug, ugc = g(F, 'user_group'), g(F, 'user_group_correct')if a.user_group and ug.lower() != a.user_group.lower():continueif a.user_group_correct and ugc.lower() != a.user_group_correct.lower():continuerows.append((fn, g(F, 'bitrix_id'), g(F, 'first_name'),g(F, 'last_name'), g(F, 'email')))blocks = []for fn, bid, first, last, email in rows:blocks.append(f' <client bitrix_id="{html.escape(bid)}">\n'f' <first_name>{html.escape(first)}</first_name>\n'f' <last_name>{html.escape(last)}</last_name>\n'f' <outreach_pipeline>{html.escape(a.pipeline)}</outreach_pipeline>\n'f' <stub_file_name>{html.escape(fn)}</stub_file_name>\n'f' <relative_path>{STUB_DIR_RELATIVE}/{html.escape(fn)}</relative_path>\n'f' </client>')doc = ('<?xml version="1.0" encoding="UTF-8"?>\n<cohort_mapping>\n'' <client_list>\n' + "\n\n".join(blocks) +'\n </client_list>\n</cohort_mapping>\n')with open(a.out, "w", encoding='utf-8') as fh:fh.write(doc)print(f"cohort: {len(rows)} clients "f"(user_group={a.user_group or '*'}, "f"user_group_correct={a.user_group_correct or '*'})")for fn, bid, first, last, email in rows:print(f" - {first} {last} <{email}> [{fn}]")print(f"\nwrote {a.out} (cohort working file, NOT the vault census)")if __name__ == "__main__":main()