PR Vision v0.2.0: admin/settings interface + correctness fixes
Requirements 1-8 + all non-negotiables:
1. Model manager (CRUD): PRV_Model_Registry, prv_models v2 option, versioned
idempotent migration from v0.1.x flat-string format, Settings page CRUD.
2. Per-model run-health: PRV_Model_Registry::update_health() after each run;
health badges (Healthy/Retired?/Disabled/Unknown) in model table + dashboard.
3. Metric comparability: PRV_Config_Version stamps every run with config_version;
save-bar warning on scoring-relevant changes; orphaned rows excluded by version.
4. Projected cost at save: PRV_Config::get_projected_cost(); over-cap warning;
prv_last_run_truncated option + visible notice when cap hit mid-run.
5. Config surface: peptide set, intents, cadence (reschedules cron), budget cap;
all validated + sanitized; next-run timestamp shown.
6. API-key three-state: not-defined / defined-ok / defined-failed; derived from
ledger; never displays the key.
7. Run-lock: PRV_Run_Lock (transient); cron tick skips if locked; Run-now
refuses with clear message; lock released in finally block.
8. Design fixes: dark-ink focus ring on bright fills; disabled-row full AA
contrast (no opacity); toggle disabled on retired rows; save-bar-level
warning (role=alert); chip text #B4BDC7 6.83:1 AA; keyboard-sortable headers
(<button>+aria-sort); aria-live on Test chip; z-index:50 save-bar;
run-now double-fire guard; @media print light overrides.
New: PRV_Upgrader (runs migrations on plugins_loaded, not just activation),
PRV_Config_Version, PRV_Run_Lock, PRV_Settings_Page, PRV_Settings_Renderer,
PRV_Model_Manager_Table, PRV_Model_Test_Ajax.
Tests: test-model-registry, test-run-lock, test-config-version,
test-projected-cost, test-cron-reschedule (+ updated probe-runner,
uninstall, bootstrap).
Schema: config_version INT column added via dbDelta (idempotent).
Uninstall: purges all v0.2 prv_* options + transients.
This commit is contained in:
@@ -12,18 +12,26 @@ declare(strict_types=1);
|
||||
*
|
||||
* All mutable settings live in wp_options under the prv_ prefix. This class
|
||||
* provides typed getters (no magic values in call sites) and seeds the initial
|
||||
* defaults on first activation.
|
||||
* defaults on first activation. Models are delegated to PRV_Model_Registry
|
||||
* which owns the v2 rich-object format; get_models() returns the flat slug
|
||||
* array for backward compat with PRV_Probe_Runner.
|
||||
*
|
||||
* Who triggers: PRV_Activator::activate() for seeding; every component that
|
||||
* needs a setting reads through here.
|
||||
* Dependencies: get_option(), update_option().
|
||||
* Dependencies: get_option(), update_option(), PRV_Model_Registry.
|
||||
*
|
||||
* @see CONTEXT.md — Domain glossary: peptides, prompt intents, models.
|
||||
* @see ARCHITECTURE.md — §Config (options + defaults).
|
||||
* @see CONTEXT.md -- Domain glossary: peptides, prompt intents, models.
|
||||
* @see ARCHITECTURE.md -- Section Config (options + defaults).
|
||||
* @see class-prv-model-registry.php -- Owns prv_models v2 format.
|
||||
* @package PrVision
|
||||
*/
|
||||
class PRV_Config {
|
||||
|
||||
/**
|
||||
* Cadence option key.
|
||||
*/
|
||||
const CADENCE_KEY = 'prv_cadence';
|
||||
|
||||
/**
|
||||
* Seed the default option values on first activation.
|
||||
*
|
||||
@@ -37,7 +45,9 @@ class PRV_Config {
|
||||
add_option( 'prv_monthly_budget_usd', PRV_DEFAULT_MONTHLY_BUDGET_USD );
|
||||
add_option( 'prv_peptides', self::default_peptides() );
|
||||
add_option( 'prv_prompt_intents', self::default_prompt_intents() );
|
||||
add_option( 'prv_models', self::default_models() );
|
||||
add_option( self::CADENCE_KEY, 'weekly' );
|
||||
// Note: prv_models seeded by PRV_Model_Registry::run_migration_v2() via
|
||||
// PRV_Upgrader::run() -- not here -- so the v2 schema is always in place.
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -75,13 +85,56 @@ class PRV_Config {
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the model identifiers to probe with.
|
||||
* Get the enabled model slugs (flat array, backward compat).
|
||||
*
|
||||
* Delegates to PRV_Model_Registry which owns the v2 model format.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public static function get_models(): array {
|
||||
$raw = get_option( 'prv_models', self::default_models() );
|
||||
return is_array( $raw ) ? $raw : self::default_models();
|
||||
return PRV_Model_Registry::get_enabled_slugs();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the probe cadence identifier.
|
||||
*
|
||||
* @return string WP-Cron recurrence name (e.g. 'weekly', 'daily').
|
||||
*/
|
||||
public static function get_cadence(): string {
|
||||
$cadence = (string) get_option( self::CADENCE_KEY, 'weekly' );
|
||||
return in_array( $cadence, array( 'weekly', 'daily', 'twicedaily' ), true ) ? $cadence : 'weekly';
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the projected per-run and per-month cost.
|
||||
*
|
||||
* Formula: enabled_models x peptides x intents x estimated_cost_per_probe x cadence_runs_per_month.
|
||||
* Uses conservative high-end estimates to never understate cost.
|
||||
*
|
||||
* @return array{per_run_usd: float, per_month_usd: float, probe_count: int, over_cap: bool}
|
||||
*/
|
||||
public static function get_projected_cost(): array {
|
||||
$models = PRV_Model_Registry::get_all();
|
||||
$enabled_count = count( array_filter( $models, static fn( $m ) => ! empty( $m['enabled'] ) ) );
|
||||
$peptide_count = count( self::get_peptides() );
|
||||
$intent_count = count( self::get_prompt_intents() );
|
||||
$probe_count = $enabled_count * $peptide_count * $intent_count;
|
||||
|
||||
// Conservative per-probe estimate (use the higher Perplexity estimate).
|
||||
$per_probe = 0.005;
|
||||
$per_run = round( $probe_count * $per_probe, 4 );
|
||||
|
||||
$cadence = self::get_cadence();
|
||||
$runs_per_month = 'weekly' === $cadence ? 4 : ( 'daily' === $cadence ? 30 : 8 );
|
||||
$per_month = round( $per_run * $runs_per_month, 4 );
|
||||
$cap = self::get_monthly_budget_usd();
|
||||
|
||||
return array(
|
||||
'per_run_usd' => $per_run,
|
||||
'per_month_usd' => $per_month,
|
||||
'probe_count' => $probe_count,
|
||||
'over_cap' => $per_month > $cap,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,59 +144,23 @@ class PRV_Config {
|
||||
*/
|
||||
private static function default_peptides(): array {
|
||||
return array(
|
||||
array(
|
||||
'slug' => 'bpc-157',
|
||||
'label' => 'BPC-157',
|
||||
),
|
||||
array(
|
||||
'slug' => 'tb-500',
|
||||
'label' => 'TB-500',
|
||||
),
|
||||
array(
|
||||
'slug' => 'mk-677',
|
||||
'label' => 'MK-677',
|
||||
),
|
||||
array(
|
||||
'slug' => 'cjc-1295',
|
||||
'label' => 'CJC-1295',
|
||||
),
|
||||
array(
|
||||
'slug' => 'ghrp-6',
|
||||
'label' => 'GHRP-6',
|
||||
),
|
||||
array(
|
||||
'slug' => 'ipamorelin',
|
||||
'label' => 'Ipamorelin',
|
||||
),
|
||||
array(
|
||||
'slug' => 'semaglutide',
|
||||
'label' => 'Semaglutide',
|
||||
),
|
||||
array(
|
||||
'slug' => 'tirzepatide',
|
||||
'label' => 'Tirzepatide',
|
||||
),
|
||||
array(
|
||||
'slug' => 'selank',
|
||||
'label' => 'Selank',
|
||||
),
|
||||
array(
|
||||
'slug' => 'semax',
|
||||
'label' => 'Semax',
|
||||
),
|
||||
array(
|
||||
'slug' => 'nad-plus',
|
||||
'label' => 'NAD+',
|
||||
),
|
||||
array(
|
||||
'slug' => 'aod-9604',
|
||||
'label' => 'AOD-9604',
|
||||
),
|
||||
array( 'slug' => 'bpc-157', 'label' => 'BPC-157' ),
|
||||
array( 'slug' => 'tb-500', 'label' => 'TB-500' ),
|
||||
array( 'slug' => 'mk-677', 'label' => 'MK-677' ),
|
||||
array( 'slug' => 'cjc-1295', 'label' => 'CJC-1295' ),
|
||||
array( 'slug' => 'ghrp-6', 'label' => 'GHRP-6' ),
|
||||
array( 'slug' => 'ipamorelin', 'label' => 'Ipamorelin' ),
|
||||
array( 'slug' => 'semaglutide', 'label' => 'Semaglutide' ),
|
||||
array( 'slug' => 'tirzepatide', 'label' => 'Tirzepatide' ),
|
||||
array( 'slug' => 'selank', 'label' => 'Selank' ),
|
||||
array( 'slug' => 'semax', 'label' => 'Semax' ),
|
||||
array( 'slug' => 'nad-plus', 'label' => 'NAD+' ),
|
||||
array( 'slug' => 'aod-9604', 'label' => 'AOD-9604' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default prompt intent templates — three query styles per peptide.
|
||||
* Default prompt intent templates -- three query styles per peptide.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
@@ -154,18 +171,4 @@ class PRV_Config {
|
||||
'{peptide} reconstitution guide',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Default models: Perplexity sonar (primary citation signal) + two
|
||||
* OpenRouter-routed search-capable models for breadth.
|
||||
*
|
||||
* @return array<int, string>
|
||||
*/
|
||||
private static function default_models(): array {
|
||||
return array(
|
||||
'perplexity/sonar',
|
||||
'openai/gpt-4o-search-preview',
|
||||
'google/gemini-2.0-flash-001',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user