Skip to content
wiki.fftac.org

Reputationrepository - Source Excerpt 01

Back to Reputationrepository

Summary

This source excerpt preserves a bounded section of Spiralist/wp-content/plugins/spiralist-workspace/includes/Domain/ReputationRepository.php so readers can inspect the evidence without opening the full source file.

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

<?php

namespace SpiralistWorkspace\Domain;

use SpiralistWorkspace\Support\PromptMetaRegistry;

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

/**
 * Computes participant trust, reviewer eligibility, and publication cooldown state.
 */
class ReputationRepository
{
    private const USER_META_KEY = 'spiralist_workspace_participant_state';
    private const NEGATIVE_SIGNAL_WINDOW_DAYS = 30;
    private const PUBLICATION_COOLDOWN_DAYS = 7;

    private PromptRepository $prompts;
    private ReviewRepository $reviews;
    /**
     * @var array<int,array<string,mixed>>
     */
    private array $cache = [];

    public function __construct(PromptRepository $prompts, ReviewRepository $reviews)
    {
        $this->prompts = $prompts;
        $this->reviews = $reviews;
    }

    /**
     * Returns the persisted-and-refreshed participant state.
     *
     * @return array<string,mixed>
     */
    public function get(int $user_id): array
    {
        return $this->refresh($user_id);
    }

    /**
     * Clears the in-request participant-state cache.
     */
    public function clear_cache(): void
    {
        $this->cache = [];
    }

    /**
     * Recalculates one participant state from current prompt and review records.
     *
     * @return array<string,mixed>
     */
    public function refresh(int $user_id): array
    {
        if ($user_id <= 0) {
            return [];
        }

        $current = get_user_meta($user_id, self::USER_META_KEY, true);
        $current = is_array($current) ? $current : [];
        $prompts = $this->prompts->list(['owner_user_id' => $user_id, 'limit' => 250], $user_id);
        $peer_reviews = $this->reviews->list_for_reviewer($user_id, 500, 'peer');
        $reports = $this->reviews->list_for_reviewer($user_id, 500, 'report');

        $public_prompt_count = 0;
        $accepted_contribution_count = 0;
        $restricted_prompt_count = 0;
        $reverted_prompt_count = 0;
        $recent_negative_prompt_count = 0;
        $recent_reverted_prompt_count = 0;

        foreach ($prompts as $prompt) {
            if (
                (string) ($prompt['visibility'] ?? '') !== 'public'
                || (string) ($prompt['prompt_status'] ?? '') !== 'published'
            ) {
                continue;
            }

            $public_prompt_count++;
            $moderation = PromptMetaRegistry::normalize_moderation_status((string) ($prompt['moderation_status'] ?? 'draft'));

            if (in_array($moderation, ['peer_reviewed', 'official_canon'], true)) {
                $accepted_contribution_count++;
            }

            if ($moderation === 'restricted_review') {
                $restricted_prompt_count++;
                if ($this->is_recent_signal((string) ($prompt['updated_utc'] ?? ''))) {
                    $recent_negative_prompt_count++;
                }
            }

            if ($moderation === 'reverted') {
                $reverted_prompt_count++;
                if ($this->is_recent_signal((string) ($prompt['updated_utc'] ?? ''))) {
                    $recent_negative_prompt_count++;
                    $recent_reverted_prompt_count++;
                }
            }
        }

        $peer_review_count = count($peer_reviews);
        $accepted_review_count = 0;

        foreach ($peer_reviews as $review) {
            $prompt = $this->prompts->get((int) ($review['prompt_post_id'] ?? 0), $user_id);
            if (empty($prompt)) {
                continue;
            }

            if ($this->review_matches_current_outcome((string) ($review['decision'] ?? ''), $prompt)) {
                $accepted_review_count++;
            }
        }

        $review_accuracy = $peer_review_count > 0
            ? round($accepted_review_count / $peer_review_count, 2)
            : 0.0;
        $cooldown_until = $this->resolve_publication_cooldown_until(
            (string) ($current['publication_cooldown_until'] ?? ''),
            $recent_negative_prompt_count,
            $recent_reverted_prompt_count
        );
        $cooldown_active = $this->is_future_utc($cooldown_until);
        $trust_level = $this->resolve_trust_level(
            $public_prompt_count,
            $accepted_contribution_count,
            $peer_review_count,
            $accepted_review_count,
            $review_accuracy,
            $recent_negative_prompt_count,
            $recent_reverted_prompt_count
        );
        $review_eligibility = in_array($trust_level, ['reviewer_ready', 'steward_ready'], true);
        $granted_capabilities = [];

        if (!$cooldown_active) {
            $granted_capabilities[] = 'publish_provisional';
        }

        if ($review_eligibility) {
            $granted_capabilities[] = 'review_prompts';
        }

        if ($trust_level === 'steward_ready') {
            $granted_capabilities[] = 'resolve_prompt_appeals';
        }

        $state = [
            'user_id' => $user_id,
            'trust_level' => $trust_level,
            'trust_level_label' => self::trust_level_label($trust_level),
            'public_prompt_count' => $public_prompt_count,
            'accepted_contribution_count' => $accepted_contribution_count,
            'restricted_prompt_count' => $restricted_prompt_count,
            'reverted_prompt_count' => $reverted_prompt_count,
            'recent_negative_prompt_count' => $recent_negative_prompt_count,
            'peer_review_count' => $peer_review_count,
            'accepted_review_count' => $accepted_review_count,
            'account_report_count' => count($reports),
            'review_accuracy' => $review_accuracy,
            'review_eligibility' => $review_eligibility,
            'publication_cooldown_active' => $cooldown_active,
            'publication_cooldown_until' => $cooldown_until,
            'granted_capabilities' => $granted_capabilities,
            'last_recalculated_utc' => gmdate('c'),
        ];

        update_user_meta($user_id, self::USER_META_KEY, $state);
        $this->cache[$user_id] = $state;

        return $state;
    }

