Skip to content

VCRI Service

The Verified Carbon Reduction Infrastructure (VCRI) service manages the full lifecycle of carbon reduction verification for SMEs. Built as the Nigeria MVP, VCRI provides a four-step pipeline: project creation, baseline setting, periodic measurement, and certificate issuance with optional blockchain anchoring.


System Architecture

flowchart TB
    subgraph Frontend["Frontend (Next.js)"]
        UI[VCRI Dashboard<br/>page.tsx]
        Hooks[useVcri Hooks<br/>React Query]
    end

    subgraph API["API Layer (FastAPI)"]
        Router[/vcri Router<br/>6 endpoints/]
    end

    subgraph Services["Service Layer"]
        VS[VcriService<br/>Business Logic]
        AS[VcriAnchorService<br/>Blockchain Anchoring]
        CS[CompanyService<br/>Company Lookup]
    end

    subgraph Database["PostgreSQL"]
        VP[(vcri_projects)]
        VM[(vcri_measurements)]
        VC[(vcri_certificates)]
        CO[(companies)]
    end

    subgraph Blockchain["External (Optional)"]
        HCS[Hedera Consensus Service<br/>HCS Topic]
    end

    UI --> Hooks
    Hooks --> Router
    Router --> VS
    Router --> CS
    VS --> VP
    VS --> VM
    VS --> VC
    VS --> AS
    CS --> CO
    AS -->|Best Effort| HCS

VCRI Lifecycle

The VCRI verification lifecycle follows a strict four-step sequence. Each step builds on the previous, ensuring data integrity throughout the process.

flowchart LR
    A["1. Create Project"] --> B["2. Set Baseline"]
    B --> C["3. Record Measurement"]
    C --> D["4. Issue Certificate"]
    D --> C

    A -.- A1["Links project to company<br/>Status: draft"]
    B -.- B1["Sets reference emissions<br/>Status: baseline_set"]
    C -.- C1["Records actual emissions<br/>for a time period"]
    D -.- D1["Calculates reduction<br/>SHA-256 hash + anchor"]

Step 1: Create Project

A VCRI project represents a specific carbon reduction intervention linked to a single SME company. Each project has a methodology identifier that defines how reductions are calculated and verified.

Source: backend/app/services/vcri_service.py:create_vcri_project()

  • Creates a VcriProject record with status="draft"
  • Validates company exists and belongs to the current tenant
  • Accepts an optional methodology (default: VCRI-NG-EE-001)

Step 2: Set Baseline

The baseline establishes the reference emissions level against which future reductions are measured. Baselines include Scope 1, 2, and optionally Scope 3 emissions in tonnes of CO2 equivalent (tCO2e).

Source: backend/app/services/vcri_service.py:upsert_vcri_baseline()

  • Updates project with baseline year, scope emissions, and evidence
  • Sets baseline_set_at timestamp
  • Transitions project status to baseline_set
  • Idempotent: calling again overwrites previous baseline

Step 3: Record Measurement

Measurements represent actual emissions during a specific time period. Multiple measurements can be recorded per project (e.g., quarterly or semi-annually).

Source: backend/app/services/vcri_service.py:create_vcri_measurement()

  • Creates a VcriMeasurement record with period dates and scope emissions
  • Evidence metadata (meter readings, utility bills) is stored as JSONB
  • total_tco2e is computed as a property: scope1 + scope2 + scope3

Step 4: Issue Certificate

Certificate issuance is the core verification step. It computes the reduction, generates a deterministic hash, and optionally anchors it on the blockchain.

Source: backend/app/services/vcri_service.py:issue_vcri_certificate()

flowchart TD
    Start[Issue Certificate Request] --> Check1{Existing certificate<br/>for this measurement?}
    Check1 -->|Yes| Return[Return existing cert]
    Check1 -->|No| Check2{Baseline set?}
    Check2 -->|No| Error1[ValueError: Baseline not set]
    Check2 -->|Yes| Calc[Calculate reduction<br/>baseline_total - measurement_total]
    Calc --> Check3{Reduction > 0?}
    Check3 -->|No| Error2[ValueError: No reduction achieved]
    Check3 -->|Yes| Hash[Build canonical JSON payload]
    Hash --> SHA[SHA-256 hash of payload]
    SHA --> Insert[Insert VcriCertificate]
    Insert --> Anchor[Call AnchorService]
    Anchor --> ACheck{Anchor result?}
    ACheck -->|submitted| Update[Update anchor_tx_id,<br/>anchor_network]
    ACheck -->|skipped/failed| Skip[Continue without anchor]
    Update --> Done[Return certificate]
    Skip --> Done

