# Portable Worker Reputation System — Build Spec

**Author:** Product Agent (Rivet)  
**Date:** 2025-07-14  
**Status:** DRAFT — Ready for Review  
**Depends on:** `spec-instant-matching.md`, `ratings-system-spec.md`

---

## 1. What This Is

A portable, worker-owned reputation system. Workers accumulate verified work history on RateRight — ratings, completed jobs, skills endorsements, hours worked — and **own it**. They can export it, share it, take it anywhere.

**Why this matters:** This is the supply-side magnet. Workers join RateRight not just to get jobs, but to **build something they keep**. Every completed job adds to their verified trade resume. No other platform in Australian construction gives workers a portable, verified work history they control.

**The pitch to workers:** "Every job you do on RateRight builds your verified trade resume. Take it anywhere — share it with any contractor, on any platform, forever."

---

## 2. Core Concepts

### 2.1 Reputation Profile

Every worker has a **Reputation Profile** — a structured, verified record of:

| Component | Source | Verifiable? |
|-----------|--------|-------------|
| Overall reputation score | Calculated | Yes — derived from verified data |
| Completed jobs count | Platform records | Yes — blockchain-anchored hash |
| Total hours worked | Platform records | Yes |
| Contractor ratings (reliability, skill, attitude) | Contractor reviews | Yes — tied to completed jobs |
| Skills & trades | Self-declared + endorsed | Partially |
| Certifications (White Card, etc.) | Uploaded + OCR verified | Yes |
| Punctuality record | GPS check-in data | Yes |
| Badges & milestones | Platform calculated | Yes |

### 2.2 Ownership Principle

- The worker's reputation data belongs to **the worker**, not RateRight.
- Workers can export their full reputation at any time.
- Workers can delete their account and still retain an exported copy.
- RateRight is the **custodian**, not the owner.
- We never sell or share reputation data without worker consent.

### 2.3 Verification Levels

Every piece of reputation data has a verification level:

| Level | Label | How | Trust Weight |
|-------|-------|-----|-------------|
| 0 | Self-Declared | Worker claims it | 0.3x |
| 1 | Contractor-Endorsed | A contractor confirms it after a job | 0.7x |
| 2 | Platform-Verified | Platform validates via external source (ABR, cert authority, GPS) | 1.0x |

**Examples:**
- Worker says "I can do formwork" → Level 0 (self-declared)
- Contractor hires worker for formwork job, rates skill 4+/5 → Level 1 (contractor-endorsed)
- Worker uploads formwork cert, platform OCR-verifies → Level 2 (platform-verified)

---

## 3. Reputation Score Algorithm

### 3.1 Composite Score

The reputation score is a single number from **0–100**, displayed as a percentage or mapped to a tier.

```
reputation_score = (
    job_completion_score  × 0.30 +
    rating_score          × 0.30 +
    reliability_score     × 0.20 +
    skills_score          × 0.10 +
    tenure_score          × 0.10
)
```

All sub-scores normalized to 0–100.

### 3.2 Sub-Score Calculations

#### Job Completion Score (30%)

```
job_completion_score = min(100, (completed_jobs / expected_jobs_for_tier) × 100)
```

| Tier | Expected Jobs | Score at Threshold |
|------|--------------|-------------------|
| New | 0 | 50 (starting score) |
| Getting Started | 5 | 60 |
| Active | 15 | 75 |
| Experienced | 50 | 90 |
| Veteran | 100+ | 100 |

Workers with 0 jobs start at 50. Each completed job increases this sub-score. Job abandonments and no-shows **subtract** from the count (1 no-show = -3 completed jobs equivalent).

#### Rating Score (30%)

```
rating_score = weighted_average_rating × 20
```

Where `weighted_average_rating` is a time-decayed weighted average of all contractor ratings (1–5 scale), so a perfect 5.0 average = 100 score.

**Time decay:** Ratings decay using a half-life model:
```
weight = e^(-λ × days_since_rating)
λ = ln(2) / 180  // half-life of 180 days
```

Ratings older than 365 days have ~25% weight. Ratings older than 2 years have ~6% weight. This keeps the score fresh — recent performance matters most.

**Dimensional breakdown:** The rating score uses the three contractor rating dimensions equally:
- Skill Level (1–5)
- Punctuality (1–5) 
- Professionalism (1–5)

```
weighted_average_rating = (
    time_weighted_avg(skill_ratings) × 0.40 +
    time_weighted_avg(punctuality_ratings) × 0.35 +
    time_weighted_avg(professionalism_ratings) × 0.25
)
```

Skill weighted slightly higher because contractors care about it most.

#### Reliability Score (20%)

```
reliability_score = (
    acceptance_follow_through × 60 +
    no_show_penalty × 40
)
```

Where:
- `acceptance_follow_through` = jobs_completed / jobs_accepted (0–1, × 100)
- `no_show_penalty` = max(0, 100 - (no_shows × 25))

One no-show costs 25 points on this sub-component. Four no-shows = 0 reliability on the penalty component.

#### Skills Score (10%)

```
skills_score = (
    verified_skills_count × 15 +          // max 60 (4 verified skills)
    contractor_endorsement_count × 5 +    // max 25 (5 endorsements) 
    certification_count × 5               // max 15 (3 certs)
)
// capped at 100
```

Skills at different verification levels contribute differently:
- Self-declared skill: 5 points
- Contractor-endorsed skill: 15 points
- Platform-verified certification: 20 points

#### Tenure Score (10%)

```
tenure_score = min(100, (months_on_platform / 12) × 100)
```

Workers reach max tenure score after 12 months on the platform. This rewards loyalty but is a small weight — a new worker with great ratings can still score high overall.

### 3.3 Score Display

| Score Range | Tier | Badge | Color |
|-------------|------|-------|-------|
| 0–39 | Needs Improvement | ⚠️ | Red |
| 40–59 | Getting Started | 🔵 | Blue |
| 60–74 | Solid | ⭐ | Green |
| 75–89 | Strong | ⭐⭐ | Gold |
| 90–100 | Elite | 🏆 | Purple |

New workers with no history display as "New to RateRight" with a blue badge, not a numerical score. The score becomes visible after **3 completed jobs with ratings**.

### 3.4 Score Recalculation

- Recalculated **on every relevant event** (job completion, new rating, skill endorsement).
- Stored as a materialized value in `worker_profiles` for fast reads.
- Background job runs nightly to apply time decay to all scores.
- Score history stored for trend analysis (weekly snapshots).

---

## 4. Skills & Verification System

### 4.1 Trade Skills Taxonomy

Predefined skill tree for construction trades:

```
trades/
├── Steelfixing
│   ├── Rebar tying
│   ├── Mesh laying
│   ├── Post-tensioning
│   └── Reading drawings
├── Formwork
│   ├── Wall forms
│   ├── Column forms
│   ├── Slab formwork
│   └── Stripping
├── Concreting
│   ├── Pouring
│   ├── Finishing
│   ├── Cutting/grinding
│   └── Pump operation
├── Carpentry
│   ├── Framing
│   ├── Fit-out
│   ├── Roofing
│   └── Decking
├── General Labour
│   ├── Site cleanup
│   ├── Material handling
│   ├── Traffic control
│   └── Demolition
└── ... (extensible)
```

### 4.2 Skill Verification Flow

**Level 0 — Self-Declared:**
1. Worker selects skills from taxonomy during profile setup or later.
2. Skills appear on profile with "Self-declared" tag.
3. Low trust weight in matching (0.3x).

**Level 1 — Contractor-Endorsed:**
1. After a completed job, contractor is prompted: "Did [worker] demonstrate these skills?"
2. Contractor confirms/adds skills from a checklist relevant to the job trade.
3. Each unique contractor endorsement for a skill increases its verification level.
4. After **3 unique contractor endorsements**, skill upgrades to Level 1.
5. Displayed as "Endorsed by 3 contractors" on profile.

**Level 2 — Platform-Verified:**
1. Worker uploads official certification (trade cert, license, ticket).
2. AI OCR extracts cert details (name, number, expiry, issuing body).
3. Cross-reference with external databases where available (SafeWork NSW, etc.).
4. Manual review for edge cases.
5. Displayed as "✓ Verified" with cert details.

### 4.3 Certification Tracking

| Certification | Verification Method | Auto-Expire? |
|--------------|-------------------|-------------|
| White Card (General Construction Induction) | OCR + SafeWork DB lookup | No (lifetime) |
| Forklift License (LF) | OCR + manual review | Yes — expiry date tracked |
| EWP (Elevated Work Platform) | OCR + manual review | Yes |
| Rigging / Dogging | OCR + manual review | Yes |
| Working at Heights | OCR | Yes |
| First Aid | OCR | Yes |
| Traffic Control | OCR | Yes |
| Confined Space | OCR | Yes |

Workers are notified **30 days before** a certification expires. Expired certs are flagged on profile (not removed — shown as "Expired").

---

## 5. User Flows

### 5.1 Worker Views Their Reputation Profile

```
┌─────────────────────────────────────┐
│ 👤 Jake Mitchell                    │
│ Steel Fixer · Sydney                │
│                                     │
│ ⭐⭐ Strong · 82/100                │
│ ████████████████░░░░ 82%            │
│                                     │
│ 📊 Stats                            │
│ ├── 47 jobs completed               │
│ ├── 380 hours worked                │
│ ├── 98% show-up rate                │
│ └── 4.6★ avg rating (39 reviews)    │
│                                     │
│ 🛠️ Skills                           │
│ ├── ✓ Rebar Tying (Verified)        │
│ ├── 👷 Mesh Laying (3 endorsements) │
│ ├── 👷 Reading Drawings (2 endorse) │
│ └── 📝 Post-tensioning (declared)   │
│                                     │
│ 📜 Certifications                   │
│ ├── ✓ White Card (verified)         │
│ ├── ✓ Working at Heights (exp 2027) │
│ └── ✓ Forklift LF (exp 2026-11)    │
│                                     │
│ 📈 Rating Trend                     │
│ Last 6 months: ↗️ Improving          │
│                                     │
│ 💬 Recent Reviews (3 shown)         │
│ ├── ★★★★★ "Solid worker, always..." │
│ ├── ★★★★☆ "Reliable and skilled..." │
│ └── ★★★★★ "Would hire again..."     │
│                                     │
│ [📤 Share Profile] [📄 Export PDF]  │
│ [⚙️ Privacy Settings]              │
└─────────────────────────────────────┘
```

