feat(endpoints): queryAvailableDrones
This commit is contained in:
parent
9fbc2f3a14
commit
ec0d9087dd
10 changed files with 291 additions and 18 deletions
41
ilp-cw-api/[POST] queryAvailableDrones/Complex.bru
Normal file
41
ilp-cw-api/[POST] queryAvailableDrones/Complex.bru
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
meta {
|
||||
name: Complex
|
||||
type: http
|
||||
seq: 3
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{API_BASE}}/queryAvailableDrones
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
[
|
||||
{
|
||||
"id": 123,
|
||||
"date": "2025-12-22",
|
||||
"time": "14:30",
|
||||
"requirements": {
|
||||
"capacity": 0.75,
|
||||
"heating": true,
|
||||
"maxCost": 13.5
|
||||
}
|
||||
},
|
||||
{
|
||||
"id": 456,
|
||||
"date": "2025-12-25",
|
||||
"time": "11:30",
|
||||
"requirements": {
|
||||
"capacity": 0.75,
|
||||
"heating": true,
|
||||
"maxCost": 13.5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
32
ilp-cw-api/[POST] queryAvailableDrones/Example.bru
Normal file
32
ilp-cw-api/[POST] queryAvailableDrones/Example.bru
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
meta {
|
||||
name: Example
|
||||
type: http
|
||||
seq: 1
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{API_BASE}}/queryAvailableDrones
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
[
|
||||
{
|
||||
"id": 123,
|
||||
"date": "2025-12-22",
|
||||
"time": "14:30",
|
||||
"requirements": {
|
||||
"capacity": 0.75,
|
||||
"cooling": false,
|
||||
"heating": true,
|
||||
"maxCost": 13.5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
meta {
|
||||
name: Treat Null as False (Cooling)
|
||||
type: http
|
||||
seq: 2
|
||||
}
|
||||
|
||||
post {
|
||||
url: {{API_BASE}}/queryAvailableDrones
|
||||
body: json
|
||||
auth: inherit
|
||||
}
|
||||
|
||||
body:json {
|
||||
[
|
||||
{
|
||||
"id": 123,
|
||||
"date": "2025-12-22",
|
||||
"time": "14:30",
|
||||
"requirements": {
|
||||
"capacity": 0.75,
|
||||
"heating": true,
|
||||
"maxCost": 13.5
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
settings {
|
||||
encodeUrl: true
|
||||
timeout: 0
|
||||
}
|
||||
8
ilp-cw-api/[POST] queryAvailableDrones/folder.bru
Normal file
8
ilp-cw-api/[POST] queryAvailableDrones/folder.bru
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
meta {
|
||||
name: [POST] queryAvailableDrones
|
||||
seq: 5
|
||||
}
|
||||
|
||||
auth {
|
||||
mode: inherit
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import io.github.js0ny.ilp_coursework.data.AttrComparatorDto;
|
|||
import io.github.js0ny.ilp_coursework.data.DeliveryPathDto;
|
||||
import io.github.js0ny.ilp_coursework.data.DroneDto;
|
||||
import io.github.js0ny.ilp_coursework.data.DronePathDto;
|
||||
import io.github.js0ny.ilp_coursework.data.MedDispathRecDto;
|
||||
import io.github.js0ny.ilp_coursework.data.MedDispatchRecDto;
|
||||
import io.github.js0ny.ilp_coursework.service.DroneInfoService;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
|
@ -56,7 +56,7 @@ public class DroneController {
|
|||
*
|
||||
* @param id The id of the drone to be queried.
|
||||
* @return 200 with {@link DroneDto}-style json if success, 404 if {@code id}
|
||||
* not found, 400 otherwise
|
||||
* not found, 400 otherwise
|
||||
*/
|
||||
@GetMapping("/droneDetails/{id}")
|
||||
public ResponseEntity<DroneDto> getDroneDetail(@PathVariable String id) {
|
||||
|
|
@ -89,17 +89,17 @@ public class DroneController {
|
|||
}
|
||||
|
||||
@PostMapping("/queryAvailableDrones")
|
||||
public String[] queryAvailableDrones(@RequestBody MedDispathRecDto[] records) {
|
||||
public String[] queryAvailableDrones(@RequestBody MedDispatchRecDto[] records) {
|
||||
return droneService.dronesMatchesRequirements(records);
|
||||
}
|
||||
|
||||
@PostMapping("/calcDeliveryPath")
|
||||
public DeliveryPathDto calculateDeliveryPath(@RequestBody MedDispathRecDto[] record) {
|
||||
return new DeliveryPathDto(0.0f, 0, new DronePathDto[] {});
|
||||
public DeliveryPathDto calculateDeliveryPath(@RequestBody MedDispatchRecDto[] record) {
|
||||
return new DeliveryPathDto(0.0f, 0, new DronePathDto[]{});
|
||||
}
|
||||
|
||||
@PostMapping("/calcDeliveryPathAsGeoJson")
|
||||
public String calculateDeliveryPathAsGeoJson(@RequestBody MedDispathRecDto[] record) {
|
||||
public String calculateDeliveryPathAsGeoJson(@RequestBody MedDispatchRecDto[] record) {
|
||||
return "{}";
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,10 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalTime;
|
||||
|
||||
public record AvailabilityTimeSegDto(
|
||||
DayOfWeek dayOfWeek,
|
||||
LocalTime from,
|
||||
LocalTime until) {
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalTime;
|
||||
|
||||
public record DroneAvailabilityDto(
|
||||
String id,
|
||||
AvailabilityTimeSegDto[] availability) {
|
||||
|
||||
public boolean checkAvailability(DayOfWeek day, LocalTime time) {
|
||||
|
||||
for (var a : availability) {
|
||||
if (a.dayOfWeek().equals(day)) {
|
||||
if (!time.isBefore(a.from()) && !time.isAfter(a.until())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -3,9 +3,10 @@ package io.github.js0ny.ilp_coursework.data;
|
|||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
|
||||
public record MedDispathRecDto(
|
||||
public record MedDispatchRecDto(
|
||||
int id,
|
||||
LocalDate date,
|
||||
LocalTime time,
|
||||
MedRequirementDto requirements) {
|
||||
MedRequirementDto requirements,
|
||||
LngLatDto delivery) {
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
package io.github.js0ny.ilp_coursework.data;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
public record ServicePointDronesDto(
|
||||
String servicePointId,
|
||||
DroneAvailabilityDto[] drones) {
|
||||
|
||||
@Nullable
|
||||
public DroneAvailabilityDto locateDroneById(String droneId) {
|
||||
for (var drone : drones) {
|
||||
if (drone.id().equals(droneId)) {
|
||||
return drone;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
@ -2,12 +2,16 @@ package io.github.js0ny.ilp_coursework.service;
|
|||
|
||||
import io.github.js0ny.ilp_coursework.data.AttrComparatorDto;
|
||||
import io.github.js0ny.ilp_coursework.data.DroneDto;
|
||||
import io.github.js0ny.ilp_coursework.data.MedDispathRecDto;
|
||||
import io.github.js0ny.ilp_coursework.data.MedDispatchRecDto;
|
||||
import io.github.js0ny.ilp_coursework.data.ServicePointDronesDto;
|
||||
import io.github.js0ny.ilp_coursework.util.AttrOperator;
|
||||
|
||||
import static io.github.js0ny.ilp_coursework.util.AttrComparator.isValueMatched;
|
||||
|
||||
import java.net.URI;
|
||||
import java.time.DayOfWeek;
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalTime;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
|
@ -23,6 +27,7 @@ public class DroneInfoService {
|
|||
|
||||
private final String baseUrl;
|
||||
private final String dronesEndpoint = "drones";
|
||||
private final String dronesForServicePointsEndpoint = "drones-for-service-points";
|
||||
|
||||
private final RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
|
|
@ -49,7 +54,7 @@ public class DroneInfoService {
|
|||
*
|
||||
* @param state determines the capability filtering
|
||||
* @return if {@code state} is true, return ids of drones with cooling
|
||||
* capability, else without cooling
|
||||
* capability, else without cooling
|
||||
* @see io.github.js0ny.ilp_coursework.controller.DroneController#getDronesWithCoolingCapability(boolean)
|
||||
*/
|
||||
public String[] dronesWithCooling(boolean state) {
|
||||
|
|
@ -59,7 +64,7 @@ public class DroneInfoService {
|
|||
DroneDto[].class);
|
||||
|
||||
if (drones == null) {
|
||||
return new String[] {};
|
||||
return new String[]{};
|
||||
}
|
||||
|
||||
return Arrays.stream(drones)
|
||||
|
|
@ -141,7 +146,7 @@ public class DroneInfoService {
|
|||
}
|
||||
}
|
||||
if (matchingDroneIds == null) {
|
||||
return new String[] {};
|
||||
return new String[]{};
|
||||
}
|
||||
return matchingDroneIds.toArray(String[]::new);
|
||||
}
|
||||
|
|
@ -157,10 +162,10 @@ public class DroneInfoService {
|
|||
* @param attrVal the attribute value to filter on
|
||||
* @param op the comparison operator
|
||||
* @return array of drone ids matching the attribute name and value (filtered by
|
||||
* {@code op})
|
||||
* {@code op})
|
||||
* @see io.github.js0ny.ilp_coursework.util.AttrComparator#isValueMatched(JsonNode,
|
||||
* String,
|
||||
* AttrOperator)
|
||||
* String,
|
||||
* AttrOperator)
|
||||
*/
|
||||
private String[] dronesWithAttributeCompared(String attrName, String attrVal, AttrOperator op) {
|
||||
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
|
||||
|
|
@ -170,7 +175,7 @@ public class DroneInfoService {
|
|||
DroneDto[].class);
|
||||
|
||||
if (drones == null) {
|
||||
return new String[] {};
|
||||
return new String[]{};
|
||||
}
|
||||
|
||||
// Use Jackson's ObjectMapper to convert DroneDto to JsonNode for dynamic
|
||||
|
|
@ -192,7 +197,112 @@ public class DroneInfoService {
|
|||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
public String[] dronesMatchesRequirements(MedDispathRecDto[] rec) {
|
||||
return new String[] {};
|
||||
/**
|
||||
* Return an array of ids of drones that match all the requirements in the
|
||||
* medical dispatch records
|
||||
* <p>
|
||||
* Associated service method with
|
||||
*
|
||||
* @param rec array of medical dispatch records
|
||||
* @return array of drone ids that match all the requirements
|
||||
* @see io.github.js0ny.ilp_coursework.controller.DroneController#queryAvailableDrones
|
||||
*/
|
||||
public String[] dronesMatchesRequirements(MedDispatchRecDto[] rec) {
|
||||
URI droneUrl = URI.create(baseUrl).resolve(dronesEndpoint);
|
||||
DroneDto[] drones = restTemplate.getForObject(
|
||||
droneUrl,
|
||||
DroneDto[].class);
|
||||
|
||||
if (drones == null || rec == null || rec.length == 0) {
|
||||
return new String[]{};
|
||||
}
|
||||
|
||||
/*
|
||||
* Traverse and filter drones, pass every record's requirement to helper
|
||||
*/
|
||||
return Arrays.stream(drones)
|
||||
.filter(drone -> drone != null && drone.capability() != null)
|
||||
.filter(drone -> Arrays.stream(rec)
|
||||
.filter(record -> record != null && record.requirements() != null)
|
||||
// Every record must be met
|
||||
.allMatch(record -> meetsRequirement(drone, record)))
|
||||
.map(DroneDto::id)
|
||||
.toArray(String[]::new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to check if a drone meets the requirement of a medical dispatch.
|
||||
*
|
||||
* @param drone the drone to be checked
|
||||
* @param record the medical dispatch record containing the requirement
|
||||
* @return true if the drone meets the requirement, false otherwise
|
||||
* @throws IllegalArgumentException when record requirements or drone capability
|
||||
* is invalid (capacity and id cannot be null
|
||||
* in {@code MedDispathRecDto})
|
||||
*/
|
||||
private boolean meetsRequirement(DroneDto drone, MedDispatchRecDto record) {
|
||||
var requirements = record.requirements();
|
||||
if (requirements == null) {
|
||||
throw new IllegalArgumentException("requirements cannot be null");
|
||||
}
|
||||
var capability = drone.capability();
|
||||
if (capability == null) {
|
||||
throw new IllegalArgumentException("drone capability cannot be null");
|
||||
}
|
||||
|
||||
float requiredCapacity = requirements.capacity();
|
||||
if (requiredCapacity <= 0 || capability.capacity() < requiredCapacity) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use boolean wrapper to allow null (not specified) values
|
||||
Boolean requiredCooling = requirements.cooling();
|
||||
Boolean requiredHeating = requirements.heating();
|
||||
Float requiredMaxCost = requirements.maxCost();
|
||||
|
||||
boolean matchesCooling = requiredCooling == null || capability.cooling() == requiredCooling;
|
||||
boolean matchesHeating = requiredHeating == null || capability.heating() == requiredHeating;
|
||||
boolean matchesCost = false;
|
||||
|
||||
float totalCost = capability.costInitial() + capability.costFinal();
|
||||
|
||||
if (capability.maxMoves() > 0) {
|
||||
totalCost += capability.costPerMove();
|
||||
}
|
||||
matchesCost = totalCost <= requiredMaxCost;
|
||||
|
||||
// Conditions: All requirements matched + availability matched, use helper
|
||||
// For minimal privilege, only pass drone id to check availability
|
||||
return matchesCooling && matchesHeating && matchesCost && checkAvailability(drone.id(), record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper to check if a drone is available at the required date and time
|
||||
*
|
||||
* @param droneId the id of the drone to be checked
|
||||
* @param record the medical dispatch record containing the required date and
|
||||
* time
|
||||
* @return true if the drone is available, false otherwise
|
||||
*/
|
||||
private boolean checkAvailability(String droneId, MedDispatchRecDto record) {
|
||||
URI droneUrl = URI.create(baseUrl).resolve(dronesForServicePointsEndpoint);
|
||||
ServicePointDronesDto[] servicePoints = restTemplate.getForObject(
|
||||
droneUrl,
|
||||
ServicePointDronesDto[].class);
|
||||
|
||||
LocalDate requiredDate = record.date();
|
||||
DayOfWeek requiredDay = requiredDate.getDayOfWeek();
|
||||
LocalTime requiredTime = record.time();
|
||||
|
||||
for (var servicePoint : servicePoints) {
|
||||
var drone = servicePoint.locateDroneById(droneId); // Nullable
|
||||
if (drone != null) {
|
||||
return drone.checkAvailability(requiredDay, requiredTime);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue