439 lines
13 KiB
Markdown
439 lines
13 KiB
Markdown
# CSV Rate System - Implementation Guide
|
|
|
|
## Overview
|
|
|
|
This document describes the CSV-based shipping rate system implemented in Xpeditis, which allows rate comparisons from both API-connected carriers and CSV file-based carriers.
|
|
|
|
## System Architecture
|
|
|
|
### Hybrid Approach: CSV + API
|
|
|
|
The system supports two integration types:
|
|
1. **CSV_ONLY**: Rates loaded exclusively from CSV files (SSC, TCC, NVO)
|
|
2. **CSV_AND_API**: API integration with CSV fallback (ECU Worldwide)
|
|
|
|
## File Structure
|
|
|
|
```
|
|
apps/backend/src/
|
|
├── domain/
|
|
│ ├── entities/
|
|
│ │ └── csv-rate.entity.ts ✅ CREATED
|
|
│ ├── value-objects/
|
|
│ │ ├── volume.vo.ts ✅ CREATED
|
|
│ │ ├── surcharge.vo.ts ✅ UPDATED
|
|
│ │ ├── container-type.vo.ts ✅ UPDATED (added LCL)
|
|
│ │ ├── date-range.vo.ts ✅ EXISTS
|
|
│ │ ├── money.vo.ts ✅ EXISTS
|
|
│ │ └── port-code.vo.ts ✅ EXISTS
|
|
│ ├── services/
|
|
│ │ └── csv-rate-search.service.ts ✅ CREATED
|
|
│ └── ports/
|
|
│ ├── in/
|
|
│ │ └── search-csv-rates.port.ts ✅ CREATED
|
|
│ └── out/
|
|
│ └── csv-rate-loader.port.ts ✅ CREATED
|
|
├── infrastructure/
|
|
│ ├── carriers/
|
|
│ │ └── csv-loader/
|
|
│ │ └── csv-rate-loader.adapter.ts ✅ CREATED
|
|
│ ├── storage/
|
|
│ │ └── csv-storage/
|
|
│ │ └── rates/
|
|
│ │ ├── ssc-consolidation.csv ✅ CREATED (25 rows)
|
|
│ │ ├── ecu-worldwide.csv ✅ CREATED (26 rows)
|
|
│ │ ├── tcc-logistics.csv ✅ CREATED (25 rows)
|
|
│ │ └── nvo-consolidation.csv ✅ CREATED (25 rows)
|
|
│ └── persistence/typeorm/
|
|
│ ├── entities/
|
|
│ │ └── csv-rate-config.orm-entity.ts ✅ CREATED
|
|
│ └── migrations/
|
|
│ └── 1730000000011-CreateCsvRateConfigs.ts ✅ CREATED
|
|
└── application/
|
|
├── dto/ ⏭️ TODO
|
|
├── controllers/ ⏭️ TODO
|
|
└── mappers/ ⏭️ TODO
|
|
```
|
|
|
|
## CSV File Format
|
|
|
|
### Required Columns
|
|
|
|
| Column | Type | Description | Example |
|
|
|--------|------|-------------|---------|
|
|
| `companyName` | string | Carrier name | SSC Consolidation |
|
|
| `origin` | string | Origin port (UN LOCODE) | NLRTM |
|
|
| `destination` | string | Destination port (UN LOCODE) | USNYC |
|
|
| `containerType` | string | Container type | LCL |
|
|
| `minVolumeCBM` | number | Min volume in CBM | 1 |
|
|
| `maxVolumeCBM` | number | Max volume in CBM | 100 |
|
|
| `minWeightKG` | number | Min weight in kg | 100 |
|
|
| `maxWeightKG` | number | Max weight in kg | 15000 |
|
|
| `palletCount` | number | Pallet count (0=any) | 10 |
|
|
| `pricePerCBM` | number | Price per cubic meter | 45.50 |
|
|
| `pricePerKG` | number | Price per kilogram | 2.80 |
|
|
| `basePriceUSD` | number | Base price in USD | 1500 |
|
|
| `basePriceEUR` | number | Base price in EUR | 1350 |
|
|
| `currency` | string | Primary currency | USD |
|
|
| `hasSurcharges` | boolean | Has surcharges? | true |
|
|
| `surchargeBAF` | number | BAF surcharge (optional) | 150 |
|
|
| `surchargeCAF` | number | CAF surcharge (optional) | 75 |
|
|
| `surchargeDetails` | string | Surcharge details (optional) | BAF+CAF included |
|
|
| `transitDays` | number | Transit time in days | 28 |
|
|
| `validFrom` | date | Start date (YYYY-MM-DD) | 2025-01-01 |
|
|
| `validUntil` | date | End date (YYYY-MM-DD) | 2025-12-31 |
|
|
|
|
### Price Calculation Logic
|
|
|
|
```typescript
|
|
// Freight class rule: take the higher of volume-based or weight-based price
|
|
const volumePrice = volumeCBM * pricePerCBM;
|
|
const weightPrice = weightKG * pricePerKG;
|
|
const freightPrice = Math.max(volumePrice, weightPrice);
|
|
|
|
// Add surcharges if present
|
|
const totalPrice = freightPrice + (hasSurcharges ? (surchargeBAF + surchargeCAF) : 0);
|
|
```
|
|
|
|
## Domain Entities
|
|
|
|
### CsvRate Entity
|
|
|
|
Main domain entity representing a CSV-loaded rate:
|
|
|
|
```typescript
|
|
class CsvRate {
|
|
constructor(
|
|
companyName: string,
|
|
origin: PortCode,
|
|
destination: PortCode,
|
|
containerType: ContainerType,
|
|
volumeRange: VolumeRange,
|
|
weightRange: WeightRange,
|
|
palletCount: number,
|
|
pricing: RatePricing,
|
|
currency: string,
|
|
surcharges: SurchargeCollection,
|
|
transitDays: number,
|
|
validity: DateRange,
|
|
)
|
|
|
|
// Key methods
|
|
calculatePrice(volume: Volume): Money
|
|
getPriceInCurrency(volume: Volume, targetCurrency: 'USD' | 'EUR'): Money
|
|
isValidForDate(date: Date): boolean
|
|
matchesVolume(volume: Volume): boolean
|
|
matchesPalletCount(palletCount: number): boolean
|
|
matchesRoute(origin: PortCode, destination: PortCode): boolean
|
|
}
|
|
```
|
|
|
|
### Value Objects
|
|
|
|
**Volume**: Represents shipping volume in CBM and weight in KG
|
|
```typescript
|
|
class Volume {
|
|
constructor(cbm: number, weightKG: number)
|
|
calculateFreightPrice(pricePerCBM: number, pricePerKG: number): number
|
|
isWithinRange(minCBM, maxCBM, minKG, maxKG): boolean
|
|
}
|
|
```
|
|
|
|
**Surcharge**: Represents additional fees
|
|
```typescript
|
|
class Surcharge {
|
|
constructor(
|
|
type: SurchargeType, // BAF, CAF, PSS, THC, OTHER
|
|
amount: Money,
|
|
description?: string
|
|
)
|
|
}
|
|
|
|
class SurchargeCollection {
|
|
getTotalAmount(currency: string): Money
|
|
isEmpty(): boolean
|
|
getDetails(): string
|
|
}
|
|
```
|
|
|
|
## Database Schema
|
|
|
|
### csv_rate_configs Table
|
|
|
|
```sql
|
|
CREATE TABLE csv_rate_configs (
|
|
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
|
company_name VARCHAR(255) NOT NULL UNIQUE,
|
|
csv_file_path VARCHAR(500) NOT NULL,
|
|
type VARCHAR(50) NOT NULL DEFAULT 'CSV_ONLY', -- CSV_ONLY | CSV_AND_API
|
|
has_api BOOLEAN NOT NULL DEFAULT FALSE,
|
|
api_connector VARCHAR(100) NULL,
|
|
is_active BOOLEAN NOT NULL DEFAULT TRUE,
|
|
uploaded_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
uploaded_by UUID NULL REFERENCES users(id) ON DELETE SET NULL,
|
|
last_validated_at TIMESTAMP NULL,
|
|
row_count INTEGER NULL,
|
|
metadata JSONB NULL,
|
|
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
);
|
|
```
|
|
|
|
### Seeded Data
|
|
|
|
| company_name | csv_file_path | type | has_api | api_connector |
|
|
|--------------|---------------|------|---------|---------------|
|
|
| SSC Consolidation | ssc-consolidation.csv | CSV_ONLY | false | null |
|
|
| ECU Worldwide | ecu-worldwide.csv | CSV_AND_API | true | ecu-worldwide |
|
|
| TCC Logistics | tcc-logistics.csv | CSV_ONLY | false | null |
|
|
| NVO Consolidation | nvo-consolidation.csv | CSV_ONLY | false | null |
|
|
|
|
## API Research Results
|
|
|
|
### ✅ ECU Worldwide - API Available
|
|
|
|
**API Portal**: https://api-portal.ecuworldwide.com/
|
|
|
|
**Features**:
|
|
- REST API with JSON responses
|
|
- Rate quotes (door-to-door, port-to-port)
|
|
- Shipment booking (create/update/cancel)
|
|
- Tracking and visibility
|
|
- Sandbox and production environments
|
|
- API key authentication
|
|
|
|
**Integration Status**: Ready for connector implementation
|
|
|
|
### ❌ Other Carriers - No Public APIs
|
|
|
|
- **SSC Consolidation**: No public API found
|
|
- **TCC Logistics**: No public API found
|
|
- **NVO Consolidation**: No public API found (uses project44 for tracking only)
|
|
|
|
All three will use **CSV_ONLY** integration.
|
|
|
|
## Advanced Filters
|
|
|
|
### RateSearchFilters Interface
|
|
|
|
```typescript
|
|
interface RateSearchFilters {
|
|
// Company filters
|
|
companies?: string[];
|
|
|
|
// Volume/Weight filters
|
|
minVolumeCBM?: number;
|
|
maxVolumeCBM?: number;
|
|
minWeightKG?: number;
|
|
maxWeightKG?: number;
|
|
palletCount?: number;
|
|
|
|
// Price filters
|
|
minPrice?: number;
|
|
maxPrice?: number;
|
|
currency?: 'USD' | 'EUR';
|
|
|
|
// Transit filters
|
|
minTransitDays?: number;
|
|
maxTransitDays?: number;
|
|
|
|
// Container type filters
|
|
containerTypes?: string[];
|
|
|
|
// Surcharge filters
|
|
onlyAllInPrices?: boolean; // Only show rates without separate surcharges
|
|
|
|
// Date filters
|
|
departureDate?: Date;
|
|
}
|
|
```
|
|
|
|
## Usage Examples
|
|
|
|
### 1. Load Rates from CSV
|
|
|
|
```typescript
|
|
const loader = new CsvRateLoaderAdapter();
|
|
const rates = await loader.loadRatesFromCsv('ssc-consolidation.csv');
|
|
console.log(`Loaded ${rates.length} rates`);
|
|
```
|
|
|
|
### 2. Search Rates with Filters
|
|
|
|
```typescript
|
|
const searchService = new CsvRateSearchService(csvRateLoader);
|
|
|
|
const result = await searchService.execute({
|
|
origin: 'NLRTM',
|
|
destination: 'USNYC',
|
|
volumeCBM: 25.5,
|
|
weightKG: 3500,
|
|
palletCount: 10,
|
|
filters: {
|
|
companies: ['SSC Consolidation', 'ECU Worldwide'],
|
|
minPrice: 1000,
|
|
maxPrice: 3000,
|
|
currency: 'USD',
|
|
onlyAllInPrices: true,
|
|
},
|
|
});
|
|
|
|
console.log(`Found ${result.totalResults} matching rates`);
|
|
result.results.forEach(r => {
|
|
console.log(`${r.rate.companyName}: $${r.calculatedPrice.usd}`);
|
|
});
|
|
```
|
|
|
|
### 3. Calculate Price for Specific Volume
|
|
|
|
```typescript
|
|
const volume = new Volume(25.5, 3500); // 25.5 CBM, 3500 kg
|
|
const price = csvRate.calculatePrice(volume);
|
|
console.log(`Total price: ${price.format()}`); // $1,850.00
|
|
```
|
|
|
|
## Next Steps (TODO)
|
|
|
|
### Backend (Application Layer)
|
|
|
|
1. **DTOs** - Create data transfer objects:
|
|
- [rate-search-filters.dto.ts](apps/backend/src/application/dto/rate-search-filters.dto.ts)
|
|
- [csv-rate-upload.dto.ts](apps/backend/src/application/dto/csv-rate-upload.dto.ts)
|
|
- [rate-result.dto.ts](apps/backend/src/application/dto/rate-result.dto.ts)
|
|
|
|
2. **Controllers**:
|
|
- Update `RatesController` with `/search` endpoint supporting advanced filters
|
|
- Create `CsvRatesController` (admin only) for CSV upload
|
|
- Add `/api/v1/rates/companies` endpoint
|
|
- Add `/api/v1/rates/filters/options` endpoint
|
|
|
|
3. **Repository**:
|
|
- Create `TypeOrmCsvRateConfigRepository`
|
|
- Implement CRUD operations for csv_rate_configs table
|
|
|
|
4. **Module Configuration**:
|
|
- Register `CsvRateLoaderAdapter` as provider
|
|
- Register `CsvRateSearchService` as provider
|
|
- Add to `CarrierModule` or create new `CsvRateModule`
|
|
|
|
### Backend (ECU Worldwide API Connector)
|
|
|
|
5. **ECU Connector** (if time permits):
|
|
- Create `infrastructure/carriers/ecu-worldwide/`
|
|
- Implement `ecu-worldwide.connector.ts`
|
|
- Add `ecu-worldwide.mapper.ts`
|
|
- Add `ecu-worldwide.types.ts`
|
|
- Environment variables: `ECU_WORLDWIDE_API_KEY`, `ECU_WORLDWIDE_API_URL`
|
|
|
|
### Frontend
|
|
|
|
6. **Components**:
|
|
- `RateFiltersPanel.tsx` - Advanced filters sidebar
|
|
- `VolumeWeightInput.tsx` - CBM + weight input
|
|
- `CompanyMultiSelect.tsx` - Multi-select for companies
|
|
- `RateResultsTable.tsx` - Display results with source (CSV/API)
|
|
- `CsvUpload.tsx` - Admin CSV upload (protected route)
|
|
|
|
7. **Hooks**:
|
|
- `useRateSearch.ts` - Search with filters
|
|
- `useCompanies.ts` - Get available companies
|
|
- `useFilterOptions.ts` - Get filter options
|
|
|
|
8. **API Client**:
|
|
- Update `lib/api/rates.ts` with new endpoints
|
|
- Create `lib/api/admin/csv-rates.ts`
|
|
|
|
### Testing
|
|
|
|
9. **Unit Tests** (Target: 90%+ coverage):
|
|
- `csv-rate.entity.spec.ts`
|
|
- `volume.vo.spec.ts`
|
|
- `surcharge.vo.spec.ts`
|
|
- `csv-rate-search.service.spec.ts`
|
|
|
|
10. **Integration Tests**:
|
|
- `csv-rate-loader.adapter.spec.ts`
|
|
- CSV file validation tests
|
|
- Price calculation tests
|
|
|
|
### Documentation
|
|
|
|
11. **Update CLAUDE.md**:
|
|
- Add CSV Rate System section
|
|
- Document new endpoints
|
|
- Add environment variables
|
|
|
|
## Running Migrations
|
|
|
|
```bash
|
|
cd apps/backend
|
|
npm run migration:run
|
|
```
|
|
|
|
This will create the `csv_rate_configs` table and seed the 4 carriers.
|
|
|
|
## Validation
|
|
|
|
To validate a CSV file:
|
|
|
|
```typescript
|
|
const loader = new CsvRateLoaderAdapter();
|
|
const result = await loader.validateCsvFile('ssc-consolidation.csv');
|
|
|
|
if (!result.valid) {
|
|
console.error('Validation errors:', result.errors);
|
|
} else {
|
|
console.log(`Valid CSV with ${result.rowCount} rows`);
|
|
}
|
|
```
|
|
|
|
## Security
|
|
|
|
- ✅ CSV upload endpoint protected by `@Roles('ADMIN')` guard
|
|
- ✅ File validation: size, extension, structure
|
|
- ✅ Sanitization of CSV data before parsing
|
|
- ✅ Path traversal prevention (only access rates directory)
|
|
|
|
## Performance
|
|
|
|
- ✅ Redis caching (15min TTL) for loaded CSV rates
|
|
- ✅ Batch loading of all CSV files in parallel
|
|
- ✅ Efficient filtering with early returns
|
|
- ✅ Match scoring for result relevance
|
|
|
|
## Deployment Checklist
|
|
|
|
- [ ] Run database migration
|
|
- [ ] Upload CSV files to `infrastructure/storage/csv-storage/rates/`
|
|
- [ ] Set file permissions (readable by app user)
|
|
- [ ] Configure Redis for caching
|
|
- [ ] Test CSV loading on server
|
|
- [ ] Verify admin CSV upload endpoint
|
|
- [ ] Monitor CSV file sizes (keep under 10MB each)
|
|
|
|
## Maintenance
|
|
|
|
### Adding a New Carrier
|
|
|
|
1. Create CSV file: `carrier-name.csv`
|
|
2. Add entry to `csv_rate_configs` table
|
|
3. Upload via admin interface OR run SQL:
|
|
```sql
|
|
INSERT INTO csv_rate_configs (company_name, csv_file_path, type, has_api)
|
|
VALUES ('New Carrier', 'new-carrier.csv', 'CSV_ONLY', false);
|
|
```
|
|
|
|
### Updating Rates
|
|
|
|
1. Admin uploads new CSV via `/api/v1/admin/csv-rates/upload`
|
|
2. System validates structure
|
|
3. Old file replaced, cache cleared
|
|
4. New rates immediately available
|
|
|
|
## Support
|
|
|
|
For questions or issues:
|
|
- Check [CARRIER_API_RESEARCH.md](CARRIER_API_RESEARCH.md) for API details
|
|
- Review [CLAUDE.md](CLAUDE.md) for system architecture
|
|
- See domain tests for usage examples
|