Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
/* (C)2023 */
package org.transitclock.api.reports;

import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;

import jakarta.persistence.criteria.*;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.hibernate.Session;
import org.hibernate.query.Query;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.transitclock.domain.hibernate.HibernateUtils;
import org.transitclock.domain.structs.ArrivalDeparture;
import org.transitclock.utils.Time;

import java.math.RoundingMode;
import java.text.DecimalFormat;
import java.time.Duration;
import java.util.*;
import java.util.stream.Collectors;

@Component
@Slf4j
@Component
@RequiredArgsConstructor
public class ScheduleAdherenceController {
// TODO: Combine routeScheduleAdherence and stopScheduleAdherence
// - Make this a REST endpoint
Expand All @@ -29,11 +37,10 @@ public class ScheduleAdherenceController {
@Value("${transitclock.web.userPredictionLimits:false}")
private Boolean usePredictionLimits;

// private static final String ADHERENCE_SQL = "(time - scheduledTime) AS scheduleAdherence";
// private static final Projection ADHERENCE_PROJECTION = Projections.sqlProjection(
// ADHERENCE_SQL, new String[] {"scheduleAdherence"}, new Type[] {DoubleType.INSTANCE});
// private static final Projection AVG_ADHERENCE_PROJECTION = Projections.sqlProjection(
// "avg" + ADHERENCE_SQL, new String[] {"scheduleAdherence"}, new Type[] {DoubleType.INSTANCE});
// TODO: Combine routeScheduleAdherence and stopScheduleAdherence
// - Make this a REST endpoint
// problem - negative schedule adherence means we're late


public List<Object> stopScheduleAdherence(
Date startDate,
Expand All @@ -59,128 +66,155 @@ public List<Object> routeScheduleAdherence(
return groupScheduleAdherence(startDate, numDays, startTime, endTime, "routeId", routeIds, byRoute, datatype);
}

public List<Integer> routeScheduleAdherenceSummary(
Date startDate,
int numDays,
String startTime,
String endTime,
Double earlyLimitParam,
Double lateLimitParam,
List<String> routeIds) {
public Map<String, String> routeScheduleAdherenceSummary(Date startDate,
int numDays,
String startTime,
String endTime,
Double earlyLimitParam,
Double lateLimitParam,
List<String> routeIds) {

int count = 0;
int early = 0;
int late = 0;
int ontime = 0;

Double earlyLimit =
(usePredictionLimits ? earlyLimitParam : (double) scheduleEarlySeconds);
Double lateLimit = (usePredictionLimits ? lateLimitParam : (double) scheduleLateSeconds);

List<Object> results = routeScheduleAdherence(startDate, numDays, startTime, endTime, routeIds, false, null);
Map<String, String> result = new HashMap<>();

for (Object o : results) {
count++;
HashMap hm = (HashMap) o;
Double d = (Double) hm.get("scheduleAdherence");
if (d > lateLimit) {

var hm = (HashMap) o;
Duration d = (Duration) hm.get("scheduleAdherence");
double totalSeconds = d.toMillis() / 1000.0;

if (totalSeconds > lateLimit) {
late++;
} else if (d < earlyLimit) {
} else if (totalSeconds < earlyLimit) {
early++;
} else {
ontime++;
}
}
logger.info(
"query complete -- earlyLimit={}, lateLimit={}, early={}, ontime={}, late={}," + " count={}",
logger.info("query complete -- earlyLimit={}, lateLimit={}, early={}, onTime={}, late={}," + " count={}",
earlyLimit,
lateLimit,
early,
ontime,
late,
count);

double earlyPercent = (1.0 - (double) (count - early) / count) * 100;
double onTimePercent = (1.0 - (double) (count - ontime) / count) * 100;
double latePercent = (1.0 - (double) (count - late) / count) * 100;
logger.info(
"count={} earlyPercent={} onTimePercent={} latePercent={}",
logger.info("count=static{} earlyPercent={} onTimePercent={} latePercent={}",
count,
earlyPercent,
onTimePercent,
latePercent);
Integer[] summary = new Integer[] {count, (int) earlyPercent, (int) onTimePercent, (int) latePercent};
return Arrays.asList(summary);
DecimalFormat df = new DecimalFormat("#.##");
df.setRoundingMode(RoundingMode.DOWN);

result.put("count", String.valueOf(count));
result.put("early", df.format(earlyPercent));
result.put("late", df.format(latePercent));
result.put("onTime", df.format(onTimePercent));
return result;
}

private List<Object> groupScheduleAdherence(
Date startDate,
int numDays,
String startTime,
String endTime,
String groupName,
List<String> idsOrEmpty,
boolean byGroup,
String datatype) {
/*
private List<Object> groupScheduleAdherence(Date startDate,
int numDays,
String startTime,
String endTime,
String groupName,
List<String> idsOrEmpty,
boolean byGroup,
String datatype) {

var qentity = QArrivalDeparture.arrivalDeparture;
Session session = HibernateUtils.getSession();
JPAQuery<ArrivalDeparture> query = new JPAQuery<>(session);

// filter ids which may be empty.
List<String> ids = new ArrayList<>();
if (idsOrEmpty != null)
for (String id : idsOrEmpty)
if (idsOrEmpty != null) {
for (String id : idsOrEmpty) {
if (!StringUtils.isBlank(id)) {
ids.add(id);
}
}
}

// Calculate end date based on start date and numDays
Date endDate = new Date(startDate.getTime() + (numDays * Time.MS_PER_DAY));

ProjectionList proj = Projections.projectionList();

if (byGroup)
proj.add(Projections.groupProperty(groupName), groupName)
.add(Projections.rowCount(), "count");
else
proj.add(Projections.property("routeId"), "routeId")
.add(Projections.property("stopId"), "stopId")
.add(Projections.property("tripId"), "tripId");

proj.add(byGroup ? AVG_ADHERENCE_PROJECTION : ADHERENCE_PROJECTION, "scheduleAdherence");

DetachedCriteria criteria = DetachedCriteria.forClass(ArrivalDeparture.class)
.add(Restrictions.between("time", startDate, endDate))
.add(Restrictions.isNotNull("scheduledTime"));
try (Session session = HibernateUtils.getSession()) {
// Create Query
CriteriaBuilder cb = session.getCriteriaBuilder();
CriteriaQuery<Object[]> query = cb.createQuery(Object[].class);
Root<ArrivalDeparture> root = query.from(ArrivalDeparture.class);

// Building predicates
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.between(root.get("time"), startDate, endDate));
predicates.add(cb.isNotNull(root.get("scheduledTime")));

// Check if we're dealing with 'arrival' or 'departure'
if ("arrival".equals(datatype)) {
predicates.add(cb.isTrue(root.get("isArrival")));
} else if ("departure".equals(datatype)) {
predicates.add(cb.isFalse(root.get("isArrival")));
}

if ("arrival".equals(datatype)) criteria.add(Restrictions.eq("isArrival", true));
else if ("departure".equals(datatype)) criteria.add(Restrictions.eq("isArrival", false));
Expression<String> timePartExpr = cb.function("TO_CHAR", String.class, root.get("time"), cb.literal("HH24:MI:SS"));
predicates.add(cb.greaterThanOrEqualTo(timePartExpr, startTime));
predicates.add(cb.lessThanOrEqualTo(timePartExpr, endTime));

String sql = "time({alias}.time) between ? and ?";
String[] values = {startTime, endTime};
Type[] types = {StringType.INSTANCE, StringType.INSTANCE};
criteria.add(Restrictions.sqlRestriction(sql, values, types));
if (!ids.isEmpty()) {
predicates.add(root.get(groupName).in(ids));
}

criteria.setProjection(proj).setResultTransformer(DetachedCriteria.ALIAS_TO_ENTITY_MAP);
query.where(predicates.toArray(new Predicate[0]));

if (ids != null && ids.size() > 0) criteria.add(Restrictions.in(groupName, ids));
*/
return Collections.emptyList();
}
// Grouping logic based on byGroup flag
if (byGroup) {
query.multiselect(
root.get(groupName),
cb.count(root),
cb.avg(cb.diff(root.get("time"), root.get("scheduledTime")))
).groupBy(root.get(groupName));
} else {
query.multiselect(
root.get("routeId"),
root.get("stopId"),
root.get("tripId"),
cb.diff(root.get("time"), root.get("scheduledTime"))
);
}

private Date endOfDay(Date endDate) {
Calendar c = Calendar.getInstance();
c.setTime(endDate);
c.set(Calendar.HOUR, 23);
c.set(Calendar.MINUTE, 59);
c.set(Calendar.SECOND, 59);
return c.getTime();
Query<Object[]> hibernateQuery = session.createQuery(query);
List<Object[]> results = hibernateQuery.getResultList();
// Get result
return results.stream()
.map(result -> {
HashMap<String, Object> map = new HashMap<>();
if (byGroup) {
map.put(groupName, result[0]);
map.put("count", result[1]);
map.put("scheduleAdherence", result[2]);
} else {
map.put("routeId", result[0]);
map.put("stopId", result[1]);
map.put("tripId", result[2]);
map.put("scheduleAdherence", result[3]);
}
return map;
})
.collect(Collectors.toList());

} catch (Exception esqEx) {
esqEx.printStackTrace();
}
return List.of();
}
//
// private static List<Object> dbify(DetachedCriteria criteria) {
// Session session = HibernateUtils.getSession();
// try {
// return criteria.getExecutableCriteria(session).list();
// } finally {
// session.close();
// }
// }
}
Original file line number Diff line number Diff line change
Expand Up @@ -171,7 +171,7 @@ ResponseEntity<ApiCommandAck> pushAvlData(
@Operation(
summary = "Cancel a trip in order to be shown in GTFS realtime.",
description = "<font color=\"#FF0000\">Experimental. It will work olny with the correct"
+ " version.</font> It cancel a trip that has no vechilce assigned.",
+ " version.</font> It cancel a trip that has no vehicle assigned.",
tags = {"command", "trip"})
ResponseEntity<ApiCommandAck> cancelTrip(
StandardParameters stdParameters,
Expand All @@ -190,7 +190,7 @@ ResponseEntity<ApiCommandAck> cancelTrip(
tags = {"command", "trip"})
ResponseEntity<ApiCommandAck> reenableTrip(
StandardParameters stdParameters,
@Parameter(description = "tripId to remove calceled satate.", required = true) @PathVariable("tripId")
@Parameter(description = "tripId to remove canceled satate.", required = true) @PathVariable("tripId")
String tripId,
@Parameter(description = "start trip time", required = false) @RequestParam(value = "at", required = false) DateTimeParam at);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import java.sql.SQLException;
import java.text.ParseException;
import java.util.List;
import java.util.Map;

import org.transitclock.api.utils.StandardParameters;

Expand Down Expand Up @@ -42,11 +43,12 @@ ResponseEntity<String> getTripsWithTravelTimes(
summary = "Returns avl report.",
description = "Returns avl report.",
tags = {"report", "vehicle"})

@GetMapping(value = "/reports/avlReport",
produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
ResponseEntity<String> getAvlReport(
StandardParameters stdParameters,
@Parameter(description = "Vehicle id") @RequestParam(value = "v") String vehicleId,
@Parameter(description = "Vehicle id") @RequestParam(value = "v", required = false) String vehicleId,
@Parameter(description = "Begin date(MM-DD-YYYY or YYYY-MM-DD") @RequestParam(value = "beginDate") String beginDate,
@Parameter(description = "Num days.", required = false) @RequestParam(value = "numDays", defaultValue = "1", required = false) int numDays,
@Parameter(description = "Begin time(HH:MM)", required = false) @RequestParam(value = "beginTime", required = false) String beginTime,
Expand Down Expand Up @@ -121,7 +123,7 @@ ResponseEntity<String> scheduleAdhReport(
produces = {MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE})
ResponseEntity<String> reportForStopById(
StandardParameters stdParameters,
@Parameter(description = "Stop id") @RequestParam(value = "stopId") String stopId,
@Parameter(description = "Stop id") @RequestParam(value = "id") String stopId,
@Parameter(description = "Begin date(MM-DD-YYYY or YYYY-MM-DD") @RequestParam(value = "beginDate") String beginDate,
@Parameter(description = "Num days.") @RequestParam(value = "numDays", defaultValue = "1", required = false) int numDays,
@Parameter(description = "Begin time(HH:MM)") @RequestParam(value = "beginTime", required = false) String beginTime,
Expand All @@ -146,7 +148,7 @@ ResponseEntity<String> reportForStopById(
ResponseEntity<String> predAccuracyRangeData(HttpServletRequest request) throws SQLException, ParseException;

@GetMapping(value = "/reports/data/summaryScheduleAdherence.jsp")
ResponseEntity<List<Integer>> summaryScheduleAdherence(HttpServletRequest request) throws ParseException;
ResponseEntity<Map<String, String>> summaryScheduleAdherence(HttpServletRequest request) throws ParseException;

@GetMapping(value = "/reports/predAccuracyScatterData.jsp")
ResponseEntity<String> predAccuracyScatterData(HttpServletRequest request) throws ParseException, SQLException;
Expand Down
Loading
Loading