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
VcriProjectrecord withstatus="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_attimestamp - 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
VcriMeasurementrecord with period dates and scope emissions - Evidence metadata (meter readings, utility bills) is stored as JSONB
total_tco2eis 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:
- Sorted keys:
json.dumps(payload, sort_keys=True) - Compact separators:
separators=(",", ":") - 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:
schemaversion identifiercertificate_hash(SHA-256 of the full payload)project_idandcompany_id(UUIDs, no PII)methodologyidentifierverified_attimestamp
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 |