feat: add LLM cost tracking with budget enforcement and admin dashboard

- New Cost_Tracker class: logs every API call (model, tokens, cost), enforces
  monthly budgets with hard-stop mode, provides aggregated reporting
- New llm_costs database table with indexes on created_at, model, operation
- Modified LLM class: call_openrouter_with_usage() extracts token usage from
  OpenRouter responses; budget gate checks before each API call
- Admin settings: monthly budget (USD), enforcement mode (disabled/warn/hard-stop),
  cost log retention period
- Admin cost dashboard: budget progress bar, daily cost chart (Chart.js),
  per-model and per-operation breakdowns, recent API call log
- PHPUnit tests: 16 tests covering cost calculation, pricing lookups,
  budget enforcement logic, and model validation
- Updated ARCHITECTURE.md with new table schema, data flow, and key decisions
- Updated CONVENTIONS.md with "how to add cost tracking for a new operation"
- Version bump to 2.4.0

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Terence
2026-04-13 04:18:30 +07:00
parent 6796121003
commit b7da4f42b9
47 changed files with 2192 additions and 164 deletions

92
tests/bootstrap.php Normal file → Executable file
View File

@@ -118,6 +118,32 @@ if ( ! function_exists( 'add_filter' ) ) {
return true;
}
}
if ( ! function_exists( 'get_transient' ) ) {
function get_transient( $transient ) {
global $_test_transients;
return isset( $_test_transients[ $transient ] ) ? $_test_transients[ $transient ] : false;
}
}
if ( ! function_exists( 'set_transient' ) ) {
function set_transient( $transient, $value, $expiration = 0 ) {
global $_test_transients;
$_test_transients[ $transient ] = $value;
return true;
}
}
if ( ! function_exists( 'delete_transient' ) ) {
function delete_transient( $transient ) {
global $_test_transients;
unset( $_test_transients[ $transient ] );
return true;
}
}
if ( ! defined( 'MINUTE_IN_SECONDS' ) ) {
define( 'MINUTE_IN_SECONDS', 60 );
}
if ( ! defined( 'DAY_IN_SECONDS' ) ) {
define( 'DAY_IN_SECONDS', 86400 );
}
if ( ! function_exists( 'add_action' ) ) {
function add_action( $tag, $callback, $priority = 10, $accepted_args = 1 ) {
return true;
@@ -212,6 +238,72 @@ require_once PEPTIDE_NEWS_PLUGIN_DIR . 'includes/class-peptide-news-content-filt
// LLM class provides is_valid_model() used by Admin sanitize callbacks.
require_once PEPTIDE_NEWS_PLUGIN_DIR . 'includes/class-peptide-news-llm.php';
// Stub AJAX-related functions used by cost tracker and other modules.
if ( ! function_exists( 'wp_send_json_success' ) ) {
function wp_send_json_success( $data = null, $status_code = null ) {
global $_test_json_response;
$_test_json_response = array( 'success' => true, 'data' => $data );
}
}
if ( ! function_exists( 'wp_send_json_error' ) ) {
function wp_send_json_error( $data = null, $status_code = null ) {
global $_test_json_response;
$_test_json_response = array( 'success' => false, 'data' => $data );
}
}
if ( ! function_exists( 'check_ajax_referer' ) ) {
function check_ajax_referer( $action = -1, $query_arg = false, $die = true ) {
return true;
}
}
if ( ! function_exists( 'wp_unslash' ) ) {
function wp_unslash( $value ) {
return is_string( $value ) ? stripslashes( $value ) : $value;
}
}
if ( ! function_exists( 'wp_json_encode' ) ) {
function wp_json_encode( $data, $options = 0, $depth = 512 ) {
return json_encode( $data, $options, $depth );
}
}
if ( ! function_exists( 'home_url' ) ) {
function home_url( $path = '' ) {
return 'https://example.com' . $path;
}
}
if ( ! function_exists( 'wp_remote_post' ) ) {
function wp_remote_post( $url, $args = array() ) {
return new WP_Error( 'http_request_not_available', 'HTTP requests not available in tests' );
}
}
if ( ! function_exists( 'wp_remote_retrieve_response_code' ) ) {
function wp_remote_retrieve_response_code( $response ) {
return 200;
}
}
if ( ! function_exists( 'wp_remote_retrieve_body' ) ) {
function wp_remote_retrieve_body( $response ) {
return '';
}
}
if ( ! function_exists( 'wp_remote_retrieve_header' ) ) {
function wp_remote_retrieve_header( $response, $header ) {
return '';
}
}
if ( ! function_exists( 'selected' ) ) {
function selected( $selected, $current = true, $echo = true ) {
$result = (string) $selected === (string) $current ? ' selected="selected"' : '';
if ( $echo ) {
echo $result;
}
return $result;
}
}
// Cost tracker provides calculate_cost(), budget checks used by tests.
require_once PEPTIDE_NEWS_PLUGIN_DIR . 'includes/class-peptide-news-cost-tracker.php';
// Stub WP_REST_Request for REST API tests.
if ( ! class_exists( 'WP_REST_Request' ) ) {
class WP_REST_Request {