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.DeliveryPathDto;
|
||||||
import io.github.js0ny.ilp_coursework.data.DroneDto;
|
import io.github.js0ny.ilp_coursework.data.DroneDto;
|
||||||
import io.github.js0ny.ilp_coursework.data.DronePathDto;
|
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 io.github.js0ny.ilp_coursework.service.DroneInfoService;
|
||||||
import org.springframework.http.ResponseEntity;
|
import org.springframework.http.ResponseEntity;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
@ -89,17 +89,17 @@ public class DroneController {
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/queryAvailableDrones")
|
@PostMapping("/queryAvailableDrones")
|
||||||
public String[] queryAvailableDrones(@RequestBody MedDispathRecDto[] records) {
|
public String[] queryAvailableDrones(@RequestBody MedDispatchRecDto[] records) {
|
||||||
return droneService.dronesMatchesRequirements(records);
|
return droneService.dronesMatchesRequirements(records);
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/calcDeliveryPath")
|
@PostMapping("/calcDeliveryPath")
|
||||||
public DeliveryPathDto calculateDeliveryPath(@RequestBody MedDispathRecDto[] record) {
|
public DeliveryPathDto calculateDeliveryPath(@RequestBody MedDispatchRecDto[] record) {
|
||||||
return new DeliveryPathDto(0.0f, 0, new DronePathDto[]{});
|
return new DeliveryPathDto(0.0f, 0, new DronePathDto[]{});
|
||||||
}
|
}
|
||||||
|
|
||||||
@PostMapping("/calcDeliveryPathAsGeoJson")
|
@PostMapping("/calcDeliveryPathAsGeoJson")
|
||||||
public String calculateDeliveryPathAsGeoJson(@RequestBody MedDispathRecDto[] record) {
|
public String calculateDeliveryPathAsGeoJson(@RequestBody MedDispatchRecDto[] record) {
|
||||||
return "{}";
|
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.LocalDate;
|
||||||
import java.time.LocalTime;
|
import java.time.LocalTime;
|
||||||
|
|
||||||
public record MedDispathRecDto(
|
public record MedDispatchRecDto(
|
||||||
int id,
|
int id,
|
||||||
LocalDate date,
|
LocalDate date,
|
||||||
LocalTime time,
|
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.AttrComparatorDto;
|
||||||
import io.github.js0ny.ilp_coursework.data.DroneDto;
|
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 io.github.js0ny.ilp_coursework.util.AttrOperator;
|
||||||
|
|
||||||
import static io.github.js0ny.ilp_coursework.util.AttrComparator.isValueMatched;
|
import static io.github.js0ny.ilp_coursework.util.AttrComparator.isValueMatched;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
|
import java.time.DayOfWeek;
|
||||||
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalTime;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
@ -23,6 +27,7 @@ public class DroneInfoService {
|
||||||
|
|
||||||
private final String baseUrl;
|
private final String baseUrl;
|
||||||
private final String dronesEndpoint = "drones";
|
private final String dronesEndpoint = "drones";
|
||||||
|
private final String dronesForServicePointsEndpoint = "drones-for-service-points";
|
||||||
|
|
||||||
private final RestTemplate restTemplate = new RestTemplate();
|
private final RestTemplate restTemplate = new RestTemplate();
|
||||||
|
|
||||||
|
|
@ -192,7 +197,112 @@ public class DroneInfoService {
|
||||||
.toArray(String[]::new);
|
.toArray(String[]::new);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String[] dronesMatchesRequirements(MedDispathRecDto[] rec) {
|
/**
|
||||||
|
* 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[]{};
|
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