**Actions available:**
- **Share Profile** → generates a public link + QR code
- **Export PDF** → downloads a formatted trade resume
- **Privacy Settings** → choose what's visible on shared profile
- **View Full History** → all jobs, ratings, endorsements
- **Edit Skills** → add/remove self-declared skills

### 5.2 Contractor Views Worker Reputation (During Matching)

When a contractor sees matched workers after posting a job:

```
┌─────────────────────────────────────┐
│ Steel Fixer — Parramatta            │
│ Need 2 · 0 of 2 filled             │
│                                     │
│ ┌─────────────────────────────────┐ │
│ │ 🏆 Jake M. — 82/100 (Strong)   │ │
│ │ ⭐ 4.6 (39 reviews) · 47 jobs  │ │
│ │ 📍 8 min away · $45/hr         │ │
│ │ ✓ White Card · ✓ Heights       │ │
│ │ Skills: Rebar ✓ · Mesh 👷      │ │
│ │ 98% reliability · ↗️ Improving  │ │
│ │ [View Profile] [Hire]           │ │
│ └─────────────────────────────────┘ │
│                                     │
│ ┌─────────────────────────────────┐ │
│ │ 🔵 Sam T. — New to RateRight   │ │
│ │ No ratings yet · 0 jobs         │ │
│ │ 📍 15 min away · $42/hr        │ │
│ │ ✓ White Card                    │ │
│ │ Skills: Rebar 📝 · Mesh 📝     │ │
│ │ "3 years formwork at Lendlease" │ │
│ │ [View Profile] [Hire]           │ │
│ └─────────────────────────────────┘ │
└─────────────────────────────────────┘
```

**What contractors see:**
- Reputation score + tier badge
- Star rating + review count
- Completed jobs count
- Relevant verified skills/certs for THIS job
- Reliability percentage
- Trend indicator
- For new workers: profile summary instead of stats

### 5.3 Worker Exports & Shares Profile

#### Share Flow:
1. Worker taps "Share Profile"
2. System generates a unique, public URL: `rateright.com.au/w/jake-mitchell-3f8a`
3. QR code generated for the same URL
4. Worker chooses sharing method: copy link, QR code, or direct share (SMS/WhatsApp/email)

#### Export PDF Flow:
1. Worker taps "Export PDF"
2. Privacy dialog: "Choose what to include"
   - ☑️ Reputation score & tier
   - ☑️ Completed jobs count & hours
   - ☑️ Average ratings
   - ☑️ Skills & certifications
   - ☑️ Recent reviews (last 5)
   - ☐ Contact details (phone/email)
   - ☐ Full job history
3. Worker taps "Generate"
4. PDF is created and downloaded / shared

#### Privacy Controls:
Workers can configure what appears on their **shared** profile (public link):

| Data | Default | Can Hide? |
|------|---------|-----------|
| Name | Shown | No (required) |
| Trade(s) | Shown | No |
| Reputation score | Shown | Yes |
| Completed jobs count | Shown | Yes |
| Average rating | Shown | Yes |
| Individual reviews | Hidden | Can show |
| Skills | Shown | Yes |
| Certifications | Shown | Yes |
| Contact details | Hidden | Can show |
| Job history details | Hidden | Can show |

**Default:** Score, stats, skills, and certs are visible. Reviews and contact details are hidden. Workers can toggle each item.

---

## 6. Database Schema

### 6.1 Core Tables

