From 56ee438449b20d873ebc2c4ce53f7c5a95ecb4d6 Mon Sep 17 00:00:00 2001 From: peptiderepo <273788462+peptiderepo@users.noreply.github.com> Date: Fri, 12 Jun 2026 14:05:21 +0700 Subject: [PATCH] fix: restore get_avg_tokens_for_stages method in cost-reporter Initial M1 push used bash-mount file read which produced a 146-line truncated version, losing the get_avg_tokens_for_stages() method (lines 145-203 in main). This broke 4 pre-existing CostTracker tests. Restored from main with M1 SQL bug fix applied: phpcs:ignore comments now on separate PHP lines above prepare() calls, not embedded inside the SQL string literals. Agent-Session: phase2-admin-m1-engineer --- includes/core/class-cost-reporter.php | 62 ++++++++++++++++++++++++++- 1 file changed, 61 insertions(+), 1 deletion(-) diff --git a/includes/core/class-cost-reporter.php b/includes/core/class-cost-reporter.php index 97a05cc..4d5689f 100644 --- a/includes/core/class-cost-reporter.php +++ b/includes/core/class-cost-reporter.php @@ -36,8 +36,9 @@ class PRAutoBlogger_Cost_Reporter { $first_of_month = gmdate( 'Y-m-01 00:00:00' ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared $result = $wpdb->get_var( - $wpdb->prepare( // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared + $wpdb->prepare( "SELECT COALESCE(SUM(estimated_cost), 0) FROM {$table} WHERE created_at >= %s AND response_status = 'success'", $first_of_month ) @@ -142,4 +143,63 @@ class PRAutoBlogger_Cost_Reporter { } return ( $this->get_monthly_spend() / $budget ) * 100.0; } + + /** + * Get average input/output token counts for given stages over a time period. + * + * Used by the model picker field renderer to calculate estimated costs + * per generation based on historical token usage. Maps setting IDs to their + * constituting stages (e.g., writing model controls outline + draft + polish). + * Moved here from Cost_Tracker in v0.18.0 (read-only reporting query; + * Cost_Tracker keeps a delegating wrapper for API compatibility). + * + * @param array $stages Stage names ('analysis', 'outline', 'draft', 'polish', 'review'). + * @param int $days Historical window (default 30 days). + * + * @return array{avg_prompt_tokens: float, avg_completion_tokens: float, sample_size: int} + * Returns empty counters if no history. Never throws. + */ + public function get_avg_tokens_for_stages( array $stages, int $days = 30 ): array { + global $wpdb; + + if ( empty( $stages ) ) { + return array( + 'avg_prompt_tokens' => 0.0, + 'avg_completion_tokens' => 0.0, + 'sample_size' => 0, + ); + } + + $cutoff_datetime = gmdate( 'Y-m-d H:i:s', time() - ( $days * DAY_IN_SECONDS ) ); + $stage_list = implode( ',', array_map( array( $wpdb, 'prepare' ), array_fill( 0, count( $stages ), '%s' ), $stages ) ); + $table_name = $wpdb->prefix . 'prautoblogger_generation_log'; + + // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared -- Prepared stage list via array_map + prepare + $query = $wpdb->prepare( + "SELECT AVG(prompt_tokens) as avg_prompt, + AVG(completion_tokens) as avg_completion, + COUNT(*) as sample_count + FROM $table_name + WHERE stage IN ( $stage_list ) + AND created_at >= %s", + $cutoff_datetime + ); + + // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- phpcs pragma above + $result = $wpdb->get_row( $query, ARRAY_A ); + + if ( ! $result || 0 === (int) ( $result['sample_count'] ?? 0 ) ) { + return array( + 'avg_prompt_tokens' => 0.0, + 'avg_completion_tokens' => 0.0, + 'sample_size' => 0, + ); + } + + return array( + 'avg_prompt_tokens' => (float) ( $result['avg_prompt'] ?? 0 ), + 'avg_completion_tokens' => (float) ( $result['avg_completion'] ?? 0 ), + 'sample_size' => (int) ( $result['sample_count'] ?? 0 ), + ); + } }