Certificate Hash Generation

The certificate hash is a deterministic SHA-256 digest of a canonical JSON payload. Determinism is achieved through:

  1. Sorted keys: json.dumps(payload, sort_keys=True)
  2. Compact separators: separators=(",", ":")
  3. UTF-8 encoding: ensure_ascii=False

This ensures the same inputs always produce the same hash, enabling independent verification.

Payload Structure

payload = {
    "vcri_version": "mvp-1",
    "project_id": str(project.id),
    "company_id": str(project.company_id),
    "methodology": project.methodology,
    "baseline": {
        "year": project.baseline_year,
        "scope1_tco2e": float(project.baseline_scope1_tco2e or 0),
        "scope2_tco2e": float(project.baseline_scope2_tco2e or 0),
        "scope3_tco2e": float(scope3) if scope3 is not None else None,
        "total_tco2e": float(baseline_total),
    },
    "measurement": {
        "id": str(measurement.id),
        "period_start": measurement.period_start.isoformat(),
        "period_end": measurement.period_end.isoformat(),
        "scope1_tco2e": float(measurement.scope1_tco2e),
        "scope2_tco2e": float(measurement.scope2_tco2e),
        "scope3_tco2e": float(scope3) if scope3 is not None else None,
        "total_tco2e": float(measurement.total_tco2e),
    },
    "reduction_tco2e": float(reduction),
    "verified_at": verified_at.isoformat(),
}

Blockchain Anchoring

The VcriAnchorService provides tamper-evident verification by anchoring certificate hashes to an external ledger. The architecture supports multiple providers, with Hedera Consensus Service (HCS) as the first implementation.

Anchor Service Architecture

flowchart TB
    subgraph AnchorService["VcriAnchorService"]
        Entry[anchor_vcri_certificate_hash]
        Entry --> ProvCheck{Provider?}
        ProvCheck -->|none/off| Skip[AnchorResult<br/>status=skipped]
        ProvCheck -->|hedera_hcs| Build[_build_hcs_message]
        ProvCheck -->|unsupported| Fail[AnchorResult<br/>status=failed]
        Build --> Submit["_submit_hcs_message_sync<br/>(via asyncio.to_thread)"]
    end

    subgraph HederaSDK["Hedera SDK"]
        Client[Client.for_testnet/mainnet]
        SetOp[client.set_operator]
        Txn[TopicMessageSubmitTransaction]
        Exec[txn.execute]
    end

    Submit --> Client
    Client --> SetOp
    SetOp --> Txn
    Txn --> Exec
    Exec --> Result[AnchorResult<br/>status=submitted<br/>tx_id, anchored_at]

Provider Configuration

Provider Value Behavior
none / off Default Anchoring skipped, certificate still issued
hedera_hcs Production Submits hash to Hedera Consensus Service topic
Other Returns failed with unsupported provider error

Privacy-Preserving Design

Only metadata is anchored on-chain. The HCS message contains:

  • schema version identifier
  • certificate_hash (SHA-256 of the full payload)
  • project_id and company_id (UUIDs, no PII)
  • methodology identifier
  • verified_at timestamp

Raw emissions data, evidence documents, and company details are never sent to the blockchain.

Hedera SDK Integration

The Hedera SDK is an optional dependency. The anchor service uses a compatibility layer that supports both Python-style (from_string, set_topic_id) and Java-style (fromString, setTopicId) method names, making it resilient to SDK version changes.

The synchronous SDK call is wrapped in asyncio.to_thread() to avoid blocking the async event loop.

AnchorResult Dataclass

@dataclass(frozen=True)
class AnchorResult:
    provider: str          # "hedera_hcs", "none"
    network: str | None    # "testnet", "mainnet"
    tx_id: str | None      # Hedera transaction ID
    anchored_at: datetime | None
    status: str            # "submitted", "skipped", "failed"
    error: str | None = None

Database Models

VcriProject

Table: vcri_projects

