Initial import: eCW Charge Validation bot and results Portal
Snapshot of the working project as of 2026-10-23 (local 2d4d4fc), one commit with no earlier history: the tracked files were de-identified first (synthetic claim ids and patient data only; see Context.md section 7). Credentials, run logs, screenshots, claim reports and client workbooks are gitignored and not included. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
commit
ba15a574ab
205 files changed
+56772
No files matched your search
@@ -0,0 +1,140 @@
|
||||
r"""
|
||||
Refresh src\ecw_bot\payer_groups.json from the team's Claim Status Dashboard.
|
||||
|
||||
python tools\import_payer_groups.py
|
||||
python tools\import_payer_groups.py "<path to the dashboard .xlsx>"
|
||||
python tools\import_payer_groups.py --dry-run
|
||||
|
||||
The dashboard (default: \\mecare2\Production\Salud-eCW RPA\Claim Status
|
||||
Dashboard Details 1001.xlsx) is the team's own template for which payer goes
|
||||
in which group: its "Payer Name" and "Payer Group Name" columns. This keeps
|
||||
ONLY those two columns — distinct pairs, payer names and group names — so no
|
||||
patient data from the dashboard ever reaches the repo.
|
||||
|
||||
Rules:
|
||||
* Every sheet with both headers is read; a payer seen with two different
|
||||
groups keeps the one it has on most rows, and the conflict is printed.
|
||||
* A payer already in payer_groups.json but NOT in the dashboard is kept (the
|
||||
document's RAE spellings, for one), with its earlier short group name
|
||||
translated to the dashboard's own ("RAE payer" -> "RAE (Behavioral
|
||||
Health)", ...), and listed so a person can check it.
|
||||
* Nothing is written with --dry-run; otherwise the old file is replaced and
|
||||
every added / changed / kept payer is listed.
|
||||
|
||||
Reads a network file only — never logs into eCW. openpyxl is used here (it is
|
||||
a developer tool, not the bot) because the dashboard is a 5 MB, 25,000-row
|
||||
workbook.
|
||||
"""
|
||||
|
||||
import sys
|
||||
import json
|
||||
import argparse
|
||||
import collections
|
||||
from pathlib import Path
|
||||
|
||||
ROOT = Path(__file__).resolve().parent.parent
|
||||
sys.path.insert(0, str(ROOT / "src"))
|
||||
for _s in (sys.stdout, sys.stderr):
|
||||
try:
|
||||
_s.reconfigure(encoding="utf-8", errors="replace")
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
DEFAULT_DASHBOARD = (r"\\mecare2\Production\Salud-eCW RPA"
|
||||
r"\Claim Status Dashboard Details 1001.xlsx")
|
||||
CONFIG = ROOT / "src" / "ecw_bot" / "payer_groups.json"
|
||||
|
||||
# The bot's earlier short group names -> the dashboard's names, for payers
|
||||
# the dashboard does not list.
|
||||
_EARLIER_TO_DASHBOARD = {
|
||||
"Commercial": "Commercial Ins",
|
||||
"RAE payer": "RAE (Behavioral Health)",
|
||||
"Dentaquest": "Dental - CHP+/DentaQuest",
|
||||
"Program": "Self Pay",
|
||||
}
|
||||
|
||||
|
||||
def read_pairs(path) -> dict:
|
||||
"""{payer: Counter({group: rows})} from every sheet with both headers."""
|
||||
import openpyxl
|
||||
wb = openpyxl.load_workbook(path, read_only=True, data_only=True)
|
||||
pairs = collections.defaultdict(collections.Counter)
|
||||
try:
|
||||
for ws in wb.worksheets:
|
||||
rows = ws.iter_rows(values_only=True)
|
||||
header = [str(h or "").strip() for h in (next(rows, None) or [])]
|
||||
if "Payer Name" not in header or "Payer Group Name" not in header:
|
||||
continue
|
||||
pi, gi = header.index("Payer Name"), header.index("Payer Group Name")
|
||||
for row in rows:
|
||||
if not row or len(row) <= max(pi, gi):
|
||||
continue
|
||||
payer = str(row[pi] or "").strip()
|
||||
group = str(row[gi] or "").strip()
|
||||
if payer and group:
|
||||
pairs[payer][group] += 1
|
||||
finally:
|
||||
wb.close()
|
||||
return pairs
|
||||
|
||||
|
||||
def build(pairs: dict, current: dict):
|
||||
"""(new mapping, conflicts, kept-from-current)."""
|
||||
mapping, conflicts = {}, {}
|
||||
for payer, groups in pairs.items():
|
||||
mapping[payer] = groups.most_common(1)[0][0]
|
||||
if len(groups) > 1:
|
||||
conflicts[payer] = dict(groups)
|
||||
kept = {}
|
||||
for payer, group in current.items():
|
||||
if payer not in mapping:
|
||||
mapping[payer] = _EARLIER_TO_DASHBOARD.get(group, group)
|
||||
kept[payer] = mapping[payer]
|
||||
return dict(sorted(mapping.items(), key=lambda kv: kv[0].lower())), \
|
||||
conflicts, kept
|
||||
|
||||
|
||||
def main(argv=None) -> int:
|
||||
ap = argparse.ArgumentParser(description=__doc__.splitlines()[1])
|
||||
ap.add_argument("dashboard", nargs="?", default=DEFAULT_DASHBOARD)
|
||||
ap.add_argument("--dry-run", action="store_true")
|
||||
args = ap.parse_args(argv)
|
||||
|
||||
current = json.loads(CONFIG.read_text(encoding="utf-8")) \
|
||||
if CONFIG.exists() else {}
|
||||
print(f"[IMPORT] Reading {args.dashboard}")
|
||||
pairs = read_pairs(args.dashboard)
|
||||
if not pairs:
|
||||
print("[IMPORT] No sheet with 'Payer Name' and 'Payer Group Name' — "
|
||||
"nothing written")
|
||||
return 1
|
||||
mapping, conflicts, kept = build(pairs, current)
|
||||
|
||||
added = sorted(p for p in mapping if p not in current)
|
||||
changed = sorted(p for p in mapping
|
||||
if p in current and current[p] != mapping[p])
|
||||
groups = collections.Counter(mapping.values())
|
||||
print(f"[IMPORT] {len(pairs)} payer(s) in the dashboard, "
|
||||
f"{len(mapping)} in the new table")
|
||||
for g, n in groups.most_common():
|
||||
print(f"[IMPORT] {n:4d} {g}")
|
||||
for p, gs in sorted(conflicts.items()):
|
||||
print(f"[IMPORT] CONFLICT {p!r}: {gs} -> kept {mapping[p]!r}")
|
||||
for p in sorted(kept):
|
||||
print(f"[IMPORT] KEPT (not in the dashboard) {p!r} -> {kept[p]!r}")
|
||||
for p in changed:
|
||||
print(f"[IMPORT] CHANGED {p!r}: {current[p]!r} -> {mapping[p]!r}")
|
||||
print(f"[IMPORT] {len(added)} added, {len(changed)} changed, "
|
||||
f"{len(kept)} kept from the old table")
|
||||
|
||||
if args.dry_run:
|
||||
print("[IMPORT] --dry-run: nothing written")
|
||||
return 0
|
||||
CONFIG.write_text(json.dumps(mapping, indent=2, ensure_ascii=False) + "\n",
|
||||
encoding="utf-8")
|
||||
print(f"[IMPORT] Wrote {CONFIG}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user