Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions backend/api/v1/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,4 @@
api_v1.register_blueprint(circular_bp, url_prefix='/circular')
api_v1.register_blueprint(biosecurity_bp, url_prefix='/biosecurity')
api_v1.register_blueprint(vaults_bp, url_prefix='/vaults')
api_v1.register_blueprint(carbon_bp, url_prefix='/carbon')
223 changes: 223 additions & 0 deletions backend/api/v1/carbon.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,223 @@
"""
Carbon & ESG Marketplace API — L3-1632
"""
from flask import Blueprint, request, jsonify
from auth_utils import token_required
from backend.extensions import db
from backend.services.carbon_sequestration_engine import (
CarbonSequestrationEngine, CARBON_CREDIT_PRICE_USD
)
from backend.models.soil_health import RegenerativeFarmingLog, CarbonMintEvent
from backend.models.sustainability import ESGMarketListing, SustainabilityScore
from backend.models.farm import Farm
import logging

logger = logging.getLogger(__name__)
carbon_bp = Blueprint('carbon', __name__)


# ─── 1. Log a Regenerative Farming Practice ───────────────────────────────────
@carbon_bp.route('/practices', methods=['POST'])
@token_required
def log_practice(current_user):
"""
Records a regenerative farming practice entry.
Requires: farm_id, practice_type, area_hectares
Optional: soil_organic_carbon_percent, bulk_density_gcm3, sampling_depth_cm
"""
data = request.get_json()
required = ['farm_id', 'practice_type', 'area_hectares']
if not data or not all(k in data for k in required):
return jsonify({'status': 'error', 'message': f'Missing required fields: {required}'}), 400

farm = Farm.query.get(data['farm_id'])
if not farm:
return jsonify({'status': 'error', 'message': 'Farm not found.'}), 404

valid_practices = ['NO_TILL', 'COVER_CROP', 'ORGANIC_FERTILIZER', 'AGROFORESTRY', 'BIOCHAR']
if data['practice_type'] not in valid_practices:
return jsonify({'status': 'error', 'message': f'Invalid practice_type. Must be one of: {valid_practices}'}), 400

# Pre-calculate estimated CO2e for immediate feedback
preview_log = RegenerativeFarmingLog(
farm_id=data['farm_id'],
practice_type=data['practice_type'],
area_hectares=float(data['area_hectares']),
soil_organic_carbon_percent=data.get('soil_organic_carbon_percent', 1.5),
bulk_density_gcm3=data.get('bulk_density_gcm3', 1.3),
sampling_depth_cm=data.get('sampling_depth_cm', 30.0),
soil_test_id=data.get('soil_test_id')
)
estimated = CarbonSequestrationEngine.calculate_co2e(preview_log)
preview_log.estimated_co2e_tonnes = estimated
db.session.add(preview_log)
db.session.commit()

return jsonify({
'status': 'success',
'data': {
**preview_log.to_dict(),
'estimated_co2e_tonnes': estimated,
'estimated_credit_value_usd': round(estimated * CARBON_CREDIT_PRICE_USD, 2),
'next_step': 'Submit for verification to unlock minting.'
}
}), 201


# ─── 2. Verify a Farming Log (Admin/Auditor action) ───────────────────────────
@carbon_bp.route('/practices/<int:log_id>/verify', methods=['PATCH'])
@token_required
def verify_practice(current_user, log_id):
"""Marks a farming log as verified, enabling credit minting."""
log = RegenerativeFarmingLog.query.get(log_id)
if not log:
return jsonify({'status': 'error', 'message': 'Log not found'}), 404

log.verified = True
db.session.commit()
return jsonify({'status': 'success', 'message': 'Log verified. Credits can now be minted.'}), 200


