diff --git a/includes/class-activator.php b/includes/class-activator.php index cd5103e..0177026 100755 --- a/includes/class-activator.php +++ b/includes/class-activator.php @@ -28,6 +28,7 @@ class PRAutoBlogger_Activator { self::create_tables(); self::set_default_options(); self::schedule_cron(); + self::backpopulate_run_id_meta_v081(); // Store the DB version so we can run migrations on future updates. update_option( 'prautoblogger_db_version', PRAUTOBLOGGER_DB_VERSION, false ); @@ -225,5 +226,62 @@ class PRAutoBlogger_Activator { if ( ! wp_next_scheduled( 'prautoblogger_collect_metrics' ) ) { wp_schedule_event( time() + HOUR_IN_SECONDS, 'prautoblogger_six_hours', 'prautoblogger_collect_metrics' ); } + + // v0.8.1: daily reaper for orphan `llm_research` rows. Pinned to + // 03:15 so it runs 15 min after the primary generation cron, giving + // any live pipeline time to finish and call amortize_research_costs + // cleanly before the reaper scans. + if ( ! wp_next_scheduled( 'prautoblogger_reap_orphan_research_rows' ) ) { + $tomorrow = strtotime( 'tomorrow 03:15' ); + if ( false !== $tomorrow ) { + wp_schedule_event( $tomorrow, 'daily', 'prautoblogger_reap_orphan_research_rows' ); + } + } + } + + /** + * One-shot v0.8.1 migration — back-populate `_prautoblogger_run_id` + * post_meta for existing posts from their gen-log rows. + * + * Lets the orphan-research-row reaper (v0.8.1+) attribute historic + * orphan rows to sibling posts via post_meta lookup, without having + * to walk the generation_log table. Gated by + * `prautoblogger_migrated_run_id_meta_v081` so it runs exactly once + * per site. + * + * Side effects: adds post_meta rows for existing PRAutoBlogger posts + * that don't already have `_prautoblogger_run_id`. + * + * @return void + */ + private static function backpopulate_run_id_meta_v081(): void { + if ( get_option( 'prautoblogger_migrated_run_id_meta_v081' ) ) { + return; + } + + global $wpdb; + $gen_log = $wpdb->prefix . 'prautoblogger_generation_log'; + + // For each (post_id, run_id) pair present in the gen-log, write + // the run_id meta if it's not already set. Uses the first run_id + // seen per post — in practice each post is linked to exactly one run. + // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery + $pairs = $wpdb->get_results( + "SELECT post_id, MIN(run_id) AS run_id FROM {$gen_log} + WHERE post_id IS NOT NULL AND run_id IS NOT NULL AND run_id != '' + GROUP BY post_id" + ); + + if ( is_array( $pairs ) ) { + foreach ( $pairs as $row ) { + $post_id = (int) ( $row->post_id ?? 0 ); + $run_id = (string) ( $row->run_id ?? '' ); + if ( $post_id > 0 && '' !== $run_id && ! metadata_exists( 'post', $post_id, '_prautoblogger_run_id' ) ) { + update_post_meta( $post_id, '_prautoblogger_run_id', $run_id ); + } + } + } + + update_option( 'prautoblogger_migrated_run_id_meta_v081', '1' ); } } diff --git a/includes/class-deactivator.php b/includes/class-deactivator.php index be1158c..a1d9405 100755 --- a/includes/class-deactivator.php +++ b/includes/class-deactivator.php @@ -36,6 +36,7 @@ class PRAutoBlogger_Deactivator { $hooks = [ 'prautoblogger_daily_generation', 'prautoblogger_collect_metrics', + 'prautoblogger_reap_orphan_research_rows', ]; foreach ( $hooks as $hook ) { diff --git a/includes/core/class-publisher.php b/includes/core/class-publisher.php index 879f812..74f7119 100755 --- a/includes/core/class-publisher.php +++ b/includes/core/class-publisher.php @@ -87,7 +87,7 @@ class PRAutoBlogger_Publisher { 'post_status' => $post_status, 'post_type' => 'post', 'post_author' => PRAutoBlogger_Post_Assembler::get_default_author_id(), - 'meta_input' => $this->build_meta( $idea, $review ), + 'meta_input' => $this->build_meta( $idea, $review, $run_id ), ]; /** @see class-prautoblogger.php — listeners registered in main loader. */ @@ -120,15 +120,22 @@ class PRAutoBlogger_Publisher { /** * Build post_meta array for generation metadata. * + * The `_prautoblogger_run_id` key (added v0.8.1) lets the orphan-research + * reaper attribute an orphan `llm_research` row back to its sibling + * articles without re-walking the gen_log table. See + * core/class-research-reaper.php. + * * @param PRAutoBlogger_Article_Idea $idea * @param PRAutoBlogger_Editorial_Review $review + * @param string|null $run_id Pipeline run UUID, or null in legacy paths. * @return array */ private function build_meta( PRAutoBlogger_Article_Idea $idea, - PRAutoBlogger_Editorial_Review $review + PRAutoBlogger_Editorial_Review $review, + ?string $run_id = null ): array { - return [ + $meta = [ '_prautoblogger_generated' => '1', '_prautoblogger_analysis_id' => $idea->get_analysis_id(), '_prautoblogger_source_ids' => wp_json_encode( $idea->get_source_ids() ), @@ -143,5 +150,9 @@ class PRAutoBlogger_Publisher { '_prautoblogger_article_type' => $idea->get_article_type(), '_prautoblogger_target_keywords' => wp_json_encode( $idea->get_target_keywords() ), ]; + if ( null !== $run_id && '' !== $run_id ) { + $meta['_prautoblogger_run_id'] = $run_id; + } + return $meta; } }