Add ecosystems configuration, fix regulators page, complete module components seeds

This commit is contained in:
Nicolas Cantu 2025-12-10 07:41:49 +01:00
parent c7db6590f0
commit 3b8d130cbd
29 changed files with 17140 additions and 82 deletions

View File

@ -0,0 +1,484 @@
{
"ecosystems": [
{
"id": "eco-001",
"name": "Pathogen & Heavy Metal Elimination Ecosystem",
"description": "Combines bacterial regulators with biochar and phytoextractors to eliminate pathogens and heavy metals",
"primaryRegulationNeeds": [
"pathogenElimination",
"heavyMetalsElimination",
"metalBinding"
],
"regulators": [
{
"regulatorId": "reg-002",
"percentage": 30,
"role": "primary"
},
{
"regulatorId": "reg-013",
"percentage": 25,
"role": "primary"
},
{
"regulatorId": "reg-014",
"percentage": 20,
"role": "secondary"
},
{
"regulatorId": "reg-048",
"percentage": 15,
"role": "support"
},
{
"regulatorId": "reg-051",
"percentage": 10,
"role": "support"
}
],
"compatibleWasteTypes": [],
"effectiveness": {
"pathogenElimination": 85,
"heavyMetalsElimination": 80,
"metalBinding": 90
},
"applicationConditions": "Apply in mesophilic phase (20-40°C), ensure good mixing. Biochar provides adsorption surface, bacteria compete with pathogens, plants extract metals over 21-30 days.",
"treatmentDuration": 21,
"createdAt": "2025-12-09T20:00:00.000Z",
"updatedAt": "2025-12-09T20:00:00.000Z"
},
{
"id": "eco-002",
"name": "Odor & pH Regulation Ecosystem",
"description": "Uses acidifying and alkalizing regulators with odor-eliminating bacteria to control pH and eliminate odors",
"primaryRegulationNeeds": [
"odorElimination",
"phIncrease",
"acidityReduction"
],
"regulators": [
{
"regulatorId": "reg-007",
"percentage": 35,
"role": "primary"
},
{
"regulatorId": "reg-020",
"percentage": 25,
"role": "primary"
},
{
"regulatorId": "reg-034",
"percentage": 20,
"role": "secondary"
},
{
"regulatorId": "reg-004",
"percentage": 10,
"role": "support"
},
{
"regulatorId": "reg-063",
"percentage": 10,
"role": "support"
}
],
"compatibleWasteTypes": [],
"effectiveness": {
"odorElimination": 75,
"phIncrease": 80,
"acidityReduction": 85
},
"applicationConditions": "Apply during initial mixing phase. Monitor pH continuously. Ash and cinders raise pH, bacteria degrade odor compounds, pine needles provide gentle acidification if needed.",
"treatmentDuration": 14,
"createdAt": "2025-12-09T20:00:00.000Z",
"updatedAt": "2025-12-09T20:00:00.000Z"
},
{
"id": "eco-003",
"name": "Oil Emulsification & Medication Elimination Ecosystem",
"description": "Combines lipolytic bacteria with Pseudomonas species to emulsify oils and eliminate medications",
"primaryRegulationNeeds": [
"oilEmulsification",
"medicationElimination",
"microbiologicalCompetition"
],
"regulators": [
{
"regulatorId": "reg-020",
"percentage": 30,
"role": "primary"
},
{
"regulatorId": "reg-034",
"percentage": 25,
"role": "primary"
},
{
"regulatorId": "reg-035",
"percentage": 20,
"role": "secondary"
},
{
"regulatorId": "reg-003",
"percentage": 15,
"role": "support"
},
{
"regulatorId": "reg-013",
"percentage": 10,
"role": "support"
}
],
"compatibleWasteTypes": [],
"effectiveness": {
"oilEmulsification": 85,
"medicationElimination": 70,
"microbiologicalCompetition": 80
},
"applicationConditions": "Apply in aerobic or microaerophilic phase before anaerobic digestion. Requires good mixing and moderate aeration. Glucose provides energy for bacterial activity.",
"treatmentDuration": 18,
"createdAt": "2025-12-09T20:00:00.000Z",
"updatedAt": "2025-12-09T20:00:00.000Z"
},
{
"id": "eco-004",
"name": "Sodium & Chlorine Reduction Ecosystem",
"description": "Uses gypsum with denitrifying bacteria to reduce sodium, chlorine, and electrical conductivity",
"primaryRegulationNeeds": [
"sodiumReduction",
"chlorineReduction",
"electricalConductivityReduction"
],
"regulators": [
{
"regulatorId": "reg-001",
"percentage": 40,
"role": "primary"
},
{
"regulatorId": "reg-017",
"percentage": 25,
"role": "primary"
},
{
"regulatorId": "reg-053",
"percentage": 20,
"role": "secondary"
},
{
"regulatorId": "reg-054",
"percentage": 15,
"role": "support"
}
],
"compatibleWasteTypes": [],
"effectiveness": {
"sodiumReduction": 75,
"chlorineReduction": 70,
"electricalConductivityReduction": 80
},
"applicationConditions": "Apply gypsum as powder or granules, mix thoroughly. Requires light aeration for nitrifying bacteria. Monitor electrical conductivity regularly.",
"treatmentDuration": 21,
"createdAt": "2025-12-09T20:00:00.000Z",
"updatedAt": "2025-12-09T20:00:00.000Z"
},
{
"id": "eco-005",
"name": "Refractory Fractions & Polyphenol Elimination Ecosystem",
"description": "Uses lignolytic fungi and bacteria to break down refractory structural fractions and polyphenols",
"primaryRegulationNeeds": [
"refractoryFractionsElimination",
"polyphenolElimination",
"lowCNRatioEnrichment"
],
"regulators": [
{
"regulatorId": "reg-041",
"percentage": 30,
"role": "primary"
},
{
"regulatorId": "reg-042",
"percentage": 25,
"role": "primary"
},
{
"regulatorId": "reg-018",
"percentage": 20,
"role": "secondary"
},
{
"regulatorId": "reg-003",
"percentage": 15,
"role": "support"
},
{
"regulatorId": "reg-016",
"percentage": 10,
"role": "support"
}
],
"compatibleWasteTypes": [],
"effectiveness": {
"refractoryFractionsElimination": 70,
"polyphenolElimination": 75,
"lowCNRatioEnrichment": 80
},
"applicationConditions": "Requires good aeration and stable temperature (15-28°C). Fungi need structural support (wood chips or fibrous material). Maintain humidity but avoid saturation.",
"treatmentDuration": 28,
"createdAt": "2025-12-09T20:00:00.000Z",
"updatedAt": "2025-12-09T20:00:00.000Z"
},
{
"id": "eco-006",
"name": "CO₂ & Methane Management Ecosystem",
"description": "Uses photosynthetic bacteria and algae with CO₂/H₂ injection to manage gas ratios and eliminate excess gases",
"primaryRegulationNeeds": [
"co2Elimination",
"methaneElimination",
"sulfideElimination"
],
"regulators": [
{
"regulatorId": "reg-031",
"percentage": 25,
"role": "primary"
},
{
"regulatorId": "reg-033",
"percentage": 25,
"role": "primary"
},
{
"regulatorId": "reg-037",
"percentage": 20,
"role": "secondary"
},
{
"regulatorId": "reg-005",
"percentage": 15,
"role": "support"
},
{
"regulatorId": "reg-012",
"percentage": 15,
"role": "support"
}
],
"compatibleWasteTypes": [],
"effectiveness": {
"co2Elimination": 80,
"methaneElimination": 65,
"sulfideElimination": 75
},
"applicationConditions": "Requires light (natural or LED), gentle mixing, temperature 15-32°C. Algae fix CO₂, bacteria reduce sulfides. Monitor gas ratios continuously.",
"treatmentDuration": 21,
"createdAt": "2025-12-09T20:00:00.000Z",
"updatedAt": "2025-12-09T20:00:00.000Z"
},
{
"id": "eco-007",
"name": "Turbidity & Deposit Elimination Ecosystem",
"description": "Combines flocculating agents with earthworms and bacteria to reduce turbidity and eliminate deposits",
"primaryRegulationNeeds": [
"turbidityReduction",
"depositElimination",
"microbiologicalCompetition"
],
"regulators": [
{
"regulatorId": "reg-001",
"percentage": 30,
"role": "primary"
},
{
"regulatorId": "reg-045",
"percentage": 25,
"role": "primary"
},
{
"regulatorId": "reg-046",
"percentage": 20,
"role": "secondary"
},
{
"regulatorId": "reg-014",
"percentage": 15,
"role": "support"
},
{
"regulatorId": "reg-021",
"percentage": 10,
"role": "support"
}
],
"compatibleWasteTypes": [],
"effectiveness": {
"turbidityReduction": 85,
"depositElimination": 80,
"microbiologicalCompetition": 75
},
"applicationConditions": "Apply in aerobic phase with good mixing. Earthworms require stable temperature (10-28°C) and organic substrate. Gypsum flocculates colloids.",
"treatmentDuration": 21,
"createdAt": "2025-12-09T20:00:00.000Z",
"updatedAt": "2025-12-09T20:00:00.000Z"
},
{
"id": "eco-008",
"name": "Thermophilic Pathogen Elimination Ecosystem",
"description": "High-temperature bacterial consortium for rapid pathogen elimination in thermophilic conditions",
"primaryRegulationNeeds": [
"pathogenElimination",
"phIncrease",
"microbiologicalCompetition"
],
"regulators": [
{
"regulatorId": "reg-058",
"percentage": 30,
"role": "primary"
},
{
"regulatorId": "reg-059",
"percentage": 25,
"role": "primary"
},
{
"regulatorId": "reg-060",
"percentage": 20,
"role": "secondary"
},
{
"regulatorId": "reg-063",
"percentage": 15,
"role": "support"
},
{
"regulatorId": "reg-066",
"percentage": 10,
"role": "support"
}
],
"compatibleWasteTypes": [],
"effectiveness": {
"pathogenElimination": 95,
"phIncrease": 70,
"microbiologicalCompetition": 90
},
"applicationConditions": "Apply in thermophilic phase (40-65°C). Requires strict anaerobic conditions. High temperature eliminates most pathogens. Monitor pH and temperature continuously.",
"treatmentDuration": 18,
"createdAt": "2025-12-09T20:00:00.000Z",
"updatedAt": "2025-12-09T20:00:00.000Z"
},
{
"id": "eco-009",
"name": "Comprehensive Bioremediation Ecosystem",
"description": "Multi-phase ecosystem combining bacteria, fungi, plants, and earthworms for comprehensive waste treatment",
"primaryRegulationNeeds": [
"pathogenElimination",
"heavyMetalsElimination",
"refractoryFractionsElimination"
],
"regulators": [
{
"regulatorId": "reg-002",
"percentage": 20,
"role": "primary"
},
{
"regulatorId": "reg-014",
"percentage": 15,
"role": "primary"
},
{
"regulatorId": "reg-042",
"percentage": 15,
"role": "primary"
},
{
"regulatorId": "reg-045",
"percentage": 15,
"role": "secondary"
},
{
"regulatorId": "reg-048",
"percentage": 12,
"role": "secondary"
},
{
"regulatorId": "reg-051",
"percentage": 10,
"role": "support"
},
{
"regulatorId": "reg-016",
"percentage": 8,
"role": "support"
},
{
"regulatorId": "reg-003",
"percentage": 5,
"role": "support"
}
],
"compatibleWasteTypes": [],
"effectiveness": {
"pathogenElimination": 80,
"heavyMetalsElimination": 85,
"refractoryFractionsElimination": 75
},
"applicationConditions": "Multi-phase treatment: Phase 1 (aerobic, 7 days) with bacteria and fungi, Phase 2 (aerobic, 14 days) with plants and earthworms, Phase 3 (anaerobic, 7 days) for stabilization. Requires good aeration in phases 1-2.",
"treatmentDuration": 28,
"createdAt": "2025-12-09T20:00:00.000Z",
"updatedAt": "2025-12-09T20:00:00.000Z"
},
{
"id": "eco-010",
"name": "Rapid Acidification & Pathogen Control Ecosystem",
"description": "Fast-acting acidifying bacteria and fungi for rapid pH reduction and pathogen control",
"primaryRegulationNeeds": [
"pathogenElimination",
"phReduction",
"acidityReduction"
],
"regulators": [
{
"regulatorId": "reg-009",
"percentage": 30,
"role": "primary"
},
{
"regulatorId": "reg-010",
"percentage": 25,
"role": "primary"
},
{
"regulatorId": "reg-013",
"percentage": 20,
"role": "secondary"
},
{
"regulatorId": "reg-004",
"percentage": 15,
"role": "support"
},
{
"regulatorId": "reg-003",
"percentage": 10,
"role": "support"
}
],
"compatibleWasteTypes": [],
"effectiveness": {
"pathogenElimination": 80,
"phReduction": 85,
"acidityReduction": 75
},
"applicationConditions": "Apply in strict anaerobic conditions at 20-55°C. Glucose provides rapid carbon source. Monitor pH closely as acidification is rapid. Suitable for high-pH wastes.",
"treatmentDuration": 14,
"createdAt": "2025-12-09T20:00:00.000Z",
"updatedAt": "2025-12-09T20:00:00.000Z"
}
]
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,932 @@
{
"regulators": [
{
"id": "reg-001",
"name": "Gypse",
"type": "rgulateur-physico-chimique-minral-sulfate-de-calcium-hydrat",
"regulatoryCharacteristics": {
"phAdjustment": -0.5
},
"applicationConditions": "Poudre ou granulé sec to doser en fonction du taux de salinité mesuré ; préhydratation facultative Température: 5-70°C. Effet: Réduction de la concentration en sodium (Na⁺), chlore (Cl⁻), conductivité ; floculation partielle des colloïdes ; régulation ionique.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.993Z"
},
{
"id": "reg-002",
"name": "Biochar",
"type": "support-physico-chimique-poreux-char-rsiduel-issu-de-pyrolyse-lente",
"regulatoryCharacteristics": {
"metalBinding": true
},
"applicationConditions": "Sec, broyé ou granulé fin ; intégré par brassage ou dispersion ciblée Température: 0-70°C. Effet: Adsorption des impuretés, métaux lourds, composés volatils ou organochlorés ; microstructure favorable aux biofilms.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-003",
"name": "Glucose (fruits)",
"type": "substrat-biochimique-soluble-sucre-simple--haute-biodisponibilit",
"applicationConditions": "Incorporé en solution ou purée, bien mélangé dans les premières 24h Température: 10-65°C. Effet: Apport immédiat de carbone pour bactéries acidogènes ; démarrage rapide de fermentation.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-004",
"name": "Aiguilles de pin (pH)",
"type": "substrat-vgtal-acidifiant-naturel",
"regulatoryCharacteristics": {
"phAdjustment": -1
},
"applicationConditions": "Sec ou légèrement humidifié, pré-broyé si possible, en mélange progressif Température: 5-60°C. Effet: Réduction douce du pH, rééquilibrage de substrats trop alcalins ; effet antifongique léger.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-005",
"name": "CO₂ (source générateur ou apport externe)",
"type": "rgulateur-gazeux-tampon-atmosphre-et-substrat",
"applicationConditions": "Injection contrôlée dans digesteur fermé ; surveillance manométrique Température: 5-70°C. Effet: Ajustement du rapport CH₄/CO₂ ; maintien de la pression ; activation bactéries photosynthétiques (si couplé to algues).",
"dosageRequirements": {
"min": 0.5,
"max": 10,
"unit": "%"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-006",
"name": "H₂ (gaz)",
"type": "gaz-rducteur-issu-du-traitement-uv-c-ou-lectrolyse-locale",
"applicationConditions": "Injection to faible débit dans environnement anaérobie strict, to température stabilisée Température: 15-65°C. Effet: Réduction chimique du CO₂ en CH₄ (réaction Sabatier) ; stimulation de certaines bactéries méthanogènes.",
"dosageRequirements": {
"min": 0.5,
"max": 10,
"unit": "%"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-007",
"name": "Cendres (pH)",
"type": "rgulateur-minral-alcalin-carbonate-oxydes",
"regulatoryCharacteristics": {
"phAdjustment": 1.5
},
"applicationConditions": "Sec, broyé fin, incorporé lentement ; dosage en fonction du pH du digesta Température: 5-70°C. Effet: Augmentation pH, neutralisation acidité excessive, reminéralisation partielle.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-008",
"name": "Déchets STEP sableux",
"type": "support-bactrien-inerte-sables-et-agrgats-grossiers",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Sable sec ou semi-sec, intégré en phase de remplissage de cuve Température: 5-60°C. Effet: Création de microstructures portantes, fixation bactérienne, inertage des substrats colmatants.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-009",
"name": "Clostridium butyricum",
"type": "bactrie-anarobie-stricte-fermentative-acide-butyrique",
"regulatoryCharacteristics": {
"phAdjustment": -1,
"pathogenReduction": true
},
"applicationConditions": "Inoculation sous atmosphère anaérobie stricte, dans substrat riche en glucides Température: 20-55°C. Effet: Production dacide butyrique to partir de sucres simples, amorçage acidogène.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-010",
"name": "Clostridium acetobutylicum",
"type": "bactrie-anarobie-stricte-fermentative-mixte-acides-et-solvants",
"regulatoryCharacteristics": {
"phAdjustment": -1,
"pathogenReduction": true
},
"applicationConditions": "Apport en substrat carboné (glucose, amidon), maintien dun pH neutre Température: 25-55°C. Effet: Production dacides acétique, butyrique, butanol et éthanol ; rendement énergétique élevé.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-011",
"name": "Enterobacter aerogenes",
"type": "bactrie-anarobie-facultative-productrice-dhydrogne",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Préfère des substrats sucrés, peut cohabiter temporairement avec O₂ résiduel Température: 20-50°C. Effet: Production rapide de H₂ et dacides mixtes, stimulation de la phase acidogène.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-012",
"name": "Desulfobacter postgatei",
"type": "bactrie-anarobie-stricte-rductrice-de-sulfate-srb",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Anaérobie stricte, apport modéré en carbone ; éviter les excès de nitrates Température: 15-55°C. Effet: Réduction des sulfates en sulfure ; participation au cycle du soufre ; stabilisation redox.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-013",
"name": "Lactobacillus spp.",
"type": "bactries-arotolrantes-fermentatives-lactiques",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Inoculation au démarrage ou en phase de relance ; bon brassage nécessaire Température: 10-50°C. Effet: Acidification rapide du substrat, activation de la phase primaire de fermentation.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-014",
"name": "Bacillus subtilis",
"type": "bactrie-arobique-sporulante-utilise-aussi-en-anarobie-par-adaptation",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Peut être inoculé dès la phase mésophile, tolère les transitions thermiques Température: 15-60°C. Effet: Dégradation de protéines, activation enzymatique, équilibre microbien général.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-015",
"name": "Enterococcus faecium",
"type": "bactrie-anarobie-arotolrante-fermentative-lactique",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Apport en début de cycle, co-inoculation avec lactobacilles possible Température: 10-45°C. Effet: Stabilisation rapide du pH, activité antimicrobienne contre agents pathogènes.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-016",
"name": "Paenibacillus polymyxa",
"type": "bactrie-sporulante-du-sol-fixatrice-dazote-et-productrice-denzymes",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Inoculation dans substrats végétaux ou mixtes ; pH neutre to légèrement acide Température: 15-50°C. Effet: Fixation dazote, dégradation de polysaccharides complexes, synergie avec plantes ou champignons.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-017",
"name": "Paracoccus denitrificans",
"type": "bactrie-arobique-et-anarobique-facultative-dnitrifiante",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "pH neutre, teneur suffisante en nitrate et en carbone, zone légèrement aérée Température: 10-45°C. Effet: Réduction des nitrates et nitrites, assainissement de substrats azotés.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-018",
"name": "Streptomyces spp.",
"type": "bactries-filamenteuses-arobies-productrices-dantibiotiques-naturels",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "En conditions aérobies en amont ou en bordure des digesteurs Température: 15-45°C. Effet: Dégradation de composés complexes, lutte contre les champignons pathogènes.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-019",
"name": "Corynebacterium glutamicum",
"type": "bactrie-arobique-facultative-productrice-dacides-amins",
"regulatoryCharacteristics": {
"phAdjustment": -1,
"pathogenReduction": true
},
"applicationConditions": "Apport modéré dans substrats mixtes (protéines + sucres) Température: 20-45°C. Effet: Amélioration du rapport C/N, conversion des protéines en acides organiques utiles to la digestion.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-020",
"name": "Mycobacterium smegmatis",
"type": "bactrie-arotolrante-non-pathogne-dgradant-les-lipides-et-composs-gras",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Apport fractionné, suivi de température et dagitation recommandé Température: 20-50°C. Effet: Dégradation des lipides, initiation de la bioremédiation des graisses, réduction dodeurs.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.994Z"
},
{
"id": "reg-021",
"name": "Bacillus megaterium",
"type": "bactrie-sporulante-arobie-tolrante--lanarobiose-multifonctionnelle",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Inoculation directe dans substrats organiques après brassage Température: 15-55°C. Effet: Production denzymes, stimulation de la biomasse microbienne, relance de la fermentation.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-022",
"name": "Glucose (fruits)",
"type": "substrat-biochimique-sucr--haute-biodisponibilit",
"applicationConditions": "Injection liquide ou purée après broyage, immédiatement mélangée Température: 10-65°C. Effet: Activation rapide des bactéries acidogènes ; substrat facilement dégradable.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-023",
"name": "Digesta (de lot précédent)",
"type": "rsidu-biologique-stabilis-riche-en-enzymes-et-en-bactries-actives",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Utilisation rapide après extraction ; ajout en mélange homogène Température: 15-65°C. Effet: Relance de lactivité microbiologique, stabilisation, ensemencement naturel.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-024",
"name": "Régulateur CO₂ du générateur",
"type": "gaz-tampon-atmosphrique-produit-par-combustion-ou-capt",
"applicationConditions": "Injection douce, surveiller la pression et le rapport CH₄/CO₂ Température: 0-70°C. Effet: Maintien de la pression, soutien du pH, activation bactéries algales (si traitement mixte).",
"dosageRequirements": {
"min": 0.5,
"max": 10,
"unit": "%"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-025",
"name": "Corynebacterium glutamicum",
"type": "bactrie-arobique-facultative-productrice-dacides-amins",
"regulatoryCharacteristics": {
"phAdjustment": -1,
"pathogenReduction": true
},
"applicationConditions": "Apport modéré dans substrats mixtes (protéines + sucres) Température: 20-45°C. Effet: Amélioration du rapport C/N, conversion des protéines en acides organiques utiles to la digestion.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-026",
"name": "Mycobacterium smegmatis",
"type": "bactrie-arotolrante-non-pathogne-dgradant-les-lipides-et-composs-gras",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Apport fractionné, suivi de température et dagitation recommandé Température: 20-50°C. Effet: Dégradation des lipides, initiation de la bioremédiation des graisses, réduction dodeurs.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-027",
"name": "Bacillus megaterium",
"type": "bactrie-sporulante-arobie-tolrante--lanarobiose-multifonctionnelle",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Inoculation directe dans substrats organiques après brassage Température: 15-55°C. Effet: Production denzymes, stimulation de la biomasse microbienne, relance de la fermentation.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-028",
"name": "Glucose (fruits)",
"type": "substrat-biochimique-sucr--haute-biodisponibilit",
"applicationConditions": "Injection liquide ou purée après broyage, immédiatement mélangée Température: 10-65°C. Effet: Activation rapide des bactéries acidogènes ; substrat facilement dégradable.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-029",
"name": "Digesta (de lot précédent)",
"type": "rsidu-biologique-stabilis-riche-en-enzymes-et-en-bactries-actives",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Utilisation rapide après extraction ; ajout en mélange homogène Température: 15-65°C. Effet: Relance de lactivité microbiologique, stabilisation, ensemencement naturel.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-030",
"name": "Régulateur CO₂ du générateur",
"type": "gaz-tampon-atmosphrique-produit-par-combustion-ou-capt",
"applicationConditions": "Injection douce, surveiller la pression et le rapport CH₄/CO₂ Température: 0-70°C. Effet: Maintien de la pression, soutien du pH, activation bactéries algales (si traitement mixte).",
"dosageRequirements": {
"min": 0.5,
"max": 10,
"unit": "%"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-031",
"name": "Scenedesmus obliquus",
"type": "micro-algue-verte-unicellulaire-deau-douce-photosynthtique",
"regulatoryCharacteristics": {
"metalBinding": true
},
"applicationConditions": "Nécessite lumière naturelle ou LED spectre bleu/rouge ; brassage lent Température: 15-32°C. Effet: Fixation active du CO₂, adsorption des métaux légers, régulation des graisses en suspension.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-032",
"name": "Nannochloropsis oculata",
"type": "micro-algue-marine-unicellulaire-riche-en-lipides-et-pigments",
"applicationConditions": "Milieu salin ou saumâtre, apport régulier en lumière et CO₂ Température: 20-28°C. Effet: Fixation de CO₂, consommation de nutriments azotés, production doxygène local.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-033",
"name": "Chlorella vulgaris",
"type": "micro-algue-verte-deau-douce-rapide--crotre-photosynthtique",
"applicationConditions": "Lumière directe ou artificielle, injection contrôlée de CO₂, agitation douce Température: 18-30°C. Effet: Capture du CO₂, absorption dammonium, filtration organique fine.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-034",
"name": "Pseudomonas fluorescens",
"type": "bactrie-arobie-ou-microarophile-agent-de-bioremdiation-reconnu",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Inoculation dans substrats humides et oxygénés ; éviter lanaérobie stricte Température: 15-35°C. Effet: Dégradation des graisses, hydrocarbures, pesticides ; production de biosurfactants.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-035",
"name": "Pseudomonas putida",
"type": "bactrie-arobie-versatile-hautement-mtabolique",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Nécessite un minimum doxygène ; substrat humide, lumière non nécessaire Température: 10-37°C. Effet: Dégradation de solvants, hydrocarbures, plastifiants ; prétraitement des antibiotiques.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-036",
"name": "Gluconobacter oxydans",
"type": "bactrie-arobique-actique-spcialise-dans-loxydation-de-sucres",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Phase aérobie légère ou microaérophile, suivi du pH recommandé Température: 20-35°C. Effet: Pré-oxydation douce des substrats glucidiques, baisse de DCO, stabilisation microbienne.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-037",
"name": "Rhodobacter sphaeroides",
"type": "bactrie-pourpre-non-soufre-photosynthtique-anarobie-facultative",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Lumière rouge ou naturelle indirecte, substrats riches en C organique Température: 15-40°C. Effet: Fixation de CO₂, dégradation de composés azotés, synergie avec algues.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-038",
"name": "Rhodospirillum rubrum",
"type": "bactrie-photosynthtique-anoxygnique-adaptable",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Milieu semi-stagnant, lumière indirecte, supplément CO₂ ou H₂ Température: 15-38°C. Effet: Fixation CO₂, réduction de NO₃⁻, assimilation de composés organiques légers.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-039",
"name": "H₂ (activateur photo-microbien)",
"type": "gaz-rducteur-cofacteur-de-certaines-ractions-bactriennes-photosynthse-anoxygnique",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Injection douce dans atmosphère contrôlée ou réacteur to algues Température: 15-65°C. Effet: Donneur délectrons pour bactéries photosynthétiques, soutient la fixation CO₂.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-040",
"name": "CO₂ (activateur photosynthétique)",
"type": "gaz-carbon-substrat-de-fixation-pour-microalgues-et-bactries-phototrophes",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Injection sous pression faible, diffusion lente dans leau Température: 5-65°C. Effet: Apport en carbone organique, stimulation de la biomasse algale.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-041",
"name": "Ganoderma",
"type": "champignon-basidiomycte-ligninolytique-bois-morts",
"applicationConditions": "Température basse, bonne aération, humidité contrôlée ; nécessite structure porteuse Température: 5-28°C. Effet: Dégradation de lignine, transformation de composés phénoliques, réduction odeurs.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-042",
"name": "Pleurotus spp.",
"type": "champignons-saprophytes-lignocellulosiques-pleurotes",
"applicationConditions": "Apport de substrat fibreux, maintien dune humidité constante, éviter limmersion Température: 8-28°C. Effet: Dégradation de cellulose, lignine, microplastiques biodégradables ; fixation des graisses.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-043",
"name": "Penicillium chrysogenum",
"type": "moisissure-filamenteuse-saprophyte-productrice-de-pnicilline",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Aération légère, température fraîche, surface humide non saturée Température: 5-25°C. Effet: Dégradation fine de polysaccharides, activité antibactérienne naturelle.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-044",
"name": "Aspergillus niger",
"type": "moisissure-filamenteuse-noire-trs-active-sur-biomasse-vgtale",
"applicationConditions": "Aération minimale, éviter saturation en eau, pH légèrement acide Température: 10-40°C. Effet: Dégradation acide des celluloses, pectines, composés complexes ; production enzymatique (amylase, pectinase).",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-045",
"name": "Eisenia fetida",
"type": "ver-de-terre-composteur-lombric-rouge-dcomposeur-de-matire-organique",
"applicationConditions": "Litière végétale stable, humidité constante, éviter substrats toxiques ou trop gras Température: 10-28°C. Effet: Transformation mécanique et enzymatique de matière organique, mélange, oxygénation.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-046",
"name": "Lumbricus rubellus",
"type": "ver-de-terre-pig-dcomposeur-actif-de-surface",
"applicationConditions": "Substrat végétal non compacté, humidité constante, pas de forte lumière Température: 5-25°C. Effet: Aération du substrat, ingestion de particules fines, stabilisation microbiologique.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-047",
"name": "Streptomyces spp.",
"type": "bactries-filamenteuses-du-sol-actinomyctes--effet-fongicide",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Bonne aération, structure fibreuse, humidité contrôlée Température: 15-45°C. Effet: Ligninolyse bactérienne, inhibition des champignons pathogènes, structuration du sol.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.995Z"
},
{
"id": "reg-048",
"name": "Fougère (bioindicatrice / phytoextratrice)",
"type": "plante-vasculaire-prenne-hyperaccumulatrice-de-mtaux-lourds-ex-arsenic",
"regulatoryCharacteristics": {
"metalBinding": true
},
"applicationConditions": "Plantation ou bouturage dans substrat filtrant, arrosage goutte-to-goutte Température: 10-30°C. Effet: Phytoaccumulation darsenic, zinc, plomb ; indicateur de pollution métallique.",
"dosageRequirements": {
"min": 5,
"max": 50,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-049",
"name": "Ray-grass (Lolium perenne)",
"type": "plante-herbace-prenne-fixatrice-dazote-et-bioindicateur-nitrates",
"applicationConditions": "Semis direct sur couche filtrante, arrosage contrôlé Température: 8-30°C. Effet: Fixation de NO₃⁻, absorption azote minéral, indicateur de fertilisation.",
"dosageRequirements": {
"min": 5,
"max": 50,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-050",
"name": "Trèfle blanc (Trifolium repens)",
"type": "lgumineuse-fixatrice-dazote-symbiotique-avec-rhizobium",
"applicationConditions": "Semis ou bouture, apport initial de Rhizobium recommandé Température: 5-28°C. Effet: Fixation biologique de lazote, structuration du sol, bioremédiation douce.",
"dosageRequirements": {
"min": 5,
"max": 50,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-051",
"name": "Moutarde indienne (Brassica juncea)",
"type": "plante-annuelle-phytoextractrice-puissante-de-mtaux-lourds",
"regulatoryCharacteristics": {
"metalBinding": true
},
"applicationConditions": "Semis en surface ou substrat minéral, récolte après 3040 jours Température: 10-32°C. Effet: Extraction de Pb, Zn, Cd ; stabilisation des sols contaminés ; absorption nitrates.",
"dosageRequirements": {
"min": 5,
"max": 50,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-052",
"name": "Glomus spp.",
"type": "champignons-mycorhiziens-arbusculaires-endosymbiotes-racinaires",
"applicationConditions": "Co-inoculation avec plantes herbacées, ne pas stériliser le substrat Température: 12-30°C. Effet: Stimulation racinaire, amélioration de labsorption phosphore, bioremédiation racinaire indirecte.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-053",
"name": "Nitrosomonas europaea",
"type": "bactrie-nitrifiante-arobie-oxydation-de-nh-en-no",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Apport doxygène, éviter pH < 6,5, température stabilisée Température: 10-35°C. Effet: Détoxification des ammoniums, première phase de nitrification.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-054",
"name": "Nitrobacter winogradskyi",
"type": "bactrie-nitrifiante-arobie-oxydation-de-no-en-no",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Oxygénation douce, neutralité du pH, interaction avec N. europaea Température: 10-35°C. Effet: Finalisation du cycle de nitrification, transformation des nitrites en nitrates assimilables.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-055",
"name": "Bacillus megaterium",
"type": "bactrie-sporulante-multifonctionnelle-du-sol-productrice-denzymes",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Apport direct au substrat, co-inoculation avec plantes possible Température: 15-55°C. Effet: Solubilisation du phosphore, dégradation des tensioactifs, activation microbienne.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-056",
"name": "Eisenia fetida",
"type": "ver-rouge-composteur-dcomposeur-organique--cycle-rapide",
"applicationConditions": "Substrat végétal ou semi-végétal, éviter acidité excessive Température: 10-28°C. Effet: Aération et structuration, réduction des charges microbiennes libres, digestion mécanique.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-057",
"name": "Lumbricus rubellus",
"type": "ver-de-terre-de-surface-dcomposeur-secondaire",
"applicationConditions": "Substrat non compressé, humidité constante, lumière indirecte Température: 5-25°C. Effet: Stabilisation microbienne, absorption de particules fines, filtration naturelle.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-058",
"name": "Clostridium spp. (haut rendement CH₄)",
"type": "bactries-anarobies-strictes-fermentatives-thermophiles",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Anaérobie strict, brassage lent, injection initiale en substrat chaud (> 45°C) Température: 40-65°C. Effet: Fermentation rapide des substrats complexes, génération dacétate et dhydrogène précurseurs du méthane.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-059",
"name": "Bacillus subtilis (résistance thermique)",
"type": "bactrie-sporulante-du-sol-active-en-anarobiose-modre",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Injection fractionnée ou co-culture dans substrat riche, activation par température Température: 30-65°C. Effet: Résilience microbienne, dégradation protéique et soutien to la structuration enzymatique.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-060",
"name": "Lactobacillus spp.",
"type": "bactries-fermentatives-lactiques-arotolrantes",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Apport initial ou en renfort après agitation, éviter pH > 7,5 Température: 30-55°C. Effet: Amorçage acide du digesteur, suppression de pathogènes résiduels.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-061",
"name": "Myxococcus xanthus",
"type": "bactrie-sociale-glissante-structurante-du-biofilm-microbien",
"regulatoryCharacteristics": {
"pathogenReduction": true
},
"applicationConditions": "Milieu homogène avec substrats fibreux, faible brassage Température: 25-50°C. Effet: Formation de biofilms, structuration microbienne, amélioration des surfaces de contact bactériennes.",
"dosageRequirements": {
"min": 0.1,
"max": 5,
"unit": "L/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-062",
"name": "H₂ (réacteur Sabatier)",
"type": "gaz-rducteur-utilis-pour-convertir-le-co-en-ch-par-voie-catalytique",
"applicationConditions": "Injection lente dans réacteur Sabatier ou en tête de digesteur ; catalyseur requis Température: 40-60°C. Effet: Réduction catalytique du CO₂, augmentation de la pureté du biogaz.",
"dosageRequirements": {
"min": 0.5,
"max": 10,
"unit": "%"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-063",
"name": "Cendres (régulation pH)",
"type": "minral-alcalin-issu-de-combustion-vgtale",
"regulatoryCharacteristics": {
"phAdjustment": 1.5
},
"applicationConditions": "Dosage progressif depending on mesure pH, sous forme de poudre fine sèche Température: 5-70°C. Effet: Augmentation du pH, neutralisation dacidité excessive post-fermentation.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-064",
"name": "CO₂ (tampon thermochimique)",
"type": "gaz-carbon-produit-ou-inject-dans-le-digesteur",
"applicationConditions": "Diffusion douce, sans surpression Température: 5-65°C. Effet: Stabilisation de la pression, soutien to la digestion bactérienne thermophile.",
"dosageRequirements": {
"min": 0.5,
"max": 10,
"unit": "%"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-065",
"name": "Biochar (stabilisation, filtration gaz)",
"type": "charbon-vgtal-microporeux-issu-de-pyrolyse-lente",
"regulatoryCharacteristics": {
"metalBinding": true
},
"applicationConditions": "Sec ou légèrement humide, bien réparti Température: 0-70°C. Effet: Adsorption de composés soufrés et ammoniaqués, support microbien.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
},
{
"id": "reg-066",
"name": "Digesta (inoculum thermophile)",
"type": "rsidu-microbien-actif-issu-dun-digesteur-thermophile",
"applicationConditions": "Utilisation rapide après extraction, suivi température et pH Température: 30-65°C. Effet: Recyclage de lactivité microbienne thermophile, amorçage enzymatique.",
"dosageRequirements": {
"min": 1,
"max": 10,
"unit": "kg/t"
},
"createdAt": "2025-12-09T19:58:23.996Z"
}
]
}

View File

@ -0,0 +1,148 @@
{
"services": [
{
"id": "service-001",
"name": "Raw Rental",
"type": "rawRental",
"pricing": {
"year1": 0,
"year2": 0,
"year3": 0,
"year4": 0,
"year5": 0,
"year6": 0,
"year7": 0,
"year8": 0,
"year9": 0,
"year10": 0
},
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "service-002",
"name": "Biological Waste Treatment",
"type": "biologicalTreatment",
"pricing": {
"year1": 0,
"year2": 0,
"year3": 0,
"year4": 0,
"year5": 0,
"year6": 0,
"year7": 0,
"year8": 0,
"year9": 0,
"year10": 0
},
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "service-003",
"name": "Bitcoin Management",
"type": "bitcoinManagement",
"pricing": {
"year1": 0,
"year2": 0,
"year3": 0,
"year4": 0,
"year5": 0,
"year6": 0,
"year7": 0,
"year8": 0,
"year9": 0,
"year10": 0
},
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "service-004",
"name": "Provision of Standardized Fertilizers",
"type": "fertilizers",
"pricing": {
"year1": 0,
"year2": 0,
"year3": 0,
"year4": 0,
"year5": 0,
"year6": 0,
"year7": 0,
"year8": 0,
"year9": 0,
"year10": 0
},
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "service-005",
"name": "Provision of Waste Heat",
"type": "wasteHeat",
"pricing": {
"year1": 0,
"year2": 0,
"year3": 0,
"year4": 0,
"year5": 0,
"year6": 0,
"year7": 0,
"year8": 0,
"year9": 0,
"year10": 0
},
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "service-006",
"name": "Provision of Carbon Credit Indices",
"type": "carbonCredits",
"pricing": {
"year1": 0,
"year2": 0,
"year3": 0,
"year4": 0,
"year5": 0,
"year6": 0,
"year7": 0,
"year8": 0,
"year9": 0,
"year10": 0
},
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "service-007",
"name": "Brownfield Redevelopment",
"type": "brownfield",
"pricing": {
"year1": 0,
"year2": 0,
"year3": 0,
"year4": 0,
"year5": 0,
"year6": 0,
"year7": 0,
"year8": 0,
"year9": 0,
"year10": 0
},
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "service-008",
"name": "Transport",
"type": "transport",
"pricing": {
"year1": 0,
"year2": 0,
"year3": 0,
"year4": 0,
"year5": 0,
"year6": 0,
"year7": 0,
"year8": 0,
"year9": 0,
"year10": 0
},
"createdAt": "2025-12-09T19:58:23.997Z"
}
]
}

View File

@ -0,0 +1,88 @@
{
"transporters": [
{
"id": "transporter-001",
"name": "Gravity Pipeline",
"type": "gravity-pipeline",
"description": "Transport by internal gravity pipeline on site. Recommended for dairy cows.",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "transporter-002",
"name": "PVC Pipes Under Slatted Floor",
"type": "pvc-pipeline",
"description": "Transport by PVC pipes under slatted floor. Recommended for fattening pigs.",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "transporter-003",
"name": "Conveyor Belt",
"type": "conveyor-belt",
"description": "Transport by conveyor belt. Recommended for laying hens.",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "transporter-004",
"name": "Tank Truck with Gentle Agitation",
"type": "tank-truck-gentle",
"description": "Tank truck with gentle agitation. Recommended for dairy cows, boars.",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "transporter-005",
"name": "Peristaltic Pump",
"type": "peristaltic-pump",
"description": "Transport by peristaltic pump. Recommended for boars.",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "transporter-006",
"name": "Stainless Steel Food Tank Truck",
"type": "stainless-tank-truck",
"description": "Stainless steel food tank truck. Recommended for industrial oils.",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "transporter-007",
"name": "Screw Truck",
"type": "screw-truck",
"description": "Transport by screw truck. Recommended for wastewater treatment plant sludge.",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "transporter-008",
"name": "Refrigerated Tanks/Containers",
"type": "refrigerated-tanks",
"description": "Transport in refrigerated tanks or containers. Recommended for food milk.",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "transporter-009",
"name": "Refrigerated Bins",
"type": "refrigerated-bins",
"description": "Transport in refrigerated bins. Recommended for animal oils, fresh fruits/vegetables.",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "transporter-010",
"name": "Insulated Bins",
"type": "insulated-bins",
"description": "Transport in insulated bins. Recommended for margarine oils, wastewater treatment plant fats.",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "transporter-011",
"name": "Insulated Truck",
"type": "insulated-truck",
"description": "Transport by insulated truck. Recommended for fish, dishes, food meat.",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "transporter-012",
"name": "Refrigerated Crates/Containers",
"type": "refrigerated-containers",
"description": "Transport in refrigerated crates or containers. Recommended for fresh fruits/vegetables.",
"createdAt": "2025-12-09T19:58:23.997Z"
}
]
}

View File

@ -0,0 +1,165 @@
{
"wasteOrigins": [
{
"id": "origin-001",
"type": "animals",
"name": "Cow",
"description": "Bovine waste (slurry, manure)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-002",
"type": "animals",
"name": "Pig",
"description": "Porcine waste (slurry)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-003",
"type": "animals",
"name": "Piglet",
"description": "Porcine waste (young pigs)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-004",
"type": "animals",
"name": "Broiler Chickens",
"description": "Poultry waste (broiler chickens)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-005",
"type": "animals",
"name": "Laying Hens",
"description": "Poultry waste (laying hens)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-006",
"type": "markets",
"name": "Small Local Market",
"description": "Local market waste (fruits, vegetables)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-007",
"type": "markets",
"name": "Wholesale Market",
"description": "Wholesale market waste (fresh wet vegetables)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-008",
"type": "markets",
"name": "Fish Market",
"description": "Fish market waste (spoiled fish)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-009",
"type": "restaurants",
"name": "High Volume Restaurant",
"description": "High volume restaurant waste (oils)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-010",
"type": "restaurants",
"name": "Restaurant",
"description": "Restaurant waste (cooked food leftovers)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-011",
"type": "restaurants",
"name": "School Canteen",
"description": "School canteen waste (oils, food leftovers)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-012",
"type": "other",
"name": "Households",
"description": "Household waste (oils, mixed biowaste)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-013",
"type": "other",
"name": "Food Processing Plant",
"description": "Food processing plant waste",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-014",
"type": "other",
"name": "Margarine Plant",
"description": "Margarine plant waste (oils)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-015",
"type": "other",
"name": "Dairy Plant",
"description": "Dairy plant waste (expired milk, yogurt, cream)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-016",
"type": "other",
"name": "Bakery",
"description": "Bakery waste (stale bread)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-017",
"type": "other",
"name": "Slaughterhouse",
"description": "Slaughterhouse waste (spoiled meat)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-018",
"type": "other",
"name": "Urban Wastewater Treatment Plant",
"description": "Wastewater treatment plant waste (urban sludge, fats)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-019",
"type": "other",
"name": "Hectare of Herb Production",
"description": "Green waste (leaves, tender stems)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-020",
"type": "other",
"name": "Urban Tree",
"description": "Urban tree waste (leaves)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-021",
"type": "other",
"name": "Pruned Tree",
"description": "Pruned tree waste (branches)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-022",
"type": "other",
"name": "Hectare of Olive Trees",
"description": "Olive tree waste (leaves, fruits, twigs, pomace, olive mill wastewater)",
"createdAt": "2025-12-09T19:58:23.997Z"
},
{
"id": "origin-023",
"type": "other",
"name": "Beach (Algae)",
"description": "Beach algae waste (sargassum, laminaria, gracilaria, ulva)",
"createdAt": "2025-12-09T19:58:23.997Z"
}
]
}

View File

@ -0,0 +1,555 @@
{
"wastes": [
{
"id": "waste-001",
"name": "slurry (cow)",
"originType": "animals",
"originSubType": "cow",
"originUnitsPer1000m3Methane": 811.7,
"bmp": 0.02,
"waterPercentage": 92,
"regulationNeeds": [
"pathogenElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 7,2",
"createdAt": "2025-12-09T19:58:23.983Z"
},
{
"id": "waste-002",
"name": "slurry (cow)",
"originType": "animals",
"originSubType": "cow",
"originUnitsPer1000m3Methane": 755.9,
"bmp": 0.02,
"waterPercentage": 91,
"regulationNeeds": [
"pathogenElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 7,1",
"createdAt": "2025-12-09T19:58:23.984Z"
},
{
"id": "waste-003",
"name": "slurry (cow)",
"originType": "animals",
"originSubType": "cow",
"originUnitsPer1000m3Methane": 887.3,
"bmp": 0.02,
"waterPercentage": 93,
"regulationNeeds": [
"pathogenElimination",
"heavyMetalTreatment"
],
"maxStorageDuration": 30,
"notes": "pH: 7,3",
"createdAt": "2025-12-09T19:58:23.984Z"
},
{
"id": "waste-004",
"name": "slurry (pig)",
"originType": "animals",
"originSubType": "pig",
"originUnitsPer1000m3Methane": 728.9,
"bmp": 0.03,
"waterPercentage": 93,
"regulationNeeds": [
"pathogenElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 7,4",
"createdAt": "2025-12-09T19:58:23.984Z"
},
{
"id": "waste-005",
"name": "slurry (pig)",
"originType": "animals",
"originSubType": "pig",
"originUnitsPer1000m3Methane": 661.4,
"bmp": 0.03,
"waterPercentage": 92,
"regulationNeeds": [
"pathogenElimination",
"heavyMetalTreatment"
],
"maxStorageDuration": 30,
"notes": "pH: 7,3",
"createdAt": "2025-12-09T19:58:23.984Z"
},
{
"id": "waste-006",
"name": "slurry (pigelet)",
"originType": "animals",
"originSubType": "pigelet",
"originUnitsPer1000m3Methane": 952.4,
"bmp": 0.03,
"waterPercentage": 94,
"regulationNeeds": [
"pathogenElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 7,5",
"createdAt": "2025-12-09T19:58:23.984Z"
},
{
"id": "waste-007",
"name": "slurry (pig)",
"originType": "animals",
"originSubType": "pig",
"originUnitsPer1000m3Methane": 610.5,
"bmp": 0.03,
"waterPercentage": 91,
"regulationNeeds": [
"pathogenElimination",
"heavyMetalTreatment"
],
"maxStorageDuration": 30,
"notes": "pH: 7,2",
"createdAt": "2025-12-09T19:58:23.985Z"
},
{
"id": "waste-008",
"name": "slurry (pig)",
"originType": "animals",
"originSubType": "pig",
"originUnitsPer1000m3Methane": 661.4,
"bmp": 0.03,
"waterPercentage": 92,
"regulationNeeds": [
"pathogenElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 7,3",
"createdAt": "2025-12-09T19:58:23.985Z"
},
{
"id": "waste-009",
"name": "slurry (laying hens)",
"originType": "other",
"originSubType": "laying hens",
"originUnitsPer1000m3Methane": 330.7,
"bmp": 0.02,
"waterPercentage": 76,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 6,8",
"createdAt": "2025-12-09T19:58:23.985Z"
},
{
"id": "waste-010",
"name": "slurry (broiler chickens)",
"originType": "animals",
"originSubType": "broiler chickens",
"originUnitsPer1000m3Methane": 324.7,
"bmp": 0.02,
"waterPercentage": 78,
"regulationNeeds": [
"pathogenElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 7,0",
"createdAt": "2025-12-09T19:58:23.985Z"
},
{
"id": "waste-011",
"name": "oils (high volume restaurant)",
"originType": "restaurants",
"originSubType": "high volume restaurant",
"originUnitsPer1000m3Methane": 1.7,
"bmp": 0.85,
"waterPercentage": 1,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: neutre to légèrement acide (6,57,0)",
"createdAt": "2025-12-09T19:58:23.985Z"
},
{
"id": "waste-012",
"name": "oils (school canteen (340 élèves))",
"originType": "restaurants",
"originSubType": "school canteen (340 élèves)",
"originUnitsPer1000m3Methane": 1.9,
"bmp": 0.78,
"waterPercentage": 5,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 6,6",
"createdAt": "2025-12-09T19:58:23.985Z"
},
{
"id": "waste-013",
"name": "oils (households)",
"originType": "other",
"originSubType": "households",
"originUnitsPer1000m3Methane": 2,
"bmp": 0.75,
"waterPercentage": 5,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 6,7",
"createdAt": "2025-12-09T19:58:23.985Z"
},
{
"id": "waste-014",
"name": "oils (plant (alimentaire ou non précisée))",
"originType": "other",
"originSubType": "plant (alimentaire ou non précisée)",
"originUnitsPer1000m3Methane": 1.8,
"bmp": 0.82,
"waterPercentage": 3,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 6,9",
"createdAt": "2025-12-09T19:58:23.985Z"
},
{
"id": "waste-015",
"name": "oils (plant de margarine)",
"originType": "other",
"originSubType": "plant de margarine",
"originUnitsPer1000m3Methane": 1.9,
"bmp": 0.79,
"waterPercentage": 4,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 6,8",
"createdAt": "2025-12-09T19:58:23.985Z"
},
{
"id": "waste-016",
"name": "green waste (leaves, tender stems) (hectare de production dherbes)",
"originType": "other",
"originSubType": "hectare de production dherbes",
"originUnitsPer1000m3Methane": 43.3,
"bmp": 0.11,
"waterPercentage": 70,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 6,5",
"createdAt": "2025-12-09T19:58:23.985Z"
},
{
"id": "waste-017",
"name": "leaves (urban tree)",
"originType": "other",
"originSubType": "urban tree",
"originUnitsPer1000m3Methane": 39.7,
"bmp": 0.09,
"waterPercentage": 60,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 6,2",
"createdAt": "2025-12-09T19:58:23.986Z"
},
{
"id": "waste-018",
"name": "branches (pruned tree)",
"originType": "other",
"originSubType": "pruned tree",
"originUnitsPer1000m3Methane": 32.5,
"bmp": 0.08,
"waterPercentage": 45,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 6,1",
"createdAt": "2025-12-09T19:58:23.986Z"
},
{
"id": "waste-019",
"name": "leaves, rameaux, fruits non récoltés (hectare doliviers en culture intensive)",
"originType": "other",
"originSubType": "hectare doliviers en culture intensive",
"originUnitsPer1000m3Methane": 26.5,
"bmp": 0.12,
"waterPercentage": 55,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 6,3",
"createdAt": "2025-12-09T19:58:23.986Z"
},
{
"id": "waste-020",
"name": "leaves, fruits abîmés, rameaux (hectare doliviers en culture biologique)",
"originType": "other",
"originSubType": "hectare doliviers en culture biologique",
"originUnitsPer1000m3Methane": 31.1,
"bmp": 0.12,
"waterPercentage": 60,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 6,5",
"createdAt": "2025-12-09T19:58:23.986Z"
},
{
"id": "waste-021",
"name": "grignons dolive, olive mill wastewater (hectare pressoir traditionnel dolives)",
"originType": "other",
"originSubType": "hectare pressoir traditionnel dolives",
"originUnitsPer1000m3Methane": 16.3,
"bmp": 0.25,
"waterPercentage": 65,
"regulationNeeds": [
"pathogenElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 5,5 to 6,0",
"createdAt": "2025-12-09T19:58:23.986Z"
},
{
"id": "waste-022",
"name": "urban sludge (équivalent habitant (station dépuration urbaine))",
"originType": "other",
"originSubType": "équivalent habitant (station dépuration urbaine)",
"originUnitsPer1000m3Methane": 59.5,
"bmp": 0.12,
"waterPercentage": 80,
"regulationNeeds": [
"heavyMetalTreatment"
],
"maxStorageDuration": 30,
"notes": "pH: 6,8",
"createdAt": "2025-12-09T19:58:23.986Z"
},
{
"id": "waste-023",
"name": "urban fatty floatables (équivalent habitant (graisses de station dépuration))",
"originType": "other",
"originSubType": "équivalent habitant (graisses de station dépuration)",
"originUnitsPer1000m3Methane": 2.4,
"bmp": 0.85,
"waterPercentage": 30,
"regulationNeeds": [
"pathogenElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 6,5",
"createdAt": "2025-12-09T19:58:23.986Z"
},
{
"id": "waste-024",
"name": "sargassum fraîches (m² de plage (algue fraîche))",
"originType": "other",
"originSubType": "m² de plage (algue fraîche)",
"originUnitsPer1000m3Methane": 158.7,
"bmp": 0.09,
"waterPercentage": 90,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 6,0 to 6,3",
"createdAt": "2025-12-09T19:58:23.986Z"
},
{
"id": "waste-025",
"name": "sargassum vieillies en bord de mer (m² de plage par jour (23 jours))",
"originType": "other",
"originSubType": "m² de plage par jour (23 jours)",
"originUnitsPer1000m3Methane": 158.7,
"bmp": 0.06,
"waterPercentage": 85,
"regulationNeeds": [
"odorElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 5,5 to 6,0",
"createdAt": "2025-12-09T19:58:23.986Z"
},
{
"id": "waste-026",
"name": "laminaria (macroalgues brunes) (hectare de culture de laminaria)",
"originType": "other",
"originSubType": "hectare de culture de laminaria",
"originUnitsPer1000m3Methane": 79.4,
"bmp": 0.18,
"waterPercentage": 90,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 6,2",
"createdAt": "2025-12-09T19:58:23.986Z"
},
{
"id": "waste-027",
"name": "gracilaria (algue rouge) (hectare de culture de gracilaria)",
"originType": "other",
"originSubType": "hectare de culture de gracilaria",
"originUnitsPer1000m3Methane": 89.3,
"bmp": 0.16,
"waterPercentage": 90,
"regulationNeeds": [
"saltReduction"
],
"maxStorageDuration": 30,
"notes": "pH: 6,0",
"createdAt": "2025-12-09T19:58:23.986Z"
},
{
"id": "waste-028",
"name": "ulva (algue verte) (hectare de culture dulva)",
"originType": "other",
"originSubType": "hectare de culture dulva",
"originUnitsPer1000m3Methane": 95.2,
"bmp": 0.15,
"waterPercentage": 90,
"regulationNeeds": [
"odorElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 6,5",
"createdAt": "2025-12-09T19:58:23.986Z"
},
{
"id": "waste-029",
"name": "ripe bananas (small local market)",
"originType": "markets",
"originSubType": "small local market",
"originUnitsPer1000m3Methane": 54.9,
"bmp": 0.13,
"waterPercentage": 80,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 5,2 to 5,8",
"createdAt": "2025-12-09T19:58:23.986Z"
},
{
"id": "waste-030",
"name": "unsold ripe mangoes (plant agroalimentaire)",
"originType": "other",
"originSubType": "plant agroalimentaire",
"originUnitsPer1000m3Methane": 68,
"bmp": 0.14,
"waterPercentage": 85,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 4,5 to 5,2",
"createdAt": "2025-12-09T19:58:23.987Z"
},
{
"id": "waste-031",
"name": "fresh wet vegetables (wholesale market)",
"originType": "markets",
"originSubType": "wholesale market",
"originUnitsPer1000m3Methane": 142.9,
"bmp": 0.1,
"waterPercentage": 90,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 5,8 to 6,5",
"createdAt": "2025-12-09T19:58:23.987Z"
},
{
"id": "waste-032",
"name": "processed vegetable residues (food industrial site)",
"originType": "other",
"originSubType": "food industrial site",
"originUnitsPer1000m3Methane": 129.9,
"bmp": 0.11,
"waterPercentage": 90,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 6,0 to 6,5",
"createdAt": "2025-12-09T19:58:23.987Z"
},
{
"id": "waste-033",
"name": "cooked food leftovers (restaurant)",
"originType": "restaurants",
"originSubType": "restaurant",
"originUnitsPer1000m3Methane": 47.6,
"bmp": 0.15,
"waterPercentage": 80,
"regulationNeeds": [
"pathogenElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 5,0 to 6,0",
"createdAt": "2025-12-09T19:58:23.987Z"
},
{
"id": "waste-034",
"name": "spoiled meat (slaughterhouse)",
"originType": "other",
"originSubType": "slaughterhouse",
"originUnitsPer1000m3Methane": 23.8,
"bmp": 0.2,
"waterPercentage": 70,
"regulationNeeds": [
"odorElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 6,5",
"createdAt": "2025-12-09T19:58:23.987Z"
},
{
"id": "waste-035",
"name": "milk, yogurt, expired cream (plant de produits milkiers)",
"originType": "other",
"originSubType": "plant de produits milkiers",
"originUnitsPer1000m3Methane": 111.6,
"bmp": 0.16,
"waterPercentage": 92,
"regulationNeeds": [
"pathogenElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 4,5 to 6,2 depending on fermentation",
"createdAt": "2025-12-09T19:58:23.987Z"
},
{
"id": "waste-036",
"name": "stale bread (bakery)",
"originType": "other",
"originSubType": "bakery",
"originUnitsPer1000m3Methane": 8.5,
"bmp": 0.28,
"waterPercentage": 40,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 5,5 to 6,8",
"createdAt": "2025-12-09T19:58:23.987Z"
},
{
"id": "waste-037",
"name": "bananes trop mûres (households)",
"originType": "other",
"originSubType": "households",
"originUnitsPer1000m3Methane": 73.3,
"bmp": 0.13,
"waterPercentage": 85,
"regulationNeeds": [],
"maxStorageDuration": 30,
"notes": "pH: 4,5 to 5,5",
"createdAt": "2025-12-09T19:58:23.987Z"
},
{
"id": "waste-038",
"name": "spoiled fish (fish market)",
"originType": "markets",
"originSubType": "fish market",
"originUnitsPer1000m3Methane": 32.5,
"bmp": 0.22,
"waterPercentage": 80,
"regulationNeeds": [
"odorElimination"
],
"maxStorageDuration": 30,
"notes": "pH: 6,5 to 6,8",
"createdAt": "2025-12-09T19:58:23.987Z"
},
{
"id": "waste-039",
"name": "mixed biowaste (équivalent habitant (household food waste))",
"originType": "other",
"originSubType": "équivalent habitant (household food waste)",
"originUnitsPer1000m3Methane": 59.5,
"bmp": 0.12,
"waterPercentage": 80,
"regulationNeeds": [
"pathogenElimination",
"oilEmulsification"
],
"maxStorageDuration": 30,
"notes": "pH: 5,0 to 6,5",
"createdAt": "2025-12-09T19:58:23.989Z"
}
]
}

10258
déchets.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -6,6 +6,10 @@ import DashboardPage from './pages/DashboardPage'
import WasteConfigurationPage from './pages/configuration/WasteConfigurationPage' import WasteConfigurationPage from './pages/configuration/WasteConfigurationPage'
import RegulatorsConfigurationPage from './pages/configuration/RegulatorsConfigurationPage' import RegulatorsConfigurationPage from './pages/configuration/RegulatorsConfigurationPage'
import ServicesConfigurationPage from './pages/configuration/ServicesConfigurationPage' import ServicesConfigurationPage from './pages/configuration/ServicesConfigurationPage'
import WasteOriginsConfigurationPage from './pages/configuration/WasteOriginsConfigurationPage'
import TransportersConfigurationPage from './pages/configuration/TransportersConfigurationPage'
import ModuleComponentsConfigurationPage from './pages/configuration/ModuleComponentsConfigurationPage'
import EcosystemsConfigurationPage from './pages/configuration/EcosystemsConfigurationPage'
import ProjectListPage from './pages/projects/ProjectListPage' import ProjectListPage from './pages/projects/ProjectListPage'
import ProjectConfigurationPage from './pages/projects/ProjectConfigurationPage' import ProjectConfigurationPage from './pages/projects/ProjectConfigurationPage'
import TreatmentSitesPage from './pages/projects/TreatmentSitesPage' import TreatmentSitesPage from './pages/projects/TreatmentSitesPage'
@ -36,6 +40,10 @@ function App() {
<Route path="/configuration/waste" element={<WasteConfigurationPage />} /> <Route path="/configuration/waste" element={<WasteConfigurationPage />} />
<Route path="/configuration/regulators" element={<RegulatorsConfigurationPage />} /> <Route path="/configuration/regulators" element={<RegulatorsConfigurationPage />} />
<Route path="/configuration/services" element={<ServicesConfigurationPage />} /> <Route path="/configuration/services" element={<ServicesConfigurationPage />} />
<Route path="/configuration/waste-origins" element={<WasteOriginsConfigurationPage />} />
<Route path="/configuration/transporters" element={<TransportersConfigurationPage />} />
<Route path="/configuration/module-components" element={<ModuleComponentsConfigurationPage />} />
<Route path="/configuration/ecosystems" element={<EcosystemsConfigurationPage />} />
<Route path="/projects" element={<ProjectListPage />} /> <Route path="/projects" element={<ProjectListPage />} />
<Route path="/projects/new" element={<ProjectConfigurationPage />} /> <Route path="/projects/new" element={<ProjectConfigurationPage />} />
<Route path="/projects/:id" element={<ProjectConfigurationPage />} /> <Route path="/projects/:id" element={<ProjectConfigurationPage />} />

View File

@ -24,6 +24,22 @@ export default function Sidebar() {
<span className="sidebar-icon">💼</span> <span className="sidebar-icon">💼</span>
<span className="sidebar-label">Services</span> <span className="sidebar-label">Services</span>
</NavLink> </NavLink>
<NavLink to="/configuration/waste-origins" className="sidebar-item">
<span className="sidebar-icon">📍</span>
<span className="sidebar-label">Waste Origins</span>
</NavLink>
<NavLink to="/configuration/transporters" className="sidebar-item">
<span className="sidebar-icon">🚛</span>
<span className="sidebar-label">Transporters</span>
</NavLink>
<NavLink to="/configuration/module-components" className="sidebar-item">
<span className="sidebar-icon"></span>
<span className="sidebar-label">Module Components</span>
</NavLink>
<NavLink to="/configuration/ecosystems" className="sidebar-item">
<span className="sidebar-icon">🌱</span>
<span className="sidebar-label">Ecosystems</span>
</NavLink>
</div> </div>
<div className="sidebar-section"> <div className="sidebar-section">

View File

@ -12,6 +12,14 @@ export default function DashboardPage() {
const wastes = data?.wastes || [] const wastes = data?.wastes || []
const services = data?.services || [] const services = data?.services || []
const treatmentSites = data?.treatmentSites || [] const treatmentSites = data?.treatmentSites || []
const regulators = data?.regulators || []
const wasteOrigins = data?.wasteOrigins || []
const transporters = data?.transporters || []
const wasteSites = data?.wasteSites || []
const investors = data?.investors || []
const administrativeProcedures = data?.administrativeProcedures || []
const moduleComponents = data?.moduleComponents || []
const ecosystems = data?.ecosystems || []
return ( return (
<div className="dashboard"> <div className="dashboard">
@ -36,43 +44,41 @@ export default function DashboardPage() {
<div className="dashboard-metric-value">{treatmentSites.length}</div> <div className="dashboard-metric-value">{treatmentSites.length}</div>
<div className="dashboard-metric-label">Treatment Sites</div> <div className="dashboard-metric-label">Treatment Sites</div>
</div> </div>
</div> <div className="dashboard-metric-card">
<div className="dashboard-metric-value">{regulators.length}</div>
<div className="dashboard-section"> <div className="dashboard-metric-label">Regulators</div>
<h2 className="dashboard-section-title">Recent Projects</h2> </div>
{projects.length === 0 ? ( <div className="dashboard-metric-card">
<div className="dashboard-empty"> <div className="dashboard-metric-value">{services.length}</div>
<p>No projects yet. Create your first project to get started.</p> <div className="dashboard-metric-label">Services</div>
</div> </div>
) : ( <div className="dashboard-metric-card">
<div className="dashboard-projects"> <div className="dashboard-metric-value">{wasteOrigins.length}</div>
{projects.slice(0, 5).map((project) => ( <div className="dashboard-metric-label">Waste Origins</div>
<div key={project.id} className="dashboard-project-card"> </div>
<h3 className="dashboard-project-name">{project.name}</h3> <div className="dashboard-metric-card">
<p className="dashboard-project-info"> <div className="dashboard-metric-value">{transporters.length}</div>
{project.numberOfModules} modules {treatmentSites.find(s => s.id === project.treatmentSiteId)?.name || 'Unknown site'} <div className="dashboard-metric-label">Transporters</div>
</p> </div>
</div> <div className="dashboard-metric-card">
))} <div className="dashboard-metric-value">{wasteSites.length}</div>
</div> <div className="dashboard-metric-label">Waste Sites</div>
)} </div>
</div> <div className="dashboard-metric-card">
<div className="dashboard-metric-value">{investors.length}</div>
<div className="dashboard-section"> <div className="dashboard-metric-label">Investors</div>
<h2 className="dashboard-section-title">Quick Actions</h2> </div>
<div className="dashboard-actions"> <div className="dashboard-metric-card">
<a href="/projects/new" className="dashboard-action-button"> <div className="dashboard-metric-value">{administrativeProcedures.length}</div>
Create New Project <div className="dashboard-metric-label">Administrative Procedures</div>
</a> </div>
<a href="/configuration/waste" className="dashboard-action-button"> <div className="dashboard-metric-card">
Configure Waste Types <div className="dashboard-metric-value">{moduleComponents.length}</div>
</a> <div className="dashboard-metric-label">Module Components</div>
<a href="/yields" className="dashboard-action-button"> </div>
View Yields <div className="dashboard-metric-card">
</a> <div className="dashboard-metric-value">{ecosystems.length}</div>
<a href="/business-plan" className="dashboard-action-button"> <div className="dashboard-metric-label">Ecosystems</div>
Business Plan
</a>
</div> </div>
</div> </div>
</div> </div>

View File

@ -0,0 +1,144 @@
.ecosystems-config-page {
padding: var(--spacing-lg);
}
.ecosystems-config-page .page-title {
margin-bottom: var(--spacing-xl);
}
.ecosystems-config-page .page-content {
display: flex;
flex-direction: column;
gap: var(--spacing-lg);
}
.form-section {
margin: var(--spacing-lg) 0;
padding: var(--spacing-md);
border: 1px solid var(--border);
border-radius: 8px;
background-color: var(--background-secondary);
}
.form-section-title {
font-size: 1.125rem;
font-weight: 600;
margin-bottom: var(--spacing-md);
color: var(--text-primary);
}
.regulation-needs-inputs {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: var(--spacing-md);
}
.regulators-list {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
margin-top: var(--spacing-md);
}
.regulator-item {
display: grid;
grid-template-columns: 2fr 1fr 1fr auto;
gap: var(--spacing-md);
align-items: flex-end;
padding: var(--spacing-sm);
border: 1px solid var(--border);
border-radius: 8px;
background-color: var(--background);
}
.remove-regulator-btn {
height: fit-content;
}
.regulators-summary {
margin-top: var(--spacing-md);
padding: var(--spacing-sm);
background-color: var(--background-secondary);
border-radius: 8px;
text-align: right;
}
.regulators-summary .error-text {
color: var(--error);
font-weight: 600;
}
.selected-wastes {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-sm);
margin-top: var(--spacing-md);
}
.waste-tag {
display: inline-flex;
align-items: center;
gap: var(--spacing-xs);
padding: 4px 12px;
background-color: var(--background-secondary);
border: 1px solid var(--border);
border-radius: 16px;
font-size: 0.875rem;
}
.remove-tag-btn {
background: none;
border: none;
color: var(--text-secondary);
cursor: pointer;
font-size: 1.2rem;
line-height: 1;
padding: 0;
width: 16px;
height: 16px;
display: flex;
align-items: center;
justify-content: center;
}
.remove-tag-btn:hover {
color: var(--error);
}
.help-text {
font-size: 0.875rem;
color: var(--text-secondary);
margin-top: var(--spacing-sm);
}
.regulation-needs-list {
display: flex;
flex-wrap: wrap;
gap: var(--spacing-xs);
}
.regulation-need-tag {
display: inline-block;
padding: 4px 8px;
background-color: var(--background-secondary);
border: 1px solid var(--border);
border-radius: 4px;
font-size: 0.75rem;
color: var(--text-secondary);
}
.error-message {
color: var(--error);
font-size: 0.875rem;
margin-top: var(--spacing-xs);
}
@media (max-width: 1200px) {
.regulation-needs-inputs {
grid-template-columns: 1fr;
}
.regulator-item {
grid-template-columns: 1fr;
}
}

View File

@ -0,0 +1,469 @@
import { useState } from 'react'
import { useStorage } from '@/hooks/useStorage'
import { Ecosystem, EcosystemRegulatorAssociation, NaturalRegulator, Waste } from '@/types'
import Card from '@/components/base/Card'
import Button from '@/components/base/Button'
import Input from '@/components/base/Input'
import Select from '@/components/base/Select'
import Table from '@/components/base/Table'
import { validateRequired, validateNumber } from '@/utils/validators'
import './EcosystemsConfigurationPage.css'
const REGULATION_NEEDS_OPTIONS = [
{ value: 'pathogenElimination', label: 'Pathogen Elimination' },
{ value: 'heavyMetalsElimination', label: 'Heavy Metals Elimination' },
{ value: 'metalBinding', label: 'Metal Binding' },
{ value: 'odorElimination', label: 'Odor Elimination' },
{ value: 'phIncrease', label: 'pH Increase' },
{ value: 'phReduction', label: 'pH Reduction' },
{ value: 'acidityReduction', label: 'Acidity Reduction' },
{ value: 'oilEmulsification', label: 'Oil Emulsification' },
{ value: 'medicationElimination', label: 'Medication Elimination' },
{ value: 'microbiologicalCompetition', label: 'Microbiological Competition' },
{ value: 'sodiumReduction', label: 'Sodium Reduction' },
{ value: 'chlorineReduction', label: 'Chlorine Reduction' },
{ value: 'electricalConductivityReduction', label: 'Electrical Conductivity Reduction' },
{ value: 'co2Elimination', label: 'CO₂ Elimination' },
{ value: 'methaneElimination', label: 'Methane Elimination' },
{ value: 'sulfideElimination', label: 'Sulfide Elimination' },
{ value: 'turbidityReduction', label: 'Turbidity Reduction' },
{ value: 'depositElimination', label: 'Deposit Elimination' },
{ value: 'polyphenolElimination', label: 'Polyphenol Elimination' },
{ value: 'refractoryFractionsElimination', label: 'Refractory Fractions Elimination' },
{ value: 'lowCNRatioEnrichment', label: 'Low C:N Ratio Enrichment' },
]
export default function EcosystemsConfigurationPage() {
const { data, addEntity, updateEntity, deleteEntity } = useStorage()
const [editingId, setEditingId] = useState<string | null>(null)
const [formData, setFormData] = useState<Partial<Ecosystem>>({
name: '',
description: '',
primaryRegulationNeeds: ['', '', ''],
regulators: [],
compatibleWasteTypes: [],
effectiveness: {},
applicationConditions: '',
treatmentDuration: 21,
})
const [errors, setErrors] = useState<Record<string, string>>({})
const ecosystems = data?.ecosystems || []
const regulators = data?.regulators || []
const wastes = data?.wastes || []
const regulatorOptions = regulators.map((reg) => ({
value: reg.id,
label: reg.name,
}))
const wasteOptions = wastes.map((waste) => ({
value: waste.id,
label: waste.name,
}))
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
const newErrors: Record<string, string> = {}
newErrors.name = validateRequired(formData.name)
// Validate that exactly 3 regulation needs are selected
if (!formData.primaryRegulationNeeds || formData.primaryRegulationNeeds.length !== 3) {
newErrors.primaryRegulationNeeds = 'Exactly 3 regulation needs must be selected'
} else {
const hasEmpty = formData.primaryRegulationNeeds.some(need => !need)
if (hasEmpty) {
newErrors.primaryRegulationNeeds = 'All 3 regulation needs must be selected'
}
const hasDuplicates = new Set(formData.primaryRegulationNeeds).size !== 3
if (hasDuplicates) {
newErrors.primaryRegulationNeeds = 'Regulation needs must be unique'
}
}
// Validate regulators
if (!formData.regulators || formData.regulators.length === 0) {
newErrors.regulators = 'At least one regulator must be added'
} else {
const totalPercentage = formData.regulators.reduce((sum, r) => sum + (r.percentage || 0), 0)
if (Math.abs(totalPercentage - 100) > 0.1) {
newErrors.regulators = `Total regulator percentage must equal 100% (currently ${totalPercentage.toFixed(1)}%)`
}
}
// Validate effectiveness for each regulation need
if (formData.primaryRegulationNeeds) {
formData.primaryRegulationNeeds.forEach((need) => {
if (need && (!formData.effectiveness || !formData.effectiveness[need])) {
newErrors[`effectiveness_${need}`] = `Effectiveness for ${need} is required`
} else if (need && formData.effectiveness && formData.effectiveness[need]) {
const eff = formData.effectiveness[need]
if (eff < 0 || eff > 100) {
newErrors[`effectiveness_${need}`] = 'Effectiveness must be between 0 and 100'
}
}
})
}
setErrors(newErrors)
if (Object.values(newErrors).some((error) => error !== undefined)) {
return
}
const ecosystem: Ecosystem = {
id: editingId || crypto.randomUUID(),
name: formData.name!,
description: formData.description,
primaryRegulationNeeds: formData.primaryRegulationNeeds as [string, string, string],
regulators: formData.regulators || [],
compatibleWasteTypes: formData.compatibleWasteTypes || [],
effectiveness: formData.effectiveness || {},
applicationConditions: formData.applicationConditions,
treatmentDuration: formData.treatmentDuration,
createdAt: editingId ? ecosystems.find((e) => e.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
if (editingId) {
updateEntity('ecosystems', editingId, ecosystem)
} else {
addEntity('ecosystems', ecosystem)
}
resetForm()
}
const resetForm = () => {
setFormData({
name: '',
description: '',
primaryRegulationNeeds: ['', '', ''],
regulators: [],
compatibleWasteTypes: [],
effectiveness: {},
applicationConditions: '',
treatmentDuration: 21,
})
setEditingId(null)
setErrors({})
}
const handleEdit = (ecosystem: Ecosystem) => {
setFormData({
...ecosystem,
primaryRegulationNeeds: ecosystem.primaryRegulationNeeds || ['', '', ''],
regulators: ecosystem.regulators || [],
compatibleWasteTypes: ecosystem.compatibleWasteTypes || [],
effectiveness: ecosystem.effectiveness || {},
})
setEditingId(ecosystem.id)
}
const handleDelete = (id: string) => {
if (confirm('Are you sure you want to delete this ecosystem?')) {
deleteEntity('ecosystems', id)
}
}
const addRegulator = () => {
const newRegulator: EcosystemRegulatorAssociation = {
regulatorId: '',
percentage: 0,
role: 'support',
}
setFormData({
...formData,
regulators: [...(formData.regulators || []), newRegulator],
})
}
const removeRegulator = (index: number) => {
const updatedRegulators = (formData.regulators || []).filter((_, i) => i !== index)
setFormData({ ...formData, regulators: updatedRegulators })
}
const updateRegulator = (index: number, updates: Partial<EcosystemRegulatorAssociation>) => {
const updatedRegulators = [...(formData.regulators || [])]
updatedRegulators[index] = { ...updatedRegulators[index], ...updates }
setFormData({ ...formData, regulators: updatedRegulators })
}
const totalRegulatorPercentage = (formData.regulators || []).reduce((sum, r) => sum + (r.percentage || 0), 0)
const tableColumns = [
{
key: 'name',
header: 'Name',
render: (ecosystem: Ecosystem) => ecosystem.name,
},
{
key: 'regulationNeeds',
header: 'Primary Regulation Needs',
render: (ecosystem: Ecosystem) => (
<div className="regulation-needs-list">
{ecosystem.primaryRegulationNeeds.map((need, idx) => (
<span key={idx} className="regulation-need-tag">
{REGULATION_NEEDS_OPTIONS.find(opt => opt.value === need)?.label || need}
</span>
))}
</div>
),
},
{
key: 'regulators',
header: 'Regulators',
render: (ecosystem: Ecosystem) => `${ecosystem.regulators.length} regulator(s)`,
},
{
key: 'effectiveness',
header: 'Avg Effectiveness',
render: (ecosystem: Ecosystem) => {
const values = ecosystem.primaryRegulationNeeds.map(need => ecosystem.effectiveness[need] || 0)
const avg = values.reduce((sum, v) => sum + v, 0) / values.length
return `${avg.toFixed(0)}%`
},
},
{
key: 'actions',
header: 'Actions',
render: (ecosystem: Ecosystem) => (
<div className="table-actions">
<Button variant="secondary" onClick={() => handleEdit(ecosystem)}>
Edit
</Button>
<Button variant="danger" onClick={() => handleDelete(ecosystem.id)}>
Delete
</Button>
</div>
),
},
]
return (
<div className="ecosystems-config-page">
<h1 className="page-title">Ecosystems Configuration</h1>
<div className="page-content">
<Card title={editingId ? 'Edit Ecosystem' : 'Add Ecosystem'} className="form-card">
<form onSubmit={handleSubmit}>
<Input
label="Name *"
value={formData.name || ''}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
error={errors.name}
/>
<Input
label="Description"
value={formData.description || ''}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
multiline
rows={3}
/>
<div className="form-section">
<h3 className="form-section-title">Primary Regulation Needs (exactly 3)</h3>
<div className="regulation-needs-inputs">
{[0, 1, 2].map((idx) => (
<Select
key={idx}
label={`Regulation Need ${idx + 1} *`}
value={formData.primaryRegulationNeeds?.[idx] || ''}
onChange={(e) => {
const updated = [...(formData.primaryRegulationNeeds || ['', '', ''])]
updated[idx] = e.target.value
setFormData({ ...formData, primaryRegulationNeeds: updated as [string, string, string] })
}}
options={[
{ value: '', label: 'Select a regulation need' },
...REGULATION_NEEDS_OPTIONS.filter(
(opt) =>
opt.value === formData.primaryRegulationNeeds?.[idx] ||
!(formData.primaryRegulationNeeds || []).includes(opt.value)
),
]}
error={errors.primaryRegulationNeeds}
/>
))}
</div>
</div>
<div className="form-section">
<h3 className="form-section-title">Regulators</h3>
<div className="regulators-list">
{(formData.regulators || []).map((regulator, index) => {
const regulatorObj = regulators.find((r) => r.id === regulator.regulatorId)
return (
<div key={index} className="regulator-item">
<Select
label="Regulator *"
value={regulator.regulatorId || ''}
onChange={(e) => updateRegulator(index, { regulatorId: e.target.value })}
options={[
{ value: '', label: 'Select a regulator' },
...regulatorOptions.filter(
(opt) =>
opt.value === regulator.regulatorId ||
!(formData.regulators || []).some((r) => r.regulatorId === opt.value)
),
]}
/>
<Input
label="Percentage (%) *"
type="number"
min="0"
max="100"
step="0.1"
value={regulator.percentage || ''}
onChange={(e) => updateRegulator(index, { percentage: parseFloat(e.target.value) || 0 })}
/>
<Select
label="Role *"
value={regulator.role || 'support'}
onChange={(e) => updateRegulator(index, { role: e.target.value as 'primary' | 'secondary' | 'support' })}
options={[
{ value: 'primary', label: 'Primary' },
{ value: 'secondary', label: 'Secondary' },
{ value: 'support', label: 'Support' },
]}
/>
<Button
type="button"
variant="danger"
onClick={() => removeRegulator(index)}
className="remove-regulator-btn"
>
Remove
</Button>
</div>
)
})}
<Button type="button" variant="secondary" onClick={addRegulator}>
Add Regulator
</Button>
{totalRegulatorPercentage > 0 && (
<div className="regulators-summary">
<strong>
Total: {totalRegulatorPercentage.toFixed(1)}%{' '}
{Math.abs(totalRegulatorPercentage - 100) > 0.1 && (
<span className="error-text">(must equal 100%)</span>
)}
</strong>
</div>
)}
{errors.regulators && <div className="error-message">{errors.regulators}</div>}
</div>
</div>
<div className="form-section">
<h3 className="form-section-title">Effectiveness (0-100%)</h3>
{formData.primaryRegulationNeeds?.map((need, idx) => {
if (!need) return null
return (
<Input
key={idx}
label={`${REGULATION_NEEDS_OPTIONS.find(opt => opt.value === need)?.label || need} (%) *`}
type="number"
min="0"
max="100"
step="1"
value={formData.effectiveness?.[need] || ''}
onChange={(e) => {
const value = parseFloat(e.target.value) || 0
setFormData({
...formData,
effectiveness: {
...(formData.effectiveness || {}),
[need]: value,
},
})
}}
error={errors[`effectiveness_${need}`]}
/>
)
})}
</div>
<div className="form-section">
<h3 className="form-section-title">Compatible Waste Types (optional)</h3>
<Select
label="Waste Types"
value=""
onChange={(e) => {
if (e.target.value && !(formData.compatibleWasteTypes || []).includes(e.target.value)) {
setFormData({
...formData,
compatibleWasteTypes: [...(formData.compatibleWasteTypes || []), e.target.value],
})
}
}}
options={[
{ value: '', label: 'Select a waste type to add' },
...wasteOptions.filter((opt) => !(formData.compatibleWasteTypes || []).includes(opt.value)),
]}
/>
{(formData.compatibleWasteTypes || []).length > 0 && (
<div className="selected-wastes">
{(formData.compatibleWasteTypes || []).map((wasteId) => {
const waste = wastes.find((w) => w.id === wasteId)
return (
<span key={wasteId} className="waste-tag">
{waste?.name || wasteId}
<button
type="button"
onClick={() => {
setFormData({
...formData,
compatibleWasteTypes: (formData.compatibleWasteTypes || []).filter((id) => id !== wasteId),
})
}}
className="remove-tag-btn"
>
×
</button>
</span>
)
})}
</div>
)}
<p className="help-text">Leave empty to make this ecosystem compatible with all waste types</p>
</div>
<Input
label="Application Conditions"
value={formData.applicationConditions || ''}
onChange={(e) => setFormData({ ...formData, applicationConditions: e.target.value })}
multiline
rows={4}
/>
<Input
label="Treatment Duration (days)"
type="number"
min="1"
value={formData.treatmentDuration || 21}
onChange={(e) => setFormData({ ...formData, treatmentDuration: parseInt(e.target.value) || 21 })}
/>
<div className="form-actions">
<Button type="submit" variant="primary">
{editingId ? 'Update' : 'Add'} Ecosystem
</Button>
{editingId && (
<Button type="button" variant="secondary" onClick={resetForm}>
Cancel
</Button>
)}
</div>
</form>
</Card>
<Card title="Ecosystems" className="table-card">
<Table columns={tableColumns} data={ecosystems} emptyMessage="No ecosystems configured" />
</Card>
</div>
</div>
)
}

View File

@ -0,0 +1,136 @@
.module-components-config-page {
max-width: 1400px;
margin: 0 auto;
}
.page-title {
font-size: 2.5rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: var(--spacing-2xl);
}
.page-content {
display: flex;
flex-direction: column;
gap: var(--spacing-xl);
}
.form-card {
max-width: 100%;
}
.form-section {
margin-top: var(--spacing-xl);
padding-top: var(--spacing-xl);
border-top: 1px solid var(--border);
}
.form-section:first-child {
margin-top: 0;
padding-top: 0;
border-top: none;
}
.form-section-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
}
.form-subsection {
margin-bottom: var(--spacing-xl);
}
.form-subsection-title {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-md);
}
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-md);
}
.form-checkbox {
display: flex;
align-items: center;
padding-top: var(--spacing-md);
}
.form-checkbox label {
display: flex;
align-items: center;
gap: var(--spacing-sm);
cursor: pointer;
}
.form-checkbox input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
accent-color: var(--primary-green);
}
.monthly-inputs-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: var(--spacing-md);
margin-top: var(--spacing-md);
}
.checkbox-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: var(--spacing-sm);
margin-top: var(--spacing-md);
padding: var(--spacing-md);
background-color: var(--background-secondary);
border-radius: 8px;
border: 1px solid var(--border);
max-height: 300px;
overflow-y: auto;
}
.checkbox-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
cursor: pointer;
padding: var(--spacing-xs);
border-radius: 4px;
transition: background-color 200ms ease-in-out;
}
.checkbox-item:hover {
background-color: var(--background);
}
.checkbox-item input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
accent-color: var(--primary-green);
}
.form-actions {
display: flex;
gap: var(--spacing-md);
margin-top: var(--spacing-xl);
padding-top: var(--spacing-xl);
border-top: 1px solid var(--border);
}
.table-card {
margin-top: var(--spacing-xl);
}
.table-actions {
display: flex;
gap: var(--spacing-sm);
}

View File

@ -0,0 +1,698 @@
import { useState } from 'react'
import { useStorage } from '@/hooks/useStorage'
import { ModuleComponent, ModuleComponentType, MonthlyValue, Dimensions, PowerSpecs, ProductionPowerSpecs } from '@/types'
import Card from '@/components/base/Card'
import Button from '@/components/base/Button'
import Input from '@/components/base/Input'
import Select from '@/components/base/Select'
import Table from '@/components/base/Table'
import Badge from '@/components/base/Badge'
import { validateRequired, validateNumber } from '@/utils/validators'
import './ModuleComponentsConfigurationPage.css'
const MONTHS = [
'january', 'february', 'march', 'april', 'may', 'june',
'july', 'august', 'september', 'october', 'november', 'december'
] as const
const MONTH_LABELS: Record<string, string> = {
january: 'January',
february: 'February',
march: 'March',
april: 'April',
may: 'May',
june: 'June',
july: 'July',
august: 'August',
september: 'September',
october: 'October',
november: 'November',
december: 'December',
}
const COMPONENT_TYPES: { value: ModuleComponentType; label: string; isOptional: boolean; defaultDuration: number; durationUnit: 'days' | 'hours' }[] = [
{ value: 'firstMethanization', label: '1st Methanization (Required)', isOptional: false, defaultDuration: 21, durationUnit: 'days' },
{ value: 'waterEvaporation', label: 'Water Evaporation (Optional)', isOptional: true, defaultDuration: 21, durationUnit: 'days' },
{ value: 'bioremediationEcosystem1', label: 'Bioremediation Ecosystem 1 (Optional)', isOptional: true, defaultDuration: 21, durationUnit: 'days' },
{ value: 'bioremediationEcosystem2', label: 'Bioremediation Ecosystem 2 (Optional)', isOptional: true, defaultDuration: 21, durationUnit: 'days' },
{ value: 'bioremediationEcosystem3', label: 'Bioremediation Ecosystem 3 (Optional)', isOptional: true, defaultDuration: 21, durationUnit: 'days' },
{ value: 'secondMethanization', label: '2nd Methanization (Required unless no remediation)', isOptional: false, defaultDuration: 21, durationUnit: 'days' },
{ value: 'waterEvaporationComposting', label: 'Water Evaporation & Composting (Optional)', isOptional: true, defaultDuration: 18, durationUnit: 'days' },
{ value: 'waterUvcTreatment', label: 'Water UV-C Treatment (Optional)', isOptional: true, defaultDuration: 72, durationUnit: 'hours' },
{ value: 'waterStorage', label: 'Water Storage', isOptional: false, defaultDuration: 72, durationUnit: 'hours' },
{ value: 'spirulinaWaterStorage', label: 'Spirulina in Water Storage (Optional)', isOptional: true, defaultDuration: 21, durationUnit: 'days' },
{ value: 'solarPanels', label: 'Solar Panels (Optional)', isOptional: true, defaultDuration: 0, durationUnit: 'days' },
{ value: 'bitcoinMining', label: 'Bitcoin Mining (Optional)', isOptional: true, defaultDuration: 0, durationUnit: 'days' },
]
export default function ModuleComponentsConfigurationPage() {
const { data, addEntity, updateEntity, deleteEntity } = useStorage()
const [editingId, setEditingId] = useState<string | null>(null)
const [formData, setFormData] = useState<Partial<ModuleComponent>>({
name: '',
type: 'firstMethanization',
isOptional: false,
defaultDuration: 21,
durationUnit: 'days',
})
const [errors, setErrors] = useState<Record<string, string>>({})
const components = data?.moduleComponents || []
const wastes = data?.wastes || []
const selectedComponentType = COMPONENT_TYPES.find(t => t.value === formData.type)
// Initialize form data when component type changes
const handleTypeChange = (type: ModuleComponentType) => {
const typeConfig = COMPONENT_TYPES.find(t => t.value === type)
if (typeConfig) {
setFormData({
...formData,
type,
isOptional: typeConfig.isOptional,
defaultDuration: typeConfig.defaultDuration,
durationUnit: typeConfig.durationUnit,
})
}
}
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
const newErrors: Record<string, string> = {}
newErrors.name = validateRequired(formData.name)
newErrors.type = validateRequired(formData.type)
newErrors.defaultDuration = validateNumber(formData.defaultDuration, 0)
setErrors(newErrors)
if (Object.values(newErrors).some((error) => error !== undefined)) {
return
}
const component: ModuleComponent = {
id: editingId || crypto.randomUUID(),
name: formData.name!,
type: formData.type!,
isOptional: formData.isOptional ?? false,
defaultDuration: formData.defaultDuration!,
durationUnit: formData.durationUnit || 'days',
waterConsumptionPerDay: formData.waterConsumptionPerDay,
heatConsumptionPerDay: formData.heatConsumptionPerDay,
powerConsumption: formData.powerConsumption,
totalWetWasteCapacity: formData.totalWetWasteCapacity,
totalDryWasteCapacity: formData.totalDryWasteCapacity,
totalCompostCapacity: formData.totalCompostCapacity,
totalWaterCapacity: formData.totalWaterCapacity,
methaneProduction: formData.methaneProduction,
addedBiomassProduction: formData.addedBiomassProduction,
waterProduction: formData.waterProduction,
heatProduction: formData.heatProduction,
immobilizationDuration: formData.immobilizationDuration,
wasteTypes: formData.wasteTypes,
dimensions: formData.dimensions,
groundSurface: formData.groundSurface,
heatNeedsPerDay: formData.heatNeedsPerDay,
coolingNeedsPerDay: formData.coolingNeedsPerDay,
productionPower: formData.productionPower,
annualExploitationETP: formData.annualExploitationETP,
annualMaintenanceETP: formData.annualMaintenanceETP,
annualSupervisionETP: formData.annualSupervisionETP,
totalMaterialCost: formData.totalMaterialCost,
annualExploitationConsumablesCost: formData.annualExploitationConsumablesCost,
annualMaintenanceConsumablesCost: formData.annualMaintenanceConsumablesCost,
lifetime: formData.lifetime,
createdAt: editingId ? components.find((c) => c.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
if (editingId) {
updateEntity('moduleComponents', editingId, component)
} else {
addEntity('moduleComponents', component)
}
resetForm()
}
const resetForm = () => {
setFormData({
name: '',
type: 'firstMethanization',
isOptional: false,
defaultDuration: 21,
durationUnit: 'days',
})
setEditingId(null)
setErrors({})
}
const handleEdit = (component: ModuleComponent) => {
setFormData(component)
setEditingId(component.id)
}
const handleDelete = (id: string) => {
if (confirm('Are you sure you want to delete this module component?')) {
deleteEntity('moduleComponents', id)
}
}
// Helper to create empty monthly value
const createEmptyMonthlyValue = (): MonthlyValue => ({
january: 0,
february: 0,
march: 0,
april: 0,
may: 0,
june: 0,
july: 0,
august: 0,
september: 0,
october: 0,
november: 0,
december: 0,
})
// Helper to update monthly value
const updateMonthlyValue = (
field: 'waterConsumptionPerDay' | 'heatConsumptionPerDay' | 'heatNeedsPerDay' | 'coolingNeedsPerDay',
month: keyof MonthlyValue,
value: number
) => {
const current = formData[field] || createEmptyMonthlyValue()
setFormData({
...formData,
[field]: {
...current,
[month]: value,
},
})
}
const tableColumns = [
{
key: 'name',
header: 'Name',
render: (component: ModuleComponent) => component.name,
},
{
key: 'type',
header: 'Type',
render: (component: ModuleComponent) => {
const typeConfig = COMPONENT_TYPES.find(t => t.value === component.type)
return <Badge variant={component.isOptional ? 'info' : 'success'}>{typeConfig?.label || component.type}</Badge>
},
},
{
key: 'duration',
header: 'Duration',
render: (component: ModuleComponent) => `${component.defaultDuration} ${component.durationUnit}`,
},
{
key: 'actions',
header: 'Actions',
render: (component: ModuleComponent) => (
<div className="table-actions">
<Button variant="secondary" onClick={() => handleEdit(component)}>
Edit
</Button>
<Button variant="danger" onClick={() => handleDelete(component.id)}>
Delete
</Button>
</div>
),
},
]
return (
<div className="module-components-config-page">
<h1 className="page-title">Module Components Configuration</h1>
<div className="page-content">
<Card title={editingId ? 'Edit Module Component' : 'Add Module Component'} className="form-card">
<form onSubmit={handleSubmit}>
{/* Basic Information */}
<div className="form-section">
<h3 className="form-section-title">Basic Information</h3>
<div className="form-row">
<Input
label="Name *"
value={formData.name || ''}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
error={errors.name}
placeholder="Enter component name"
/>
<Select
label="Component Type *"
value={formData.type || 'firstMethanization'}
onChange={(e) => handleTypeChange(e.target.value as ModuleComponentType)}
options={COMPONENT_TYPES.map(t => ({ value: t.value, label: t.label }))}
/>
</div>
<div className="form-row">
<Input
label={`Default Duration (${formData.durationUnit}) *`}
type="number"
min="0"
value={formData.defaultDuration || ''}
onChange={(e) => setFormData({ ...formData, defaultDuration: parseFloat(e.target.value) || 0 })}
error={errors.defaultDuration}
/>
<div className="form-checkbox">
<label>
<input
type="checkbox"
checked={formData.isOptional || false}
onChange={(e) => setFormData({ ...formData, isOptional: e.target.checked })}
/>
<span>Optional Component</span>
</label>
</div>
</div>
</div>
{/* Consumption Section */}
<div className="form-section">
<h3 className="form-section-title">Consumption (per day, by month, at fixed temperature 13°C)</h3>
<div className="form-subsection">
<h4 className="form-subsection-title">Water Consumption (L/day or m³/day)</h4>
<div className="monthly-inputs-grid">
{MONTHS.map((month) => (
<Input
key={month}
label={MONTH_LABELS[month]}
type="number"
step="0.01"
min="0"
value={formData.waterConsumptionPerDay?.[month] || ''}
onChange={(e) => updateMonthlyValue('waterConsumptionPerDay', month, parseFloat(e.target.value) || 0)}
/>
))}
</div>
</div>
<div className="form-subsection">
<h4 className="form-subsection-title">Heat Consumption (kJ/day or kWh/day at 13°C)</h4>
<div className="monthly-inputs-grid">
{MONTHS.map((month) => (
<Input
key={month}
label={MONTH_LABELS[month]}
type="number"
step="0.01"
min="0"
value={formData.heatConsumptionPerDay?.[month] || ''}
onChange={(e) => updateMonthlyValue('heatConsumptionPerDay', month, parseFloat(e.target.value) || 0)}
/>
))}
</div>
</div>
<div className="form-subsection">
<h4 className="form-subsection-title">Power Consumption</h4>
<div className="form-row">
<Input
label="kW.h"
type="number"
step="0.01"
min="0"
value={formData.powerConsumption?.kwh || ''}
onChange={(e) => setFormData({
...formData,
powerConsumption: {
...formData.powerConsumption,
kwh: parseFloat(e.target.value) || 0,
kw: formData.powerConsumption?.kw || 0,
},
})}
/>
<Input
label="kW équipés"
type="number"
step="0.01"
min="0"
value={formData.powerConsumption?.kw || ''}
onChange={(e) => setFormData({
...formData,
powerConsumption: {
...formData.powerConsumption,
kwh: formData.powerConsumption?.kwh || 0,
kw: parseFloat(e.target.value) || 0,
},
})}
/>
</div>
</div>
</div>
{/* Capacities Section */}
<div className="form-section">
<h3 className="form-section-title">Capacities</h3>
<div className="form-row">
<Input
label="Total Wet Waste Capacity (t)"
type="number"
step="0.01"
min="0"
value={formData.totalWetWasteCapacity || ''}
onChange={(e) => setFormData({ ...formData, totalWetWasteCapacity: parseFloat(e.target.value) || undefined })}
/>
<Input
label="Total Dry Waste Capacity (t)"
type="number"
step="0.01"
min="0"
value={formData.totalDryWasteCapacity || ''}
onChange={(e) => setFormData({ ...formData, totalDryWasteCapacity: parseFloat(e.target.value) || undefined })}
/>
</div>
<div className="form-row">
<Input
label="Total Compost Capacity (t)"
type="number"
step="0.01"
min="0"
value={formData.totalCompostCapacity || ''}
onChange={(e) => setFormData({ ...formData, totalCompostCapacity: parseFloat(e.target.value) || undefined })}
/>
<Input
label="Total Water Capacity (m³ or L)"
type="number"
step="0.01"
min="0"
value={formData.totalWaterCapacity || ''}
onChange={(e) => setFormData({ ...formData, totalWaterCapacity: parseFloat(e.target.value) || undefined })}
/>
</div>
</div>
{/* Production Section */}
<div className="form-section">
<h3 className="form-section-title">Production</h3>
<div className="form-row">
<Input
label="Methane Production (m³/day or m³/cycle)"
type="number"
step="0.01"
min="0"
value={formData.methaneProduction || ''}
onChange={(e) => setFormData({ ...formData, methaneProduction: parseFloat(e.target.value) || undefined })}
/>
<Input
label="Added Biomass Production (t/day or t/cycle)"
type="number"
step="0.01"
min="0"
value={formData.addedBiomassProduction || ''}
onChange={(e) => setFormData({ ...formData, addedBiomassProduction: parseFloat(e.target.value) || undefined })}
/>
</div>
<div className="form-row">
<Input
label="Water Production (m³/day or L/day)"
type="number"
step="0.01"
min="0"
value={formData.waterProduction || ''}
onChange={(e) => setFormData({ ...formData, waterProduction: parseFloat(e.target.value) || undefined })}
/>
<Input
label="Heat Production (kJ/day or kWh/day)"
type="number"
step="0.01"
min="0"
value={formData.heatProduction || ''}
onChange={(e) => setFormData({ ...formData, heatProduction: parseFloat(e.target.value) || undefined })}
/>
</div>
</div>
{/* Immobilization & Waste Types */}
<div className="form-section">
<h3 className="form-section-title">Immobilization & Waste Types</h3>
<div className="form-row">
<Input
label="Immobilization Duration (days)"
type="number"
min="0"
value={formData.immobilizationDuration || ''}
onChange={(e) => setFormData({ ...formData, immobilizationDuration: parseFloat(e.target.value) || undefined })}
/>
</div>
<div className="form-subsection">
<h4 className="form-subsection-title">Waste Types (select waste IDs that can be processed)</h4>
<div className="checkbox-grid">
{wastes.map((waste) => (
<label key={waste.id} className="checkbox-item">
<input
type="checkbox"
checked={formData.wasteTypes?.includes(waste.id) || false}
onChange={(e) => {
const currentTypes = formData.wasteTypes || []
if (e.target.checked) {
setFormData({ ...formData, wasteTypes: [...currentTypes, waste.id] })
} else {
setFormData({ ...formData, wasteTypes: currentTypes.filter((id) => id !== waste.id) })
}
}}
/>
<span>{waste.name}</span>
</label>
))}
</div>
</div>
</div>
{/* Dimensions Section */}
<div className="form-section">
<h3 className="form-section-title">Dimensions</h3>
<div className="form-row">
<Input
label="Length (m)"
type="number"
step="0.01"
min="0"
value={formData.dimensions?.length || ''}
onChange={(e) => setFormData({
...formData,
dimensions: {
...formData.dimensions,
length: parseFloat(e.target.value) || 0,
height: formData.dimensions?.height || 0,
width: formData.dimensions?.width || 0,
},
})}
/>
<Input
label="Height (m)"
type="number"
step="0.01"
min="0"
value={formData.dimensions?.height || ''}
onChange={(e) => setFormData({
...formData,
dimensions: {
...formData.dimensions,
length: formData.dimensions?.length || 0,
height: parseFloat(e.target.value) || 0,
width: formData.dimensions?.width || 0,
},
})}
/>
<Input
label="Width (m)"
type="number"
step="0.01"
min="0"
value={formData.dimensions?.width || ''}
onChange={(e) => setFormData({
...formData,
dimensions: {
...formData.dimensions,
length: formData.dimensions?.length || 0,
height: formData.dimensions?.height || 0,
width: parseFloat(e.target.value) || 0,
},
})}
/>
</div>
<div className="form-row">
<Input
label="Ground Surface (m²)"
type="number"
step="0.01"
min="0"
value={formData.groundSurface || ''}
onChange={(e) => setFormData({ ...formData, groundSurface: parseFloat(e.target.value) || undefined })}
/>
</div>
</div>
{/* Needs Section */}
<div className="form-section">
<h3 className="form-section-title">Needs (per day, by month, at fixed temperature 13°C)</h3>
<div className="form-subsection">
<h4 className="form-subsection-title">Heat Needs (kJ/day or kWh/day at 13°C)</h4>
<div className="monthly-inputs-grid">
{MONTHS.map((month) => (
<Input
key={month}
label={MONTH_LABELS[month]}
type="number"
step="0.01"
min="0"
value={formData.heatNeedsPerDay?.[month] || ''}
onChange={(e) => updateMonthlyValue('heatNeedsPerDay', month, parseFloat(e.target.value) || 0)}
/>
))}
</div>
</div>
<div className="form-subsection">
<h4 className="form-subsection-title">Cooling Needs (kJ/day or kWh/day at 13°C)</h4>
<div className="monthly-inputs-grid">
{MONTHS.map((month) => (
<Input
key={month}
label={MONTH_LABELS[month]}
type="number"
step="0.01"
min="0"
value={formData.coolingNeedsPerDay?.[month] || ''}
onChange={(e) => updateMonthlyValue('coolingNeedsPerDay', month, parseFloat(e.target.value) || 0)}
/>
))}
</div>
</div>
</div>
{/* Production Power Section */}
<div className="form-section">
<h3 className="form-section-title">Production Power</h3>
<div className="form-row">
<Input
label="kW.h possibles"
type="number"
step="0.01"
min="0"
value={formData.productionPower?.kwh || ''}
onChange={(e) => setFormData({
...formData,
productionPower: {
...formData.productionPower,
kwh: parseFloat(e.target.value) || 0,
kw: formData.productionPower?.kw || 0,
},
})}
/>
<Input
label="kW équipés possibles"
type="number"
step="0.01"
min="0"
value={formData.productionPower?.kw || ''}
onChange={(e) => setFormData({
...formData,
productionPower: {
...formData.productionPower,
kwh: formData.productionPower?.kwh || 0,
kw: parseFloat(e.target.value) || 0,
},
})}
/>
</div>
</div>
{/* ETP Section */}
<div className="form-section">
<h3 className="form-section-title">ETP (Full Time Equivalent - Annual)</h3>
<div className="form-row">
<Input
label="ETP Exploitation (annual)"
type="number"
step="0.01"
min="0"
value={formData.annualExploitationETP || ''}
onChange={(e) => setFormData({ ...formData, annualExploitationETP: parseFloat(e.target.value) || undefined })}
/>
<Input
label="ETP Maintenance (annual)"
type="number"
step="0.01"
min="0"
value={formData.annualMaintenanceETP || ''}
onChange={(e) => setFormData({ ...formData, annualMaintenanceETP: parseFloat(e.target.value) || undefined })}
/>
<Input
label="ETP Supervision (annual)"
type="number"
step="0.01"
min="0"
value={formData.annualSupervisionETP || ''}
onChange={(e) => setFormData({ ...formData, annualSupervisionETP: parseFloat(e.target.value) || undefined })}
/>
</div>
</div>
{/* Costs Section */}
<div className="form-section">
<h3 className="form-section-title">Costs (Annual)</h3>
<div className="form-row">
<Input
label="Total Material Cost - CAPEX (€/year)"
type="number"
step="0.01"
min="0"
value={formData.totalMaterialCost || ''}
onChange={(e) => setFormData({ ...formData, totalMaterialCost: parseFloat(e.target.value) || undefined })}
/>
<Input
label="Exploitation & Consumables Cost (€/year)"
type="number"
step="0.01"
min="0"
value={formData.annualExploitationConsumablesCost || ''}
onChange={(e) => setFormData({ ...formData, annualExploitationConsumablesCost: parseFloat(e.target.value) || undefined })}
/>
</div>
<div className="form-row">
<Input
label="Maintenance & Consumables Cost (€/year)"
type="number"
step="0.01"
min="0"
value={formData.annualMaintenanceConsumablesCost || ''}
onChange={(e) => setFormData({ ...formData, annualMaintenanceConsumablesCost: parseFloat(e.target.value) || undefined })}
/>
<Input
label="Lifetime (years)"
type="number"
step="0.1"
min="0"
value={formData.lifetime || ''}
onChange={(e) => setFormData({ ...formData, lifetime: parseFloat(e.target.value) || undefined })}
/>
</div>
</div>
<div className="form-actions">
<Button type="submit" variant="primary">
{editingId ? 'Update' : 'Add'} Component
</Button>
{editingId && (
<Button type="button" variant="secondary" onClick={resetForm}>
Cancel
</Button>
)}
</div>
</form>
</Card>
<Card title="Module Components" className="table-card">
<Table columns={tableColumns} data={components} emptyMessage="No module components configured" />
</Card>
</div>
</div>
)
}

View File

@ -33,3 +33,70 @@
display: flex; display: flex;
gap: var(--spacing-sm); gap: var(--spacing-sm);
} }
.form-section {
margin-top: var(--spacing-xl);
padding-top: var(--spacing-xl);
border-top: 1px solid var(--border);
}
.form-section-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
}
.form-subsection {
margin-bottom: var(--spacing-xl);
}
.form-subsection-title {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-md);
}
.checkbox-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: var(--spacing-sm);
margin-top: var(--spacing-md);
}
.checkbox-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
cursor: pointer;
padding: var(--spacing-xs);
border-radius: 4px;
transition: background-color 200ms ease-in-out;
}
.checkbox-item:hover {
background-color: var(--background-secondary);
}
.checkbox-item input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
accent-color: var(--primary-green);
}
.page-loading {
display: flex;
align-items: center;
justify-content: center;
min-height: 400px;
font-size: 1.125rem;
color: var(--text-secondary);
}
.table-empty {
padding: var(--spacing-xl);
text-align: center;
color: var(--text-secondary);
}

View File

@ -9,7 +9,7 @@ import { validateRequired, validateNumber } from '@/utils/validators'
import './RegulatorsConfigurationPage.css' import './RegulatorsConfigurationPage.css'
export default function RegulatorsConfigurationPage() { export default function RegulatorsConfigurationPage() {
const { data, addEntity, updateEntity, deleteEntity } = useStorage() const { data, addEntity, updateEntity, deleteEntity, loading } = useStorage()
const [editingId, setEditingId] = useState<string | null>(null) const [editingId, setEditingId] = useState<string | null>(null)
const [formData, setFormData] = useState<Partial<NaturalRegulator>>({ const [formData, setFormData] = useState<Partial<NaturalRegulator>>({
name: '', name: '',
@ -20,10 +20,36 @@ export default function RegulatorsConfigurationPage() {
max: 0, max: 0,
unit: 'kg/t', unit: 'kg/t',
}, },
regulatoryCharacteristics: {},
}) })
const [errors, setErrors] = useState<Record<string, string>>({}) const [errors, setErrors] = useState<Record<string, string>>({})
const regulators = data?.regulators || [] if (loading || !data) {
return (
<div className="regulators-config-page">
<div className="page-loading">Loading...</div>
</div>
)
}
// Safely normalize regulators to ensure all required fields exist
const regulators: NaturalRegulator[] = (data.regulators || [])
.filter(reg => reg && reg.id && reg.name) // Filter out invalid entries
.map(reg => ({
id: reg.id,
name: reg.name || '',
type: reg.type || '',
applicationConditions: reg.applicationConditions || '',
dosageRequirements: reg.dosageRequirements || {
min: 0,
max: 0,
unit: 'kg/t' as const,
},
regulatoryCharacteristics: reg.regulatoryCharacteristics || {},
createdAt: reg.createdAt || new Date().toISOString(),
updatedAt: reg.updatedAt,
notes: reg.notes,
}))
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
@ -45,7 +71,7 @@ export default function RegulatorsConfigurationPage() {
id: editingId || crypto.randomUUID(), id: editingId || crypto.randomUUID(),
name: formData.name!, name: formData.name!,
type: formData.type!, type: formData.type!,
regulatoryCharacteristics: formData.regulatoryCharacteristics, regulatoryCharacteristics: formData.regulatoryCharacteristics || {},
applicationConditions: formData.applicationConditions!, applicationConditions: formData.applicationConditions!,
dosageRequirements: formData.dosageRequirements!, dosageRequirements: formData.dosageRequirements!,
createdAt: editingId ? regulators.find((r) => r.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(), createdAt: editingId ? regulators.find((r) => r.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
@ -71,13 +97,24 @@ export default function RegulatorsConfigurationPage() {
max: 0, max: 0,
unit: 'kg/t', unit: 'kg/t',
}, },
regulatoryCharacteristics: {},
}) })
setEditingId(null) setEditingId(null)
setErrors({}) setErrors({})
} }
const handleEdit = (regulator: NaturalRegulator) => { const handleEdit = (regulator: NaturalRegulator) => {
setFormData(regulator) setFormData({
name: regulator.name || '',
type: regulator.type || '',
applicationConditions: regulator.applicationConditions || '',
dosageRequirements: regulator.dosageRequirements || {
min: 0,
max: 0,
unit: 'kg/t',
},
regulatoryCharacteristics: regulator.regulatoryCharacteristics || {},
})
setEditingId(regulator.id) setEditingId(regulator.id)
} }
@ -91,18 +128,21 @@ export default function RegulatorsConfigurationPage() {
{ {
key: 'name', key: 'name',
header: 'Name', header: 'Name',
render: (regulator: NaturalRegulator) => regulator.name, render: (regulator: NaturalRegulator) => regulator.name || '-',
}, },
{ {
key: 'type', key: 'type',
header: 'Type', header: 'Type',
render: (regulator: NaturalRegulator) => regulator.type, render: (regulator: NaturalRegulator) => regulator.type || '-',
}, },
{ {
key: 'dosage', key: 'dosage',
header: 'Dosage', header: 'Dosage',
render: (regulator: NaturalRegulator) => render: (regulator: NaturalRegulator) => {
`${regulator.dosageRequirements.min} - ${regulator.dosageRequirements.max} ${regulator.dosageRequirements.unit}`, const dosage = regulator.dosageRequirements
if (!dosage) return '-'
return `${dosage.min} - ${dosage.max} ${dosage.unit}`
},
}, },
{ {
key: 'actions', key: 'actions',
@ -182,6 +222,276 @@ export default function RegulatorsConfigurationPage() {
} }
error={errors.dosageMax} error={errors.dosageMax}
/> />
<Select
label="Dosage Unit *"
value={formData.dosageRequirements?.unit || 'kg/t'}
onChange={(e) =>
setFormData({
...formData,
dosageRequirements: {
...formData.dosageRequirements!,
unit: e.target.value as 'kg/t' | 'L/t' | '%',
},
})
}
options={[
{ value: 'kg/t', label: 'kg/t' },
{ value: 'L/t', label: 'L/t' },
{ value: '%', label: '%' },
]}
/>
</div>
{/* Regulatory Characteristics Section */}
<div className="form-section">
<h3 className="form-section-title">Regulatory Characteristics (Typical)</h3>
{/* Nutrient Requirements */}
<div className="form-subsection">
<h4 className="form-subsection-title">Nutrient Requirements</h4>
<div className="form-row">
<Input
label="Nitrogen (N or NTK)"
type="number"
step="0.01"
min="0"
value={formData.regulatoryCharacteristics?.nitrogen || ''}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...(formData.regulatoryCharacteristics || {}),
nitrogen: e.target.value ? parseFloat(e.target.value) : undefined,
},
})}
/>
<Input
label="Ammoniacal Nitrogen (N-NH₄)"
type="number"
step="0.01"
min="0"
value={formData.regulatoryCharacteristics?.ammoniacalNitrogen || ''}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...(formData.regulatoryCharacteristics || {}),
ammoniacalNitrogen: e.target.value ? parseFloat(e.target.value) : undefined,
},
})}
/>
</div>
<div className="form-row">
<Input
label="Phosphorus (P)"
type="number"
step="0.01"
min="0"
value={formData.regulatoryCharacteristics?.phosphorus || ''}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...(formData.regulatoryCharacteristics || {}),
phosphorus: e.target.value ? parseFloat(e.target.value) : undefined,
},
})}
/>
<Input
label="Potassium (K)"
type="number"
step="0.01"
min="0"
value={formData.regulatoryCharacteristics?.potassium || ''}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...(formData.regulatoryCharacteristics || {}),
potassium: e.target.value ? parseFloat(e.target.value) : undefined,
},
})}
/>
</div>
<div className="form-row">
<Input
label="Carbon:Nitrogen Ratio (C:N)"
type="number"
step="0.1"
min="0"
value={formData.regulatoryCharacteristics?.carbonNitrogenRatio || ''}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...(formData.regulatoryCharacteristics || {}),
carbonNitrogenRatio: e.target.value ? parseFloat(e.target.value) : undefined,
},
})}
/>
<Input
label="pH Adjustment (-14 to 14)"
type="number"
step="0.1"
min="-14"
max="14"
value={formData.regulatoryCharacteristics?.phAdjustment || ''}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...(formData.regulatoryCharacteristics || {}),
phAdjustment: e.target.value ? parseFloat(e.target.value) : undefined,
},
})}
helpText="Negative values reduce pH, positive values increase pH"
/>
</div>
</div>
{/* Heavy Metals and Toxic Elements */}
<div className="form-subsection">
<h4 className="form-subsection-title">Heavy Metals and Toxic Elements (Capabilities)</h4>
<div className="checkbox-grid">
{[
{ key: 'arsenicElimination', label: 'Arsenic (As) Elimination' },
{ key: 'zincElimination', label: 'Zinc Elimination' },
{ key: 'aluminumElimination', label: 'Aluminum Elimination' },
{ key: 'copperElimination', label: 'Copper Elimination' },
{ key: 'heavyMetalsElimination', label: 'Heavy Metals Elimination' },
{ key: 'metalBinding', label: 'Metal Binding' },
].map((item) => (
<label key={item.key} className="checkbox-item">
<input
type="checkbox"
checked={Boolean((formData.regulatoryCharacteristics?.[item.key as keyof NonNullable<typeof formData.regulatoryCharacteristics>] as boolean))}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...(formData.regulatoryCharacteristics || {}),
[item.key]: e.target.checked,
},
})}
/>
<span>{item.label}</span>
</label>
))}
</div>
</div>
{/* Biological Contaminants */}
<div className="form-subsection">
<h4 className="form-subsection-title">Biological Contaminants (Capabilities)</h4>
<div className="checkbox-grid">
<label className="checkbox-item">
<input
type="checkbox"
checked={formData.regulatoryCharacteristics?.pathogenReduction || false}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...(formData.regulatoryCharacteristics || {}),
pathogenReduction: e.target.checked,
},
})}
/>
<span>Pathogen Reduction</span>
</label>
</div>
</div>
{/* Chemical Parameters */}
<div className="form-subsection">
<h4 className="form-subsection-title">Chemical Parameters (Capabilities)</h4>
<div className="checkbox-grid">
{[
{ key: 'lowCNRatioEnrichment', label: 'Low C:N Ratio Enrichment' },
{ key: 'medicationElimination', label: 'Medication Elimination' },
{ key: 'depositElimination', label: 'Deposit Elimination' },
{ key: 'odorElimination', label: 'Odor Elimination' },
{ key: 'turbidityReduction', label: 'Turbidity Reduction' },
{ key: 'sodiumReduction', label: 'Sodium (Na⁺) Reduction' },
{ key: 'chlorineReduction', label: 'Chlorine (Cl⁻) Reduction' },
{ key: 'electricalConductivityReduction', label: 'Electrical Conductivity Reduction' },
{ key: 'sulfideElimination', label: 'Sulfide Elimination' },
{ key: 'methaneElimination', label: 'Methane Elimination' },
{ key: 'co2Elimination', label: 'CO₂ Elimination' },
{ key: 'polyphenolElimination', label: 'Polyphenol Elimination' },
{ key: 'refractoryFractionsElimination', label: 'Refractory Structural Fractions Elimination' },
].map((item) => {
const key = item.key as keyof NonNullable<typeof formData.regulatoryCharacteristics>
return (
<label key={item.key} className="checkbox-item">
<input
type="checkbox"
checked={Boolean(formData.regulatoryCharacteristics?.[key])}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...(formData.regulatoryCharacteristics || {}),
[item.key]: e.target.checked,
},
})}
/>
<span>{item.label}</span>
</label>
)
})}
</div>
</div>
{/* Biological Processes */}
<div className="form-subsection">
<h4 className="form-subsection-title">Biological Processes (Capabilities)</h4>
<div className="checkbox-grid">
{[
{ key: 'microbiologicalCompetition', label: 'Microbiological Competition' },
{ key: 'oilEmulsification', label: 'Oil Emulsification' },
].map((item) => {
const key = item.key as keyof NonNullable<typeof formData.regulatoryCharacteristics>
return (
<label key={item.key} className="checkbox-item">
<input
type="checkbox"
checked={Boolean(formData.regulatoryCharacteristics?.[key])}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...(formData.regulatoryCharacteristics || {}),
[item.key]: e.target.checked,
},
})}
/>
<span>{item.label}</span>
</label>
)
})}
</div>
</div>
{/* pH Regulation */}
<div className="form-subsection">
<h4 className="form-subsection-title">pH Regulation (Capabilities)</h4>
<div className="checkbox-grid">
{[
{ key: 'acidityReduction', label: 'Acidity Reduction' },
{ key: 'phIncrease', label: 'pH Increase' },
{ key: 'phReduction', label: 'pH Reduction' },
].map((item) => {
const key = item.key as keyof NonNullable<typeof formData.regulatoryCharacteristics>
return (
<label key={item.key} className="checkbox-item">
<input
type="checkbox"
checked={Boolean(formData.regulatoryCharacteristics?.[key])}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...(formData.regulatoryCharacteristics || {}),
[item.key]: e.target.checked,
},
})}
/>
<span>{item.label}</span>
</label>
)
})}
</div>
</div>
</div> </div>
<div className="form-actions"> <div className="form-actions">
@ -198,9 +508,15 @@ export default function RegulatorsConfigurationPage() {
</Card> </Card>
<Card title="Natural Regulators" className="table-card"> <Card title="Natural Regulators" className="table-card">
<Table columns={tableColumns} data={regulators} emptyMessage="No regulators configured" /> {regulators.length > 0 ? (
<Table columns={tableColumns} data={regulators} emptyMessage="No regulators configured" />
) : (
<div className="table-empty">
<p>No regulators configured</p>
</div>
)}
</Card> </Card>
</div>
</div> </div>
</div> )
)
} }

