P0 security: - PRV_Gateway_Client: remove response body from RuntimeException (HTTP status only) - PRV_Capture_Writer: allowlist-only write (prompt/response text, never headers/auth) - Mandatory capture order: can_afford → probe → persist_result → update_row_cost → write_meta → capture_io - capture_io last and wrapped in swallowing try/catch New tables (SCHEMA_VERSION 3): - prv_call_meta: cost/metadata per call, kept indefinitely - prv_call_io: rendered prompt + raw response, pruned on retention window (default 90d) New classes: - PRV_Call_Meta_Table, PRV_Call_Io_Table: table schema + dbDelta - PRV_Capture_Writer: metadata + I/O write; security allowlist enforced - PRV_Prune_Cron: daily prv_daily_prune cron deletes aged prv_call_io rows only - PRV_Cost_Rollup_Query: MTD summary, model breakdown, drill-down, projection - PRV_Call_Log_Query: paginated + filtered call-log query + per-call I/O fetch - PRV_Costs_Page + PRV_Costs_Renderer: Costs admin sub-page (cap progress, bento, model table, drill-down) - PRV_Call_Log_Page + PRV_Call_Log_Table_Renderer: Call Log admin sub-page (filter bar, paginated table, WCAG AA pagination) - PRV_Call_Drawer_Renderer: non-modal complementary drawer (5 states: Normal/Pruned/Legacy/Failed/Loading) - PRV_Call_Drawer_Script: inline JS for drawer (300-line split from renderer) - PRV_Call_Detail_Ajax: wp_ajax_prv_call_detail AJAX handler - PRV_Admin_Page_Css: CSS provider (300-line split from PRV_Admin_Page) - PRV_Probe_Run_Executor: inner probe loop (300-line split from PRV_Probe_Runner) Updated: - PRV_Probe_Result: tokens_in/tokens_out params + getters - Both providers: report tokens_in/tokens_out from usage.prompt_tokens/completion_tokens - PRV_Probe_Runner: slimmed to lock+UUID+delegation; injects PRV_Capture_Writer - PRV_Config: get_io_retention_days(); PRV_IO_RETENTION_DEFAULT_DAYS constant - PRV_Upgrader/Activator/Deactivator/Plugin: register new tables, cron, pages, AJAX - Settings: retention days field + save in controller - uninstall.php: DROP both new tables, clear prv_daily_prune cron - Subnav 4-tab strip (Dashboard | Costs | Call Log | Settings) on all pages - ARCHITECTURE.md, CHANGELOG.md, CONTEXT.md, CONVENTIONS.md updated Tests: - test-capture-writer.php: write_meta, write_io, P0 security (401 record has no key/Bearer/sk-or-) - test-cost-audit.php: MTD reconciliation, projection, prune, forced capture_io exception, table drops Agent-Session: peptiderepo-geomonitor-v0.3.0-cost-audit
225 lines
6.4 KiB
PHP
225 lines
6.4 KiB
PHP
<?php
|
|
/**
|
|
* Configuration getters and default-seeding for PR Vision.
|
|
*
|
|
* @package PrVision
|
|
*/
|
|
|
|
declare(strict_types=1);
|
|
|
|
/**
|
|
* Configuration helper: seed defaults and retrieve plugin settings.
|
|
*
|
|
* 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. 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(), PRV_Model_Registry.
|
|
*
|
|
* @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.
|
|
*
|
|
* Uses add_option() so existing values are never overwritten on re-activation.
|
|
*
|
|
* Side effects: Writes multiple wp_options rows on first run.
|
|
*
|
|
* @return void
|
|
*/
|
|
public static function seed_defaults(): void {
|
|
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( 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.
|
|
}
|
|
|
|
/**
|
|
* Get the monthly budget cap in USD.
|
|
*
|
|
* @return float Positive float; falls back to the plugin default.
|
|
*/
|
|
public static function get_monthly_budget_usd(): float {
|
|
$val = (float) get_option( 'prv_monthly_budget_usd', PRV_DEFAULT_MONTHLY_BUDGET_USD );
|
|
return $val > 0.0 ? $val : PRV_DEFAULT_MONTHLY_BUDGET_USD;
|
|
}
|
|
|
|
/**
|
|
* Get the list of peptide slugs/labels to probe.
|
|
*
|
|
* Each element is an associative array with 'slug' and 'label' keys.
|
|
*
|
|
* @return array<int, array{slug: string, label: string}>
|
|
*/
|
|
public static function get_peptides(): array {
|
|
$raw = get_option( 'prv_peptides', self::default_peptides() );
|
|
return is_array( $raw ) ? $raw : self::default_peptides();
|
|
}
|
|
|
|
/**
|
|
* Get the prompt intent templates.
|
|
*
|
|
* Each string may contain {peptide} as a placeholder.
|
|
*
|
|
* @return array<int, string>
|
|
*/
|
|
public static function get_prompt_intents(): array {
|
|
$raw = get_option( 'prv_prompt_intents', self::default_prompt_intents() );
|
|
return is_array( $raw ) ? $raw : self::default_prompt_intents();
|
|
}
|
|
|
|
/**
|
|
* 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 {
|
|
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,
|
|
);
|
|
}
|
|
|
|
|
|
/**
|
|
* Get the I/O retention window in days.
|
|
*
|
|
* Raw prompt + response text in prv_call_io is pruned after this many days.
|
|
* Cost/metadata in prv_call_meta is kept indefinitely.
|
|
*
|
|
* @return int Days; minimum 1, default PRV_IO_RETENTION_DEFAULT_DAYS.
|
|
*/
|
|
public static function get_io_retention_days(): int {
|
|
$val = (int) get_option( 'prv_io_retention_days', PRV_IO_RETENTION_DEFAULT_DAYS );
|
|
return max( 1, $val );
|
|
}
|
|
|
|
/**
|
|
* Default set of ~12 high-interest peptides (slug + human-readable label).
|
|
*
|
|
* @return array<int, array{slug: string, label: string}>
|
|
*/
|
|
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',
|
|
),
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Default prompt intent templates -- three query styles per peptide.
|
|
*
|
|
* @return array<int, string>
|
|
*/
|
|
private static function default_prompt_intents(): array {
|
|
return array(
|
|
'what is {peptide}',
|
|
'{peptide} benefits and dosage',
|
|
'{peptide} reconstitution guide',
|
|
);
|
|
}
|
|
}
|