handle_actions(); $repo = new PR_Core_Candidate_Queue_Repository(); $candidates = $repo->find_by_status( 'pending' ); $pep_repo = new PR_Core_Peptide_Repository(); echo '
'; echo '

' . esc_html__( 'AI Candidate Queue', 'peptide-repo-core' ) . '

'; echo '

' . esc_html__( 'Review AI-extracted dosing data candidates. Approved rows are copied to the dosing table.', 'peptide-repo-core' ) . '

'; if ( empty( $candidates ) ) { echo '

' . esc_html__( 'No pending candidates.', 'peptide-repo-core' ) . '

'; echo '
'; return; } echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; echo ''; foreach ( $candidates as $c ) { $peptide = $pep_repo->find_by_id( $c->peptide_id ); $name = $peptide ? $peptide->title : '#' . $c->peptide_id; $dose = $c->dose_min !== null ? number_format( $c->dose_min, 2 ) : '?'; if ( $c->dose_max !== null && $c->dose_max !== $c->dose_min ) { $dose .= ' – ' . number_format( $c->dose_max, 2 ); } $dose .= ' ' . esc_html( $c->dose_unit ); $study = $c->study_title ? esc_html( mb_substr( $c->study_title, 0, 60 ) ) : '—'; if ( $c->citation_pubmed_id ) { $study .= sprintf( ' PubMed', esc_attr( $c->citation_pubmed_id ) ); } $confidence_pct = number_format( $c->extraction_confidence * 100, 0 ) . '%'; $approve_url = wp_nonce_url( add_query_arg( [ 'action' => 'pr_core_approve', 'candidate_id' => $c->id ] ), 'pr_core_queue_action' ); $reject_url = wp_nonce_url( add_query_arg( [ 'action' => 'pr_core_reject', 'candidate_id' => $c->id ] ), 'pr_core_queue_action' ); echo ''; printf( '', esc_html( $name ) ); printf( '', esc_html( $c->route ) ); printf( '', esc_html( $dose ) ); printf( '', esc_html( $c->population ) ); printf( '', $study ); // Contains escaped HTML + link. printf( '', esc_html( $confidence_pct ) ); printf( '', esc_url( $approve_url ), esc_html__( 'Approve', 'peptide-repo-core' ), esc_url( $reject_url ), esc_html__( 'Reject', 'peptide-repo-core' ) ); echo ''; } echo '
' . esc_html__( 'Peptide', 'peptide-repo-core' ) . '' . esc_html__( 'Route', 'peptide-repo-core' ) . '' . esc_html__( 'Dose Range', 'peptide-repo-core' ) . '' . esc_html__( 'Population', 'peptide-repo-core' ) . '' . esc_html__( 'Study', 'peptide-repo-core' ) . '' . esc_html__( 'Confidence', 'peptide-repo-core' ) . '' . esc_html__( 'Actions', 'peptide-repo-core' ) . '
%s%s%s%s%s%s%s %s
'; } /** * Process approve/reject actions from query parameters. * * Side effects: database writes, admin notices. * * @return void */ private function handle_actions(): void { $action = sanitize_text_field( $_GET['action'] ?? '' ); if ( ! in_array( $action, [ 'pr_core_approve', 'pr_core_reject' ], true ) ) { return; } if ( ! check_admin_referer( 'pr_core_queue_action' ) ) { return; } $candidate_id = absint( $_GET['candidate_id'] ?? 0 ); if ( 0 === $candidate_id ) { return; } $repo = new PR_Core_Candidate_Queue_Repository(); $reviewer_id = get_current_user_id(); if ( 'pr_core_approve' === $action ) { $dosing_id = $repo->approve( $candidate_id, $reviewer_id ); if ( $dosing_id > 0 ) { add_action( 'admin_notices', static function () { echo '

' . esc_html__( 'Candidate approved and added to dosing data.', 'peptide-repo-core' ) . '

'; } ); } } else { $repo->reject( $candidate_id, $reviewer_id ); add_action( 'admin_notices', static function () { echo '

' . esc_html__( 'Candidate rejected.', 'peptide-repo-core' ) . '

'; } ); } } }