Skip to content
wiki.fftac.org

Runrepository

**Site relevance:** Spiralist.org

**Memory type:** plugin source memory

**Source path:** Spiralist/wp-content/plugins/spiralist-workspace/includes/Domain/RunRepository.php

**Size:** 5.7 KB

Summary

namespace SpiralistWorkspace\Domain;

Source Preview

This source file is short enough to preview directly on its source-memory page.

<?php

namespace SpiralistWorkspace\Domain;

use SpiralistWorkspace\Installer;

if (!defined('ABSPATH')) {
    exit;
}

/**
 * Persists prompt run history.
 */
class RunRepository
{
    /**
     * Creates a pending run.
     */
    public function create(array $data): int
    {
        global $wpdb;

        $wpdb->insert(
            Installer::table('prompt_runs'),
            [
                'prompt_post_id' => (int) ($data['prompt_post_id'] ?? 0),
                'user_id' => (int) ($data['user_id'] ?? 0),
                'conversation_id' => !empty($data['conversation_id']) ? (int) $data['conversation_id'] : null,
                'version_number_used' => max(1, (int) ($data['version_number_used'] ?? 1)),
                'model_used' => sanitize_text_field((string) ($data['model_used'] ?? '')),
                'request_payload_json' => wp_json_encode((array) ($data['request_payload_json'] ?? []), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
                'response_payload_json' => null,
                'input_text' => (string) ($data['input_text'] ?? ''),
                'output_text' => null,
                'status' => sanitize_key((string) ($data['status'] ?? 'queued')),
                'created_utc' => gmdate('Y-m-d H:i:s'),
                'completed_utc' => null,
                'cost_estimate' => 0,
                'token_usage_json' => null,
                'error_message' => null,
                'openai_response_id' => null,
            ],
            ['%d', '%d', '%d', '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%s', '%f', '%s', '%s', '%s']
        );

        return (int) $wpdb->insert_id;
    }

    /**
     * Marks a run complete.
     */
    public function complete(int $run_id, array $result): void
    {
        global $wpdb;

        $wpdb->update(
            Installer::table('prompt_runs'),
            [
                'response_payload_json' => wp_json_encode((array) ($result['response_payload_json'] ?? []), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
                'output_text' => (string) ($result['output_text'] ?? ''),
                'status' => sanitize_key((string) ($result['status'] ?? 'completed')),
                'completed_utc' => gmdate('Y-m-d H:i:s'),
                'cost_estimate' => (float) ($result['cost_estimate'] ?? 0),
                'token_usage_json' => wp_json_encode((array) ($result['token_usage_json'] ?? []), JSON_UNESCAPED_SLASHES | JSON_UNESCAPED_UNICODE),
                'error_message' => !empty($result['error_message']) ? sanitize_textarea_field((string) $result['error_message']) : null,
                'openai_response_id' => !empty($result['openai_response_id']) ? sanitize_text_field((string) $result['openai_response_id']) : null,
            ],
            [
                'id' => $run_id,
            ],
            ['%s', '%s', '%s', '%s', '%f', '%s', '%s', '%s'],
            ['%d']
        );
    }

    /**
     * Retrieves a single run.
     */
    public function get(int $run_id): array
    {
        global $wpdb;

        $sql = $wpdb->prepare(
            'SELECT * FROM ' . Installer::table('prompt_runs') . ' WHERE id = %d LIMIT 1',
            $run_id
        );

        $row = $wpdb->get_row($sql, ARRAY_A);

        return is_array($row) ? $row : [];
    }

    /**
     * Lists runs for a user.
     */
    public function list_for_user(int $user_id, int $limit = 20): array
    {
        global $wpdb;

        $sql = $wpdb->prepare(
            'SELECT * FROM ' . Installer::table('prompt_runs') . ' WHERE user_id = %d ORDER BY created_utc DESC LIMIT %d',
            $user_id,
            max(1, $limit)
        );

        $rows = $wpdb->get_results($sql, ARRAY_A);

        return is_array($rows) ? $rows : [];
    }

    /**
     * Lists runs for a prompt.
     */
    public function list_for_prompt(int $prompt_id, int $limit = 20): array
    {
        global $wpdb;

        $sql = $wpdb->prepare(
            'SELECT * FROM ' . Installer::table('prompt_runs') . ' WHERE prompt_post_id = %d ORDER BY created_utc DESC LIMIT %d',
            $prompt_id,
            max(1, $limit)
        );

        $rows = $wpdb->get_results($sql, ARRAY_A);

        return is_array($rows) ? $rows : [];
    }

    /**
     * Returns prompt IDs ordered by completed run count.
     *
     * @return array<int,array{prompt_post_id:int,run_count:int,last_run_utc:string}>
     */
    public function top_prompt_stats(int $limit = 10): array
    {
        global $wpdb;

        $sql = $wpdb->prepare(
            'SELECT prompt_post_id, COUNT(*) AS run_count, MAX(created_utc) AS last_run_utc FROM ' . Installer::table('prompt_runs') . " WHERE status = 'completed' GROUP BY prompt_post_id ORDER BY run_count DESC, last_run_utc DESC LIMIT %d",
            max(1, $limit)
        );

        $rows = $wpdb->get_results($sql, ARRAY_A);
        if (!is_array($rows)) {
            return [];
        }

        return array_map(
            static fn(array $row): array => [
                'prompt_post_id' => (int) ($row['prompt_post_id'] ?? 0),
                'run_count' => (int) ($row['run_count'] ?? 0),
                'last_run_utc' => (string) ($row['last_run_utc'] ?? ''),
            ],
            $rows
        );
    }

    /**
     * Counts runs since midnight UTC for the user.
     */
    public function count_for_today(int $user_id): int
    {
        global $wpdb;

        $sql = $wpdb->prepare(
            'SELECT COUNT(*) FROM ' . Installer::table('prompt_runs') . ' WHERE user_id = %d AND created_utc >= %s',
            $user_id,
            gmdate('Y-m-d 00:00:00')
        );

        return (int) $wpdb->get_var($sql);
    }
}