Skip to content

Commit 5c2da25

Browse files
authored
Merge pull request #1585 from NASA-AMMOS/feat/proc-sched-external-events
Schedule Activities Based On External Events
2 parents 8118519 + b9055eb commit 5c2da25

File tree

24 files changed

+810
-9
lines changed

24 files changed

+810
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures;
2+
3+
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure;
4+
import gov.nasa.ammos.aerie.procedural.scheduling.Goal;
5+
import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan;
6+
import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart;
7+
import gov.nasa.ammos.aerie.procedural.timeline.plan.EventQuery;
8+
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
9+
import org.jetbrains.annotations.NotNull;
10+
11+
import java.util.Map;
12+
13+
@SchedulingProcedure
14+
public record ExternalEventsSimpleGoal() implements Goal {
15+
@Override
16+
public void run(@NotNull final EditablePlan plan) {
17+
EventQuery eventQuery = new EventQuery("TestGroup", null, null);
18+
19+
for (final var e: plan.events(eventQuery)) {
20+
plan.create("BiteBanana", new DirectiveStart.Absolute(e.getInterval().start), Map.of("biteSize", SerializedValue.of(1)));
21+
}
22+
plan.commit();
23+
}
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures;
2+
3+
import gov.nasa.ammos.aerie.procedural.scheduling.Goal;
4+
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure;
5+
import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan;
6+
import gov.nasa.ammos.aerie.procedural.timeline.payloads.ExternalSource;
7+
import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart;
8+
import gov.nasa.ammos.aerie.procedural.timeline.plan.EventQuery;
9+
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
10+
import org.jetbrains.annotations.NotNull;
11+
12+
import java.util.List;
13+
import java.util.Map;
14+
15+
@SchedulingProcedure
16+
public record ExternalEventsSourceQueryGoal() implements Goal {
17+
@Override
18+
public void run(@NotNull final EditablePlan plan) {
19+
20+
// extract events belonging to the second source
21+
EventQuery eventQuery = new EventQuery(
22+
null,
23+
null,
24+
List.of(new ExternalSource("NewTest.json", "TestGroup_2"))
25+
);
26+
27+
for (final var e: plan.events(eventQuery)) {
28+
// filter events that we schedule off of by key
29+
if (e.key.contains("01")) {
30+
plan.create(
31+
"BiteBanana",
32+
// place the directive such that it is coincident with the event's start
33+
new DirectiveStart.Absolute(e.getInterval().start),
34+
Map.of("biteSize", SerializedValue.of(1)));
35+
}
36+
}
37+
plan.commit();
38+
}
39+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package gov.nasa.jpl.aerie.e2e.procedural.scheduling.procedures;
2+
3+
import gov.nasa.ammos.aerie.procedural.scheduling.Goal;
4+
import gov.nasa.ammos.aerie.procedural.scheduling.annotations.SchedulingProcedure;
5+
import gov.nasa.ammos.aerie.procedural.scheduling.plan.EditablePlan;
6+
import gov.nasa.ammos.aerie.procedural.timeline.payloads.activities.DirectiveStart;
7+
import gov.nasa.ammos.aerie.procedural.timeline.plan.EventQuery;
8+
import gov.nasa.jpl.aerie.merlin.protocol.types.SerializedValue;
9+
import org.jetbrains.annotations.NotNull;
10+
11+
import java.util.List;
12+
import java.util.Map;
13+
14+
@SchedulingProcedure
15+
public record ExternalEventsTypeQueryGoal() implements Goal {
16+
@Override
17+
public void run(@NotNull final EditablePlan plan) {
18+
19+
// demonstrate more complicated query functionality
20+
EventQuery eventQuery = new EventQuery(
21+
List.of("TestGroup", "TestGroup_2"),
22+
List.of("TestType"),
23+
null
24+
);
25+
26+
for (final var e: plan.events(eventQuery)) {
27+
plan.create("BiteBanana", new DirectiveStart.Absolute(e.getInterval().start), Map.of("biteSize", SerializedValue.of(1)));
28+
}
29+
plan.commit();
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
package gov.nasa.jpl.aerie.e2e.procedural.scheduling;
2+
3+
import gov.nasa.jpl.aerie.e2e.types.GoalInvocationId;
4+
import gov.nasa.jpl.aerie.e2e.types.Plan;
5+
import gov.nasa.jpl.aerie.e2e.utils.GatewayRequests;
6+
import gov.nasa.jpl.aerie.e2e.utils.HasuraRequests;
7+
import gov.nasa.jpl.aerie.merlin.protocol.types.Duration;
8+
import org.junit.jupiter.api.AfterEach;
9+
import org.junit.jupiter.api.BeforeEach;
10+
import org.junit.jupiter.api.Test;
11+
12+
import java.io.IOException;
13+
import java.time.Instant;
14+
import java.util.ArrayList;
15+
import java.util.Comparator;
16+
import java.util.List;
17+
18+
import static org.junit.jupiter.api.Assertions.assertEquals;
19+
20+
public class ExternalEventsTests extends ProceduralSchedulingSetup {
21+
private GoalInvocationId procedureId;
22+
private final static String SOURCE_TYPE = "TestType";
23+
private final static String EVENT_TYPE = "TestType";
24+
private final static String ADDITIONAL_EVENT_TYPE = EVENT_TYPE + "_2";
25+
private final static String DERIVATION_GROUP = "TestGroup";
26+
private final static String ADDITIONAL_DERIVATION_GROUP = DERIVATION_GROUP + "_2";
27+
28+
private final HasuraRequests.ExternalSource externalSource = new HasuraRequests.ExternalSource(
29+
"Test.json",
30+
SOURCE_TYPE,
31+
DERIVATION_GROUP,
32+
"2024-01-01T00:00:00Z",
33+
"2023-01-01T00:00:00Z",
34+
"2023-01-08T00:00:00Z",
35+
"2024-10-01T00:00:00Z"
36+
);
37+
private final List<HasuraRequests.ExternalEvent> externalEvents = List.of(
38+
new HasuraRequests.ExternalEvent(
39+
"Event_01",
40+
EVENT_TYPE,
41+
externalSource.key(),
42+
externalSource.derivation_group_name(),
43+
"2023-01-01T01:00:00Z",
44+
"01:00:00"
45+
),
46+
new HasuraRequests.ExternalEvent(
47+
"Event_02",
48+
EVENT_TYPE,
49+
externalSource.key(),
50+
externalSource.derivation_group_name(),
51+
"2023-01-01T03:00:00Z",
52+
"01:00:00"
53+
),
54+
new HasuraRequests.ExternalEvent(
55+
"Event_03",
56+
EVENT_TYPE,
57+
externalSource.key(),
58+
externalSource.derivation_group_name(),
59+
"2023-01-01T05:00:00Z",
60+
"01:00:00"
61+
)
62+
);
63+
64+
private final HasuraRequests.ExternalSource additionalExternalSource = new HasuraRequests.ExternalSource(
65+
"NewTest.json",
66+
SOURCE_TYPE,
67+
ADDITIONAL_DERIVATION_GROUP,
68+
"2024-01-01T00:00:00Z",
69+
"2023-01-01T00:00:00Z",
70+
"2023-01-08T00:00:00Z",
71+
"2024-10-01T00:00:00Z"
72+
);
73+
74+
private final List<HasuraRequests.ExternalEvent> additionalExternalEvents = List.of(
75+
new HasuraRequests.ExternalEvent(
76+
"Event_01",
77+
EVENT_TYPE,
78+
additionalExternalSource.key(),
79+
additionalExternalSource.derivation_group_name(),
80+
"2023-01-02T01:00:00Z",
81+
"01:00:00"
82+
),
83+
new HasuraRequests.ExternalEvent(
84+
"Event_02",
85+
ADDITIONAL_EVENT_TYPE,
86+
additionalExternalSource.key(),
87+
additionalExternalSource.derivation_group_name(),
88+
"2023-01-02T03:00:00Z",
89+
"01:00:00"
90+
),
91+
new HasuraRequests.ExternalEvent(
92+
"Event_03",
93+
ADDITIONAL_EVENT_TYPE,
94+
additionalExternalSource.key(),
95+
additionalExternalSource.derivation_group_name(),
96+
"2023-01-02T05:00:00Z",
97+
"01:00:00"
98+
)
99+
);
100+
101+
@BeforeEach
102+
void localBeforeEach() throws IOException {
103+
// Upload some External Events (and associated infrastructure)
104+
hasura.insertExternalSourceType(SOURCE_TYPE);
105+
hasura.insertExternalEventType(EVENT_TYPE);
106+
hasura.insertDerivationGroup(DERIVATION_GROUP, SOURCE_TYPE);
107+
hasura.insertExternalSource(externalSource);
108+
hasura.insertExternalEvents(externalEvents);
109+
hasura.insertPlanDerivationGroupAssociation(planId, DERIVATION_GROUP);
110+
111+
// Upload additional External Events in a different derivation group and of a different type
112+
hasura.insertExternalEventType(ADDITIONAL_EVENT_TYPE);
113+
hasura.insertDerivationGroup(ADDITIONAL_DERIVATION_GROUP, SOURCE_TYPE);
114+
hasura.insertExternalSource(additionalExternalSource);
115+
hasura.insertExternalEvents(additionalExternalEvents);
116+
hasura.insertPlanDerivationGroupAssociation(planId, ADDITIONAL_DERIVATION_GROUP);
117+
}
118+
119+
@AfterEach
120+
void localAfterEach() throws IOException {
121+
hasura.deleteSchedulingGoal(procedureId.goalId());
122+
123+
// External Event Related
124+
hasura.deletePlanDerivationGroupAssociation(planId, DERIVATION_GROUP);
125+
hasura.deletePlanDerivationGroupAssociation(planId, ADDITIONAL_DERIVATION_GROUP);
126+
hasura.deleteExternalSource(externalSource);
127+
hasura.deleteExternalSource(additionalExternalSource);
128+
hasura.deleteDerivationGroup(DERIVATION_GROUP);
129+
hasura.deleteDerivationGroup(ADDITIONAL_DERIVATION_GROUP);
130+
hasura.deleteExternalSourceType(SOURCE_TYPE);
131+
hasura.deleteExternalEventType(EVENT_TYPE);
132+
hasura.deleteExternalEventType(ADDITIONAL_EVENT_TYPE);
133+
}
134+
135+
@Test
136+
void testExternalEventSimple() throws IOException {
137+
// first, run the goal
138+
try (final var gateway = new GatewayRequests(playwright)) {
139+
int procedureJarId = gateway.uploadJarFile("build/libs/ExternalEventsSimpleGoal.jar");
140+
// Add Scheduling Procedure
141+
procedureId = hasura.createSchedulingSpecProcedure(
142+
"Test Scheduling Procedure",
143+
procedureJarId,
144+
specId,
145+
0
146+
);
147+
}
148+
hasura.awaitScheduling(specId);
149+
final var plan = hasura.getPlan(planId);
150+
final var activities = plan.activityDirectives();
151+
152+
// ensure the order lines up with the events'
153+
activities.sort(Comparator.comparing(Plan.ActivityDirective::startOffset));
154+
155+
// compare arrays
156+
assertEquals(externalEvents.size(), activities.size());
157+
for (int i = 0; i < activities.size(); i++) {
158+
Instant activityStartTime = Duration.addToInstant(
159+
Instant.parse(planStartTimestamp),
160+
Duration.fromString(activities.get(i).startOffset())
161+
);
162+
assertEquals(externalEvents.get(i).start_time(), activityStartTime.toString());
163+
}
164+
}
165+
166+
@Test
167+
void testExternalEventTypeQuery() throws IOException {
168+
// first, run the goal
169+
try (final var gateway = new GatewayRequests(playwright)) {
170+
int procedureJarId = gateway.uploadJarFile("build/libs/ExternalEventsTypeQueryGoal.jar");
171+
// Add Scheduling Procedure
172+
procedureId = hasura.createSchedulingSpecProcedure(
173+
"Test Scheduling Procedure",
174+
procedureJarId,
175+
specId,
176+
0
177+
);
178+
}
179+
hasura.awaitScheduling(specId);
180+
final var plan = hasura.getPlan(planId);
181+
final var activities = plan.activityDirectives();
182+
183+
// ensure the orderings line up
184+
activities.sort(Comparator.comparing(Plan.ActivityDirective::startOffset));
185+
186+
// get the set of events we expect (anything in TestGroup or TestGroup_2, and of type TestType)
187+
List<HasuraRequests.ExternalEvent> expected = new ArrayList<>();
188+
expected.addAll(externalEvents);
189+
expected.addAll(
190+
additionalExternalEvents.stream()
191+
.filter(e -> e.event_type_name().equals(EVENT_TYPE))
192+
.toList()
193+
);
194+
195+
// explicitly ensure the orderings line up
196+
expected.sort(Comparator.comparing(HasuraRequests.ExternalEvent::start_time));
197+
198+
// compare arrays
199+
assertEquals(expected.size(), activities.size());
200+
for (int i = 0; i < activities.size(); i++) {
201+
Instant activityStartTime = Duration.addToInstant(
202+
Instant.parse(planStartTimestamp),
203+
Duration.fromString(activities.get(i).startOffset())
204+
);
205+
assertEquals(activityStartTime.toString(), expected.get(i).start_time());
206+
}
207+
}
208+
209+
@Test
210+
void testExternalEventSourceQuery() throws IOException {
211+
// first, run the goal
212+
try (final var gateway = new GatewayRequests(playwright)) {
213+
int procedureJarId = gateway.uploadJarFile("build/libs/ExternalEventsSourceQueryGoal.jar");
214+
// Add Scheduling Procedure
215+
procedureId = hasura.createSchedulingSpecProcedure(
216+
"Test Scheduling Procedure",
217+
procedureJarId,
218+
specId,
219+
0
220+
);
221+
}
222+
hasura.awaitScheduling(specId);
223+
final var plan = hasura.getPlan(planId);
224+
final var activities = plan.activityDirectives();
225+
226+
// only 1 activity this time
227+
assertEquals(1, activities.size());
228+
Instant activityStartTime = Duration.addToInstant(
229+
Instant.parse(planStartTimestamp),
230+
Duration.fromString(activities.get(0).startOffset())
231+
);
232+
assertEquals(activityStartTime.toString(), additionalExternalEvents.get(0).start_time());
233+
}
234+
}

0 commit comments

Comments
 (0)