What a preset is
A scoring preset is a complete, named snapshot of every scoring parameter: base points, multipliers, review state weights, daily quotas, diminishing-return thresholds, and zero-point condition toggles. Each GitHub App installation has at least one preset. Exactly one is active at any time.
When you switch the active preset, all 6 time periods are already pre-computed for all active presets, so the switch is instant — no re-scoring required.
Default preset guarantee
ensureDefaultPreset(installationId) in lib/supabase/leaderboard-db.ts is called on every score request. It creates a preset named 'Default' with is_active = true if no preset exists yet for the installation. This means the system is never in a state where scoring has no configuration.
One active preset per installation
Enforced by a partial unique index on scoring_presets:
CREATE UNIQUE INDEX ON scoring_presets (installation_id)
WHERE is_active = true;Setting a different preset active atomically clears is_active on the current one. This is handled by setActiveScoringRulesPreset().
Deletion constraints (application-layer enforcement)
- The last remaining preset cannot be deleted.
- The currently active preset cannot be deleted.
These constraints are enforced in application code before the DB call, not by a database trigger.
Preset storage model
A single preset is spread across 8 tables:
| Table | Stores | Cardinality |
|---|---|---|
scoring_presets | Preset name, is_active, installation_id | 1 row per preset |
scoring_preset_rules | weekly_threshold, decay_factor, floor_fraction, spam_penalty, pr_closed_no_merge_penalty | 1 row per preset |
scoring_preset_base_points | Points per signal_type | Up to 10 rows (one per signal type) |
scoring_preset_multipliers | kind, factor, sort_order | Up to 3 rows (one per multiplier_kind) |
scoring_preset_multiplier_signal_types | Which signal types a multiplier applies to | M:N join of multipliers × signal_types |
scoring_preset_review_state_weights | state, weight, sort_order | Up to 3 rows (one per review_state) |
scoring_preset_daily_quotas | Per-signal-type quota_limit | Rows only for signal types with quotas |
scoring_preset_zero_point_conditions | Per-kind enabled boolean | Up to 5 rows (one per zero_point_kind) |
CRUD functions (lib/supabase/leaderboard-db.ts)
| Function | Description |
|---|---|
getScoringRules(presetId) | Load a full preset (all 8 tables) as a typed object |
listScoringRulesPresets(installationId) | List all presets for an installation |
getActiveScoringRulesPreset(installationId) | Return the single active preset |
ensureDefaultPreset(installationId) | Create default preset if none exists |
createScoringRulesPreset(installationId, data) | Create a new preset (all 8 tables in one transaction) |
updateScoringRulesPreset(presetId, data) | Update an existing preset |
setActiveScoringRulesPreset(installationId, presetId) | Swap active preset atomically |
deleteScoringRulesPreset(presetId) | Delete (subject to deletion constraints) |
Switching presets
Because writePresetComputedScores() writes results for all 6 time periods whenever scoring runs, and scoring runs for all active presets, switching to a different preset does not trigger a re-score. The new active preset already has current computed scores. The API response switches immediately on the next request.
If a preset is newly created and has never been scored, the first request after activation triggers a full score run for all 6 periods.
Preset enums (from migration 007)
These enums are defined in 007_scoring_presets.sql, not in 001_enums_and_utilities.sql:
| Enum | Values |
|---|---|
review_state | approved, changes_requested, commented |
multiplier_kind | merged_pr_commit, pr_linked_to_issue, first_activity |
zero_point_kind | self_review, self_merge, bot_activity, issue_closed_no_pr, pr_closed_no_merge |