Billing (libraries: billing, billfunc)
Purpose
The Billing domain produces periodic premium/fee bills for self-funded plan clients. A
billing definition (bill_defs) names a set of client groups and a set of billing
rules; each rule computes charges at one of three levels — per bill, per group, or per
member — using effective-dated rate tables. A billing run (bill_runs +
bill_run_defs) executes one or more definitions as of a bill date, writes charge detail
to bill_charges, recalculates prior periods to generate retroactive adjustment charges,
and assembles a printable group bill (invoice/statement) stored in the report tables.
Receipts (payments) and manual charge adjustments are entered against a definition and
appear on the next bill's balance-forward section.
The original HOMER analysis document (User Guide ch. 4 "Billing (A/R)", process owners
Rolf Gaab and Avis Konocz) frames the module as five major functions: (1) define the
rules for billing (when/how to bill, how to allocate, how to distribute payments);
(2) calculate, store and print bills — including retroactive adjustments for prior-period
enrollment changes and balance forward of unpaid balances/surpluses; (3) accept payments;
(4) reconcile payments to billed amounts ("recalc") with adjusting entries; and
(5) distribute the allocated payments (create checks). Step 5 was explicitly left manual
("Scope requires 1 through 4 be satisfied with acceptable feeds to manual process for
5") (User Guide ch. 4). The implemented billing/billfunc libraries cover 1–4;
distribution/check writing lives in the Funding domain. Note the guide predates the
implementation — where it disagrees with extracted code, code wins and the difference is
flagged below.
- Charge computation engine:
billfunc/n_billproc("Functions for billing process, including computing bill, group, and member rates"). - Bill/invoice assembly:
billfunc/n_bill_functions("Global billing functions"). - Set-based recalc/adjustment SQL and stored-procedure calls:
billfunc/n_billproc_sql(calls Oracle packagepkg_bills). - Screens (18 windows) live in
billing; see the Screen catalog.
Actors
Evidenced by screens and menu entries (homersys/m_sys_frame Billing menu):
- Billing administrator — maintains billing definitions, rules, rate tables and
ranges (
billing/w_bill_bill_def_maint,billing/w_bill_bill_rule_maint,billing/w_bill_bill_range_maint). - Billing operator — submits/re-submits/deletes billing runs
(
billing/w_bill_run_def_maint), views and prints finished bills (billing/w_bill_queue), runs exception reports (billing/w_bill_reports). - Accounts receivable clerk — enters receipts and manual charge adjustments
(
billing/w_bill_receipt_maint,billing/w_bill_adj_maint). - Batch process — an external batch application executes SUBMITTED runs by calling
n_billproc.of_billandn_bill_functions.of_report; a console/scheduler object (n_batch_scheduler) receives progress log lines (billfunc/n_billproc.of_registerconsole). The run screen's comments refer to this process as "SFPIBACK" (billing/w_bill_run_def_maintpfc_save comment); its executable is outside the extracted libraries.
Key workflows
1. Billing setup
- Define billing rules (
bill_rules): code, description, bill level (BILL / GROUP / MEMBER, code keyBLNGLEVELperbilling/dddw_billing_level), optional rate type (rate variables), default category and frequency (billing/w_bill_bill_rule_maint). - Attach parameters (
bill_rule_parms+bill_rule_parm_details) that decide which groups/members a rule applies to; sources are limited to ELECTIONS, GROUPS, GROUP_COVERAGES, GROUP_OPTION_VALS, MEMBER_CAT_VAL, MEMBER_ELIGIBILITIES and PLAN_PKGS (billfunc/n_billproc.of_ruleparmsql). - Define rate tables per rule (
bill_rate_tables, effective-dated from/thru) with rate amounts per rate item (bill_rate_amts), rounding codes (code keysBILLRATERN,BILLUNITRNperbilling/dddw_bill_rate_rounding_cd,billing/dddw_bill_unit_rounding_cd) and vendor allocations (bill_rate_allocs, method PCT or AMT). - Define bill ranges (
bill_ranges+bill_range_items) — banded lookup tables (for example age bands) that map a computed value to a rate-item heading (billing/w_bill_bill_range_maint,billfunc/n_billproc.of_assemblerateitem). - Create a billing definition (
bill_defs): code, description, frequency, billing-year begin month, adjustment periods/logic, report formats, billing begin date, inactive date; attach groups (bill_def_groups), attach rules with per-definition category/frequency/rate-period and from/thru dates (bill_def_rules); set options (bill_def_option_vals) (billing/w_bill_bill_def_maint).
2. Billing run lifecycle
- Submission writes a
bill_runsrow (key fromBILL_RUN_SEQ) withbill_dtanddue_dt, and onebill_run_defsrow per selected definition with statusSUBMITTED(orDELAYEDwhen "Delay Processing" is checked) (billing/w_bill_run_def_maintpfc_preupdate, pfc_addrow). - Deleting a run removes its charges and recalc rows (
pkg_bills.delete_bill_charges,pkg_bills.delete_recalc_chargesviabillfunc/n_billproc_sql), deletes the run's bill reports/report data, deletes its receipts, and detaches receipts and manual adjustments that only appeared on that bill back to the unassigned run (−100) (billing/w_bill_run_def_maintpfc_save). - Re-submitting deletes the run's charges and voids its reports (status
VOID), then sets the run-definition status back toSUBMITTED(billing/w_bill_run_def_maintpfc_save).
3. Charge calculation (n_billproc.of_calculate_bill)
For the definition and bill date, the engine retrieves definition rules, rate tables,
amounts, allocations, rate variables, ranges and parameter details as of the bill date
(billfunc/n_billproc.of_billdataretrieve), plus the group tree of the definition
(each bill_def_groups root expanded to leaf groups via the groups
parent/child hierarchy — billfunc/d_bill_groups). Then per rule:
- Frequency gate — ANNUAL rules bill only in their begin month; QUARTERLY rules in
begin month +0/3/6/9; MONTHLY always; ONCE rules are skipped if a charge already
exists for the rule in the from/thru window (
of_calculate_bill). - BILL level — one charge with units fixed at 1 and the single rate amount of the
effective rate table (
of_billlevelrule). - GROUP level — for each leaf group where the rule's parameter SQL is satisfied
(
of_groupruleapplies), one flat-rate charge; only FLAT rates are supported at group level — any other rate type raises a fatal error (of_grouplevelrule). - MEMBER level — for each leaf group, the parameter SQL resolves the member
population (active
member_eligibilitiessubscriber rows as of the date, joined to elections/coverage where coverage-based —of_memberruleapplies,billfunc/d_bill_rule_members); each member gets a charge computed from the rate item (see below) (of_memberlevelrule). - Charge record — every charge is split across the rule's vendor allocations, one
bill_chargesrow per allocation entity, key fromBILL_CHARGE_SEQ, with charge typeNEW, the bill period date, level, category, rate item, units, rate and election data (of_recordcharge,of_allocateamt,of_newkey).
Rate math (identical at all three levels) is a three-step sequence — rounding is
applied twice, to different quantities (of_billlevelrule, of_grouplevelrule,
of_memberlevelrule, of_round):
units = round(units, unit_rounding_cd)— units are rounded before the division.charge = (units ÷ rate_unit) × rate_amt, where a null or zerorate_unitis coerced to 1.charge = round(charge, rate_rounding_cd).
Both rounding codes share one vocabulary, decoded by prefix:
| Code | Effect | Example |
|---|---|---|
NEARn | Round(x ÷ n) × n — nearest multiple of n | NEAR.01 → nearest cent |
DOWNn | Truncate(x ÷ n) × n — toward zero | DOWN1 → whole dollars down |
UPn | Ceiling(x ÷ n) × n | UP1 → whole dollars up |
| anything else (including null) | default: round to the nearest cent | — |
Units are 1 everywhere except volume-rated member charges: when a member-level rate
table's rate_unit is not 1, units are the member's election coverage benefit amount
(e.g. per-$1,000 life volume), and a member missing that amount is a fatal error
(of_memberlevelrule).
No proration: the final specification confirms the implemented as-of-date behavior — "All billing rules are created based upon monthly billing and per month charges. Charges are generated based upon coverages existing on the first day of the month, and are not prorated" (User Guide ch. 4). Earlier analysis in the same chapter catalogs five proration methods (Mid-Month, True Percentage, First Half, Whole/half month, No Proration) that were considered and then dropped from scope; the engine's member selection as of the bill date is equivalent to Mid-Month/no-proration.
Rate item resolution (of_assemblerateitem): if the rule's rate type has no rate
variables the item is FLAT; otherwise each rate variable's SQL template
(bill_rate_vars.rate_var_sql) is resolved by substituting :group_id,
:member_sys_key, :bill_dt, :birth_dt, :gender_cd, :coverage_level,
:coverage_amt and executing it (or reading the variable directly when the template is
just a variable). If the variable references a bill range, the raw value is translated to
the matching range-item heading (numeric ranges only). Results concatenate as
SOURCE.VARIABLE=value;... and must match a bill_rate_amts.rate_item exactly.
Rate as-of date: a definition rule's rate_period_cd selects which rate table
applies — CALYR uses Jan 1 of the bill year, PLANYR uses the group's STOPLOSS
period start (group_periods via of_groupperiod), otherwise the bill date itself
(of_billlevelrule, of_grouplevelrule, of_memberlevelrule).
4. Retroactive adjustments (n_billproc.of_adjust_bill)
For each of the definition's adj_periods prior periods (stepping back one month,
quarter, or year according to definition frequency), bounded below by
adj_beg_dt (default: 5 years back) and by adjustment logic (adj_logic_cd:
BILLYEAR = not before billing-year start, CALYEAR = not before Jan 1, BILLCAL =
combination):
- Recalculate the period with current data into
bill_recalc_charges_temp(charge typeRECALC), zeroing member key at GROUP level and member/group at BILL level. - Summarize history: insert the surviving net historical charges for that period
into the same temp table (charge type
HISTORICAL) — summing all prior-run charge amounts per member/group/category/rule/allocation entity, carrying rate details from the latest prior charge, and excluding items already fully dropped viaADJ-DROP(billfunc/n_billproc_sql.of_inserthistoricalcharges). - Compare RECALC vs HISTORICAL and emit adjustment charges into
bill_charges(of_recordadjustment):- in RECALC but not HISTORICAL →
ADJ-ADD(positive) (billfunc/d_billproc_adj_add); - in HISTORICAL but not RECALC →
ADJ-DROP(amounts negated) (billfunc/d_billproc_adj_drop); - same member/group/category but different rule or allocation entity → paired
ADJ-RATEreversal (−historical) and re-bill (+recalc) (billfunc/d_billproc_hist_adj_rate,d_billproc_recalc_adj_rate); - same rule/entity but changed election level, election benefit amount, or rate item
→ paired
ADJ-CHANGEreversal and re-bill (billfunc/d_billproc_hist_adj_change,d_billproc_recalc_adj_change).
- in RECALC but not HISTORICAL →
- Recalc rows are persisted from temp to
bill_recalc_chargesviapkg_bills.move_temp_to_recalc; temp tables are truncated and indexes rebuilt viapkg_billsprocedures (billfunc/n_billproc_sql).
5. Bill/statement production (n_bill_functions.of_report)
For the run/definition, definition groups are resolved to active eligibility-level
groups as of the bill date (billfunc/d_billprt_elig_groups); depending on option
BILL_EACH one combined bill or one bill per group is produced (of_report). Each bill
is a composite report assembled from sub-reports (of_create_bill2): mailing/remit
page with balance summary, member detail, optional age-rate detail, member adjustment
detail, member bulk adjustments, group charges, optional per-group category summaries,
bill-level charges, group summary, overall category summary, optional vendor
disbursement summary, final summary, and an optional client self-adjustment page. The
finished report is stored via n_report_data.of_InsertReportData with category BILL
(errors as BILLERR / BILLPRTERR) and linked to the run in bill_reports
(of_savebill, n_billproc.of_bill).
Statement balance section (of_mailingpage, billfunc/d_billprt_def_balance_info):
- Previous balance = all prior-run charges (excluding this run's manual adjustments)
minus all prior receipts (excluding those first appearing on this run), limited to
runs on/after the definition's
bill_beg_dt. - Payments = receipts whose
appearance_run_sys_keyequals this run. - Client adjustments = manual
bill_chargeswhosemanual_adj_run_sys_keyequals this run. - Current billed = NEW + ADJ-ADD/DROP/CHANGE/RATE totals by level (member, group,
other/bill) (
billfunc/d_billprt_charge_total). - Current due = previous balance − payments + client adjustments + current billed.
6. Receipts and manual adjustments
- Receipts are entered per definition against a specific COMPLETE billed run (and group
or member when the definition bills per group/member), with amount, date, check
number, description; new receipts get
appearance_run_sys_key = -100(unassigned) and can be edited or deleted only while unassigned (billing/w_bill_receipt_maint,billing/w_bill_receipt_response,billing/d_bill_receipt_view). - Manual adjustments are add/drop charge entries per definition: "drop" reverses
selected existing NEW charges (negated amounts, charge type
ADJ-DROP,manual_adj_run_sys_key = -100); manual entries can be deleted only while still unassigned (billing/w_bill_adj_maint). - User-guide context: adjustments taken by the client on their payment ("self adjustments") are posted manually so that client mispayments are not silently absorbed; the intended rule was that the only member-level adjustment a client may take on a bill is for terminated employees, with under/overpayment variances routed to an over/short account (222 miscellaneous, 207 premiums) during the reconciliation ("recalc") process (User Guide ch. 4). Reconciliation itself is an accounting-side process outside these libraries.
- Binder checks are a second class of incoming money the guide requires but the code does not model: estimated premium paid to a carrier before any bill exists, which must reduce the first bill generated (BIL-49).
7. Commissions hand-off
The user guide ties commissions to billing/AR as follows: commission amounts owed by
SFPI to a broker "may be built into the amount charged to the group, but they are not
directly associated" — the money is collected as part of billing (often withheld within
the billed amount) but calculated and disbursed entirely separately by the Commissions
function, outside the check run, maintained by Accounting; a broker is paid only after
the client's bill has been paid, with an estimated payment trued up after allocation if
needed (User Guide ch. 4, ch. 8). The extracted billing/billfunc libraries contain
no commission calculation or broker-payment processing; commission-related objects found
in the sales library are documented under Groups & Plans. In billing terms, a
withheld commission is simply a vendor allocation of a rate amount
(bill_rate_allocs); the onward broker payment is out of scope for this module.
The guide adds that a case may carry multiple brokers with a split formula, that brokers are paid once a month (Medicare-supplement brokers may take several payments), and that the commission report lists client, date, dollars, plan year and broker code sorted by client number. It leaves four questions unanswered that a migration must still settle: how a commission check is voided; which commission payments are taxable and therefore generate a 1099; whether SFPI and M3 commissions accumulate separately under distinct payer identities; and whether internal sales staff are paid from the same pool. The guide's own wishlist was to commission on actual enrollment, fees and premiums rather than an annualized estimate (User Guide ch. 8).
8. COBRA coupons and individual billing (user guide — not in these libraries)
The guide defines coupons as "the names for the bills sent to Cobra-covered
individuals" and requires: COBRA elections billed through the same rate machinery with
COBRA-specific rates at the coverage/option level (an election is COBRA per
ELECTIONS.COBRA_FLG); a claim-funding component of the COBRA premium stored separately
and allocated to claims reserve instead of the standard payees; coupon books generated
per enrollment/renewal event through end of eligibility or COBRA plan year; first
payment due 105 days after the qualifying event and subsequent payments 30 days after
the first of the month; coverage terminated on missed payment (User Guide ch. 4).
Medicare billing (500-xxx groups) likewise bills individuals, with per-subscriber rate
overrides and potentially a distinct broker/policy number per subscriber (User Guide
ch. 4). In the extracted code the rule editor supports a COBRA-flag criterion
(billing/d_bill_election_cobra_flg), but no coupon production, individual-bill
destination, or per-subscriber rate override exists in billing/billfunc — the
implemented module produces group bills only. See BIL-44/BIL-46.
Business rules
Each statement is testable; citations are library/object.
- BIL-1 A billing run/definition combination that already has charges must not be
billed again; the run aborts with a fatal error
(
billfunc/n_billproc.of_bill). - BIL-2 A run must not be billed when a later-dated bill already exists for the
definition, nor when a bill has already been produced for the same calendar month
(
billfunc/n_billproc.of_bill). - BIL-3 ANNUAL rules generate charges only when the bill month equals the rule's
begin month; QUARTERLY rules only when the bill month is the begin month plus 0, 3, 6
or 9 (mod 12); MONTHLY rules always; unrecognized frequencies are fatal errors
(
billfunc/n_billproc.of_calculate_bill). - BIL-4 A ONCE-frequency rule is skipped when any charge already exists for that
rule/definition/run within the rule's from/thru date window
(
billfunc/n_billproc.of_calculate_bill). - BIL-5 Rule level must be BILL, GROUP, or MEMBER; any other level is a fatal error
(
billfunc/n_billproc.of_calculate_bill). - BIL-6 The rate table used is the one effective for the rate as-of date, which is
Jan 1 of the bill year for rate period
CALYR, the group's STOPLOSS plan-year start forPLANYR, and the bill date otherwise; a missing rate table is a fatal error (billfunc/n_billproc.of_billlevelrule,of_grouplevelrule,of_memberlevelrule). - BIL-7 A bill-level rule uses a single rate amount with units fixed at 1; multiple
rate amounts on a bill-level rule produce a warning and the first is used
(
billfunc/n_billproc.of_billlevelrule). - BIL-8 Group-level rules support only FLAT rate amounts; a non-flat rate type at
group level is a fatal error (
billfunc/n_billproc.of_grouplevelrule). - BIL-9 Charge amount = (units ÷ rate unit) × rate amount, where units are rounded
per the rate table's unit-rounding code before the division and the result is
rounded per its rate-rounding code; a null or zero rate unit is treated as 1.
Rounding codes are NEARn (nearest multiple of n), DOWNn (truncate toward zero),
UPn (ceiling), defaulting to nearest cent
(
billfunc/n_billproc.of_round,of_billlevelrule,of_memberlevelrule). - BIL-10 When a member-level rate table's rate unit is not 1, billing units are the
member's election coverage benefit amount; a member missing this amount is a fatal
error (
billfunc/n_billproc.of_memberlevelrule). - BIL-11 For non-flat member rates, the rate item is assembled from the rule's rate
variables (resolved per member via SQL substitution of group, member, bill date,
birth date, gender, coverage level and coverage amount) and must match a rate amount's
rate item exactly; no match is a fatal error
(
billfunc/n_billproc.of_assemblerateitem,of_memberlevelrule). - BIL-12 A rate variable tied to a bill range translates the computed value to the
range item heading whose numeric from/thru brackets contain it
(
billfunc/n_billproc.of_assemblerateitem). - BIL-13 A member is in a rule's population only while holding an active subscriber
eligibility row in the group as of the member date, excluding members with life
status 'Fake' or 'PCS' (
billfunc/d_bill_rule_members). - BIL-14 When a definition carries the
MAXENROLLoption date, member selection is as of the earlier of the bill date and that option date (billfunc/n_billproc.of_memberruleapplies). - BIL-15 For coverage-based member rules, the election used for billing is the one
matching the coverage-type parameter with the lowest
BILLCOVRcode display sequence among the member's active elections (billfunc/n_billproc.of_ruleparmsql). - BIL-16 Rule parameter sources are restricted to ELECTIONS, GROUPS,
GROUP_COVERAGES, GROUP_OPTION_VALS (GROUP_OPTIONS is auto-corrected to
GROUP_OPTION_VALS), MEMBER_CAT_VAL, MEMBER_ELIGIBILITIES and PLAN_PKGS; any other
source is a fatal error (
billfunc/n_billproc.of_ruleparmsql). - BIL-17 For GROUP_OPTION_VALS and MEMBER_CAT_VAL parameters, the parameter
variable is a code (option/category code), and the compared column is selected by
that code's value type (NUMBER/DATE/CODE/CHAR); an unexpected value type is a fatal
error (
billfunc/n_billproc.of_ruleparmsql). - BIL-18 Parameter details connected by OR start a new union branch in which the
fixed source-join criteria are repeated, so OR alternatives each still satisfy
the base joins (
billfunc/n_billproc.of_ruleparmsql). - BIL-19 A charge is recorded only when its amount, election benefit amount, or
election level is non-zero/non-empty (
billfunc/n_billproc.of_recordcharge). - BIL-20 Every recorded charge is split into one row per allocation entity of its
rate amount; percentage allocations must total exactly 100% (under- or
over-allocation is a fatal error, except a single-entity allocation which takes the
full amount with a warning); amount allocations must reconcile to the rate amount,
with the final entity absorbing rounding remainder
(
billfunc/n_billproc.of_allocateamt,of_recordcharge). - BIL-21 Adjustment processing recalculates at most
adj_periodsprior periods, stepping by the definition frequency, never beforeadj_beg_dt, never more than 5 years back, further limited by adjustment logic BILLYEAR (billing-year start), CALYEAR (Jan 1), or BILLCAL (billfunc/n_billproc.of_adjust_bill). - BIL-22 The historical baseline for a period is the net sum of all prior-run
charges per member/group/category/rule/allocation entity, excluding combinations
whose charges already net to zero at a prior ADJ-DROP
(
billfunc/n_billproc_sql.of_inserthistoricalcharges). - BIL-23 Adjustment charge types and signs: ADJ-ADD (+), ADJ-DROP (−),
ADJ-RATE (−historical, +recalculated), ADJ-CHANGE (−historical, +recalculated);
ADJ-CHANGE is triggered by differences in election level, election benefit amount, or
rate item (
billfunc/n_billproc.of_recordadjustment,billfunc/d_billproc_hist_adj_change). - BIL-24 For ADJ-CHANGE the reversing negative entry must be committed before the
positive entry with a lower charge key, because election info is taken from the
greatest historical charge key (comment in
billfunc/n_billproc.of_adjustbill). - BIL-25 Submitting runs assigns a new run key to all newly selected definitions
with the entered bill date (also used as due date) and status SUBMITTED, or DELAYED
when delay processing is chosen (
billing/w_bill_run_def_maintpfc_preupdate, pfc_addrow). - BIL-26 Only a definition's latest run appears in the run-maintenance lists, and
only definitions active (not inactive) as of the bill date are offered
(
billing/d_bill_run_def_maint). - BIL-27 A definition row cannot be selected for submission when its latest bill
date is later than the entered bill date (
billing/w_bill_run_def_maintdw_bill_def_list clicked/doubleclicked). - BIL-28 Removing a previously submitted run deletes its charges and recalc rows,
deletes its bill reports/report objects/report data, deletes its receipts (after user
confirmation when receipts exist), and re-points receipts and manual adjustments that
appeared on it back to the unassigned run (−100); re-submission instead deletes
charges and voids reports (
billing/w_bill_run_def_maintpfc_save, pfc_deleterow). - BIL-29 Every billing definition owns a permanent "unassigned" run
(
bill_run_sys_key = -100, status FAKE) used to hold receipts and manual adjustments before they appear on a real bill (billing/w_bill_bill_def_maintpfc_preupdate). - BIL-30 Each definition rule must have a billing category and a from date before
the definition can be saved; a definition must have a definition code
(
billing/w_bill_bill_def_maintpfc_preupdate). - BIL-31 Receipts can be created only against billed runs dated on/after the
definition's billing begin date; earlier bills are historical only
(
billing/w_bill_receipt_maintdw_receipt_view ue_enter). - BIL-32 Receipts and manual adjustment charges can be edited or deleted only while
unassigned (appearance/manual-adjustment run = −100), i.e. before they appear on a
bill (
billing/w_bill_receipt_maint,billing/w_bill_adj_maintpfc_deleterow). - BIL-33 Only NEW charges may be manually dropped; dropping creates reversing
ADJ-DROP rows (negated charge and election benefit amounts) attached to the
unassigned run, with a warning when the selection appears already dropped
(
billing/w_bill_adj_maint.wf_dropcharges). - BIL-34 Statement current-due = previous balance − payments + client adjustments +
current billed, where previous balance counts only runs on/after the definition's
billing begin date (
billfunc/n_bill_functions.of_mailingpage,billfunc/d_billprt_def_balance_info). - BIL-35 With definition option
BILL_EACH = GROUP, a separate bill is produced per eligible group (groups without charges are skipped with a warning); otherwise a single combined bill is produced (billfunc/n_bill_functions.of_report). - BIL-36 Bill content options come from
bill_def_option_vals: ALLOW_ADJ (client self-adjustment page), DISB_SUMM (vendor disbursement summary), AGERATEDTL (age-rate premium detail), CATSBYGRP (category summaries by group), MISC_COL1/MISC_COL2 (member-detail miscellaneous columns, defaults EFF_DT and HCN), PAYABLE_TO, BILL_EACH, MAXENROLL (billfunc/n_bill_functions.of_create_bill2,of_mailingpage,billfunc/n_billproc.of_memberruleapplies). - BIL-37 Bill processing errors are aggregated (duplicates counted, not repeated)
into an error report saved with the run under category BILLERR (calculation) or
BILLPRTERR (print); fatal errors stop the run
(
billfunc/n_billproc.of_reporterror,of_bill,billfunc/n_bill_functions.of_savebill). - BIL-38 Printing a voided report requires explicit confirmation; printed reports
are marked with status PRINT, and the queue can filter to unprinted (no status) and
non-void reports (
billing/w_bill_queuepfc_print, ue_filter). - BIL-39 The definition group list is stored at any level of the group hierarchy but
billing always expands it to leaf-level groups (those with no child groups) for
charge processing (
billfunc/d_bill_groups). - BIL-40 A definition rule may be restricted to specific groups of the definition
via a MEMBER_ELIGIBILITIES.GROUP_ID rule parameter; when present, group- and
member-level processing skips groups not in that list
(
billfunc/d_bill_bill_def_rules,billfunc/n_billproc.of_calculate_bill).
Requirements below are sourced from the original user guide (requirements-era statements, not observed code behavior); code citations note where the implementation matches or diverges.
- BIL-41 A bill may be rerun/rebilled until a payment is applied to it or a later period is billed, and rebilling a prior period invalidates all subsequent bills; a bill may be deleted only when no payments have been booked against it (User Guide ch. 4). The implemented run screen enforces the later-bill sequence at submission (BIL-2, BIL-27) but permits removing a run that has receipts after user confirmation, deleting those receipts (BIL-28). The user guide (ch. 4) states bills with booked payments must not be deleted — discrepancy for SME confirmation.
- BIL-42 Rebilling a period deletes the summary (lump-sum) manual adjustments defined for that bill along with the rest of the bill's data (accounting must re-enter them); individual member adjustments are maintained through the rebill (User Guide ch. 4). The implemented removal path instead detaches receipts and manual adjustments that appeared on the run back to the unassigned run (−100), preserving them (BIL-28) — SME to confirm which behavior the target system needs.
- BIL-43 Commission amounts may be built into billed amounts (withheld) but are
owed and disbursed to brokers separately by the Commissions function, outside the
check run; a broker is paid only after the client's bill has been paid (User Guide
ch. 4, ch. 8). No commission processing exists in
billing/billfunc. - BIL-44 COBRA elections (per
ELECTIONS.COBRA_FLG) bill through the same rule/rate machinery with COBRA-specific rates at the coverage/option level; the COBRA claim-funding component is stored as a separate billing component and allocated to claims reserve rather than to the standard payees (User Guide ch. 4). The rule editor supports the COBRA-flag criterion (billing/d_bill_election_cobra_flg); a claims-reserve allocation would be modeled as a vendor allocation entity — SME to confirm the production configuration. - BIL-45 Retroactive enrollment adjustments must not be applied further back than
the group's last renewal (plan-year start) — a carrier rule — with a termination
exception reaching plan-year start plus one month, allowed only until the second
plan-year month is billed (User Guide ch. 4). This is the business intent behind
bill_defs.adj_logic_cd(BILLYEAR); the active code path does not enforce it (see resolved open question 4). - BIL-46 Billing Destination distinguishes billing the group (default) from billing the individual subscriber (COBRA coupons, Medicare), including per-subscriber rate overrides for Medicare and running bills for a single person (User Guide ch. 4, ch. 8). Not evidenced in the extracted billing libraries — the implemented module produces group bills only; individual billing would be new scope in a migration.
- BIL-48 Bill Format, Billing Destination, Billing Level and Sort Order are bill-output attributes settable as group defaults and overridable per detail bill, so one group can take Statement Billing on one set of coverages and Regular billing on another; the format catalog is Regular, Statement Billing, Pridecare and COBRA Coupons, with a "dynamic" format (columns suppressed for coverages the group does not carry) requested but never built (User Guide ch. 4). The extracted code has no format selector — see "Bill formats specified in the guide" above.
- BIL-49 A binder check — an estimated premium paid to a carrier before any bill
exists, used when a group is new to a carrier — represents payment of unbilled
premium, so the first bill generated must be reduced by the binder amount. The guide
notes the first few months may deliberately be billed at once so the unbilled balance
does not persist (User Guide ch. 4). No binder-check concept exists in
billing/billfunc; in Funding it surfaces only as an unallocated amount in reconciliation. - BIL-50 Billing Frequency Start Date specifies which combination of day, month/day or month/day/year is used with Billing Frequency to derive billing dates, and the guide asks for a billing calendar that can be produced for a client or member (User Guide ch. 4). The implementation instead stores a billing-year begin month plus per-rule begin months and evaluates frequency against the bill month (BIL-3); no calendar output exists.
- BIL-51 Two receivables reports are required and absent from the extracted code:
clients billed whose payment has not been received, and unbilled clients (User Guide
ch. 4). The nearest implemented artifacts are the statement balance-forward section
(BIL-34) and the billing exception report (
billing/n_verify_bill), neither of which answers either question across clients. - BIL-47 Charges are generated for coverages existing as of the first day of the billing month with no proration; the final specification fixes group billing frequency at one month (User Guide ch. 4). The implemented engine matches the no-proration/as-of-date rule (BIL-13) but supports ANNUAL, QUARTERLY, MONTHLY and ONCE rule frequencies (BIL-3, BIL-4) — the code's richer frequency support wins; the guide's earlier analysis also lists Biweekly/Semi-Monthly/9thly/10thly/13thly/ 24thly frequencies that were never implemented.
Rate/charge computation summary
| Level | Population | Units | Rate item | Rate |
|---|---|---|---|---|
| BILL | one charge per rule | 1 | first rate amount | single table amount (billfunc/n_billproc.of_billlevelrule) |
| GROUP | leaf groups passing rule parameters | 1 | FLAT only | flat table amount (billfunc/n_billproc.of_grouplevelrule) |
| MEMBER | eligible members passing rule parameters | 1, or election benefit amount when rate unit ≠ 1 | FLAT or assembled from rate variables (age, gender, coverage level, member category, ranges) | matched bill_rate_amts.rate_amt (billfunc/n_billproc.of_memberlevelrule) |
Elections drive member premiums three ways: the election's coverage type selects the
rule (parameters, BILLCOVR priority), its coverage level / benefit amount feed the rate
item and units, and the election's values are stored on the charge
(election_level, election_benefit_amt) so later recalcs can detect changes
(billfunc/n_billproc.of_memberlevelrule, of_recordcharge,
billfunc/d_billproc_hist_adj_change).
Vendor allocation links billing to funding/disbursement: every charge row carries an
alloc_entity_sys_key resolved from bill_rate_allocs, and the disbursement summary
reports charge totals by vendor (billfunc/n_billproc.of_allocateamt,
billing/d_billprt_disbursement_division_summary,
billfunc/d_billprt_rpt2_disbursement_summary).
Data
Core tables (see schema.json CRUD: billing and billfunc both read/write most of
these):
| Table | Role | Key columns (from schema.json) |
|---|---|---|
bill_defs | Billing definition | bill_def_cd, description, frequency, beg_month, adj_periods, adj_beg_dt, adj_beg_month, adj_logic_cd, bill_beg_dt, inactive_dt, bill_rpt_fmt_sys_key, alloc_rpt_fmt_sys_key |
bill_def_groups | Groups per definition | bill_def_sys_key, group_id |
bill_def_rules | Rules per definition (effective-dated) | bill_def_sys_key, bill_rule_sys_key, from_dt, thru_dt, bill_category, frequency, beg_month, rate_period_cd |
bill_def_options / bill_def_option_vals | Definition options (effective-dated typed values) | bill_option_cd, option_val_cd/char/dt/no, eff_dt, exp_dt |
bill_rules | Rule master | bill_rule_cd, bill_level, bill_rate_type_sys_key, default_bill_category, default_frequency |
bill_rule_parms / bill_rule_parm_details | Rule applicability criteria | parm_source, parm_connector, seq; parm_variable, parm_operator, parm_value, value_type_cd |
bill_rate_types / bill_rate_vars | Rate type and its variables | bill_level; seq, rate_source, rate_variable, rate_var_sql, code_key, bill_range_sys_key |
bill_ranges / bill_range_items | Banded lookup ranges | bill_range_cd, value_type_cd; heading, number_from/thru, char_from/thru |
bill_rate_tables | Effective-dated rate tables per rule | from_dt, thru_dt, rate_unit, rate_rounding_cd, unit_rounding_cd, alloc_method |
bill_rate_amts | Rate per rate item | rate_item, rate_amt |
bill_rate_allocs | Vendor split per rate amount | alloc_entity_sys_key, allocate_amt |
bill_runs / bill_run_defs | Run header / per-definition status | bill_dt, due_dt, status_cd; status_cd (SUBMITTED, DELAYED, COMPLETE, FAKE), total_billed_amt |
bill_charges | Charge detail (one row per allocation entity) | bill_run/def/rule/rate_amt keys, bill_period_dt, bill_level, bill_category, charge_type (NEW, ADJ-ADD, ADJ-DROP, ADJ-RATE, ADJ-CHANGE), charge_amt, rate_item, rate_amt, bill_units, election_level, election_benefit_amt, alloc_entity_sys_key, member_sys_key, group_id, manual_adj_run_sys_key, bill_receipt_sys_key |
bill_charges_temp, bill_recalc_charges, bill_recalc_charges_temp | Batch staging for charges and recalc comparison | mirror bill_charges plus charge_type HISTORICAL/RECALC, vendor_category |
bill_receipts | Payments received | bill_run_sys_key, bill_def_sys_key, receipt_amt, receipt_dt, check_number, group_id, member_sys_key, appearance_run_sys_key |
bill_reports | Link from run to stored report | report_data_sys_key, status_cd (PRINT, VOID) |
External reads: groups, group_periods, group_coverages, group_option_vals,
member_eligibilities, members, elections, member_cat_val, plan_pkgs, codes,
vendors/alloc_entities, report_data/report_objects
(billfunc/n_billproc.of_ruleparmsql, billfunc/d_bill_rule_members,
billing/dddw_bill_vendors, billing/d_bill_report_data).
Database sequences: BILL_RUN_SEQ, BILL_CHARGE_SEQ, BILL_RECEIPT_SEQ, plus a
generic per-table sequence helper (billfunc/n_bill_functions.of_getbillsequence).
Oracle package pkg_bills provides: move_temp_to_recalc, move_temp_to_charges,
truncate_temp_charges, truncate_temp_recalc, delete_recalc_charges,
build_temp_recalc_ix, delete_bill_charges (billfunc/n_billproc_sql).
Invoice/statement outputs
Composite group bill assembled per run/definition (billfunc/n_bill_functions.of_create_bill2):
| Section | DataWindow | Notes |
|---|---|---|
| Mailing/remit page with balance summary | billfunc/d_billprt_rpt2_mailing_page | previous balance, payments, client adjustments, member/group/other charges and adjustments, current billed/due; remit address from group contacts or letter header, PAYABLE_TO option |
| Member detail | billfunc/d_billprt_rpt2_member_detail | per-group; misc columns per MISC_COL1/2 options |
| Age rate detail | billfunc/d_billprt_rpt2_age_rate_detail | option AGERATEDTL |
| Member adjustment detail | billfunc/d_billprt_rpt2_member_adj_detail | |
| Member bulk adjustments | billfunc/d_billprt_rpt2_member_bulk_adj | |
| Group charges | billfunc/d_billprt_rpt2_group_charges | |
| Category summary per group | billfunc/d_billprt_rpt2_category_summary | option CATSBYGRP |
| Bill-level charges | billfunc/d_billprt_rpt2_bill_charges | |
| Group summary | billfunc/d_billprt_rpt2_bill_group_summary | when billing combined |
| Overall category summary | billfunc/d_billprt_rpt2_category_group_summary | |
| Vendor disbursement summary | billfunc/d_billprt_rpt2_disbursement_summary | option DISB_SUMM |
| Final summary | billfunc/d_billprt_final_summary | |
| Client self-adjustment page | billfunc/d_billprt_client_adj_page | option ALLOW_ADJ |
Bill formats specified in the guide (not implemented as such)
The guide treats Bill Format as a first-class setup field — one of four "Bill Output"
attributes (Billing Destination, Billing Level, Sort Order, Bill Format) that may be set
as group-level defaults and overridden per detail bill, so that one group can take
statement billing on one set of coverages and regular billing on another (User Guide
ch. 4). The extracted code has no format selector: bill_defs carries report-format
keys and the option BILL_EACH, and every definition renders the one composite layout
catalogued above.
| Format | What the guide requires | Status in code |
|---|---|---|
| Regular | The standard itemized group bill | The implemented layout |
| Statement Billing | Only a summary by billing component, summarized by payee; used by groups that write their own checks to third parties | Not implemented; the money side appears as "Received and Forwarded" entries (see Funding) |
| Pridecare | Groups 509-xxx, billed entirely outside HOMER on integrated spreadsheets; tiered rates for larger groups, a single untiered rate under 10 covered lives; three companion Monthly Disbursement reports plus Monthly Premium and Monthly Volumes | Not implemented — the guide's minimum scope was only to capture the data needed to bill Pridecare |
| COBRA Coupons | Individual bills to COBRA beneficiaries (see §8 above) | Not implemented |
| Dynamic (requested) | Suppress columns a group does not carry — e.g. no vision column when the group has no vision coverage | Requested by both Pridecare and general users; never built |
OME-RESA bills similarly allocate billed amounts across payees through hard-coded break-downs and spreadsheets; the guide expected the component/allocation model already defined (vendor allocations, BIL-20) to absorb it (User Guide ch. 4).
Stored reports are browsed and printed from the Bill Report Queue
(billing/w_bill_queue, list billing/d_bill_bill_reports_list). Standalone reports:
disbursement summary by division (billing/w_billprt_disbursement_division_summary),
flex charge billing for Depcare/Medreim elections (billing/w_billrpt_flex_charges),
billing exception and definition-rate reports (billing/w_bill_reports,
billing/n_verify_bill).
Migration notes
- The rule-parameter and rate-variable engines store fragments of Oracle SQL in data
(
bill_rule_parm_details.parm_value,bill_rate_vars.rate_var_sql) and build/execute dynamic SQL with string substitution at run time (billfunc/n_billproc.of_ruleparmsql,of_assemblerateitem). A migration should replace this with a declarative criteria/rating model; the value space actually used must be inventoried from production data. - Charge rows are denormalized per allocation entity; totals per charge exist only as sums. Preserve this grain or introduce a charge header/allocation split.
- Sentinel values are load-bearing: run −100 = "unassigned" bucket for receipts and
manual adjustments (
billing/w_bill_bill_def_maint,billing/w_bill_adj_maint);1900-01-01treated as null date;12/31/2499/2500as open-ended dates. - Oracle-specific machinery: CONNECT BY group-hierarchy expansion
(
billfunc/d_bill_groups),pkg_billsstored procedures, dedicated rollback segmentBIGRBS(billfunc/n_billproc_sql.of_setrollback), sequences, RULE optimizer hints. - The adjustment comparison depends on exact equality of composite keys including
rate_itemstrings (up to 2000 chars); formatting changes to rate items would generate spurious ADJ-CHANGE churn (billfunc/d_billproc_hist_adj_change). - Reports are persisted as serialized PowerBuilder report objects in report tables
(
n_report_data); a migration needs a replacement rendering/archive strategy for historical bills. - The commented-out in-memory adjustment algorithm (
of_adjustbill) was superseded by the temp-table version (of_adjust_bill); only the latter is called (billfunc/n_billproc.of_bill).
Open questions
- Batch executor (still open): the process that picks up SUBMITTED/DELAYED runs
and calls
n_billproc.of_bill/n_bill_functions.of_report(referred to as "SFPIBACK" inbilling/w_bill_run_def_maintcomments) is not in the extracted libraries and the user guide does not describe it either. The guide does confirm the trigger model: bills are produced only on accounting's request — "No bills will be produced automatically by the system"; accounting selects the definitions/groups to run each cycle (User Guide ch. 4) — matching the submit-then-batch-pickup design. Where SFPIBACK runs, how DELAYED differs from SUBMITTED, and what setsbill_run_defs.status_cdto COMPLETE remain unresolved. - Attachment of receipts/adjustments to a run (still open): receipts and manual
adjustments are created against run −100 and the balance queries read them by
appearance_run_sys_key/manual_adj_run_sys_keyequal to the current run (billfunc/d_billprt_def_balance_info), and run deletion resets them to −100 (billing/w_bill_run_def_maint), but the code that flips −100 to the live run at bill time was not found in these libraries (possiblypkg_billsor the batch app). The user guide confirms the design intent — unassigned adjustments/receipts appear on the next bill and are then reconciled against it (User Guide ch. 4) — but not the mechanism. bill_rule_def_delete_responsesemantics (partially resolved): the business meaning of Re-submit vs Remove follows the guide's rebill rules — "A bill may be rerun and restored until the next bill is run", and bills may be deleted only until payments are booked (User Guide ch. 4); Re-submit is a rebill of the same run (charges deleted, reports voided) for correction before payment. Whether preserving DELAYED status on re-submit (billing/w_bill_run_def_maintpfc_deleterow) is deliberate remains open. See BIL-41/BIL-42 for guide-vs-code differences on payments and manual adjustments.- Adjustment logic default (intent resolved, code discrepancy remains):
ls_adj_logicis initialized to null and never read frombill_defs.adj_logic_cdin the active code path, so the BILLYEAR/CALYEAR/BILLCAL limits appear dormant (billfunc/n_billproc.of_adjust_billsetsSetNull(ls_adj_logic)after reading definition fields). The guide supplies the intended semantics: retroactive adjustments are limited (carrier rule) to the group's plan year / last renewal date, with a termination exception of one extra month allowed only until the second plan-year month is billed (User Guide ch. 4) — i.e. BILLYEAR = billing/plan-year floor, CALYEAR = calendar-year floor, BILLCAL = the combination. The user guide (ch. 4) states the plan-year limit should be enforced; the active code path nulls it out — discrepancy for SME confirmation (was it disabled deliberately, relying onadj_periods/adj_beg_dtonly?). See BIL-45. - Group-level rate variables (resolved): only flat rates are required at group
level. The guide specifies group-level premiums as "Fixed fees payable over a
specified number of months, always starting with the first month of the plan year"
(User Guide ch. 4) — flat amounts, consistent with the implementation's
FLAT-only restriction (
billfunc/n_billproc.of_grouplevelrule, BIL-8). Variable rating at group level is not a requirement. bill_chargesadd/change/adj columns (add_amt,add_count,change_amt,adj_count,bill_amt,bill_count,bill_volumeetc. inschema.json) are not populated by the examined code paths — confirm whether any external process uses them.cm_billingis a case-management billing table used byum_cm/um_urlibraries, not by this domain (schema.json); confirm it is out of scope for premium billing migration.- Flex billing report (resolved direction): the guide's wishlist is explicit —
"Currently Homer does nothing to differentiate flex and non-flex. Flex billing is
done as a completely separate bill. This should be done off same billing program",
and "Consolidate Flex and other bills, rates, and accounting records" (User Guide
ch. 4). The target system should fold flex billing into the normal billing
definition machinery, replacing the hard-coded 'Depcare'/'Medreim' report with its
manually entered rate (
billing/d_billrpt_flex_charges,billing/w_billrpt_flex_charges).