```sql
-- Worker reputation profile (materialized/cached scores)
CREATE TABLE worker_reputation (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    worker_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
    
    -- Composite score (0-100)
    reputation_score NUMERIC(5,2) NOT NULL DEFAULT 50.00,
    reputation_tier TEXT NOT NULL DEFAULT 'new',  -- new, getting_started, solid, strong, elite
    
    -- Sub-scores (0-100)
    job_completion_score NUMERIC(5,2) NOT NULL DEFAULT 50.00,
    rating_score NUMERIC(5,2) NOT NULL DEFAULT 0.00,
    reliability_score NUMERIC(5,2) NOT NULL DEFAULT 100.00,
    skills_score NUMERIC(5,2) NOT NULL DEFAULT 0.00,
    tenure_score NUMERIC(5,2) NOT NULL DEFAULT 0.00,
    
    -- Aggregate stats
    total_jobs_completed INTEGER NOT NULL DEFAULT 0,
    total_jobs_accepted INTEGER NOT NULL DEFAULT 0,
    total_hours_worked NUMERIC(10,2) NOT NULL DEFAULT 0.00,
    total_no_shows INTEGER NOT NULL DEFAULT 0,
    total_cancellations INTEGER NOT NULL DEFAULT 0,
    total_ratings_received INTEGER NOT NULL DEFAULT 0,
    avg_rating NUMERIC(3,2),  -- null until 3 ratings
    avg_skill_rating NUMERIC(3,2),
    avg_punctuality_rating NUMERIC(3,2),
    avg_professionalism_rating NUMERIC(3,2),
    
    -- Trend
    score_trend TEXT DEFAULT 'stable',  -- improving, stable, declining
    score_30d_ago NUMERIC(5,2),
    
    -- Visibility
    score_visible BOOLEAN NOT NULL DEFAULT FALSE,  -- true after 3 rated jobs
    
    -- Sharing
    share_slug TEXT UNIQUE,  -- e.g., 'jake-mitchell-3f8a'
    share_settings JSONB NOT NULL DEFAULT '{
        "show_score": true,
        "show_jobs_count": true,
        "show_ratings": true,
        "show_reviews": false,
        "show_skills": true,
        "show_certs": true,
        "show_contact": false,
        "show_job_history": false
    }'::jsonb,
    
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    
    CONSTRAINT unique_worker_reputation UNIQUE(worker_id)
);

-- Score history for trend tracking
CREATE TABLE reputation_score_history (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    worker_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
    reputation_score NUMERIC(5,2) NOT NULL,
    job_completion_score NUMERIC(5,2) NOT NULL,
    rating_score NUMERIC(5,2) NOT NULL,
    reliability_score NUMERIC(5,2) NOT NULL,
    skills_score NUMERIC(5,2) NOT NULL,
    tenure_score NUMERIC(5,2) NOT NULL,
    snapshot_date DATE NOT NULL,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    
    CONSTRAINT unique_weekly_snapshot UNIQUE(worker_id, snapshot_date)
);

-- Skills taxonomy
CREATE TABLE skills (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    trade_category TEXT NOT NULL,       -- e.g., 'steelfixing'
    skill_name TEXT NOT NULL,           -- e.g., 'Rebar Tying'
    skill_slug TEXT NOT NULL UNIQUE,    -- e.g., 'rebar-tying'
    description TEXT,
    is_active BOOLEAN NOT NULL DEFAULT TRUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Worker's claimed/verified skills
CREATE TABLE worker_skills (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    worker_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
    skill_id UUID NOT NULL REFERENCES skills(id),
    
    -- Verification level
    verification_level INTEGER NOT NULL DEFAULT 0,  -- 0=self-declared, 1=endorsed, 2=verified
    
    -- Endorsement tracking
    endorsement_count INTEGER NOT NULL DEFAULT 0,
    unique_endorser_count INTEGER NOT NULL DEFAULT 0,  -- unique contractors who endorsed
    
    -- Platform verification
    verified_at TIMESTAMPTZ,
    verification_source TEXT,  -- e.g., 'cert_upload', 'safework_nsw'
    
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    
    CONSTRAINT unique_worker_skill UNIQUE(worker_id, skill_id)
);

-- Skill endorsements from contractors
CREATE TABLE skill_endorsements (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    worker_id UUID NOT NULL REFERENCES profiles(id),
    skill_id UUID NOT NULL REFERENCES skills(id),
    contractor_id UUID NOT NULL REFERENCES profiles(id),
    job_id UUID NOT NULL REFERENCES jobs(id),
    
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    
    -- One endorsement per contractor per skill per job
    CONSTRAINT unique_endorsement UNIQUE(worker_id, skill_id, contractor_id, job_id)
);

-- Certifications
CREATE TABLE worker_certifications (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    worker_id UUID NOT NULL REFERENCES profiles(id) ON DELETE CASCADE,
    cert_type TEXT NOT NULL,            -- e.g., 'white_card', 'forklift_lf', 'ewp'
    cert_name TEXT NOT NULL,            -- display name
    cert_number TEXT,                   -- card/license number
    issued_by TEXT,                     -- issuing authority
    issue_date DATE,
    expiry_date DATE,                   -- null for lifetime certs
    
    -- Verification
    verification_status TEXT NOT NULL DEFAULT 'pending',  -- pending, verified, rejected, expired
    verified_at TIMESTAMPTZ,
    verification_method TEXT,           -- 'ocr', 'manual', 'api_lookup'
    verification_notes TEXT,
    
    -- Document
    document_url TEXT,                  -- stored in Supabase Storage
    ocr_extracted_data JSONB,           -- raw OCR output for audit
    
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

-- Job completion records (detailed per-job reputation data)
CREATE TABLE job_completion_records (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    worker_id UUID NOT NULL REFERENCES profiles(id),
    contractor_id UUID NOT NULL REFERENCES profiles(id),
    job_id UUID NOT NULL REFERENCES jobs(id),
    
    -- Completion details
    status TEXT NOT NULL,  -- 'completed', 'no_show', 'cancelled_by_worker', 'cancelled_by_contractor'
    hours_worked NUMERIC(5,2),
    
    -- Check-in/out (GPS verified)
    checked_in_at TIMESTAMPTZ,
    checked_out_at TIMESTAMPTZ,
    check_in_location GEOGRAPHY(POINT, 4326),
    check_in_distance_meters NUMERIC(8,2),  -- distance from job site at check-in
    
    -- Rating given by contractor for this job
    rating_skill INTEGER CHECK (rating_skill BETWEEN 1 AND 5),
    rating_punctuality INTEGER CHECK (rating_punctuality BETWEEN 1 AND 5),
    rating_professionalism INTEGER CHECK (rating_professionalism BETWEEN 1 AND 5),
    rating_comment TEXT,
    rating_tags TEXT[],  -- quick tags like 'excellent_quality', 'great_attitude'
    rated_at TIMESTAMPTZ,
    
    -- Skills endorsed during this job
    skills_endorsed UUID[],  -- array of skill_ids endorsed
    
    -- Hash for export verification
    record_hash TEXT,  -- SHA-256 hash of key fields for tamper detection
    
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    
    CONSTRAINT unique_job_worker UNIQUE(job_id, worker_id)
);

-- Shared profile access log
CREATE TABLE profile_share_access_log (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    worker_id UUID NOT NULL REFERENCES profiles(id),
    share_slug TEXT NOT NULL,
    accessed_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    accessor_ip TEXT,
    accessor_user_agent TEXT,
    referrer TEXT
);

-- PDF export history
CREATE TABLE profile_exports (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    worker_id UUID NOT NULL REFERENCES profiles(id),
    export_type TEXT NOT NULL,  -- 'pdf', 'json', 'link'
    export_settings JSONB,     -- what was included
    file_url TEXT,             -- for PDFs stored in Supabase Storage
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);
```

