Credit-style scoring engine. Loads variable/attribute
definitions from def_scorecard, reads a primary
context record, evaluates each variable against the
bureau/simulation data, and returns an integer score.
This module was extracted from
factory.service/package.core/ObjServiceScorecard.py
so that the scoring engine is available directly in
factory.core. The service
(ObjServiceScorecard.ObjServiceApi) remains as a
thin subclass providing the service-framework
Connect/Send interface.
Objects.Object
-> ObjData.ObjData
-> ObjApi.ObjApi
-> ObjScorecard.ObjScorecard <- this
-> ObjServiceScorecard.ObjServiceApi
compute_score_run(scorecard, guid, external_vars)
1. Load scorecard rules from def_scorecard
(cached, pre-parsed, grouped by variable)
2. Load context table from def_scorecard_context
(cached)
3. Load bureau variables from context table
(cached per guid)
4. Merge external_vars on top
5. For each variable:
- Convert bureau value to float once
- Iterate rules in rank order
- First match wins (break)
- Add score_contribution to return_score
6. Write tracking rows (batched SQL)
7. Return integer score
compute_score_run(scorecard, context, context_table, inbound_guid, ...)Main scoring entry point. Returns integer score.
compute_score(param1, param2, param3, param4)Thin wrapper called by service invocations.
compute_score_set(scorecard, list, ...)Batch-scores a list of GUIDs.
_ensure_scorecard_table(table_name)Creates data_scorecard_{name} on first use. Guarded
by _SCORECARD_TABLES set — once per table per process.
validate_scorecard(scorecard, package, version)Validates completeness: existence, fallback coverage,
NULL scores, duplicate AttributeIDs, BETWEEN bounds.
activate_version(scorecard, version, package)Deactivates all versions, activates specified one,
clears cache.
create_scorecard_version(scorecard, new_version, ...)Copies existing version to new version.
compare_scorecard_versions(scorecard, v1, v2, package)Diff of added/removed/modified variables.
| Type | Condition |
|---|---|
ASSIGN / = |
Always matches |
EQUAL / == |
Exact match (alpha or numeric) |
IN |
Value in pre-parsed set (O(1) lookup) |
LOWERTHAN / < |
Bureau value < attribute |
LOWERANDEQUAL / <= |
Bureau value <= attribute |
GREATERTHAN / > |
Bureau value > attribute |
GREATERANDEQUAL / >= |
Bureau value >= attribute |
BETWEEN / BETWEENUBE |
lower < value <= higher |
BETWEENLBE |
lower <= value < higher |
BETWEENNE |
lower < value < higher |
ELSE |
Fallback if no other rule matched |
| Table | Purpose |
|---|---|
def_scorecard |
Variable/attribute rules with scores |
def_scorecard_context |
Maps scorecard -> primary data table |
data_scorecard_{name} |
Per-scorecard tracking (auto-created) |
data_scorecard |
Combined tracking for all scorecards |
data_scorecard_values |
Per-variable tracking (when DO_TRACKING) |
| Variable | Key | Purpose |
|---|---|---|
BUFFER_DEF_SCORECARDS |
"{pkg}:{scorecard}" |
Grouped rules dict (variable -> rule list) |
BUFFER_DEF_CONTEXT |
"{pkg}:{data_scorecard}" |
Context table name + guid column |
BUFFER_BUREAU_VARIABLES |
"{table}:{guid}" |
Raw bureau row per GUID |
_CONTEXT_INDEXES_ENSURED |
bool | One-time index creation guard |
_TABLES_CREATED |
bool | One-time create_tables_from_yaml guard |
_SCORECARD_TABLES |
set | Per-table _ensure_scorecard_table guard |
_VERSION_CHECKED |
set | Per-scorecard+package _ensure_versioned guard |
Tracking rows use deterministic GUIDs derived from
the inbound GUID:
| Row | GUID pattern |
|---|---|
| Per-variable | {inbound_guid}_{step_number} |
| FINAL row | {inbound_guid}_FINAL |
| Score row | {inbound_guid}_{scorecard} |
No UUID generation at scoring time.
| # | Fix | Before | After |
|---|---|---|---|
| 1 | create_tables_from_yaml once per process | Per init | Once |
| 2 | _ensure_scorecard_table once per table | Per score | Once |
| 3 | _ensure_versioned once per scorecard | Per score | Once |
| 4 | Pre-parse rules: int scores, float bounds, IN sets | Per rule | On cache load |
| 5 | IN matching via set lookup | Regex + loop | O(1) |
| 6 | BETWEEN uses pre-parsed floats | float() per rule | Direct comparison |
| 7 | Deterministic tracking GUIDs | get_uuid() per row | String concat |
| 8 | Removed get_deployment() after scoring | DB call per score | Eliminated |
| 9 | Rules grouped by variable | Flat loop + Executed dict | break on first match |
Baseline: 0.71s -> 0.24s (3x faster), 62 production
test cases, zero score drift.
# Run baseline against local scrubbed snapshot
pytest resource.test/pytests/factory.core/test_ObjScorecard_baseline.py -v
# Baseline file path (not committed)
export SCORECARD_BASELINE_FILE=resource.test/local/scorecard_baseline.json
Each test case runs compute_score_run() with exact
bureau variables from the local baseline and asserts
the score matches. Any engine change that shifts a
score will fail.
Updated: 2026-04-13