NVTS™ Enterprise Certification Platform — Production Architecture & Implementation Blueprint
Organization: Kenya Coffee School Framework: Natural Value Traceability System (NVTS™) Founder: Alfred Gitau Mwaura Design System Standards: Modern, Enterprise-Grade, ISO/IEC 17065-Inspired, Material-Influenced, Fully Accessible (WCAG 2.1 AA)
1. Executive System Architecture
The Natural Value Traceability System (NVTS™) is engineered as a zero-trust, multi-tenant digital traceability, sustainability scoring, and certification platform. Designed primarily for agricultural value chains—starting with high-grade specialty coffee—it supports strict chain-of-custody tracking, multi-pillar audit workflows, and immutable product passport verifications.
+--------------------------------------------------------------------------------------------------+
| CLIENT / UI LAYER (Vite + React 18) |
| +---------------------+ +--------------------+ +-----------------------+ +-----------------+ |
| | Applicant Portal | | Auditor Portal | | Council / Admin Panel | | Public Passport | |
| +---------------------+ +--------------------+ +-----------------------+ +-----------------+ |
| | | | | |
| +--------------------------------------------------------------------------------------------+ |
| | React Router v6 + TanStack Query v5 + React Hook Form + Zod | |
| +--------------------------------------------------------------------------------------------+ |
| | |
| REST / Realtime Engine |
+----------------------------------------------|---------------------------------------------------+
|
+----------------------------------------------v---------------------------------------------------+
| SUPABASE BACKEND SERVICES |
| +-----------------------+ +-----------------------+ +--------------------------------+ |
| | Supabase Auth (JWT) | | PostgreSQL + PostGIS | | Storage Buckets (Audit Docs) | |
| +-----------------------+ +-----------------------+ +--------------------------------+ |
| | | | |
| +--------------+--------------+---------------------------------+ |
| | |
| +-------------------------------+ |
| | Row-Level Security (RLS) | |
| | Database Triggers & Webhooks | |
| | Edge Functions (Audit Calc) | |
| +-------------------------------+ |
+--------------------------------------------------------------------------------------------------+
2. PostgreSQL Relational Schema & RLS Policies
Execute the following SQL script directly within the Supabase SQL Editor to establish the database structure, triggers, security policies, and standard seed data.
-- Enable necessary extensions
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "postgis";
-- -----------------------------------------------------------------------------
-- ENUMS
-- -----------------------------------------------------------------------------
CREATE TYPE user_role AS ENUM (
'super_admin',
'certification_manager',
'lead_auditor',
'auditor',
'technical_reviewer',
'certification_council',
'applicant',
'producer',
'public_user'
);
CREATE TYPE cert_level AS ENUM (
'Diamond',
'Platinum',
'Gold',
'Silver',
'Bronze',
'Improvement Required'
);
CREATE TYPE app_status AS ENUM (
'Draft',
'Submitted',
'Under_Review',
'Audit_Scheduled',
'Audit_In_Progress',
'Council_Review',
'Approved',
'Rejected',
'Suspended'
);
CREATE TYPE pillar_category AS ENUM ('Nature', 'People', 'Culture', 'Quality', 'Trade');
-- -----------------------------------------------------------------------------
-- 1. USERS & PROFILES
-- -----------------------------------------------------------------------------
CREATE TABLE profiles (
id UUID PRIMARY KEY REFERENCES auth.users(id) ON DELETE CASCADE,
email TEXT NOT NULL UNIQUE,
full_name TEXT NOT NULL,
phone TEXT,
role user_role NOT NULL DEFAULT 'applicant',
organization_id UUID,
avatar_url TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- -----------------------------------------------------------------------------
-- 2. ORGANIZATIONS
-- -----------------------------------------------------------------------------
CREATE TABLE organizations (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
name TEXT NOT NULL,
registration_number TEXT UNIQUE,
tax_id TEXT,
country TEXT NOT NULL DEFAULT 'Kenya',
county_region TEXT NOT NULL,
address TEXT,
primary_contact_name TEXT NOT NULL,
primary_contact_email TEXT NOT NULL,
primary_contact_phone TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
ALTER TABLE profiles
ADD CONSTRAINT fk_profiles_organization
FOREIGN KEY (organization_id) REFERENCES organizations(id) ON DELETE SET NULL;
-- -----------------------------------------------------------------------------
-- 3. FARMS & LAND PARCELS
-- -----------------------------------------------------------------------------
CREATE TABLE farms (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
name TEXT NOT NULL,
location_name TEXT NOT NULL,
gps_latitude NUMERIC(10,8) NOT NULL,
gps_longitude NUMERIC(11,8) NOT NULL,
total_area_hectares NUMERIC(8,2) NOT NULL,
altitude_masl INT NOT NULL, -- Meters Above Sea Level
soil_type TEXT,
water_source TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- -----------------------------------------------------------------------------
-- 4. APPLICATIONS
-- -----------------------------------------------------------------------------
CREATE TABLE applications (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
application_number TEXT UNIQUE NOT NULL,
organization_id UUID NOT NULL REFERENCES organizations(id) ON DELETE CASCADE,
farm_id UUID NOT NULL REFERENCES farms(id) ON DELETE CASCADE,
status app_status NOT NULL DEFAULT 'Draft',
target_scope TEXT NOT NULL DEFAULT 'Coffee Production & Processing',
submitted_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- -----------------------------------------------------------------------------
-- 5. AUDITS & SCORING
-- -----------------------------------------------------------------------------
CREATE TABLE audits (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
application_id UUID NOT NULL REFERENCES applications(id) ON DELETE CASCADE,
lead_auditor_id UUID REFERENCES profiles(id),
scheduled_date DATE NOT NULL,
completion_date DATE,
score_nature NUMERIC(5,2) DEFAULT 0.00,
score_people NUMERIC(5,2) DEFAULT 0.00,
score_culture NUMERIC(5,2) DEFAULT 0.00,
score_quality NUMERIC(5,2) DEFAULT 0.00,
score_trade NUMERIC(5,2) DEFAULT 0.00,
overall_score NUMERIC(5,2) DEFAULT 0.00,
recommended_level cert_level,
auditor_notes TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE audit_items (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
audit_id UUID NOT NULL REFERENCES audits(id) ON DELETE CASCADE,
pillar pillar_category NOT NULL,
indicator_code TEXT NOT NULL,
indicator_title TEXT NOT NULL,
max_score INT DEFAULT 5,
score_given INT CHECK (score_given >= 0 AND score_given <= 5),
evidence_summary TEXT,
non_conformity_level TEXT CHECK (non_conformity_level IN ('None', 'Minor', 'Major', 'Critical')),
corrective_action_required TEXT,
updated_at TIMESTAMPTZ DEFAULT NOW()
);
-- -----------------------------------------------------------------------------
-- 6. CERTIFICATES
-- -----------------------------------------------------------------------------
CREATE TABLE certificates (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
certificate_number TEXT UNIQUE NOT NULL,
application_id UUID NOT NULL REFERENCES applications(id),
audit_id UUID NOT NULL REFERENCES audits(id),
organization_id UUID NOT NULL REFERENCES organizations(id),
certification_level cert_level NOT NULL,
overall_score NUMERIC(5,2) NOT NULL,
issue_date DATE NOT NULL,
expiry_date DATE NOT NULL,
is_active BOOLEAN DEFAULT TRUE,
digital_signature_hash TEXT NOT NULL,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- -----------------------------------------------------------------------------
-- 7. PRODUCTS & LOT PASS PORTS
-- -----------------------------------------------------------------------------
CREATE TABLE product_passports (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
lot_number TEXT UNIQUE NOT NULL,
certificate_id UUID NOT NULL REFERENCES certificates(id),
farm_id UUID NOT NULL REFERENCES farms(id),
crop_type TEXT NOT NULL DEFAULT 'Coffee',
variety TEXT NOT NULL, -- e.g., SL28, SL34, Ruiru 11, Batian
harvest_year INT NOT NULL,
processing_method TEXT NOT NULL, -- Wash, Natural, Honey, Anaerobic
quality_grade TEXT NOT NULL, -- AA, AB, PB, C
cup_score NUMERIC(4,2), -- Specialty Coffee SCA Score
moisture_content_pct NUMERIC(4,2),
water_activity NUMERIC(4,3),
density_g_l INT,
initial_brix NUMERIC(4,1),
dry_ferment_hours NUMERIC(4,1),
soak_duration_hours NUMERIC(4,1),
soak_water_temp_c NUMERIC(4,1),
terminal_ph NUMERIC(3,2),
qr_code_url TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);
-- -----------------------------------------------------------------------------
-- 8. CHAIN OF CUSTODY (EVENT LOG)
-- -----------------------------------------------------------------------------
CREATE TABLE chain_of_custody_events (
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
product_passport_id UUID NOT NULL REFERENCES product_passports(id) ON DELETE CASCADE,
stage TEXT NOT NULL, -- Seed, Nursery, Farm, Harvest, Processing, Warehouse, Export, Roaster, Retail, Consumer
actor_name TEXT NOT NULL,
facility_location TEXT NOT NULL,
timestamp TIMESTAMPTZ NOT NULL DEFAULT NOW(),
verification_hash TEXT NOT NULL,
notes TEXT
);
-- -----------------------------------------------------------------------------
-- ROW-LEVEL SECURITY POLICIES (RLS)
-- -----------------------------------------------------------------------------
ALTER TABLE profiles ENABLE ROW LEVEL SECURITY;
ALTER TABLE organizations ENABLE ROW LEVEL SECURITY;
ALTER TABLE applications ENABLE ROW LEVEL SECURITY;
ALTER TABLE audits ENABLE ROW LEVEL SECURITY;
ALTER TABLE certificates ENABLE ROW LEVEL SECURITY;
ALTER TABLE product_passports ENABLE ROW LEVEL SECURITY;
ALTER TABLE chain_of_custody_events ENABLE ROW LEVEL SECURITY;
-- Profiles: Users can read their own profile; Admins read all.
CREATE POLICY "Users read own profile" ON profiles
FOR SELECT USING (auth.uid() = id);
CREATE POLICY "Admins manage all profiles" ON profiles
FOR ALL USING (
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role IN ('super_admin', 'certification_manager'))
);
-- Public Read Policies for Certificates & Passports
CREATE POLICY "Public certificate verification" ON certificates
FOR SELECT USING (is_active = true);
CREATE POLICY "Public product passport view" ON product_passports
FOR SELECT USING (true);
CREATE POLICY "Public chain of custody view" ON chain_of_custody_events
FOR SELECT USING (true);
-- Applications: Applicants access their organization's data
CREATE POLICY "Applicants access own org applications" ON applications
FOR ALL USING (
organization_id IN (SELECT organization_id FROM profiles WHERE id = auth.uid())
OR
EXISTS (SELECT 1 FROM profiles WHERE id = auth.uid() AND role IN ('super_admin', 'certification_manager', 'lead_auditor', 'auditor', 'certification_council'))
);
-- -----------------------------------------------------------------------------
-- AUTOMATIC SCORING & CERTIFICATION LEVEL TRIGGER
-- -----------------------------------------------------------------------------
CREATE OR REPLACE FUNCTION calculate_audit_scores()
RETURNS TRIGGER AS $$
DECLARE
v_nat NUMERIC;
v_peo NUMERIC;
v_cul NUMERIC;
v_qua NUMERIC;
v_tra NUMERIC;
v_tot NUMERIC;
v_lvl cert_level;
BEGIN
-- Compute averages per pillar normalized to percentage (0-100)
SELECT
COALESCE(AVG(CASE WHEN pillar = 'Nature' THEN (score_given::numeric / max_score) * 100 END), 0),
COALESCE(AVG(CASE WHEN pillar = 'People' THEN (score_given::numeric / max_score) * 100 END), 0),
COALESCE(AVG(CASE WHEN pillar = 'Culture' THEN (score_given::numeric / max_score) * 100 END), 0),
COALESCE(AVG(CASE WHEN pillar = 'Quality' THEN (score_given::numeric / max_score) * 100 END), 0),
COALESCE(AVG(CASE WHEN pillar = 'Trade' THEN (score_given::numeric / max_score) * 100 END), 0)
INTO v_nat, v_peo, v_cul, v_qua, v_tra
FROM audit_items
WHERE audit_id = NEW.audit_id;
-- Weighted Total (Equal weighting across 5 pillars: 20% each)
v_tot := (v_nat + v_peo + v_cul + v_qua + v_tra) / 5.0;
-- Assign Tier Level based on standard benchmarks
IF v_tot >= 95.0 THEN v_lvl := 'Diamond';
ELSIF v_tot >= 90.0 THEN v_lvl := 'Platinum';
ELSIF v_tot >= 85.0 THEN v_lvl := 'Gold';
ELSIF v_tot >= 80.0 THEN v_lvl := 'Silver';
ELSIF v_tot >= 70.0 THEN v_lvl := 'Bronze';
ELSE v_lvl := 'Improvement Required';
END IF;
UPDATE audits
SET
score_nature = ROUND(v_nat, 2),
score_people = ROUND(v_peo, 2),
score_culture = ROUND(v_cul, 2),
score_quality = ROUND(v_qua, 2),
score_trade = ROUND(v_tra, 2),
overall_score = ROUND(v_tot, 2),
recommended_level = v_lvl
WHERE id = NEW.audit_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
CREATE TRIGGER trg_recalculate_audit_score
AFTER INSERT OR UPDATE ON audit_items
FOR EACH ROW EXECUTE FUNCTION calculate_audit_scores();
3. Five-Pillar Audit Engine Logic & Mathematical Model
The NVTS™ Standard evaluates producers across five core pillars. Every indicator within a pillar is scored from 0 to 5 based on objective verifiers.
Mathematical Formulation
- Indicator Score Normalization:
\text{Score}_{i,p} = \frac{S_{i,p}}{S_{i,p}^{\max}} \times 100Where S_{i,p} is the raw score given for indicator i in pillar p, and S_{i,p}^{\max} = 5. - Pillar Score (\text{Pillar}_p):
\text{Pillar}_p = \frac{1}{N_p} \sum_{i=1}^{N_p} \left( \frac{S_{i,p}}{5} \times 100 \right)Where N_p is the total number of evaluated indicators in pillar p. - Composite NVTS™ Score (\text{Score}_{\text{NVTS}}):
\text{Score}_{\text{NVTS}} = \sum_{p \in \{\text{Nature, People, Culture, Quality, Trade}\}} \left( w_p \times \text{Pillar}_p \right)With standard equal weighting w_p = 0.20 for each pillar.
Certification Tier Benchmarks
| NVTS™ Score Threshold | Tier Level | Designator | Action Required / Status |
|---|---|---|---|
| 95.00% – 100.00% | Diamond | ★★★★★ | Full Certification (3-Year Validity, Annual Surveillance) |
| 90.00% – 94.99% | Platinum | ★★★★☆ | Full Certification (3-Year Validity, Annual Surveillance) |
| 85.00% – 89.99% | Gold | ★★★★ | Standard Certification (2-Year Validity) |
| 80.00% – 84.99% | Silver | ★★★ | Conditional Certification (1-Year Minor CAP Execution) |
| 70.00% – 79.99% | Bronze | ★★ | Provisional Certification (Mandatory Re-audit in 6 Months) |
| < 70.00% | Improvement Required | — | Certification Denied (Root-Cause Remediation Plan) |
4. Frontend Implementation Architecture
TypeScript Types & Interfaces (src/types/nvts.ts)
export type UserRole =
| 'super_admin'
| 'certification_manager'
| 'lead_auditor'
| 'auditor'
| 'technical_reviewer'
| 'certification_council'
| 'applicant'
| 'producer'
| 'public_user';
export type CertificationLevel =
| 'Diamond'
| 'Platinum'
| 'Gold'
| 'Silver'
| 'Bronze'
| 'Improvement Required';
export type PillarCategory = 'Nature' | 'People' | 'Culture' | 'Quality' | 'Trade';
export interface AuditIndicator {
id: string;
pillar: PillarCategory;
indicatorCode: string;
indicatorTitle: string;
maxScore: number;
scoreGiven: number;
evidenceSummary: string;
nonConformityLevel: 'None' | 'Minor' | 'Major' | 'Critical';
correctiveActionRequired?: string;
}
export interface ProductPassport {
lotNumber: string;
organizationName: string;
farmName: string;
altitudeMasl: number;
gpsCoordinates: { lat: number; lng: number };
cropVariety: string;
harvestYear: number;
processingMethod: string;
qualityGrade: string;
cupScore: number;
physicochemical: {
initialBrix: number;
dryFermentHours: number;
terminalPh: number;
soakDurationHours: number;
soakWaterTempC: number;
moistureContentPct: number;
waterActivity: number;
densityGL: number;
};
nvtsScore: number;
certLevel: CertificationLevel;
certificateNumber: string;
}
Key UI Component 1: Audit Engine Component (src/components/AuditEngine.tsx)
Below is the dynamic five-pillar audit scorecard with automated real-time calculation and validation.
import React, { useState } from 'react';
import { PillarCategory, AuditIndicator, CertificationLevel } from '../types/nvts';
const INITIAL_INDICATORS: AuditIndicator[] = [
// Nature
{ id: '1', pillar: 'Nature', indicatorCode: 'NAT-01', indicatorTitle: 'Shade Tree Canopy Cover (>= 40%) & Biodiversity Protection', maxScore: 5, scoreGiven: 4, evidenceSummary: 'Native trees present, canopy density measured at 42%.', nonConformityLevel: 'None' },
{ id: '2', pillar: 'Nature', indicatorCode: 'NAT-02', indicatorTitle: 'Water Resource Management & Pulping Effluent Treatment', maxScore: 5, scoreGiven: 5, evidenceSummary: 'Recirculating eco-pulper used; eco-wetland filtration active.', nonConformityLevel: 'None' },
// People
{ id: '3', pillar: 'People', indicatorCode: 'PEO-01', indicatorTitle: 'Fair Living Wage & Occupational Health and Safety Protocols', maxScore: 5, scoreGiven: 4, evidenceSummary: 'Workers paid 18% above minimum wage. PPE provided.', nonConformityLevel: 'None' },
// Culture
{ id: '4', pillar: 'Culture', indicatorCode: 'CUL-01', indicatorTitle: 'Heritage Variety Conservation & Traditional Husbandry Knowledge', maxScore: 5, scoreGiven: 5, evidenceSummary: 'Preservation of legacy SL28/SL34 plots with organic composting.', nonConformityLevel: 'None' },
// Quality
{ id: '5', pillar: 'Quality', indicatorCode: 'QUA-01', indicatorTitle: 'Fermentation Parameter Control (BRIX, Temp, pH)', maxScore: 5, scoreGiven: 4, evidenceSummary: 'Logbook verified: Dry ferment 16h, soak 33h at 17.5°C, pH 4.4.', nonConformityLevel: 'None' },
// Trade
{ id: '6', pillar: 'Trade', indicatorCode: 'TRA-01', indicatorTitle: 'Traceable Payment Distribution & Transparent Cost-Breakdown', maxScore: 5, scoreGiven: 5, evidenceSummary: 'Direct mobile payment ledger verified against lot deliveries.', nonConformityLevel: 'None' }
];
export const AuditEngine: React.FC = () => {
const [indicators, setIndicators] = useState<AuditIndicator[]>(INITIAL_INDICATORS);
const updateScore = (id: string, score: number) => {
setIndicators(prev => prev.map(ind => ind.id === id ? { ...ind, scoreGiven: score } : ind));
};
const calculatePillarScore = (pillar: PillarCategory): number => {
const items = indicators.filter(i => i.pillar === pillar);
if (items.length === 0) return 0;
const sum = items.reduce((acc, i) => acc + (i.scoreGiven / i.maxScore) * 100, 0);
return sum / items.length;
};
const scoreNature = calculatePillarScore('Nature');
const scorePeople = calculatePillarScore('People');
const scoreCulture = calculatePillarScore('Culture');
const scoreQuality = calculatePillarScore('Quality');
const scoreTrade = calculatePillarScore('Trade');
const overallScore = (scoreNature + scorePeople + scoreCulture + scoreQuality + scoreTrade) / 5;
const getTier = (score: number): { level: CertificationLevel; badgeStyle: string } => {
if (score >= 95) return { level: 'Diamond', badgeStyle: 'bg-blue-100 text-blue-800 border-blue-400' };
if (score >= 90) return { level: 'Platinum', badgeStyle: 'bg-purple-100 text-purple-800 border-purple-400' };
if (score >= 85) return { level: 'Gold', badgeStyle: 'bg-yellow-100 text-yellow-800 border-yellow-500' };
if (score >= 80) return { level: 'Silver', badgeStyle: 'bg-slate-200 text-slate-800 border-slate-400' };
if (score >= 70) return { level: 'Bronze', badgeStyle: 'bg-amber-100 text-amber-800 border-amber-600' };
return { level: 'Improvement Required', badgeStyle: 'bg-red-100 text-red-800 border-red-400' };
};
const currentTier = getTier(overallScore);
return (
<div className="max-w-6xl mx-auto p-6 bg-white shadow-xl rounded-2xl border border-gray-100">
<div className="flex flex-col md:flex-row justify-between items-start md:items-center pb-6 border-b border-gray-200 gap-4">
<div>
<h2 className="text-2xl font-bold text-[#0B6B3A]">NVTS™ Digital Scorecard Engine</h2>
<p className="text-sm text-gray-500">ISO/IEC 17065 Compliant Sustainability Assessment</p>
</div>
<div className="flex items-center gap-4 bg-gray-50 p-4 rounded-xl border border-gray-200">
<div>
<div className="text-xs text-gray-500 font-semibold uppercase">Composite Score</div>
<div className="text-3xl font-extrabold text-[#333333]">{overallScore.toFixed(2)}%</div>
</div>
<div className={`px-4 py-2 rounded-lg border font-bold text-sm ${currentTier.badgeStyle}`}>
{currentTier.level}
</div>
</div>
</div>
{/* Pillar Breakdown Grid */}
<div className="grid grid-cols-2 md:grid-cols-5 gap-3 my-6">
{[
{ name: 'Nature', score: scoreNature, color: 'bg-emerald-500' },
{ name: 'People', score: scorePeople, color: 'bg-blue-500' },
{ name: 'Culture', score: scoreCulture, color: 'bg-amber-500' },
{ name: 'Quality', score: scoreQuality, color: 'bg-[#6F4E37]' },
{ name: 'Trade', score: scoreTrade, color: 'bg-[#C9A227]' }
].map(p => (
<div key={p.name} className="p-3 bg-gray-50 rounded-lg border border-gray-200">
<span className="text-xs font-semibold text-gray-600 uppercase">{p.name}</span>
<div className="text-xl font-bold text-gray-900 mt-1">{p.score.toFixed(1)}%</div>
<div className="w-full bg-gray-200 h-1.5 rounded-full mt-2 overflow-hidden">
<div className={`${p.color} h-1.5`} style={{ width: `${p.score}%` }}></div>
</div>
</div>
))}
</div>
{/* Indicator Assessment Table */}
<div className="overflow-x-auto mt-6">
<table className="w-full text-left border-collapse">
<thead>
<tr className="bg-[#0B6B3A] text-white text-xs uppercase tracking-wider">
<th className="p-3 rounded-tl-lg">Code</th>
<th className="p-3">Pillar</th>
<th className="p-3">Indicator Title</th>
<th className="p-3 text-center">Score (0-5)</th>
<th className="p-3 rounded-tr-lg">Evidence / Auditor Summary</th>
</tr>
</thead>
<tbody className="divide-y divide-gray-200 text-sm">
{indicators.map(item => (
<tr key={item.id} className="hover:bg-gray-50">
<td className="p-3 font-mono font-bold text-gray-700">{item.indicatorCode}</td>
<td className="p-3">
<span className="px-2 py-1 text-xs rounded bg-gray-100 font-medium text-gray-700">
{item.pillar}
</span>
</td>
<td className="p-3 font-medium text-gray-900">{item.indicatorTitle}</td>
<td className="p-3 text-center">
<select
value={item.scoreGiven}
onChange={(e) => updateScore(item.id, parseInt(e.target.value))}
className="p-1 border rounded bg-white font-bold text-[#0B6B3A] focus:ring-2 focus:ring-[#0B6B3A]"
>
{[0, 1, 2, 3, 4, 5].map(val => (
<option key={val} value={val}>{val}</option>
))}
</select>
</td>
<td className="p-3 text-gray-600">{item.evidenceSummary}</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
);
};
Key UI Component 2: Digital Product Passport & Chain of Custody (src/components/ProductPassportView.tsx)
This component displays the complete batch details, physical processing metrics, and chain of custody tracking.
import React from 'react';
import { ProductPassport } from '../types/nvts';
const SAMPLE_PASSPORT: ProductPassport = {
lotNumber: 'NVTS-KE-2026-AA084',
organizationName: 'Nyeri Farmers Co-operative Union',
farmName: 'Kamunyaka Estate - Parcel B',
altitudeMasl: 1820,
gpsCoordinates: { lat: -0.4167, lng: 36.9500 },
cropVariety: 'SL28 & Batian Blend',
harvestYear: 2026,
processingMethod: 'Double Fermented Washed',
qualityGrade: 'AA (Screen 17/18)',
cupScore: 89.25,
physicochemical: {
initialBrix: 21.5,
dryFermentHours: 16.5,
terminalPh: 4.38,
soakDurationHours: 33.0,
soakWaterTempC: 17.5,
moistureContentPct: 11.3,
waterActivity: 0.54,
densityGL: 835
},
nvtsScore: 94.20,
certLevel: 'Platinum',
certificateNumber: 'CERT-NVTS-2026-8812'
};
const CHAIN_TIMELINE = [
{ stage: 'Seed & Nursery', date: '2021-04-12', actor: 'KCS Certified Seedbank', detail: 'SL28 Certified Seedling Planting' },
{ stage: 'Harvesting', date: '2025-12-10', actor: 'Kamunyaka Pickers Guild', detail: '100% Red Ripe Cherries (21.5° BRIX)' },
{ stage: 'Processing & Soak', date: '2025-12-12', actor: 'Kamunyaka Wet Mill', detail: 'Dry Ferment 16.5h -> 33h Cold Soak at 17.5°C' },
{ stage: 'Milling & Grading', date: '2026-01-20', actor: 'Central Kenya Dry Mill', detail: 'Graded Screen 17/18 (AA), Moisture 11.3%' },
{ stage: 'NVTS Audit & Verification', date: '2026-02-05', actor: 'Lead Auditor A. Gitau', detail: 'Platinum Rating Awarded (94.20%)' },
{ stage: 'Export Shipping', date: '2026-03-01', actor: 'Mombasa Logistics Hub', detail: 'Container Seal Verified - Hash #0x89f...a12' }
];
export const ProductPassportView: React.FC = () => {
const p = SAMPLE_PASSPORT;
return (
<div className="max-w-5xl mx-auto p-8 bg-slate-50 min-h-screen">
{/* Header Banner */}
<div className="bg-[#0B6B3A] text-white p-6 rounded-t-2xl flex flex-col md:flex-row justify-between items-start md:items-center gap-4">
<div>
<span className="text-xs uppercase tracking-widest text-[#C9A227] font-extrabold">NVTS™ Digital Product Passport</span>
<h1 className="text-3xl font-black mt-1">Lot #{p.lotNumber}</h1>
<p className="text-sm opacity-90">{p.organizationName} | {p.farmName}</p>
</div>
<div className="bg-white text-gray-900 px-6 py-3 rounded-xl border-2 border-[#C9A227] text-center">
<div className="text-xs font-bold text-gray-500 uppercase">NVTS Tier</div>
<div className="text-2xl font-black text-[#6F4E37]">{p.certLevel}</div>
<div className="text-xs font-semibold text-emerald-700">{p.nvtsScore}% Score</div>
</div>
</div>
{/* Main Grid */}
<div className="bg-white p-6 rounded-b-2xl shadow-lg border border-gray-200 grid grid-cols-1 md:grid-cols-3 gap-6">
{/* Physical & Origin Attributes */}
<div className="space-y-4 border-r border-gray-100 pr-4">
<h3 className="text-sm font-bold uppercase tracking-wider text-[#6F4E37] border-b pb-2">Origin & Terroir</h3>
<ul className="text-sm space-y-2">
<li><strong className="text-gray-600">Location:</strong> Nyeri, Kenya</li>
<li><strong className="text-gray-600">GPS:</strong> {p.gpsCoordinates.lat}, {p.gpsCoordinates.lng}</li>
<li><strong className="text-gray-600">Altitude:</strong> {p.altitudeMasl} MASL</li>
<li><strong className="text-gray-600">Variety:</strong> {p.cropVariety}</li>
<li><strong className="text-gray-600">Grade:</strong> {p.qualityGrade}</li>
<li><strong className="text-gray-600">Cup Score:</strong> <span className="font-bold text-[#0B6B3A]">{p.cupScore} / 100 SCA</span></li>
</ul>
</div>
{/* Harvest Processing Metrics */}
<div className="space-y-4 border-r border-gray-100 pr-4">
<h3 className="text-sm font-bold uppercase tracking-wider text-[#6F4E37] border-b pb-2">Physicochemical Profile</h3>
<ul className="text-sm space-y-2 font-mono">
<li><span className="text-gray-600">Initial BRIX:</span> <strong>{p.physicochemical.initialBrix}° Brix</strong></li>
<li><span className="text-gray-600">Dry Ferment:</span> <strong>{p.physicochemical.dryFermentHours} Hours</strong></li>
<li><span className="text-gray-600">Terminal pH:</span> <strong>{p.physicochemical.terminalPh}</strong></li>
<li><span className="text-gray-600">Soak Duration:</span> <strong>{p.physicochemical.soakDurationHours} Hours</strong></li>
<li><span className="text-gray-600">Soak Water Temp:</span> <strong>{p.physicochemical.soakWaterTempC}°C</strong></li>
<li><span className="text-gray-600">Moisture Content:</span> <strong>{p.physicochemical.moistureContentPct}%</strong></li>
<li><span className="text-gray-600">Water Activity ($a_w$):</span> <strong>{p.physicochemical.waterActivity}</strong></li>
<li><span className="text-gray-600">Density:</span> <strong>{p.physicochemical.densityGL} g/L</strong></li>
</ul>
</div>
{/* Certificate Reference & Verification */}
<div className="space-y-4 flex flex-col justify-between">
<div>
<h3 className="text-sm font-bold uppercase tracking-wider text-[#6F4E37] border-b pb-2">Verification</h3>
<div className="mt-3 p-3 bg-gray-50 rounded-lg border border-gray-200">
<p className="text-xs text-gray-500">Certificate Reference</p>
<p className="text-sm font-bold font-mono text-[#0B6B3A]">{p.certificateNumber}</p>
<div className="mt-2 text-xs text-emerald-700 font-semibold flex items-center gap-1">
<span className="w-2 h-2 rounded-full bg-emerald-500 inline-block"></span> Verified Authentic
</div>
</div>
</div>
<button className="w-full py-3 bg-[#0B6B3A] text-white font-bold rounded-xl hover:bg-emerald-800 transition-colors shadow-md">
Download Official Certificate (PDF)
</button>
</div>
</div>
{/* Chain of Custody Timeline */}
<div className="mt-8 bg-white p-6 rounded-2xl shadow-lg border border-gray-200">
<h3 className="text-lg font-bold text-[#333333] mb-6">Chain of Custody Ledger</h3>
<div className="relative border-l-2 border-[#0B6B3A] ml-4 space-y-6">
{CHAIN_TIMELINE.map((evt, idx) => (
<div key={idx} className="relative pl-6">
<span className="absolute -left-[9px] top-1 w-4 h-4 rounded-full bg-[#C9A227] border-2 border-white"></span>
<div className="flex justify-between items-baseline">
<h4 className="text-sm font-bold text-gray-900">{evt.stage}</h4>
<time className="text-xs font-mono text-gray-500">{evt.date}</time>
</div>
<p className="text-xs font-semibold text-[#0B6B3A]">{evt.actor}</p>
<p className="text-xs text-gray-600 mt-0.5">{evt.detail}</p>
</div>
))}
</div>
</div>
</div>
);
};
5. Security Architecture (RLS Policy Enforcement)
Data protection and multi-tenant access controls are enforced at the PostgreSQL level using Supabase Row-Level Security (RLS).
+-----------------------------------+
| INCOMING REST / GRAPHQL REQUEST|
+-----------------------------------+
|
v
+-----------------------------------+
| SUPABASE AUTH JWT VALIDATION |
| (Extracts user_id & role claim) |
+-----------------------------------+
|
v
+-----------------------------------+
| POSTGRES RLS EVALUATION |
+-----------------------------------+
font-mono / SQL
|
+---------------------------+---------------------------+
| |
v v
+--------------------+ +--------------------+
| ROLE = 'applicant' | | ROLE = 'auditor' |
| Filter: | | Filter: |
| org_id = | | assigned_auditor = |
| auth.jwt().org_id | | auth.jwt().user_id |
+--------------------+ +--------------------+
Security Directives
- Auditor Field Isolation: Auditors can read and update only
auditsandaudit_itemsassigned to them vialead_auditor_id. - Public Scope: Public users can query
certificatesandproduct_passportswhereis_active = true. Access to internal audit evidence and score breakdown logs is restricted. - Immutability of Issued Certificates: Database triggers prevent updating
certificatesrecords once issued. Revocations or updates require explicit administrative escalation.
6. Interactive Process Flow & Verification Engine
Below is the procedural lifecycle from initial registration to public QR certificate verification.
+---------------------------------------------------------------------------------------------------+
| APPLICATION PHASE |
| [Producer / Applicant] ---> Fills Organization, Farm, and Crop Details ---> Submits Application |
+---------------------------------------------------------------------------------------------------+
|
v
+---------------------------------------------------------------------------------------------------+
| AUDIT & ASSESSMENT PHASE |
| [Certification Manager] ---> Assigns Lead Auditor |
| [Lead Auditor] ---> On-site Inspection & Digital Audit Scoring (Nature, People, Culture, |
| Quality, Trade) |
| [Database Engine] ---> Trigger Executes: Calculates Pillar Averages & Recommends Level |
+---------------------------------------------------------------------------------------------------+
|
v
+---------------------------------------------------------------------------------------------------+
| COUNCIL REVIEW & ISSUANCE PHASE |
| [Technical Reviewer] ---> Conducts Compliance Check & Verifies Evidence |
| [Certification Council]---> Formally Approves Certification |
| [System Generator] ---> Generates Certificate, SHA-256 Hash, & Product Passport QR Code |
+---------------------------------------------------------------------------------------------------+
|
v
+---------------------------------------------------------------------------------------------------+
| TRACEABILITY & VERIFICATION PHASE |
| [Consumer / Buyer] ---> Scans QR Code on Coffee Packaging |
| [Public Portal] ---> Displays Lot Origin, SCA Cup Score, Physicochemical Log, & CoC Ledger |
+---------------------------------------------------------------------------------------------------+
7. Verification & Deployment Roadmap
To move this system to production, follow these key setup steps:
+------------------------------------------------+
| STEP 1: DATABASE DEPLOYMENT |
| Run SQL Schema & Triggers on Supabase Postgres|
+------------------------------------------------+
|
v
+------------------------------------------------+
| STEP 2: ENVIRONMENT CONFIGURATION |
| Configure .env with Supabase API Keys |
+------------------------------------------------+
|
v
+------------------------------------------------+
| STEP 3: INTEGRATION TESTING |
| Execute Test Suite (Audit Engine & Scoring) |
+------------------------------------------------+
|
v
+------------------------------------------------+
| STEP 4: PRODUCTION DEPLOYMENT |
| Deploy Frontend on Vercel / Netlify |
+------------------------------------------------+
