prefix . 'prautoblogger_content_scores'; // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $top_posts = $wpdb->get_results( "SELECT cs.post_id, cs.composite_score, p.post_title // phpcs:ignore WordPress.DB.PreparedSQL.InterpolatedNotPrepared FROM {$scores_table} cs JOIN {$wpdb->posts} p ON p.ID = cs.post_id WHERE cs.composite_score > 0 ORDER BY cs.composite_score DESC LIMIT 5", ARRAY_A ); if ( empty( $top_posts ) ) { return ''; } $lines = array( 'Top performing past articles (learn from these):' ); foreach ( $top_posts as $post ) { $lines[] = sprintf( '- "%s" (score: %.1f)', $post['post_title'], (float) $post['composite_score'] ); } return implode( "\n", $lines ); } /** * Get titles of recently published articles so the LLM avoids repeating them. * * Fetches generated posts from the last 30 days. A wider window than the * scorer's 7-day dedup because the LLM is smart enough to find genuinely * new angles even on adjacent topics — we just need it to know what exists. * * Side effects: database read via WP_Query. * * @return string Formatted context block, or empty string if no recent articles. */ public static function get_recent_articles_context(): string { $query = new \WP_Query( array( 'post_type' => 'post', 'post_status' => array( 'publish', 'draft', 'pending' ), 'meta_key' => '_prautoblogger_generated', 'meta_value' => '1', 'date_query' => array( array( 'after' => '30 days ago' ) ), 'posts_per_page' => 100, 'fields' => 'ids', 'no_found_rows' => true, ) ); if ( empty( $query->posts ) ) { return ''; } $titles = array(); foreach ( $query->posts as $post_id ) { $titles[] = '- "' . get_the_title( $post_id ) . '"'; } $block = 'IMPORTANT — Topics already covered (last 30 days). Do NOT suggest topics that overlap with these. '; $block .= "Find genuinely NEW angles, questions, or subjects that are NOT already covered:\n"; $block .= implode( "\n", $titles ); return $block; } }