### 6.2 Indexes

```sql
-- Fast reputation lookup by worker
CREATE INDEX idx_worker_reputation_worker ON worker_reputation(worker_id);
CREATE INDEX idx_worker_reputation_score ON worker_reputation(reputation_score DESC);
CREATE INDEX idx_worker_reputation_tier ON worker_reputation(reputation_tier);

-- Skills lookups
CREATE INDEX idx_worker_skills_worker ON worker_skills(worker_id);
CREATE INDEX idx_worker_skills_level ON worker_skills(verification_level);
CREATE INDEX idx_skill_endorsements_worker ON skill_endorsements(worker_id);
CREATE INDEX idx_skill_endorsements_contractor ON skill_endorsements(contractor_id);

-- Certifications
CREATE INDEX idx_worker_certs_worker ON worker_certifications(worker_id);
CREATE INDEX idx_worker_certs_expiry ON worker_certifications(expiry_date) 
    WHERE verification_status = 'verified';

-- Job completion
CREATE INDEX idx_job_completion_worker ON job_completion_records(worker_id);
CREATE INDEX idx_job_completion_status ON job_completion_records(worker_id, status);

-- Share slug for public profiles
CREATE INDEX idx_worker_reputation_slug ON worker_reputation(share_slug) 
    WHERE share_slug IS NOT NULL;

-- Score history for trends
CREATE INDEX idx_score_history_worker_date ON reputation_score_history(worker_id, snapshot_date DESC);
```

### 6.3 RLS Policies

```sql
-- Workers can read their own reputation
CREATE POLICY worker_read_own_reputation ON worker_reputation
    FOR SELECT USING (auth.uid() = worker_id);

-- Workers can update their share settings
CREATE POLICY worker_update_share_settings ON worker_reputation
    FOR UPDATE USING (auth.uid() = worker_id)
    WITH CHECK (auth.uid() = worker_id);

-- Contractors can read reputation of matched workers
CREATE POLICY contractor_read_reputation ON worker_reputation
    FOR SELECT USING (
        EXISTS (
            SELECT 1 FROM job_matches jm
            JOIN jobs j ON j.id = jm.job_id
            WHERE jm.worker_id = worker_reputation.worker_id
            AND j.contractor_id = auth.uid()
        )
        OR score_visible = TRUE  -- visible to all after 3 rated jobs
    );

-- Public access via share slug (handled by edge function, not RLS)

-- Workers manage their own skills
CREATE POLICY worker_manage_skills ON worker_skills
    FOR ALL USING (auth.uid() = worker_id);

-- Contractors can create endorsements for workers they've hired
CREATE POLICY contractor_create_endorsement ON skill_endorsements
    FOR INSERT WITH CHECK (
        auth.uid() = contractor_id
        AND EXISTS (
            SELECT 1 FROM job_completion_records jcr
            WHERE jcr.worker_id = skill_endorsements.worker_id
            AND jcr.job_id = skill_endorsements.job_id
            AND jcr.contractor_id = auth.uid()
            AND jcr.status = 'completed'
        )
    );
```

---

## 7. Export Formats

### 7.1 Shareable Link (Public Profile)

**URL format:** `rateright.com.au/w/{share_slug}`

**Page content (based on privacy settings):**
- Worker name + trade
- Reputation score + tier badge
- Stats: jobs completed, hours worked, reliability %
- Skills with verification badges
- Certifications with expiry dates
- Selected reviews (if enabled)
- "Verified by RateRight" trust seal + date

**Technical:**
- SSR page (Next.js) for SEO + instant load
- Open Graph meta tags for social sharing previews
- Canonical URL for SEO
- No login required to view

### 7.2 PDF Trade Resume

**Layout:** Clean, professional, A4 format.