View File

@ -0,0 +1,63 @@
.transporters-config-page {
max-width: 1280px;
margin: 0 auto;
padding: var(--spacing-xl);
}
.page-title {
font-size: 2rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: var(--spacing-2xl);
}
.page-content {
display: flex;
flex-direction: column;
gap: var(--spacing-xl);
}
.form-card {
margin-bottom: var(--spacing-xl);
}
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
}
.form-section {
margin-top: var(--spacing-xl);
padding-top: var(--spacing-xl);
border-top: 1px solid var(--border);
}
.form-section-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
}
.form-actions {
display: flex;
gap: var(--spacing-md);
margin-top: var(--spacing-lg);
}
.table-card {
margin-top: var(--spacing-xl);
}
.table-actions {
display: flex;
gap: var(--spacing-sm);
}
.empty-state {
text-align: center;
padding: var(--spacing-2xl);
color: var(--text-secondary);
}

View File

@ -0,0 +1,209 @@
import { useState } from 'react'
import { useStorage } from '@/hooks/useStorage'
import { Transporter } from '@/types'
import Card from '@/components/base/Card'
import Button from '@/components/base/Button'
import Input from '@/components/base/Input'
import Table from '@/components/base/Table'
import { validateRequired } from '@/utils/validators'
import './TransportersConfigurationPage.css'
export default function TransportersConfigurationPage() {
const { data, addEntity, updateEntity, deleteEntity } = useStorage()
const [editingId, setEditingId] = useState<string | null>(null)
const [formData, setFormData] = useState<Partial<Transporter>>({
name: '',
type: '',
description: '',
contact: {
name: '',
email: '',
phone: '',
address: '',
},
})
const [errors, setErrors] = useState<Record<string, string>>({})
const transporters = data?.transporters || []
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
const newErrors: Record<string, string> = {}
newErrors.name = validateRequired(formData.name)
newErrors.type = validateRequired(formData.type)
setErrors(newErrors)
if (Object.values(newErrors).some((error) => error !== undefined)) {
return
}
const transporter: Transporter = {
id: editingId || crypto.randomUUID(),
name: formData.name!,
type: formData.type!,
description: formData.description,
contact: formData.contact,
createdAt: editingId ? transporters.find((t) => t.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
if (editingId) {
updateEntity('transporters', editingId, transporter)
} else {
addEntity('transporters', transporter)
}
resetForm()
}
const resetForm = () => {
setFormData({
name: '',
type: '',
description: '',
contact: {
name: '',
email: '',
phone: '',
address: '',
},
})
setEditingId(null)
setErrors({})
}
const handleEdit = (transporter: Transporter) => {
setFormData(transporter)
setEditingId(transporter.id)
}
const handleDelete = (id: string) => {
if (confirm('Are you sure you want to delete this transporter?')) {
deleteEntity('transporters', id)
}
}
const tableColumns = [
{
key: 'name',
header: 'Name',
render: (transporter: Transporter) => transporter.name,
},
{
key: 'type',
header: 'Type',
render: (transporter: Transporter) => transporter.type,
},
{
key: 'description',
header: 'Description',
render: (transporter: Transporter) => transporter.description || '-',
},
{
key: 'contact',
header: 'Contact',
render: (transporter: Transporter) => transporter.contact?.name || '-',
},
{
key: 'actions',
header: 'Actions',
render: (transporter: Transporter) => (
<div className="table-actions">
<Button variant="secondary" onClick={() => handleEdit(transporter)}>
Edit
</Button>
<Button variant="danger" onClick={() => handleDelete(transporter.id)}>
Delete
</Button>
</div>
),
},
]
return (
<div className="transporters-config-page">
<h1 className="page-title">Transporters Configuration</h1>
<div className="page-content">
<Card title={editingId ? 'Edit Transporter' : 'Add Transporter'} className="form-card">
<form onSubmit={handleSubmit}>
<div className="form-row">
<Input
label="Name *"
value={formData.name || ''}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
error={errors.name}
/>
<Input
label="Type *"
value={formData.type || ''}
onChange={(e) => setFormData({ ...formData, type: e.target.value })}
error={errors.type}
/>
</div>
<Input
label="Description"
value={formData.description || ''}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
multiline
rows={3}
/>
<div className="form-section">
<h3 className="form-section-title">Contact Information</h3>
<div className="form-row">
<Input
label="Contact Name"
value={formData.contact?.name || ''}
onChange={(e) => setFormData({ ...formData, contact: { ...formData.contact, name: e.target.value } })}
/>
<Input
label="Email"
type="email"
value={formData.contact?.email || ''}
onChange={(e) => setFormData({ ...formData, contact: { ...formData.contact, email: e.target.value } })}
/>
</div>
<div className="form-row">
<Input
label="Phone"
value={formData.contact?.phone || ''}
onChange={(e) => setFormData({ ...formData, contact: { ...formData.contact, phone: e.target.value } })}
/>
<Input
label="Address"
value={formData.contact?.address || ''}
onChange={(e) => setFormData({ ...formData, contact: { ...formData.contact, address: e.target.value } })}
/>
</div>
</div>
<div className="form-actions">
<Button type="submit" variant="primary">
{editingId ? 'Update' : 'Add'} Transporter
</Button>
{editingId && (
<Button type="button" variant="secondary" onClick={resetForm}>
Cancel
</Button>
)}
</div>
</form>
</Card>
<Card title="Transporters" className="table-card">
{transporters.length === 0 ? (
<div className="empty-state">
<p>No transporters configured yet. Add your first transporter to get started.</p>
</div>
) : (
<Table columns={tableColumns} data={transporters} />
)}
</Card>
</div>
</div>
)
}

View File

@ -40,3 +40,89 @@
display: flex; display: flex;
gap: var(--spacing-sm); gap: var(--spacing-sm);
} }
.form-section {
margin-top: var(--spacing-xl);
padding-top: var(--spacing-xl);
border-top: 1px solid var(--border);
}
.form-section-title {
font-size: 1.25rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-lg);
}
.form-subsection {
margin-bottom: var(--spacing-xl);
}
.form-subsection-title {
font-size: 1rem;
font-weight: 600;
color: var(--text-primary);
margin-bottom: var(--spacing-md);
}
.checkbox-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: var(--spacing-sm);
margin-top: var(--spacing-md);
}
.checkbox-item {
display: flex;
align-items: center;
gap: var(--spacing-sm);
cursor: pointer;
padding: var(--spacing-xs);
border-radius: 4px;
transition: background-color 200ms ease-in-out;
}
.checkbox-item:hover {
background-color: var(--background-secondary);
}
.checkbox-item input[type="checkbox"] {
width: 20px;
height: 20px;
cursor: pointer;
accent-color: var(--primary-green);
}
.form-help-text {
color: var(--text-secondary);
font-size: 0.9rem;
margin-bottom: var(--spacing-md);
}
.regulators-list {
display: flex;
flex-direction: column;
gap: var(--spacing-md);
margin-top: var(--spacing-md);
}
.regulator-association-item {
display: grid;
grid-template-columns: 2fr 1fr auto;
gap: var(--spacing-md);
align-items: end;
padding: var(--spacing-md);
background-color: var(--background-secondary);
border-radius: 8px;
border: 1px solid var(--border);
}
.regulators-summary {
margin-top: var(--spacing-md);
padding: var(--spacing-md);
background-color: var(--background);
border-radius: 8px;
border: 1px solid var(--border);
text-align: right;
color: var(--text-primary);
}

