mattrguerra/meridian-skillsmainPrivate on GitHub, published here in full

select_cohort.py

Language Python3.9 KB95 lines

skills/partner-pipeline-push/scripts/select_cohort.py

  1. #!/usr/bin/env python3
  2. """select_cohort.py: pick a cohort from the vault's client stubs by tier fields
  3. and emit the cohort mapping XML.
  4. Usage:
  5. python3 select_cohort.py \
  6. --vault "/path/to/03-Clients/client-stubs" \
  7. --user-group Retail \
  8. --user-group-correct "Palladium Partner" \
  9. --pipeline partner_outreach \
  10. --out cohort-mapping.xml
  11. Match rule: a stub is in the cohort if its frontmatter user_group == --user-group
  12. (when given) AND user_group_correct == --user-group-correct (when given). Either
  13. filter may be omitted. Prints the matching stubs and writes the XML.
  14. Notes
  15. -----
  16. * Stubs live flat in `03-Clients/client-stubs/`, one .md per client.
  17. * `user_group_correct` is the computed, true tier and is the one to filter on.
  18. Raw `user_group` is blank on most stubs.
  19. * Tier values are the full labels "Palladium Partner", "Platinum Partner", NOT the
  20. bare tier ("Palladium"). The Bitrix User Group enum matches these.
  21. * The output is a THROWAWAY working file for one push. It is NOT the vault census
  22. (`03-Clients/partner-client-master-list.xml`). Never write one over the other.
  23. * `bitrix_id` is copied through from the stub. Where populated it is a verified
  24. Bitrix contact id, but coverage is partial: link contacts by EMAIL, not by this.
  25. """
  26. import argparse, os, re, html
  27. STUB_DIR_RELATIVE = "/03-Clients/client-stubs"
  28. def fm(text):
  29. m = re.match(r'^---\n(.*?)\n---', text, re.S)
  30. return m.group(1) if m else ''
  31. def g(f, k):
  32. m = re.search(r'(?m)^' + re.escape(k) + r':\s*(.*)$', f)
  33. return (m.group(1).strip().strip('"').strip("'").strip() if m else '')
  34. def main():
  35. ap = argparse.ArgumentParser()
  36. ap.add_argument("--vault", required=True,
  37. help="path to 03-Clients/client-stubs")
  38. ap.add_argument("--user-group", default="")
  39. ap.add_argument("--user-group-correct", default="",
  40. help='e.g. "Palladium Partner" (full label, not the bare tier)')
  41. ap.add_argument("--pipeline", default="partner_outreach",
  42. help="vault pipeline key: partner_outreach | quarterly_checkin | winback")
  43. ap.add_argument("--out", default="cohort-mapping.xml")
  44. a = ap.parse_args()
  45. rows = []
  46. for fn in sorted(os.listdir(a.vault)):
  47. if not fn.endswith('.md') or fn.startswith('CLAUDE'):
  48. continue
  49. with open(os.path.join(a.vault, fn), encoding='utf-8') as fh:
  50. F = fm(fh.read())
  51. ug, ugc = g(F, 'user_group'), g(F, 'user_group_correct')
  52. if a.user_group and ug.lower() != a.user_group.lower():
  53. continue
  54. if a.user_group_correct and ugc.lower() != a.user_group_correct.lower():
  55. continue
  56. rows.append((fn, g(F, 'bitrix_id'), g(F, 'first_name'),
  57. g(F, 'last_name'), g(F, 'email')))
  58. blocks = []
  59. for fn, bid, first, last, email in rows:
  60. blocks.append(
  61. f' <client bitrix_id="{html.escape(bid)}">\n'
  62. f' <first_name>{html.escape(first)}</first_name>\n'
  63. f' <last_name>{html.escape(last)}</last_name>\n'
  64. f' <outreach_pipeline>{html.escape(a.pipeline)}</outreach_pipeline>\n'
  65. f' <stub_file_name>{html.escape(fn)}</stub_file_name>\n'
  66. f' <relative_path>{STUB_DIR_RELATIVE}/{html.escape(fn)}</relative_path>\n'
  67. f' </client>')
  68. doc = ('<?xml version="1.0" encoding="UTF-8"?>\n<cohort_mapping>\n'
  69. ' <client_list>\n' + "\n\n".join(blocks) +
  70. '\n </client_list>\n</cohort_mapping>\n')
  71. with open(a.out, "w", encoding='utf-8') as fh:
  72. fh.write(doc)
  73. print(f"cohort: {len(rows)} clients "
  74. f"(user_group={a.user_group or '*'}, "
  75. f"user_group_correct={a.user_group_correct or '*'})")
  76. for fn, bid, first, last, email in rows:
  77. print(f" - {first} {last} <{email}> [{fn}]")
  78. print(f"\nwrote {a.out} (cohort working file, NOT the vault census)")
  79. if __name__ == "__main__":
  80. main()