```
┌─────────────────────────────────────────────┐
│ [RateRight Logo]     VERIFIED TRADE RESUME  │
│                                              │
│ JAKE MITCHELL                                │
│ Steel Fixer · Sydney, NSW                    │
│ rateright.com.au/w/jake-mitchell-3f8a        │
│                                              │
│ ═══════════════════════════════════════════  │
│                                              │
│ REPUTATION SCORE         82/100 (Strong)     │
│ ████████████████░░░░                         │
│                                              │
│ WORK HISTORY                                 │
│ • 47 jobs completed on RateRight             │
│ • 380+ hours of verified work                │
│ • 98% show-up rate                           │
│ • Active since March 2025                    │
│                                              │
│ RATINGS                  ★★★★½ 4.6/5.0      │
│ • Skill:           ★★★★★ 4.8                │
│ • Punctuality:     ★★★★☆ 4.5                │
│ • Professionalism: ★★★★½ 4.6                │
│ Based on 39 contractor reviews               │
│                                              │
│ VERIFIED SKILLS                              │
│ ✓ Rebar Tying (Platform Verified)            │
│ ✓ Mesh Laying (Endorsed by 3 contractors)    │
│ ✓ Reading Drawings (Endorsed by 2)           │
│ • Post-tensioning (Self-declared)            │
│                                              │
│ CERTIFICATIONS                               │
│ ✓ White Card – Verified · #WC-NSW-12345     │
│ ✓ Working at Heights – Exp: Mar 2027        │
│ ✓ Forklift LF – Exp: Nov 2026              │
│                                              │
│ SELECTED REVIEWS                             │
│ ★★★★★ "Jake is one of the most reliable     │
│ steelfixers we've had on site. Always on     │
│ time, gets the job done." — Buildcorp, Oct   │
│                                              │
│ ═══════════════════════════════════════════  │
│ Verified by RateRight · Generated 14 Jul 25  │
│ Scan QR to verify: [QR CODE]                │
│ Verification ID: RPT-3f8a-2025-07-14        │
└─────────────────────────────────────────────┘
```

**Technical:**
- Generated server-side using Puppeteer or `@react-pdf/renderer`
- Stored in Supabase Storage with a unique verification ID
- QR code links to the live shareable profile (so the PDF can be verified against current data)
- Watermark-free — this is the worker's document

### 7.3 QR Code

- Generated using `qrcode` npm package
- Contains the shareable profile URL
- Available as standalone image (PNG/SVG) or embedded in PDF
- Suitable for printing on hard hats, hi-vis vests, or personal cards

### 7.4 JSON Export (Data Portability)

Workers can export their full reputation data as structured JSON:

```json
{
    "export_version": "1.0",
    "exported_at": "2025-07-14T10:30:00Z",
    "verification_id": "RPT-3f8a-2025-07-14",
    "worker": {
        "name": "Jake Mitchell",
        "trade_primary": "Steel Fixer",
        "location": "Sydney, NSW",
        "member_since": "2025-03-01"
    },
    "reputation": {
        "score": 82,
        "tier": "strong",
        "score_visible": true
    },
    "stats": {
        "jobs_completed": 47,
        "hours_worked": 380.5,
        "show_up_rate": 0.98,
        "total_ratings": 39,
        "avg_rating": 4.6
    },
    "skills": [...],
    "certifications": [...],
    "job_history": [...],
    "ratings_received": [...],
    "record_hashes": [...]  // for verification
}
```

---

## 8. Anti-Gaming Measures

### 8.1 Fake Reviews

**Problem:** Contractors and workers collude to create fake jobs and positive reviews.

**Defences:**
1. **Payment gate:** A job only generates a ratable event if the **$50 hire fee was charged**. No payment = no job record = no reputation gain. This is the primary defence — it costs $50 per fake review.
2. **Unique contractor cap:** Reviews from the same contractor have diminishing returns. First 3 reviews from contractor X count fully. Reviews 4–10 count at 50% weight. Reviews 11+ count at 25%.
3. **Job-review linkage:** Every review is tied to a specific job_id. No orphan reviews.
4. **GPS verification:** Check-in location must be within 500m of the job site. If a worker "completes" a job but never checked in near the site, the job record is flagged.

### 8.2 Self-Dealing

**Problem:** Worker creates a contractor account (or has a friend do it), posts fake jobs, hires themselves.

**Defences:**
1. **ABN verification for contractors:** Contractor accounts require a valid ABN linked to a real registered business. Cross-referenced with ABR.
2. **Device fingerprinting:** Flag if the same device creates both a worker and contractor account.
3. **Payment method uniqueness:** The Stripe card used for hiring must be unique per contractor account.
4. **Network analysis:** Nightly batch job looks for suspicious patterns:
   - Contractor X only ever hires Worker Y
   - Worker Y only ever works for Contractor X
   - Jobs are suspiciously short or formulaic
   - Jobs are always at the same location with the same duration
5. **Manual review trigger:** Any worker whose reputation score jumps more than 15 points in 7 days gets flagged for manual review.

### 8.3 Rating Manipulation

**Problem:** Contractors threaten bad reviews to coerce workers, or workers retaliate.

**Defences:**
1. **Double-blind ratings:** Neither party sees the other's rating until both have rated (or 7 days pass). From existing ratings spec.
2. **Retaliation detection:** If both parties rate within 1 hour and both give 1 star, flag for review.
3. **Outlier dampening:** A single 1-star review when a worker has 20+ reviews at 4.5+ average is dampened (weight reduced to 0.5x). Still counted, but doesn't nuke the score.
4. **Review response:** Workers can post a public response to any review (max 280 chars). This discourages unfair reviews — the worker gets to tell their side.

### 8.4 Score Inflation Prevention

1. **Time decay** (§3.2) naturally prevents score stagnation.
2. **Tier thresholds** require sustained performance, not one-time spikes.
3. **Score caps per component** prevent gaming a single dimension.
4. **Weekly recalculation** with decay means inactive workers' scores drift toward baseline over time.