View File

@ -1,6 +1,6 @@
import { useState } from 'react' import { useState } from 'react'
import { useStorage } from '@/hooks/useStorage' import { useStorage } from '@/hooks/useStorage'
import { Waste } from '@/types' import { Waste, WasteRegulatorAssociation, NaturalRegulator } from '@/types'
import Card from '@/components/base/Card' import Card from '@/components/base/Card'
import Button from '@/components/base/Button' import Button from '@/components/base/Button'
import Input from '@/components/base/Input' import Input from '@/components/base/Input'
@ -27,6 +27,8 @@ export default function WasteConfigurationPage() {
const [errors, setErrors] = useState<Record<string, string>>({}) const [errors, setErrors] = useState<Record<string, string>>({})
const wastes = data?.wastes || [] const wastes = data?.wastes || []
const wasteOrigins = data?.wasteOrigins || []
const regulators = data?.regulators || []
const originTypeOptions = [ const originTypeOptions = [
{ value: 'animals', label: 'Animals' }, { value: 'animals', label: 'Animals' },
@ -35,6 +37,16 @@ export default function WasteConfigurationPage() {
{ value: 'other', label: 'Other' }, { value: 'other', label: 'Other' },
] ]
const wasteOriginOptions = wasteOrigins.map((origin) => ({
value: origin.id,
label: `${origin.name} (${origin.type})`,
}))
const regulatorOptions = regulators.map((reg) => ({
value: reg.id,
label: reg.name,
}))
const handleSubmit = (e: React.FormEvent) => { const handleSubmit = (e: React.FormEvent) => {
e.preventDefault() e.preventDefault()
@ -56,11 +68,13 @@ export default function WasteConfigurationPage() {
name: formData.name!, name: formData.name!,
originType: formData.originType!, originType: formData.originType!,
originSubType: formData.originSubType, originSubType: formData.originSubType,
wasteOriginId: formData.wasteOriginId,
originUnitsPer1000m3Methane: formData.originUnitsPer1000m3Methane!, originUnitsPer1000m3Methane: formData.originUnitsPer1000m3Methane!,
bmp: formData.bmp!, bmp: formData.bmp!,
waterPercentage: formData.waterPercentage!, waterPercentage: formData.waterPercentage!,
regulationNeeds: formData.regulationNeeds || [], regulationNeeds: formData.regulationNeeds || [],
regulatoryCharacteristics: formData.regulatoryCharacteristics, regulatoryCharacteristics: formData.regulatoryCharacteristics,
regulators: formData.regulators || [],
maxStorageDuration: formData.maxStorageDuration!, maxStorageDuration: formData.maxStorageDuration!,
createdAt: editingId ? wastes.find((w) => w.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(), createdAt: editingId ? wastes.find((w) => w.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
updatedAt: new Date().toISOString(), updatedAt: new Date().toISOString(),
@ -80,10 +94,12 @@ export default function WasteConfigurationPage() {
name: '', name: '',
originType: 'animals', originType: 'animals',
originSubType: '', originSubType: '',
wasteOriginId: undefined,
originUnitsPer1000m3Methane: 0, originUnitsPer1000m3Methane: 0,
bmp: 0, bmp: 0,
waterPercentage: 75, waterPercentage: 75,
regulationNeeds: [], regulationNeeds: [],
regulators: [],
maxStorageDuration: 30, maxStorageDuration: 30,
}) })
setEditingId(null) setEditingId(null)
@ -166,12 +182,24 @@ export default function WasteConfigurationPage() {
/> />
</div> </div>
<Input <div className="form-row">
label="Origin Sub Type" <Input
value={formData.originSubType || ''} label="Origin Sub Type"
onChange={(e) => setFormData({ ...formData, originSubType: e.target.value })} value={formData.originSubType || ''}
placeholder="Enter sub type (optional)" onChange={(e) => setFormData({ ...formData, originSubType: e.target.value })}
/> placeholder="Enter sub type (optional)"
/>
<Select
label="Waste Origin"
value={formData.wasteOriginId || ''}
onChange={(e) => setFormData({ ...formData, wasteOriginId: e.target.value || undefined })}
options={[
{ value: '', label: 'None' },
...wasteOriginOptions,
]}
helpText="Associate this waste with a waste origin (optional)"
/>
</div>
<div className="form-row"> <div className="form-row">
<Input <Input
@ -216,6 +244,348 @@ export default function WasteConfigurationPage() {
/> />
</div> </div>
{/* Regulation Needs Section */}
<div className="form-section">
<h3 className="form-section-title">Typical Regulation Needs</h3>
<div className="checkbox-grid">
{[
{ key: 'pathogenElimination', label: 'Pathogen Elimination' },
{ key: 'heavyMetalTreatment', label: 'Heavy Metal Treatment' },
{ key: 'odorElimination', label: 'Odor Elimination' },
{ key: 'oilEmulsification', label: 'Oil Emulsification' },
{ key: 'saltReduction', label: 'Salt Reduction' },
].map((need) => (
<label key={need.key} className="checkbox-item">
<input
type="checkbox"
checked={formData.regulationNeeds?.includes(need.key) || false}
onChange={(e) => {
const currentNeeds = formData.regulationNeeds || []
if (e.target.checked) {
setFormData({ ...formData, regulationNeeds: [...currentNeeds, need.key] })
} else {
setFormData({ ...formData, regulationNeeds: currentNeeds.filter((n) => n !== need.key) })
}
}}
/>
<span>{need.label}</span>
</label>
))}
</div>
</div>
{/* Regulatory Characteristics Section */}
<div className="form-section">
<h3 className="form-section-title">Regulatory Characteristics</h3>
{/* Nutrient Requirements */}
<div className="form-subsection">
<h4 className="form-subsection-title">Nutrient Requirements</h4>
<div className="form-row">
<Input
label="Nitrogen (N or NTK)"
type="number"
step="0.01"
min="0"
value={formData.regulatoryCharacteristics?.nitrogen || ''}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...formData.regulatoryCharacteristics,
nitrogen: parseFloat(e.target.value) || undefined,
},
})}
/>
<Input
label="Ammoniacal Nitrogen (N-NH₄)"
type="number"
step="0.01"
min="0"
value={formData.regulatoryCharacteristics?.ammoniacalNitrogen || ''}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...formData.regulatoryCharacteristics,
ammoniacalNitrogen: parseFloat(e.target.value) || undefined,
},
})}
/>
</div>
<div className="form-row">
<Input
label="Phosphorus (P)"
type="number"
step="0.01"
min="0"
value={formData.regulatoryCharacteristics?.phosphorus || ''}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...formData.regulatoryCharacteristics,
phosphorus: parseFloat(e.target.value) || undefined,
},
})}
/>
<Input
label="Potassium (K)"
type="number"
step="0.01"
min="0"
value={formData.regulatoryCharacteristics?.potassium || ''}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...formData.regulatoryCharacteristics,
potassium: parseFloat(e.target.value) || undefined,
},
})}
/>
</div>
<div className="form-row">
<Input
label="Carbon:Nitrogen Ratio (C:N)"
type="number"
step="0.1"
min="0"
value={formData.regulatoryCharacteristics?.carbonNitrogenRatio || ''}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...formData.regulatoryCharacteristics,
carbonNitrogenRatio: parseFloat(e.target.value) || undefined,
},
})}
/>
</div>
</div>
{/* Heavy Metals and Toxic Elements */}
<div className="form-subsection">
<h4 className="form-subsection-title">Heavy Metals and Toxic Elements (Needed)</h4>
<div className="checkbox-grid">
{[
{ key: 'arsenicElimination', label: 'Arsenic (As) Elimination' },
{ key: 'zincElimination', label: 'Zinc Elimination' },
{ key: 'aluminumElimination', label: 'Aluminum Elimination' },
{ key: 'copperElimination', label: 'Copper Elimination' },
{ key: 'heavyMetalsElimination', label: 'Heavy Metals Elimination' },
].map((item) => (
<label key={item.key} className="checkbox-item">
<input
type="checkbox"
checked={formData.regulatoryCharacteristics?.[item.key as keyof typeof formData.regulatoryCharacteristics] || false}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...formData.regulatoryCharacteristics,
[item.key]: e.target.checked,
},
})}
/>
<span>{item.label}</span>
</label>
))}
</div>
</div>
{/* Biological Contaminants */}
<div className="form-subsection">
<h4 className="form-subsection-title">Biological Contaminants (Needed)</h4>
<div className="checkbox-grid">
<label className="checkbox-item">
<input
type="checkbox"
checked={formData.regulatoryCharacteristics?.pathogenElimination || false}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...formData.regulatoryCharacteristics,
pathogenElimination: e.target.checked,
},
})}
/>
<span>Pathogen Elimination</span>
</label>
</div>
</div>
{/* Chemical Parameters */}
<div className="form-subsection">
<h4 className="form-subsection-title">Chemical Parameters (Needed)</h4>
<div className="checkbox-grid">
{[
{ key: 'lowCNRatioEnrichment', label: 'Low C:N Ratio Enrichment' },
{ key: 'medicationElimination', label: 'Medication Elimination' },
{ key: 'depositElimination', label: 'Deposit Elimination' },
{ key: 'odorElimination', label: 'Odor Elimination' },
{ key: 'turbidityReduction', label: 'Turbidity Reduction' },
{ key: 'sodiumReduction', label: 'Sodium (Na⁺) Reduction' },
{ key: 'chlorineReduction', label: 'Chlorine (Cl⁻) Reduction' },
{ key: 'electricalConductivityReduction', label: 'Electrical Conductivity Reduction' },
{ key: 'sulfideElimination', label: 'Sulfide Elimination' },
{ key: 'methaneElimination', label: 'Methane Elimination' },
{ key: 'co2Elimination', label: 'CO₂ Elimination' },
{ key: 'polyphenolElimination', label: 'Polyphenol Elimination' },
{ key: 'refractoryFractionsElimination', label: 'Refractory Structural Fractions Elimination' },
].map((item) => (
<label key={item.key} className="checkbox-item">
<input
type="checkbox"
checked={formData.regulatoryCharacteristics?.[item.key as keyof typeof formData.regulatoryCharacteristics] || false}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...formData.regulatoryCharacteristics,
[item.key]: e.target.checked,
},
})}
/>
<span>{item.label}</span>
</label>
))}
</div>
</div>
{/* Biological Processes */}
<div className="form-subsection">
<h4 className="form-subsection-title">Biological Processes (Needed)</h4>
<div className="checkbox-grid">
{[
{ key: 'microbiologicalCompetition', label: 'Microbiological Competition' },
{ key: 'oilEmulsification', label: 'Oil Emulsification' },
].map((item) => (
<label key={item.key} className="checkbox-item">
<input
type="checkbox"
checked={formData.regulatoryCharacteristics?.[item.key as keyof typeof formData.regulatoryCharacteristics] || false}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...formData.regulatoryCharacteristics,
[item.key]: e.target.checked,
},
})}
/>
<span>{item.label}</span>
</label>
))}
</div>
</div>
{/* pH Regulation */}
<div className="form-subsection">
<h4 className="form-subsection-title">pH Regulation (Needed)</h4>
<div className="checkbox-grid">
{[
{ key: 'acidityReduction', label: 'Acidity Reduction' },
{ key: 'phIncrease', label: 'pH Increase' },
{ key: 'phReduction', label: 'pH Reduction' },
].map((item) => (
<label key={item.key} className="checkbox-item">
<input
type="checkbox"
checked={formData.regulatoryCharacteristics?.[item.key as keyof typeof formData.regulatoryCharacteristics] || false}
onChange={(e) => setFormData({
...formData,
regulatoryCharacteristics: {
...formData.regulatoryCharacteristics,
[item.key]: e.target.checked,
},
})}
/>
<span>{item.label}</span>
</label>
))}
</div>
</div>
</div>
{/* Regulators Association Section */}
<Card title="Associated Regulators" className="form-section">
<p className="form-help-text">
Add regulators to this waste to address its regulation needs. Specify the percentage of total volume for each regulator.
</p>
<div className="regulators-list">
{(formData.regulators || []).map((association, index) => {
const regulator = regulators.find((r) => r.id === association.regulatorId)
return (
<div key={index} className="regulator-association-item">
<Select
label="Regulator"
value={association.regulatorId}
onChange={(e) => {
const updatedRegulators = [...(formData.regulators || [])]
updatedRegulators[index] = {
...updatedRegulators[index],
regulatorId: e.target.value,
}
setFormData({ ...formData, regulators: updatedRegulators })
}}
options={[
{ value: '', label: 'Select a regulator' },
...regulatorOptions.filter(
(opt) =>
opt.value === association.regulatorId ||
!(formData.regulators || []).some((a) => a.regulatorId === opt.value)
),
]}
/>
<Input
label="Volume Percentage (%)"
type="number"
min="0"
max="100"
step="0.1"
value={association.percentage || ''}
onChange={(e) => {
const updatedRegulators = [...(formData.regulators || [])]
updatedRegulators[index] = {
...updatedRegulators[index],
percentage: parseFloat(e.target.value) || 0,
}
setFormData({ ...formData, regulators: updatedRegulators })
}}
helpText={regulator ? `Regulator: ${regulator.name}` : undefined}
/>
<Button
type="button"
variant="danger"
onClick={() => {
const updatedRegulators = (formData.regulators || []).filter((_, i) => i !== index)
setFormData({ ...formData, regulators: updatedRegulators })
}}
>
Remove
</Button>
</div>
)
})}
<Button
type="button"
variant="secondary"
onClick={() => {
const newAssociation: WasteRegulatorAssociation = {
regulatorId: '',
percentage: 0,
}
setFormData({
...formData,
regulators: [...(formData.regulators || []), newAssociation],
})
}}
>
Add Regulator
</Button>
{(formData.regulators || []).length > 0 && (
<div className="regulators-summary">
<strong>
Total: {(formData.regulators || []).reduce((sum, a) => sum + (a.percentage || 0), 0).toFixed(1)}%
</strong>
</div>
)}
</div>
</Card>
<div className="form-actions"> <div className="form-actions">
<Button type="submit" variant="primary"> <Button type="submit" variant="primary">
{editingId ? 'Update' : 'Add'} Waste Type {editingId ? 'Update' : 'Add'} Waste Type

