$kept Scored kept sources. * @param array $discarded Scored discarded sources (over max). * @return void Side effects: DB inserts; no-op when audit tables absent. */ public function write( string $run_id, array $kept, array $discarded ): void { if ( ! PRAutoBlogger_Audit_Writer::is_available() ) { return; } global $wpdb; $table = $wpdb->prefix . 'prautoblogger_run_sources'; $now = current_time( 'mysql' ); $sources = array_merge( array_map( static fn( $s ) => $s + array( 'is_kept' => 1, 'discard_reason' => '', ), $kept ), array_map( static fn( $s ) => $s + array( 'is_kept' => 0, 'discard_reason' => 'Exceeded maximum kept sources per run', ), $discarded ) ); foreach ( $sources as $s ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery $wpdb->insert( $table, array( 'run_id' => $run_id, 'agent_role' => $s['agent_role'] ?? 'curator', 'source_url' => $s['url'], 'doi' => $this->extract_doi( $s['url'] ), 'kept' => $s['is_kept'], 'reason' => $s['is_kept'] ? sprintf( 'Quality %.3f (relevance=%.2f)', $s['quality_score'], $s['relevance'] ) : $s['discard_reason'], 'quality_score' => $s['quality_score'], 'created_at' => $now, ) ); } } // ── Private helpers ───────────────────────────────────────────────── /** * Extract a DOI from a URL if one is present. * * @param string $url Source URL. * @return string|null DOI, or null. */ private function extract_doi( string $url ): ?string { if ( preg_match( '#10\.\d{4,}/\S+#', $url, $matches ) ) { return rtrim( $matches[0], '.' ); } return null; } }