pif / app /rules_engine.py
pramodmisra's picture
Claude Opus 5 (1M context)
Fix: CE_PROD_SUFFIX / CE_SUFFIX now drive the CE's producer code
129db36
Raw History Blame Contribute Delete
28 kB
"""
Commission rules engine.
Reads all configuration from the database and computes the summary table output.
v2.1 changes:
- Bell ringer offering removed (no thresholds, no bell_ringer key in output).
- CCP rendered before CE in year tables and participants list.
- 'House Standard Agreement' is its own type (house_processor=True): no P1
field, House Account row deducted by CE/CCP layers, Bella Santana added
automatically as approval processor.
- 'Life Origination Fee Agreement' transitions to House Account in Year 2+
via CommissionTier.use_house_account.
"""
from typing import Optional
from sqlalchemy.orm import Session
from app.models import (
CommissionAgreement, CommissionTier, FixedConstant, Producer,
ClientExecutive,
)
# Fixed-code agreements (P1 auto-filled, locked)
FIXED_CODE_AGREEMENTS = {
"Gebhardt Agreement": {"name": "Steve Gebhardt", "code": "GEBST2", "suffix": 2},
"Beacham Agreement": {"name": "Wade Beacham", "code": "BEAWA2", "suffix": 2},
}
# Fixed-PAIR agreements (BOTH P1 and P2 auto-filled & locked) that β€” unlike the
# locked-pair Mentor/Bond agreements below β€” KEEP the CE/CCP layers available.
# Round 5: Fitch Irick (Wayne Dean + John Streer). Internal TypeCode FITC-IRICK.
FIXED_PAIR_AGREEMENTS = {
"Fitch Irick Agreement": {
"p1": {"name": "Wayne Dean", "code": "DEAWA2", "suffix": 2},
"p2": {"name": "John Streer", "code": "STRJO2", "suffix": 2},
},
}
# Referral / Origination-style agreements: P1 + a separate "Originating Producer"
# slot (sourced from the P2 tiers). Round 5 added the EMB variant alongside the
# original (now displayed as "- Middle Market"). Internal names are the keys.
REFERRAL_ORIG_AGREEMENTS = {
"Referral / Origination Fee Agreement", # display: "... - Middle Market"
"Referral / Origination Fee Agreement - EMB",
}
# Round 6: detection is no longer tied to the exact internal `name`. The admin tier
# editor can rename an agreement's internal name, which used to silently strip the
# Originating-Producer slot off the renamed row. We now also match on the display
# name / a stable prefix, so renamed or admin-created referral variants keep working.
REFERRAL_PREFIX = "Referral / Origination Fee Agreement"
def _is_referral_orig(name: Optional[str], display_name: Optional[str]) -> bool:
if name in REFERRAL_ORIG_AGREEMENTS:
return True
return ((name or "").startswith(REFERRAL_PREFIX)
or (display_name or "").startswith(REFERRAL_PREFIX))
# Agreements where CE/CCP layers deduct ONLY from P1 (not split across all producers)
P1_ONLY_DEDUCTION_AGREEMENTS = {
"Closer Agreement",
} | REFERRAL_ORIG_AGREEMENTS
# Documentation-only CE placeholder (Round 5). Carries 0% commission / 0% credit,
# never deducts from producers, and is excluded from the approval-email workflow.
NO_CE_CODE = "NOCEA1"
# Bella Santana is the standing approval processor for House Standard agreements
HOUSE_STANDARD_PROCESSOR = {
"name": "Bella Santana",
"code": "SANBE1",
"email": "bsantana@snellingswalters.com",
}
# Round 4 β€” locked-pair agreements: both P1 and P2 forced to specific producers,
# no CE/CCP/Originator. Mentor agreements also surface a separate "Mentor Code"
# row (MENTO3-6). Round 8 β€” in Unvalidated Years that row is the ONLY producer row
# displayed (20% commission / 100% production credit); the Mentee/Mentor rows are
# suppressed from the table but remain full approvers.
LOCKED_PAIR_AGREEMENTS = {
"Renno Mentor Agreement (MENTO3)": {
"mentor_code": "MENTO3",
"p1": {"name": "David Roos", "code": "ROODA2", "prefix": "ROODA", "suffix": 2},
"p2": {"name": "Chip Renno", "code": "RENCH2", "prefix": "RENCH", "suffix": 2},
},
"Harmon Mentor Agreement (MENTO4)": {
"mentor_code": "MENTO4",
"p1": {"name": "Brady Niblock", "code": "NIBBR2", "prefix": "NIBBR", "suffix": 2},
"p2": {"name": "Steve Harmon", "code": "HARST2", "prefix": "HARST", "suffix": 2},
},
"Levine Mentor Agreement (MENTO5)": {
"mentor_code": "MENTO5",
"p1": {"name": "Harrison O'Neal", "code": "ONEHA2", "prefix": "ONEHA", "suffix": 2},
"p2": {"name": "Eric Levine", "code": "LEVER2", "prefix": "LEVER", "suffix": 2},
},
"Byrd Mentor Agreement (MENTO6)": {
"mentor_code": "MENTO6",
"p1": {"name": "Matthew Downing", "code": "DOWMA2", "prefix": "DOWMA", "suffix": 2},
"p2": {"name": "Tara Byrd", "code": "BYRTA2", "prefix": "BYRTA", "suffix": 2},
},
"Bond Partner Agreement (Webb/Herron)": {
"mentor_code": None,
"p1": {"name": "Allan Webb", "code": "WEBAL2", "prefix": "WEBAL", "suffix": 2},
"p2": {"name": "Frank Herron", "code": "HERFR2", "prefix": "HERFR", "suffix": 2},
},
}
def get_constant(db: Session, key: str) -> float:
c = db.query(FixedConstant).filter(FixedConstant.key == key).first()
return c.value if c else 0
def get_all_constants(db: Session) -> dict:
return {c.key: c.value for c in db.query(FixedConstant).all()}
def get_agreement_with_tiers(db: Session, agreement_id: int) -> Optional[dict]:
agr = db.query(CommissionAgreement).filter(
CommissionAgreement.id == agreement_id
).first()
if not agr:
return None
p1_tiers = [t for t in agr.tiers if t.producer_role == "P1"]
p2_tiers = [t for t in agr.tiers if t.producer_role == "P2"]
return {
"id": agr.id, "name": agr.name,
"display_name": getattr(agr, "display_name", None) or agr.name,
"p1_suffix": agr.p1_suffix, "p2_suffix": agr.p2_suffix,
"ce_as_producer": agr.ce_as_producer,
"house_processor": bool(agr.house_processor),
"locked_pair": bool(getattr(agr, "locked_pair", False)),
"P1": [_tier_to_dict(t) for t in p1_tiers] or None,
"P2": [_tier_to_dict(t) for t in p2_tiers] or None,
}
def _tier_to_dict(t: CommissionTier) -> dict:
return {
"years": t.years_label, "commission": t.commission, "credit": t.credit,
"agreement": t.agreement_label, "note": t.note, "flag": t.flag,
"use_house_account": bool(t.use_house_account),
}
def _abe_status(suffix) -> str:
if suffix == 1: return "ABE"
if suffix == 2: return "Non-ABE"
return "β€”"
def _ce_code_base(code: str) -> Optional[str]:
"""'BRAJO1' -> 'BRAJO'. None when the code has no re-suffixable form: the
NOCEA1 documentation placeholder (approval_routes.py matches that literal
string, so it must never be rewritten) or any code not ending in a digit."""
if not code or code == NO_CE_CODE or not code[-1].isdigit():
return None
return code[:-1]
def ce_code_variants(code: str) -> set:
"""Both Epic suffix forms of a CE code (…1 ABE / …2 Non-ABE), for callers that
must match approvals recorded under either β€” see the CE delete guard in
admin_routes. Single-element for NOCEA1 / non-numeric codes."""
base = _ce_code_base(code)
if base is None:
return {code} if code else set()
return {code, f"{base}1", f"{base}2"}
def _apply_ce_suffix(db: Session, code: str, suffix) -> str:
"""The CE list stores ONE canonical code per person (seeded as the ABE '…1'
form). Epic gives each CE both an ABE (…1) and a Non-ABE (…2) code, and
CE_SUFFIX / CE_PROD_SUFFIX decides which one an agreement uses β€” so re-suffix
the stored code rather than printing it verbatim. Returns the code unchanged
when there is nothing safe to rewrite:
- no re-suffixable base (NOCEA1, or a code not ending in a digit);
- a suffix outside (1, 2), e.g. an admin typing 1.5 into the constant;
- the derived code already belongs to a DIFFERENT ClientExecutive. Two CEs
sharing a name-base get …1 and …2 from _gen_ce_code, and admins can type
codes freely, so without this guard a re-suffix could stamp one person's
Epic code onto another's agreement and approval record.
NOTE: the stored ClientExecutive.code is never modified β€” saved drafts and the
form's CE picker key on it (see compute_summary's ce_record lookup)."""
base = _ce_code_base(code)
if base is None or suffix not in (1, 2):
return code
derived = f"{base}{suffix}"
if derived == code:
return code
clash = db.query(ClientExecutive).filter(ClientExecutive.code == derived).first()
return code if clash else derived
def _get_tier_at_index(tiers, index):
if not tiers or index >= len(tiers):
return None
return tiers[index]
def compute_summary(db: Session, form_data: dict) -> dict:
agr_id = form_data.get("agreement_id")
if not agr_id:
return {"error": "No agreement selected", "year_tables": [],
"participants": [], "warnings": []}
R = get_agreement_with_tiers(db, agr_id)
if not R:
return {"error": "Agreement not found", "year_tables": [],
"participants": [], "warnings": []}
consts = get_all_constants(db)
p1pfx = form_data.get("producer1_prefix")
p2pfx = form_data.get("producer2_prefix")
is_locked_pair = bool(R.get("locked_pair")) or R["name"] in LOCKED_PAIR_AGREEMENTS
locked_pair = LOCKED_PAIR_AGREEMENTS.get(R["name"]) if is_locked_pair else None
# Fitch Irick-style fixed PAIR: both producers locked, but CE/CCP stay available.
is_fixed_pair = R["name"] in FIXED_PAIR_AGREEMENTS
fixed_pair = FIXED_PAIR_AGREEMENTS.get(R["name"]) if is_fixed_pair else None
if is_locked_pair and locked_pair:
# Force P1/P2 prefixes from the locked-pair dict; engine ignores form payload
# for these fields so a tampered request can't substitute different producers.
p1pfx = locked_pair["p1"]["prefix"]
p2pfx = locked_pair["p2"]["prefix"]
elif is_fixed_pair and fixed_pair:
# Fitch Irick-style: both producers forced from the dict (codes overridden
# below). Use code-minus-suffix as the prefix so P2 renders + validation passes.
p1pfx = fixed_pair["p1"]["code"][:-1]
p2pfx = fixed_pair["p2"]["code"][:-1]
# Round 7: locked-pair agreements (Mentor 3-6 + Bond Partner) now carry the CE
# layer too β€” the form sends the mandatory "No Client Executive" (NOCEA1) dropdown
# default, and a real CE deducts the standard 2% (split across producers). CCP
# stays disabled for locked pairs (see below).
include_ce = form_data.get("include_ce", False)
# CE selector value is now a ClientExecutive code (key 'ce_code'); fall back
# to the legacy 'ce_prefix' so older saved drafts still resolve.
cepfx = (form_data.get("ce_code") or form_data.get("ce_prefix")) if include_ce else None
# NOCEA1 is a documentation-only placeholder: 0% / 0%, no deduction, no approval email.
is_nocea = (cepfx == NO_CE_CODE)
include_ccp = form_data.get("include_ccp", False) and not is_locked_pair
ccp_selection = form_data.get("ccp_selection") if not is_locked_pair else None
has_cc = include_ccp and ccp_selection
is_ce = R["ce_as_producer"]
is_house_processor = R["house_processor"]
is_referral_orig = _is_referral_orig(R["name"], R["display_name"]) and not is_locked_pair
is_fixed_code = R["name"] in FIXED_CODE_AGREEMENTS
# P1-only deduction == Closer Agreement plus every referral/origination variant
# (kept in sync with REFERRAL detection so renamed referral rows deduct correctly).
is_p1_only_deduction = (R["name"] == "Closer Agreement") or is_referral_orig
if is_referral_orig:
has_p2 = False
orig_tiers = R["P2"]
else:
has_p2 = R["P2"] is not None
orig_tiers = None
warnings = []
participants = []
def _lookup_name(prefix):
if not prefix: return ""
p = db.query(Producer).filter(Producer.prefix == prefix).first()
return p.name if p else ""
# Resolve the CE from the standalone ClientExecutive list first; if the
# selector value isn't a known CE code, fall back to the legacy Producer
# path below so existing workflows/drafts behave exactly as before.
ce_record = (
db.query(ClientExecutive).filter(ClientExecutive.code == cepfx).first()
if cepfx else None
)
ce_email = ce_record.email if ce_record else None
p1name = _lookup_name(p1pfx) if not is_house_processor else ""
p2name = _lookup_name(p2pfx)
cename = ce_record.name if ce_record else _lookup_name(cepfx)
p1suf = R["p1_suffix"]
p2suf = R["p2_suffix"]
ce_suf = int(consts.get("CE_PROD_SUFFIX", 2)) if is_ce else int(consts.get("CE_SUFFIX", 1))
if is_locked_pair and locked_pair:
# Locked pair wins over every other code resolution path
p1code = locked_pair["p1"]["code"]
p1name = locked_pair["p1"]["name"]
p1suf = locked_pair["p1"]["suffix"]
p2code = locked_pair["p2"]["code"]
p2name = locked_pair["p2"]["name"]
p2suf = locked_pair["p2"]["suffix"]
elif is_fixed_pair and fixed_pair:
# Both producers hardcoded (Fitch Irick). CE/CCP layers stay active.
p1code = fixed_pair["p1"]["code"]
p1name = fixed_pair["p1"]["name"]
p1suf = fixed_pair["p1"]["suffix"]
p2code = fixed_pair["p2"]["code"]
p2name = fixed_pair["p2"]["name"]
p2suf = fixed_pair["p2"]["suffix"]
elif is_fixed_code:
fixed = FIXED_CODE_AGREEMENTS[R["name"]]
p1code = fixed["code"]
p1name = fixed["name"]
p1suf = fixed["suffix"]
p2code = f"{p2pfx}{p2suf}" if (has_p2 and p2pfx and p2suf) else "β€”"
elif is_house_processor:
p1code = "β€”"
p2code = f"{p2pfx}{p2suf}" if (has_p2 and p2pfx and p2suf) else "β€”"
else:
p1code = f"{p1pfx}{p1suf}" if (p1pfx and p1suf) else "β€”"
p2code = f"{p2pfx}{p2suf}" if (has_p2 and p2pfx and p2suf) else "β€”"
# The CE list stores one canonical code per person (all seeded as '…1'), so the
# stored code alone can never reflect CE_SUFFIX / CE_PROD_SUFFIX β€” re-suffix it
# here so the Code column and the ABE Status column below are driven by the same
# ce_suf. Legacy (non-CE-list) path keeps building prefix+suffix as before.
cecode = (_apply_ce_suffix(db, ce_record.code, ce_suf) if ce_record
else (f"{cepfx}{ce_suf}" if cepfx else "β€”"))
has_orig_employee = form_data.get("employee_as_originator", False) and is_referral_orig
has_orig_producer = (not has_orig_employee and
form_data.get("orig_producer_prefix") and is_referral_orig)
has_orig = has_orig_producer or has_orig_employee
if has_orig_producer:
opfx = form_data["orig_producer_prefix"]
ocode = f"{opfx}{p2suf}" if p2suf else f"{opfx}2"
oname = _lookup_name(opfx)
elif has_orig_employee:
ocode = "EMPOR2"
oname = form_data.get("orig_employee_name", "")
else:
ocode = "β€”"
oname = ""
CE_COMM = consts.get("CE_COMM", 2)
CC_COMM = consts.get("CC_COMM", 5)
total_deduction = 0
# NOCEA1 is a 0% documentation placeholder β€” it never deducts from producers.
if cepfx and not is_ce and not is_nocea: total_deduction += CE_COMM
if has_cc: total_deduction += CC_COMM
# Validation
if (not p1pfx and not is_ce and not is_fixed_code and not is_house_processor
and not is_locked_pair
and R["name"] != "Ceded Accounts - CE as Producer"):
warnings.append("Producer 1 (Lead) is required but not selected.")
if has_p2 and not p2pfx and not is_locked_pair:
warnings.append(f"{R['name']} uses two producers β€” select Producer 2.")
if is_ce and not cepfx:
warnings.append("This agreement needs a Client Executive as producer β€” select one above.")
if is_referral_orig and not has_orig:
warnings.append("Referral / Origination Fee Agreement requires an Originating Producer β€” select one.")
is_bond = "bond" in R["name"].lower()
if has_cc and is_bond:
warnings.append("Complex Claims Practice (CCP) is not eligible for Bond Agreement.")
# Build per-year tables
year_labels = []
for src in ([R["P1"]] + ([orig_tiers] if is_referral_orig and orig_tiers else
[R["P2"]] if has_p2 and R["P2"] else [])):
if src:
for t in src:
if t["years"] not in year_labels:
year_labels.append(t["years"])
if not year_labels:
year_labels = ["All Years"]
year_tables = []
for yi, year_label in enumerate(year_labels):
year_rows = []
p1_tier = _get_tier_at_index(R["P1"], yi) if R["P1"] else None
p2_tier = None
orig_tier_data = None
if is_referral_orig and orig_tiers:
orig_tier_data = _get_tier_at_index(orig_tiers, yi)
elif has_p2 and R["P2"]:
p2_tier = _get_tier_at_index(R["P2"], yi)
# Are we rendering a House-Account row in lieu of P1 for this year?
# Two trigger conditions:
# 1. Agreement has house_processor=True (House Standard).
# 2. The P1 tier itself flags use_house_account=True (Life Y2+).
p1_uses_house = bool(
is_house_processor or (p1_tier and p1_tier.get("use_house_account"))
)
active_producers = 0
if p1_tier and p1_tier["commission"] is not None and not is_ce and not p1_uses_house:
active_producers += 1
if p2_tier and p2_tier["commission"] is not None: active_producers += 1
if orig_tier_data and orig_tier_data["commission"] is not None: active_producers += 1
if is_p1_only_deduction:
p1_ded, p2_ded, orig_ded = total_deduction, 0, 0
else:
pp = total_deduction / active_producers if active_producers > 0 else 0
p1_ded = p2_ded = orig_ded = pp
if p1_uses_house and p1_tier:
# House Account row β€” deducts CE/CCP from base 100% (or whatever tier defines).
base = p1_tier["commission"] if p1_tier["commission"] is not None else 100.0
house_comm = base
if cepfx and not is_ce and not is_nocea:
house_comm -= CE_COMM
if has_cc and not is_bond:
house_comm -= CC_COMM
year_rows.append({
"role": "House Account",
"agreement_type": p1_tier["agreement"] or "House Standard Agreement",
"commission_pct": round(max(house_comm, 0), 1),
"production_pct": p1_tier.get("credit") if p1_tier.get("credit") is not None else 100.0,
"abe_status": "Non-ABE",
"code": "ACCHO1",
"name": "House Account",
})
else:
# Round 8 β€” Mentor agreements (MENTO3-6) collapse the Mentee (P1) and
# Mentor (P2) rows into the single MENTO code row during Unvalidated
# Years. DISPLAY ONLY: the tiers, producer codes, participants list and
# approval routing are untouched, so both producers still receive their
# approval email and still block finalization.
mentor_code = locked_pair.get("mentor_code") if (is_locked_pair and locked_pair) else None
mentor_rollup = bool(mentor_code) and year_label == "Unvalidated Years"
if not is_ce and p1_tier and p1_tier["commission"] is not None and not mentor_rollup:
# Mentor agreements transition the former Mentee from Non-ABE Code 2
# to ABE Code 1 in the Validated Years tier.
p1_row_code = p1code
p1_row_abe = _abe_status(p1suf)
p1_row_role = "Producer 1"
if is_locked_pair and locked_pair and locked_pair.get("mentor_code"):
if p1_tier["agreement"] == "Standard Agreement":
p1_row_code = f"{locked_pair['p1']['prefix']}1"
p1_row_abe = "ABE"
else:
p1_row_role = "Mentee (P1)"
year_rows.append({"role": p1_row_role, "agreement_type": p1_tier["agreement"],
"commission_pct": round(max(p1_tier["commission"] - p1_ded, 0), 1),
"production_pct": p1_tier["credit"], "abe_status": p1_row_abe,
"code": p1_row_code, "name": p1name})
if (not is_referral_orig and p2_tier and p2_tier["commission"] is not None
and p2pfx and not mentor_rollup):
p2_row_role = "Producer 2"
if is_locked_pair and locked_pair and locked_pair.get("mentor_code"):
p2_row_role = "Mentor (P2)"
year_rows.append({"role": p2_row_role, "agreement_type": p2_tier["agreement"],
"commission_pct": round(max(p2_tier["commission"] - p2_ded, 0), 1),
"production_pct": p2_tier["credit"], "abe_status": _abe_status(p2suf),
"code": p2code, "name": p2name})
if is_referral_orig and has_orig and orig_tier_data and orig_tier_data["commission"] is not None:
year_rows.append({"role": "Originating Producer", "agreement_type": orig_tier_data["agreement"],
"commission_pct": round(max(orig_tier_data["commission"] - orig_ded, 0), 1),
"production_pct": orig_tier_data["credit"],
"abe_status": "Non-ABE" if has_orig_employee else _abe_status(p2suf),
"code": ocode, "name": oname})
# Mentor agreements only: add the MENTO code row in Unvalidated Years
# (mechanically derived, not stored as a producer tier). Round 8 β€” this
# row now carries the commission that used to sit on the suppressed
# Mentee/Mentor rows. Read the rate off the tier rather than hardcoding
# 20 so admin edits in the panel flow through; the p2_tier fallback
# covers an admin edit that dropped the P1 tier (cf. the Fitch Irick
# repair in start.py). Being the only commission-bearing producer row
# left, it absorbs the FULL CE/CCP deduction rather than a per-producer
# share β€” so 20% with NOCEA1, 18% with a real CE (18 + 2 = 20).
if mentor_rollup:
base_tier = p1_tier if (p1_tier and p1_tier["commission"] is not None) else p2_tier
base = base_tier["commission"] if (base_tier and base_tier["commission"] is not None) else 0
mento_comm = round(max(base - total_deduction, 0), 1)
# Keep this row's integer style ("20%", not "20.0%") β€” the generators
# print the raw value and this row has always shown whole numbers.
if mento_comm == int(mento_comm):
mento_comm = int(mento_comm)
year_rows.append({"role": "Mentor Code", "agreement_type": "Mentor Code",
"commission_pct": mento_comm,
"production_pct": 100,
"abe_status": "Non-ABE", "code": mentor_code, "name": mentor_code})
# CCP row appears BEFORE CE row (v2.1)
if has_cc and not is_bond:
ccp_name = "Mike Parsa" if ccp_selection == "mike_parsa" else "House Account"
ccp_code = "PARMI2" if ccp_selection == "mike_parsa" else "ACCHO1"
year_rows.append({"role": "CCP Layer", "agreement_type": "Complex Claims Practice",
"commission_pct": CC_COMM, "production_pct": consts.get("CC_CREDIT", 0),
"abe_status": _abe_status(2), "code": ccp_code, "name": ccp_name})
if cepfx and not is_ce:
# NOCEA1 placeholder β†’ 0% / 0% and a documentation label; real CE β†’ standard 2%.
year_rows.append({
"role": "CE Layer",
"agreement_type": "No Client Executive" if is_nocea else "Client Executive Agreement",
"commission_pct": 0 if is_nocea else CE_COMM,
"production_pct": 0 if is_nocea else consts.get("CE_CREDIT", 0),
"abe_status": "Non-ABE" if is_nocea else _abe_status(ce_suf),
"code": cecode, "name": cename})
elif is_ce and cepfx:
ce_prod_comm = consts.get("CE_PROD_COMM", 10)
if has_cc and not is_bond:
ce_prod_comm = max(ce_prod_comm - CC_COMM, 0)
year_rows.append({"role": "Client Executive", "agreement_type": "Ceded Accounts β€” CE as Producer",
"commission_pct": ce_prod_comm,
"production_pct": consts.get("CE_PROD_CREDIT", 100),
# ce_suf IS CE_PROD_SUFFIX on this branch (is_ce) β€” read it
# rather than the constant again, so this label and the
# code above provably come from one value.
"abe_status": _abe_status(ce_suf),
"code": cecode, "name": cename})
if year_rows:
year_tables.append({"year_label": year_label, "rows": year_rows})
# Participants list β€” order: P1, P2, Originator, House Standard processor, CCP, CE, CCP-Mike fallback
seen_codes = set()
def _add_participant(role, code, name, email=None, no_approval=False):
if not code or code == "β€”" or code in seen_codes:
return
entry = {"role": role, "code": code, "name": name}
if email:
entry["email"] = email
if no_approval:
# Visible in tracking + PDF, but auto-satisfied: no approval email,
# never blocks finalization (NOCEA1 documentation placeholder).
entry["no_approval"] = True
participants.append(entry)
seen_codes.add(code)
if is_house_processor:
# Bella Santana is the standing processor
_add_participant("Approval Processor", HOUSE_STANDARD_PROCESSOR["code"],
HOUSE_STANDARD_PROCESSOR["name"], HOUSE_STANDARD_PROCESSOR["email"])
if is_fixed_code:
fixed = FIXED_CODE_AGREEMENTS[R["name"]]
_add_participant("Producer 1", fixed["code"], fixed["name"])
elif p1pfx and not is_ce and not is_house_processor:
p1_role = "Producer 1"
if is_locked_pair and locked_pair and locked_pair.get("mentor_code"):
p1_role = "Mentee (P1)"
_add_participant(p1_role, p1code, p1name)
if has_p2 and p2pfx:
p2_role = "Producer 2"
if is_locked_pair and locked_pair and locked_pair.get("mentor_code"):
p2_role = "Mentor (P2)"
_add_participant(p2_role, p2code, p2name)
if has_orig:
_add_participant("Originating Producer" if has_orig_producer else "Originating Employee",
ocode, oname)
# CCP before CE in approval order (v2.1)
if has_cc:
# Mike Parsa always approves regardless of which CCP option was selected
_add_participant("CCP (Mike Parsa)", "PARMI2", "Mike Parsa")
if cepfx:
# Pass the CE's own email when it came from the ClientExecutive list;
# when None (legacy producer path), form_routes falls back to its
# existing producer-email lookup exactly as before.
_add_participant("CE as Producer" if is_ce else "Client Executive",
cecode, cename, email=ce_email, no_approval=is_nocea)
return {
"agreement_name": R["display_name"] or R["name"], # user-facing label (PDF/DOCX/dashboard)
"year_tables": year_tables,
"participants": participants,
"warnings": warnings,
"house_processor": is_house_processor,
}