Skip to content

feat: add vendor trust score endpoint and leaderboard UI (closes #45)#52

Closed
Aharshi3614 wants to merge 1 commit into
jpdevhub:mainfrom
Aharshi3614:feature/h05-vendor-trust-score
Closed

feat: add vendor trust score endpoint and leaderboard UI (closes #45)#52
Aharshi3614 wants to merge 1 commit into
jpdevhub:mainfrom
Aharshi3614:feature/h05-vendor-trust-score

Conversation

@Aharshi3614
Copy link
Copy Markdown

@Aharshi3614 Aharshi3614 commented Jun 1, 2026

Description

Adds vendor trust scoring and a public leaderboard.

Changes

  • Added vendor trust score migration
  • Added vendor trust score API endpoints
  • Added leaderboard page UI
  • Added leaderboard route to the frontend
  • Registered vendor routes in backend

Closes #45


Summary by cubic

Adds vendor trust scoring and a public leaderboard to rank vendors by average freshness. Addresses #45 by exposing read endpoints, a recalc flow, and a new /leaderboard page.

  • New Features

    • Backend vendors router with:
      • GET /api/v1/vendors/leaderboard (public)
      • GET /api/v1/vendors/{vendor_id}/trust-score (public)
      • POST /api/v1/vendors/{vendor_id}/recalculate
    • Badge tiers: gold (≥80), silver (≥60), bronze (≥40); unranked if <5 scans.
    • Trend uses last 7 days vs previous 7 days with a ±3 threshold.
    • Frontend Leaderboard page at /leaderboard showing badge, score, scans, and trend.
    • Registered vendor routes in the backend.
  • Migration

    • Adds trust_badge, trend, total_scans, avg_freshness_score to vendors.
    • Creates index on avg_freshness_score for faster sorting.
    • Run the SQL migration, then backfill by calling POST /api/v1/vendors/{vendor_id}/recalculate for vendors with scans.

Written for commit 9e2ee09. Summary will update on new commits.

Review in cubic

@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 1, 2026

@Aharshi3614 is attempting to deploy a commit to the karan3431's projects Team on Vercel.

A member of the Team first needs to authorize it.

@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 1, 2026

🎉 Thank you for your Pull Request! We're thrilled to have your contribution to FreshScan AI.

Before we review, please make sure you have:

  • Followed the CONTRIBUTING.md guidelines.
  • Ensured all automated CI checks (linting, tests) are passing.
  • Checked that your commit messages follow the Conventional Commits format.

A maintainer will review your code as soon as possible!

Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

6 issues found across 5 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/migrations/add_vendor_trust_score.sql">

<violation number="1" location="backend/migrations/add_vendor_trust_score.sql:12">
P1: `avg_freshness_score NUMERIC(4,2)` cannot store the value `100.00`, which is possible when a vendor's average freshness reaches the maximum of the 0–100 percentage scale. This will cause numeric overflow errors at runtime.</violation>

<violation number="2" location="backend/migrations/add_vendor_trust_score.sql:12">
P2: `ADD COLUMN IF NOT EXISTS` silently skips the entire column definition when a column already exists, masking missing or incorrect type, defaults, and CHECK constraints. The `avg_freshness_score` column already exists in `master_schema.sql` as `INTEGER DEFAULT 0`, so this migration silently fails to change it to `NUMERIC(4,2) DEFAULT 0.0`. Similarly, `trust_badge` and `trend` CHECK constraints and defaults would be skipped if those columns were ever added manually. This creates schema drift across environments.</violation>
</file>

<file name="backend/vendors.py">

<violation number="1" location="backend/vendors.py:35">
P2: Trend calculation incorrectly ignores valid `freshness_index = 0` values, which can skew vendor trend results.</violation>

<violation number="2" location="backend/vendors.py:57">
P1: Missing range validation on `limit` query parameter in public `/leaderboard` endpoint</violation>

<violation number="3" location="backend/vendors.py:99">
P1: State-changing POST `/recalculate` endpoint is unauthenticated, allowing any caller to mutate vendor trust scores, badges, and trends. Other write endpoints in the app require `Depends(get_current_user)` but this endpoint lacks auth entirely.</violation>
</file>

<file name="src/pages/Leaderboard.tsx">

<violation number="1" location="src/pages/Leaderboard.tsx:35">
P1: This fetch bypasses the configured API base URL, so leaderboard requests can fail in deployed environments where backend and frontend are on different origins.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

CHECK (trend IN ('up', 'down', 'stable'))
DEFAULT 'stable',
ADD COLUMN IF NOT EXISTS total_scans INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS avg_freshness_score NUMERIC(4,2) DEFAULT 0.0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: avg_freshness_score NUMERIC(4,2) cannot store the value 100.00, which is possible when a vendor's average freshness reaches the maximum of the 0–100 percentage scale. This will cause numeric overflow errors at runtime.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/migrations/add_vendor_trust_score.sql, line 12:

<comment>`avg_freshness_score NUMERIC(4,2)` cannot store the value `100.00`, which is possible when a vendor's average freshness reaches the maximum of the 0–100 percentage scale. This will cause numeric overflow errors at runtime.</comment>

<file context>
@@ -0,0 +1,16 @@
+    CHECK (trend IN ('up', 'down', 'stable'))
+    DEFAULT 'stable',
+  ADD COLUMN IF NOT EXISTS total_scans INTEGER DEFAULT 0,
+  ADD COLUMN IF NOT EXISTS avg_freshness_score NUMERIC(4,2) DEFAULT 0.0;
+
+-- Index for fast leaderboard sorting
</file context>

Comment thread backend/vendors.py
"""

@router.get("/leaderboard")
async def get_leaderboard(limit: int = 20):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: Missing range validation on limit query parameter in public /leaderboard endpoint

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/vendors.py, line 57:

<comment>Missing range validation on `limit` query parameter in public `/leaderboard` endpoint</comment>

<file context>
@@ -0,0 +1,143 @@
+    """
+
+    @router.get("/leaderboard")
+    async def get_leaderboard(limit: int = 20):
+        """Public leaderboard — no auth required."""
+        try:
</file context>

Comment thread backend/vendors.py
except Exception as exc:
raise HTTPException(status_code=500, detail=str(exc))

@router.post("/{vendor_id}/recalculate")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: State-changing POST /recalculate endpoint is unauthenticated, allowing any caller to mutate vendor trust scores, badges, and trends. Other write endpoints in the app require Depends(get_current_user) but this endpoint lacks auth entirely.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/vendors.py, line 99:

<comment>State-changing POST `/recalculate` endpoint is unauthenticated, allowing any caller to mutate vendor trust scores, badges, and trends. Other write endpoints in the app require `Depends(get_current_user)` but this endpoint lacks auth entirely.</comment>

<file context>
@@ -0,0 +1,143 @@
+        except Exception as exc:
+            raise HTTPException(status_code=500, detail=str(exc))
+
+    @router.post("/{vendor_id}/recalculate")
+    async def recalculate_trust_score(vendor_id: str):
+        """
</file context>

Comment thread src/pages/Leaderboard.tsx
const [error, setError] = useState<string | null>(null);

useEffect(() => {
fetch("/api/v1/vendors/leaderboard")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This fetch bypasses the configured API base URL, so leaderboard requests can fail in deployed environments where backend and frontend are on different origins.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/pages/Leaderboard.tsx, line 35:

<comment>This fetch bypasses the configured API base URL, so leaderboard requests can fail in deployed environments where backend and frontend are on different origins.</comment>

<file context>
@@ -0,0 +1,120 @@
+  const [error,   setError]     = useState<string | null>(null);
+
+  useEffect(() => {
+    fetch("/api/v1/vendors/leaderboard")
+      .then((r) => {
+        if (!r.ok) throw new Error("Failed to fetch leaderboard.");
</file context>

CHECK (trend IN ('up', 'down', 'stable'))
DEFAULT 'stable',
ADD COLUMN IF NOT EXISTS total_scans INTEGER DEFAULT 0,
ADD COLUMN IF NOT EXISTS avg_freshness_score NUMERIC(4,2) DEFAULT 0.0;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: ADD COLUMN IF NOT EXISTS silently skips the entire column definition when a column already exists, masking missing or incorrect type, defaults, and CHECK constraints. The avg_freshness_score column already exists in master_schema.sql as INTEGER DEFAULT 0, so this migration silently fails to change it to NUMERIC(4,2) DEFAULT 0.0. Similarly, trust_badge and trend CHECK constraints and defaults would be skipped if those columns were ever added manually. This creates schema drift across environments.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/migrations/add_vendor_trust_score.sql, line 12:

<comment>`ADD COLUMN IF NOT EXISTS` silently skips the entire column definition when a column already exists, masking missing or incorrect type, defaults, and CHECK constraints. The `avg_freshness_score` column already exists in `master_schema.sql` as `INTEGER DEFAULT 0`, so this migration silently fails to change it to `NUMERIC(4,2) DEFAULT 0.0`. Similarly, `trust_badge` and `trend` CHECK constraints and defaults would be skipped if those columns were ever added manually. This creates schema drift across environments.</comment>

<file context>
@@ -0,0 +1,16 @@
+    CHECK (trend IN ('up', 'down', 'stable'))
+    DEFAULT 'stable',
+  ADD COLUMN IF NOT EXISTS total_scans INTEGER DEFAULT 0,
+  ADD COLUMN IF NOT EXISTS avg_freshness_score NUMERIC(4,2) DEFAULT 0.0;
+
+-- Index for fast leaderboard sorting
</file context>

Comment thread backend/vendors.py
.lt("timestamp", week_ago).execute()

def avg(rows):
vals = [r["freshness_index"] for r in rows if r.get("freshness_index")]
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Trend calculation incorrectly ignores valid freshness_index = 0 values, which can skew vendor trend results.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/vendors.py, line 35:

<comment>Trend calculation incorrectly ignores valid `freshness_index = 0` values, which can skew vendor trend results.</comment>

<file context>
@@ -0,0 +1,143 @@
+        .lt("timestamp", week_ago).execute()
+
+    def avg(rows):
+        vals = [r["freshness_index"] for r in rows if r.get("freshness_index")]
+        return sum(vals) / len(vals) if vals else None
+
</file context>

Copy link
Copy Markdown
Owner

@jpdevhub jpdevhub left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Awesome work on the backend logic, Aharshi! The endpoints and data aggregations are extremely solid. However, the frontend (Leaderboard.tsx) needs some heavy styling adjustments to fit our project's custom design system before we can merge.

Comment thread src/pages/Leaderboard.tsx
Comment on lines +16 to +22
const BADGE: Record<Badge, { emoji: string; label: string; color: string }> = {
gold: { emoji: "🥇", label: "Gold", color: "#f59e0b" },
silver: { emoji: "🥈", label: "Silver", color: "#9ca3af" },
bronze: { emoji: "🥉", label: "Bronze", color: "#f97316" },
unranked: { emoji: "⚪", label: "Unranked", color: "#d1d5db" },
};

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NO EMOJIS: This project strictly avoids emojis to maintain a professional, academic/cyberpunk aesthetic. Please remove the 🥇🥈🥉⚪ emojis. Instead, use mono-spaced text labels like [ GOLD ] or use custom SVG icons.

Comment thread src/pages/Leaderboard.tsx
Comment on lines +22 to +24

const TREND: Record<Trend, { icon: string; color: string; label: string }> = {
up: { icon: "↑", color: "#22c55e", label: "Improving" },
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hardcoded Colors: You used classes like bg-white, text-gray-800, and text-blue-600. These completely break our Dark/Light theme toggle. You must use our CSS variables. For example, replace bg-white with bg-surface-low or use the existing component. Replace text colors with text-on-surface or text-neon.

Comment thread src/pages/Leaderboard.tsx
const trend = TREND[vendor.trend ?? "stable"];
return (
<div
key={vendor.id}
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Brutalist Aesthetic: Our app avoids soft, rounded UI elements. Please remove rounded-xl and shadow-sm and stick to the sharp borders (border-outline-variant/30) used in the rest of the application.

Comment thread src/pages/Leaderboard.tsx
Comment on lines +58 to +62
<div className="max-w-2xl mx-auto px-4 py-10">
<h1 className="text-3xl font-bold mb-1">🐟 Vendor Trust Leaderboard</h1>
<p className="text-gray-500 mb-8 text-sm">
Rankings based on anonymous freshness scans across markets.
</p>
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please utilize our custom fonts. Headers should use font-[family-name:var(--font-display)] and metrics/small text should use font-[family-name:var(--font-mono)] tracking-widest to match the terminal-like appearance.

@Aharshi3614 Aharshi3614 closed this by deleting the head repository Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

H-05: Vendor Trust Score & Public Leaderboard

2 participants