---

## 9. Cold Start Problem

### 9.1 New Workers (Zero History)

**The challenge:** Workers with no history look risky to contractors. But every worker starts with no history.

**Solutions:**

1. **"New to RateRight" Badge:**
   - Workers with <3 rated jobs show this badge instead of a score.
   - Badge is designed to be neutral/positive, not stigmatizing.
   - Copy: "New to RateRight — give them a shot!"

2. **Profile Richness Bonus:**
   - New workers who complete their profile fully (photo, bio, skills, certs) get a matching boost.
   - Specifically: +10 points in the matching algorithm for workers with >80% profile completion.
   - This decays after 5 completed jobs (by then, the actual reputation takes over).

3. **Credential Front-Loading:**
   - Let new workers upload certs and get them verified **before** their first job.
   - A worker with a verified White Card + Forklift LF + Working at Heights starts with tangible verified assets, even with zero reviews.
   - These show prominently on their match card.

4. **Voice/Text Profile Summary:**
   - New workers' voice signup summary is displayed on their match card.
   - Contractors see: *"3 years steelfixing at Lendlease, moved to Sydney last month"*
   - This gives contractors **context** even without platform history.

5. **First-Job Guarantee (Optional — Business Decision):**
   - Consider: If a new worker no-shows on their first job, RateRight refunds the contractor's $50.
   - Reduces contractor risk in trying new workers.
   - Capped at 1 refund per contractor per month to prevent abuse.
   - **Needs Michael's approval** — has revenue impact.

6. **Initial Score: 50/100:**
   - New workers start at exactly 50. Not shown publicly, but used internally for matching.
   - This is the midpoint — neither penalized nor rewarded.
   - First 3–5 jobs have **amplified** impact on score (high sensitivity period).

### 9.2 Score Sensitivity in Early Career

To help new workers build reputation faster:

```
score_sensitivity_multiplier = 
    if completed_jobs < 5:  2.0   // double impact
    if completed_jobs < 15: 1.5   // 50% boost
    if completed_jobs < 30: 1.2   // slight boost  
    else: 1.0                     // normal
```

This means:
- First 5-star rating as a new worker has 2x the score impact as the same rating for a veteran.
- But a no-show also has 2x the negative impact — cuts both ways.

---

## 10. Integration with Instant Matching Algorithm

### 10.1 Reputation as Matching Input

From `spec-instant-matching.md`, the matching score is:

```
match_score = (
    reliability_score × 0.35 +
    proximity_score   × 0.25 +
    rate_score        × 0.15 +
    repeat_hire_bonus × 0.10 +
    priority_badge    × 0.15
)
```

The `reliability_score` (35% weight) in the matching algorithm is **directly derived from the reputation system**:

```
matching_reliability_score = (
    (worker_reputation.reputation_score × 0.60) +
    (worker_reputation.reliability_score × 0.40)
)
```

This means the reputation system feeds the matching algorithm, but with emphasis on the reliability sub-component (which directly measures show-up rate and follow-through).

### 10.2 Reputation-Based Matching Bonuses

| Reputation Tier | Matching Bonus |
|----------------|---------------|
| Elite (90–100) | +10 to match_score |
| Strong (75–89) | +5 to match_score |
| Solid (60–74) | +0 |
| Getting Started (40–59) | -0 |
| Needs Improvement (<40) | -10 to match_score |
| New (no score yet) | +0 (profile richness bonus may apply) |

### 10.3 Contractor Preferences

Allow contractors to set matching preferences:

```
contractor_preferences = {
    min_reputation_score: null,    // e.g., 60 — filter out below this
    min_rating: null,              // e.g., 4.0 — filter out below this
    prefer_verified_skills: true,  // boost workers with Level 1+ skills
    allow_new_workers: true,       // include "New to RateRight" workers
    require_certs: ['white_card']  // must have these verified certs
}
```

These preferences are applied as **filters** before scoring, not as score modifiers. This keeps the algorithm fast.

### 10.4 Data Flow

```
[Job Posted] 
    → [Matching Engine queries worker_reputation table]
    → [Geo filter + Availability filter]
    → [Score calculation uses reputation_score as input]
    → [Ranked results shown to contractor with reputation badges]
```

The `worker_reputation` table is the **single source of truth** for all reputation data used in matching. It's denormalized for speed — the matching engine never needs to join against ratings or job history tables during a live match query.

---

## 11. API Endpoints

### Worker Reputation API

```
GET    /api/reputation/:workerId          → Full reputation profile (auth required)
GET    /api/reputation/:workerId/summary  → Score + tier + stats (for matching)
GET    /api/reputation/:workerId/history  → Score history over time
GET    /api/reputation/:workerId/reviews  → Paginated reviews
POST   /api/reputation/:workerId/export   → Generate PDF/JSON export
PATCH  /api/reputation/:workerId/sharing  → Update share settings
GET    /api/w/:shareSlug                  → Public shared profile (no auth)
```

### Skills API

```
GET    /api/skills                        → List all skills in taxonomy
GET    /api/skills/:tradeCategory         → Skills for a specific trade
POST   /api/workers/:id/skills            → Worker declares a skill
DELETE /api/workers/:id/skills/:skillId   → Worker removes a declared skill
POST   /api/endorsements                  → Contractor endorses a skill (post-job)
```

