Score Computation framework
Score Computation — Feature Design Document
Project: OpenG2P Registry Gen2 Feature: Score Computation Status: Design / Pre-implementation Date: 2026-04-09
1. Overview
Score Computation is an asynchronous, extensible feature that automatically computes domain-specific scores (e.g. PMT Score, FSF Score) for records in a registry, whenever a change request that touches one or more contributing attributes is approved.
The design follows the same metadata → trigger → queue → beat/worker → interface/factory → results pattern used by deduplication and functional ID generation in the existing codebase. Core provides all infrastructure; domain extensions provide only the computation logic.
2. Scope and Constraints
Score computation is available only for registers with
register_purpose = RegisterPurposeEnum.REGISTER. Registers with purposePROGRAM_REGISTERorTABLEare excluded.Every insert or update to a domain register table is always mediated through a Change Request — there are no direct writes. Score computation therefore triggers on Change Request approval, not on CR creation.
The feature is fully asynchronous: the approval API returns immediately; the score is computed in the background by a Celery worker.
The base registry (
registry-platform/core/openg2p-registry-coreandregistry-platform/celery) provides all infrastructure. The actual computation formula lives entirely in the registry's own extension package.
3. Design Across Repositories
registry-platform/core/openg2p-registry-core
New models, interface, factory, controller service
registry-platform/celery
New beat producer, worker, Workers constant, config keys
registry-platform/apis
New API endpoint on staff portal
registry-platform/ui/ui-widgets
New scores-display widget
The registry's extension package
Concrete compute service implementations (per score type)
4. Data Model
4.1 G2PRegisterScoreDefinition
Stores the score types configured for a register, and which attributes contribute to each score. One register may have multiple score definitions (one per score type).
Location: registry-platform/core/openg2p-registry-core/src/openg2p_registry_core/models/g2p_register_score_definition.py
Constraint enforced at service layer: register_purpose must be RegisterPurposeEnum.REGISTER when creating or updating a score definition.
Unique constraint: (register_id, score_type) — a register may only have one definition per score type.
4.2 G2PScoreComputeQueue
One queue item per (internal_record_id, score_type) per trigger event. Follows the same pattern as G2PFunctionalIdGenerationQueue.
Location: registry-platform/core/openg2p-registry-core/src/openg2p_registry_core/models/g2p_score_compute_queue.py
Upsert behaviour: When a trigger event fires for a (internal_record_id, score_type) pair that already has a PENDING queue item, the existing row is updated (change_request_id and contributing_attribute_values are refreshed) rather than inserting a duplicate. This ensures the worker always computes on the most recent approved state.
4.3 G2PRegisterScore
Stores the latest computed score per record per score type. One row per (internal_record_id, score_type), upserted on every computation.
Location: registry-platform/core/openg2p-registry-core/src/openg2p_registry_core/models/g2p_register_score.py
Upsert key: (internal_record_id, score_type). The score_id remains stable across recomputations; only computed_score, computed_at, and triggered_by_cr_id are updated.
4.4 G2PRegisterScoreHistory (recommended)
Appends an immutable snapshot on every computation. Consistent with the pattern used by G2PRegisterHistory in the existing codebase.
This table is append-only. It enables answering "how has this record's PMT score changed over time and which CR caused each change?"
5. Trigger: Change Request Approval
The queue is populated inside G2PRegisterService.approve_change_request(), after the domain record has been updated and the history snapshot written. This is the only correct trigger point — the contributing attribute values in the domain table are not updated until approval.
Trigger Logic
_enqueue_score_computations logic
6. Interface and Factory
6.1 G2PScoreComputeInterface
Location: registry-platform/core/openg2p-registry-core/.../interfaces/g2p_score_compute_interface.py
6.2 G2PScoreComputeFactory
Location: registry-platform/core/openg2p-registry-core/.../interfaces/g2p_score_compute_factory.py
Mirrors G2PPayloadEnricherFactory exactly. Uses importlib to load the implementation from the extensions namespace at runtime.
7. Celery Beat Producer and Worker
7.1 Beat Producer
Location: registry-platform/celery/.../tasks/score_compute_beat_producer.py
Follows the exact pattern of deduplication_register_beat_producer.py.
7.2 Worker
Location: registry-platform/celery/.../tasks/score_compute_worker.py
The worker is stateless — it reads everything it needs from the queue item (including the attribute snapshot and the change_request_id). The worker delegates to the factory; it never contains formula logic.
7.3 Workers constants update
Add to registry-platform/celery/.../utils/workers.py:
7.4 Beat schedule and config
Add to openg2p-registry-celery-beat-producers/src/.../app.py beat schedule:
Add to config.py:
8. Core Service
Location: registry-platform/core/openg2p-registry-core/.../services/g2p_score_compute_service.py
Provides the _enqueue_score_computations method (called from G2PRegisterService.approve_change_request) and manages the score definition CRUD. This is a BaseService singleton.
9. Controller Service and API Endpoint
9.1 Controller Service
Location: registry-platform/core/openg2p-registry-core/.../controller_services/g2p_score_controller_service.py
9.2 Staff Portal API Controller
Location: registry-platform/apis/openg2p-registry-staff-portal-api/.../controllers/g2p_score_controller.py
10. Extension Implementation
Directory structure in the registry's extension package
Example PMT Score implementation
Namespace packaging (pyproject.toml)
This ensures the factory's importlib.import_module("openg2p_registry_extensions.score_compute.services") resolves correctly when the extension package is installed alongside core.
11. UI — Scores Display
11.1 New widget: scores-display
Location: registry-platform/ui/ui-widgets/src/widgets/ScoresDisplayWidget.tsx
A read-only widget that fetches and displays all computed scores for the current record.
Renders as a compact table:
PMT_SCORE
47.32
2026-04-08 14:22
CR-00123
FSF_SCORE
82.10
2026-04-08 14:22
CR-00123
Shows a spinner/pending badge when a PENDING queue item exists for the record (polled via a secondary data source or SSE).
Register the widget in src/registry/defaultWidgets.ts:
11.2 Core Section in UI Schema
Scores are surfaced as a core section — a section managed by the registry platform, not user-configurable via SectionBuilder. Distinguish it with a section-is-core: true flag in SectionConfig:
SectionBuilder should hide or lock sections with section-is-core: true to prevent accidental editing.
12. End-to-End Data Flow
13. Migration Checklist
All new tables require Alembic migrations. Add to CoreInitializer.migrate_database():
14. Summary of All New Artifacts
registry-platform/core/openg2p-registry-core
Model
models/g2p_register_score_definition.py
Score type config per register
Model
models/g2p_score_compute_queue.py
Async compute queue
Model
models/g2p_register_score.py
Latest score per record/type
Model
models/g2p_register_score_history.py
Immutable score audit trail
Interface
interfaces/g2p_score_compute_interface.py
Abstract compute_score()
Factory
interfaces/g2p_score_compute_factory.py
Dynamic loader by score_type
Service
services/g2p_score_compute_service.py
Enqueue logic, CRUD, results
Ctrl Svc
controller_services/g2p_score_controller_service.py
HTTP layer
registry-platform/celery
Beat
tasks/score_compute_beat_producer.py
Polls PENDING, dispatches
Worker
tasks/score_compute_worker.py
Computes, persists, retries
Constant
utils/workers.py
SCORE_COMPUTE_WORKER
Config
config.py
score_compute_beat_producer_frequency
registry-platform/apis
Controller
g2p_score_controller.py
5 new endpoints
registry-platform/ui/ui-widgets
Widget
widgets/ScoresDisplayWidget.tsx
scores-display widget
Types
types/index.ts
ScoresDisplayConfig type
Registry
registry/defaultWidgets.ts
Auto-register scores-display
The registry's extension package
Impl
score_compute/services/g2p_score_compute_service_pmt_score.py
PMT formula
Impl
score_compute/services/g2p_score_compute_service_fsf_score.py
FSF formula
Last updated
Was this helpful?