View File

@ -0,0 +1,50 @@
.waste-origins-config-page {
max-width: 1280px;
margin: 0 auto;
padding: var(--spacing-xl);
}
.page-title {
font-size: 2rem;
font-weight: 700;
color: var(--text-primary);
margin-bottom: var(--spacing-2xl);
}
.page-content {
display: flex;
flex-direction: column;
gap: var(--spacing-xl);
}
.form-card {
margin-bottom: var(--spacing-xl);
}
.form-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: var(--spacing-lg);
margin-bottom: var(--spacing-lg);
}
.form-actions {
display: flex;
gap: var(--spacing-md);
margin-top: var(--spacing-lg);
}
.table-card {
margin-top: var(--spacing-xl);
}
.table-actions {
display: flex;
gap: var(--spacing-sm);
}
.empty-state {
text-align: center;
padding: var(--spacing-2xl);
color: var(--text-secondary);
}

View File

@ -0,0 +1,171 @@
import { useState } from 'react'
import { useStorage } from '@/hooks/useStorage'
import { WasteOrigin } from '@/types'
import Card from '@/components/base/Card'
import Button from '@/components/base/Button'
import Input from '@/components/base/Input'
import Select from '@/components/base/Select'
import Table from '@/components/base/Table'
import { validateRequired } from '@/utils/validators'
import './WasteOriginsConfigurationPage.css'
export default function WasteOriginsConfigurationPage() {
const { data, addEntity, updateEntity, deleteEntity } = useStorage()
const [editingId, setEditingId] = useState<string | null>(null)
const [formData, setFormData] = useState<Partial<WasteOrigin>>({
type: 'animals',
name: '',
description: '',
})
const [errors, setErrors] = useState<Record<string, string>>({})
const wasteOrigins = data?.wasteOrigins || []
const originTypeOptions = [
{ value: 'animals', label: 'Animals' },
{ value: 'markets', label: 'Markets' },
{ value: 'restaurants', label: 'Restaurants' },
{ value: 'other', label: 'Other' },
]
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
const newErrors: Record<string, string> = {}
newErrors.name = validateRequired(formData.name)
newErrors.type = validateRequired(formData.type)
setErrors(newErrors)
if (Object.values(newErrors).some((error) => error !== undefined)) {
return
}
const wasteOrigin: WasteOrigin = {
id: editingId || crypto.randomUUID(),
type: formData.type!,
name: formData.name!,
description: formData.description,
createdAt: editingId ? wasteOrigins.find((o) => o.id === editingId)?.createdAt || new Date().toISOString() : new Date().toISOString(),
updatedAt: new Date().toISOString(),
}
if (editingId) {
updateEntity('wasteOrigins', editingId, wasteOrigin)
} else {
addEntity('wasteOrigins', wasteOrigin)
}
resetForm()
}
const resetForm = () => {
setFormData({
type: 'animals',
name: '',
description: '',
})
setEditingId(null)
setErrors({})
}
const handleEdit = (wasteOrigin: WasteOrigin) => {
setFormData(wasteOrigin)
setEditingId(wasteOrigin.id)
}
const handleDelete = (id: string) => {
if (confirm('Are you sure you want to delete this waste origin?')) {
deleteEntity('wasteOrigins', id)
}
}
const tableColumns = [
{
key: 'type',
header: 'Type',
render: (wasteOrigin: WasteOrigin) => wasteOrigin.type,
},
{
key: 'name',
header: 'Name',
render: (wasteOrigin: WasteOrigin) => wasteOrigin.name,
},
{
key: 'description',
header: 'Description',
render: (wasteOrigin: WasteOrigin) => wasteOrigin.description || '-',
},
{
key: 'actions',
header: 'Actions',
render: (wasteOrigin: WasteOrigin) => (
<div className="table-actions">
<Button variant="secondary" onClick={() => handleEdit(wasteOrigin)}>
Edit
</Button>
<Button variant="danger" onClick={() => handleDelete(wasteOrigin.id)}>
Delete
</Button>
</div>
),
},
]
return (
<div className="waste-origins-config-page">
<h1 className="page-title">Waste Origins Configuration</h1>
<div className="page-content">
<Card title={editingId ? 'Edit Waste Origin' : 'Add Waste Origin'} className="form-card">
<form onSubmit={handleSubmit}>
<div className="form-row">
<Select
label="Type *"
value={formData.type || 'animals'}
onChange={(e) => setFormData({ ...formData, type: e.target.value as WasteOrigin['type'] })}
options={originTypeOptions}
error={errors.type}
/>
<Input
label="Name *"
value={formData.name || ''}
onChange={(e) => setFormData({ ...formData, name: e.target.value })}
error={errors.name}
/>
</div>
<Input
label="Description"
value={formData.description || ''}
onChange={(e) => setFormData({ ...formData, description: e.target.value })}
multiline
rows={3}
/>
<div className="form-actions">
<Button type="submit" variant="primary">
{editingId ? 'Update' : 'Add'} Waste Origin
</Button>
{editingId && (
<Button type="button" variant="secondary" onClick={resetForm}>
Cancel
</Button>
)}
</div>
</form>
</Card>
<Card title="Waste Origins" className="table-card">
{wasteOrigins.length === 0 ? (
<div className="empty-state">
<p>No waste origins configured yet. Add your first waste origin to get started.</p>
</div>
) : (
<Table columns={tableColumns} data={wasteOrigins} />
)}
</Card>
</div>
</div>
)
}

