Add seed-to-seed transport tracking, demand forecasting, and vertical farming systems
This comprehensive update implements: Transport Tracking System: - Complete seed-to-seed lifecycle tracking with 9 event types - TransportChain blockchain for immutable transport records - Carbon footprint calculation per transport method - Food miles tracking with Haversine distance calculation - QR code generation for full traceability Demand Forecasting System: - Consumer preference registration and aggregation - Regional demand signal generation - Supply gap identification and market matching - Grower planting recommendations with risk assessment - Seasonal planning integration Vertical Farming Module: - Multi-zone facility management - Environmental control systems (HVAC, CO2, humidity, lighting) - Growing recipes with stage-based environment targets - Crop batch tracking with health scoring - Farm analytics generation Documentation: - Complete docs/ folder structure for Turborepo - Seed-to-seed transport concept documentation - Demand forecasting and seasonal planning guides - System architecture and user blockchain design - Transport API reference - Vertical farming integration guide Agent Report: - AGENT_REPORT.md with 5 parallel agent tasks for continued development - API routes implementation task - UI components task - Vertical farming pages task - Testing suite task - Documentation completion task
This commit is contained in:
parent
24569757f2
commit
ac93368e9a
14 changed files with 5947 additions and 0 deletions
608
AGENT_REPORT.md
Normal file
608
AGENT_REPORT.md
Normal file
|
|
@ -0,0 +1,608 @@
|
||||||
|
# LocalGreenChain Agent Report
|
||||||
|
|
||||||
|
## Implementation Summary
|
||||||
|
|
||||||
|
This document outlines the comprehensive implementation of LocalGreenChain's seed-to-seed transport tracking system, demand-driven agriculture platform, and vertical farming integration. After merging this PR, **5 specialized agents** can be dispatched in parallel to continue development without overlapping concerns.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Was Implemented
|
||||||
|
|
||||||
|
### 1. Seed-to-Seed Transport Tracking System
|
||||||
|
|
||||||
|
**Files Created:**
|
||||||
|
- `lib/transport/types.ts` - Complete type definitions for all transport events
|
||||||
|
- `lib/transport/tracker.ts` - TransportChain blockchain implementation
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- 9 transport event types covering full seed-to-seed lifecycle
|
||||||
|
- Carbon footprint calculation per transport method
|
||||||
|
- Food miles tracking with Haversine distance calculation
|
||||||
|
- QR code generation for traceability
|
||||||
|
- Cross-chain reference support for plant transfers
|
||||||
|
- Environmental impact comparison vs conventional agriculture
|
||||||
|
|
||||||
|
### 2. Demand Forecasting System
|
||||||
|
|
||||||
|
**Files Created:**
|
||||||
|
- `lib/demand/types.ts` - Consumer preferences, demand signals, recommendations
|
||||||
|
- `lib/demand/forecaster.ts` - DemandForecaster with ML-ready architecture
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Consumer preference registration and aggregation
|
||||||
|
- Regional demand signal generation
|
||||||
|
- Supply gap identification
|
||||||
|
- Grower planting recommendations with risk assessment
|
||||||
|
- Seasonal planning integration
|
||||||
|
- Market matching between supply and demand
|
||||||
|
|
||||||
|
### 3. Vertical Farming Module
|
||||||
|
|
||||||
|
**Files Created:**
|
||||||
|
- `lib/vertical-farming/types.ts` - Complete VF facility, zone, and system types
|
||||||
|
- `lib/vertical-farming/controller.ts` - VerticalFarmController with recipes
|
||||||
|
|
||||||
|
**Features:**
|
||||||
|
- Multi-zone facility management
|
||||||
|
- Environmental control systems (HVAC, CO2, humidity, lighting)
|
||||||
|
- Irrigation and nutrient delivery systems
|
||||||
|
- Growing recipes with stage-based environment targets
|
||||||
|
- Crop batch tracking with health scoring
|
||||||
|
- Analytics generation (yield, efficiency, financials)
|
||||||
|
- Default recipes for lettuce, basil, and microgreens
|
||||||
|
|
||||||
|
### 4. Documentation Structure
|
||||||
|
|
||||||
|
**Created `/docs/` folder with:**
|
||||||
|
- `README.md` - Documentation index and project overview
|
||||||
|
- `concepts/seed-to-seed-transport.md` - Complete transport system documentation
|
||||||
|
- `concepts/demand-forecasting.md` - Demand-driven agriculture explanation
|
||||||
|
- `architecture/system-overview.md` - Full system architecture
|
||||||
|
- `architecture/user-blockchain.md` - Per-user blockchain design
|
||||||
|
- `api/transport-api.md` - Complete API reference
|
||||||
|
- `vertical-farming/README.md` - Vertical farming integration guide
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## System Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ LOCALGREENCHAIN PLATFORM │
|
||||||
|
├─────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────┐│
|
||||||
|
│ │ TRANSPORT │ │ DEMAND │ │ VERTICAL │ │ PLANT ││
|
||||||
|
│ │ TRACKER │ │ FORECASTER │ │ FARM │ │ BLOCKCHAIN││
|
||||||
|
│ │ │ │ │ │ CONTROLLER │ │ ││
|
||||||
|
│ │ - Events │ │ - Prefs │ │ - Zones │ │ - Lineage ││
|
||||||
|
│ │ - Carbon │ │ - Signals │ │ - Recipes │ │ - Registry││
|
||||||
|
│ │ - QR Codes │ │ - Forecasts │ │ - Analytics │ │ - Privacy ││
|
||||||
|
│ └─────────────┘ └─────────────┘ └─────────────┘ └───────────┘│
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ └────────────────┴────────────────┴────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌─────────────────┐ │
|
||||||
|
│ │ USER BLOCKCHAIN │ │
|
||||||
|
│ │ Per-user chain │ │
|
||||||
|
│ │ with cross-refs│ │
|
||||||
|
│ └─────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# AGENT TASKS
|
||||||
|
|
||||||
|
After this PR is merged, dispatch the following 5 agents **in parallel**. Each agent has a self-contained scope with no dependencies on other agents' work.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent_1: API Routes Implementation
|
||||||
|
|
||||||
|
**Scope:** Create all API route handlers for the new transport, demand, and vertical farming systems.
|
||||||
|
|
||||||
|
### Task Description
|
||||||
|
|
||||||
|
Create the following API route files in `/pages/api/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
pages/api/
|
||||||
|
├── transport/
|
||||||
|
│ ├── seed-acquisition.ts # POST - Record seed acquisition
|
||||||
|
│ ├── planting.ts # POST - Record planting event
|
||||||
|
│ ├── growing.ts # POST - Record growing transport
|
||||||
|
│ ├── harvest.ts # POST - Record harvest event
|
||||||
|
│ ├── distribution.ts # POST - Record distribution
|
||||||
|
│ ├── seed-saving.ts # POST - Record seed saving
|
||||||
|
│ ├── seed-sharing.ts # POST - Record seed sharing
|
||||||
|
│ ├── journey/[plantId].ts # GET - Get plant journey
|
||||||
|
│ ├── footprint/[userId].ts # GET - Get environmental impact
|
||||||
|
│ ├── verify/[blockHash].ts # GET - Verify block integrity
|
||||||
|
│ └── qr/[id].ts # GET - Generate QR code data
|
||||||
|
├── demand/
|
||||||
|
│ ├── preferences.ts # POST/GET - Consumer preferences
|
||||||
|
│ ├── signal.ts # POST - Generate demand signal
|
||||||
|
│ ├── recommendations.ts # GET - Get planting recommendations
|
||||||
|
│ ├── forecast.ts # GET - Get demand forecast
|
||||||
|
│ ├── supply.ts # POST - Register supply commitment
|
||||||
|
│ └── match.ts # POST - Create market match
|
||||||
|
└── vertical-farm/
|
||||||
|
├── register.ts # POST - Register new farm
|
||||||
|
├── [farmId]/
|
||||||
|
│ ├── index.ts # GET - Get farm details
|
||||||
|
│ ├── zones.ts # GET/POST - Manage zones
|
||||||
|
│ └── analytics.ts # GET - Get farm analytics
|
||||||
|
├── batch/
|
||||||
|
│ ├── start.ts # POST - Start crop batch
|
||||||
|
│ ├── [batchId]/
|
||||||
|
│ │ ├── index.ts # GET - Get batch details
|
||||||
|
│ │ ├── environment.ts # PUT - Record environment
|
||||||
|
│ │ └── harvest.ts # POST - Complete harvest
|
||||||
|
└── recipes.ts # GET - List growing recipes
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation Guidelines
|
||||||
|
|
||||||
|
1. Follow existing API patterns in `/pages/api/plants/`
|
||||||
|
2. Use the imported types from `/lib/transport/types.ts`, `/lib/demand/types.ts`, `/lib/vertical-farming/types.ts`
|
||||||
|
3. Use singleton managers: `getTransportChain()`, `getDemandForecaster()`, `getVerticalFarmController()`
|
||||||
|
4. Return consistent JSON responses with `success`, `data`, and `error` fields
|
||||||
|
5. Add input validation for all POST endpoints
|
||||||
|
6. Include proper HTTP status codes (201 for creates, 200 for success, 400/404/500 for errors)
|
||||||
|
|
||||||
|
### Files to Read First
|
||||||
|
- `/lib/transport/tracker.ts` - TransportChain class and methods
|
||||||
|
- `/lib/demand/forecaster.ts` - DemandForecaster class and methods
|
||||||
|
- `/lib/vertical-farming/controller.ts` - VerticalFarmController class and methods
|
||||||
|
- `/pages/api/plants/register.ts` - Example API pattern
|
||||||
|
|
||||||
|
### Acceptance Criteria
|
||||||
|
- All endpoints functional and returning proper responses
|
||||||
|
- Error handling for invalid inputs
|
||||||
|
- TypeScript types properly used
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent_2: UI Components for Transport & Demand
|
||||||
|
|
||||||
|
**Scope:** Create React components for transport tracking and demand visualization.
|
||||||
|
|
||||||
|
### Task Description
|
||||||
|
|
||||||
|
Create the following components in `/components/`:
|
||||||
|
|
||||||
|
```
|
||||||
|
components/
|
||||||
|
├── transport/
|
||||||
|
│ ├── TransportTimeline.tsx # Visual timeline of transport events
|
||||||
|
│ ├── JourneyMap.tsx # Map showing plant journey
|
||||||
|
│ ├── CarbonFootprintCard.tsx # Display carbon metrics
|
||||||
|
│ ├── QRCodeDisplay.tsx # Render and download QR codes
|
||||||
|
│ └── TransportEventForm.tsx # Form for recording events
|
||||||
|
├── demand/
|
||||||
|
│ ├── DemandSignalCard.tsx # Show regional demand signal
|
||||||
|
│ ├── PreferencesForm.tsx # Consumer preference input
|
||||||
|
│ ├── RecommendationList.tsx # Planting recommendations
|
||||||
|
│ ├── SupplyGapChart.tsx # Visualize supply vs demand
|
||||||
|
│ └── SeasonalCalendar.tsx # Seasonal availability view
|
||||||
|
└── analytics/
|
||||||
|
├── EnvironmentalImpact.tsx # Carbon/miles comparison
|
||||||
|
├── FoodMilesTracker.tsx # Track food miles over time
|
||||||
|
└── SavingsCalculator.tsx # Show savings vs conventional
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation Guidelines
|
||||||
|
|
||||||
|
1. Use existing component patterns from `/components/`
|
||||||
|
2. Use Tailwind CSS for styling (project uses Tailwind)
|
||||||
|
3. Components should be reusable and accept props
|
||||||
|
4. Include loading states and error handling
|
||||||
|
5. Use React Query patterns for data fetching (see existing components)
|
||||||
|
6. Make components responsive (mobile-first)
|
||||||
|
|
||||||
|
### Component Specifications
|
||||||
|
|
||||||
|
**TransportTimeline.tsx:**
|
||||||
|
```typescript
|
||||||
|
interface Props {
|
||||||
|
plantId: string;
|
||||||
|
events: TransportEvent[];
|
||||||
|
}
|
||||||
|
// Display vertical timeline with icons per event type
|
||||||
|
// Show date, location, carbon footprint for each event
|
||||||
|
```
|
||||||
|
|
||||||
|
**DemandSignalCard.tsx:**
|
||||||
|
```typescript
|
||||||
|
interface Props {
|
||||||
|
signal: DemandSignal;
|
||||||
|
onViewDetails?: () => void;
|
||||||
|
}
|
||||||
|
// Show region, demand items, supply status
|
||||||
|
// Color-coded status (green=surplus, yellow=balanced, red=shortage)
|
||||||
|
```
|
||||||
|
|
||||||
|
**CarbonFootprintCard.tsx:**
|
||||||
|
```typescript
|
||||||
|
interface Props {
|
||||||
|
impact: EnvironmentalImpact;
|
||||||
|
showComparison?: boolean;
|
||||||
|
}
|
||||||
|
// Display total carbon, food miles
|
||||||
|
// Optional comparison chart vs conventional
|
||||||
|
```
|
||||||
|
|
||||||
|
### Files to Read First
|
||||||
|
- `/components/EnvironmentalDisplay.tsx` - Example display component
|
||||||
|
- `/components/EnvironmentalForm.tsx` - Example form component
|
||||||
|
- `/lib/transport/types.ts` - Transport types
|
||||||
|
- `/lib/demand/types.ts` - Demand types
|
||||||
|
|
||||||
|
### Acceptance Criteria
|
||||||
|
- All components render correctly
|
||||||
|
- Props are properly typed
|
||||||
|
- Responsive design works on mobile
|
||||||
|
- Consistent styling with existing components
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent_3: Vertical Farming UI & Pages
|
||||||
|
|
||||||
|
**Scope:** Create complete UI pages and components for vertical farm management.
|
||||||
|
|
||||||
|
### Task Description
|
||||||
|
|
||||||
|
Create the following pages and components:
|
||||||
|
|
||||||
|
```
|
||||||
|
pages/
|
||||||
|
├── vertical-farm/
|
||||||
|
│ ├── index.tsx # List all farms / dashboard
|
||||||
|
│ ├── register.tsx # Register new farm form
|
||||||
|
│ ├── [farmId]/
|
||||||
|
│ │ ├── index.tsx # Farm detail view
|
||||||
|
│ │ ├── zones.tsx # Zone management
|
||||||
|
│ │ ├── batches.tsx # Active crop batches
|
||||||
|
│ │ └── analytics.tsx # Farm analytics
|
||||||
|
|
||||||
|
components/
|
||||||
|
├── vertical-farm/
|
||||||
|
│ ├── FarmCard.tsx # Farm summary card
|
||||||
|
│ ├── ZoneGrid.tsx # Grid display of zones
|
||||||
|
│ ├── ZoneDetailCard.tsx # Individual zone details
|
||||||
|
│ ├── EnvironmentGauge.tsx # Real-time env readings
|
||||||
|
│ ├── BatchProgress.tsx # Crop batch progress bar
|
||||||
|
│ ├── RecipeSelector.tsx # Select growing recipe
|
||||||
|
│ ├── AlertPanel.tsx # Environment alerts display
|
||||||
|
│ ├── GrowthStageIndicator.tsx # Visual stage progress
|
||||||
|
│ └── ResourceUsageChart.tsx # Energy/water usage chart
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation Guidelines
|
||||||
|
|
||||||
|
1. Follow existing page patterns from `/pages/plants/`
|
||||||
|
2. Use the VerticalFarmController API
|
||||||
|
3. Include real-time updates for environment readings (polling or websocket)
|
||||||
|
4. Dashboard should show key metrics at a glance
|
||||||
|
5. Zone management should allow starting batches and monitoring progress
|
||||||
|
|
||||||
|
### Page Specifications
|
||||||
|
|
||||||
|
**index.tsx (Dashboard):**
|
||||||
|
- List user's farms with key stats
|
||||||
|
- Quick actions: Add farm, View analytics
|
||||||
|
- Summary cards showing total plants, upcoming harvests
|
||||||
|
|
||||||
|
**[farmId]/index.tsx (Farm Detail):**
|
||||||
|
- Farm info and specs
|
||||||
|
- Zone overview with status colors
|
||||||
|
- Current alerts
|
||||||
|
- Quick navigation to zones and batches
|
||||||
|
|
||||||
|
**[farmId]/zones.tsx (Zone Management):**
|
||||||
|
- Grid of zones with current crop info
|
||||||
|
- Click zone to expand details
|
||||||
|
- Start new batch action
|
||||||
|
- Environment readings
|
||||||
|
|
||||||
|
**[farmId]/analytics.tsx (Analytics):**
|
||||||
|
- Yield over time chart
|
||||||
|
- Efficiency metrics
|
||||||
|
- Top performing crops
|
||||||
|
- Resource usage trends
|
||||||
|
|
||||||
|
### Files to Read First
|
||||||
|
- `/lib/vertical-farming/types.ts` - All VF types
|
||||||
|
- `/lib/vertical-farming/controller.ts` - Controller methods
|
||||||
|
- `/pages/plants/explore.tsx` - Example page pattern
|
||||||
|
- `/docs/vertical-farming/README.md` - VF documentation
|
||||||
|
|
||||||
|
### Acceptance Criteria
|
||||||
|
- All pages functional with proper routing
|
||||||
|
- Data fetches from API endpoints
|
||||||
|
- Forms validate input properly
|
||||||
|
- Visual feedback for all actions
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent_4: Testing Suite
|
||||||
|
|
||||||
|
**Scope:** Create comprehensive tests for all new modules.
|
||||||
|
|
||||||
|
### Task Description
|
||||||
|
|
||||||
|
Create tests for transport, demand, and vertical farming systems:
|
||||||
|
|
||||||
|
```
|
||||||
|
__tests__/ (or tests/)
|
||||||
|
├── lib/
|
||||||
|
│ ├── transport/
|
||||||
|
│ │ ├── tracker.test.ts # TransportChain tests
|
||||||
|
│ │ ├── carbon.test.ts # Carbon calculation tests
|
||||||
|
│ │ └── types.test.ts # Type validation tests
|
||||||
|
│ ├── demand/
|
||||||
|
│ │ ├── forecaster.test.ts # DemandForecaster tests
|
||||||
|
│ │ ├── aggregation.test.ts # Demand aggregation tests
|
||||||
|
│ │ └── recommendations.test.ts # Recommendation logic
|
||||||
|
│ └── vertical-farming/
|
||||||
|
│ ├── controller.test.ts # VerticalFarmController tests
|
||||||
|
│ ├── recipes.test.ts # Recipe stage logic
|
||||||
|
│ └── environment.test.ts # Environment alert tests
|
||||||
|
├── api/
|
||||||
|
│ ├── transport.test.ts # Transport API tests
|
||||||
|
│ ├── demand.test.ts # Demand API tests
|
||||||
|
│ └── vertical-farm.test.ts # VF API tests
|
||||||
|
└── integration/
|
||||||
|
├── seed-to-seed.test.ts # Full lifecycle test
|
||||||
|
├── demand-to-harvest.test.ts # Demand → Plant → Harvest
|
||||||
|
└── vf-batch-lifecycle.test.ts # Complete VF batch test
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Specifications
|
||||||
|
|
||||||
|
**TransportChain Tests:**
|
||||||
|
```typescript
|
||||||
|
describe('TransportChain', () => {
|
||||||
|
it('should create genesis block on initialization');
|
||||||
|
it('should record seed acquisition event');
|
||||||
|
it('should calculate carbon footprint correctly');
|
||||||
|
it('should track plant journey across events');
|
||||||
|
it('should generate valid QR data');
|
||||||
|
it('should verify chain integrity');
|
||||||
|
it('should reject invalid events');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**DemandForecaster Tests:**
|
||||||
|
```typescript
|
||||||
|
describe('DemandForecaster', () => {
|
||||||
|
it('should register consumer preferences');
|
||||||
|
it('should aggregate demand by region');
|
||||||
|
it('should calculate supply gaps');
|
||||||
|
it('should generate planting recommendations');
|
||||||
|
it('should apply seasonal factors');
|
||||||
|
it('should assess risk factors');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**VerticalFarmController Tests:**
|
||||||
|
```typescript
|
||||||
|
describe('VerticalFarmController', () => {
|
||||||
|
it('should register a vertical farm');
|
||||||
|
it('should start crop batch with recipe');
|
||||||
|
it('should update batch progress');
|
||||||
|
it('should detect environment alerts');
|
||||||
|
it('should complete harvest and record yield');
|
||||||
|
it('should generate accurate analytics');
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Integration Tests:**
|
||||||
|
```typescript
|
||||||
|
describe('Seed-to-Seed Lifecycle', () => {
|
||||||
|
it('should track complete lifecycle from seed acquisition to seed saving');
|
||||||
|
// Create seed batch → Plant → Grow → Harvest → Save seeds → New generation
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation Guidelines
|
||||||
|
|
||||||
|
1. Use Jest or the existing test framework (check `package.json`)
|
||||||
|
2. Mock external services (geolocation, plants.net API)
|
||||||
|
3. Test edge cases (empty data, invalid inputs, boundary conditions)
|
||||||
|
4. Include performance tests for blockchain operations
|
||||||
|
5. Ensure test isolation (no shared state between tests)
|
||||||
|
|
||||||
|
### Files to Read First
|
||||||
|
- `/lib/transport/tracker.ts`
|
||||||
|
- `/lib/demand/forecaster.ts`
|
||||||
|
- `/lib/vertical-farming/controller.ts`
|
||||||
|
- Existing tests in `/cypress/` for patterns
|
||||||
|
|
||||||
|
### Acceptance Criteria
|
||||||
|
- All tests pass
|
||||||
|
- Coverage > 80% for new code
|
||||||
|
- Edge cases covered
|
||||||
|
- Integration tests demonstrate full workflows
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Agent_5: Documentation & Guides Completion
|
||||||
|
|
||||||
|
**Scope:** Complete all remaining documentation, guides, and examples.
|
||||||
|
|
||||||
|
### Task Description
|
||||||
|
|
||||||
|
Complete the documentation structure:
|
||||||
|
|
||||||
|
```
|
||||||
|
docs/
|
||||||
|
├── guides/
|
||||||
|
│ ├── quick-start.md # 5-minute getting started
|
||||||
|
│ ├── installation.md # Detailed installation
|
||||||
|
│ ├── configuration.md # Environment variables, settings
|
||||||
|
│ ├── grower-guide.md # Complete grower workflow
|
||||||
|
│ ├── consumer-guide.md # Consumer usage guide
|
||||||
|
│ ├── transport-guide.md # Transport logging guide
|
||||||
|
│ └── vertical-farm-guide.md # VF operator guide
|
||||||
|
├── api/
|
||||||
|
│ ├── rest-api.md # Full REST API reference
|
||||||
|
│ ├── demand-api.md # Demand API reference
|
||||||
|
│ └── vertical-farming-api.md # VF API reference
|
||||||
|
├── concepts/
|
||||||
|
│ ├── blockchain.md # Blockchain explained
|
||||||
|
│ ├── seasonal-planning.md # Seasonal optimization
|
||||||
|
│ └── carbon-footprint.md # Carbon tracking explained
|
||||||
|
├── architecture/
|
||||||
|
│ ├── data-flow.md # Data flow diagrams
|
||||||
|
│ └── transport-tracking.md # Transport architecture
|
||||||
|
├── vertical-farming/
|
||||||
|
│ ├── environmental-control.md # Env control details
|
||||||
|
│ ├── automation.md # Automation systems
|
||||||
|
│ └── integration.md # Integration guide
|
||||||
|
└── examples/
|
||||||
|
├── seed-to-harvest.md # Example workflow
|
||||||
|
├── demand-driven-planting.md # Demand example
|
||||||
|
└── vertical-farm-setup.md # VF setup example
|
||||||
|
```
|
||||||
|
|
||||||
|
### Documentation Requirements
|
||||||
|
|
||||||
|
**quick-start.md:**
|
||||||
|
- Prerequisites (Bun, etc.)
|
||||||
|
- Clone, install, run in < 5 minutes
|
||||||
|
- First plant registration
|
||||||
|
- View your plant on the network
|
||||||
|
|
||||||
|
**grower-guide.md:**
|
||||||
|
- Setting up as a grower
|
||||||
|
- Registering plants
|
||||||
|
- Recording transport events
|
||||||
|
- Responding to demand signals
|
||||||
|
- Harvesting and seed saving
|
||||||
|
- Analytics and optimization
|
||||||
|
|
||||||
|
**consumer-guide.md:**
|
||||||
|
- Setting preferences
|
||||||
|
- Finding local produce
|
||||||
|
- Scanning QR codes
|
||||||
|
- Understanding plant history
|
||||||
|
- Providing feedback
|
||||||
|
|
||||||
|
**vertical-farm-guide.md:**
|
||||||
|
- Facility registration
|
||||||
|
- Zone configuration
|
||||||
|
- Recipe selection
|
||||||
|
- Batch management
|
||||||
|
- Environment monitoring
|
||||||
|
- Analytics interpretation
|
||||||
|
|
||||||
|
### Code Examples
|
||||||
|
|
||||||
|
Include working code examples:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Example: Register a plant and track transport
|
||||||
|
const plant = await registerPlant({
|
||||||
|
commonName: 'Tomato',
|
||||||
|
scientificName: 'Solanum lycopersicum',
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
|
||||||
|
await recordTransport({
|
||||||
|
eventType: 'seed_acquisition',
|
||||||
|
seedBatchId: 'batch-001',
|
||||||
|
// ...
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Diagram Requirements
|
||||||
|
|
||||||
|
Create ASCII diagrams for:
|
||||||
|
1. Complete data flow from seed to seed
|
||||||
|
2. Demand signal aggregation process
|
||||||
|
3. Vertical farm batch lifecycle
|
||||||
|
4. Carbon footprint calculation flow
|
||||||
|
|
||||||
|
### Files to Read First
|
||||||
|
- `/docs/README.md` - Existing doc structure
|
||||||
|
- `/docs/concepts/seed-to-seed-transport.md` - Transport docs
|
||||||
|
- `/docs/vertical-farming/README.md` - VF docs
|
||||||
|
- `/README.md` - Main project README
|
||||||
|
|
||||||
|
### Acceptance Criteria
|
||||||
|
- All guide files created and complete
|
||||||
|
- Code examples are accurate and tested
|
||||||
|
- Diagrams are clear and accurate
|
||||||
|
- Cross-references between docs work
|
||||||
|
- Consistent formatting and style
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Parallel Execution Notes
|
||||||
|
|
||||||
|
These 5 agents can run **completely in parallel** because:
|
||||||
|
|
||||||
|
1. **Agent_1 (API)**: Works only in `/pages/api/` - no overlap with UI
|
||||||
|
2. **Agent_2 (UI Components)**: Creates `/components/` only - no overlap with pages
|
||||||
|
3. **Agent_3 (VF Pages)**: Creates `/pages/vertical-farm/` - separate from components
|
||||||
|
4. **Agent_4 (Tests)**: Creates `/__tests__/` - completely separate directory
|
||||||
|
5. **Agent_5 (Docs)**: Works only in `/docs/` - no code changes
|
||||||
|
|
||||||
|
### Execution Command
|
||||||
|
|
||||||
|
After PR merge, dispatch all 5 agents with:
|
||||||
|
|
||||||
|
```
|
||||||
|
Agent_1: "Read AGENT_REPORT.md section 'Agent_1: API Routes Implementation' and complete all tasks"
|
||||||
|
Agent_2: "Read AGENT_REPORT.md section 'Agent_2: UI Components for Transport & Demand' and complete all tasks"
|
||||||
|
Agent_3: "Read AGENT_REPORT.md section 'Agent_3: Vertical Farming UI & Pages' and complete all tasks"
|
||||||
|
Agent_4: "Read AGENT_REPORT.md section 'Agent_4: Testing Suite' and complete all tasks"
|
||||||
|
Agent_5: "Read AGENT_REPORT.md section 'Agent_5: Documentation & Guides Completion' and complete all tasks"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Summary of New Files
|
||||||
|
|
||||||
|
### Library Files (Core Implementation)
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `lib/transport/types.ts` | Transport event type definitions |
|
||||||
|
| `lib/transport/tracker.ts` | TransportChain blockchain |
|
||||||
|
| `lib/demand/types.ts` | Demand forecasting types |
|
||||||
|
| `lib/demand/forecaster.ts` | DemandForecaster implementation |
|
||||||
|
| `lib/vertical-farming/types.ts` | Vertical farm types |
|
||||||
|
| `lib/vertical-farming/controller.ts` | VerticalFarmController |
|
||||||
|
|
||||||
|
### Documentation Files
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `docs/README.md` | Documentation index |
|
||||||
|
| `docs/concepts/seed-to-seed-transport.md` | Transport system docs |
|
||||||
|
| `docs/concepts/demand-forecasting.md` | Demand system docs |
|
||||||
|
| `docs/architecture/system-overview.md` | Architecture overview |
|
||||||
|
| `docs/architecture/user-blockchain.md` | Per-user blockchain design |
|
||||||
|
| `docs/api/transport-api.md` | Transport API reference |
|
||||||
|
| `docs/vertical-farming/README.md` | Vertical farming guide |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environmental Impact Goals
|
||||||
|
|
||||||
|
This implementation enables LocalGreenChain to achieve:
|
||||||
|
|
||||||
|
| Metric | Target | How |
|
||||||
|
|--------|--------|-----|
|
||||||
|
| Food Miles Reduction | 97% | Local production + vertical farms |
|
||||||
|
| Carbon Reduction | 89% | Short transport + clean energy |
|
||||||
|
| Water Reduction | 90% | Vertical farm efficiency |
|
||||||
|
| Food Waste Reduction | 75% | Demand-driven production |
|
||||||
|
| Year-Round Availability | 100% | Vertical farm integration |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*This report was generated as part of the LocalGreenChain seed-to-seed transport implementation. For questions, see the documentation or open an issue.*
|
||||||
99
docs/README.md
Normal file
99
docs/README.md
Normal file
|
|
@ -0,0 +1,99 @@
|
||||||
|
# LocalGreenChain Documentation
|
||||||
|
|
||||||
|
> **A demand-driven, seasonal agriculture platform leveraging blockchain technology to track plant lineage from seed-to-seed while reducing the global footprint of human consumption.**
|
||||||
|
|
||||||
|
## Vision
|
||||||
|
|
||||||
|
LocalGreenChain transforms agriculture by enabling:
|
||||||
|
|
||||||
|
- **Demand-Driven Production**: Grow what consumers need, when they need it
|
||||||
|
- **Seasonal Optimization**: Align production with natural growing cycles
|
||||||
|
- **Footprint Reduction**: Minimize transportation, waste, and resource consumption
|
||||||
|
- **Complete Traceability**: Track every plant from seed origin to harvest and back to seed
|
||||||
|
|
||||||
|
## Documentation Index
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
- [Quick Start Guide](./guides/quick-start.md)
|
||||||
|
- [Installation](./guides/installation.md)
|
||||||
|
- [Configuration](./guides/configuration.md)
|
||||||
|
|
||||||
|
### Core Concepts
|
||||||
|
- [Seed-to-Seed Transport System](./concepts/seed-to-seed-transport.md)
|
||||||
|
- [Blockchain Architecture](./concepts/blockchain.md)
|
||||||
|
- [Demand Forecasting](./concepts/demand-forecasting.md)
|
||||||
|
- [Seasonal Planning](./concepts/seasonal-planning.md)
|
||||||
|
- [Carbon Footprint Tracking](./concepts/carbon-footprint.md)
|
||||||
|
|
||||||
|
### Architecture
|
||||||
|
- [System Overview](./architecture/system-overview.md)
|
||||||
|
- [Data Flow](./architecture/data-flow.md)
|
||||||
|
- [User Blockchain](./architecture/user-blockchain.md)
|
||||||
|
- [Transport Tracking](./architecture/transport-tracking.md)
|
||||||
|
|
||||||
|
### Vertical Farming
|
||||||
|
- [Overview](./vertical-farming/README.md)
|
||||||
|
- [Environmental Control](./vertical-farming/environmental-control.md)
|
||||||
|
- [Automation Systems](./vertical-farming/automation.md)
|
||||||
|
- [Integration Guide](./vertical-farming/integration.md)
|
||||||
|
|
||||||
|
### API Reference
|
||||||
|
- [REST API](./api/rest-api.md)
|
||||||
|
- [Transport API](./api/transport-api.md)
|
||||||
|
- [Demand API](./api/demand-api.md)
|
||||||
|
- [Vertical Farming API](./api/vertical-farming-api.md)
|
||||||
|
|
||||||
|
### Guides
|
||||||
|
- [Grower Guide](./guides/grower-guide.md)
|
||||||
|
- [Consumer Guide](./guides/consumer-guide.md)
|
||||||
|
- [Transport Guide](./guides/transport-guide.md)
|
||||||
|
- [Vertical Farm Operator Guide](./guides/vertical-farm-guide.md)
|
||||||
|
|
||||||
|
## Project Goals
|
||||||
|
|
||||||
|
### Environmental Impact Reduction
|
||||||
|
|
||||||
|
```
|
||||||
|
Traditional Supply Chain:
|
||||||
|
[Seed] → [Farm] → [Processor] → [Distributor] → [Retailer] → [Consumer]
|
||||||
|
↓ ↓ ↓ ↓
|
||||||
|
Waste Transport Storage Spoilage
|
||||||
|
(30%) Emissions Energy (15%)
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
LocalGreenChain Model:
|
||||||
|
[Seed] ← → [Local Grower] ← → [Consumer]
|
||||||
|
↓
|
||||||
|
[Vertical Farm]
|
||||||
|
↓
|
||||||
|
Demand-Optimized
|
||||||
|
Zero-Waste Cycle
|
||||||
|
```
|
||||||
|
|
||||||
|
### Key Metrics We Track
|
||||||
|
|
||||||
|
| Metric | Traditional | LocalGreenChain Target |
|
||||||
|
|--------|-------------|----------------------|
|
||||||
|
| Food Miles | 1,500+ avg | < 50 miles |
|
||||||
|
| Spoilage Rate | 30-40% | < 5% |
|
||||||
|
| Carbon/lb | 2.5 kg CO2 | 0.3 kg CO2 |
|
||||||
|
| Water Usage | 100% | 10% (vertical) |
|
||||||
|
| Pesticide Use | Variable | Zero (controlled) |
|
||||||
|
|
||||||
|
## Technology Stack
|
||||||
|
|
||||||
|
- **Runtime**: Bun (fast JavaScript runtime)
|
||||||
|
- **Frontend**: Next.js, React, TypeScript, Tailwind CSS
|
||||||
|
- **Blockchain**: Custom proof-of-work implementation
|
||||||
|
- **Transport Tracking**: GPS integration, QR code scanning
|
||||||
|
- **Demand Engine**: Machine learning forecasting
|
||||||
|
- **Vertical Farming**: IoT sensor integration, automation APIs
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
See [CONTRIBUTING.md](../CONTRIBUTING.md) for guidelines on contributing to LocalGreenChain.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT License - see [LICENSE](../LICENSE) for details.
|
||||||
476
docs/api/transport-api.md
Normal file
476
docs/api/transport-api.md
Normal file
|
|
@ -0,0 +1,476 @@
|
||||||
|
# Transport API Reference
|
||||||
|
|
||||||
|
The Transport API enables seed-to-seed tracking of all plant movement and transformation events within the LocalGreenChain ecosystem.
|
||||||
|
|
||||||
|
## Base URL
|
||||||
|
|
||||||
|
```
|
||||||
|
Development: http://localhost:3001/api/transport
|
||||||
|
Production: https://api.localgreenchain.org/transport
|
||||||
|
```
|
||||||
|
|
||||||
|
## Authentication
|
||||||
|
|
||||||
|
All transport endpoints require authentication:
|
||||||
|
|
||||||
|
```http
|
||||||
|
Authorization: Bearer <user_token>
|
||||||
|
X-Wallet-Address: <wallet_address>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
|
||||||
|
### Record Seed Acquisition
|
||||||
|
|
||||||
|
Record the acquisition of seeds from any source.
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/transport/seed-acquisition
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"seedBatchId": "batch-2025-001",
|
||||||
|
"sourceType": "seed_bank",
|
||||||
|
"species": "Solanum lycopersicum",
|
||||||
|
"variety": "Cherokee Purple",
|
||||||
|
"quantity": 50,
|
||||||
|
"quantityUnit": "seeds",
|
||||||
|
"geneticLineageId": "lineage-tomato-cp-001",
|
||||||
|
"generation": 0,
|
||||||
|
"fromLocation": {
|
||||||
|
"latitude": 40.7128,
|
||||||
|
"longitude": -74.0060,
|
||||||
|
"locationType": "seed_bank",
|
||||||
|
"facilityName": "Baker Creek Seeds"
|
||||||
|
},
|
||||||
|
"toLocation": {
|
||||||
|
"latitude": 40.7589,
|
||||||
|
"longitude": -73.9851,
|
||||||
|
"locationType": "farm",
|
||||||
|
"facilityName": "Urban Farm NYC"
|
||||||
|
},
|
||||||
|
"transportMethod": "local_delivery",
|
||||||
|
"certifications": ["organic", "heirloom"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"event": {
|
||||||
|
"id": "evt-seed-acq-123",
|
||||||
|
"timestamp": "2025-01-15T10:30:00Z",
|
||||||
|
"eventType": "seed_acquisition",
|
||||||
|
"status": "verified"
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
"index": 5,
|
||||||
|
"hash": "0000abc123...",
|
||||||
|
"carbonFootprintKg": 0.02,
|
||||||
|
"foodMiles": 5.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Record Planting
|
||||||
|
|
||||||
|
Record when seeds are planted.
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/transport/planting
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"seedBatchId": "batch-2025-001",
|
||||||
|
"plantIds": ["plant-001", "plant-002", "plant-003"],
|
||||||
|
"plantingMethod": "transplant",
|
||||||
|
"quantityPlanted": 25,
|
||||||
|
"growingEnvironment": "vertical_farm",
|
||||||
|
"location": {
|
||||||
|
"latitude": 40.7589,
|
||||||
|
"longitude": -73.9851,
|
||||||
|
"locationType": "vertical_farm",
|
||||||
|
"facilityName": "Urban Farm NYC"
|
||||||
|
},
|
||||||
|
"expectedHarvestDate": "2025-03-15T00:00:00Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"event": {
|
||||||
|
"id": "evt-planting-124",
|
||||||
|
"timestamp": "2025-01-20T08:00:00Z",
|
||||||
|
"eventType": "planting",
|
||||||
|
"plantIds": ["plant-001", "plant-002", "plant-003"]
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
"index": 6,
|
||||||
|
"hash": "0000def456..."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Record Growing Transport
|
||||||
|
|
||||||
|
Record plant movement during growing (transplanting, relocating).
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/transport/growing
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"plantIds": ["plant-001", "plant-002"],
|
||||||
|
"reason": "transplant",
|
||||||
|
"plantStage": "seedling",
|
||||||
|
"handlingMethod": "potted",
|
||||||
|
"fromLocation": {
|
||||||
|
"latitude": 40.7589,
|
||||||
|
"longitude": -73.9851,
|
||||||
|
"locationType": "greenhouse",
|
||||||
|
"facilityName": "Seedling House A"
|
||||||
|
},
|
||||||
|
"toLocation": {
|
||||||
|
"latitude": 40.7589,
|
||||||
|
"longitude": -73.9852,
|
||||||
|
"locationType": "vertical_farm",
|
||||||
|
"facilityName": "Main Growing Tower"
|
||||||
|
},
|
||||||
|
"transportMethod": "walking",
|
||||||
|
"rootDisturbance": "minimal",
|
||||||
|
"acclimatizationRequired": true,
|
||||||
|
"acclimatizationDays": 3
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Record Harvest
|
||||||
|
|
||||||
|
Record harvest events with yield data.
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/transport/harvest
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"plantIds": ["plant-001", "plant-002", "plant-003"],
|
||||||
|
"harvestBatchId": "harvest-2025-050",
|
||||||
|
"harvestType": "full",
|
||||||
|
"produceType": "tomatoes",
|
||||||
|
"grossWeight": 15.5,
|
||||||
|
"netWeight": 14.2,
|
||||||
|
"weightUnit": "kg",
|
||||||
|
"qualityGrade": "A",
|
||||||
|
"packagingType": "compostable_clamshell",
|
||||||
|
"temperatureRequired": {
|
||||||
|
"min": 10,
|
||||||
|
"max": 15,
|
||||||
|
"optimal": 12,
|
||||||
|
"unit": "celsius"
|
||||||
|
},
|
||||||
|
"shelfLifeHours": 168,
|
||||||
|
"seedsSaved": true,
|
||||||
|
"seedBatchIdCreated": "batch-2025-002"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"success": true,
|
||||||
|
"event": {
|
||||||
|
"id": "evt-harvest-125",
|
||||||
|
"timestamp": "2025-03-15T06:00:00Z",
|
||||||
|
"eventType": "harvest",
|
||||||
|
"harvestBatchId": "harvest-2025-050",
|
||||||
|
"netWeight": 14.2
|
||||||
|
},
|
||||||
|
"block": {
|
||||||
|
"index": 25,
|
||||||
|
"hash": "0000ghi789...",
|
||||||
|
"carbonFootprintKg": 0.1
|
||||||
|
},
|
||||||
|
"seedBatch": {
|
||||||
|
"id": "batch-2025-002",
|
||||||
|
"parentPlants": ["plant-001", "plant-002", "plant-003"],
|
||||||
|
"generation": 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Record Distribution
|
||||||
|
|
||||||
|
Record movement through distribution chain.
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/transport/distribution
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"batchIds": ["harvest-2025-050"],
|
||||||
|
"destinationType": "consumer",
|
||||||
|
"fromLocation": {
|
||||||
|
"latitude": 40.7589,
|
||||||
|
"longitude": -73.9851,
|
||||||
|
"locationType": "vertical_farm"
|
||||||
|
},
|
||||||
|
"toLocation": {
|
||||||
|
"latitude": 40.7614,
|
||||||
|
"longitude": -73.9776,
|
||||||
|
"locationType": "consumer"
|
||||||
|
},
|
||||||
|
"transportMethod": "bicycle",
|
||||||
|
"orderId": "order-2025-1234",
|
||||||
|
"customerType": "individual",
|
||||||
|
"deliveryWindow": {
|
||||||
|
"start": "2025-03-15T14:00:00Z",
|
||||||
|
"end": "2025-03-15T18:00:00Z"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Record Seed Saving
|
||||||
|
|
||||||
|
Record seed saving from harvested plants.
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/transport/seed-saving
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"parentPlantIds": ["plant-001", "plant-002"],
|
||||||
|
"newSeedBatchId": "batch-2025-003",
|
||||||
|
"collectionMethod": "fermentation",
|
||||||
|
"seedCount": 200,
|
||||||
|
"seedWeight": 15,
|
||||||
|
"seedWeightUnit": "grams",
|
||||||
|
"storageConditions": {
|
||||||
|
"temperature": 10,
|
||||||
|
"humidity": 40,
|
||||||
|
"lightExposure": "dark",
|
||||||
|
"containerType": "vacuum_sealed",
|
||||||
|
"desiccant": true,
|
||||||
|
"estimatedViability": 5
|
||||||
|
},
|
||||||
|
"newGenerationNumber": 2,
|
||||||
|
"availableForSharing": true,
|
||||||
|
"sharingTerms": "trade"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Record Seed Sharing
|
||||||
|
|
||||||
|
Record when seeds are shared with others.
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/transport/seed-sharing
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"seedBatchId": "batch-2025-003",
|
||||||
|
"quantityShared": 25,
|
||||||
|
"quantityUnit": "seeds",
|
||||||
|
"sharingType": "trade",
|
||||||
|
"tradeDetails": "Traded for 25 basil seeds",
|
||||||
|
"recipientId": "user-xyz-456",
|
||||||
|
"fromLocation": {...},
|
||||||
|
"toLocation": {...},
|
||||||
|
"recipientAgreement": true,
|
||||||
|
"growingCommitment": "Will grow and report back",
|
||||||
|
"reportBackRequired": true
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Query Endpoints
|
||||||
|
|
||||||
|
### Get Plant Journey
|
||||||
|
|
||||||
|
Get complete transport history for a plant.
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/transport/journey/:plantId
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plantId": "plant-001",
|
||||||
|
"seedBatchOrigin": "batch-2025-001",
|
||||||
|
"currentCustodian": "user-abc-123",
|
||||||
|
"currentLocation": {
|
||||||
|
"latitude": 40.7614,
|
||||||
|
"longitude": -73.9776,
|
||||||
|
"locationType": "consumer"
|
||||||
|
},
|
||||||
|
"currentStage": "post_harvest",
|
||||||
|
"events": [
|
||||||
|
{
|
||||||
|
"eventType": "seed_acquisition",
|
||||||
|
"timestamp": "2025-01-15T10:30:00Z",
|
||||||
|
"fromLocation": {...},
|
||||||
|
"toLocation": {...}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"eventType": "planting",
|
||||||
|
"timestamp": "2025-01-20T08:00:00Z",
|
||||||
|
...
|
||||||
|
},
|
||||||
|
...
|
||||||
|
],
|
||||||
|
"totalFoodMiles": 12.5,
|
||||||
|
"totalCarbonKg": 0.35,
|
||||||
|
"daysInTransit": 2,
|
||||||
|
"daysGrowing": 54,
|
||||||
|
"generation": 1,
|
||||||
|
"ancestorPlantIds": ["parent-plant-001"],
|
||||||
|
"descendantSeedBatches": ["batch-2025-003"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Get Environmental Impact
|
||||||
|
|
||||||
|
Get carbon footprint and food miles for a user.
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/transport/footprint/:userId
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"totalCarbonKg": 15.5,
|
||||||
|
"totalFoodMiles": 250,
|
||||||
|
"carbonPerKgProduce": 0.25,
|
||||||
|
"milesPerKgProduce": 4.0,
|
||||||
|
"breakdownByMethod": {
|
||||||
|
"bicycle": { "distance": 50, "carbon": 0 },
|
||||||
|
"electric_vehicle": { "distance": 150, "carbon": 3.0 },
|
||||||
|
"walking": { "distance": 50, "carbon": 0 }
|
||||||
|
},
|
||||||
|
"breakdownByEventType": {
|
||||||
|
"seed_acquisition": { "count": 5, "carbon": 0.5 },
|
||||||
|
"distribution": { "count": 50, "carbon": 10.0 },
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"comparisonToConventional": {
|
||||||
|
"carbonSaved": 140.5,
|
||||||
|
"milesSaved": 74750,
|
||||||
|
"percentageReduction": 90
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Verify Block
|
||||||
|
|
||||||
|
Verify a transport block's integrity.
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/transport/verify/:blockHash
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"valid": true,
|
||||||
|
"block": {
|
||||||
|
"index": 25,
|
||||||
|
"hash": "0000ghi789...",
|
||||||
|
"previousHash": "0000fgh678...",
|
||||||
|
"timestamp": "2025-03-15T06:00:00Z"
|
||||||
|
},
|
||||||
|
"chainValid": true,
|
||||||
|
"crossReferences": [
|
||||||
|
{
|
||||||
|
"userId": "user-xyz-456",
|
||||||
|
"blockIndex": 12,
|
||||||
|
"type": "receiver",
|
||||||
|
"verified": true
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Generate QR Code
|
||||||
|
|
||||||
|
Generate QR code data for a plant or batch.
|
||||||
|
|
||||||
|
```http
|
||||||
|
GET /api/transport/qr/:plantId
|
||||||
|
```
|
||||||
|
|
||||||
|
**Response:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plantId": "plant-001",
|
||||||
|
"blockchainAddress": "0x1234567890abcdef...",
|
||||||
|
"quickLookupUrl": "https://localgreenchain.org/track/plant-001",
|
||||||
|
"lineageHash": "a1b2c3d4e5f6...",
|
||||||
|
"currentCustodian": "user-abc-123",
|
||||||
|
"lastEventType": "distribution",
|
||||||
|
"lastEventTimestamp": "2025-03-15T14:30:00Z",
|
||||||
|
"verificationCode": "A1B2C3D4",
|
||||||
|
"qrCodeImage": "data:image/png;base64,..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Error Codes
|
||||||
|
|
||||||
|
| Code | Message | Description |
|
||||||
|
|------|---------|-------------|
|
||||||
|
| 400 | Invalid request | Missing or invalid parameters |
|
||||||
|
| 401 | Unauthorized | Invalid or missing auth token |
|
||||||
|
| 403 | Forbidden | Not authorized for this resource |
|
||||||
|
| 404 | Not found | Plant/batch/block not found |
|
||||||
|
| 409 | Conflict | Duplicate event or chain conflict |
|
||||||
|
| 422 | Validation error | Data validation failed |
|
||||||
|
| 500 | Server error | Internal server error |
|
||||||
|
|
||||||
|
## Rate Limits
|
||||||
|
|
||||||
|
| Endpoint Type | Limit | Window |
|
||||||
|
|---------------|-------|--------|
|
||||||
|
| Read (GET) | 100 | per minute |
|
||||||
|
| Write (POST) | 20 | per minute |
|
||||||
|
| Batch operations | 5 | per minute |
|
||||||
|
|
||||||
|
## Webhooks
|
||||||
|
|
||||||
|
Subscribe to transport events:
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/transport/webhooks
|
||||||
|
Content-Type: application/json
|
||||||
|
|
||||||
|
{
|
||||||
|
"url": "https://your-server.com/webhook",
|
||||||
|
"events": ["seed_acquisition", "harvest", "distribution"],
|
||||||
|
"filters": {
|
||||||
|
"plantIds": ["plant-001", "plant-002"],
|
||||||
|
"userId": "user-abc-123"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
246
docs/architecture/system-overview.md
Normal file
246
docs/architecture/system-overview.md
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
# System Architecture Overview
|
||||||
|
|
||||||
|
LocalGreenChain is a distributed agricultural tracking platform built on blockchain technology with a focus on demand-driven, seasonal production and reduced environmental impact.
|
||||||
|
|
||||||
|
## High-Level Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ LOCALGREENCHAIN PLATFORM │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ PRESENTATION LAYER │ │
|
||||||
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │ │
|
||||||
|
│ │ │ Web App │ │ Mobile PWA │ │ IoT Hub │ │ QR Scanner│ │ │
|
||||||
|
│ │ │ (Next.js) │ │ (React) │ │ Dashboard │ │ App │ │ │
|
||||||
|
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ API LAYER │ │
|
||||||
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │ │
|
||||||
|
│ │ │ Plants │ │ Transport │ │ Demand │ │ Vertical │ │ │
|
||||||
|
│ │ │ API │ │ API │ │ API │ │ Farm API │ │ │
|
||||||
|
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ SERVICE LAYER │ │
|
||||||
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │ │
|
||||||
|
│ │ │ Blockchain │ │ Climate │ │ Demand │ │ Carbon │ │ │
|
||||||
|
│ │ │ Service │ │ Service │ │ Forecasting│ │ Calculator│ │ │
|
||||||
|
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │ │
|
||||||
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌────────────┐ │ │
|
||||||
|
│ │ │ Geolocation │ │ Privacy │ │ Seed │ │ Analytics │ │ │
|
||||||
|
│ │ │ Service │ │ (Tor) │ │ Service │ │ Engine │ │ │
|
||||||
|
│ │ └─────────────┘ └─────────────┘ └─────────────┘ └────────────┘ │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ DATA LAYER │ │
|
||||||
|
│ │ ┌─────────────────────────────────────────────────────────────┐ │ │
|
||||||
|
│ │ │ USER BLOCKCHAINS │ │ │
|
||||||
|
│ │ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │ │
|
||||||
|
│ │ │ │ User A │ │ User B │ │ User C │ │ ... │ │ │ │
|
||||||
|
│ │ │ │ Chain │ │ Chain │ │ Chain │ │ │ │ │ │
|
||||||
|
│ │ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │ │
|
||||||
|
│ │ └─────────────────────────────────────────────────────────────┘ │ │
|
||||||
|
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
|
||||||
|
│ │ │ Master │ │ Demand │ │ Climate │ │ │
|
||||||
|
│ │ │ Ledger │ │ Store │ │ Store │ │ │
|
||||||
|
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Components
|
||||||
|
|
||||||
|
### 1. User Blockchain System
|
||||||
|
|
||||||
|
Each user operates their own blockchain that records:
|
||||||
|
- Plant registrations
|
||||||
|
- Growth updates
|
||||||
|
- Transport events
|
||||||
|
- Harvest records
|
||||||
|
- Seed saving activities
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// User blockchain structure
|
||||||
|
interface UserBlockchain {
|
||||||
|
userId: string;
|
||||||
|
publicKey: string;
|
||||||
|
chain: PlantBlock[];
|
||||||
|
transportChain: TransportBlock[];
|
||||||
|
demandPreferences: DemandPreference[];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Master Ledger
|
||||||
|
|
||||||
|
A global ledger that:
|
||||||
|
- Indexes all user blockchains
|
||||||
|
- Maintains cross-references for plant lineage
|
||||||
|
- Validates chain integrity
|
||||||
|
- Calculates network-wide statistics
|
||||||
|
|
||||||
|
### 3. Demand Forecasting Engine
|
||||||
|
|
||||||
|
Predicts demand based on:
|
||||||
|
- Historical consumption patterns
|
||||||
|
- Seasonal trends
|
||||||
|
- Weather forecasts
|
||||||
|
- Consumer preferences
|
||||||
|
- Regional availability
|
||||||
|
|
||||||
|
### 4. Carbon Calculator
|
||||||
|
|
||||||
|
Tracks environmental impact:
|
||||||
|
- Transport emissions
|
||||||
|
- Growing resource usage
|
||||||
|
- Packaging lifecycle
|
||||||
|
- Waste generation
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
### Plant Registration Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
User Input → Validation → Block Creation → Mining → Chain Addition → Index Update
|
||||||
|
↓
|
||||||
|
Master Ledger Sync
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transport Event Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Transport Scan → GPS Capture → Condition Check → Block Creation → Chain Update
|
||||||
|
↓ ↓ ↓
|
||||||
|
QR Decode Location Record Temp/Humidity
|
||||||
|
↓ ↓ ↓
|
||||||
|
Plant Lookup Distance Calc Alert if OOB
|
||||||
|
↓
|
||||||
|
Carbon Calculation
|
||||||
|
```
|
||||||
|
|
||||||
|
### Demand Signal Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Consumer Preference → Aggregation → Forecasting → Grower Signals
|
||||||
|
↓ ↓
|
||||||
|
Market Data Planting Recommendations
|
||||||
|
↓ ↓
|
||||||
|
Weather API Seasonal Optimization
|
||||||
|
```
|
||||||
|
|
||||||
|
## Module Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
localgreenchain/
|
||||||
|
├── lib/
|
||||||
|
│ ├── blockchain/ # Core blockchain implementation
|
||||||
|
│ │ ├── PlantBlock.ts # Block structure
|
||||||
|
│ │ ├── PlantChain.ts # Chain operations
|
||||||
|
│ │ ├── UserChain.ts # Per-user blockchain
|
||||||
|
│ │ ├── types.ts # Type definitions
|
||||||
|
│ │ └── manager.ts # Singleton manager
|
||||||
|
│ │
|
||||||
|
│ ├── transport/ # Transport tracking
|
||||||
|
│ │ ├── events.ts # Event types
|
||||||
|
│ │ ├── tracker.ts # Transport logic
|
||||||
|
│ │ ├── carbon.ts # Emissions calculator
|
||||||
|
│ │ └── qr.ts # QR code generation
|
||||||
|
│ │
|
||||||
|
│ ├── demand/ # Demand forecasting
|
||||||
|
│ │ ├── forecaster.ts # ML forecasting
|
||||||
|
│ │ ├── seasonal.ts # Seasonal planning
|
||||||
|
│ │ ├── aggregator.ts # Demand aggregation
|
||||||
|
│ │ └── signals.ts # Grower notifications
|
||||||
|
│ │
|
||||||
|
│ ├── vertical-farming/ # Vertical farm integration
|
||||||
|
│ │ ├── controller.ts # Environment control
|
||||||
|
│ │ ├── sensors.ts # IoT integration
|
||||||
|
│ │ ├── automation.ts # Automated systems
|
||||||
|
│ │ └── recipes.ts # Growing recipes
|
||||||
|
│ │
|
||||||
|
│ ├── environment/ # Environmental tracking
|
||||||
|
│ │ ├── types.ts # Environment types
|
||||||
|
│ │ └── analysis.ts # Data analysis
|
||||||
|
│ │
|
||||||
|
│ ├── privacy/ # Privacy features
|
||||||
|
│ │ └── anonymity.ts # Tor integration
|
||||||
|
│ │
|
||||||
|
│ └── services/ # External services
|
||||||
|
│ ├── geolocation.ts # Location services
|
||||||
|
│ ├── plantsnet.ts # Plants.net API
|
||||||
|
│ └── tor.ts # Tor connectivity
|
||||||
|
│
|
||||||
|
├── pages/
|
||||||
|
│ ├── api/ # API routes
|
||||||
|
│ │ ├── plants/ # Plant endpoints
|
||||||
|
│ │ ├── transport/ # Transport endpoints
|
||||||
|
│ │ ├── demand/ # Demand endpoints
|
||||||
|
│ │ └── vertical-farm/ # VF endpoints
|
||||||
|
│ │
|
||||||
|
│ └── [pages] # UI pages
|
||||||
|
│
|
||||||
|
├── components/ # React components
|
||||||
|
├── docs/ # Documentation
|
||||||
|
└── types/ # Global types
|
||||||
|
```
|
||||||
|
|
||||||
|
## Security Model
|
||||||
|
|
||||||
|
### Blockchain Security
|
||||||
|
|
||||||
|
- **Proof-of-Work**: Mining difficulty prevents rapid chain modification
|
||||||
|
- **Hash Linking**: Each block references previous hash
|
||||||
|
- **Validation**: Full chain validation before accepting data
|
||||||
|
|
||||||
|
### User Security
|
||||||
|
|
||||||
|
- **Wallet Addresses**: Pseudonymous identification
|
||||||
|
- **Tor Integration**: Optional anonymous operation
|
||||||
|
- **Location Privacy**: Configurable precision levels
|
||||||
|
|
||||||
|
### Data Integrity
|
||||||
|
|
||||||
|
- **Immutable Records**: Historical data cannot be altered
|
||||||
|
- **Cross-Verification**: Multiple parties can verify events
|
||||||
|
- **Audit Trail**: Complete history of all operations
|
||||||
|
|
||||||
|
## Scalability Strategy
|
||||||
|
|
||||||
|
### Current Implementation (10K plants)
|
||||||
|
- File-based blockchain storage
|
||||||
|
- Single-server architecture
|
||||||
|
- Client-side rendering
|
||||||
|
|
||||||
|
### Medium Scale (100K plants)
|
||||||
|
- PostgreSQL/MongoDB backend
|
||||||
|
- Redis caching layer
|
||||||
|
- Load-balanced API servers
|
||||||
|
|
||||||
|
### Large Scale (1M+ plants)
|
||||||
|
- Distributed node network
|
||||||
|
- Geographic sharding
|
||||||
|
- P2P blockchain sync
|
||||||
|
- Mobile-first architecture
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
### External APIs
|
||||||
|
- **Plants.net**: Plant identification
|
||||||
|
- **OpenStreetMap**: Geocoding
|
||||||
|
- **Weather APIs**: Climate data
|
||||||
|
- **Carbon APIs**: Emission factors
|
||||||
|
|
||||||
|
### IoT Devices
|
||||||
|
- **Temperature sensors**: Cold chain monitoring
|
||||||
|
- **GPS trackers**: Transport tracking
|
||||||
|
- **Humidity sensors**: Environment monitoring
|
||||||
|
- **Vertical farm controllers**: Automated growing
|
||||||
353
docs/architecture/user-blockchain.md
Normal file
353
docs/architecture/user-blockchain.md
Normal file
|
|
@ -0,0 +1,353 @@
|
||||||
|
# User Blockchain Architecture
|
||||||
|
|
||||||
|
Every user in LocalGreenChain operates their own blockchain, creating a decentralized network of plant lineage and transport records that collectively form a global agricultural tracking system.
|
||||||
|
|
||||||
|
## Why Per-User Blockchains?
|
||||||
|
|
||||||
|
### Traditional Centralized Approach
|
||||||
|
```
|
||||||
|
All Data → Central Database → Single Point of Failure
|
||||||
|
↓
|
||||||
|
Trust the operator
|
||||||
|
Data can be modified
|
||||||
|
Privacy concerns
|
||||||
|
```
|
||||||
|
|
||||||
|
### LocalGreenChain Distributed Approach
|
||||||
|
```
|
||||||
|
User A Chain ←→ User B Chain ←→ User C Chain
|
||||||
|
↓ ↓ ↓
|
||||||
|
Own data Own data Own data
|
||||||
|
Own history Own history Own history
|
||||||
|
↓ ↓ ↓
|
||||||
|
Cross-referenced via Master Ledger
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ USER BLOCKCHAIN NETWORK │
|
||||||
|
├─────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ MASTER LEDGER │ │
|
||||||
|
│ │ ┌─────────────────────────────────────────────────────┐ │ │
|
||||||
|
│ │ │ Plant Index: plantId → [userA:block5, userB:block12] │ │ │
|
||||||
|
│ │ │ Lineage Index: parentId → [childId1, childId2...] │ │ │
|
||||||
|
│ │ │ Transport Index: batchId → [events...] │ │ │
|
||||||
|
│ │ │ User Directory: userId → publicKey, chainHash │ │ │
|
||||||
|
│ │ └─────────────────────────────────────────────────────┘ │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ ↑ │
|
||||||
|
│ ┌───────────────┼───────────────┐ │
|
||||||
|
│ ▼ ▼ ▼ │
|
||||||
|
│ ┌───────────────┐ ┌───────────────┐ ┌───────────────┐ │
|
||||||
|
│ │ USER A CHAIN │ │ USER B CHAIN │ │ USER C CHAIN │ │
|
||||||
|
│ ├───────────────┤ ├───────────────┤ ├───────────────┤ │
|
||||||
|
│ │ Block 0: Gen │ │ Block 0: Gen │ │ Block 0: Gen │ │
|
||||||
|
│ │ Block 1: Reg │ │ Block 1: Acq │ │ Block 1: Reg │ │
|
||||||
|
│ │ Block 2: Grow │ │ Block 2: Plant│ │ Block 2: Clone│ │
|
||||||
|
│ │ Block 3: Harv │ │ Block 3: Grow │ │ Block 3: Trans│ │
|
||||||
|
│ │ Block 4: Send │ │ Block 4: Recv │ │ Block 4: ... │ │
|
||||||
|
│ │ ... │ │ ... │ │ ... │ │
|
||||||
|
│ └───────────────┘ └───────────────┘ └───────────────┘ │
|
||||||
|
│ ↓ ↓ ↓ │
|
||||||
|
│ └───────────────────┼───────────────────┘ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌───────────────────┐ │
|
||||||
|
│ │ CROSS-CHAIN REFS │ │
|
||||||
|
│ │ Plant P in A:3 │ │
|
||||||
|
│ │ received by B:4 │ │
|
||||||
|
│ │ cloned to C:2 │ │
|
||||||
|
│ └───────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Block Types
|
||||||
|
|
||||||
|
### Plant Registration Block
|
||||||
|
```typescript
|
||||||
|
interface PlantBlock {
|
||||||
|
index: number;
|
||||||
|
timestamp: string;
|
||||||
|
blockType: 'plant_registration';
|
||||||
|
|
||||||
|
plant: {
|
||||||
|
id: string;
|
||||||
|
commonName: string;
|
||||||
|
scientificName: string;
|
||||||
|
propagationType: 'original' | 'seed' | 'clone' | 'cutting';
|
||||||
|
generation: number;
|
||||||
|
parentPlantId?: string; // Cross-chain reference
|
||||||
|
location: PlantLocation;
|
||||||
|
owner: PlantOwner;
|
||||||
|
childPlants: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
previousHash: string;
|
||||||
|
hash: string;
|
||||||
|
nonce: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Transport Event Block
|
||||||
|
```typescript
|
||||||
|
interface TransportBlock {
|
||||||
|
index: number;
|
||||||
|
timestamp: string;
|
||||||
|
blockType: 'transport_event';
|
||||||
|
|
||||||
|
transportEvent: {
|
||||||
|
eventType: TransportEventType;
|
||||||
|
fromLocation: TransportLocation;
|
||||||
|
toLocation: TransportLocation;
|
||||||
|
plantIds?: string[];
|
||||||
|
batchIds?: string[];
|
||||||
|
carbonFootprintKg: number;
|
||||||
|
senderId: string; // Cross-chain reference
|
||||||
|
receiverId: string; // Cross-chain reference
|
||||||
|
};
|
||||||
|
|
||||||
|
previousHash: string;
|
||||||
|
hash: string;
|
||||||
|
nonce: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Update Block
|
||||||
|
```typescript
|
||||||
|
interface EnvironmentBlock {
|
||||||
|
index: number;
|
||||||
|
timestamp: string;
|
||||||
|
blockType: 'environment_update';
|
||||||
|
|
||||||
|
environment: {
|
||||||
|
plantId: string;
|
||||||
|
readings: GrowingEnvironment;
|
||||||
|
healthScore: number;
|
||||||
|
alerts: EnvironmentAlert[];
|
||||||
|
};
|
||||||
|
|
||||||
|
previousHash: string;
|
||||||
|
hash: string;
|
||||||
|
nonce: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Cross-Chain References
|
||||||
|
|
||||||
|
When plants move between users, cross-chain references maintain lineage:
|
||||||
|
|
||||||
|
```
|
||||||
|
User A's Chain: User B's Chain:
|
||||||
|
┌────────────────────┐ ┌────────────────────┐
|
||||||
|
│ Block 5 │ │ Block 12 │
|
||||||
|
│ Type: Send Plant │────────────→│ Type: Receive Plant│
|
||||||
|
│ PlantId: tomato-1 │ │ PlantId: tomato-1 │
|
||||||
|
│ To: UserB │ │ From: UserA │
|
||||||
|
│ ToBlock: pending │←────────────│ FromBlock: A:5 │
|
||||||
|
└────────────────────┘ └────────────────────┘
|
||||||
|
|
||||||
|
After confirmation:
|
||||||
|
Block 5.ToBlock = "B:12" // Updated with cross-reference
|
||||||
|
```
|
||||||
|
|
||||||
|
## Chain Synchronization
|
||||||
|
|
||||||
|
### Local-First Operation
|
||||||
|
```
|
||||||
|
1. User creates block locally
|
||||||
|
2. Block is mined (proof-of-work)
|
||||||
|
3. Block added to local chain
|
||||||
|
4. Sync to master ledger index
|
||||||
|
5. Cross-references updated
|
||||||
|
```
|
||||||
|
|
||||||
|
### Conflict Resolution
|
||||||
|
```
|
||||||
|
If chain fork detected:
|
||||||
|
1. Longer chain wins (most work)
|
||||||
|
2. Orphaned blocks flagged
|
||||||
|
3. Cross-references updated
|
||||||
|
4. Users notified of conflicts
|
||||||
|
```
|
||||||
|
|
||||||
|
## Privacy Model
|
||||||
|
|
||||||
|
### Default Privacy
|
||||||
|
- Location can be fuzzy (city-level)
|
||||||
|
- Owner name can be pseudonymous
|
||||||
|
- Wallet addresses are pseudonymous
|
||||||
|
|
||||||
|
### Enhanced Privacy (Tor)
|
||||||
|
- Onion routing for chain sync
|
||||||
|
- Hidden service for API access
|
||||||
|
- No IP logging
|
||||||
|
- Anonymous registration
|
||||||
|
|
||||||
|
### Public Data
|
||||||
|
- Plant species/variety
|
||||||
|
- Lineage relationships
|
||||||
|
- Transport events (anonymized)
|
||||||
|
- Environmental data (aggregated)
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
### Single Chain Verification
|
||||||
|
```typescript
|
||||||
|
function verifyChain(chain: Block[]): boolean {
|
||||||
|
for (let i = 1; i < chain.length; i++) {
|
||||||
|
const current = chain[i];
|
||||||
|
const previous = chain[i - 1];
|
||||||
|
|
||||||
|
// Verify hash
|
||||||
|
if (current.hash !== calculateHash(current)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify chain link
|
||||||
|
if (current.previousHash !== previous.hash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify proof-of-work
|
||||||
|
if (!current.hash.startsWith('0'.repeat(difficulty))) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cross-Chain Verification
|
||||||
|
```typescript
|
||||||
|
function verifyCrossReference(
|
||||||
|
senderChain: Block[],
|
||||||
|
receiverChain: Block[],
|
||||||
|
reference: CrossReference
|
||||||
|
): boolean {
|
||||||
|
const senderBlock = senderChain[reference.senderBlockIndex];
|
||||||
|
const receiverBlock = receiverChain[reference.receiverBlockIndex];
|
||||||
|
|
||||||
|
// Verify matching plant/batch IDs
|
||||||
|
if (senderBlock.plantId !== receiverBlock.plantId) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify timestamps (receiver after sender)
|
||||||
|
if (new Date(receiverBlock.timestamp) < new Date(senderBlock.timestamp)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify signatures
|
||||||
|
if (!verifySignature(senderBlock, senderPublicKey)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Querying the Network
|
||||||
|
|
||||||
|
### Find Plant History
|
||||||
|
```typescript
|
||||||
|
// Query across all user chains
|
||||||
|
const history = masterLedger.queryPlantHistory(plantId);
|
||||||
|
|
||||||
|
// Returns:
|
||||||
|
{
|
||||||
|
plantId: 'tomato-123',
|
||||||
|
currentOwner: 'userC',
|
||||||
|
registeredBy: 'userA',
|
||||||
|
generation: 2,
|
||||||
|
history: [
|
||||||
|
{ user: 'userA', block: 1, event: 'registered', date: '2025-01-01' },
|
||||||
|
{ user: 'userA', block: 5, event: 'cloned', date: '2025-02-15' },
|
||||||
|
{ user: 'userB', block: 12, event: 'received', date: '2025-02-16' },
|
||||||
|
{ user: 'userB', block: 20, event: 'grew', date: '2025-03-01' },
|
||||||
|
{ user: 'userC', block: 8, event: 'received', date: '2025-03-15' },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Find Lineage
|
||||||
|
```typescript
|
||||||
|
const lineage = masterLedger.queryLineage(plantId, generations: 5);
|
||||||
|
|
||||||
|
// Returns tree structure
|
||||||
|
{
|
||||||
|
plant: 'tomato-123',
|
||||||
|
generation: 2,
|
||||||
|
ancestors: [
|
||||||
|
{ plant: 'tomato-orig', generation: 0, owner: 'userA' },
|
||||||
|
{ plant: 'tomato-clone1', generation: 1, owner: 'userA' }
|
||||||
|
],
|
||||||
|
descendants: [
|
||||||
|
{ plant: 'tomato-123-seed1', generation: 3, owner: 'userD' },
|
||||||
|
{ plant: 'tomato-123-seed2', generation: 3, owner: 'userE' }
|
||||||
|
],
|
||||||
|
siblings: [
|
||||||
|
{ plant: 'tomato-clone2', generation: 2, owner: 'userF' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Data Storage
|
||||||
|
|
||||||
|
### Local Storage
|
||||||
|
Each user stores their chain locally:
|
||||||
|
```
|
||||||
|
~/.localgreenchain/
|
||||||
|
├── chain.json # Main blockchain
|
||||||
|
├── transport-chain.json # Transport events
|
||||||
|
├── keys/
|
||||||
|
│ ├── private.key # User's private key
|
||||||
|
│ └── public.key # User's public key
|
||||||
|
└── cache/
|
||||||
|
└── master-index.json # Cached master ledger
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cloud Sync (Optional)
|
||||||
|
```
|
||||||
|
User Chain → Encrypted → Cloud Backup
|
||||||
|
↓
|
||||||
|
Only user can decrypt
|
||||||
|
Master ledger has index only
|
||||||
|
```
|
||||||
|
|
||||||
|
## Performance Considerations
|
||||||
|
|
||||||
|
### Block Size
|
||||||
|
- Target: < 10KB per block
|
||||||
|
- Compression for large data
|
||||||
|
- Images stored as references
|
||||||
|
|
||||||
|
### Mining Difficulty
|
||||||
|
- Adjustable per chain
|
||||||
|
- Lower for personal use (difficulty: 2)
|
||||||
|
- Higher for shared chains (difficulty: 4)
|
||||||
|
|
||||||
|
### Sync Frequency
|
||||||
|
- Real-time for active users
|
||||||
|
- Batch sync for inactive users
|
||||||
|
- Prioritize cross-references
|
||||||
|
|
||||||
|
## Security Measures
|
||||||
|
|
||||||
|
### Against Tampering
|
||||||
|
- Hash chains prevent modification
|
||||||
|
- Cross-references create witnesses
|
||||||
|
- Master ledger provides redundancy
|
||||||
|
|
||||||
|
### Against Sybil Attacks
|
||||||
|
- Proof-of-work makes spam expensive
|
||||||
|
- Reputation system for users
|
||||||
|
- Rate limiting on registrations
|
||||||
|
|
||||||
|
### Against Privacy Leaks
|
||||||
|
- Minimal data in master ledger
|
||||||
|
- Tor integration optional
|
||||||
|
- User controls visibility
|
||||||
356
docs/concepts/demand-forecasting.md
Normal file
356
docs/concepts/demand-forecasting.md
Normal file
|
|
@ -0,0 +1,356 @@
|
||||||
|
# Demand Forecasting & Seasonal Planning
|
||||||
|
|
||||||
|
LocalGreenChain's demand forecasting system enables truly demand-driven agriculture by connecting consumer preferences with grower capabilities, optimizing for seasonal availability and minimal environmental impact.
|
||||||
|
|
||||||
|
## Core Philosophy
|
||||||
|
|
||||||
|
### Traditional Agriculture Problem
|
||||||
|
|
||||||
|
```
|
||||||
|
Grower decides → Plants → Harvests → Finds buyers → Surplus/shortage
|
||||||
|
↓ ↓ ↓ ↓ ↓
|
||||||
|
Guesses Fixed plan Hope sells Waste if not Lost income
|
||||||
|
```
|
||||||
|
|
||||||
|
### LocalGreenChain Solution
|
||||||
|
|
||||||
|
```
|
||||||
|
Consumer signals → Aggregated demand → Grower recommendation → Matched harvest
|
||||||
|
↓ ↓ ↓ ↓
|
||||||
|
Preferences Regional needs Optimal planting Pre-sold produce
|
||||||
|
```
|
||||||
|
|
||||||
|
## Demand Signal Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ DEMAND SIGNAL GENERATION │
|
||||||
|
├─────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ CONSUMER PREFERENCES │
|
||||||
|
│ ┌─────────────────┐ │
|
||||||
|
│ │ Consumer A │──┐ │
|
||||||
|
│ │ - Tomatoes: 2kg │ │ │
|
||||||
|
│ │ - Lettuce: 1kg │ │ │
|
||||||
|
│ │ - Organic only │ │ │
|
||||||
|
│ └─────────────────┘ │ │
|
||||||
|
│ ┌─────────────────┐ │ ┌─────────────────────┐ │
|
||||||
|
│ │ Consumer B │──┼─────→│ DEMAND AGGREGATOR │ │
|
||||||
|
│ │ - Tomatoes: 1kg │ │ │ │ │
|
||||||
|
│ │ - Basil: 200g │ │ │ Region: Downtown │ │
|
||||||
|
│ │ - Local pref │ │ │ Radius: 25km │ │
|
||||||
|
│ └─────────────────┘ │ │ Season: Summer │ │
|
||||||
|
│ ┌─────────────────┐ │ │ │ │
|
||||||
|
│ │ Consumer C... │──┘ │ Total Consumers: │ │
|
||||||
|
│ │ │ │ 150 │ │
|
||||||
|
│ └─────────────────┘ └──────────┬──────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌─────────────────────┐ │
|
||||||
|
│ │ DEMAND SIGNAL │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Tomatoes: 180kg/wk │ │
|
||||||
|
│ │ Lettuce: 95kg/wk │ │
|
||||||
|
│ │ Basil: 15kg/wk │ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ Gap: 120kg tomatoes │ │
|
||||||
|
│ │ Status: SHORTAGE │ │
|
||||||
|
│ └─────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Consumer Preferences
|
||||||
|
|
||||||
|
### Preference Structure
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface ConsumerPreference {
|
||||||
|
// Who
|
||||||
|
consumerId: string;
|
||||||
|
location: { latitude, longitude, maxDeliveryRadiusKm };
|
||||||
|
|
||||||
|
// What they want
|
||||||
|
preferredCategories: ['leafy_greens', 'herbs', 'nightshades'];
|
||||||
|
preferredItems: [
|
||||||
|
{ produceType: 'tomato', priority: 'must_have', weeklyQuantity: 2 },
|
||||||
|
{ produceType: 'lettuce', priority: 'preferred', weeklyQuantity: 1 },
|
||||||
|
{ produceType: 'basil', priority: 'nice_to_have' }
|
||||||
|
];
|
||||||
|
|
||||||
|
// How they want it
|
||||||
|
certificationPreferences: ['organic', 'local'];
|
||||||
|
deliveryPreferences: {
|
||||||
|
method: ['home_delivery', 'farmers_market'],
|
||||||
|
frequency: 'weekly'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Constraints
|
||||||
|
householdSize: 4;
|
||||||
|
weeklyBudget: 75;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Priority Levels
|
||||||
|
|
||||||
|
| Priority | Weight | Meaning |
|
||||||
|
|----------|--------|---------|
|
||||||
|
| must_have | 10 | Essential - will seek elsewhere if not available |
|
||||||
|
| preferred | 7 | Strong preference - important for satisfaction |
|
||||||
|
| nice_to_have | 4 | Would enjoy - not essential |
|
||||||
|
| occasional | 2 | Sometimes interested - opportunistic |
|
||||||
|
|
||||||
|
## Demand Signals
|
||||||
|
|
||||||
|
### Regional Aggregation
|
||||||
|
|
||||||
|
Demand signals aggregate preferences within a geographic region:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface DemandSignal {
|
||||||
|
region: { centerLat, centerLon, radiusKm, name };
|
||||||
|
seasonalPeriod: 'summer';
|
||||||
|
|
||||||
|
demandItems: [
|
||||||
|
{
|
||||||
|
produceType: 'tomato',
|
||||||
|
weeklyDemandKg: 180,
|
||||||
|
consumerCount: 95,
|
||||||
|
aggregatePriority: 8.5,
|
||||||
|
urgency: 'this_week',
|
||||||
|
inSeason: true,
|
||||||
|
matchedSupply: 60,
|
||||||
|
gapKg: 120 // Opportunity for growers!
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
supplyStatus: 'shortage'; // surplus | balanced | shortage | critical
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Supply Matching
|
||||||
|
|
||||||
|
The system continuously matches demand with available supply:
|
||||||
|
|
||||||
|
```
|
||||||
|
Demand: 180 kg/week tomatoes
|
||||||
|
|
||||||
|
Current Supply:
|
||||||
|
- Grower A: 30 kg committed
|
||||||
|
- Grower B: 20 kg committed
|
||||||
|
- Vertical Farm X: 10 kg committed
|
||||||
|
Total: 60 kg
|
||||||
|
|
||||||
|
Gap: 120 kg/week
|
||||||
|
|
||||||
|
→ Generate planting recommendations for growers
|
||||||
|
→ Vertical farms can respond within 6-8 weeks
|
||||||
|
→ Traditional growers plan for next season
|
||||||
|
```
|
||||||
|
|
||||||
|
## Planting Recommendations
|
||||||
|
|
||||||
|
### Recommendation Engine
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Generate recommendations for a grower
|
||||||
|
const recommendations = forecaster.generatePlantingRecommendations(
|
||||||
|
growerId,
|
||||||
|
growerLat,
|
||||||
|
growerLon,
|
||||||
|
deliveryRadiusKm: 50,
|
||||||
|
availableSpaceSqm: 1000,
|
||||||
|
season: 'summer'
|
||||||
|
);
|
||||||
|
|
||||||
|
// Returns prioritized recommendations
|
||||||
|
[
|
||||||
|
{
|
||||||
|
produceType: 'tomato',
|
||||||
|
recommendedQuantity: 200, // sqm
|
||||||
|
expectedYieldKg: 1600,
|
||||||
|
projectedDemandKg: 480, // monthly gap
|
||||||
|
projectedPricePerKg: 4.50,
|
||||||
|
projectedRevenue: 7200,
|
||||||
|
riskFactors: [...],
|
||||||
|
explanation: "Based on 3 demand signals showing 120kg weekly gap..."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
produceType: 'lettuce',
|
||||||
|
recommendedQuantity: 100,
|
||||||
|
...
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### Risk Assessment
|
||||||
|
|
||||||
|
Each recommendation includes risk factors:
|
||||||
|
|
||||||
|
| Risk Type | Description | Mitigation |
|
||||||
|
|-----------|-------------|------------|
|
||||||
|
| weather | Outdoor crop vulnerable to conditions | Consider greenhouse |
|
||||||
|
| oversupply | Other growers may fill gap | Start smaller, scale up |
|
||||||
|
| market | Demand may shift | Diversify crops |
|
||||||
|
| pest | Known pest pressure in region | Preventive measures |
|
||||||
|
|
||||||
|
## Seasonal Planning
|
||||||
|
|
||||||
|
### Seasonal Crop Calendar
|
||||||
|
|
||||||
|
```
|
||||||
|
SPRING (Mar-May)
|
||||||
|
├── Lettuce ★★★★★
|
||||||
|
├── Spinach ★★★★★
|
||||||
|
├── Peas ★★★★☆
|
||||||
|
├── Radishes ★★★★☆
|
||||||
|
└── Carrots ★★★☆☆
|
||||||
|
|
||||||
|
SUMMER (Jun-Aug)
|
||||||
|
├── Tomatoes ★★★★★
|
||||||
|
├── Peppers ★★★★★
|
||||||
|
├── Cucumbers ★★★★☆
|
||||||
|
├── Basil ★★★★★
|
||||||
|
└── Beans ★★★★☆
|
||||||
|
|
||||||
|
FALL (Sep-Nov)
|
||||||
|
├── Kale ★★★★★
|
||||||
|
├── Broccoli ★★★★☆
|
||||||
|
├── Brussels Sprouts ★★★★☆
|
||||||
|
├── Squash ★★★★☆
|
||||||
|
└── Lettuce ★★★★★
|
||||||
|
|
||||||
|
WINTER (Dec-Feb)
|
||||||
|
├── Microgreens ★★★★★ (indoor)
|
||||||
|
├── Sprouts ★★★★★ (indoor)
|
||||||
|
├── Kale ★★★★☆
|
||||||
|
└── Spinach ★★★☆☆
|
||||||
|
|
||||||
|
★ = Demand strength in season
|
||||||
|
```
|
||||||
|
|
||||||
|
### Year-Round Planning with Vertical Farms
|
||||||
|
|
||||||
|
Vertical farms can produce year-round, filling seasonal gaps:
|
||||||
|
|
||||||
|
```
|
||||||
|
Traditional Supply: ████████████░░░░░░░░░░████████████
|
||||||
|
Vertical Farm: ████████████████████████████████████
|
||||||
|
Combined: ████████████████████████████████████
|
||||||
|
|
||||||
|
Legend: █ = Supply available, ░ = Gap
|
||||||
|
```
|
||||||
|
|
||||||
|
## Forecasting Models
|
||||||
|
|
||||||
|
### Simple Moving Average
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Basic forecast from historical data
|
||||||
|
const forecast = history.reduce((a, b) => a + b, 0) / history.length;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Seasonal Adjustment
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Apply seasonal multiplier
|
||||||
|
const seasonalFactor = isInSeason(produceType, currentSeason) ? 1.2 : 0.6;
|
||||||
|
const adjustedForecast = baseForecast * seasonalFactor;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Trend Analysis
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Detect growing/declining interest
|
||||||
|
const trend = (latestDemand - earliestDemand) / dataPoints;
|
||||||
|
const futureDemand = currentDemand + (trend * weeksAhead);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration Points
|
||||||
|
|
||||||
|
### With Transport System
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Transport events update demand fulfillment
|
||||||
|
transportChain.recordEvent({
|
||||||
|
eventType: 'distribution',
|
||||||
|
batchIds: ['harvest-123'],
|
||||||
|
destinationType: 'consumer',
|
||||||
|
// Updates matched supply in demand signals
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Vertical Farms
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Vertical farms can respond quickly to demand signals
|
||||||
|
const gaps = demandSignal.demandItems.filter(i => i.gapKg > 0 && i.urgency === 'this_week');
|
||||||
|
|
||||||
|
for (const gap of gaps) {
|
||||||
|
if (canGrowInVerticalFarm(gap.produceType)) {
|
||||||
|
// Start batch immediately
|
||||||
|
verticalFarmController.startCropBatch(...);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Blockchain
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// All demand signals and commitments recorded on chain
|
||||||
|
plantChain.recordDemandSignal(signal);
|
||||||
|
plantChain.recordSupplyCommitment(commitment);
|
||||||
|
plantChain.recordMarketMatch(match);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### For Consumers
|
||||||
|
|
||||||
|
1. **Set Preferences Early**: The more lead time, the better matching
|
||||||
|
2. **Be Flexible**: Mark some items as "nice to have"
|
||||||
|
3. **Accept Seasonal Variety**: In-season produce is better and cheaper
|
||||||
|
4. **Provide Feedback**: Help improve recommendations
|
||||||
|
|
||||||
|
### For Growers
|
||||||
|
|
||||||
|
1. **Register Capacity**: Let the system know what you can grow
|
||||||
|
2. **Follow Recommendations**: Data-driven planting reduces risk
|
||||||
|
3. **Commit Early**: Pre-committed supply gets priority matching
|
||||||
|
4. **Diversify**: Grow multiple crops to balance risk
|
||||||
|
|
||||||
|
### For the System
|
||||||
|
|
||||||
|
1. **Aggregate Regionally**: Local matching reduces transport
|
||||||
|
2. **Prioritize Seasonal**: In-season crops have better yields
|
||||||
|
3. **Balance Supply**: Prevent oversupply situations
|
||||||
|
4. **Learn Continuously**: Improve forecasts from actual outcomes
|
||||||
|
|
||||||
|
## Environmental Impact
|
||||||
|
|
||||||
|
### Demand-Driven Benefits
|
||||||
|
|
||||||
|
| Metric | Traditional | Demand-Driven | Savings |
|
||||||
|
|--------|-------------|---------------|---------|
|
||||||
|
| Food Waste | 30-40% | 5-10% | 75% reduction |
|
||||||
|
| Transport Miles | 1,500 avg | < 50 local | 97% reduction |
|
||||||
|
| Unsold Produce | 20% | < 5% | 75% reduction |
|
||||||
|
| Energy (storage) | High | Minimal | 80% reduction |
|
||||||
|
|
||||||
|
### Carbon Calculation
|
||||||
|
|
||||||
|
```
|
||||||
|
Traditional Tomato (California to NYC):
|
||||||
|
Growing: 0.5 kg CO2/kg
|
||||||
|
Transport: 2.0 kg CO2/kg
|
||||||
|
Storage: 0.3 kg CO2/kg
|
||||||
|
Total: 2.8 kg CO2/kg
|
||||||
|
|
||||||
|
LocalGreenChain Tomato (local vertical farm):
|
||||||
|
Growing: 0.3 kg CO2/kg
|
||||||
|
Transport: 0.02 kg CO2/kg
|
||||||
|
Storage: 0.0 kg CO2/kg
|
||||||
|
Total: 0.32 kg CO2/kg
|
||||||
|
|
||||||
|
Savings: 89% carbon reduction
|
||||||
|
```
|
||||||
277
docs/concepts/seed-to-seed-transport.md
Normal file
277
docs/concepts/seed-to-seed-transport.md
Normal file
|
|
@ -0,0 +1,277 @@
|
||||||
|
# Seed-to-Seed Transport System
|
||||||
|
|
||||||
|
The Seed-to-Seed Transport System is the core tracking mechanism of LocalGreenChain. It provides complete traceability from the moment a seed is sourced, through planting, growing, harvesting, consumption, and back to seed collection for the next generation.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ SEED-TO-SEED LIFECYCLE │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||||
|
│ │ SEED │───→│ TRANSIT │───→│ PLANT │───→│ GROW │ │
|
||||||
|
│ │ SOURCE │ │ TO FARM │ │ │ │ │ │
|
||||||
|
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
||||||
|
│ │ │ │
|
||||||
|
│ │ ▼ │
|
||||||
|
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
||||||
|
│ │ │ SEED │←───│ HARVEST │←───│ MATURE │ │
|
||||||
|
│ │ │ SAVE │ │ │ │ │ │
|
||||||
|
│ │ └──────────┘ └──────────┘ └──────────┘ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ │ ▼ ▼ │
|
||||||
|
│ │ ┌──────────┐ ┌──────────┐ │
|
||||||
|
│ └────────→│ NEXT │ │ TRANSPORT │ │
|
||||||
|
│ │ GENERATION│ │ TO MARKET │ │
|
||||||
|
│ └──────────┘ └──────────┘ │
|
||||||
|
│ │ │
|
||||||
|
│ ▼ │
|
||||||
|
│ ┌──────────┐ │
|
||||||
|
│ │ CONSUMER │ │
|
||||||
|
│ │ DELIVERY │ │
|
||||||
|
│ └──────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Transport Event Types
|
||||||
|
|
||||||
|
### 1. Seed Acquisition Transport
|
||||||
|
Track seeds from their source to the growing location.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface SeedAcquisitionTransport {
|
||||||
|
eventType: 'seed_acquisition';
|
||||||
|
seedBatchId: string;
|
||||||
|
sourceType: 'seed_bank' | 'previous_harvest' | 'trade' | 'purchase' | 'wild_collected';
|
||||||
|
sourceLocation: TransportLocation;
|
||||||
|
destinationLocation: TransportLocation;
|
||||||
|
quantity: number;
|
||||||
|
unit: 'seeds' | 'grams' | 'packets';
|
||||||
|
geneticLineageId?: string;
|
||||||
|
certifications?: string[];
|
||||||
|
transportMethod: TransportMethod;
|
||||||
|
environmentalConditions: TransportEnvironment;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Growing Transport
|
||||||
|
Track movement of plants during their growing lifecycle (transplanting, relocation).
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface GrowingTransport {
|
||||||
|
eventType: 'growing_transport';
|
||||||
|
plantId: string;
|
||||||
|
reason: 'transplant' | 'relocation' | 'hardening_off' | 'seasonal_move';
|
||||||
|
fromLocation: TransportLocation;
|
||||||
|
toLocation: TransportLocation;
|
||||||
|
plantStage: PlantStage;
|
||||||
|
handlingMethod: 'bare_root' | 'potted' | 'root_ball' | 'hydroponic_transfer';
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Harvest Transport
|
||||||
|
Track movement from growing site to processing/distribution.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface HarvestTransport {
|
||||||
|
eventType: 'harvest_transport';
|
||||||
|
plantIds: string[];
|
||||||
|
harvestBatchId: string;
|
||||||
|
fromLocation: TransportLocation;
|
||||||
|
toLocation: TransportLocation;
|
||||||
|
harvestType: 'full_harvest' | 'partial_harvest' | 'continuous_harvest';
|
||||||
|
produceWeight: number;
|
||||||
|
unit: 'kg' | 'lbs' | 'units';
|
||||||
|
packagingType: string;
|
||||||
|
temperatureRequired: TemperatureRange;
|
||||||
|
shelfLife: number; // hours
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Distribution Transport
|
||||||
|
Track movement through distribution chain to consumers.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface DistributionTransport {
|
||||||
|
eventType: 'distribution';
|
||||||
|
harvestBatchId: string;
|
||||||
|
fromLocation: TransportLocation;
|
||||||
|
toLocation: TransportLocation;
|
||||||
|
destinationType: 'consumer' | 'market' | 'restaurant' | 'processor' | 'vertical_farm';
|
||||||
|
transportMethod: TransportMethod;
|
||||||
|
carbonFootprint: number; // kg CO2
|
||||||
|
distance: number; // km
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 5. Seed Saving Transport
|
||||||
|
Track seed collection and storage for next generation.
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface SeedSavingTransport {
|
||||||
|
eventType: 'seed_saving';
|
||||||
|
parentPlantIds: string[];
|
||||||
|
newSeedBatchId: string;
|
||||||
|
seedCount: number;
|
||||||
|
viabilityTestDate?: string;
|
||||||
|
germinationRate?: number;
|
||||||
|
storageLocation: TransportLocation;
|
||||||
|
storageConditions: SeedStorageConditions;
|
||||||
|
nextGenerationId: string;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Blockchain Recording
|
||||||
|
|
||||||
|
Each transport event creates a new block in the user's blockchain:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface TransportBlock {
|
||||||
|
index: number;
|
||||||
|
timestamp: string;
|
||||||
|
transportEvent: TransportEvent;
|
||||||
|
previousHash: string;
|
||||||
|
hash: string;
|
||||||
|
nonce: number;
|
||||||
|
|
||||||
|
// Additional verification
|
||||||
|
signatures: {
|
||||||
|
sender?: string;
|
||||||
|
receiver?: string;
|
||||||
|
verifier?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Environmental impact
|
||||||
|
carbonFootprint: number;
|
||||||
|
foodMiles: number;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Chain of Custody
|
||||||
|
|
||||||
|
The system maintains an unbroken chain of custody:
|
||||||
|
|
||||||
|
```
|
||||||
|
Seed Bank (Block #1)
|
||||||
|
↓ Transport: seed_acquisition
|
||||||
|
Grower A (Block #2)
|
||||||
|
↓ Plant & Grow
|
||||||
|
Grower A (Block #3-20) [growth updates]
|
||||||
|
↓ Transport: harvest_transport
|
||||||
|
Local Hub (Block #21)
|
||||||
|
↓ Transport: distribution
|
||||||
|
Consumer B (Block #22)
|
||||||
|
↓ Consume & Save Seeds
|
||||||
|
Seed Storage (Block #23)
|
||||||
|
↓ Transport: seed_saving → New lineage begins
|
||||||
|
```
|
||||||
|
|
||||||
|
## Environmental Impact Tracking
|
||||||
|
|
||||||
|
Every transport calculates:
|
||||||
|
|
||||||
|
1. **Carbon Footprint**
|
||||||
|
- Vehicle emissions based on transport method
|
||||||
|
- Refrigeration/climate control energy
|
||||||
|
- Packaging materials lifecycle
|
||||||
|
|
||||||
|
2. **Food Miles**
|
||||||
|
- Actual distance traveled
|
||||||
|
- Mode-weighted distance (air vs truck vs local)
|
||||||
|
|
||||||
|
3. **Time-to-Consumer**
|
||||||
|
- Freshness degradation tracking
|
||||||
|
- Optimal delivery windows
|
||||||
|
|
||||||
|
4. **Resource Usage**
|
||||||
|
- Water (for any irrigation during transport)
|
||||||
|
- Electricity (refrigeration, lighting)
|
||||||
|
- Packaging waste
|
||||||
|
|
||||||
|
## Integration with User Blockchain
|
||||||
|
|
||||||
|
Each user's blockchain maintains their personal transport history:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
class UserTransportChain {
|
||||||
|
userId: string;
|
||||||
|
chain: TransportBlock[];
|
||||||
|
|
||||||
|
// Get all plants currently in user's possession
|
||||||
|
getCurrentInventory(): PlantInventory[];
|
||||||
|
|
||||||
|
// Get complete history for a plant
|
||||||
|
getPlantJourney(plantId: string): TransportEvent[];
|
||||||
|
|
||||||
|
// Calculate total environmental impact
|
||||||
|
getTotalFootprint(): EnvironmentalImpact;
|
||||||
|
|
||||||
|
// Get lineage across generations
|
||||||
|
getMultiGenerationLineage(plantId: string, generations: number): LineageTree;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## QR Code Integration
|
||||||
|
|
||||||
|
Each plant/batch gets a unique QR code containing:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"plantId": "lgc_abc123",
|
||||||
|
"blockchainAddress": "0x...",
|
||||||
|
"quickLookup": "https://localgreenchain.org/track/lgc_abc123",
|
||||||
|
"lineageHash": "sha256...",
|
||||||
|
"currentCustodian": "user_xyz",
|
||||||
|
"lastTransportBlock": 42
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Scanning provides instant access to:
|
||||||
|
- Complete growing history
|
||||||
|
- All transport events
|
||||||
|
- Environmental conditions throughout lifecycle
|
||||||
|
- Carbon footprint calculation
|
||||||
|
- Previous generation lineage
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
See [Transport API Documentation](../api/transport-api.md) for complete API reference.
|
||||||
|
|
||||||
|
### Key Endpoints
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/transport/seed-acquisition
|
||||||
|
POST /api/transport/growing
|
||||||
|
POST /api/transport/harvest
|
||||||
|
POST /api/transport/distribution
|
||||||
|
POST /api/transport/seed-saving
|
||||||
|
|
||||||
|
GET /api/transport/journey/:plantId
|
||||||
|
GET /api/transport/footprint/:userId
|
||||||
|
GET /api/transport/verify/:blockHash
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### For Growers
|
||||||
|
|
||||||
|
1. **Record promptly**: Log transport events as they happen
|
||||||
|
2. **Verify conditions**: Note temperature, humidity, handling
|
||||||
|
3. **Save seeds systematically**: Maintain genetic diversity
|
||||||
|
4. **Minimize transport**: Source locally, deliver locally
|
||||||
|
|
||||||
|
### For Transporters
|
||||||
|
|
||||||
|
1. **Maintain cold chain**: Use temperature loggers
|
||||||
|
2. **Optimize routes**: Reduce food miles
|
||||||
|
3. **Report delays**: Update estimated arrival times
|
||||||
|
4. **Handle gently**: Protect plant integrity
|
||||||
|
|
||||||
|
### For Consumers
|
||||||
|
|
||||||
|
1. **Scan on receipt**: Verify chain of custody
|
||||||
|
2. **Report issues**: Flag any problems immediately
|
||||||
|
3. **Save seeds**: Continue the genetic lineage
|
||||||
|
4. **Share feedback**: Help improve the system
|
||||||
305
docs/vertical-farming/README.md
Normal file
305
docs/vertical-farming/README.md
Normal file
|
|
@ -0,0 +1,305 @@
|
||||||
|
# Vertical Farming Integration
|
||||||
|
|
||||||
|
Vertical farming is a cornerstone of LocalGreenChain's mission to create demand-driven, seasonal agriculture that reduces the global footprint of human consumption. This section covers everything you need to know about integrating vertical farms into the LocalGreenChain ecosystem.
|
||||||
|
|
||||||
|
## Why Vertical Farming?
|
||||||
|
|
||||||
|
### Environmental Benefits
|
||||||
|
|
||||||
|
| Metric | Traditional Farming | Vertical Farming | Improvement |
|
||||||
|
|--------|-------------------|------------------|-------------|
|
||||||
|
| Water Usage | 100% | 5-10% | 90-95% reduction |
|
||||||
|
| Land Required | 100% | 1-2% | 98% reduction |
|
||||||
|
| Pesticides | Variable | Zero | 100% elimination |
|
||||||
|
| Food Miles | 1,500+ avg | < 10 miles | 99% reduction |
|
||||||
|
| Growing Season | Seasonal | Year-round | 365 days/year |
|
||||||
|
| Yield/sqm/year | 3-5 kg | 50-100 kg | 10-20x increase |
|
||||||
|
|
||||||
|
### Demand-Driven Production
|
||||||
|
|
||||||
|
Vertical farms enable true demand-driven agriculture:
|
||||||
|
|
||||||
|
```
|
||||||
|
Consumer Demand Signal → Planting Decision → Harvest → Delivery
|
||||||
|
Day 0 Day 1 Day 35 Day 35
|
||||||
|
|
||||||
|
Traditional: 6-12 months planning horizon
|
||||||
|
Vertical: 1-6 weeks from signal to delivery
|
||||||
|
```
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ VERTICAL FARM ECOSYSTEM │
|
||||||
|
├─────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ LOCALGREENCHAIN PLATFORM │ │
|
||||||
|
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ │ │
|
||||||
|
│ │ │ Demand │ │ Transport │ │ Blockchain │ │ │
|
||||||
|
│ │ │ Forecaster│ │ Tracker │ │ Ledger │ │ │
|
||||||
|
│ │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ │ │
|
||||||
|
│ └────────┼──────────────┼──────────────┼───────────────────────┘ │
|
||||||
|
│ │ │ │ │
|
||||||
|
│ ▼ ▼ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ VERTICAL FARM CONTROLLER │ │
|
||||||
|
│ │ ┌───────────┐ ┌───────────┐ ┌───────────┐ ┌──────────┐│ │
|
||||||
|
│ │ │Environment│ │ Nutrition │ │ Lighting │ │Automation ││ │
|
||||||
|
│ │ │ Control │ │ System │ │ System │ │ System ││ │
|
||||||
|
│ │ └─────┬─────┘ └─────┬─────┘ └─────┬─────┘ └────┬─────┘│ │
|
||||||
|
│ └────────┼──────────────┼──────────────┼─────────────┼────────┘ │
|
||||||
|
│ │ │ │ │ │
|
||||||
|
│ ▼ ▼ ▼ ▼ │
|
||||||
|
│ ┌─────────────────────────────────────────────────────────────┐ │
|
||||||
|
│ │ GROWING ZONES │ │
|
||||||
|
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
|
||||||
|
│ │ │ Zone A │ │ Zone B │ │ Zone C │ │ Zone D │ │ │
|
||||||
|
│ │ │Lettuce │ │ Basil │ │Microgrns│ │Tomatoes │ │ │
|
||||||
|
│ │ │Day 15/35│ │Day 20/42│ │Day 8/14 │ │Day 45/80│ │ │
|
||||||
|
│ │ └─────────┘ └─────────┘ └─────────┘ └─────────┘ │ │
|
||||||
|
│ └─────────────────────────────────────────────────────────────┘ │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Growing Systems
|
||||||
|
|
||||||
|
### NFT (Nutrient Film Technique)
|
||||||
|
|
||||||
|
Best for: Leafy greens, herbs
|
||||||
|
- Thin film of nutrients flows over roots
|
||||||
|
- Low water usage
|
||||||
|
- Fast growth cycles
|
||||||
|
|
||||||
|
### DWC (Deep Water Culture)
|
||||||
|
|
||||||
|
Best for: Lettuce, basil, larger plants
|
||||||
|
- Roots suspended in aerated nutrient solution
|
||||||
|
- Simple to maintain
|
||||||
|
- Excellent oxygenation
|
||||||
|
|
||||||
|
### Aeroponics
|
||||||
|
|
||||||
|
Best for: High-value crops, root vegetables
|
||||||
|
- Roots misted with nutrients
|
||||||
|
- Maximum oxygen exposure
|
||||||
|
- Fastest growth rates
|
||||||
|
|
||||||
|
### Vertical Towers/ZipGrow
|
||||||
|
|
||||||
|
Best for: Microgreens, leafy greens, strawberries
|
||||||
|
- Space-efficient vertical growing
|
||||||
|
- Easy harvest access
|
||||||
|
- Modular expansion
|
||||||
|
|
||||||
|
## Environmental Control
|
||||||
|
|
||||||
|
### Temperature Management
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
interface TemperatureControl {
|
||||||
|
// Day/night differential is crucial
|
||||||
|
dayTarget: 22; // °C
|
||||||
|
nightTarget: 18; // °C
|
||||||
|
|
||||||
|
// Acceptable ranges
|
||||||
|
minCritical: 10; // Below this: growth stops
|
||||||
|
maxCritical: 35; // Above this: heat stress
|
||||||
|
|
||||||
|
// Control systems
|
||||||
|
cooling: ['HVAC', 'evaporative', 'chilled_water'];
|
||||||
|
heating: ['heat_pump', 'radiant', 'air_handling'];
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Humidity Control
|
||||||
|
|
||||||
|
- **Seedling Stage**: 80-90% RH
|
||||||
|
- **Vegetative Stage**: 60-70% RH
|
||||||
|
- **Flowering/Fruiting**: 50-60% RH
|
||||||
|
- **Pre-Harvest**: 40-50% RH (reduces disease risk)
|
||||||
|
|
||||||
|
### CO2 Enrichment
|
||||||
|
|
||||||
|
```
|
||||||
|
Ambient CO2: ~420 ppm
|
||||||
|
Optimal Growth: 800-1200 ppm
|
||||||
|
Maximum Benefit: 1500 ppm
|
||||||
|
|
||||||
|
Warning: Above 1500 ppm provides diminishing returns
|
||||||
|
and increases operational costs
|
||||||
|
```
|
||||||
|
|
||||||
|
### Lighting
|
||||||
|
|
||||||
|
| Stage | PPFD (µmol/m²/s) | DLI (mol/m²/day) | Hours |
|
||||||
|
|-------|-----------------|------------------|-------|
|
||||||
|
| Germination | 100-150 | 4-6 | 16-18 |
|
||||||
|
| Seedling | 150-250 | 8-12 | 18 |
|
||||||
|
| Vegetative | 250-400 | 15-25 | 16-18 |
|
||||||
|
| Flowering | 400-600 | 25-40 | 12-16 |
|
||||||
|
|
||||||
|
## Growing Recipes
|
||||||
|
|
||||||
|
LocalGreenChain includes pre-configured growing recipes for common crops:
|
||||||
|
|
||||||
|
### Lettuce (35-day cycle)
|
||||||
|
```yaml
|
||||||
|
Recipe: Butterhead Lettuce - Fast Cycle
|
||||||
|
Stages:
|
||||||
|
- Germination (Day 0-3): 150 PPFD, 80% RH, EC 0.8
|
||||||
|
- Seedling (Day 4-10): 200 PPFD, 70% RH, EC 1.2
|
||||||
|
- Vegetative (Day 11-28): 300 PPFD, 65% RH, EC 1.6
|
||||||
|
- Finishing (Day 29-35): 250 PPFD, 60% RH, EC 1.2
|
||||||
|
Expected Yield: 180g per head
|
||||||
|
Yield/sqm/year: 4000g (11+ cycles)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basil (42-day cycle)
|
||||||
|
```yaml
|
||||||
|
Recipe: Genovese Basil - Aromatic
|
||||||
|
Stages:
|
||||||
|
- Germination (Day 0-5): 100 PPFD, 80% RH, EC 0.6
|
||||||
|
- Seedling (Day 6-14): 200 PPFD, 70% RH, EC 1.0
|
||||||
|
- Vegetative (Day 15-35): 400 PPFD, 65% RH, EC 1.4
|
||||||
|
- Harvest Ready (Day 36-42): 350 PPFD, 60% RH, EC 1.0
|
||||||
|
Expected Yield: 120g per plant
|
||||||
|
Yield/sqm/year: 2400g (8+ cycles)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Microgreens (14-day cycle)
|
||||||
|
```yaml
|
||||||
|
Recipe: Microgreens Mix - Quick Turn
|
||||||
|
Stages:
|
||||||
|
- Sowing (Day 0-2): Dark, 90% RH
|
||||||
|
- Germination (Day 3-5): 100 PPFD, 80% RH
|
||||||
|
- Growth (Day 6-12): 250 PPFD, 65% RH, EC 0.8
|
||||||
|
- Harvest (Day 13-14): 200 PPFD, 60% RH
|
||||||
|
Expected Yield: 200g per tray
|
||||||
|
Yield/sqm/year: 2000g (26 cycles)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Integration with LocalGreenChain
|
||||||
|
|
||||||
|
### Demand-Driven Planting
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
// Example: Demand signal triggers planting
|
||||||
|
const demandSignal = demandForecaster.generateDemandSignal(
|
||||||
|
lat, lon, radiusKm, regionName, season
|
||||||
|
);
|
||||||
|
|
||||||
|
// Find gaps in supply
|
||||||
|
const gaps = demandSignal.demandItems.filter(item => item.gapKg > 0);
|
||||||
|
|
||||||
|
// Start crop batches to fill gaps
|
||||||
|
for (const gap of gaps) {
|
||||||
|
const recipe = findBestRecipe(gap.produceType);
|
||||||
|
const batch = verticalFarmController.startCropBatch(
|
||||||
|
farmId,
|
||||||
|
availableZoneId,
|
||||||
|
recipe.id,
|
||||||
|
seedBatchId,
|
||||||
|
calculatePlantCount(gap.gapKg, recipe.expectedYieldGrams)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Blockchain Recording
|
||||||
|
|
||||||
|
Every crop batch is recorded on the blockchain:
|
||||||
|
|
||||||
|
1. **Planting Event**: Seed batch origin, recipe used, zone assigned
|
||||||
|
2. **Environment Logs**: Daily readings stored for traceability
|
||||||
|
3. **Harvest Event**: Actual yield, quality grade, destination
|
||||||
|
4. **Transport**: Movement from farm to consumer
|
||||||
|
|
||||||
|
### Consumer Traceability
|
||||||
|
|
||||||
|
Consumers can scan a QR code to see:
|
||||||
|
- Which vertical farm grew their produce
|
||||||
|
- The exact growing recipe used
|
||||||
|
- All environmental conditions during growth
|
||||||
|
- Seed lineage and generation
|
||||||
|
- Carbon footprint of production
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
### For Operators
|
||||||
|
|
||||||
|
1. **Start Small**: Begin with easy crops (lettuce, microgreens)
|
||||||
|
2. **Standardize Recipes**: Use proven recipes before experimenting
|
||||||
|
3. **Monitor Closely**: Environmental data is your best friend
|
||||||
|
4. **Maintain Equipment**: Preventive maintenance prevents crop loss
|
||||||
|
5. **Track Everything**: Data enables optimization
|
||||||
|
|
||||||
|
### For Integration
|
||||||
|
|
||||||
|
1. **Register Farm**: Add your facility to LocalGreenChain
|
||||||
|
2. **Configure Zones**: Set up growing areas with sensor integration
|
||||||
|
3. **Import Recipes**: Use community recipes or create custom ones
|
||||||
|
4. **Connect Demand**: Link to regional demand signals
|
||||||
|
5. **Record Everything**: Let the blockchain track your success
|
||||||
|
|
||||||
|
## API Reference
|
||||||
|
|
||||||
|
See [Vertical Farming API](../api/vertical-farming-api.md) for complete API documentation.
|
||||||
|
|
||||||
|
### Key Endpoints
|
||||||
|
|
||||||
|
```http
|
||||||
|
POST /api/vertical-farm/register
|
||||||
|
POST /api/vertical-farm/zone/create
|
||||||
|
POST /api/vertical-farm/batch/start
|
||||||
|
PUT /api/vertical-farm/batch/:id/environment
|
||||||
|
POST /api/vertical-farm/batch/:id/harvest
|
||||||
|
GET /api/vertical-farm/:id/analytics
|
||||||
|
GET /api/vertical-farm/recipes
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resource Efficiency
|
||||||
|
|
||||||
|
### Energy Optimization
|
||||||
|
|
||||||
|
- **LED Lighting**: 2.5-3.0 µmol/J efficacy
|
||||||
|
- **Climate Control**: Heat pump systems
|
||||||
|
- **Renewable Integration**: Solar/wind compatible
|
||||||
|
- **Off-Peak Operation**: Shift energy-intensive tasks
|
||||||
|
|
||||||
|
### Water Conservation
|
||||||
|
|
||||||
|
- **Recirculating Systems**: 95%+ water reuse
|
||||||
|
- **Condensate Recovery**: Capture HVAC moisture
|
||||||
|
- **Rainwater Harvesting**: Supplement fresh water
|
||||||
|
- **Quality Monitoring**: Prevent nutrient lockout
|
||||||
|
|
||||||
|
## Scaling Considerations
|
||||||
|
|
||||||
|
### Small Scale (< 1,000 sqm)
|
||||||
|
- Single facility management
|
||||||
|
- Manual/semi-automated
|
||||||
|
- 5-10 crop varieties
|
||||||
|
- Local market focus
|
||||||
|
|
||||||
|
### Medium Scale (1,000-10,000 sqm)
|
||||||
|
- Multi-zone automation
|
||||||
|
- Recipe optimization
|
||||||
|
- 20-30 crop varieties
|
||||||
|
- Regional distribution
|
||||||
|
|
||||||
|
### Large Scale (> 10,000 sqm)
|
||||||
|
- Full automation
|
||||||
|
- AI-driven optimization
|
||||||
|
- 50+ crop varieties
|
||||||
|
- Multi-region supply
|
||||||
|
- Integration with multiple demand centers
|
||||||
|
|
||||||
|
## Future Development
|
||||||
|
|
||||||
|
- **AI Optimization**: Machine learning for recipe improvement
|
||||||
|
- **Robotic Harvesting**: Fully automated crop handling
|
||||||
|
- **Genetic Tracking**: DNA verification of plant lineage
|
||||||
|
- **Carbon Credits**: Earn credits for sustainable production
|
||||||
|
- **Community Recipes**: Share and rate growing recipes
|
||||||
578
lib/demand/forecaster.ts
Normal file
578
lib/demand/forecaster.ts
Normal file
|
|
@ -0,0 +1,578 @@
|
||||||
|
/**
|
||||||
|
* Demand Forecaster for LocalGreenChain
|
||||||
|
* Aggregates consumer preferences and generates planting recommendations
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
ConsumerPreference,
|
||||||
|
DemandSignal,
|
||||||
|
DemandItem,
|
||||||
|
PlantingRecommendation,
|
||||||
|
DemandForecast,
|
||||||
|
ProduceForecast,
|
||||||
|
SupplyCommitment,
|
||||||
|
MarketMatch,
|
||||||
|
SeasonalPlan,
|
||||||
|
ProduceCategory,
|
||||||
|
RiskFactor
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
// Seasonal growing data for common produce
|
||||||
|
const SEASONAL_DATA: Record<string, {
|
||||||
|
categories: ProduceCategory[];
|
||||||
|
growingDays: number;
|
||||||
|
seasons: ('spring' | 'summer' | 'fall' | 'winter')[];
|
||||||
|
yieldPerSqm: number; // kg per sq meter
|
||||||
|
idealTemp: { min: number; max: number };
|
||||||
|
}> = {
|
||||||
|
'lettuce': {
|
||||||
|
categories: ['leafy_greens'],
|
||||||
|
growingDays: 45,
|
||||||
|
seasons: ['spring', 'fall'],
|
||||||
|
yieldPerSqm: 4,
|
||||||
|
idealTemp: { min: 10, max: 21 }
|
||||||
|
},
|
||||||
|
'tomato': {
|
||||||
|
categories: ['nightshades', 'fruits'],
|
||||||
|
growingDays: 80,
|
||||||
|
seasons: ['summer'],
|
||||||
|
yieldPerSqm: 8,
|
||||||
|
idealTemp: { min: 18, max: 29 }
|
||||||
|
},
|
||||||
|
'spinach': {
|
||||||
|
categories: ['leafy_greens'],
|
||||||
|
growingDays: 40,
|
||||||
|
seasons: ['spring', 'fall', 'winter'],
|
||||||
|
yieldPerSqm: 3,
|
||||||
|
idealTemp: { min: 5, max: 18 }
|
||||||
|
},
|
||||||
|
'kale': {
|
||||||
|
categories: ['leafy_greens', 'brassicas'],
|
||||||
|
growingDays: 55,
|
||||||
|
seasons: ['spring', 'fall', 'winter'],
|
||||||
|
yieldPerSqm: 3.5,
|
||||||
|
idealTemp: { min: 5, max: 24 }
|
||||||
|
},
|
||||||
|
'basil': {
|
||||||
|
categories: ['herbs'],
|
||||||
|
growingDays: 30,
|
||||||
|
seasons: ['spring', 'summer'],
|
||||||
|
yieldPerSqm: 2,
|
||||||
|
idealTemp: { min: 18, max: 29 }
|
||||||
|
},
|
||||||
|
'microgreens': {
|
||||||
|
categories: ['microgreens'],
|
||||||
|
growingDays: 14,
|
||||||
|
seasons: ['spring', 'summer', 'fall', 'winter'],
|
||||||
|
yieldPerSqm: 1.5,
|
||||||
|
idealTemp: { min: 18, max: 24 }
|
||||||
|
},
|
||||||
|
'cucumber': {
|
||||||
|
categories: ['squash'],
|
||||||
|
growingDays: 60,
|
||||||
|
seasons: ['summer'],
|
||||||
|
yieldPerSqm: 10,
|
||||||
|
idealTemp: { min: 18, max: 30 }
|
||||||
|
},
|
||||||
|
'pepper': {
|
||||||
|
categories: ['nightshades'],
|
||||||
|
growingDays: 75,
|
||||||
|
seasons: ['summer'],
|
||||||
|
yieldPerSqm: 6,
|
||||||
|
idealTemp: { min: 18, max: 29 }
|
||||||
|
},
|
||||||
|
'carrot': {
|
||||||
|
categories: ['root_vegetables'],
|
||||||
|
growingDays: 70,
|
||||||
|
seasons: ['spring', 'fall'],
|
||||||
|
yieldPerSqm: 5,
|
||||||
|
idealTemp: { min: 7, max: 24 }
|
||||||
|
},
|
||||||
|
'strawberry': {
|
||||||
|
categories: ['berries', 'fruits'],
|
||||||
|
growingDays: 90,
|
||||||
|
seasons: ['spring', 'summer'],
|
||||||
|
yieldPerSqm: 3,
|
||||||
|
idealTemp: { min: 15, max: 26 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export class DemandForecaster {
|
||||||
|
private preferences: Map<string, ConsumerPreference> = new Map();
|
||||||
|
private supplyCommitments: Map<string, SupplyCommitment> = new Map();
|
||||||
|
private demandSignals: Map<string, DemandSignal> = new Map();
|
||||||
|
private marketMatches: Map<string, MarketMatch> = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register consumer preference
|
||||||
|
*/
|
||||||
|
registerPreference(preference: ConsumerPreference): void {
|
||||||
|
this.preferences.set(preference.consumerId, preference);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register supply commitment from grower
|
||||||
|
*/
|
||||||
|
registerSupply(commitment: SupplyCommitment): void {
|
||||||
|
this.supplyCommitments.set(commitment.id, commitment);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate demand signal for a region
|
||||||
|
*/
|
||||||
|
generateDemandSignal(
|
||||||
|
centerLat: number,
|
||||||
|
centerLon: number,
|
||||||
|
radiusKm: number,
|
||||||
|
regionName: string,
|
||||||
|
season: 'spring' | 'summer' | 'fall' | 'winter'
|
||||||
|
): DemandSignal {
|
||||||
|
// Find consumers in region
|
||||||
|
const regionalConsumers = Array.from(this.preferences.values()).filter(pref => {
|
||||||
|
const distance = this.calculateDistance(
|
||||||
|
centerLat, centerLon,
|
||||||
|
pref.location.latitude, pref.location.longitude
|
||||||
|
);
|
||||||
|
return distance <= radiusKm;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Aggregate demand by produce type
|
||||||
|
const demandMap = new Map<string, {
|
||||||
|
consumers: Set<string>;
|
||||||
|
totalWeeklyKg: number;
|
||||||
|
priorities: number[];
|
||||||
|
certifications: Set<string>;
|
||||||
|
prices: number[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
for (const consumer of regionalConsumers) {
|
||||||
|
for (const item of consumer.preferredItems) {
|
||||||
|
const existing = demandMap.get(item.produceType) || {
|
||||||
|
consumers: new Set(),
|
||||||
|
totalWeeklyKg: 0,
|
||||||
|
priorities: [],
|
||||||
|
certifications: new Set(),
|
||||||
|
prices: []
|
||||||
|
};
|
||||||
|
|
||||||
|
existing.consumers.add(consumer.consumerId);
|
||||||
|
|
||||||
|
// Calculate weekly demand based on household size
|
||||||
|
const weeklyKg = (item.weeklyQuantity || 0.5) * consumer.householdSize;
|
||||||
|
existing.totalWeeklyKg += weeklyKg;
|
||||||
|
|
||||||
|
// Track priority
|
||||||
|
const priorityValue = item.priority === 'must_have' ? 10 :
|
||||||
|
item.priority === 'preferred' ? 7 :
|
||||||
|
item.priority === 'nice_to_have' ? 4 : 2;
|
||||||
|
existing.priorities.push(priorityValue);
|
||||||
|
|
||||||
|
// Track certifications
|
||||||
|
consumer.certificationPreferences.forEach(cert =>
|
||||||
|
existing.certifications.add(cert)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Track price expectations
|
||||||
|
if (consumer.weeklyBudget && consumer.preferredItems.length > 0) {
|
||||||
|
const avgPricePerItem = consumer.weeklyBudget / consumer.preferredItems.length;
|
||||||
|
existing.prices.push(avgPricePerItem / weeklyKg);
|
||||||
|
}
|
||||||
|
|
||||||
|
demandMap.set(item.produceType, existing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert to demand items
|
||||||
|
const demandItems: DemandItem[] = Array.from(demandMap.entries()).map(([produceType, data]) => {
|
||||||
|
const seasonalData = SEASONAL_DATA[produceType.toLowerCase()];
|
||||||
|
const inSeason = seasonalData?.seasons.includes(season) ?? true;
|
||||||
|
|
||||||
|
const avgPriority = data.priorities.length > 0
|
||||||
|
? data.priorities.reduce((a, b) => a + b, 0) / data.priorities.length
|
||||||
|
: 5;
|
||||||
|
|
||||||
|
const avgPrice = data.prices.length > 0
|
||||||
|
? data.prices.reduce((a, b) => a + b, 0) / data.prices.length
|
||||||
|
: 5;
|
||||||
|
|
||||||
|
return {
|
||||||
|
produceType,
|
||||||
|
category: seasonalData?.categories[0] || 'leafy_greens',
|
||||||
|
weeklyDemandKg: data.totalWeeklyKg,
|
||||||
|
monthlyDemandKg: data.totalWeeklyKg * 4,
|
||||||
|
consumerCount: data.consumers.size,
|
||||||
|
aggregatePriority: Math.round(avgPriority),
|
||||||
|
urgency: avgPriority >= 8 ? 'immediate' :
|
||||||
|
avgPriority >= 6 ? 'this_week' :
|
||||||
|
avgPriority >= 4 ? 'this_month' : 'next_season',
|
||||||
|
preferredCertifications: Array.from(data.certifications),
|
||||||
|
averageWillingPrice: Math.round(avgPrice * 100) / 100,
|
||||||
|
priceUnit: 'per_kg',
|
||||||
|
inSeason,
|
||||||
|
seasonalAvailability: {
|
||||||
|
spring: seasonalData?.seasons.includes('spring') ?? true,
|
||||||
|
summer: seasonalData?.seasons.includes('summer') ?? true,
|
||||||
|
fall: seasonalData?.seasons.includes('fall') ?? true,
|
||||||
|
winter: seasonalData?.seasons.includes('winter') ?? false
|
||||||
|
},
|
||||||
|
matchedSupply: 0,
|
||||||
|
matchedGrowers: 0,
|
||||||
|
gapKg: data.totalWeeklyKg
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
// Calculate supply matching
|
||||||
|
const regionalSupply = Array.from(this.supplyCommitments.values()).filter(supply =>
|
||||||
|
supply.status === 'available' || supply.status === 'partially_committed'
|
||||||
|
);
|
||||||
|
|
||||||
|
for (const item of demandItems) {
|
||||||
|
const matchingSupply = regionalSupply.filter(s =>
|
||||||
|
s.produceType.toLowerCase() === item.produceType.toLowerCase()
|
||||||
|
);
|
||||||
|
|
||||||
|
item.matchedSupply = matchingSupply.reduce((sum, s) => sum + s.remainingKg, 0);
|
||||||
|
item.matchedGrowers = matchingSupply.length;
|
||||||
|
item.gapKg = Math.max(0, item.weeklyDemandKg - item.matchedSupply);
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalWeeklyDemand = demandItems.reduce((sum, item) => sum + item.weeklyDemandKg, 0);
|
||||||
|
const totalSupply = demandItems.reduce((sum, item) => sum + item.matchedSupply, 0);
|
||||||
|
const totalGap = demandItems.reduce((sum, item) => sum + item.gapKg, 0);
|
||||||
|
|
||||||
|
const signal: DemandSignal = {
|
||||||
|
id: `demand-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
region: {
|
||||||
|
centerLat,
|
||||||
|
centerLon,
|
||||||
|
radiusKm,
|
||||||
|
name: regionName
|
||||||
|
},
|
||||||
|
periodStart: new Date().toISOString(),
|
||||||
|
periodEnd: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000).toISOString(),
|
||||||
|
seasonalPeriod: season,
|
||||||
|
demandItems: demandItems.sort((a, b) => b.aggregatePriority - a.aggregatePriority),
|
||||||
|
totalConsumers: regionalConsumers.length,
|
||||||
|
totalWeeklyDemandKg: totalWeeklyDemand,
|
||||||
|
confidenceLevel: Math.min(100, regionalConsumers.length * 2),
|
||||||
|
currentSupplyKg: totalSupply,
|
||||||
|
supplyGapKg: totalGap,
|
||||||
|
supplyStatus: totalGap <= 0 ? 'surplus' :
|
||||||
|
totalGap < totalWeeklyDemand * 0.1 ? 'balanced' :
|
||||||
|
totalGap < totalWeeklyDemand * 0.3 ? 'shortage' : 'critical'
|
||||||
|
};
|
||||||
|
|
||||||
|
this.demandSignals.set(signal.id, signal);
|
||||||
|
return signal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate planting recommendations for a grower
|
||||||
|
*/
|
||||||
|
generatePlantingRecommendations(
|
||||||
|
growerId: string,
|
||||||
|
growerLat: number,
|
||||||
|
growerLon: number,
|
||||||
|
deliveryRadiusKm: number,
|
||||||
|
availableSpaceSqm: number,
|
||||||
|
season: 'spring' | 'summer' | 'fall' | 'winter'
|
||||||
|
): PlantingRecommendation[] {
|
||||||
|
const recommendations: PlantingRecommendation[] = [];
|
||||||
|
|
||||||
|
// Find relevant demand signals
|
||||||
|
const relevantSignals = Array.from(this.demandSignals.values()).filter(signal => {
|
||||||
|
const distance = this.calculateDistance(
|
||||||
|
growerLat, growerLon,
|
||||||
|
signal.region.centerLat, signal.region.centerLon
|
||||||
|
);
|
||||||
|
return distance <= deliveryRadiusKm + signal.region.radiusKm &&
|
||||||
|
signal.seasonalPeriod === season;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Aggregate demand items across signals
|
||||||
|
const aggregatedDemand = new Map<string, {
|
||||||
|
totalGapKg: number;
|
||||||
|
avgPrice: number;
|
||||||
|
avgPriority: number;
|
||||||
|
signalIds: string[];
|
||||||
|
}>();
|
||||||
|
|
||||||
|
for (const signal of relevantSignals) {
|
||||||
|
for (const item of signal.demandItems) {
|
||||||
|
if (item.gapKg > 0 && item.inSeason) {
|
||||||
|
const existing = aggregatedDemand.get(item.produceType) || {
|
||||||
|
totalGapKg: 0,
|
||||||
|
avgPrice: 0,
|
||||||
|
avgPriority: 0,
|
||||||
|
signalIds: []
|
||||||
|
};
|
||||||
|
|
||||||
|
existing.totalGapKg += item.gapKg;
|
||||||
|
existing.avgPrice = (existing.avgPrice * existing.signalIds.length + item.averageWillingPrice) /
|
||||||
|
(existing.signalIds.length + 1);
|
||||||
|
existing.avgPriority = (existing.avgPriority * existing.signalIds.length + item.aggregatePriority) /
|
||||||
|
(existing.signalIds.length + 1);
|
||||||
|
existing.signalIds.push(signal.id);
|
||||||
|
|
||||||
|
aggregatedDemand.set(item.produceType, existing);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort by opportunity score (gap * price * priority)
|
||||||
|
const sortedOpportunities = Array.from(aggregatedDemand.entries())
|
||||||
|
.map(([produceType, data]) => ({
|
||||||
|
produceType,
|
||||||
|
...data,
|
||||||
|
score: data.totalGapKg * data.avgPrice * data.avgPriority / 100
|
||||||
|
}))
|
||||||
|
.sort((a, b) => b.score - a.score);
|
||||||
|
|
||||||
|
// Allocate space to top opportunities
|
||||||
|
let remainingSpace = availableSpaceSqm;
|
||||||
|
|
||||||
|
for (const opportunity of sortedOpportunities) {
|
||||||
|
if (remainingSpace <= 0) break;
|
||||||
|
|
||||||
|
const seasonalData = SEASONAL_DATA[opportunity.produceType.toLowerCase()];
|
||||||
|
if (!seasonalData) continue;
|
||||||
|
|
||||||
|
// Calculate space needed
|
||||||
|
const yieldPerSqm = seasonalData.yieldPerSqm;
|
||||||
|
const neededSpace = Math.min(
|
||||||
|
remainingSpace,
|
||||||
|
opportunity.totalGapKg / yieldPerSqm
|
||||||
|
);
|
||||||
|
|
||||||
|
if (neededSpace < 1) continue;
|
||||||
|
|
||||||
|
const expectedYield = neededSpace * yieldPerSqm;
|
||||||
|
const projectedRevenue = expectedYield * opportunity.avgPrice;
|
||||||
|
|
||||||
|
// Assess risks
|
||||||
|
const riskFactors: RiskFactor[] = [];
|
||||||
|
|
||||||
|
if (seasonalData.seasons.length === 1) {
|
||||||
|
riskFactors.push({
|
||||||
|
type: 'weather',
|
||||||
|
severity: 'medium',
|
||||||
|
description: 'Single season crop with weather sensitivity',
|
||||||
|
mitigationSuggestion: 'Consider greenhouse/vertical farm growing'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opportunity.totalGapKg > expectedYield * 3) {
|
||||||
|
riskFactors.push({
|
||||||
|
type: 'market',
|
||||||
|
severity: 'low',
|
||||||
|
description: 'Strong demand exceeds your capacity',
|
||||||
|
mitigationSuggestion: 'Consider partnering with other growers'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opportunity.totalGapKg < expectedYield * 0.5) {
|
||||||
|
riskFactors.push({
|
||||||
|
type: 'oversupply',
|
||||||
|
severity: 'medium',
|
||||||
|
description: 'Risk of oversupply if demand doesn\'t grow',
|
||||||
|
mitigationSuggestion: 'Start with smaller quantity and scale up'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const overallRisk = riskFactors.some(r => r.severity === 'high') ? 'high' :
|
||||||
|
riskFactors.some(r => r.severity === 'medium') ? 'medium' : 'low';
|
||||||
|
|
||||||
|
const plantByDate = new Date();
|
||||||
|
const harvestStart = new Date(plantByDate.getTime() + seasonalData.growingDays * 24 * 60 * 60 * 1000);
|
||||||
|
const harvestEnd = new Date(harvestStart.getTime() + 21 * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
recommendations.push({
|
||||||
|
id: `rec-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
growerId,
|
||||||
|
produceType: opportunity.produceType,
|
||||||
|
category: seasonalData.categories[0],
|
||||||
|
recommendedQuantity: Math.round(neededSpace),
|
||||||
|
quantityUnit: 'sqm',
|
||||||
|
expectedYieldKg: Math.round(expectedYield * 10) / 10,
|
||||||
|
yieldConfidence: 75,
|
||||||
|
plantByDate: plantByDate.toISOString(),
|
||||||
|
expectedHarvestStart: harvestStart.toISOString(),
|
||||||
|
expectedHarvestEnd: harvestEnd.toISOString(),
|
||||||
|
growingDays: seasonalData.growingDays,
|
||||||
|
projectedDemandKg: opportunity.totalGapKg,
|
||||||
|
projectedPricePerKg: Math.round(opportunity.avgPrice * 100) / 100,
|
||||||
|
projectedRevenue: Math.round(projectedRevenue * 100) / 100,
|
||||||
|
marketConfidence: Math.min(90, 50 + opportunity.signalIds.length * 10),
|
||||||
|
riskFactors,
|
||||||
|
overallRisk,
|
||||||
|
demandSignalIds: opportunity.signalIds,
|
||||||
|
explanation: `Based on ${opportunity.signalIds.length} demand signal(s) showing a gap of ${Math.round(opportunity.totalGapKg)}kg ` +
|
||||||
|
`for ${opportunity.produceType}. With ${Math.round(neededSpace)} sqm, you can produce approximately ${Math.round(expectedYield)}kg ` +
|
||||||
|
`at an expected price of $${opportunity.avgPrice.toFixed(2)}/kg.`
|
||||||
|
});
|
||||||
|
|
||||||
|
remainingSpace -= neededSpace;
|
||||||
|
}
|
||||||
|
|
||||||
|
return recommendations;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate demand forecast
|
||||||
|
*/
|
||||||
|
generateForecast(
|
||||||
|
regionName: string,
|
||||||
|
forecastWeeks: number = 12
|
||||||
|
): DemandForecast {
|
||||||
|
const forecasts: ProduceForecast[] = [];
|
||||||
|
|
||||||
|
// Get historical demand signals for the region
|
||||||
|
const historicalSignals = Array.from(this.demandSignals.values())
|
||||||
|
.filter(s => s.region.name === regionName)
|
||||||
|
.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
||||||
|
|
||||||
|
// Aggregate by produce type
|
||||||
|
const produceHistory = new Map<string, number[]>();
|
||||||
|
|
||||||
|
for (const signal of historicalSignals) {
|
||||||
|
for (const item of signal.demandItems) {
|
||||||
|
const history = produceHistory.get(item.produceType) || [];
|
||||||
|
history.push(item.weeklyDemandKg);
|
||||||
|
produceHistory.set(item.produceType, history);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate forecasts
|
||||||
|
for (const [produceType, history] of produceHistory) {
|
||||||
|
if (history.length === 0) continue;
|
||||||
|
|
||||||
|
const avgDemand = history.reduce((a, b) => a + b, 0) / history.length;
|
||||||
|
const trend = history.length > 1
|
||||||
|
? (history[history.length - 1] - history[0]) / history.length
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
const seasonalData = SEASONAL_DATA[produceType.toLowerCase()];
|
||||||
|
const currentSeason = this.getCurrentSeason();
|
||||||
|
const seasonalFactor = seasonalData?.seasons.includes(currentSeason) ? 1.2 : 0.6;
|
||||||
|
|
||||||
|
const predictedDemand = (avgDemand + trend * forecastWeeks) * seasonalFactor;
|
||||||
|
|
||||||
|
forecasts.push({
|
||||||
|
produceType,
|
||||||
|
category: seasonalData?.categories[0] || 'leafy_greens',
|
||||||
|
predictedDemandKg: Math.round(predictedDemand * 10) / 10,
|
||||||
|
confidenceInterval: {
|
||||||
|
low: Math.round(predictedDemand * 0.7 * 10) / 10,
|
||||||
|
high: Math.round(predictedDemand * 1.3 * 10) / 10
|
||||||
|
},
|
||||||
|
confidence: Math.min(95, 50 + history.length * 5),
|
||||||
|
trend: trend > 0.1 ? 'increasing' : trend < -0.1 ? 'decreasing' : 'stable',
|
||||||
|
trendStrength: Math.min(100, Math.abs(trend) * 100),
|
||||||
|
seasonalFactor,
|
||||||
|
predictedPricePerKg: 5, // Default price
|
||||||
|
priceConfidenceInterval: { low: 3, high: 8 },
|
||||||
|
factors: [
|
||||||
|
{
|
||||||
|
name: 'Seasonal adjustment',
|
||||||
|
type: 'seasonal',
|
||||||
|
impact: Math.round((seasonalFactor - 1) * 100),
|
||||||
|
description: seasonalData?.seasons.includes(currentSeason)
|
||||||
|
? 'In season - higher demand expected'
|
||||||
|
: 'Out of season - lower demand expected'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Historical trend',
|
||||||
|
type: 'trend',
|
||||||
|
impact: Math.round(trend * 10),
|
||||||
|
description: trend > 0 ? 'Growing popularity' : trend < 0 ? 'Declining interest' : 'Stable demand'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: `forecast-${Date.now()}`,
|
||||||
|
generatedAt: new Date().toISOString(),
|
||||||
|
region: regionName,
|
||||||
|
forecastPeriod: {
|
||||||
|
start: new Date().toISOString(),
|
||||||
|
end: new Date(Date.now() + forecastWeeks * 7 * 24 * 60 * 60 * 1000).toISOString()
|
||||||
|
},
|
||||||
|
forecasts: forecasts.sort((a, b) => b.predictedDemandKg - a.predictedDemandKg),
|
||||||
|
modelVersion: '1.0.0',
|
||||||
|
dataPointsUsed: historicalSignals.length,
|
||||||
|
lastTrainingDate: new Date().toISOString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private getCurrentSeason(): 'spring' | 'summer' | 'fall' | 'winter' {
|
||||||
|
const month = new Date().getMonth();
|
||||||
|
if (month >= 2 && month <= 4) return 'spring';
|
||||||
|
if (month >= 5 && month <= 7) return 'summer';
|
||||||
|
if (month >= 8 && month <= 10) return 'fall';
|
||||||
|
return 'winter';
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
|
||||||
|
const R = 6371;
|
||||||
|
const dLat = (lat2 - lat1) * Math.PI / 180;
|
||||||
|
const dLon = (lon2 - lon1) * Math.PI / 180;
|
||||||
|
const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||||
|
Math.cos(lat1 * Math.PI / 180) * Math.cos(lat2 * Math.PI / 180) *
|
||||||
|
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||||
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
return R * c;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export state
|
||||||
|
*/
|
||||||
|
toJSON(): object {
|
||||||
|
return {
|
||||||
|
preferences: Array.from(this.preferences.entries()),
|
||||||
|
supplyCommitments: Array.from(this.supplyCommitments.entries()),
|
||||||
|
demandSignals: Array.from(this.demandSignals.entries()),
|
||||||
|
marketMatches: Array.from(this.marketMatches.entries())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import state
|
||||||
|
*/
|
||||||
|
static fromJSON(data: any): DemandForecaster {
|
||||||
|
const forecaster = new DemandForecaster();
|
||||||
|
if (data.preferences) {
|
||||||
|
for (const [key, value] of data.preferences) {
|
||||||
|
forecaster.preferences.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.supplyCommitments) {
|
||||||
|
for (const [key, value] of data.supplyCommitments) {
|
||||||
|
forecaster.supplyCommitments.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.demandSignals) {
|
||||||
|
for (const [key, value] of data.demandSignals) {
|
||||||
|
forecaster.demandSignals.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.marketMatches) {
|
||||||
|
for (const [key, value] of data.marketMatches) {
|
||||||
|
forecaster.marketMatches.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return forecaster;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singleton instance
|
||||||
|
let forecasterInstance: DemandForecaster | null = null;
|
||||||
|
|
||||||
|
export function getDemandForecaster(): DemandForecaster {
|
||||||
|
if (!forecasterInstance) {
|
||||||
|
forecasterInstance = new DemandForecaster();
|
||||||
|
}
|
||||||
|
return forecasterInstance;
|
||||||
|
}
|
||||||
379
lib/demand/types.ts
Normal file
379
lib/demand/types.ts
Normal file
|
|
@ -0,0 +1,379 @@
|
||||||
|
/**
|
||||||
|
* Demand Forecasting Types for LocalGreenChain
|
||||||
|
* Enables demand-driven, seasonal agriculture planning
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Consumer preference for produce
|
||||||
|
export interface ConsumerPreference {
|
||||||
|
consumerId: string;
|
||||||
|
createdAt: string;
|
||||||
|
updatedAt: string;
|
||||||
|
|
||||||
|
// Location for local matching
|
||||||
|
location: {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
maxDeliveryRadiusKm: number;
|
||||||
|
city?: string;
|
||||||
|
region?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dietary preferences
|
||||||
|
dietaryType: ('omnivore' | 'vegetarian' | 'vegan' | 'pescatarian' | 'flexitarian')[];
|
||||||
|
allergies: string[];
|
||||||
|
dislikes: string[];
|
||||||
|
|
||||||
|
// Produce preferences
|
||||||
|
preferredCategories: ProduceCategory[];
|
||||||
|
preferredItems: ProducePreference[];
|
||||||
|
|
||||||
|
// Quality preferences
|
||||||
|
certificationPreferences: ('organic' | 'non_gmo' | 'biodynamic' | 'local' | 'heirloom')[];
|
||||||
|
freshnessImportance: 1 | 2 | 3 | 4 | 5;
|
||||||
|
priceImportance: 1 | 2 | 3 | 4 | 5;
|
||||||
|
sustainabilityImportance: 1 | 2 | 3 | 4 | 5;
|
||||||
|
|
||||||
|
// Delivery preferences
|
||||||
|
deliveryPreferences: {
|
||||||
|
method: ('home_delivery' | 'pickup_point' | 'farmers_market' | 'csa')[];
|
||||||
|
frequency: 'daily' | 'twice_weekly' | 'weekly' | 'bi_weekly' | 'monthly';
|
||||||
|
preferredDays: ('monday' | 'tuesday' | 'wednesday' | 'thursday' | 'friday' | 'saturday' | 'sunday')[];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Household info
|
||||||
|
householdSize: number;
|
||||||
|
weeklyBudget?: number;
|
||||||
|
currency?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProduceCategory =
|
||||||
|
| 'leafy_greens'
|
||||||
|
| 'root_vegetables'
|
||||||
|
| 'nightshades'
|
||||||
|
| 'brassicas'
|
||||||
|
| 'alliums'
|
||||||
|
| 'legumes'
|
||||||
|
| 'squash'
|
||||||
|
| 'herbs'
|
||||||
|
| 'microgreens'
|
||||||
|
| 'sprouts'
|
||||||
|
| 'mushrooms'
|
||||||
|
| 'fruits'
|
||||||
|
| 'berries'
|
||||||
|
| 'citrus'
|
||||||
|
| 'tree_fruits'
|
||||||
|
| 'melons'
|
||||||
|
| 'edible_flowers';
|
||||||
|
|
||||||
|
export interface ProducePreference {
|
||||||
|
produceType: string;
|
||||||
|
category: ProduceCategory;
|
||||||
|
priority: 'must_have' | 'preferred' | 'nice_to_have' | 'occasional';
|
||||||
|
weeklyQuantity?: number;
|
||||||
|
unit?: string;
|
||||||
|
varietyPreferences?: string[];
|
||||||
|
seasonalOnly: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demand signal from aggregated preferences
|
||||||
|
export interface DemandSignal {
|
||||||
|
id: string;
|
||||||
|
timestamp: string;
|
||||||
|
|
||||||
|
// Location scope
|
||||||
|
region: {
|
||||||
|
centerLat: number;
|
||||||
|
centerLon: number;
|
||||||
|
radiusKm: number;
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Time scope
|
||||||
|
periodStart: string;
|
||||||
|
periodEnd: string;
|
||||||
|
seasonalPeriod: 'spring' | 'summer' | 'fall' | 'winter';
|
||||||
|
|
||||||
|
// Aggregated demand
|
||||||
|
demandItems: DemandItem[];
|
||||||
|
|
||||||
|
// Statistics
|
||||||
|
totalConsumers: number;
|
||||||
|
totalWeeklyDemandKg: number;
|
||||||
|
confidenceLevel: number; // 0-100
|
||||||
|
|
||||||
|
// Supply status
|
||||||
|
currentSupplyKg: number;
|
||||||
|
supplyGapKg: number;
|
||||||
|
supplyStatus: 'surplus' | 'balanced' | 'shortage' | 'critical';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DemandItem {
|
||||||
|
produceType: string;
|
||||||
|
category: ProduceCategory;
|
||||||
|
scientificName?: string;
|
||||||
|
|
||||||
|
// Quantities
|
||||||
|
weeklyDemandKg: number;
|
||||||
|
monthlyDemandKg: number;
|
||||||
|
consumerCount: number;
|
||||||
|
|
||||||
|
// Priority
|
||||||
|
aggregatePriority: number; // 1-10
|
||||||
|
urgency: 'immediate' | 'this_week' | 'this_month' | 'next_season';
|
||||||
|
|
||||||
|
// Preferences
|
||||||
|
preferredCertifications: string[];
|
||||||
|
averageWillingPrice: number;
|
||||||
|
priceUnit: string;
|
||||||
|
|
||||||
|
// Seasonal info
|
||||||
|
inSeason: boolean;
|
||||||
|
seasonalAvailability: {
|
||||||
|
spring: boolean;
|
||||||
|
summer: boolean;
|
||||||
|
fall: boolean;
|
||||||
|
winter: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Supply matching
|
||||||
|
matchedSupply: number;
|
||||||
|
matchedGrowers: number;
|
||||||
|
gapKg: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Grower planting recommendation
|
||||||
|
export interface PlantingRecommendation {
|
||||||
|
id: string;
|
||||||
|
timestamp: string;
|
||||||
|
growerId: string;
|
||||||
|
|
||||||
|
// Recommendation details
|
||||||
|
produceType: string;
|
||||||
|
variety?: string;
|
||||||
|
category: ProduceCategory;
|
||||||
|
|
||||||
|
// Quantities
|
||||||
|
recommendedQuantity: number;
|
||||||
|
quantityUnit: 'plants' | 'seeds' | 'kg_expected_yield';
|
||||||
|
expectedYieldKg: number;
|
||||||
|
yieldConfidence: number; // 0-100
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
plantByDate: string;
|
||||||
|
expectedHarvestStart: string;
|
||||||
|
expectedHarvestEnd: string;
|
||||||
|
growingDays: number;
|
||||||
|
|
||||||
|
// Market opportunity
|
||||||
|
projectedDemandKg: number;
|
||||||
|
projectedPricePerKg: number;
|
||||||
|
projectedRevenue: number;
|
||||||
|
marketConfidence: number; // 0-100
|
||||||
|
|
||||||
|
// Risk assessment
|
||||||
|
riskFactors: RiskFactor[];
|
||||||
|
overallRisk: 'low' | 'medium' | 'high';
|
||||||
|
|
||||||
|
// Reasoning
|
||||||
|
demandSignalIds: string[];
|
||||||
|
explanation: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RiskFactor {
|
||||||
|
type: 'weather' | 'pest' | 'disease' | 'market' | 'oversupply' | 'labor' | 'transport';
|
||||||
|
severity: 'low' | 'medium' | 'high';
|
||||||
|
description: string;
|
||||||
|
mitigationSuggestion?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seasonal planning
|
||||||
|
export interface SeasonalPlan {
|
||||||
|
id: string;
|
||||||
|
growerId: string;
|
||||||
|
year: number;
|
||||||
|
season: 'spring' | 'summer' | 'fall' | 'winter';
|
||||||
|
|
||||||
|
// Location context
|
||||||
|
location: {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
hardinessZone: string;
|
||||||
|
microclimate?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Growing capacity
|
||||||
|
growingCapacity: {
|
||||||
|
outdoorSqMeters?: number;
|
||||||
|
greenhouseSqMeters?: number;
|
||||||
|
verticalFarmSqMeters?: number;
|
||||||
|
hydroponicUnits?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Planned crops
|
||||||
|
plannedCrops: PlannedCrop[];
|
||||||
|
|
||||||
|
// Expected outcomes
|
||||||
|
expectedTotalYieldKg: number;
|
||||||
|
expectedRevenue: number;
|
||||||
|
expectedCarbonFootprintKg: number;
|
||||||
|
|
||||||
|
// Status
|
||||||
|
status: 'draft' | 'confirmed' | 'in_progress' | 'completed';
|
||||||
|
completionPercentage?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface PlannedCrop {
|
||||||
|
produceType: string;
|
||||||
|
variety?: string;
|
||||||
|
|
||||||
|
// Planting
|
||||||
|
plantingDate: string;
|
||||||
|
plantingMethod: 'direct_sow' | 'transplant' | 'hydroponic' | 'aeroponic';
|
||||||
|
quantity: number;
|
||||||
|
quantityUnit: 'plants' | 'seeds' | 'sqm' | 'units';
|
||||||
|
|
||||||
|
// Space allocation
|
||||||
|
allocatedSpace: number;
|
||||||
|
spaceUnit: 'sqm' | 'sqft' | 'units';
|
||||||
|
growingLocation: 'outdoor' | 'greenhouse' | 'vertical_farm' | 'indoor';
|
||||||
|
|
||||||
|
// Expected harvest
|
||||||
|
expectedHarvestStart: string;
|
||||||
|
expectedHarvestEnd: string;
|
||||||
|
expectedYieldKg: number;
|
||||||
|
|
||||||
|
// Market allocation
|
||||||
|
preCommittedKg: number;
|
||||||
|
preCommittedTo: string[]; // consumer IDs or market names
|
||||||
|
|
||||||
|
// Status
|
||||||
|
status: 'planned' | 'planted' | 'growing' | 'harvesting' | 'completed' | 'failed';
|
||||||
|
actualYieldKg?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Demand forecast model
|
||||||
|
export interface DemandForecast {
|
||||||
|
id: string;
|
||||||
|
generatedAt: string;
|
||||||
|
|
||||||
|
// Scope
|
||||||
|
region: string;
|
||||||
|
forecastPeriod: {
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forecasts by produce type
|
||||||
|
forecasts: ProduceForecast[];
|
||||||
|
|
||||||
|
// Model info
|
||||||
|
modelVersion: string;
|
||||||
|
dataPointsUsed: number;
|
||||||
|
lastTrainingDate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProduceForecast {
|
||||||
|
produceType: string;
|
||||||
|
category: ProduceCategory;
|
||||||
|
|
||||||
|
// Predicted demand
|
||||||
|
predictedDemandKg: number;
|
||||||
|
confidenceInterval: {
|
||||||
|
low: number;
|
||||||
|
high: number;
|
||||||
|
};
|
||||||
|
confidence: number; // 0-100
|
||||||
|
|
||||||
|
// Trends
|
||||||
|
trend: 'increasing' | 'stable' | 'decreasing';
|
||||||
|
trendStrength: number; // 0-100
|
||||||
|
seasonalFactor: number; // multiplier
|
||||||
|
|
||||||
|
// Price prediction
|
||||||
|
predictedPricePerKg: number;
|
||||||
|
priceConfidenceInterval: {
|
||||||
|
low: number;
|
||||||
|
high: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Influencing factors
|
||||||
|
factors: ForecastFactor[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ForecastFactor {
|
||||||
|
name: string;
|
||||||
|
type: 'seasonal' | 'trend' | 'event' | 'weather' | 'economic' | 'preference';
|
||||||
|
impact: number; // -100 to +100
|
||||||
|
description: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Supply commitment from grower
|
||||||
|
export interface SupplyCommitment {
|
||||||
|
id: string;
|
||||||
|
growerId: string;
|
||||||
|
timestamp: string;
|
||||||
|
|
||||||
|
produceType: string;
|
||||||
|
variety?: string;
|
||||||
|
|
||||||
|
// Commitment details
|
||||||
|
committedQuantityKg: number;
|
||||||
|
availableFrom: string;
|
||||||
|
availableUntil: string;
|
||||||
|
|
||||||
|
// Pricing
|
||||||
|
pricePerKg: number;
|
||||||
|
currency: string;
|
||||||
|
minimumOrderKg: number;
|
||||||
|
bulkDiscountThreshold?: number;
|
||||||
|
bulkDiscountPercent?: number;
|
||||||
|
|
||||||
|
// Quality
|
||||||
|
certifications: string[];
|
||||||
|
freshnessGuaranteeHours: number;
|
||||||
|
|
||||||
|
// Delivery
|
||||||
|
deliveryRadiusKm: number;
|
||||||
|
deliveryMethods: ('grower_delivery' | 'customer_pickup' | 'hub_dropoff' | 'third_party')[];
|
||||||
|
|
||||||
|
// Status
|
||||||
|
status: 'available' | 'partially_committed' | 'fully_committed' | 'expired';
|
||||||
|
remainingKg: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Market match between supply and demand
|
||||||
|
export interface MarketMatch {
|
||||||
|
id: string;
|
||||||
|
timestamp: string;
|
||||||
|
|
||||||
|
demandSignalId: string;
|
||||||
|
supplyCommitmentId: string;
|
||||||
|
|
||||||
|
consumerId: string;
|
||||||
|
growerId: string;
|
||||||
|
|
||||||
|
produceType: string;
|
||||||
|
matchedQuantityKg: number;
|
||||||
|
|
||||||
|
// Transaction details
|
||||||
|
agreedPricePerKg: number;
|
||||||
|
totalPrice: number;
|
||||||
|
currency: string;
|
||||||
|
|
||||||
|
// Delivery
|
||||||
|
deliveryDate: string;
|
||||||
|
deliveryMethod: string;
|
||||||
|
deliveryLocation: {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
address?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Status
|
||||||
|
status: 'pending' | 'confirmed' | 'in_transit' | 'delivered' | 'completed' | 'cancelled';
|
||||||
|
|
||||||
|
// Ratings
|
||||||
|
consumerRating?: number;
|
||||||
|
growerRating?: number;
|
||||||
|
feedback?: string;
|
||||||
|
}
|
||||||
516
lib/transport/tracker.ts
Normal file
516
lib/transport/tracker.ts
Normal file
|
|
@ -0,0 +1,516 @@
|
||||||
|
/**
|
||||||
|
* Transport Tracker for LocalGreenChain
|
||||||
|
* Manages seed-to-seed transport events and blockchain recording
|
||||||
|
*/
|
||||||
|
|
||||||
|
import crypto from 'crypto';
|
||||||
|
import {
|
||||||
|
TransportEvent,
|
||||||
|
TransportBlock,
|
||||||
|
TransportLocation,
|
||||||
|
TransportMethod,
|
||||||
|
PlantJourney,
|
||||||
|
EnvironmentalImpact,
|
||||||
|
TransportQRData,
|
||||||
|
CARBON_FACTORS,
|
||||||
|
SeedAcquisitionEvent,
|
||||||
|
PlantingEvent,
|
||||||
|
GrowingTransportEvent,
|
||||||
|
HarvestEvent,
|
||||||
|
ProcessingEvent,
|
||||||
|
DistributionEvent,
|
||||||
|
ConsumerDeliveryEvent,
|
||||||
|
SeedSavingEvent,
|
||||||
|
SeedSharingEvent,
|
||||||
|
TransportEventType
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TransportChain - Blockchain for transport events
|
||||||
|
*/
|
||||||
|
export class TransportChain {
|
||||||
|
public chain: TransportBlock[];
|
||||||
|
public difficulty: number;
|
||||||
|
private eventIndex: Map<string, TransportBlock[]>;
|
||||||
|
private plantEvents: Map<string, TransportEvent[]>;
|
||||||
|
private batchEvents: Map<string, TransportEvent[]>;
|
||||||
|
|
||||||
|
constructor(difficulty: number = 3) {
|
||||||
|
this.chain = [this.createGenesisBlock()];
|
||||||
|
this.difficulty = difficulty;
|
||||||
|
this.eventIndex = new Map();
|
||||||
|
this.plantEvents = new Map();
|
||||||
|
this.batchEvents = new Map();
|
||||||
|
}
|
||||||
|
|
||||||
|
private createGenesisBlock(): TransportBlock {
|
||||||
|
const genesisEvent: SeedAcquisitionEvent = {
|
||||||
|
id: 'genesis-transport-0',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
eventType: 'seed_acquisition',
|
||||||
|
fromLocation: {
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
|
locationType: 'seed_bank',
|
||||||
|
facilityName: 'LocalGreenChain Genesis'
|
||||||
|
},
|
||||||
|
toLocation: {
|
||||||
|
latitude: 0,
|
||||||
|
longitude: 0,
|
||||||
|
locationType: 'seed_bank',
|
||||||
|
facilityName: 'LocalGreenChain Genesis'
|
||||||
|
},
|
||||||
|
distanceKm: 0,
|
||||||
|
durationMinutes: 0,
|
||||||
|
transportMethod: 'walking',
|
||||||
|
carbonFootprintKg: 0,
|
||||||
|
senderId: 'system',
|
||||||
|
receiverId: 'system',
|
||||||
|
status: 'verified',
|
||||||
|
seedBatchId: 'genesis-seed-batch',
|
||||||
|
sourceType: 'seed_bank',
|
||||||
|
species: 'Blockchain primordialis',
|
||||||
|
quantity: 1,
|
||||||
|
quantityUnit: 'seeds',
|
||||||
|
generation: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
index: 0,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
transportEvent: genesisEvent,
|
||||||
|
previousHash: '0',
|
||||||
|
hash: this.calculateHash(0, new Date().toISOString(), genesisEvent, '0', 0),
|
||||||
|
nonce: 0,
|
||||||
|
cumulativeCarbonKg: 0,
|
||||||
|
cumulativeFoodMiles: 0,
|
||||||
|
chainLength: 1
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private calculateHash(
|
||||||
|
index: number,
|
||||||
|
timestamp: string,
|
||||||
|
event: TransportEvent,
|
||||||
|
previousHash: string,
|
||||||
|
nonce: number
|
||||||
|
): string {
|
||||||
|
const data = `${index}${timestamp}${JSON.stringify(event)}${previousHash}${nonce}`;
|
||||||
|
return crypto.createHash('sha256').update(data).digest('hex');
|
||||||
|
}
|
||||||
|
|
||||||
|
private mineBlock(block: TransportBlock): void {
|
||||||
|
const target = '0'.repeat(this.difficulty);
|
||||||
|
while (block.hash.substring(0, this.difficulty) !== target) {
|
||||||
|
block.nonce++;
|
||||||
|
block.hash = this.calculateHash(
|
||||||
|
block.index,
|
||||||
|
block.timestamp,
|
||||||
|
block.transportEvent,
|
||||||
|
block.previousHash,
|
||||||
|
block.nonce
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getLatestBlock(): TransportBlock {
|
||||||
|
return this.chain[this.chain.length - 1];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record a new transport event
|
||||||
|
*/
|
||||||
|
recordEvent(event: TransportEvent): TransportBlock {
|
||||||
|
const latestBlock = this.getLatestBlock();
|
||||||
|
|
||||||
|
// Calculate carbon footprint if not provided
|
||||||
|
if (!event.carbonFootprintKg) {
|
||||||
|
event.carbonFootprintKg = this.calculateCarbon(
|
||||||
|
event.transportMethod,
|
||||||
|
event.distanceKm,
|
||||||
|
this.estimateWeight(event)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const newBlock: TransportBlock = {
|
||||||
|
index: this.chain.length,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
transportEvent: event,
|
||||||
|
previousHash: latestBlock.hash,
|
||||||
|
hash: '',
|
||||||
|
nonce: 0,
|
||||||
|
cumulativeCarbonKg: latestBlock.cumulativeCarbonKg + event.carbonFootprintKg,
|
||||||
|
cumulativeFoodMiles: latestBlock.cumulativeFoodMiles + event.distanceKm,
|
||||||
|
chainLength: this.chain.length + 1
|
||||||
|
};
|
||||||
|
|
||||||
|
newBlock.hash = this.calculateHash(
|
||||||
|
newBlock.index,
|
||||||
|
newBlock.timestamp,
|
||||||
|
event,
|
||||||
|
newBlock.previousHash,
|
||||||
|
newBlock.nonce
|
||||||
|
);
|
||||||
|
|
||||||
|
this.mineBlock(newBlock);
|
||||||
|
this.chain.push(newBlock);
|
||||||
|
this.indexEvent(event, newBlock);
|
||||||
|
|
||||||
|
return newBlock;
|
||||||
|
}
|
||||||
|
|
||||||
|
private indexEvent(event: TransportEvent, block: TransportBlock): void {
|
||||||
|
// Index by event ID
|
||||||
|
const eventBlocks = this.eventIndex.get(event.id) || [];
|
||||||
|
eventBlocks.push(block);
|
||||||
|
this.eventIndex.set(event.id, eventBlocks);
|
||||||
|
|
||||||
|
// Index by plant IDs
|
||||||
|
const plantIds = this.extractPlantIds(event);
|
||||||
|
for (const plantId of plantIds) {
|
||||||
|
const events = this.plantEvents.get(plantId) || [];
|
||||||
|
events.push(event);
|
||||||
|
this.plantEvents.set(plantId, events);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Index by batch IDs
|
||||||
|
const batchIds = this.extractBatchIds(event);
|
||||||
|
for (const batchId of batchIds) {
|
||||||
|
const events = this.batchEvents.get(batchId) || [];
|
||||||
|
events.push(event);
|
||||||
|
this.batchEvents.set(batchId, events);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractPlantIds(event: TransportEvent): string[] {
|
||||||
|
switch (event.eventType) {
|
||||||
|
case 'planting':
|
||||||
|
return (event as PlantingEvent).plantIds;
|
||||||
|
case 'growing_transport':
|
||||||
|
return (event as GrowingTransportEvent).plantIds;
|
||||||
|
case 'harvest':
|
||||||
|
return (event as HarvestEvent).plantIds;
|
||||||
|
case 'seed_saving':
|
||||||
|
return (event as SeedSavingEvent).parentPlantIds;
|
||||||
|
default:
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private extractBatchIds(event: TransportEvent): string[] {
|
||||||
|
const batchIds: string[] = [];
|
||||||
|
|
||||||
|
switch (event.eventType) {
|
||||||
|
case 'seed_acquisition':
|
||||||
|
batchIds.push((event as SeedAcquisitionEvent).seedBatchId);
|
||||||
|
break;
|
||||||
|
case 'planting':
|
||||||
|
batchIds.push((event as PlantingEvent).seedBatchId);
|
||||||
|
break;
|
||||||
|
case 'harvest':
|
||||||
|
batchIds.push((event as HarvestEvent).harvestBatchId);
|
||||||
|
if ((event as HarvestEvent).seedBatchIdCreated) {
|
||||||
|
batchIds.push((event as HarvestEvent).seedBatchIdCreated!);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'processing':
|
||||||
|
batchIds.push(...(event as ProcessingEvent).harvestBatchIds);
|
||||||
|
batchIds.push((event as ProcessingEvent).processingBatchId);
|
||||||
|
break;
|
||||||
|
case 'distribution':
|
||||||
|
batchIds.push(...(event as DistributionEvent).batchIds);
|
||||||
|
break;
|
||||||
|
case 'consumer_delivery':
|
||||||
|
batchIds.push(...(event as ConsumerDeliveryEvent).batchIds);
|
||||||
|
break;
|
||||||
|
case 'seed_saving':
|
||||||
|
batchIds.push((event as SeedSavingEvent).newSeedBatchId);
|
||||||
|
break;
|
||||||
|
case 'seed_sharing':
|
||||||
|
batchIds.push((event as SeedSharingEvent).seedBatchId);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return batchIds;
|
||||||
|
}
|
||||||
|
|
||||||
|
private estimateWeight(event: TransportEvent): number {
|
||||||
|
// Estimate weight in kg based on event type
|
||||||
|
switch (event.eventType) {
|
||||||
|
case 'seed_acquisition':
|
||||||
|
case 'seed_saving':
|
||||||
|
case 'seed_sharing':
|
||||||
|
return 0.1; // Seeds are light
|
||||||
|
case 'planting':
|
||||||
|
return 0.5;
|
||||||
|
case 'growing_transport':
|
||||||
|
return 2;
|
||||||
|
case 'harvest':
|
||||||
|
return (event as HarvestEvent).netWeight || 5;
|
||||||
|
case 'processing':
|
||||||
|
return (event as ProcessingEvent).outputWeight || 5;
|
||||||
|
case 'distribution':
|
||||||
|
case 'consumer_delivery':
|
||||||
|
return 5;
|
||||||
|
default:
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate carbon footprint
|
||||||
|
*/
|
||||||
|
calculateCarbon(method: TransportMethod, distanceKm: number, weightKg: number): number {
|
||||||
|
const factor = CARBON_FACTORS[method] || 0.1;
|
||||||
|
return factor * distanceKm * weightKg;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculate distance between two locations using Haversine formula
|
||||||
|
*/
|
||||||
|
static calculateDistance(from: TransportLocation, to: TransportLocation): number {
|
||||||
|
const R = 6371; // Earth's radius in km
|
||||||
|
const dLat = TransportChain.toRadians(to.latitude - from.latitude);
|
||||||
|
const dLon = TransportChain.toRadians(to.longitude - from.longitude);
|
||||||
|
|
||||||
|
const a =
|
||||||
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
||||||
|
Math.cos(TransportChain.toRadians(from.latitude)) *
|
||||||
|
Math.cos(TransportChain.toRadians(to.latitude)) *
|
||||||
|
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
||||||
|
|
||||||
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
||||||
|
return R * c;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static toRadians(degrees: number): number {
|
||||||
|
return degrees * (Math.PI / 180);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get complete journey for a plant
|
||||||
|
*/
|
||||||
|
getPlantJourney(plantId: string): PlantJourney | null {
|
||||||
|
const events = this.plantEvents.get(plantId);
|
||||||
|
if (!events || events.length === 0) return null;
|
||||||
|
|
||||||
|
// Sort events by timestamp
|
||||||
|
const sortedEvents = [...events].sort(
|
||||||
|
(a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()
|
||||||
|
);
|
||||||
|
|
||||||
|
const lastEvent = sortedEvents[sortedEvents.length - 1];
|
||||||
|
|
||||||
|
// Calculate metrics
|
||||||
|
let totalFoodMiles = 0;
|
||||||
|
let totalCarbonKg = 0;
|
||||||
|
let daysInTransit = 0;
|
||||||
|
|
||||||
|
for (const event of sortedEvents) {
|
||||||
|
totalFoodMiles += event.distanceKm;
|
||||||
|
totalCarbonKg += event.carbonFootprintKg;
|
||||||
|
daysInTransit += event.durationMinutes / (60 * 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find seed batch origin
|
||||||
|
const seedAcquisition = sortedEvents.find(e => e.eventType === 'seed_acquisition') as SeedAcquisitionEvent | undefined;
|
||||||
|
const planting = sortedEvents.find(e => e.eventType === 'planting') as PlantingEvent | undefined;
|
||||||
|
|
||||||
|
// Calculate growing days
|
||||||
|
const plantingDate = planting ? new Date(planting.timestamp) : null;
|
||||||
|
const lastDate = new Date(lastEvent.timestamp);
|
||||||
|
const daysGrowing = plantingDate
|
||||||
|
? Math.floor((lastDate.getTime() - plantingDate.getTime()) / (1000 * 60 * 60 * 24))
|
||||||
|
: 0;
|
||||||
|
|
||||||
|
// Determine current stage
|
||||||
|
let currentStage: PlantJourney['currentStage'] = 'seed';
|
||||||
|
if (sortedEvents.some(e => e.eventType === 'seed_saving')) {
|
||||||
|
currentStage = 'seed_saving';
|
||||||
|
} else if (sortedEvents.some(e => e.eventType === 'harvest')) {
|
||||||
|
currentStage = 'post_harvest';
|
||||||
|
} else if (sortedEvents.some(e => e.eventType === 'growing_transport')) {
|
||||||
|
const lastGrowing = sortedEvents.filter(e => e.eventType === 'growing_transport').pop() as GrowingTransportEvent;
|
||||||
|
currentStage = lastGrowing.plantStage;
|
||||||
|
} else if (sortedEvents.some(e => e.eventType === 'planting')) {
|
||||||
|
currentStage = 'seedling';
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
plantId,
|
||||||
|
seedBatchOrigin: seedAcquisition?.seedBatchId || planting?.seedBatchId || 'unknown',
|
||||||
|
currentCustodian: lastEvent.receiverId,
|
||||||
|
currentLocation: lastEvent.toLocation,
|
||||||
|
currentStage,
|
||||||
|
events: sortedEvents,
|
||||||
|
totalFoodMiles,
|
||||||
|
totalCarbonKg,
|
||||||
|
daysInTransit: Math.round(daysInTransit),
|
||||||
|
daysGrowing,
|
||||||
|
generation: seedAcquisition?.generation || 0,
|
||||||
|
ancestorPlantIds: seedAcquisition?.parentPlantIds || [],
|
||||||
|
descendantSeedBatches: sortedEvents
|
||||||
|
.filter(e => e.eventType === 'seed_saving')
|
||||||
|
.map(e => (e as SeedSavingEvent).newSeedBatchId)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get environmental impact summary for a user
|
||||||
|
*/
|
||||||
|
getEnvironmentalImpact(userId: string): EnvironmentalImpact {
|
||||||
|
const userEvents = this.chain
|
||||||
|
.filter(block =>
|
||||||
|
block.transportEvent.senderId === userId ||
|
||||||
|
block.transportEvent.receiverId === userId
|
||||||
|
)
|
||||||
|
.map(block => block.transportEvent);
|
||||||
|
|
||||||
|
let totalCarbonKg = 0;
|
||||||
|
let totalFoodMiles = 0;
|
||||||
|
let totalWeight = 0;
|
||||||
|
|
||||||
|
const breakdownByMethod: EnvironmentalImpact['breakdownByMethod'] = {} as any;
|
||||||
|
const breakdownByEventType: EnvironmentalImpact['breakdownByEventType'] = {} as any;
|
||||||
|
|
||||||
|
for (const event of userEvents) {
|
||||||
|
totalCarbonKg += event.carbonFootprintKg;
|
||||||
|
totalFoodMiles += event.distanceKm;
|
||||||
|
totalWeight += this.estimateWeight(event);
|
||||||
|
|
||||||
|
// Method breakdown
|
||||||
|
if (!breakdownByMethod[event.transportMethod]) {
|
||||||
|
breakdownByMethod[event.transportMethod] = { distance: 0, carbon: 0 };
|
||||||
|
}
|
||||||
|
breakdownByMethod[event.transportMethod].distance += event.distanceKm;
|
||||||
|
breakdownByMethod[event.transportMethod].carbon += event.carbonFootprintKg;
|
||||||
|
|
||||||
|
// Event type breakdown
|
||||||
|
if (!breakdownByEventType[event.eventType]) {
|
||||||
|
breakdownByEventType[event.eventType] = { count: 0, carbon: 0 };
|
||||||
|
}
|
||||||
|
breakdownByEventType[event.eventType].count++;
|
||||||
|
breakdownByEventType[event.eventType].carbon += event.carbonFootprintKg;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conventional comparison (assume 1500 miles avg, 2.5 kg CO2/lb)
|
||||||
|
const conventionalMiles = totalWeight * 1500;
|
||||||
|
const conventionalCarbon = totalWeight * 2.5;
|
||||||
|
|
||||||
|
return {
|
||||||
|
totalCarbonKg,
|
||||||
|
totalFoodMiles,
|
||||||
|
carbonPerKgProduce: totalWeight > 0 ? totalCarbonKg / totalWeight : 0,
|
||||||
|
milesPerKgProduce: totalWeight > 0 ? totalFoodMiles / totalWeight : 0,
|
||||||
|
breakdownByMethod,
|
||||||
|
breakdownByEventType,
|
||||||
|
comparisonToConventional: {
|
||||||
|
carbonSaved: Math.max(0, conventionalCarbon - totalCarbonKg),
|
||||||
|
milesSaved: Math.max(0, conventionalMiles - totalFoodMiles),
|
||||||
|
percentageReduction: conventionalCarbon > 0
|
||||||
|
? Math.round((1 - totalCarbonKg / conventionalCarbon) * 100)
|
||||||
|
: 0
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate QR code data for a plant or batch
|
||||||
|
*/
|
||||||
|
generateQRData(plantId?: string, batchId?: string): TransportQRData {
|
||||||
|
const events = plantId
|
||||||
|
? this.plantEvents.get(plantId)
|
||||||
|
: batchId
|
||||||
|
? this.batchEvents.get(batchId)
|
||||||
|
: [];
|
||||||
|
|
||||||
|
const lastEvent = events && events.length > 0
|
||||||
|
? events[events.length - 1]
|
||||||
|
: null;
|
||||||
|
|
||||||
|
const lineageHash = crypto.createHash('sha256')
|
||||||
|
.update(JSON.stringify(events))
|
||||||
|
.digest('hex')
|
||||||
|
.substring(0, 16);
|
||||||
|
|
||||||
|
return {
|
||||||
|
plantId,
|
||||||
|
batchId,
|
||||||
|
blockchainAddress: this.getLatestBlock().hash.substring(0, 42),
|
||||||
|
quickLookupUrl: `https://localgreenchain.org/track/${plantId || batchId}`,
|
||||||
|
lineageHash,
|
||||||
|
currentCustodian: lastEvent?.receiverId || 'unknown',
|
||||||
|
lastEventType: lastEvent?.eventType || 'seed_acquisition',
|
||||||
|
lastEventTimestamp: lastEvent?.timestamp || new Date().toISOString(),
|
||||||
|
verificationCode: crypto.randomBytes(4).toString('hex').toUpperCase()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Verify chain integrity
|
||||||
|
*/
|
||||||
|
isChainValid(): boolean {
|
||||||
|
for (let i = 1; i < this.chain.length; i++) {
|
||||||
|
const currentBlock = this.chain[i];
|
||||||
|
const previousBlock = this.chain[i - 1];
|
||||||
|
|
||||||
|
// Verify hash
|
||||||
|
const expectedHash = this.calculateHash(
|
||||||
|
currentBlock.index,
|
||||||
|
currentBlock.timestamp,
|
||||||
|
currentBlock.transportEvent,
|
||||||
|
currentBlock.previousHash,
|
||||||
|
currentBlock.nonce
|
||||||
|
);
|
||||||
|
|
||||||
|
if (currentBlock.hash !== expectedHash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify chain link
|
||||||
|
if (currentBlock.previousHash !== previousBlock.hash) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export to JSON
|
||||||
|
*/
|
||||||
|
toJSON(): object {
|
||||||
|
return {
|
||||||
|
difficulty: this.difficulty,
|
||||||
|
chain: this.chain
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import from JSON
|
||||||
|
*/
|
||||||
|
static fromJSON(data: any): TransportChain {
|
||||||
|
const chain = new TransportChain(data.difficulty);
|
||||||
|
chain.chain = data.chain;
|
||||||
|
|
||||||
|
// Rebuild indexes
|
||||||
|
for (const block of chain.chain) {
|
||||||
|
chain.indexEvent(block.transportEvent, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singleton instance
|
||||||
|
let transportChainInstance: TransportChain | null = null;
|
||||||
|
|
||||||
|
export function getTransportChain(): TransportChain {
|
||||||
|
if (!transportChainInstance) {
|
||||||
|
transportChainInstance = new TransportChain();
|
||||||
|
}
|
||||||
|
return transportChainInstance;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function setTransportChain(chain: TransportChain): void {
|
||||||
|
transportChainInstance = chain;
|
||||||
|
}
|
||||||
431
lib/transport/types.ts
Normal file
431
lib/transport/types.ts
Normal file
|
|
@ -0,0 +1,431 @@
|
||||||
|
/**
|
||||||
|
* Transport Tracking Types for LocalGreenChain
|
||||||
|
* Seed-to-Seed transport event tracking with blockchain integration
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Transport Location with full traceability
|
||||||
|
export interface TransportLocation {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
address?: string;
|
||||||
|
city?: string;
|
||||||
|
region?: string;
|
||||||
|
country?: string;
|
||||||
|
postalCode?: string;
|
||||||
|
locationType: 'farm' | 'greenhouse' | 'vertical_farm' | 'warehouse' | 'hub' | 'market' | 'consumer' | 'seed_bank' | 'other';
|
||||||
|
facilityId?: string;
|
||||||
|
facilityName?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transport method definitions
|
||||||
|
export type TransportMethod =
|
||||||
|
| 'walking'
|
||||||
|
| 'bicycle'
|
||||||
|
| 'electric_vehicle'
|
||||||
|
| 'hybrid_vehicle'
|
||||||
|
| 'gasoline_vehicle'
|
||||||
|
| 'diesel_truck'
|
||||||
|
| 'electric_truck'
|
||||||
|
| 'refrigerated_truck'
|
||||||
|
| 'rail'
|
||||||
|
| 'ship'
|
||||||
|
| 'air'
|
||||||
|
| 'drone'
|
||||||
|
| 'local_delivery'
|
||||||
|
| 'customer_pickup';
|
||||||
|
|
||||||
|
// Carbon emission factors (kg CO2 per km per kg of cargo)
|
||||||
|
export const CARBON_FACTORS: Record<TransportMethod, number> = {
|
||||||
|
walking: 0,
|
||||||
|
bicycle: 0,
|
||||||
|
electric_vehicle: 0.02,
|
||||||
|
hybrid_vehicle: 0.08,
|
||||||
|
gasoline_vehicle: 0.12,
|
||||||
|
diesel_truck: 0.15,
|
||||||
|
electric_truck: 0.03,
|
||||||
|
refrigerated_truck: 0.25,
|
||||||
|
rail: 0.01,
|
||||||
|
ship: 0.008,
|
||||||
|
air: 0.5,
|
||||||
|
drone: 0.01,
|
||||||
|
local_delivery: 0.05,
|
||||||
|
customer_pickup: 0.1
|
||||||
|
};
|
||||||
|
|
||||||
|
// Plant lifecycle stages
|
||||||
|
export type PlantStage =
|
||||||
|
| 'seed'
|
||||||
|
| 'germinating'
|
||||||
|
| 'seedling'
|
||||||
|
| 'vegetative'
|
||||||
|
| 'flowering'
|
||||||
|
| 'fruiting'
|
||||||
|
| 'mature'
|
||||||
|
| 'harvesting'
|
||||||
|
| 'post_harvest'
|
||||||
|
| 'seed_saving';
|
||||||
|
|
||||||
|
// Environmental conditions during transport
|
||||||
|
export interface TransportEnvironment {
|
||||||
|
temperatureMin: number;
|
||||||
|
temperatureMax: number;
|
||||||
|
temperatureAvg: number;
|
||||||
|
humidityMin: number;
|
||||||
|
humidityMax: number;
|
||||||
|
humidityAvg: number;
|
||||||
|
lightExposure: 'dark' | 'low' | 'ambient' | 'bright';
|
||||||
|
shockEvents?: number;
|
||||||
|
handlingNotes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temperature requirements for produce
|
||||||
|
export interface TemperatureRange {
|
||||||
|
min: number;
|
||||||
|
max: number;
|
||||||
|
optimal: number;
|
||||||
|
unit: 'celsius' | 'fahrenheit';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed storage conditions
|
||||||
|
export interface SeedStorageConditions {
|
||||||
|
temperature: number;
|
||||||
|
humidity: number;
|
||||||
|
lightExposure: 'dark' | 'minimal';
|
||||||
|
containerType: 'envelope' | 'jar' | 'vacuum_sealed' | 'moisture_proof' | 'seed_vault';
|
||||||
|
desiccant: boolean;
|
||||||
|
estimatedViability: number; // years
|
||||||
|
}
|
||||||
|
|
||||||
|
// Base transport event
|
||||||
|
export interface BaseTransportEvent {
|
||||||
|
id: string;
|
||||||
|
timestamp: string;
|
||||||
|
eventType: TransportEventType;
|
||||||
|
|
||||||
|
// Locations
|
||||||
|
fromLocation: TransportLocation;
|
||||||
|
toLocation: TransportLocation;
|
||||||
|
|
||||||
|
// Distance and duration
|
||||||
|
distanceKm: number;
|
||||||
|
durationMinutes: number;
|
||||||
|
|
||||||
|
// Environmental impact
|
||||||
|
transportMethod: TransportMethod;
|
||||||
|
carbonFootprintKg: number;
|
||||||
|
|
||||||
|
// Verification
|
||||||
|
senderId: string;
|
||||||
|
receiverId: string;
|
||||||
|
senderSignature?: string;
|
||||||
|
receiverSignature?: string;
|
||||||
|
verifierSignature?: string;
|
||||||
|
|
||||||
|
// Status
|
||||||
|
status: 'pending' | 'in_transit' | 'delivered' | 'verified' | 'disputed';
|
||||||
|
|
||||||
|
// Metadata
|
||||||
|
notes?: string;
|
||||||
|
photos?: string[];
|
||||||
|
documents?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type TransportEventType =
|
||||||
|
| 'seed_acquisition'
|
||||||
|
| 'planting'
|
||||||
|
| 'growing_transport'
|
||||||
|
| 'harvest'
|
||||||
|
| 'processing'
|
||||||
|
| 'distribution'
|
||||||
|
| 'consumer_delivery'
|
||||||
|
| 'seed_saving'
|
||||||
|
| 'seed_sharing';
|
||||||
|
|
||||||
|
// Seed Acquisition Transport Event
|
||||||
|
export interface SeedAcquisitionEvent extends BaseTransportEvent {
|
||||||
|
eventType: 'seed_acquisition';
|
||||||
|
|
||||||
|
seedBatchId: string;
|
||||||
|
sourceType: 'seed_bank' | 'previous_harvest' | 'trade' | 'purchase' | 'wild_collected' | 'gift';
|
||||||
|
|
||||||
|
// Seed details
|
||||||
|
species: string;
|
||||||
|
variety?: string;
|
||||||
|
quantity: number;
|
||||||
|
quantityUnit: 'seeds' | 'grams' | 'ounces' | 'packets';
|
||||||
|
|
||||||
|
// Lineage
|
||||||
|
geneticLineageId?: string;
|
||||||
|
parentPlantIds?: string[];
|
||||||
|
generation: number;
|
||||||
|
|
||||||
|
// Quality
|
||||||
|
germinationRate?: number;
|
||||||
|
purityPercentage?: number;
|
||||||
|
harvestDate?: string;
|
||||||
|
expirationDate?: string;
|
||||||
|
|
||||||
|
// Certifications
|
||||||
|
certifications?: ('organic' | 'non_gmo' | 'heirloom' | 'certified_seed' | 'biodynamic')[];
|
||||||
|
certificationDocuments?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Planting Event
|
||||||
|
export interface PlantingEvent extends BaseTransportEvent {
|
||||||
|
eventType: 'planting';
|
||||||
|
|
||||||
|
seedBatchId: string;
|
||||||
|
plantIds: string[]; // New plant IDs created
|
||||||
|
|
||||||
|
plantingMethod: 'direct_sow' | 'transplant' | 'indoor_start' | 'hydroponic' | 'aeroponic';
|
||||||
|
sowingDepth?: number; // mm
|
||||||
|
spacing?: number; // cm
|
||||||
|
quantityPlanted: number;
|
||||||
|
|
||||||
|
growingEnvironment: 'outdoor' | 'greenhouse' | 'indoor' | 'vertical_farm';
|
||||||
|
expectedHarvestDate?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Growing Transport Event (transplanting, moving plants)
|
||||||
|
export interface GrowingTransportEvent extends BaseTransportEvent {
|
||||||
|
eventType: 'growing_transport';
|
||||||
|
|
||||||
|
plantIds: string[];
|
||||||
|
reason: 'transplant' | 'relocation' | 'hardening_off' | 'seasonal_move' | 'upgrade_facility';
|
||||||
|
|
||||||
|
plantStage: PlantStage;
|
||||||
|
handlingMethod: 'bare_root' | 'potted' | 'root_ball' | 'hydroponic_transfer' | 'aeroponic_transfer';
|
||||||
|
|
||||||
|
rootDisturbance: 'none' | 'minimal' | 'moderate' | 'significant';
|
||||||
|
acclimatizationRequired: boolean;
|
||||||
|
acclimatizationDays?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Harvest Event
|
||||||
|
export interface HarvestEvent extends BaseTransportEvent {
|
||||||
|
eventType: 'harvest';
|
||||||
|
|
||||||
|
plantIds: string[];
|
||||||
|
harvestBatchId: string;
|
||||||
|
|
||||||
|
harvestType: 'full' | 'partial' | 'continuous' | 'selective';
|
||||||
|
produceType: string; // e.g., "tomatoes", "lettuce heads", "basil leaves"
|
||||||
|
|
||||||
|
// Quantities
|
||||||
|
grossWeight: number;
|
||||||
|
netWeight: number;
|
||||||
|
weightUnit: 'kg' | 'lbs' | 'oz' | 'grams';
|
||||||
|
itemCount?: number;
|
||||||
|
|
||||||
|
// Quality
|
||||||
|
qualityGrade?: 'A' | 'B' | 'C' | 'processing';
|
||||||
|
qualityNotes?: string;
|
||||||
|
|
||||||
|
// Storage
|
||||||
|
packagingType: string;
|
||||||
|
temperatureRequired: TemperatureRange;
|
||||||
|
shelfLifeHours: number;
|
||||||
|
|
||||||
|
// For seed saving
|
||||||
|
seedsSaved: boolean;
|
||||||
|
seedBatchIdCreated?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Processing Event
|
||||||
|
export interface ProcessingEvent extends BaseTransportEvent {
|
||||||
|
eventType: 'processing';
|
||||||
|
|
||||||
|
harvestBatchIds: string[];
|
||||||
|
processingBatchId: string;
|
||||||
|
|
||||||
|
processingType: 'washing' | 'cutting' | 'packaging' | 'freezing' | 'drying' | 'canning' | 'juicing' | 'fermenting';
|
||||||
|
|
||||||
|
inputWeight: number;
|
||||||
|
outputWeight: number;
|
||||||
|
weightUnit: 'kg' | 'lbs';
|
||||||
|
|
||||||
|
wasteWeight: number;
|
||||||
|
wasteDisposal: 'compost' | 'animal_feed' | 'biogas' | 'landfill';
|
||||||
|
|
||||||
|
outputProducts: ProcessedProduct[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ProcessedProduct {
|
||||||
|
productId: string;
|
||||||
|
productType: string;
|
||||||
|
quantity: number;
|
||||||
|
unit: string;
|
||||||
|
shelfLifeDays: number;
|
||||||
|
storageRequirements: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Distribution Event
|
||||||
|
export interface DistributionEvent extends BaseTransportEvent {
|
||||||
|
eventType: 'distribution';
|
||||||
|
|
||||||
|
batchIds: string[]; // harvest or processing batch IDs
|
||||||
|
destinationType: 'consumer' | 'market' | 'restaurant' | 'processor' | 'distributor' | 'vertical_farm';
|
||||||
|
|
||||||
|
// Order info
|
||||||
|
orderId?: string;
|
||||||
|
customerType: 'individual' | 'business' | 'cooperative' | 'institution';
|
||||||
|
|
||||||
|
// Delivery
|
||||||
|
deliveryWindow: {
|
||||||
|
start: string;
|
||||||
|
end: string;
|
||||||
|
};
|
||||||
|
actualDeliveryTime?: string;
|
||||||
|
deliveryAttempts: number;
|
||||||
|
|
||||||
|
// Chain of custody
|
||||||
|
handoffVerified: boolean;
|
||||||
|
recipientName?: string;
|
||||||
|
recipientSignature?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Consumer Delivery Event
|
||||||
|
export interface ConsumerDeliveryEvent extends BaseTransportEvent {
|
||||||
|
eventType: 'consumer_delivery';
|
||||||
|
|
||||||
|
orderId: string;
|
||||||
|
batchIds: string[];
|
||||||
|
|
||||||
|
deliveryMethod: 'home_delivery' | 'pickup_point' | 'farmers_market' | 'csa_distribution' | 'restaurant_delivery';
|
||||||
|
|
||||||
|
// Final mile details
|
||||||
|
finalMileMethod: TransportMethod;
|
||||||
|
packagingReturned: boolean;
|
||||||
|
feedbackReceived: boolean;
|
||||||
|
feedbackRating?: number;
|
||||||
|
feedbackNotes?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed Saving Event
|
||||||
|
export interface SeedSavingEvent extends BaseTransportEvent {
|
||||||
|
eventType: 'seed_saving';
|
||||||
|
|
||||||
|
parentPlantIds: string[];
|
||||||
|
newSeedBatchId: string;
|
||||||
|
|
||||||
|
// Seed collection
|
||||||
|
collectionMethod: 'dry_seed' | 'wet_seed' | 'fermentation' | 'threshing';
|
||||||
|
seedCount?: number;
|
||||||
|
seedWeight?: number;
|
||||||
|
seedWeightUnit?: 'grams' | 'ounces';
|
||||||
|
|
||||||
|
// Quality testing
|
||||||
|
viabilityTestDate?: string;
|
||||||
|
germinationRate?: number;
|
||||||
|
purityTest?: boolean;
|
||||||
|
purityPercentage?: number;
|
||||||
|
|
||||||
|
// Storage
|
||||||
|
storageConditions: SeedStorageConditions;
|
||||||
|
storageLocationId: string;
|
||||||
|
|
||||||
|
// Lineage
|
||||||
|
newGenerationNumber: number;
|
||||||
|
geneticNotes?: string;
|
||||||
|
|
||||||
|
// Sharing plans
|
||||||
|
availableForSharing: boolean;
|
||||||
|
sharingTerms?: 'free' | 'trade' | 'sale' | 'restricted';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Seed Sharing Event
|
||||||
|
export interface SeedSharingEvent extends BaseTransportEvent {
|
||||||
|
eventType: 'seed_sharing';
|
||||||
|
|
||||||
|
seedBatchId: string;
|
||||||
|
quantityShared: number;
|
||||||
|
quantityUnit: 'seeds' | 'grams' | 'packets';
|
||||||
|
|
||||||
|
sharingType: 'gift' | 'trade' | 'sale' | 'seed_library';
|
||||||
|
tradeDetails?: string;
|
||||||
|
saleAmount?: number;
|
||||||
|
saleCurrency?: string;
|
||||||
|
|
||||||
|
recipientAgreement: boolean;
|
||||||
|
growingCommitment?: string;
|
||||||
|
reportBackRequired: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Union type for all transport events
|
||||||
|
export type TransportEvent =
|
||||||
|
| SeedAcquisitionEvent
|
||||||
|
| PlantingEvent
|
||||||
|
| GrowingTransportEvent
|
||||||
|
| HarvestEvent
|
||||||
|
| ProcessingEvent
|
||||||
|
| DistributionEvent
|
||||||
|
| ConsumerDeliveryEvent
|
||||||
|
| SeedSavingEvent
|
||||||
|
| SeedSharingEvent;
|
||||||
|
|
||||||
|
// Transport Block for blockchain
|
||||||
|
export interface TransportBlock {
|
||||||
|
index: number;
|
||||||
|
timestamp: string;
|
||||||
|
transportEvent: TransportEvent;
|
||||||
|
previousHash: string;
|
||||||
|
hash: string;
|
||||||
|
nonce: number;
|
||||||
|
|
||||||
|
// Cumulative metrics
|
||||||
|
cumulativeCarbonKg: number;
|
||||||
|
cumulativeFoodMiles: number;
|
||||||
|
chainLength: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Plant Journey - complete transport history
|
||||||
|
export interface PlantJourney {
|
||||||
|
plantId: string;
|
||||||
|
seedBatchOrigin: string;
|
||||||
|
currentCustodian: string;
|
||||||
|
currentLocation: TransportLocation;
|
||||||
|
currentStage: PlantStage;
|
||||||
|
|
||||||
|
events: TransportEvent[];
|
||||||
|
|
||||||
|
// Metrics
|
||||||
|
totalFoodMiles: number;
|
||||||
|
totalCarbonKg: number;
|
||||||
|
daysInTransit: number;
|
||||||
|
daysGrowing: number;
|
||||||
|
|
||||||
|
// Lineage
|
||||||
|
generation: number;
|
||||||
|
ancestorPlantIds: string[];
|
||||||
|
descendantSeedBatches: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environmental Impact Summary
|
||||||
|
export interface EnvironmentalImpact {
|
||||||
|
totalCarbonKg: number;
|
||||||
|
totalFoodMiles: number;
|
||||||
|
carbonPerKgProduce: number;
|
||||||
|
milesPerKgProduce: number;
|
||||||
|
|
||||||
|
breakdownByMethod: Record<TransportMethod, { distance: number; carbon: number }>;
|
||||||
|
breakdownByEventType: Record<TransportEventType, { count: number; carbon: number }>;
|
||||||
|
|
||||||
|
comparisonToConventional: {
|
||||||
|
carbonSaved: number;
|
||||||
|
milesSaved: number;
|
||||||
|
percentageReduction: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// QR Code Data
|
||||||
|
export interface TransportQRData {
|
||||||
|
plantId?: string;
|
||||||
|
batchId?: string;
|
||||||
|
blockchainAddress: string;
|
||||||
|
quickLookupUrl: string;
|
||||||
|
lineageHash: string;
|
||||||
|
currentCustodian: string;
|
||||||
|
lastEventType: TransportEventType;
|
||||||
|
lastEventTimestamp: string;
|
||||||
|
verificationCode: string;
|
||||||
|
}
|
||||||
759
lib/vertical-farming/controller.ts
Normal file
759
lib/vertical-farming/controller.ts
Normal file
|
|
@ -0,0 +1,759 @@
|
||||||
|
/**
|
||||||
|
* Vertical Farm Controller for LocalGreenChain
|
||||||
|
* Manages vertical farm operations, automation, and optimization
|
||||||
|
*/
|
||||||
|
|
||||||
|
import {
|
||||||
|
VerticalFarm,
|
||||||
|
GrowingZone,
|
||||||
|
ZoneEnvironmentTargets,
|
||||||
|
ZoneEnvironmentReadings,
|
||||||
|
EnvironmentAlert,
|
||||||
|
GrowingRecipe,
|
||||||
|
CropBatch,
|
||||||
|
ResourceUsage,
|
||||||
|
FarmAnalytics,
|
||||||
|
LightSchedule,
|
||||||
|
NutrientRecipe,
|
||||||
|
BatchIssue
|
||||||
|
} from './types';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Vertical Farm Controller
|
||||||
|
*/
|
||||||
|
export class VerticalFarmController {
|
||||||
|
private farms: Map<string, VerticalFarm> = new Map();
|
||||||
|
private recipes: Map<string, GrowingRecipe> = new Map();
|
||||||
|
private batches: Map<string, CropBatch> = new Map();
|
||||||
|
private resourceLogs: Map<string, ResourceUsage[]> = new Map();
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.initializeDefaultRecipes();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize default growing recipes
|
||||||
|
*/
|
||||||
|
private initializeDefaultRecipes(): void {
|
||||||
|
const defaultRecipes: GrowingRecipe[] = [
|
||||||
|
{
|
||||||
|
id: 'recipe-lettuce-butterhead',
|
||||||
|
name: 'Butterhead Lettuce - Fast Cycle',
|
||||||
|
cropType: 'lettuce',
|
||||||
|
variety: 'butterhead',
|
||||||
|
version: '1.0',
|
||||||
|
stages: [
|
||||||
|
{
|
||||||
|
name: 'Germination',
|
||||||
|
daysStart: 0,
|
||||||
|
daysEnd: 3,
|
||||||
|
temperature: { day: 20, night: 18 },
|
||||||
|
humidity: { day: 80, night: 85 },
|
||||||
|
co2Ppm: 800,
|
||||||
|
lightHours: 18,
|
||||||
|
lightPpfd: 150,
|
||||||
|
nutrientRecipeId: 'nutrient-seedling',
|
||||||
|
targetEc: 0.8,
|
||||||
|
targetPh: 6.0,
|
||||||
|
actions: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Seedling',
|
||||||
|
daysStart: 4,
|
||||||
|
daysEnd: 10,
|
||||||
|
temperature: { day: 21, night: 18 },
|
||||||
|
humidity: { day: 70, night: 75 },
|
||||||
|
co2Ppm: 1000,
|
||||||
|
lightHours: 18,
|
||||||
|
lightPpfd: 200,
|
||||||
|
nutrientRecipeId: 'nutrient-vegetative',
|
||||||
|
targetEc: 1.2,
|
||||||
|
targetPh: 6.0,
|
||||||
|
actions: [
|
||||||
|
{ day: 10, action: 'transplant', description: 'Transplant to final position', automated: true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Vegetative Growth',
|
||||||
|
daysStart: 11,
|
||||||
|
daysEnd: 28,
|
||||||
|
temperature: { day: 22, night: 18 },
|
||||||
|
humidity: { day: 65, night: 70 },
|
||||||
|
co2Ppm: 1200,
|
||||||
|
lightHours: 16,
|
||||||
|
lightPpfd: 300,
|
||||||
|
nutrientRecipeId: 'nutrient-vegetative',
|
||||||
|
targetEc: 1.6,
|
||||||
|
targetPh: 6.0,
|
||||||
|
actions: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Finishing',
|
||||||
|
daysStart: 29,
|
||||||
|
daysEnd: 35,
|
||||||
|
temperature: { day: 20, night: 16 },
|
||||||
|
humidity: { day: 60, night: 65 },
|
||||||
|
co2Ppm: 800,
|
||||||
|
lightHours: 14,
|
||||||
|
lightPpfd: 250,
|
||||||
|
nutrientRecipeId: 'nutrient-finishing',
|
||||||
|
targetEc: 1.2,
|
||||||
|
targetPh: 6.0,
|
||||||
|
actions: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
expectedDays: 35,
|
||||||
|
expectedYieldGrams: 180,
|
||||||
|
expectedYieldPerSqm: 4000,
|
||||||
|
requirements: {
|
||||||
|
positions: 1,
|
||||||
|
zoneType: 'NFT',
|
||||||
|
minimumPpfd: 200,
|
||||||
|
idealTemperatureC: 21
|
||||||
|
},
|
||||||
|
source: 'internal',
|
||||||
|
rating: 4.5,
|
||||||
|
timesUsed: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'recipe-basil-genovese',
|
||||||
|
name: 'Genovese Basil - Aromatic',
|
||||||
|
cropType: 'basil',
|
||||||
|
variety: 'genovese',
|
||||||
|
version: '1.0',
|
||||||
|
stages: [
|
||||||
|
{
|
||||||
|
name: 'Germination',
|
||||||
|
daysStart: 0,
|
||||||
|
daysEnd: 5,
|
||||||
|
temperature: { day: 24, night: 22 },
|
||||||
|
humidity: { day: 80, night: 85 },
|
||||||
|
co2Ppm: 800,
|
||||||
|
lightHours: 16,
|
||||||
|
lightPpfd: 100,
|
||||||
|
nutrientRecipeId: 'nutrient-seedling',
|
||||||
|
targetEc: 0.6,
|
||||||
|
targetPh: 6.2,
|
||||||
|
actions: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Seedling',
|
||||||
|
daysStart: 6,
|
||||||
|
daysEnd: 14,
|
||||||
|
temperature: { day: 25, night: 22 },
|
||||||
|
humidity: { day: 70, night: 75 },
|
||||||
|
co2Ppm: 1000,
|
||||||
|
lightHours: 18,
|
||||||
|
lightPpfd: 200,
|
||||||
|
nutrientRecipeId: 'nutrient-vegetative',
|
||||||
|
targetEc: 1.0,
|
||||||
|
targetPh: 6.2,
|
||||||
|
actions: [
|
||||||
|
{ day: 14, action: 'transplant', description: 'Transplant to growing system', automated: true }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Vegetative',
|
||||||
|
daysStart: 15,
|
||||||
|
daysEnd: 35,
|
||||||
|
temperature: { day: 26, night: 22 },
|
||||||
|
humidity: { day: 65, night: 70 },
|
||||||
|
co2Ppm: 1200,
|
||||||
|
lightHours: 18,
|
||||||
|
lightPpfd: 400,
|
||||||
|
nutrientRecipeId: 'nutrient-herbs',
|
||||||
|
targetEc: 1.4,
|
||||||
|
targetPh: 6.0,
|
||||||
|
actions: [
|
||||||
|
{ day: 25, action: 'top', description: 'Top plants to encourage bushiness', automated: false }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Harvest Ready',
|
||||||
|
daysStart: 36,
|
||||||
|
daysEnd: 42,
|
||||||
|
temperature: { day: 24, night: 20 },
|
||||||
|
humidity: { day: 60, night: 65 },
|
||||||
|
co2Ppm: 800,
|
||||||
|
lightHours: 16,
|
||||||
|
lightPpfd: 350,
|
||||||
|
nutrientRecipeId: 'nutrient-finishing',
|
||||||
|
targetEc: 1.0,
|
||||||
|
targetPh: 6.0,
|
||||||
|
actions: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
expectedDays: 42,
|
||||||
|
expectedYieldGrams: 120,
|
||||||
|
expectedYieldPerSqm: 2400,
|
||||||
|
requirements: {
|
||||||
|
positions: 1,
|
||||||
|
zoneType: 'NFT',
|
||||||
|
minimumPpfd: 300,
|
||||||
|
idealTemperatureC: 25
|
||||||
|
},
|
||||||
|
source: 'internal',
|
||||||
|
rating: 4.3,
|
||||||
|
timesUsed: 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'recipe-microgreens-mix',
|
||||||
|
name: 'Microgreens Mix - Quick Turn',
|
||||||
|
cropType: 'microgreens',
|
||||||
|
variety: 'mixed',
|
||||||
|
version: '1.0',
|
||||||
|
stages: [
|
||||||
|
{
|
||||||
|
name: 'Sowing',
|
||||||
|
daysStart: 0,
|
||||||
|
daysEnd: 2,
|
||||||
|
temperature: { day: 22, night: 20 },
|
||||||
|
humidity: { day: 90, night: 90 },
|
||||||
|
co2Ppm: 600,
|
||||||
|
lightHours: 0,
|
||||||
|
lightPpfd: 0,
|
||||||
|
nutrientRecipeId: 'nutrient-none',
|
||||||
|
targetEc: 0,
|
||||||
|
targetPh: 6.0,
|
||||||
|
actions: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Germination',
|
||||||
|
daysStart: 3,
|
||||||
|
daysEnd: 5,
|
||||||
|
temperature: { day: 22, night: 20 },
|
||||||
|
humidity: { day: 80, night: 85 },
|
||||||
|
co2Ppm: 800,
|
||||||
|
lightHours: 12,
|
||||||
|
lightPpfd: 100,
|
||||||
|
nutrientRecipeId: 'nutrient-none',
|
||||||
|
targetEc: 0,
|
||||||
|
targetPh: 6.0,
|
||||||
|
actions: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Growth',
|
||||||
|
daysStart: 6,
|
||||||
|
daysEnd: 12,
|
||||||
|
temperature: { day: 21, night: 19 },
|
||||||
|
humidity: { day: 65, night: 70 },
|
||||||
|
co2Ppm: 1000,
|
||||||
|
lightHours: 16,
|
||||||
|
lightPpfd: 250,
|
||||||
|
nutrientRecipeId: 'nutrient-microgreens',
|
||||||
|
targetEc: 0.8,
|
||||||
|
targetPh: 6.0,
|
||||||
|
actions: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'Harvest',
|
||||||
|
daysStart: 13,
|
||||||
|
daysEnd: 14,
|
||||||
|
temperature: { day: 20, night: 18 },
|
||||||
|
humidity: { day: 60, night: 65 },
|
||||||
|
co2Ppm: 600,
|
||||||
|
lightHours: 14,
|
||||||
|
lightPpfd: 200,
|
||||||
|
nutrientRecipeId: 'nutrient-none',
|
||||||
|
targetEc: 0,
|
||||||
|
targetPh: 6.0,
|
||||||
|
actions: []
|
||||||
|
}
|
||||||
|
],
|
||||||
|
expectedDays: 14,
|
||||||
|
expectedYieldGrams: 200,
|
||||||
|
expectedYieldPerSqm: 2000,
|
||||||
|
requirements: {
|
||||||
|
positions: 1,
|
||||||
|
zoneType: 'rack_system',
|
||||||
|
minimumPpfd: 150,
|
||||||
|
idealTemperatureC: 21
|
||||||
|
},
|
||||||
|
source: 'internal',
|
||||||
|
rating: 4.7,
|
||||||
|
timesUsed: 0
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
for (const recipe of defaultRecipes) {
|
||||||
|
this.recipes.set(recipe.id, recipe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register a new vertical farm
|
||||||
|
*/
|
||||||
|
registerFarm(farm: VerticalFarm): void {
|
||||||
|
this.farms.set(farm.id, farm);
|
||||||
|
this.resourceLogs.set(farm.id, []);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get farm by ID
|
||||||
|
*/
|
||||||
|
getFarm(farmId: string): VerticalFarm | undefined {
|
||||||
|
return this.farms.get(farmId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Start a new crop batch
|
||||||
|
*/
|
||||||
|
startCropBatch(
|
||||||
|
farmId: string,
|
||||||
|
zoneId: string,
|
||||||
|
recipeId: string,
|
||||||
|
seedBatchId: string,
|
||||||
|
plantCount: number
|
||||||
|
): CropBatch {
|
||||||
|
const farm = this.farms.get(farmId);
|
||||||
|
if (!farm) throw new Error(`Farm ${farmId} not found`);
|
||||||
|
|
||||||
|
const zone = farm.zones.find(z => z.id === zoneId);
|
||||||
|
if (!zone) throw new Error(`Zone ${zoneId} not found in farm ${farmId}`);
|
||||||
|
|
||||||
|
const recipe = this.recipes.get(recipeId);
|
||||||
|
if (!recipe) throw new Error(`Recipe ${recipeId} not found`);
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const expectedHarvest = new Date(now.getTime() + recipe.expectedDays * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
const batch: CropBatch = {
|
||||||
|
id: `batch-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`,
|
||||||
|
farmId,
|
||||||
|
zoneId,
|
||||||
|
cropType: recipe.cropType,
|
||||||
|
variety: recipe.variety,
|
||||||
|
recipeId,
|
||||||
|
seedBatchId,
|
||||||
|
plantIds: [],
|
||||||
|
plantCount,
|
||||||
|
plantingDate: now.toISOString(),
|
||||||
|
currentStage: recipe.stages[0].name,
|
||||||
|
currentDay: 0,
|
||||||
|
healthScore: 100,
|
||||||
|
expectedHarvestDate: expectedHarvest.toISOString(),
|
||||||
|
expectedYieldKg: (recipe.expectedYieldGrams * plantCount) / 1000,
|
||||||
|
status: 'germinating',
|
||||||
|
issues: [],
|
||||||
|
environmentLog: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Generate plant IDs
|
||||||
|
for (let i = 0; i < plantCount; i++) {
|
||||||
|
batch.plantIds.push(`${batch.id}-plant-${i}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update zone
|
||||||
|
zone.currentCrop = recipe.cropType;
|
||||||
|
zone.plantIds = batch.plantIds;
|
||||||
|
zone.plantingDate = batch.plantingDate;
|
||||||
|
zone.expectedHarvestDate = batch.expectedHarvestDate;
|
||||||
|
zone.status = 'planted';
|
||||||
|
|
||||||
|
// Set environment targets from recipe
|
||||||
|
const firstStage = recipe.stages[0];
|
||||||
|
zone.environmentTargets = {
|
||||||
|
temperatureC: { min: firstStage.temperature.night - 2, max: firstStage.temperature.day + 2, target: firstStage.temperature.day },
|
||||||
|
humidityPercent: { min: firstStage.humidity.day - 10, max: firstStage.humidity.night + 5, target: firstStage.humidity.day },
|
||||||
|
co2Ppm: { min: firstStage.co2Ppm - 200, max: firstStage.co2Ppm + 200, target: firstStage.co2Ppm },
|
||||||
|
lightPpfd: { min: firstStage.lightPpfd * 0.8, max: firstStage.lightPpfd * 1.2, target: firstStage.lightPpfd },
|
||||||
|
lightHours: firstStage.lightHours,
|
||||||
|
nutrientEc: { min: firstStage.targetEc - 0.2, max: firstStage.targetEc + 0.2, target: firstStage.targetEc },
|
||||||
|
nutrientPh: { min: firstStage.targetPh - 0.3, max: firstStage.targetPh + 0.3, target: firstStage.targetPh },
|
||||||
|
waterTempC: { min: 18, max: 24, target: 20 }
|
||||||
|
};
|
||||||
|
|
||||||
|
this.batches.set(batch.id, batch);
|
||||||
|
recipe.timesUsed++;
|
||||||
|
|
||||||
|
return batch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update batch progress
|
||||||
|
*/
|
||||||
|
updateBatchProgress(batchId: string): CropBatch {
|
||||||
|
const batch = this.batches.get(batchId);
|
||||||
|
if (!batch) throw new Error(`Batch ${batchId} not found`);
|
||||||
|
|
||||||
|
const recipe = this.recipes.get(batch.recipeId);
|
||||||
|
if (!recipe) throw new Error(`Recipe ${batch.recipeId} not found`);
|
||||||
|
|
||||||
|
const plantingDate = new Date(batch.plantingDate);
|
||||||
|
const now = new Date();
|
||||||
|
batch.currentDay = Math.floor((now.getTime() - plantingDate.getTime()) / (24 * 60 * 60 * 1000));
|
||||||
|
|
||||||
|
// Determine current stage
|
||||||
|
for (const stage of recipe.stages) {
|
||||||
|
if (batch.currentDay >= stage.daysStart && batch.currentDay <= stage.daysEnd) {
|
||||||
|
batch.currentStage = stage.name;
|
||||||
|
|
||||||
|
// Update zone targets
|
||||||
|
const farm = this.farms.get(batch.farmId);
|
||||||
|
const zone = farm?.zones.find(z => z.id === batch.zoneId);
|
||||||
|
if (zone) {
|
||||||
|
zone.environmentTargets = {
|
||||||
|
temperatureC: { min: stage.temperature.night - 2, max: stage.temperature.day + 2, target: stage.temperature.day },
|
||||||
|
humidityPercent: { min: stage.humidity.day - 10, max: stage.humidity.night + 5, target: stage.humidity.day },
|
||||||
|
co2Ppm: { min: stage.co2Ppm - 200, max: stage.co2Ppm + 200, target: stage.co2Ppm },
|
||||||
|
lightPpfd: { min: stage.lightPpfd * 0.8, max: stage.lightPpfd * 1.2, target: stage.lightPpfd },
|
||||||
|
lightHours: stage.lightHours,
|
||||||
|
nutrientEc: { min: stage.targetEc - 0.2, max: stage.targetEc + 0.2, target: stage.targetEc },
|
||||||
|
nutrientPh: { min: stage.targetPh - 0.3, max: stage.targetPh + 0.3, target: stage.targetPh },
|
||||||
|
waterTempC: { min: 18, max: 24, target: 20 }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update status
|
||||||
|
if (batch.currentDay >= recipe.expectedDays) {
|
||||||
|
batch.status = 'ready';
|
||||||
|
} else if (batch.currentDay > 3) {
|
||||||
|
batch.status = 'growing';
|
||||||
|
}
|
||||||
|
|
||||||
|
return batch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Record environment reading
|
||||||
|
*/
|
||||||
|
recordEnvironment(zoneId: string, readings: ZoneEnvironmentReadings): EnvironmentAlert[] {
|
||||||
|
const alerts: EnvironmentAlert[] = [];
|
||||||
|
|
||||||
|
// Find the zone
|
||||||
|
let targetZone: GrowingZone | undefined;
|
||||||
|
let farm: VerticalFarm | undefined;
|
||||||
|
|
||||||
|
for (const f of this.farms.values()) {
|
||||||
|
const zone = f.zones.find(z => z.id === zoneId);
|
||||||
|
if (zone) {
|
||||||
|
targetZone = zone;
|
||||||
|
farm = f;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!targetZone || !farm) return alerts;
|
||||||
|
|
||||||
|
const targets = targetZone.environmentTargets;
|
||||||
|
|
||||||
|
// Check temperature
|
||||||
|
if (readings.temperatureC < targets.temperatureC.min) {
|
||||||
|
alerts.push({
|
||||||
|
parameter: 'temperature',
|
||||||
|
type: readings.temperatureC < targets.temperatureC.min - 5 ? 'critical_low' : 'low',
|
||||||
|
value: readings.temperatureC,
|
||||||
|
threshold: targets.temperatureC.min,
|
||||||
|
timestamp: readings.timestamp,
|
||||||
|
acknowledged: false
|
||||||
|
});
|
||||||
|
} else if (readings.temperatureC > targets.temperatureC.max) {
|
||||||
|
alerts.push({
|
||||||
|
parameter: 'temperature',
|
||||||
|
type: readings.temperatureC > targets.temperatureC.max + 5 ? 'critical_high' : 'high',
|
||||||
|
value: readings.temperatureC,
|
||||||
|
threshold: targets.temperatureC.max,
|
||||||
|
timestamp: readings.timestamp,
|
||||||
|
acknowledged: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check humidity
|
||||||
|
if (readings.humidityPercent < targets.humidityPercent.min) {
|
||||||
|
alerts.push({
|
||||||
|
parameter: 'humidity',
|
||||||
|
type: 'low',
|
||||||
|
value: readings.humidityPercent,
|
||||||
|
threshold: targets.humidityPercent.min,
|
||||||
|
timestamp: readings.timestamp,
|
||||||
|
acknowledged: false
|
||||||
|
});
|
||||||
|
} else if (readings.humidityPercent > targets.humidityPercent.max) {
|
||||||
|
alerts.push({
|
||||||
|
parameter: 'humidity',
|
||||||
|
type: 'high',
|
||||||
|
value: readings.humidityPercent,
|
||||||
|
threshold: targets.humidityPercent.max,
|
||||||
|
timestamp: readings.timestamp,
|
||||||
|
acknowledged: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check EC
|
||||||
|
if (readings.ec < targets.nutrientEc.min) {
|
||||||
|
alerts.push({
|
||||||
|
parameter: 'ec',
|
||||||
|
type: 'low',
|
||||||
|
value: readings.ec,
|
||||||
|
threshold: targets.nutrientEc.min,
|
||||||
|
timestamp: readings.timestamp,
|
||||||
|
acknowledged: false
|
||||||
|
});
|
||||||
|
} else if (readings.ec > targets.nutrientEc.max) {
|
||||||
|
alerts.push({
|
||||||
|
parameter: 'ec',
|
||||||
|
type: 'high',
|
||||||
|
value: readings.ec,
|
||||||
|
threshold: targets.nutrientEc.max,
|
||||||
|
timestamp: readings.timestamp,
|
||||||
|
acknowledged: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check pH
|
||||||
|
if (readings.ph < targets.nutrientPh.min) {
|
||||||
|
alerts.push({
|
||||||
|
parameter: 'ph',
|
||||||
|
type: 'low',
|
||||||
|
value: readings.ph,
|
||||||
|
threshold: targets.nutrientPh.min,
|
||||||
|
timestamp: readings.timestamp,
|
||||||
|
acknowledged: false
|
||||||
|
});
|
||||||
|
} else if (readings.ph > targets.nutrientPh.max) {
|
||||||
|
alerts.push({
|
||||||
|
parameter: 'ph',
|
||||||
|
type: 'high',
|
||||||
|
value: readings.ph,
|
||||||
|
threshold: targets.nutrientPh.max,
|
||||||
|
timestamp: readings.timestamp,
|
||||||
|
acknowledged: false
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update zone readings
|
||||||
|
readings.alerts = alerts;
|
||||||
|
targetZone.currentEnvironment = readings;
|
||||||
|
|
||||||
|
// Log to batch if exists
|
||||||
|
const batch = Array.from(this.batches.values()).find(b =>
|
||||||
|
b.zoneId === zoneId && b.status !== 'completed' && b.status !== 'failed'
|
||||||
|
);
|
||||||
|
if (batch) {
|
||||||
|
batch.environmentLog.push({
|
||||||
|
timestamp: readings.timestamp,
|
||||||
|
readings
|
||||||
|
});
|
||||||
|
|
||||||
|
// Adjust health score based on alerts
|
||||||
|
if (alerts.some(a => a.type.includes('critical'))) {
|
||||||
|
batch.healthScore = Math.max(0, batch.healthScore - 5);
|
||||||
|
} else if (alerts.length > 0) {
|
||||||
|
batch.healthScore = Math.max(0, batch.healthScore - 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return alerts;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Complete harvest
|
||||||
|
*/
|
||||||
|
completeHarvest(batchId: string, actualYieldKg: number, qualityGrade: string): CropBatch {
|
||||||
|
const batch = this.batches.get(batchId);
|
||||||
|
if (!batch) throw new Error(`Batch ${batchId} not found`);
|
||||||
|
|
||||||
|
batch.actualHarvestDate = new Date().toISOString();
|
||||||
|
batch.actualYieldKg = actualYieldKg;
|
||||||
|
batch.qualityGrade = qualityGrade;
|
||||||
|
batch.status = 'completed';
|
||||||
|
|
||||||
|
// Update zone
|
||||||
|
const farm = this.farms.get(batch.farmId);
|
||||||
|
const zone = farm?.zones.find(z => z.id === batch.zoneId);
|
||||||
|
if (zone) {
|
||||||
|
zone.status = 'cleaning';
|
||||||
|
zone.currentCrop = '';
|
||||||
|
zone.plantIds = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
return batch;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generate farm analytics
|
||||||
|
*/
|
||||||
|
generateAnalytics(farmId: string, periodDays: number = 30): FarmAnalytics {
|
||||||
|
const farm = this.farms.get(farmId);
|
||||||
|
if (!farm) throw new Error(`Farm ${farmId} not found`);
|
||||||
|
|
||||||
|
const now = new Date();
|
||||||
|
const periodStart = new Date(now.getTime() - periodDays * 24 * 60 * 60 * 1000);
|
||||||
|
|
||||||
|
// Get completed batches in period
|
||||||
|
const completedBatches = Array.from(this.batches.values()).filter(b =>
|
||||||
|
b.farmId === farmId &&
|
||||||
|
b.status === 'completed' &&
|
||||||
|
b.actualHarvestDate &&
|
||||||
|
new Date(b.actualHarvestDate) >= periodStart
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalYieldKg = completedBatches.reduce((sum, b) => sum + (b.actualYieldKg || 0), 0);
|
||||||
|
const totalExpectedYield = completedBatches.reduce((sum, b) => sum + b.expectedYieldKg, 0);
|
||||||
|
|
||||||
|
// Calculate yield per sqm per year
|
||||||
|
const growingAreaSqm = farm.specs.growingAreaSqm;
|
||||||
|
const yearlyMultiplier = 365 / periodDays;
|
||||||
|
const yieldPerSqmPerYear = growingAreaSqm > 0 ? (totalYieldKg * yearlyMultiplier) / growingAreaSqm : 0;
|
||||||
|
|
||||||
|
// Quality breakdown
|
||||||
|
const gradeACounts = completedBatches.filter(b => b.qualityGrade === 'A').length;
|
||||||
|
const gradeAPercent = completedBatches.length > 0 ? (gradeACounts / completedBatches.length) * 100 : 0;
|
||||||
|
|
||||||
|
// Wastage (difference between expected and actual)
|
||||||
|
const wastageKg = Math.max(0, totalExpectedYield - totalYieldKg);
|
||||||
|
const wastagePercent = totalExpectedYield > 0 ? (wastageKg / totalExpectedYield) * 100 : 0;
|
||||||
|
|
||||||
|
// Success rate
|
||||||
|
const allBatches = Array.from(this.batches.values()).filter(b =>
|
||||||
|
b.farmId === farmId &&
|
||||||
|
new Date(b.plantingDate) >= periodStart
|
||||||
|
);
|
||||||
|
const failedBatches = allBatches.filter(b => b.status === 'failed').length;
|
||||||
|
const cropSuccessRate = allBatches.length > 0 ? ((allBatches.length - failedBatches) / allBatches.length) * 100 : 100;
|
||||||
|
|
||||||
|
// Resource usage
|
||||||
|
const resourceHistory = this.resourceLogs.get(farmId) || [];
|
||||||
|
const periodResources = resourceHistory.filter(r =>
|
||||||
|
new Date(r.periodStart) >= periodStart
|
||||||
|
);
|
||||||
|
|
||||||
|
const totalElectricity = periodResources.reduce((sum, r) => sum + r.electricityKwh, 0);
|
||||||
|
const totalWater = periodResources.reduce((sum, r) => sum + r.waterUsageL, 0);
|
||||||
|
const totalCost = periodResources.reduce((sum, r) =>
|
||||||
|
sum + r.electricityCostUsd + r.waterCostUsd + r.nutrientCostUsd + r.co2CostUsd, 0
|
||||||
|
);
|
||||||
|
|
||||||
|
// Top crops
|
||||||
|
const cropYields = new Map<string, number>();
|
||||||
|
const cropRevenue = new Map<string, number>();
|
||||||
|
|
||||||
|
for (const batch of completedBatches) {
|
||||||
|
const currentYield = cropYields.get(batch.cropType) || 0;
|
||||||
|
cropYields.set(batch.cropType, currentYield + (batch.actualYieldKg || 0));
|
||||||
|
|
||||||
|
// Estimate revenue (placeholder - would come from actual sales)
|
||||||
|
const estimatedRevenue = (batch.actualYieldKg || 0) * 10; // $10/kg placeholder
|
||||||
|
const currentRevenue = cropRevenue.get(batch.cropType) || 0;
|
||||||
|
cropRevenue.set(batch.cropType, currentRevenue + estimatedRevenue);
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalRevenue = Array.from(cropRevenue.values()).reduce((a, b) => a + b, 0);
|
||||||
|
|
||||||
|
return {
|
||||||
|
farmId,
|
||||||
|
generatedAt: now.toISOString(),
|
||||||
|
period: `${periodDays} days`,
|
||||||
|
totalYieldKg: Math.round(totalYieldKg * 10) / 10,
|
||||||
|
yieldPerSqmPerYear: Math.round(yieldPerSqmPerYear * 10) / 10,
|
||||||
|
cropCyclesCompleted: completedBatches.length,
|
||||||
|
averageCyclesDays: completedBatches.length > 0
|
||||||
|
? completedBatches.reduce((sum, b) => {
|
||||||
|
const start = new Date(b.plantingDate);
|
||||||
|
const end = new Date(b.actualHarvestDate!);
|
||||||
|
return sum + (end.getTime() - start.getTime()) / (24 * 60 * 60 * 1000);
|
||||||
|
}, 0) / completedBatches.length
|
||||||
|
: 0,
|
||||||
|
averageQualityScore: completedBatches.length > 0
|
||||||
|
? completedBatches.reduce((sum, b) => sum + b.healthScore, 0) / completedBatches.length
|
||||||
|
: 0,
|
||||||
|
gradeAPercent: Math.round(gradeAPercent),
|
||||||
|
wastagePercent: Math.round(wastagePercent * 10) / 10,
|
||||||
|
cropSuccessRate: Math.round(cropSuccessRate),
|
||||||
|
spaceUtilization: farm.currentCapacityUtilization,
|
||||||
|
laborHoursPerKg: totalYieldKg > 0 ? 0.5 : 0, // Placeholder
|
||||||
|
revenueUsd: Math.round(totalRevenue),
|
||||||
|
costUsd: Math.round(totalCost),
|
||||||
|
profitMarginPercent: totalRevenue > 0 ? Math.round(((totalRevenue - totalCost) / totalRevenue) * 100) : 0,
|
||||||
|
revenuePerSqm: growingAreaSqm > 0 ? Math.round((totalRevenue / growingAreaSqm) * yearlyMultiplier) : 0,
|
||||||
|
carbonFootprintKgPerKg: totalYieldKg > 0 ? 0.3 : 0, // Estimated - very low for VF
|
||||||
|
waterUseLPerKg: totalYieldKg > 0 ? totalWater / totalYieldKg : 0,
|
||||||
|
energyUseKwhPerKg: totalYieldKg > 0 ? totalElectricity / totalYieldKg : 0,
|
||||||
|
topCropsByYield: Array.from(cropYields.entries())
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.slice(0, 5)
|
||||||
|
.map(([crop, yieldKg]) => ({ crop, yieldKg: Math.round(yieldKg * 10) / 10 })),
|
||||||
|
topCropsByRevenue: Array.from(cropRevenue.entries())
|
||||||
|
.sort((a, b) => b[1] - a[1])
|
||||||
|
.slice(0, 5)
|
||||||
|
.map(([crop, revenueUsd]) => ({ crop, revenueUsd: Math.round(revenueUsd) })),
|
||||||
|
topCropsByEfficiency: Array.from(cropYields.entries())
|
||||||
|
.map(([crop, yieldKg]) => {
|
||||||
|
const batches = completedBatches.filter(b => b.cropType === crop);
|
||||||
|
const avgHealth = batches.reduce((sum, b) => sum + b.healthScore, 0) / batches.length;
|
||||||
|
return { crop, efficiencyScore: Math.round(avgHealth) };
|
||||||
|
})
|
||||||
|
.sort((a, b) => b.efficiencyScore - a.efficiencyScore)
|
||||||
|
.slice(0, 5)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all recipes
|
||||||
|
*/
|
||||||
|
getRecipes(): GrowingRecipe[] {
|
||||||
|
return Array.from(this.recipes.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add custom recipe
|
||||||
|
*/
|
||||||
|
addRecipe(recipe: GrowingRecipe): void {
|
||||||
|
this.recipes.set(recipe.id, recipe);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Export state
|
||||||
|
*/
|
||||||
|
toJSON(): object {
|
||||||
|
return {
|
||||||
|
farms: Array.from(this.farms.entries()),
|
||||||
|
recipes: Array.from(this.recipes.entries()),
|
||||||
|
batches: Array.from(this.batches.entries()),
|
||||||
|
resourceLogs: Array.from(this.resourceLogs.entries())
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Import state
|
||||||
|
*/
|
||||||
|
static fromJSON(data: any): VerticalFarmController {
|
||||||
|
const controller = new VerticalFarmController();
|
||||||
|
|
||||||
|
if (data.farms) {
|
||||||
|
for (const [key, value] of data.farms) {
|
||||||
|
controller.farms.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.recipes) {
|
||||||
|
for (const [key, value] of data.recipes) {
|
||||||
|
controller.recipes.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.batches) {
|
||||||
|
for (const [key, value] of data.batches) {
|
||||||
|
controller.batches.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (data.resourceLogs) {
|
||||||
|
for (const [key, value] of data.resourceLogs) {
|
||||||
|
controller.resourceLogs.set(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return controller;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singleton
|
||||||
|
let controllerInstance: VerticalFarmController | null = null;
|
||||||
|
|
||||||
|
export function getVerticalFarmController(): VerticalFarmController {
|
||||||
|
if (!controllerInstance) {
|
||||||
|
controllerInstance = new VerticalFarmController();
|
||||||
|
}
|
||||||
|
return controllerInstance;
|
||||||
|
}
|
||||||
564
lib/vertical-farming/types.ts
Normal file
564
lib/vertical-farming/types.ts
Normal file
|
|
@ -0,0 +1,564 @@
|
||||||
|
/**
|
||||||
|
* Vertical Farming Types for LocalGreenChain
|
||||||
|
* Complete vertical farm management, automation, and integration
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Vertical Farm facility
|
||||||
|
export interface VerticalFarm {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
ownerId: string;
|
||||||
|
|
||||||
|
// Location
|
||||||
|
location: {
|
||||||
|
latitude: number;
|
||||||
|
longitude: number;
|
||||||
|
address: string;
|
||||||
|
city: string;
|
||||||
|
country: string;
|
||||||
|
timezone: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Facility specs
|
||||||
|
specs: FacilitySpecs;
|
||||||
|
|
||||||
|
// Growing zones
|
||||||
|
zones: GrowingZone[];
|
||||||
|
|
||||||
|
// Systems
|
||||||
|
environmentalControl: EnvironmentalControlSystem;
|
||||||
|
irrigationSystem: IrrigationSystem;
|
||||||
|
lightingSystem: LightingSystem;
|
||||||
|
nutrientSystem: NutrientDeliverySystem;
|
||||||
|
|
||||||
|
// Automation
|
||||||
|
automationLevel: 'manual' | 'semi_automated' | 'fully_automated';
|
||||||
|
automationSystems: AutomationSystem[];
|
||||||
|
|
||||||
|
// Status
|
||||||
|
status: 'offline' | 'starting' | 'operational' | 'maintenance' | 'emergency';
|
||||||
|
operationalSince: string;
|
||||||
|
lastMaintenanceDate: string;
|
||||||
|
|
||||||
|
// Performance
|
||||||
|
currentCapacityUtilization: number; // 0-100
|
||||||
|
averageYieldEfficiency: number; // vs theoretical max
|
||||||
|
energyEfficiencyScore: number; // 0-100
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface FacilitySpecs {
|
||||||
|
totalAreaSqm: number;
|
||||||
|
growingAreaSqm: number;
|
||||||
|
numberOfLevels: number;
|
||||||
|
ceilingHeightM: number;
|
||||||
|
|
||||||
|
// Capacity
|
||||||
|
totalGrowingPositions: number;
|
||||||
|
currentActivePlants: number;
|
||||||
|
|
||||||
|
// Infrastructure
|
||||||
|
powerCapacityKw: number;
|
||||||
|
waterStorageL: number;
|
||||||
|
backupPowerHours: number;
|
||||||
|
|
||||||
|
// Certifications
|
||||||
|
certifications: ('organic' | 'gap' | 'haccp' | 'iso_22000' | 'local_food_safety')[];
|
||||||
|
|
||||||
|
// Building
|
||||||
|
buildingType: 'warehouse' | 'greenhouse' | 'container' | 'purpose_built' | 'retrofit';
|
||||||
|
insulation: 'standard' | 'high_efficiency' | 'passive';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GrowingZone {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
level: number;
|
||||||
|
|
||||||
|
// Dimensions
|
||||||
|
areaSqm: number;
|
||||||
|
lengthM: number;
|
||||||
|
widthM: number;
|
||||||
|
|
||||||
|
// Growing system
|
||||||
|
growingMethod: 'NFT' | 'DWC' | 'ebb_flow' | 'aeroponics' | 'vertical_towers' | 'rack_system';
|
||||||
|
plantPositions: number;
|
||||||
|
|
||||||
|
// Current status
|
||||||
|
currentCrop: string;
|
||||||
|
plantIds: string[];
|
||||||
|
plantingDate: string;
|
||||||
|
expectedHarvestDate: string;
|
||||||
|
|
||||||
|
// Environment targets
|
||||||
|
environmentTargets: ZoneEnvironmentTargets;
|
||||||
|
|
||||||
|
// Current readings
|
||||||
|
currentEnvironment: ZoneEnvironmentReadings;
|
||||||
|
|
||||||
|
// Status
|
||||||
|
status: 'empty' | 'preparing' | 'planted' | 'growing' | 'harvesting' | 'cleaning';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ZoneEnvironmentTargets {
|
||||||
|
temperatureC: { min: number; max: number; target: number };
|
||||||
|
humidityPercent: { min: number; max: number; target: number };
|
||||||
|
co2Ppm: { min: number; max: number; target: number };
|
||||||
|
lightPpfd: { min: number; max: number; target: number };
|
||||||
|
lightHours: number;
|
||||||
|
nutrientEc: { min: number; max: number; target: number };
|
||||||
|
nutrientPh: { min: number; max: number; target: number };
|
||||||
|
waterTempC: { min: number; max: number; target: number };
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ZoneEnvironmentReadings {
|
||||||
|
timestamp: string;
|
||||||
|
|
||||||
|
// Air
|
||||||
|
temperatureC: number;
|
||||||
|
humidityPercent: number;
|
||||||
|
co2Ppm: number;
|
||||||
|
vpd: number; // Vapor Pressure Deficit
|
||||||
|
|
||||||
|
// Light
|
||||||
|
ppfd: number;
|
||||||
|
dli: number; // Daily Light Integral
|
||||||
|
|
||||||
|
// Water/Nutrients
|
||||||
|
waterTempC: number;
|
||||||
|
ec: number;
|
||||||
|
ph: number;
|
||||||
|
dissolvedOxygenPpm: number;
|
||||||
|
|
||||||
|
// Air quality
|
||||||
|
airflowMs: number;
|
||||||
|
|
||||||
|
// Status flags
|
||||||
|
alerts: EnvironmentAlert[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface EnvironmentAlert {
|
||||||
|
parameter: string;
|
||||||
|
type: 'low' | 'high' | 'critical_low' | 'critical_high' | 'sensor_fault';
|
||||||
|
value: number;
|
||||||
|
threshold: number;
|
||||||
|
timestamp: string;
|
||||||
|
acknowledged: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Environmental Control System
|
||||||
|
export interface EnvironmentalControlSystem {
|
||||||
|
hvacUnits: HVACUnit[];
|
||||||
|
co2Injection: CO2System;
|
||||||
|
humidification: HumidificationSystem;
|
||||||
|
airCirculation: AirCirculationSystem;
|
||||||
|
|
||||||
|
// Control mode
|
||||||
|
controlMode: 'manual' | 'scheduled' | 'adaptive' | 'ai_optimized';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HVACUnit {
|
||||||
|
id: string;
|
||||||
|
type: 'cooling' | 'heating' | 'heat_pump';
|
||||||
|
capacityKw: number;
|
||||||
|
zones: string[];
|
||||||
|
status: 'off' | 'running' | 'fault';
|
||||||
|
currentPowerKw: number;
|
||||||
|
setpointC: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface CO2System {
|
||||||
|
type: 'tank' | 'generator' | 'burner';
|
||||||
|
capacityKg: number;
|
||||||
|
currentLevelKg: number;
|
||||||
|
injectionRateKgPerHour: number;
|
||||||
|
status: 'off' | 'injecting' | 'maintaining' | 'fault';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface HumidificationSystem {
|
||||||
|
type: 'misting' | 'ultrasonic' | 'evaporative';
|
||||||
|
capacityLPerHour: number;
|
||||||
|
status: 'off' | 'running' | 'fault';
|
||||||
|
currentOutput: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface AirCirculationSystem {
|
||||||
|
fans: {
|
||||||
|
id: string;
|
||||||
|
zone: string;
|
||||||
|
type: 'circulation' | 'exhaust' | 'intake';
|
||||||
|
speedPercent: number;
|
||||||
|
status: 'off' | 'running' | 'fault';
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Irrigation System
|
||||||
|
export interface IrrigationSystem {
|
||||||
|
type: 'recirculating' | 'drain_to_waste';
|
||||||
|
|
||||||
|
// Tanks
|
||||||
|
freshWaterTankL: number;
|
||||||
|
freshWaterLevelL: number;
|
||||||
|
nutrientTankL: number;
|
||||||
|
nutrientLevelL: number;
|
||||||
|
wasteTankL: number;
|
||||||
|
wasteLevelL: number;
|
||||||
|
|
||||||
|
// Water treatment
|
||||||
|
waterTreatment: {
|
||||||
|
ro: boolean;
|
||||||
|
uv: boolean;
|
||||||
|
ozone: boolean;
|
||||||
|
filtration: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pumps
|
||||||
|
pumps: {
|
||||||
|
id: string;
|
||||||
|
type: 'main' | 'zone' | 'recirculation' | 'drain';
|
||||||
|
status: 'off' | 'running' | 'fault';
|
||||||
|
flowRateLPerMin: number;
|
||||||
|
}[];
|
||||||
|
|
||||||
|
// Schedule
|
||||||
|
irrigationSchedule: IrrigationSchedule[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IrrigationSchedule {
|
||||||
|
zoneId: string;
|
||||||
|
frequencyPerDay: number;
|
||||||
|
durationMinutes: number;
|
||||||
|
times: string[]; // HH:MM format
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lighting System
|
||||||
|
export interface LightingSystem {
|
||||||
|
type: 'LED' | 'HPS' | 'fluorescent' | 'hybrid';
|
||||||
|
|
||||||
|
// Fixtures
|
||||||
|
fixtures: LightFixture[];
|
||||||
|
|
||||||
|
// Schedules
|
||||||
|
lightSchedules: LightSchedule[];
|
||||||
|
|
||||||
|
// Energy
|
||||||
|
totalWattage: number;
|
||||||
|
currentWattage: number;
|
||||||
|
efficacyUmolJ: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LightFixture {
|
||||||
|
id: string;
|
||||||
|
zoneId: string;
|
||||||
|
type: 'LED_full_spectrum' | 'LED_red_blue' | 'LED_tunable' | 'HPS' | 'fluorescent';
|
||||||
|
wattage: number;
|
||||||
|
ppfdAtCanopy: number;
|
||||||
|
spectrum: {
|
||||||
|
red: number;
|
||||||
|
blue: number;
|
||||||
|
green: number;
|
||||||
|
farRed: number;
|
||||||
|
uv: number;
|
||||||
|
};
|
||||||
|
dimmingPercent: number;
|
||||||
|
status: 'off' | 'on' | 'dimmed' | 'fault';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface LightSchedule {
|
||||||
|
zoneId: string;
|
||||||
|
photoperiod: {
|
||||||
|
onTime: string; // HH:MM
|
||||||
|
offTime: string;
|
||||||
|
totalHours: number;
|
||||||
|
};
|
||||||
|
intensity: {
|
||||||
|
dayPpfd: number;
|
||||||
|
nightPpfd: number;
|
||||||
|
rampMinutes: number;
|
||||||
|
};
|
||||||
|
spectrum?: {
|
||||||
|
vegetative: { red: number; blue: number };
|
||||||
|
flowering: { red: number; blue: number };
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Nutrient Delivery System
|
||||||
|
export interface NutrientDeliverySystem {
|
||||||
|
mixingMethod: 'manual' | 'semi_auto' | 'fully_auto';
|
||||||
|
|
||||||
|
// Stock solutions
|
||||||
|
stockSolutions: StockSolution[];
|
||||||
|
|
||||||
|
// Dosing
|
||||||
|
dosingPumps: DosingPump[];
|
||||||
|
|
||||||
|
// Current mix
|
||||||
|
currentRecipe: NutrientRecipe;
|
||||||
|
|
||||||
|
// Monitoring
|
||||||
|
monitoring: {
|
||||||
|
ec: number;
|
||||||
|
ph: number;
|
||||||
|
lastCalibration: string;
|
||||||
|
calibrationDue: string;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StockSolution {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: 'nutrient_a' | 'nutrient_b' | 'cal_mag' | 'ph_up' | 'ph_down' | 'silica' | 'beneficial';
|
||||||
|
concentration: string;
|
||||||
|
tankSizeL: number;
|
||||||
|
currentLevelL: number;
|
||||||
|
reorderThresholdL: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface DosingPump {
|
||||||
|
id: string;
|
||||||
|
solutionId: string;
|
||||||
|
flowRateMlPerMin: number;
|
||||||
|
status: 'idle' | 'dosing' | 'fault';
|
||||||
|
totalDosedMl: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface NutrientRecipe {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
cropType: string;
|
||||||
|
growthStage: 'seedling' | 'vegetative' | 'transition' | 'flowering' | 'fruiting';
|
||||||
|
|
||||||
|
// Targets
|
||||||
|
targetEc: number;
|
||||||
|
targetPh: number;
|
||||||
|
|
||||||
|
// Ratios
|
||||||
|
ratios: {
|
||||||
|
n: number;
|
||||||
|
p: number;
|
||||||
|
k: number;
|
||||||
|
ca: number;
|
||||||
|
mg: number;
|
||||||
|
s: number;
|
||||||
|
fe: number;
|
||||||
|
mn: number;
|
||||||
|
zn: number;
|
||||||
|
cu: number;
|
||||||
|
b: number;
|
||||||
|
mo: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Dosing
|
||||||
|
dosingRatiosMlPerL: { solutionId: string; mlPerL: number }[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Automation System
|
||||||
|
export interface AutomationSystem {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
type: 'planting' | 'transplanting' | 'harvesting' | 'packaging' | 'monitoring' | 'cleaning';
|
||||||
|
|
||||||
|
// Hardware
|
||||||
|
hardware: {
|
||||||
|
type: 'robotic_arm' | 'conveyor' | 'camera' | 'sensor_array' | 'mobile_robot';
|
||||||
|
model: string;
|
||||||
|
status: 'idle' | 'running' | 'maintenance' | 'fault';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Capabilities
|
||||||
|
capabilities: string[];
|
||||||
|
throughputPerHour: number;
|
||||||
|
|
||||||
|
// Integration
|
||||||
|
apiEndpoint?: string;
|
||||||
|
integrationStatus: 'connected' | 'disconnected' | 'error';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Growing Recipe
|
||||||
|
export interface GrowingRecipe {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
cropType: string;
|
||||||
|
variety?: string;
|
||||||
|
version: string;
|
||||||
|
|
||||||
|
// Stages
|
||||||
|
stages: GrowthStage[];
|
||||||
|
|
||||||
|
// Expected outcomes
|
||||||
|
expectedDays: number;
|
||||||
|
expectedYieldGrams: number;
|
||||||
|
expectedYieldPerSqm: number;
|
||||||
|
|
||||||
|
// Requirements
|
||||||
|
requirements: {
|
||||||
|
positions: number;
|
||||||
|
zoneType: string;
|
||||||
|
minimumPpfd: number;
|
||||||
|
idealTemperatureC: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Source
|
||||||
|
source: 'internal' | 'community' | 'commercial';
|
||||||
|
author?: string;
|
||||||
|
rating?: number;
|
||||||
|
timesUsed: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface GrowthStage {
|
||||||
|
name: string;
|
||||||
|
daysStart: number;
|
||||||
|
daysEnd: number;
|
||||||
|
|
||||||
|
// Environment
|
||||||
|
temperature: { day: number; night: number };
|
||||||
|
humidity: { day: number; night: number };
|
||||||
|
co2Ppm: number;
|
||||||
|
lightHours: number;
|
||||||
|
lightPpfd: number;
|
||||||
|
|
||||||
|
// Nutrients
|
||||||
|
nutrientRecipeId: string;
|
||||||
|
targetEc: number;
|
||||||
|
targetPh: number;
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
actions: StageAction[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface StageAction {
|
||||||
|
day: number;
|
||||||
|
action: 'transplant' | 'thin' | 'train' | 'top' | 'pollinate' | 'harvest_partial';
|
||||||
|
description: string;
|
||||||
|
automated: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crop Batch for tracking
|
||||||
|
export interface CropBatch {
|
||||||
|
id: string;
|
||||||
|
farmId: string;
|
||||||
|
zoneId: string;
|
||||||
|
|
||||||
|
// Crop info
|
||||||
|
cropType: string;
|
||||||
|
variety?: string;
|
||||||
|
recipeId: string;
|
||||||
|
|
||||||
|
// Planting
|
||||||
|
seedBatchId: string;
|
||||||
|
plantIds: string[];
|
||||||
|
plantCount: number;
|
||||||
|
plantingDate: string;
|
||||||
|
transplantDate?: string;
|
||||||
|
|
||||||
|
// Progress
|
||||||
|
currentStage: string;
|
||||||
|
currentDay: number;
|
||||||
|
healthScore: number;
|
||||||
|
|
||||||
|
// Expected
|
||||||
|
expectedHarvestDate: string;
|
||||||
|
expectedYieldKg: number;
|
||||||
|
|
||||||
|
// Actual (after harvest)
|
||||||
|
actualHarvestDate?: string;
|
||||||
|
actualYieldKg?: number;
|
||||||
|
qualityGrade?: string;
|
||||||
|
|
||||||
|
// Status
|
||||||
|
status: 'germinating' | 'growing' | 'ready' | 'harvesting' | 'completed' | 'failed';
|
||||||
|
|
||||||
|
// Issues
|
||||||
|
issues: BatchIssue[];
|
||||||
|
|
||||||
|
// Environmental log
|
||||||
|
environmentLog: {
|
||||||
|
timestamp: string;
|
||||||
|
readings: ZoneEnvironmentReadings;
|
||||||
|
}[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BatchIssue {
|
||||||
|
id: string;
|
||||||
|
timestamp: string;
|
||||||
|
type: 'pest' | 'disease' | 'nutrient' | 'environment' | 'equipment' | 'other';
|
||||||
|
severity: 'minor' | 'moderate' | 'severe' | 'critical';
|
||||||
|
description: string;
|
||||||
|
affectedPlants: number;
|
||||||
|
resolution?: string;
|
||||||
|
resolvedAt?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Energy and Resource Tracking
|
||||||
|
export interface ResourceUsage {
|
||||||
|
farmId: string;
|
||||||
|
periodStart: string;
|
||||||
|
periodEnd: string;
|
||||||
|
|
||||||
|
// Energy
|
||||||
|
electricityKwh: number;
|
||||||
|
electricityCostUsd: number;
|
||||||
|
renewablePercent: number;
|
||||||
|
peakDemandKw: number;
|
||||||
|
|
||||||
|
// Water
|
||||||
|
waterUsageL: number;
|
||||||
|
waterCostUsd: number;
|
||||||
|
waterRecycledPercent: number;
|
||||||
|
|
||||||
|
// CO2
|
||||||
|
co2UsedKg: number;
|
||||||
|
co2CostUsd: number;
|
||||||
|
|
||||||
|
// Nutrients
|
||||||
|
nutrientsUsedL: number;
|
||||||
|
nutrientCostUsd: number;
|
||||||
|
|
||||||
|
// Comparison
|
||||||
|
kwhPerKgProduce: number;
|
||||||
|
litersPerKgProduce: number;
|
||||||
|
costPerKgProduce: number;
|
||||||
|
|
||||||
|
// Benchmarks
|
||||||
|
industryBenchmarkKwhPerKg: number;
|
||||||
|
industryBenchmarkLitersPerKg: number;
|
||||||
|
efficiencyVsBenchmark: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Vertical Farm Analytics
|
||||||
|
export interface FarmAnalytics {
|
||||||
|
farmId: string;
|
||||||
|
generatedAt: string;
|
||||||
|
period: string;
|
||||||
|
|
||||||
|
// Production
|
||||||
|
totalYieldKg: number;
|
||||||
|
yieldPerSqmPerYear: number;
|
||||||
|
cropCyclesCompleted: number;
|
||||||
|
averageCyclesDays: number;
|
||||||
|
|
||||||
|
// Quality
|
||||||
|
averageQualityScore: number;
|
||||||
|
gradeAPercent: number;
|
||||||
|
wastagePercent: number;
|
||||||
|
|
||||||
|
// Efficiency
|
||||||
|
cropSuccessRate: number;
|
||||||
|
spaceUtilization: number;
|
||||||
|
laborHoursPerKg: number;
|
||||||
|
|
||||||
|
// Financial
|
||||||
|
revenueUsd: number;
|
||||||
|
costUsd: number;
|
||||||
|
profitMarginPercent: number;
|
||||||
|
revenuePerSqm: number;
|
||||||
|
|
||||||
|
// Environmental
|
||||||
|
carbonFootprintKgPerKg: number;
|
||||||
|
waterUseLPerKg: number;
|
||||||
|
energyUseKwhPerKg: number;
|
||||||
|
|
||||||
|
// Top crops
|
||||||
|
topCropsByYield: { crop: string; yieldKg: number }[];
|
||||||
|
topCropsByRevenue: { crop: string; revenueUsd: number }[];
|
||||||
|
topCropsByEfficiency: { crop: string; efficiencyScore: number }[];
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue