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 '| ' . esc_html__( 'Peptide', 'peptide-repo-core' ) . ' | ';
echo '' . esc_html__( 'Route', 'peptide-repo-core' ) . ' | ';
echo '' . esc_html__( 'Dose Range', 'peptide-repo-core' ) . ' | ';
echo '' . esc_html__( 'Population', 'peptide-repo-core' ) . ' | ';
echo '' . esc_html__( 'Study', 'peptide-repo-core' ) . ' | ';
echo '' . esc_html__( 'Confidence', 'peptide-repo-core' ) . ' | ';
echo '' . esc_html__( 'Actions', 'peptide-repo-core' ) . ' | ';
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(
array(
'action' => 'pr_core_approve',
'candidate_id' => $c->id,
)
),
'pr_core_queue_action'
);
$reject_url = wp_nonce_url(
add_query_arg(
array(
'action' => 'pr_core_reject',
'candidate_id' => $c->id,
)
),
'pr_core_queue_action'
);
echo '';
printf( '| %s | ', esc_html( $name ) );
printf( '%s | ', esc_html( $c->route ) );
printf( '%s | ', esc_html( $dose ) );
printf( '%s | ', esc_html( $c->population ) );
printf( '%s | ', $study ); // Contains escaped HTML + link.
printf( '%s | ', esc_html( $confidence_pct ) );
printf(
'%s %s | ',
esc_url( $approve_url ),
esc_html__( 'Approve', 'peptide-repo-core' ),
esc_url( $reject_url ),
esc_html__( 'Reject', 'peptide-repo-core' )
);
echo '
';
}
echo '
';
}
/**
* 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, array( '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' ) . '
';
}
);
}
}
}