# ─── 3. Mint Credits for a Verified Log ───────────────────────────────────────
@carbon_bp.route('/practices/<int:log_id>/mint', methods=['POST'])
@token_required
def mint_credits(current_user, log_id):
"""
Triggers the Carbon Sequestration Engine to mint credits for a verified log.
Autonomously posts a double-entry ledger transaction.
"""
price = request.get_json(silent=True) or {}
price_per_tonne = price.get('price_per_tonne_usd', CARBON_CREDIT_PRICE_USD)

event, err = CarbonSequestrationEngine.mint_credits(log_id, price_per_tonne)
if err:
return jsonify({'status': 'error', 'message': err}), 400

return jsonify({
'status': 'success',
'data': {
**event.to_dict(),
'ledger_txn_posted': event.ledger_transaction_id is not None
}
}), 201


# ─── 4. List Credits on ESG Marketplace ──────────────────────────────────────
@carbon_bp.route('/market/list', methods=['POST'])
@token_required
def list_on_market(current_user):
"""
Lists a minted carbon credit batch on the internal ESG marketplace.
Requires: mint_event_id
Optional: asking_price_usd, description
"""
data = request.get_json()
if not data or 'mint_event_id' not in data:
return jsonify({'status': 'error', 'message': 'mint_event_id required.'}), 400

listing, err = CarbonSequestrationEngine.list_on_esg_market(
mint_event_id=data['mint_event_id'],
asking_price_usd=data.get('asking_price_usd'),
description=data.get('description')
)
if err:
return jsonify({'status': 'error', 'message': err}), 400

return jsonify({'status': 'success', 'data': listing.to_dict()}), 201


# ─── 5. Browse ESG Marketplace ────────────────────────────────────────────────
@carbon_bp.route('/market/listings', methods=['GET'])
@token_required
def browse_market(current_user):
"""
Returns all active ESG marketplace listings with farm & credit metadata.
Supports filter: ?min_credits=5&max_price=200
"""
query = ESGMarketListing.query.filter_by(status='ACTIVE')

min_credits = request.args.get('min_credits', type=float)
max_price = request.args.get('max_price', type=float)
if min_credits:
query = query.filter(ESGMarketListing.credits_offered >= min_credits)
if max_price:
query = query.filter(ESGMarketListing.asking_price_usd <= max_price)

listings = query.order_by(ESGMarketListing.listed_at.desc()).all()
return jsonify({
'status': 'success',
'count': len(listings),
'data': [l.to_dict() for l in listings]
}), 200


# ─── 6. Purchase Credits (Corporate Buyer) ───────────────────────────────────
@carbon_bp.route('/market/purchase/<int:listing_id>', methods=['POST'])
@token_required
def purchase_credits(current_user, listing_id):
"""
Settles an ESG carbon credit purchase via double-entry ledger.
"""
listing, err = CarbonSequestrationEngine.settle_esg_purchase(
listing_id=listing_id,
buyer_user_id=current_user.id
)
if err:
return jsonify({'status': 'error', 'message': err}), 400

return jsonify({
'status': 'success',
'message': 'Purchase settled. Credits transferred to your carbon portfolio.',
'data': listing.to_dict()
}), 200


# ─── 7. Farm Carbon Profile ───────────────────────────────────────────────────
@carbon_bp.route('/profile/<int:farm_id>', methods=['GET'])
@token_required
def get_carbon_profile(current_user, farm_id):
"""
Returns a comprehensive carbon & ESG profile for a farm.
Includes sequestration history, minted credits, sustainability score, and marketplace activity.
"""
farm = Farm.query.get(farm_id)
if not farm:
return jsonify({'status': 'error', 'message': 'Farm not found.'}), 404

logs = RegenerativeFarmingLog.query.filter_by(farm_id=farm_id).order_by(
RegenerativeFarmingLog.logged_at.desc()).all()
events = CarbonMintEvent.query.filter_by(farm_id=farm_id).all()
score = SustainabilityScore.query.filter_by(farm_id=farm_id).first()
active_listings = ESGMarketListing.query.filter_by(
farm_id=farm_id, status='ACTIVE').count()
sold_listings = ESGMarketListing.query.filter_by(
farm_id=farm_id, status='SOLD').count()