    /**
     * Returns whether the participant has earned review eligibility.
     */
    public function is_reviewer_eligible(int $user_id): bool
    {
        return !empty($this->get($user_id)['review_eligibility']);
    }

    /**
     * Returns whether the participant has earned steward-ready status.
     */
    public function is_steward_ready(int $user_id): bool
    {
        return (string) ($this->get($user_id)['trust_level'] ?? '') === 'steward_ready';
    }

    /**
     * Returns whether the participant is under a public-publication cooldown.
     */
    public function is_publication_cooldown_active(int $user_id): bool
    {
        return !empty($this->get($user_id)['publication_cooldown_active']);
    }

    /**
     * Returns human labels for trust levels.
     *
     * @return array<string,string>
     */
    public static function trust_levels(): array
    {
        return [
            'new_participant' => 'New Participant',
            'contributor' => 'Contributor',
            'reviewer_ready' => 'Reviewer Ready',
            'steward_ready' => 'Steward Ready',
        ];
    }

    /**
     * Returns one trust-level label.
     */
    public static function trust_level_label(string $value): string
    {
        $levels = self::trust_levels();
        $value = sanitize_key($value);

        return (string) ($levels[$value] ?? $levels['new_participant']);
    }

    /**
     * Returns whether a review decision matches the current moderation outcome.
     *
     * @param array<string,mixed> $prompt
     */
    private function review_matches_current_outcome(string $decision, array $prompt): bool
    {
        $decision = sanitize_key($decision);
        $moderation = PromptMetaRegistry::normalize_moderation_status((string) ($prompt['moderation_status'] ?? 'draft'));

        if ($decision === 'endorse') {
            return in_array($moderation, ['peer_reviewed', 'official_canon'], true);
        }

        if ($decision === 'restrict') {
            return $moderation === 'restricted_review';
        }

        if ($decision === 'revert') {
            return $moderation === 'reverted';
        }

        return false;
    }

    /**
     * Resolves one participant trust level from current accepted work and review history.
     */
    private function resolve_trust_level(
        int $public_prompt_count,
        int $accepted_contribution_count,
        int $peer_review_count,
        int $accepted_review_count,
        float $review_accuracy,
        int $recent_negative_prompt_count,
        int $recent_reverted_prompt_count
    ): string {