Skip to main content

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 package pkg_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_bill and n_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_maint pfc_save comment); its executable is outside the extracted libraries.

Key workflows​

1. Billing setup​

  1. Define billing rules (bill_rules): code, description, bill level (BILL / GROUP / MEMBER, code key BLNGLEVEL per billing/dddw_billing_level), optional rate type (rate variables), default category and frequency (billing/w_bill_bill_rule_maint).
  2. 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).
  3. Define rate tables per rule (bill_rate_tables, effective-dated from/thru) with rate amounts per rate item (bill_rate_amts), rounding codes (code keys BILLRATERN, BILLUNITRN per billing/dddw_bill_rate_rounding_cd, billing/dddw_bill_unit_rounding_cd) and vendor allocations (bill_rate_allocs, method PCT or AMT).
  4. 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).
  5. 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_runs row (key from BILL_RUN_SEQ) with bill_dt and due_dt, and one bill_run_defs row per selected definition with status SUBMITTED (or DELAYED when "Delay Processing" is checked) (billing/w_bill_run_def_maint pfc_preupdate, pfc_addrow).
  • Deleting a run removes its charges and recalc rows (pkg_bills.delete_bill_charges, pkg_bills.delete_recalc_charges via billfunc/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_maint pfc_save).
  • Re-submitting deletes the run's charges and voids its reports (status VOID), then sets the run-definition status back to SUBMITTED (billing/w_bill_run_def_maint pfc_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_eligibilities subscriber 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_charges row per allocation entity, key from BILL_CHARGE_SEQ, with charge type NEW, 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):

  1. units = round(units, unit_rounding_cd) — units are rounded before the division.
  2. charge = (units ÷ rate_unit) × rate_amt, where a null or zero rate_unit is coerced to 1.
  3. charge = round(charge, rate_rounding_cd).

Both rounding codes share one vocabulary, decoded by prefix:

CodeEffectExample
NEARnRound(x ÷ n) × n — nearest multiple of nNEAR.01 → nearest cent
DOWNnTruncate(x ÷ n) × n — toward zeroDOWN1 → whole dollars down
UPnCeiling(x ÷ n) × nUP1 → 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):

  1. Recalculate the period with current data into bill_recalc_charges_temp (charge type RECALC), zeroing member key at GROUP level and member/group at BILL level.
  2. 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 via ADJ-DROP (billfunc/n_billproc_sql.of_inserthistoricalcharges).
  3. 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-RATE reversal (−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-CHANGE reversal and re-bill (billfunc/d_billproc_hist_adj_change, d_billproc_recalc_adj_change).
  4. Recalc rows are persisted from temp to bill_recalc_charges via pkg_bills.move_temp_to_recalc; temp tables are truncated and indexes rebuilt via pkg_bills procedures (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_key equals this run.
  • Client adjustments = manual bill_charges whose manual_adj_run_sys_key equals 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 for PLANYR, 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 MAXENROLL option 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 BILLCOVR code 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_periods prior periods, stepping by the definition frequency, never before adj_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_maint pfc_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_maint dw_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_maint pfc_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_maint pfc_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_maint pfc_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_maint dw_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_maint pfc_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_queue pfc_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​

LevelPopulationUnitsRate itemRate
BILLone charge per rule1first rate amountsingle table amount (billfunc/n_billproc.of_billlevelrule)
GROUPleaf groups passing rule parameters1FLAT onlyflat table amount (billfunc/n_billproc.of_grouplevelrule)
MEMBEReligible members passing rule parameters1, or election benefit amount when rate unit ≠ 1FLAT 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):

TableRoleKey columns (from schema.json)
bill_defsBilling definitionbill_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_groupsGroups per definitionbill_def_sys_key, group_id
bill_def_rulesRules 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_valsDefinition options (effective-dated typed values)bill_option_cd, option_val_cd/char/dt/no, eff_dt, exp_dt
bill_rulesRule masterbill_rule_cd, bill_level, bill_rate_type_sys_key, default_bill_category, default_frequency
bill_rule_parms / bill_rule_parm_detailsRule applicability criteriaparm_source, parm_connector, seq; parm_variable, parm_operator, parm_value, value_type_cd
bill_rate_types / bill_rate_varsRate type and its variablesbill_level; seq, rate_source, rate_variable, rate_var_sql, code_key, bill_range_sys_key
bill_ranges / bill_range_itemsBanded lookup rangesbill_range_cd, value_type_cd; heading, number_from/thru, char_from/thru
bill_rate_tablesEffective-dated rate tables per rulefrom_dt, thru_dt, rate_unit, rate_rounding_cd, unit_rounding_cd, alloc_method
bill_rate_amtsRate per rate itemrate_item, rate_amt
bill_rate_allocsVendor split per rate amountalloc_entity_sys_key, allocate_amt
bill_runs / bill_run_defsRun header / per-definition statusbill_dt, due_dt, status_cd; status_cd (SUBMITTED, DELAYED, COMPLETE, FAKE), total_billed_amt
bill_chargesCharge 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_tempBatch staging for charges and recalc comparisonmirror bill_charges plus charge_type HISTORICAL/RECALC, vendor_category
bill_receiptsPayments receivedbill_run_sys_key, bill_def_sys_key, receipt_amt, receipt_dt, check_number, group_id, member_sys_key, appearance_run_sys_key
bill_reportsLink from run to stored reportreport_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):