### Certifications API

```
GET    /api/workers/:id/certifications              → List worker's certs
POST   /api/workers/:id/certifications              → Upload a certification
GET    /api/workers/:id/certifications/:certId      → Cert details + verification status
DELETE /api/workers/:id/certifications/:certId      → Remove a cert
```

### Internal / Admin API

```
POST   /api/internal/reputation/recalculate/:workerId  → Force recalculate
POST   /api/internal/reputation/batch-decay             → Run nightly decay job
GET    /api/internal/reputation/flags                   → Flagged accounts for review
POST   /api/internal/reputation/review/:flagId          → Resolve a flag
```

---

## 12. Background Jobs

### 12.1 Nightly Score Decay

**Schedule:** Daily at 02:00 AEST  
**What it does:**
1. Recalculates `rating_score` for all workers with time-decayed weights.
2. Updates `reputation_score` composite for all workers.
3. Compares with `score_30d_ago` to set `score_trend`.
4. Updates `reputation_tier` if score crossed a threshold.

**Performance:** Process in batches of 500 workers. Target: <5 minutes for 10,000 workers.

### 12.2 Weekly Score Snapshot

**Schedule:** Every Sunday at 03:00 AEST  
**What it does:**
1. Inserts a row into `reputation_score_history` for every active worker.
2. Calculates 30-day trend based on 4-week rolling comparison.

### 12.3 Certification Expiry Check

**Schedule:** Daily at 08:00 AEST  
**What it does:**
1. Finds certs expiring within 30 days → send notification to worker.
2. Finds expired certs → update `verification_status` to 'expired'.
3. Recalculates affected workers' skills scores.

### 12.4 Anti-Gaming Scan

**Schedule:** Daily at 04:00 AEST  
**What it does:**
1. Runs pattern detection queries (§8.2).
2. Flags suspicious accounts.
3. Alerts admin channel.

---

## 13. Implementation Priority

### Phase 1: Core Reputation (Week 1–2)
- [ ] `worker_reputation` table + triggers on job completion
- [ ] Score calculation function (all 5 sub-scores)
- [ ] Score recalculation on job complete / rating received
- [ ] Display reputation on worker profile screen
- [ ] Display reputation in matching results
- [ ] Feed reputation into matching algorithm
- [ ] "New to RateRight" badge for workers with <3 rated jobs

### Phase 2: Skills & Endorsements (Week 3)
- [ ] Skills taxonomy table + seed data
- [ ] Worker self-declares skills in profile
- [ ] Post-job endorsement flow for contractors
- [ ] Endorsement count tracking + Level 1 upgrade logic
- [ ] Skills display on profile and match cards

### Phase 3: Export & Sharing (Week 4)
- [ ] Public profile page (SSR)
- [ ] Share slug generation
- [ ] QR code generation
- [ ] Privacy settings UI
- [ ] PDF generation (trade resume)
- [ ] JSON export endpoint

### Phase 4: Certifications (Week 5)
- [ ] Cert upload flow
- [ ] OCR extraction (existing White Card flow extended)
- [ ] Verification status tracking
- [ ] Expiry notifications
- [ ] Cert display on profiles

### Phase 5: Anti-Gaming & Polish (Week 6)
- [ ] Nightly score decay job
- [ ] Weekly snapshot job
- [ ] Anti-gaming detection queries
- [ ] Admin dashboard for flagged accounts
- [ ] Score trend indicators
- [ ] Outlier rating dampening

---

## 14. Success Metrics

| Metric | Target | Measurement |
|--------|--------|-------------|
| Workers with score visible (3+ rated jobs) | 60% of active workers | % of workers who completed onboarding |
| Average reputation score | 65–75 | Mean across all scored workers |
| Profile export rate | 20% of scored workers | Workers who generated at least 1 PDF/link |
| Shared profile views | 500/month by month 6 | Page views on public profiles |
| Contractor filter usage | 30% of job posts | Jobs where contractor set min reputation |
| Rating completion rate | >70% | Ratings submitted / ratable jobs |
| Score-based matching lift | +15% hire-through rate | A/B test: matches with vs without reputation data |

---

## 15. Open Questions for Michael

1. **First-Job Guarantee:** Should we refund the $50 if a new worker (0 history) no-shows? Reduces contractor risk but costs us revenue. Cap at 1/month per contractor?

2. **Score Visibility:** Should the numerical score (e.g., 82/100) be visible to workers only, or also to contractors? Alternative: contractors see tier + star rating but not the composite score.

3. **Negative Score Floor:** Can a worker's score go below 20? Or do we just deactivate accounts at some threshold?

4. **Data Portability Regulations:** Do we need to comply with any specific Australian data portability rules for the JSON export? CDR doesn't apply to us, but good to check.

5. **Blockchain Anchoring:** The spec mentions record hashes for tamper detection. Do we want to actually anchor these on-chain (e.g., Ethereum/Polygon) for true verifiability, or is a simple hash + database record sufficient for now?

---

*This spec builds on top of the existing `ratings-system-spec.md` and `spec-instant-matching.md`. The reputation system is the **connective tissue** between those two systems — it takes rating inputs and produces matching outputs, with the worker's portable profile as the valuable byproduct.*