return jsonify({
'status': 'success',
'data': {
'farm': {
'id': farm.id,
'name': farm.name,
'is_no_till': farm.is_no_till,
'cover_crop_active': farm.cover_crop_active,
'organic_certified': farm.organic_certified,
'sequestration_tier': farm.sequestration_tier,
'total_credits_minted': farm.total_carbon_credits_minted
},
'sustainability_score': {
'overall_rating': score.overall_rating if score else 0,
'esg_carbon_score': score.esg_carbon_score if score else 0,
'total_credits': score.total_credits_minted if score else 0
},
'practice_logs': [l.to_dict() for l in logs],
'mint_events': [e.to_dict() for e in events],
'marketplace': {
'active_listings': active_listings,
'completed_sales': sold_listings,
'total_revenue_usd': sum(
e.sale_price_usd for e in events if e.sale_price_usd
)
}
}
}), 200
75 changes: 52 additions & 23 deletions backend/models/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@
from .audit_log import AuditLog, UserSession
from .media_payload import MediaPayload
from .weather import WeatherData, CropAdvisory, AdvisorySubscription, RiskTrigger
from .sustainability import CarbonPractice, CreditLedger, AuditRequest, CarbonLedger, EmissionSource, SustainabilityScore
from .vendor_profile import VendorProfile # Updated from procurement to vendor_profile
from .sustainability import (
CarbonPractice, CreditLedger, AuditRequest, CarbonLedger,
EmissionSource, SustainabilityScore, ESGMarketListing
)
from .vendor_profile import VendorProfile
from .procurement import ProcurementItem, BulkOrder, OrderEvent
from .irrigation import IrrigationZone, SensorLog, ValveStatus, IrrigationSchedule
from .processing import ProcessingBatch, StageLog, QualityCheck, ProcessingStage, SpectralScanData, DynamicGradeAdjustment
from .irrigation import IrrigationZone, SensorLog, ValveStatus, IrrigationSchedule, AquiferLevel, WaterRightsQuota
from .processing import ProcessingBatch, StageLog, QualityCheck, ProcessingStage
from .processing import ProcessingBatch, StageLog, QualityCheck, ProcessingStage, SpectralScanData, DynamicGradeAdjustment
from .insurance_v2 import CropPolicy, ClaimRequest, PayoutLedger, AdjusterNote
from .machinery import EngineHourLog, MaintenanceCycle, DamageReport, RepairOrder, ComponentWearMap, MaintenanceEscrow
from .soil_health import SoilTest, FertilizerRecommendation, ApplicationLog
from .machinery import EngineHourLog, MaintenanceCycle, DamageReport, RepairOrder, AssetValueSnapshot, ComponentWearMap, MaintenanceEscrow
from .soil_health import SoilTest, FertilizerRecommendation, ApplicationLog, RegenerativeFarmingLog, CarbonMintEvent
from .loan_v2 import RepaymentSchedule, PaymentHistory, DefaultRiskScore, CollectionNote
from .warehouse import WarehouseLocation, StockItem, StockMovement, ReconciliationLog
from .climate import ClimateZone, SensorNode, TelemetryLog, AutomationTrigger
from .labor import WorkerProfile, WorkShift, HarvestLog, PayrollEntry, LaborROIHistory
from .logistics_v2 import DriverProfile, DeliveryVehicle, TransportRoute, FuelLog
from .labor import WorkerProfile, WorkShift, HarvestLog, PayrollEntry
from .logistics_v2 import (
DriverProfile, DeliveryVehicle, TransportRoute, FuelLog,
Expand All @@ -37,8 +40,6 @@
from .transparency import ProduceReview, PriceAdjustmentLog
from .barter import BarterTransaction, BarterResource, ResourceValueIndex
from .financials import FarmBalanceSheet, SolvencySnapshot, ProfitabilityIndex
from .machinery import AssetValueSnapshot
from .labor import LaborROIHistory
from .reliability_log import ReliabilityLog
from .market import ForwardContract, PriceHedgingLog
from .circular import WasteInventory, BioEnergyOutput, CircularCredit
Expand All @@ -50,44 +51,72 @@
)

