-
Notifications
You must be signed in to change notification settings - Fork 0
ECOTRACK-1: Green Route Advisor: pre-shipment scenario comparison #21
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # Changelog | ||
|
|
||
| All notable changes to this project will be documented in this file. | ||
|
|
||
| ## [Unreleased] | ||
|
|
||
| ## [2026-05-20] | ||
|
|
||
| ### Added | ||
| - Green Route Advisor v1 scenario comparison endpoint: `POST /api/v1/shipments/compare`. | ||
| - Scenario comparison DTOs for request/response payloads: | ||
| - `backend/src/main/java/com/ecotrack/dto/ScenarioComparisonRequestDTO.java` | ||
| - `backend/src/main/java/com/ecotrack/dto/ScenarioComparisonResponseDTO.java` | ||
| - `backend/src/main/java/com/ecotrack/dto/ScenarioInputDTO.java` | ||
| - `backend/src/main/java/com/ecotrack/dto/ScenarioResultDTO.java` | ||
| - Shipment Hub UI support for side-by-side estimated scenario comparison and preferred scenario highlighting. | ||
| - Client-side Shipment Hub status filter behavior and empty-state handling. | ||
|
|
||
| ### Changed | ||
| - Shipment service now compares at least two scenarios using existing `SustainabilityService` logic, with explicit rule `MIN_ESTIMATED_CO2E` and methodology version `GLEC Framework v3`. | ||
| - Shipment Hub status filter resets to `All Statuses` after creating a shipment. | ||
|
|
||
| ### Known Limitations | ||
| - Full project frontend lint remains blocked by pre-existing issues outside feature scope. | ||
| - Backend test execution in this environment is blocked by local Java compiler/toolchain initialization error. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| package com.ecotrack.dto; | ||
|
|
||
| import lombok.Data; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Data | ||
| public class ScenarioComparisonRequestDTO { | ||
| private List<ScenarioInputDTO> scenarios; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.ecotrack.dto; | ||
|
|
||
| import lombok.Builder; | ||
| import lombok.Data; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Data | ||
| @Builder | ||
| public class ScenarioComparisonResponseDTO { | ||
| private String preferredScenario; | ||
| private String rankingRule; | ||
| private String methodologyVersion; | ||
| private List<ScenarioResultDTO> scenarios; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| package com.ecotrack.dto; | ||
|
|
||
| import lombok.Data; | ||
|
|
||
| import java.util.UUID; | ||
|
|
||
| @Data | ||
| public class ScenarioInputDTO { | ||
| private String name; | ||
| private String origin; | ||
| private String destination; | ||
| private Double distanceKm; | ||
| private Double payloadTons; | ||
| private String transportMode; | ||
| private UUID vehicleId; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| package com.ecotrack.dto; | ||
|
|
||
| import lombok.Builder; | ||
| import lombok.Data; | ||
|
|
||
| @Data | ||
| @Builder | ||
| public class ScenarioResultDTO { | ||
| private String scenario; | ||
| private String origin; | ||
| private String destination; | ||
| private String transportMode; | ||
| private Double distanceKm; | ||
| private Double payloadTons; | ||
| private String vehicleModel; | ||
| private Double estimatedCo2; | ||
| private String estimateLabel; | ||
| private boolean preferred; | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,11 +2,15 @@ | |
|
|
||
| import com.ecotrack.dto.ShipmentDTO; | ||
| import com.ecotrack.dto.ShipmentDetailDTO; | ||
| import com.ecotrack.dto.ScenarioComparisonResponseDTO; | ||
| import com.ecotrack.dto.ScenarioInputDTO; | ||
| import com.ecotrack.dto.ScenarioResultDTO; | ||
| import com.ecotrack.model.EmissionLog; | ||
| import com.ecotrack.model.Shipment; | ||
| import com.ecotrack.model.Vehicle; | ||
| import com.ecotrack.model.enums.FuelType; | ||
| import com.ecotrack.model.enums.ShipmentStatus; | ||
| import com.ecotrack.model.enums.TransportMode; | ||
| import com.ecotrack.repository.EmissionLogRepository; | ||
| import com.ecotrack.repository.ShipmentRepository; | ||
| import com.ecotrack.repository.VehicleRepository; | ||
|
|
@@ -19,6 +23,8 @@ | |
| import java.time.LocalDateTime; | ||
| import java.util.List; | ||
| import java.util.UUID; | ||
| import java.util.Comparator; | ||
| import java.util.Objects; | ||
| import java.util.stream.Collectors; | ||
|
|
||
| @Service | ||
|
|
@@ -99,6 +105,75 @@ public List<ShipmentDTO> getLive() { | |
| .stream().map(this::toDTO).collect(Collectors.toList()); | ||
| } | ||
|
|
||
| public ScenarioComparisonResponseDTO compareScenarios(List<ScenarioInputDTO> scenarios) { | ||
| if (scenarios == null || scenarios.size() < 2) { | ||
| throw new IllegalArgumentException("At least 2 scenarios are required for comparison"); | ||
| } | ||
|
|
||
| List<ScenarioResultDTO> results = scenarios.stream() | ||
| .map(this::buildScenarioResult) | ||
| .collect(Collectors.toList()); | ||
|
Comment on lines
+113
to
+115
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against null scenario items before mapping. If any element in Suggested fix List<ScenarioResultDTO> results = scenarios.stream()
+ .peek(s -> {
+ if (s == null) {
+ throw new IllegalArgumentException("Scenario entries must not be null");
+ }
+ })
.map(this::buildScenarioResult)
.collect(Collectors.toList());🤖 Prompt for AI Agents |
||
|
|
||
| ScenarioResultDTO preferred = results.stream() | ||
| .min(Comparator.comparing(ScenarioResultDTO::getEstimatedCo2)) | ||
| .orElseThrow(() -> new IllegalArgumentException("No scenarios to compare")); | ||
|
|
||
| results.forEach(result -> result.setPreferred(Objects.equals(result.getScenario(), preferred.getScenario()))); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Preferred flag computation is incorrect when scenario names are duplicated.
Suggested fix- results.forEach(result -> result.setPreferred(Objects.equals(result.getScenario(), preferred.getScenario())));
+ results.forEach(result -> result.setPreferred(result == preferred));🤖 Prompt for AI Agents |
||
|
|
||
| return ScenarioComparisonResponseDTO.builder() | ||
| .preferredScenario(preferred.getScenario()) | ||
| .rankingRule("MIN_ESTIMATED_CO2E") | ||
| .methodologyVersion("GLEC Framework v3") | ||
| .scenarios(results) | ||
| .build(); | ||
| } | ||
|
|
||
| private ScenarioResultDTO buildScenarioResult(ScenarioInputDTO input) { | ||
| if (input.getDistanceKm() == null || input.getDistanceKm() <= 0) { | ||
| throw new IllegalArgumentException("distanceKm must be greater than 0"); | ||
| } | ||
| if (input.getPayloadTons() == null || input.getPayloadTons() <= 0) { | ||
| throw new IllegalArgumentException("payloadTons must be greater than 0"); | ||
| } | ||
| if (input.getVehicleId() == null) { | ||
| throw new IllegalArgumentException("vehicleId is required"); | ||
| } | ||
|
|
||
| TransportMode mode; | ||
| try { | ||
| mode = TransportMode.valueOf(input.getTransportMode().toUpperCase()); | ||
| } catch (Exception ex) { | ||
| throw new IllegalArgumentException("Unsupported transportMode: " + input.getTransportMode()); | ||
| } | ||
|
Comment on lines
+145
to
+147
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: find . -name "ShipmentService.java" -type fRepository: Ostaps/ecotrack Length of output: 125 🏁 Script executed: wc -l ./backend/src/main/java/com/ecotrack/service/ShipmentService.javaRepository: Ostaps/ecotrack Length of output: 129 🏁 Script executed: sed -n '135,155p' ./backend/src/main/java/com/ecotrack/service/ShipmentService.javaRepository: Ostaps/ecotrack Length of output: 1015 🏁 Script executed: cat > /tmp/verify_exception.java << 'EOF'
// Verifying IllegalArgumentException constructor
public class TestException {
public static void main(String[] args) {
try {
throw new Exception("root cause");
} catch (Exception ex) {
// This should compile - IllegalArgumentException accepts Throwable cause
throw new IllegalArgumentException("Message", ex);
}
}
}
EOF
# Just checking Java syntax - IllegalArgumentException(String message, Throwable cause) is standard Java API
echo "IllegalArgumentException constructors:"
echo "1. IllegalArgumentException(String message)"
echo "2. IllegalArgumentException(String message, Throwable cause)"
echo "3. IllegalArgumentException(Throwable cause)"Repository: Ostaps/ecotrack Length of output: 248 Preserve the original exception cause when wrapping transport mode parse errors. Line 146 drops the caught exception Suggested fix } catch (Exception ex) {
- throw new IllegalArgumentException("Unsupported transportMode: " + input.getTransportMode());
+ throw new IllegalArgumentException("Unsupported transportMode: " + input.getTransportMode(), ex);
}🧰 Tools🪛 PMD (7.24.0)[Medium] 146-146: PreserveStackTrace (Best Practices): Thrown exception does not preserve the stack trace of exception 'ex' on all code paths (PreserveStackTrace (Best Practices)) 🤖 Prompt for AI Agents |
||
|
|
||
| Vehicle vehicle = vehicleRepository.findById(input.getVehicleId()) | ||
| .orElseThrow(() -> new EntityNotFoundException("Vehicle not found: " + input.getVehicleId())); | ||
|
|
||
| Shipment shipment = Shipment.builder() | ||
| .origin(input.getOrigin()) | ||
| .destination(input.getDestination()) | ||
| .distanceKm(input.getDistanceKm()) | ||
| .payloadTons(input.getPayloadTons()) | ||
| .transportMode(mode) | ||
| .vehicle(vehicle) | ||
| .build(); | ||
|
|
||
| double estimate = sustainabilityService.calculateEmissions(shipment, vehicle); | ||
|
|
||
| return ScenarioResultDTO.builder() | ||
| .scenario(input.getName()) | ||
| .origin(input.getOrigin()) | ||
| .destination(input.getDestination()) | ||
| .transportMode(mode.name()) | ||
| .distanceKm(input.getDistanceKm()) | ||
| .payloadTons(input.getPayloadTons()) | ||
| .vehicleModel(vehicle.getModel()) | ||
| .estimatedCo2(estimate) | ||
| .estimateLabel("Estimated") | ||
| .preferred(false) | ||
| .build(); | ||
| } | ||
|
|
||
| // ── Mapping helpers ────────────────────────────────────────────────────── | ||
|
|
||
| public ShipmentDTO toDTO(Shipment s) { | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| # Green Route Advisor v1 | ||
|
|
||
| ## Scope | ||
| Green Route Advisor v1 adds pre-shipment scenario comparison in Shipment Hub while reusing the existing emissions calculation path. | ||
|
Comment on lines
+3
to
+4
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add blank lines after section headings to satisfy markdownlint MD022. Several headings are followed immediately by content. Insert one empty line after each affected heading so docs lint passes consistently. Proposed fix ## Scope
+
Green Route Advisor v1 adds pre-shipment scenario comparison in Shipment Hub while reusing the existing emissions calculation path.
@@
## API Contract
+
Endpoint: `POST /api/v1/shipments/compare`
@@
## UI Behavior (Shipment Hub)
+
File: `frontend/src/pages/ShipmentHub.jsx`
@@
## Related Shipment Hub Fix
+
The Shipment Hub status dropdown now:
@@
## Verification Status
+
Implemented and wired across backend/frontend. Full-suite validation is partially blocked by known pre-existing/global environment issues:Also applies to: 16-17, 35-36, 43-44, 50-51 🧰 Tools🪛 markdownlint-cli2 (0.22.1)[warning] 3-3: Headings should be surrounded by blank lines (MD022, blanks-around-headings) 🤖 Prompt for AI Agents |
||
|
|
||
| In scope: | ||
| - Compare at least two scenarios side-by-side from planning inputs. | ||
| - Determine preferred scenario by lowest estimated CO2e. | ||
| - Return methodology traceability in API response. | ||
| - Visually mark compared values as `Estimated`. | ||
|
|
||
| Out of scope: | ||
| - Any parallel emissions engine or alternate formula path. | ||
| - Multi-objective optimization (cost/time/carbon weighting). | ||
|
|
||
| ## API Contract | ||
| Endpoint: `POST /api/v1/shipments/compare` | ||
|
|
||
| Request body: | ||
| - `scenarios`: array of scenario objects, minimum 2. | ||
| - Scenario fields: `name`, `origin`, `destination`, `distanceKm`, `payloadTons`, `transportMode`, `vehicleId`. | ||
|
|
||
| Response body: | ||
| - `preferredScenario`: scenario name with lowest estimated CO2e. | ||
| - `rankingRule`: `MIN_ESTIMATED_CO2E`. | ||
| - `methodologyVersion`: `GLEC Framework v3`. | ||
| - `scenarios`: result array with per-scenario estimate and `estimateLabel` (`Estimated`). | ||
|
|
||
| Validation behavior: | ||
| - Rejects requests with fewer than two scenarios. | ||
| - Rejects non-positive `distanceKm` and `payloadTons`. | ||
| - Rejects unsupported `transportMode` values. | ||
| - Rejects unknown `vehicleId` values. | ||
|
|
||
| ## UI Behavior (Shipment Hub) | ||
| File: `frontend/src/pages/ShipmentHub.jsx` | ||
|
|
||
| - Adds a Green Route Advisor compare panel with two scenario inputs. | ||
| - Calls `compareShipmentScenarios` from `frontend/src/api/shipments.js`. | ||
| - Renders side-by-side estimated results and highlights preferred scenario. | ||
| - Shows ranking rule and methodology version returned by backend. | ||
|
|
||
| ## Related Shipment Hub Fix | ||
| The Shipment Hub status dropdown now: | ||
| - Updates React state on selection. | ||
| - Filters shipment rows client-side (no extra request). | ||
| - Shows an explicit empty state when no rows match. | ||
| - Resets to `All Statuses` after successful shipment creation. | ||
|
|
||
| ## Verification Status | ||
| Implemented and wired across backend/frontend. Full-suite validation is partially blocked by known pre-existing/global environment issues: | ||
| - Frontend project-wide lint includes unrelated violations in other pages. | ||
| - Backend `mvn test` fails in this environment due to toolchain initialization error. | ||
|
|
||
| Targeted validation completed during implementation: | ||
| - Changed Shipment Hub file lint passes. | ||
| - Compare flow wiring and response rendering are implemented and integrated. | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix heading spacing in changelog sections (MD022).
### Added,### Changed, and### Known Limitationsshould each be followed by a blank line.Proposed fix
Also applies to: 19-20, 23-24
🧰 Tools
🪛 markdownlint-cli2 (0.22.1)
[warning] 9-9: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
🤖 Prompt for AI Agents