View File

@ -13,20 +13,84 @@ export interface User extends BaseEntity {
lastLogin?: string lastLogin?: string
} }
// Waste Origin
export interface WasteOrigin extends BaseEntity {
type: 'animals' | 'markets' | 'restaurants' | 'other'
name: string
description?: string
}
// Transporter
export interface Transporter extends BaseEntity {
name: string
type: string
description?: string
contact?: {
name: string
email?: string
phone?: string
address?: string
}
}
// Waste Regulator Association
export interface WasteRegulatorAssociation {
regulatorId: string // Reference to NaturalRegulator
percentage: number // Percentage of total volume (0-100)
}
// Waste // Waste
export interface Waste extends BaseEntity { export interface Waste extends BaseEntity {
name: string name: string
originType: 'animals' | 'markets' | 'restaurants' | 'other' originType: 'animals' | 'markets' | 'restaurants' | 'other'
originSubType?: string originSubType?: string
wasteOriginId?: string // Reference to WasteOrigin (allows n wastes to 1 origin)
regulators?: WasteRegulatorAssociation[] // Associated regulators with volume percentages
originUnitsPer1000m3Methane: number originUnitsPer1000m3Methane: number
bmp: number // Nm³ CH₄/kg VS bmp: number // Nm³ CH₄/kg VS
waterPercentage: number // 0-100 waterPercentage: number // 0-100
regulationNeeds: string[] regulationNeeds: string[] // Typical regulation needs (pathogenElimination, odorElimination, etc.)
regulatoryCharacteristics?: { regulatoryCharacteristics?: {
nitrogen?: number // Nutrient Requirements
phosphorus?: number nitrogen?: number // Total Nitrogen (N or NTK)
potassium?: number ammoniacalNitrogen?: number // N-NH₄ - Indicator of rapid effect
carbonNitrogenRatio?: number phosphorus?: number // Total Phosphorus (P)
potassium?: number // Total Potassium (K)
carbonNitrogenRatio?: number // C:N ratio
// Heavy Metals and Toxic Elements
arsenicElimination?: boolean // Arsenic (As) elimination needed
zincElimination?: boolean // Zinc elimination needed
aluminumElimination?: boolean // Aluminum elimination needed
copperElimination?: boolean // Copper elimination needed
heavyMetalsElimination?: boolean // Heavy metals elimination needed
// Biological Contaminants
pathogenElimination?: boolean // Pathogen elimination needed
// Chemical Parameters
lowCNRatioEnrichment?: boolean // Low C:N ratio enrichment needed
medicationElimination?: boolean // Medication elimination needed
depositElimination?: boolean // Deposit elimination needed
odorElimination?: boolean // Odor elimination needed
turbidityReduction?: boolean // Turbidity reduction needed
sodiumReduction?: boolean // Sodium (Na⁺) reduction needed
chlorineReduction?: boolean // Chlorine (Cl⁻) reduction needed
electricalConductivityReduction?: boolean // Electrical conductivity reduction needed
sulfideElimination?: boolean // Sulfide elimination needed
methaneElimination?: boolean // Methane elimination needed
co2Elimination?: boolean // CO₂ elimination needed
polyphenolElimination?: boolean // Polyphenol elimination needed
refractoryFractionsElimination?: boolean // Elimination of refractory structural fractions needed
// Biological Processes
microbiologicalCompetition?: boolean // Microbiological competition needed
oilEmulsification?: boolean // Oil emulsification needed
// pH Regulation
acidityReduction?: boolean // Acidity reduction needed
phIncrease?: boolean // pH increase needed
phReduction?: boolean // pH reduction needed
} }
maxStorageDuration: number // days maxStorageDuration: number // days
} }
@ -36,13 +100,48 @@ export interface NaturalRegulator extends BaseEntity {
name: string name: string
type: string type: string
regulatoryCharacteristics?: { regulatoryCharacteristics?: {
nitrogen?: number // Nutrient Requirements
phosphorus?: number nitrogen?: number // Total Nitrogen (N or NTK)
potassium?: number ammoniacalNitrogen?: number // N-NH₄ - Indicator of rapid effect
carbonNitrogenRatio?: number phosphorus?: number // Total Phosphorus (P)
phAdjustment?: number // -14 to 14 potassium?: number // Total Potassium (K)
metalBinding?: boolean carbonNitrogenRatio?: number // C:N ratio
pathogenReduction?: boolean
// Heavy Metals and Toxic Elements
arsenicElimination?: boolean // Arsenic (As) elimination
zincElimination?: boolean // Zinc elimination
aluminumElimination?: boolean // Aluminum elimination
copperElimination?: boolean // Copper elimination
heavyMetalsElimination?: boolean // Heavy metals elimination
metalBinding?: boolean // General metal binding capability
// Biological Contaminants
pathogenReduction?: boolean // Pathogen elimination
// Chemical Parameters
lowCNRatioEnrichment?: boolean // Low C:N ratio enrichment
medicationElimination?: boolean // Medication elimination
depositElimination?: boolean // Deposit elimination
odorElimination?: boolean // Odor elimination
turbidityReduction?: boolean // Turbidity reduction
sodiumReduction?: boolean // Sodium (Na⁺) reduction
chlorineReduction?: boolean // Chlorine (Cl⁻) reduction
electricalConductivityReduction?: boolean // Electrical conductivity reduction
sulfideElimination?: boolean // Sulfide elimination
methaneElimination?: boolean // Methane elimination
co2Elimination?: boolean // CO₂ elimination
polyphenolElimination?: boolean // Polyphenol elimination
refractoryFractionsElimination?: boolean // Elimination of refractory structural fractions (lignin, alginates, fucoidans, crystalline cellulose, and insoluble fibers)
// Biological Processes
microbiologicalCompetition?: boolean // Microbiological competition
oilEmulsification?: boolean // Oil emulsification
// pH Regulation
acidityReduction?: boolean // Acidity reduction
phIncrease?: boolean // pH increase
phReduction?: boolean // pH reduction
phAdjustment?: number // -14 to 14 - Direct pH adjustment value
} }
applicationConditions: string applicationConditions: string
dosageRequirements: { dosageRequirements: {
@ -246,6 +345,135 @@ export interface Project extends BaseEntity {
businessPlan?: BusinessPlan businessPlan?: BusinessPlan
} }
// Module Component Types
export type ModuleComponentType =
| 'firstMethanization' // 1ère méthanisation (obligatoire) 21j
| 'waterEvaporation' // Evaporation eau (facultatif) 21j
| 'bioremediationEcosystem1' // Biorémidation par écosystemes 1 (facultatif) 21j
| 'bioremediationEcosystem2' // Biorémidation par écosystemes 2 (facultatif) 21j
| 'bioremediationEcosystem3' // Biorémidation par écosystemes 3 (facultatif) 21j
| 'secondMethanization' // 2nd méthanisation (obligatoire sauf si aucune remédiation) 21j
| 'waterEvaporationComposting' // Evaporation eau et compostage pour engrais (facultatif) 18j
| 'waterUvcTreatment' // Traitement de l'eau uvc (facultatif) 72h
| 'waterStorage' // Stockage eau 72h
| 'spirulinaWaterStorage' // Spiruline dans le stockage d'eau (facultatif) 21j
| 'solarPanels' // Panneaux solaire (facultatif)
| 'bitcoinMining' // Minage de bitcoins (facultatif)
export interface MonthlyValue {
january: number
february: number
march: number
april: number
may: number
june: number
july: number
august: number
september: number
october: number
november: number
december: number
}
export interface Dimensions {
length: number // m
height: number // m
width: number // m
}
export interface PowerSpecs {
kwh: number // kW.h
kw: number // kW équipés
}
export interface ProductionPowerSpecs {
kwh: number // kW.h possibles
kw: number // kW équipés possibles
}
// Module Component
export interface ModuleComponent extends BaseEntity {
name: string
type: ModuleComponentType
isOptional: boolean
defaultDuration: number // days or hours
durationUnit: 'days' | 'hours'
// Consumption (per day, by month, at fixed temperature 13°C)
waterConsumptionPerDay?: MonthlyValue // L/day or m³/day
heatConsumptionPerDay?: MonthlyValue // kJ/day or kWh/day at 13°C
powerConsumption?: PowerSpecs // kW.h and kW équipés
// Capacities
totalWetWasteCapacity?: number // t
totalDryWasteCapacity?: number // t
totalCompostCapacity?: number // t
totalWaterCapacity?: number // m³ or L
// Production
methaneProduction?: number // m³/day or m³/cycle
addedBiomassProduction?: number // t/day or t/cycle
waterProduction?: number // m³/day or L/day
heatProduction?: number // kJ/day or kWh/day
// Immobilization
immobilizationDuration?: number // days
// Waste types
wasteTypes?: string[] // waste IDs that can be processed
// Dimensions
dimensions?: Dimensions // L, h, l in meters
groundSurface?: number // m² (surface au sol)
// Needs (per day, by month, at fixed temperature 13°C)
heatNeedsPerDay?: MonthlyValue // kJ/day or kWh/day at 13°C
coolingNeedsPerDay?: MonthlyValue // kJ/day or kWh/day at 13°C
// Production power
productionPower?: ProductionPowerSpecs // kW.h and kW équipés possibles
// ETP (Equivalent Temps Plein - Full Time Equivalent)
annualExploitationETP?: number // ETP exploitation annuel
annualMaintenanceETP?: number // ETP maintenance annuel
annualSupervisionETP?: number // ETP supervision annuel
// Costs (annual)
totalMaterialCost?: number // €/year (CAPEX)
annualExploitationConsumablesCost?: number // €/year
annualMaintenanceConsumablesCost?: number // €/year
// Lifetime
lifetime?: number // years (Durée de vie)
}
// Ecosystem Regulator Association
export interface EcosystemRegulatorAssociation {
regulatorId: string // Reference to NaturalRegulator
percentage: number // Percentage of total volume (0-100)
role: 'primary' | 'secondary' | 'support' // Role in the ecosystem
}
// Ecosystem
export interface Ecosystem extends BaseEntity {
name: string
description?: string
// Three primary regulation needs this ecosystem addresses
primaryRegulationNeeds: [string, string, string] // Exactly 3 regulation needs
// Regulators that compose this ecosystem
regulators: EcosystemRegulatorAssociation[]
// Waste types that can benefit from this ecosystem (optional, empty means all)
compatibleWasteTypes?: string[] // waste IDs
// Effectiveness rating for each regulation need (0-100)
effectiveness: {
[regulationNeed: string]: number // 0-100 percentage
}
// Application conditions
applicationConditions?: string
// Estimated treatment duration (days)
treatmentDuration?: number
}
// Storage structure // Storage structure
export interface StorageData { export interface StorageData {
version: string version: string
@ -254,6 +482,10 @@ export interface StorageData {
wastes: Waste[] wastes: Waste[]
regulators: NaturalRegulator[] regulators: NaturalRegulator[]
services: Service[] services: Service[]
wasteOrigins: WasteOrigin[]
transporters: Transporter[]
moduleComponents: ModuleComponent[] // Added
ecosystems: Ecosystem[] // Added
treatmentSites: TreatmentSite[] treatmentSites: TreatmentSite[]
wasteSites: WasteSite[] wasteSites: WasteSite[]
investors: Investor[] investors: Investor[]

View File

@ -1,16 +1,21 @@
import { StorageData } from '@/types' import { StorageData, Waste, NaturalRegulator } from '@/types'
import { seedWastes } from '@/data/seedWastes' import wastesSeeds from '../../data/seeds/wastes-seeds.json'
import { seedRegulators } from '@/data/seedRegulators' import regulatorsSeeds from '../../data/seeds/regulators-seeds.json'
import { initializeStorage } from './storage' import { initializeStorage } from './storage'
// Load seed data from JSON files
export function loadSeedData(): StorageData { export function loadSeedData(): StorageData {
const data = initializeStorage() const data = initializeStorage()
// Add seed wastes // Add seed wastes from JSON file
data.wastes = seedWastes if (wastesSeeds && Array.isArray(wastesSeeds.wastes)) {
data.wastes = wastesSeeds.wastes as Waste[]
}
// Add seed regulators // Add seed regulators from JSON file
data.regulators = seedRegulators if (regulatorsSeeds && Array.isArray(regulatorsSeeds.regulators)) {
data.regulators = regulatorsSeeds.regulators as NaturalRegulator[]
}
return data return data
} }
@ -18,10 +23,12 @@ export function loadSeedData(): StorageData {
export function addSeedDataToExisting(data: StorageData): StorageData { export function addSeedDataToExisting(data: StorageData): StorageData {
// Only add wastes that don't already exist // Only add wastes that don't already exist
const existingWasteIds = new Set(data.wastes.map(w => w.id)) const existingWasteIds = new Set(data.wastes.map(w => w.id))
const seedWastes = (wastesSeeds && Array.isArray(wastesSeeds.wastes) ? wastesSeeds.wastes : []) as Waste[]
const newWastes = seedWastes.filter(w => !existingWasteIds.has(w.id)) const newWastes = seedWastes.filter(w => !existingWasteIds.has(w.id))
// Only add regulators that don't already exist // Only add regulators that don't already exist
const existingRegulatorIds = new Set(data.regulators.map(r => r.id)) const existingRegulatorIds = new Set(data.regulators.map(r => r.id))
const seedRegulators = (regulatorsSeeds && Array.isArray(regulatorsSeeds.regulators) ? regulatorsSeeds.regulators : []) as NaturalRegulator[]
const newRegulators = seedRegulators.filter(r => !existingRegulatorIds.has(r.id)) const newRegulators = seedRegulators.filter(r => !existingRegulatorIds.has(r.id))
return { return {

View File

@ -1,4 +1,11 @@
import { StorageData } from '@/types' import { StorageData, Waste, NaturalRegulator, Service, WasteOrigin, Transporter, ModuleComponent, Ecosystem } from '@/types'
import wastesSeeds from '../../data/seeds/wastes-seeds.json'
import regulatorsSeeds from '../../data/seeds/regulators-seeds.json'
import servicesSeeds from '../../data/seeds/services-seeds.json'
import wasteOriginsSeeds from '../../data/seeds/waste-origins-seeds.json'
import transportersSeeds from '../../data/seeds/transporters-seeds.json'
import moduleComponentsSeeds from '../../data/seeds/module-components-seeds.json'
import ecosystemsSeeds from '../../data/seeds/ecosystems-seeds.json'
const STORAGE_KEY = '4nkwaste_simulator_data' const STORAGE_KEY = '4nkwaste_simulator_data'
const USER_KEY = '4nkwaste_simulator_user' const USER_KEY = '4nkwaste_simulator_user'
@ -15,6 +22,10 @@ export function initializeStorage(): StorageData {
wastes: [], wastes: [],
regulators: [], regulators: [],
services: [], services: [],
wasteOrigins: [],
transporters: [],
moduleComponents: [],
ecosystems: [],
treatmentSites: [], treatmentSites: [],
wasteSites: [], wasteSites: [],
investors: [], investors: [],
@ -23,17 +34,111 @@ export function initializeStorage(): StorageData {
} }
} }
// Load data from localStorage // Load seed data from JSON files
function loadSeedData(): StorageData {
const seedData = initializeStorage()
// Load wastes from seed file
if (wastesSeeds && Array.isArray(wastesSeeds.wastes)) {
seedData.wastes = wastesSeeds.wastes as Waste[]
}
// Load regulators from seed file
if (regulatorsSeeds && Array.isArray(regulatorsSeeds.regulators)) {
seedData.regulators = regulatorsSeeds.regulators as NaturalRegulator[]
}
// Load services from seed file
if (servicesSeeds && Array.isArray(servicesSeeds.services)) {
seedData.services = servicesSeeds.services as Service[]
}
// Load waste origins from seed file
if (wasteOriginsSeeds && Array.isArray(wasteOriginsSeeds.wasteOrigins)) {
seedData.wasteOrigins = wasteOriginsSeeds.wasteOrigins as WasteOrigin[]
}
// Load transporters from seed file
if (transportersSeeds && Array.isArray(transportersSeeds.transporters)) {
seedData.transporters = transportersSeeds.transporters as Transporter[]
}
// Load module components from seed file
if (moduleComponentsSeeds && Array.isArray(moduleComponentsSeeds.moduleComponents)) {
seedData.moduleComponents = moduleComponentsSeeds.moduleComponents as ModuleComponent[]
}
// Load ecosystems from seed file
if (ecosystemsSeeds && Array.isArray(ecosystemsSeeds.ecosystems)) {
seedData.ecosystems = ecosystemsSeeds.ecosystems as Ecosystem[]
}
return seedData
}
// Merge local data over seed data (local data takes precedence)
function mergeData(seedData: StorageData, localData: StorageData): StorageData {
return {
...seedData,
...localData,
// For arrays, merge by ID (local data overrides seeds)
wastes: mergeArraysById(seedData.wastes, localData.wastes),
regulators: mergeArraysById(seedData.regulators, localData.regulators),
services: mergeArraysById(seedData.services, localData.services),
wasteOrigins: mergeArraysById(seedData.wasteOrigins, localData.wasteOrigins),
transporters: mergeArraysById(seedData.transporters, localData.transporters),
moduleComponents: mergeArraysById(seedData.moduleComponents, localData.moduleComponents),
ecosystems: mergeArraysById(seedData.ecosystems, localData.ecosystems),
// Keep local data for other arrays
users: localData.users || seedData.users,
treatmentSites: localData.treatmentSites || seedData.treatmentSites,
wasteSites: localData.wasteSites || seedData.wasteSites,
investors: localData.investors || seedData.investors,
administrativeProcedures: localData.administrativeProcedures || seedData.administrativeProcedures,
projects: localData.projects || seedData.projects,
// Preserve version and lastModified from local data if exists
version: localData.version || seedData.version,
lastModified: localData.lastModified || seedData.lastModified,
}
}
// Helper to merge arrays by ID (local items override seed items with same ID)
function mergeArraysById<T extends { id: string }>(seedArray: T[], localArray: T[]): T[] {
const localMap = new Map(localArray.map(item => [item.id, item]))
const seedMap = new Map(seedArray.map(item => [item.id, item]))
// Start with all seed items
const merged = new Map(seedMap)
// Override with local items (local takes precedence)
localMap.forEach((item, id) => {
merged.set(id, item)
})
return Array.from(merged.values())
}
// Load data from localStorage, merging with seed data
export function loadStorage(): StorageData { export function loadStorage(): StorageData {
try { try {
const data = localStorage.getItem(STORAGE_KEY) // First load seed data
if (!data) { const seedData = loadSeedData()
return initializeStorage()
// Then try to load local data
const localDataString = localStorage.getItem(STORAGE_KEY)
if (!localDataString) {
// No local data, return seed data
return seedData
} }
return JSON.parse(data) as StorageData
const localData = JSON.parse(localDataString) as StorageData
// Merge: local data overrides seed data
return mergeData(seedData, localData)
} catch (error) { } catch (error) {
console.error('Error loading storage:', error) console.error('Error loading storage:', error)
return initializeStorage() // On error, fallback to seed data
return loadSeedData()
} }
} }
@ -67,6 +172,10 @@ export function importData(jsonString: string): { success: boolean; errors: stri
if (!Array.isArray(data.wastes)) errors.push('Invalid wastes array') if (!Array.isArray(data.wastes)) errors.push('Invalid wastes array')
if (!Array.isArray(data.regulators)) errors.push('Invalid regulators array') if (!Array.isArray(data.regulators)) errors.push('Invalid regulators array')
if (!Array.isArray(data.services)) errors.push('Invalid services array') if (!Array.isArray(data.services)) errors.push('Invalid services array')
if (!Array.isArray(data.wasteOrigins)) errors.push('Invalid wasteOrigins array')
if (!Array.isArray(data.transporters)) errors.push('Invalid transporters array')
if (!Array.isArray(data.moduleComponents)) errors.push('Invalid moduleComponents array')
if (!Array.isArray(data.ecosystems)) errors.push('Invalid ecosystems array')
if (!Array.isArray(data.treatmentSites)) errors.push('Invalid treatmentSites array') if (!Array.isArray(data.treatmentSites)) errors.push('Invalid treatmentSites array')
if (!Array.isArray(data.wasteSites)) errors.push('Invalid wasteSites array') if (!Array.isArray(data.wasteSites)) errors.push('Invalid wasteSites array')
if (!Array.isArray(data.investors)) errors.push('Invalid investors array') if (!Array.isArray(data.investors)) errors.push('Invalid investors array')

5
src/vite-env.d.ts vendored
View File

@ -1 +1,6 @@
/// <reference types="vite/client" /> /// <reference types="vite/client" />
declare module '*.json' {
const value: any
export default value
}

128
translate_seeds.py Normal file
View File

@ -0,0 +1,128 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
Temporary script to translate seed files from French to English
"""
import json
import re
# Translation dictionary for common terms
translations = {
"lisier": "slurry",
"vache": "cow",
"porc": "pig",
"porcelet": "piglet",
"poules pondeuses": "laying hens",
"poulets de chair": "broiler chickens",
"huiles": "oils",
"restaurant à fort volume": "high volume restaurant",
"cantine scolaire": "school canteen",
"ménages": "households",
"usine": "plant",
"usine de margarine": "margarine plant",
"déchets verts": "green waste",
"feuilles": "leaves",
"tiges tendres": "tender stems",
"hectare de production d'herbes": "hectare of herb production",
"arbre urbain": "urban tree",
"branches": "branches",
"arbre élagué": "pruned tree",
"hectare d'oliviers": "hectare of olive trees",
"grignons d'olive": "olive pomace",
"margines": "olive mill wastewater",
"boues urbaines": "urban sludge",
"station d'épuration urbaine": "urban wastewater treatment plant",
"flottats graisseux urbains": "urban fatty floatables",
"graisses de station d'épuration": "wastewater treatment plant fats",
"sargasses": "sargassum",
"laminaires": "laminaria",
"gracilaria": "gracilaria",
"ulves": "ulva",
"bananes mûres": "ripe bananas",
"petit marché local": "small local market",
"mangues mûres invendues": "unsold ripe mangoes",
"usine agroalimentaire": "food processing plant",
"légumes frais humides": "fresh wet vegetables",
"marché de gros": "wholesale market",
"résidus de légumes transformés": "processed vegetable residues",
"site industriel agroalimentaire": "food industrial site",
"restes de repas cuisinés": "cooked food leftovers",
"viande avariée": "spoiled meat",
"abattoir": "slaughterhouse",
"lait": "milk",
"yaourt": "yogurt",
"crème périmés": "expired cream",
"usine de produits laitiers": "dairy plant",
"pain rassis": "stale bread",
"boulangerie": "bakery",
"poissons avariés": "spoiled fish",
"marché aux poissons": "fish market",
"biodéchets mixtes": "mixed biowaste",
"déchets alimentaires ménagers": "household food waste",
}
def translate_text(text):
"""Simple translation function"""
if not text:
return text
# Replace common terms
result = text
for fr, en in translations.items():
result = result.replace(fr, en)
# Translate pH notes
result = re.sub(r'pH:\s*([0-9,\.]+)', r'pH: \1', result)
result = result.replace('à', 'to')
result = result.replace('selon', 'depending on')
return result
def translate_wastes():
"""Translate wastes-seeds.json"""
with open('data/seeds/wastes-seeds.json', 'r', encoding='utf-8') as f:
data = json.load(f)
for waste in data['wastes']:
if 'name' in waste:
waste['name'] = translate_text(waste['name'])
if 'originSubType' in waste:
waste['originSubType'] = translate_text(waste['originSubType'])
if 'notes' in waste:
waste['notes'] = translate_text(waste['notes'])
with open('data/seeds/wastes-seeds.json', 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
def translate_regulators():
"""Translate and clean regulators-seeds.json"""
with open('data/seeds/regulators-seeds.json', 'r', encoding='utf-8') as f:
data = json.load(f)
# Filter out invalid entries
valid_regulators = []
for reg in data['regulators']:
# Skip entries with type "unknown" or invalid names
if reg.get('type') == 'unknown':
continue
if reg.get('name') in ['Logique de symbiose :', 'Effets requis :', 'Cohabitation recommandée :']:
continue
# Translate
if 'name' in reg:
reg['name'] = translate_text(reg['name'])
if 'applicationConditions' in reg:
reg['applicationConditions'] = translate_text(reg['applicationConditions'])
valid_regulators.append(reg)
data['regulators'] = valid_regulators
with open('data/seeds/regulators-seeds.json', 'w', encoding='utf-8') as f:
json.dump(data, f, indent=2, ensure_ascii=False)
if __name__ == '__main__':
translate_wastes()
translate_regulators()
print("Translation complete!")

View File

@ -31,6 +31,6 @@
"@/types/*": ["./src/types/*"] "@/types/*": ["./src/types/*"]
} }
}, },
"include": ["src"], "include": ["src", "data/seeds/*.json"],
"references": [{ "path": "./tsconfig.node.json" }] "references": [{ "path": "./tsconfig.node.json" }]
} }