__all__ = [
'User', 'UserRole', 'LoanRequest', 'PredictionHistory',
'Notification', 'File', 'YieldPool', 'PoolContribution',
'ResourceShare', 'PoolVote', 'DiseaseIncident', 'OutbreakZone', 'OutbreakAlert',
# Core
'User', 'UserRole', 'LoanRequest', 'PredictionHistory',
'Notification', 'File', 'YieldPool', 'PoolContribution',
'ResourceShare', 'PoolVote',
# Disease & Outbreak
'DiseaseIncident', 'OutbreakZone', 'OutbreakAlert',
'MigrationVector', 'ContainmentZone',
# Traceability
'SupplyBatch', 'CustodyLog', 'QualityGrade', 'BatchStatus',
# Insurance
'InsurancePolicy', 'LegacyClaim', 'RiskScoreHistory', 'DynamicPremiumLog', 'RiskFactorSnapshot',
'CropPolicy', 'ClaimRequest', 'PayoutLedger', 'AdjusterNote',
# Community
'ForumCategory', 'ForumThread', 'PostComment', 'Upvote', 'UserReputation',
'Question', 'Answer', 'KnowledgeVote', 'Badge', 'UserBadge', 'UserExpertise',
# Equipment & Rental
'Equipment', 'RentalBooking', 'AvailabilityCalendar', 'PaymentEscrow',
# Farm
'Farm', 'FarmMember', 'FarmAsset', 'FarmRole',
# Alerts
'Alert', 'AlertPreference',
# Audit
'AuditLog', 'UserSession', 'MediaPayload',
# Weather
'WeatherData', 'CropAdvisory', 'AdvisorySubscription', 'RiskTrigger',
'CarbonPractice', 'CreditLedger', 'AuditRequest', 'CarbonLedger', 'EmissionSource', 'SustainabilityScore',
# Sustainability & ESG
'CarbonPractice', 'CreditLedger', 'AuditRequest', 'CarbonLedger', 'EmissionSource',
'SustainabilityScore', 'ESGMarketListing',
# Procurement
'VendorProfile', 'ProcurementItem', 'BulkOrder', 'OrderEvent',
# Irrigation & Water
'IrrigationZone', 'SensorLog', 'ValveStatus', 'IrrigationSchedule',
'AquiferLevel', 'WaterRightsQuota',
# Processing & Grading
'ProcessingBatch', 'StageLog', 'QualityCheck', 'ProcessingStage',
'CropPolicy', 'ClaimRequest', 'PayoutLedger', 'AdjusterNote',
'SpectralScanData', 'DynamicGradeAdjustment',
# Machinery
'EngineHourLog', 'MaintenanceCycle', 'DamageReport', 'RepairOrder',
'AssetValueSnapshot', 'ComponentWearMap', 'MaintenanceEscrow',
# Soil & Carbon Sequestration
'SoilTest', 'FertilizerRecommendation', 'ApplicationLog',
'RegenerativeFarmingLog', 'CarbonMintEvent',
# Finance
'RepaymentSchedule', 'PaymentHistory', 'DefaultRiskScore', 'CollectionNote',
'FarmBalanceSheet', 'SolvencySnapshot', 'ProfitabilityIndex',
# Warehouse
'WarehouseLocation', 'StockItem', 'StockMovement', 'ReconciliationLog',
# Climate
'ClimateZone', 'SensorNode', 'TelemetryLog', 'AutomationTrigger',
'WorkerProfile', 'WorkShift', 'HarvestLog', 'PayrollEntry',
# Labor
'WorkerProfile', 'WorkShift', 'HarvestLog', 'PayrollEntry', 'LaborROIHistory',
# Logistics
'DriverProfile', 'DeliveryVehicle', 'TransportRoute', 'FuelLog',
# Transparency & Barter
'PhytoSanitaryCertificate', 'FreightEscrow', 'CustomsCheckpoint', 'GPSTelemetry',
'Alert', 'AlertPreference',
'AuditLog', 'UserSession',
'MediaPayload',
'ProduceReview', 'PriceAdjustmentLog',
'BarterTransaction', 'BarterResource', 'ResourceValueIndex',
'FarmBalanceSheet', 'SolvencySnapshot', 'ProfitabilityIndex',
'AssetValueSnapshot', 'LaborROIHistory', 'ReliabilityLog',
'MigrationVector', 'ContainmentZone',
'ForwardContract', 'PriceHedgingLog',
# Reliability & Market
'ReliabilityLog', 'ForwardContract', 'PriceHedgingLog',
# Circular Economy
'WasteInventory', 'BioEnergyOutput', 'CircularCredit',
# Double-Entry Ledger
'LedgerAccount', 'LedgerTransaction', 'LedgerEntry',
'FXValuationSnapshot', 'Vault', 'VaultCurrencyPosition', 'FXRate',
'AccountType', 'EntryType', 'TransactionType',
'SpectralScanData', 'DynamicGradeAdjustment'
'AquiferLevel', 'WaterRightsQuota'
'SpectralScanData', 'DynamicGradeAdjustment',
'ComponentWearMap', 'MaintenanceEscrow'
]
4 changes: 2 additions & 2 deletions backend/models/barter.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,8 +62,8 @@ class BarterResource(db.Model):
provider_id = db.Column(db.Integer, db.ForeignKey('users.id'), nullable=False)