Column Type Constraints Description
id UUID PK Auto-generated
tenant_id UUID NOT NULL, indexed Tenant isolation
company_id UUID FK → companies.id, CASCADE Parent company
name VARCHAR(255) NOT NULL Project name
description TEXT Project description
methodology VARCHAR(100) NOT NULL, default VCRI-NG-EE-001 Verification methodology
status VARCHAR(50) NOT NULL, default draft draft, baseline_set, active, verified
baseline_year INTEGER Reference year for baseline
baseline_scope1_tco2e NUMERIC(15,4) Scope 1 baseline in tCO2e
baseline_scope2_tco2e NUMERIC(15,4) Scope 2 baseline in tCO2e
baseline_scope3_tco2e NUMERIC(15,4) Scope 3 baseline in tCO2e
baseline_evidence JSONB NOT NULL, default {} Supporting evidence metadata
baseline_set_at TIMESTAMPTZ When baseline was set/updated

Computed Property: baseline_total_tco2e — sum of scope 1 + 2 + 3 (returns None if no baseline set)

VcriMeasurement

Table: vcri_measurements

Column Type Constraints Description
id UUID PK Auto-generated
tenant_id UUID NOT NULL, indexed Tenant isolation
project_id UUID FK → vcri_projects.id, CASCADE Parent project
period_start DATE NOT NULL Measurement period start
period_end DATE NOT NULL Measurement period end
scope1_tco2e NUMERIC(15,4) NOT NULL Scope 1 in tCO2e
scope2_tco2e NUMERIC(15,4) NOT NULL Scope 2 in tCO2e
scope3_tco2e NUMERIC(15,4) Scope 3 in tCO2e (optional)
evidence JSONB NOT NULL, default {} Supporting evidence metadata

Computed Property: total_tco2e — sum of scope 1 + 2 + 3

VcriCertificate

Table: vcri_certificates

Column Type Constraints Description
id UUID PK Auto-generated
tenant_id UUID NOT NULL, indexed Tenant isolation
project_id UUID FK → vcri_projects.id, CASCADE Parent project
measurement_id UUID FK → vcri_measurements.id, UNIQUE Certified measurement
reduction_tco2e NUMERIC(15,4) NOT NULL Verified reduction amount
certificate_hash VARCHAR(64) NOT NULL, indexed SHA-256 hash
payload JSONB NOT NULL Full canonical payload
verified_at TIMESTAMPTZ NOT NULL Verification timestamp
anchor_network VARCHAR(50) Blockchain network name
anchor_tx_id VARCHAR(255) Blockchain transaction ID
anchor_timestamp TIMESTAMPTZ On-chain timestamp

Unique Constraint: (tenant_id, measurement_id) — one certificate per measurement per tenant.


Frontend Integration

Dashboard Page

Source: frontend/src/app/dashboard/vcri/page.tsx

The VCRI dashboard provides:

  • Project listing with status indicators
  • Baseline input forms with scope-by-scope entry
  • Measurement recording with period selection
  • Certificate viewing with hash and anchor details

React Query Hooks

Source: frontend/src/lib/api/hooks/useVcri.ts

Hook Method Endpoint Description
useVcriProjects GET /vcri/projects List projects (paginated)
useVcriProject GET /vcri/projects/{id} Single project details
useCreateVcriProject POST /vcri/projects Create new project
useSetBaseline POST /vcri/projects/{id}/baseline Set/update baseline
useCreateMeasurement POST /vcri/projects/{id}/measurements Record measurement
useIssueCertificate POST /vcri/projects/{id}/certificates Issue certificate

Source Files

File Lines Description
backend/app/api/v1/vcri.py 152 API route handlers (6 endpoints)
backend/app/services/vcri_service.py 238 Business logic, hash generation, certificate issuance
backend/app/services/vcri_anchor_service.py 170 Hedera HCS blockchain anchoring
backend/app/models/database/vcri_project.py 90 SQLAlchemy project model
backend/app/models/database/vcri_measurement.py 54 SQLAlchemy measurement model
backend/app/models/database/vcri_certificate.py 69 SQLAlchemy certificate model
backend/app/models/schemas/vcri.py 169 Pydantic request/response schemas
frontend/src/app/dashboard/vcri/page.tsx 466 VCRI dashboard page
frontend/src/lib/api/hooks/useVcri.ts 95 React Query hooks