SectionDataWindowNotes
Mailing/remit page with balance summarybillfunc/d_billprt_rpt2_mailing_pageprevious 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 detailbillfunc/d_billprt_rpt2_member_detailper-group; misc columns per MISC_COL1/2 options
Age rate detailbillfunc/d_billprt_rpt2_age_rate_detailoption AGERATEDTL
Member adjustment detailbillfunc/d_billprt_rpt2_member_adj_detail
Member bulk adjustmentsbillfunc/d_billprt_rpt2_member_bulk_adj
Group chargesbillfunc/d_billprt_rpt2_group_charges
Category summary per groupbillfunc/d_billprt_rpt2_category_summaryoption CATSBYGRP
Bill-level chargesbillfunc/d_billprt_rpt2_bill_charges
Group summarybillfunc/d_billprt_rpt2_bill_group_summarywhen billing combined
Overall category summarybillfunc/d_billprt_rpt2_category_group_summary
Vendor disbursement summarybillfunc/d_billprt_rpt2_disbursement_summaryoption DISB_SUMM
Final summarybillfunc/d_billprt_final_summary
Client self-adjustment pagebillfunc/d_billprt_client_adj_pageoption 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.

FormatWhat the guide requiresStatus in code
RegularThe standard itemized group billThe implemented layout
Statement BillingOnly a summary by billing component, summarized by payee; used by groups that write their own checks to third partiesNot implemented; the money side appears as "Received and Forwarded" entries (see Funding)
PridecareGroups 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 VolumesNot implemented — the guide's minimum scope was only to capture the data needed to bill Pridecare
COBRA CouponsIndividual 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 coverageRequested 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-01 treated as null date; 12/31/2499/2500 as open-ended dates.
  • Oracle-specific machinery: CONNECT BY group-hierarchy expansion (billfunc/d_bill_groups), pkg_bills stored procedures, dedicated rollback segment BIGRBS (billfunc/n_billproc_sql.of_setrollback), sequences, RULE optimizer hints.
  • The adjustment comparison depends on exact equality of composite keys including rate_item strings (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​

  1. 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" in billing/w_bill_run_def_maint comments) 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 sets bill_run_defs.status_cd to COMPLETE remain unresolved.
  2. 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_key equal 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 (possibly pkg_bills or 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.
  3. bill_rule_def_delete_response semantics (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_maint pfc_deleterow) is deliberate remains open. See BIL-41/BIL-42 for guide-vs-code differences on payments and manual adjustments.
  4. Adjustment logic default (intent resolved, code discrepancy remains): ls_adj_logic is initialized to null and never read from bill_defs.adj_logic_cd in the active code path, so the BILLYEAR/CALYEAR/BILLCAL limits appear dormant (billfunc/n_billproc.of_adjust_bill sets SetNull(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 on adj_periods/adj_beg_dt only?). See BIL-45.
  5. 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.
  6. bill_charges add/change/adj columns (add_amt, add_count, change_amt, adj_count, bill_amt, bill_count, bill_volume etc. in schema.json) are not populated by the examined code paths — confirm whether any external process uses them.
  7. cm_billing is a case-management billing table used by um_cm/um_ur libraries, not by this domain (schema.json); confirm it is out of scope for premium billing migration.
  8. 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).