# Resource Metadata
resource_category = db.Column(db.String(50), nullable=False) # MACHINERY, LABOR, COMMODITY, SEEDS, CIRCULAR_CREDIT, WATER_QUOTA
resource_reference_id = db.Column(db.Integer) # e.g. Equipment ID or CircularCredit ID
resource_category = db.Column(db.String(50), nullable=False) # MACHINERY, LABOR, COMMODITY, SEEDS, CIRCULAR_CREDIT, WATER_QUOTA, CARBON_CREDIT
resource_reference_id = db.Column(db.Integer) # e.g. Equipment ID, CircularCredit ID, CarbonMintEvent ID
resource_name = db.Column(db.String(100))

quantity = db.Column(db.Float, nullable=False)
Expand Down
7 changes: 7 additions & 0 deletions backend/models/farm.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,13 @@ class Farm(db.Model):
predicted_yield_volume = db.Column(db.Float, default=0.0) # Estimated kg
last_velocity_update = db.Column(db.DateTime)

# Regenerative Carbon Tracking (L3-1632)
is_no_till = db.Column(db.Boolean, default=False) # No-tillage practice
cover_crop_active = db.Column(db.Boolean, default=False) # Cover cropping in rotation
organic_certified = db.Column(db.Boolean, default=False) # Certified organic (no synthetic)
sequestration_tier = db.Column(db.String(20), default='TIER_1') # TIER_1 to TIER_4
total_carbon_credits_minted = db.Column(db.Float, default=0.0) # Lifetime credits earned

created_at = db.Column(db.DateTime, default=datetime.utcnow)

# Relationships
Expand Down
2 changes: 2 additions & 0 deletions backend/models/ledger.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class TransactionType(enum.Enum):
DIVIDEND = 'DIVIDEND'
FEE = 'FEE'
INTEREST = 'INTEREST'
CARBON_CREDIT_MINT = 'CARBON_CREDIT_MINT' # New credit minted from sequestration
CARBON_CREDIT_SALE = 'CARBON_CREDIT_SALE' # Credit sold to ESG buyer


class LedgerAccount(db.Model):
Expand Down
Loading
Loading