Repository: nasa-ammos/aerie
Issue: Cannot use ActivityPresets with Enum Values in Scheduling EDSL

### Checked for duplicates

Yes - I've already checked

### Is this a regression?

No - This is a new bug

### Version

develop

### Describe the bug

Goals that use ActivityPresets to define the `activityTemplate` or `activityFinder` fields fail to compile with a type error if the ActivityType of the preset has an enum parameter.

Example Error:
```json
[{"errors":
  [{"location":
    {"column":52,"line":5},
    "message":"TypeError: TS2345 Argument of type '{ peelDirection: string; }' is not assignable to parameter of type 'ParameterTypePeelBanana'.\\n  Types of property 'peelDirection' are incompatible.\\n    Type 'string' is not assignable to type '\\"fromStem\\" | \\"fromTip\\" | Discrete<\\"fromStem\\" | \\"fromTip\\">'.","stack":"at (5:52)"}],"goal_id":3}]
```

This may potentially also occur with `activityFinder`.

### Reproduction

1. Spin up Aerie.
2. Upload a BananaNation model and create a plan from it.
3. Create a preset for PeelBanana.
4. Create the following scheduling goal for the plan:
```typescript
export default (): Goal => {
  return Goal.CoexistenceGoal({
    forEach: Real.Resource("/peel").greaterThan(3),
    activityFinder: ActivityExpression.build(ActivityTypes.PeelBanana, ActivityPresets.PeelBanana["FromTip"]),
    activityTemplate: ActivityTemplates.PeelBanana(ActivityPresets.PeelBanana["FromTip"]),
    startsAt: TimingConstraint.singleton(WindowProperty.START).plus(Temporal.Duration.from({ minutes: 5 }))
  });
}
```
5. Attempt to run simulation. 

### Logs

_No response_

### System Info

```shell
Firefox
M1 Mac
```


### Severity

Moderate

Changed files:
scheduler-server/src/test/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationServiceTest.java
scheduler-server/src/testFixtures/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationServiceTestFixtures.java
scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationService.java
scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java

diff --git a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationService.java b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationService.java
index 52f4ac042..12602b164 100644
--- a/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationService.java
+++ b/scheduler-server/src/main/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationService.java
@@ -130,7 +130,7 @@ return (<T>makeAllDiscreteProfile(args))
     for (final var activityTypeCode : activityTypeCodes) {
       result.add(indent("%s: Object.freeze({".formatted(activityTypeCode.activityTypeName)));
       for (final var preset: activityTypeCode.presets.entrySet()) {
-        result.add(indent(indent("get \\"%s\\"(): %s {".formatted(preset.getKey(), serializedValueToType(SerializedValue.of(preset.getValue()))))));
+        result.add(indent(indent("get \\"%s\\"(): %s {".formatted(preset.getKey(), TypescriptType.toString(activityTypeCode.getArgumentsType(), false)))));
         result.add(indent(indent(indent("return {"))));
         for (final var argument: preset.getValue().entrySet()) {
           final var deserializedJson = serializedValueP.unparse(argument.getValue());
@@ -153,7 +153,13 @@ return (<T>makeAllDiscreteProfile(args))
     return joinLines(s.lines().map(line -> "  " + line).toList());
   }
 
-  private record ActivityTypeCode(String activityTypeName, List<ActivityParameter> parameterTypes, Map<String, Map<String, SerializedValue>> presets) {}
+  private record ActivityTypeCode(String activityTypeName, List<ActivityParameter> parameterTypes, Map<String, Map<String, SerializedValue>> presets) {
+    public TypescriptType getArgumentsType() {
+      return new TypescriptType.TSStruct(
+          parameterTypes.stream().map($ -> Pair.of($.name(), $.type())).toList()
+      );
+    }
+  }
 
   private record ActivityParameter(String name, TypescriptType type) {}
 
@@ -170,7 +176,7 @@ return (<T>makeAllDiscreteProfile(args))
     /**
      * Print this type out in one line.
      */
-    static String toString(final TypescriptType type) {
+    static String toString(final TypescriptType type, boolean topLevelStructIsProfile) {
       if (type instanceof TSString) {
         return "(string | Discrete<string>)";
       } else if (type instanceof TSDouble) {
@@ -183,16 +189,24 @@ return (<T>makeAllDiscreteProfile(args))
       } else if (type instanceof TSDuration) {
         return "(Temporal.Duration | Discrete<Temporal.Duration>)";
       } else if (type instanceof TSArray t) {
-        final var typeStr = toString(t.elementType());
+        final var typeStr = toString(t.elementType(), true);
         return "((" +typeStr+ ")[] | Discrete<("+typeStr+")[]>)";
       } else if (type instanceof TSStruct t) {
 
-        var typeStr = "{ ";
-        for(final var keyAndType :  t.keysAndTypes()){
-          typeStr += keyAndType.getLeft() + ":"+ toString(keyAndType.getRight())+",";
+        var typeStr = new StringBuilder("{ ");
+        for (final var keyAndType :  t.keysAndTypes()) {
+          typeStr
+              .append(keyAndType.getLeft())
+              .append(":")
+              .append(toString(keyAndType.getRight(), true))
+              .append(",");
+        }
+        typeStr.append("}");
+        if (topLevelStructIsProfile) {
+          return "(%s | Discrete<%s>)".formatted(typeStr, typeStr);
+        } else {
+          return typeStr.toString();
         }
-        typeStr+="}";
-        return "(%s | Discrete<%s>)".formatted(typeStr, typeStr);
       } else if (type instanceof TSEnum t) {
         final var typeStr = "(" + String.join(" | ", t.values().stream().map(x -> "\\"" + x + "\\"").toList()) + ")";
         return "(%s | Discrete<%s>)".formatted(typeStr, typeStr);
@@ -212,11 +226,11 @@ return (<T>makeAllDiscreteProfile(args))
         .entrySet()
         .stream()
         .sorted(Map.Entry.comparingByKey())
-        .map($ -> new ActivityParameter($.getKey(), valueSchemaToTypescriptTypeOrProfile($.getValue())))
+        .map($ -> new ActivityParameter($.getKey(), valueSchemaToTypescriptType($.getValue())))
         .toList();
   }
 
-  private static TypescriptType valueSchemaToTypescriptTypeOrProfile(final ValueSchema valueSchema) {
+  private static TypescriptType valueSchemaToTypescriptType(final ValueSchema valueSchema) {
     return valueSchema.match(new ValueSchema.Visitor<>() {
       @Override
       public TypescriptType onReal() {
@@ -250,7 +264,7 @@ return (<T>makeAllDiscreteProfile(args))
 
       @Override
       public TypescriptType onSeries(final ValueSchema value) {
-        return new TypescriptType.TSArray(valueSchemaToTypescriptTypeOrProfile(value));
+        return new TypescriptType.TSArray(valueSchemaToTypescriptType(value));
       }
 
       @Override
@@ -263,7 +277,7 @@ return (<T>makeAllDiscreteProfile(args))
                 .map($ ->
                          Pair.of(
                              $.getKey(),
-                             valueSchemaToTypescriptTypeOrProfile($.getValue())))
+                             valueSchemaToTypescriptType($.getValue())))
                 .toList());
       }
 
diff --git a/scheduler-server/src/test/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationServiceTest.java b/scheduler-server/src/test/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationServiceTest.java
index 6380da870..28242ddf9 100644
--- a/scheduler-server/src/test/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationServiceTest.java
+++ b/scheduler-server/src/test/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationServiceTest.java
@@ -18,6 +18,7 @@ import type { Windows } from './constraints-edsl-fluent-api.js';
 import type * as ConstraintEDSL from './constraints-edsl-fluent-api.js'
 interface SampleActivity1 extends ActivityTemplate<ActivityType.SampleActivity1> {}
 interface SampleActivity2 extends ActivityTemplate<ActivityType.SampleActivity2> {}
+interface SampleActivity3 extends ActivityTemplate<ActivityType.SampleActivity3> {}
 interface SampleActivityEmpty extends ActivityTemplate<ActivityType.SampleActivityEmpty> {}
 export function makeAllDiscreteProfile (argument: any) : any{
   if (argument === undefined){
@@ -68,6 +69,11 @@ const ActivityTemplateConstructors = {
   // @ts-ignore
     return { activityType: ActivityType.SampleActivity2, args: makeArgumentsDiscreteProfiles(args) };
   },
+  SampleActivity3: function SampleActivity3Constructor(args:  ConstraintEDSL.Gen.ActivityTypeParameterMap[ActivityType.SampleActivity3]
+  ): SampleActivity3 {
+  // @ts-ignore
+    return { activityType: ActivityType.SampleActivity3, args: makeArgumentsDiscreteProfiles(args) };
+  },
   SampleActivityEmpty: function SampleActivityEmptyConstructor(): SampleActivityEmpty {
     return { activityType: ActivityType.SampleActivityEmpty, args: {} };
   },
@@ -76,14 +82,19 @@ const ActivityPresetMap = Object.freeze({
   SampleActivity1: Object.freeze({
   }),
   SampleActivity2: Object.freeze({
-    get "my preset"(): {
-      "quantity": number,
-    } {
+    get "my preset"(): { quantity:(number | Real),} {
       return {
         "quantity": 5,
       };
     },
   }),
+  SampleActivity3: Object.freeze({
+    get "my preset"(): { variant:(("option1" | "option2") | Discrete<("option1" | "option2")>),} {
+      return {
+        "variant": "option1",
+      };
+    },
+  }),
   SampleActivityEmpty: Object.freeze({
   }),
 });
diff --git a/scheduler-server/src/testFixtures/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationServiceTestFixtures.java b/scheduler-server/src/testFixtures/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationServiceTestFixtures.java
index 7082a45f4..659b20921 100644
--- a/scheduler-server/src/testFixtures/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationServiceTestFixtures.java
+++ b/scheduler-server/src/testFixtures/java/gov/nasa/jpl/aerie/scheduler/server/services/TypescriptCodeGenerationServiceTestFixtures.java
@@ -46,6 +46,20 @@ public final class TypescriptCodeGenerationServiceTestFixtures {
                       Map.of("quantity", SerializedValue.of(5))
                   )
               ),
+              new MissionModelService.ActivityType(
+                  "SampleActivity3",
+                  Map.of(
+                      "variant",
+                      ValueSchema.ofVariant(List.of(
+                        new ValueSchema.Variant("option1", "option1"),
+                        new ValueSchema.Variant("option2", "option2")
+                      ))
+                  ),
+                  Map.of(
+                      "my preset",
+                      Map.of("variant", SerializedValue.of("option1"))
+                  )
+              ),
               new MissionModelService.ActivityType(
                   "SampleActivityEmpty",
                   Map.of(),
diff --git a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java
index 92237ffad..8ac852ed8 100644
--- a/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java
+++ b/scheduler-worker/src/test/java/gov/nasa/jpl/aerie/scheduler/worker/services/SchedulingDSLCompilationServiceTests.java
@@ -153,6 +153,12 @@ class SchedulingDSLCompilationServiceTests {
     ));
   }
 
+  private static StructExpressionAt getSampleActivity3PresetParameters() {
+    return new StructExpressionAt(Map.ofEntries(
+       Map.entry("variant", new ProfileExpression<>(new DiscreteValue(SerializedValue.of("option1"))))
+    ));
+  }
+
   @Test
   void  testSchedulingDSL_basic()
   {
@@ -1005,4 +1011,31 @@ class SchedulingDSLCompilationServiceTests {
     }
   }
 
+  @Test
+  void testPresetWithEnum() {
+    final var result = schedulingDSLCompilationService.compileSchedulingGoalDSL(
+        missionModelService,
+        PLAN_ID, """
+                export default (): Goal => {
+                  return Goal.ActivityRecurrenceGoal({
+                    activityTemplate: ActivityTemplates.SampleActivity3(ActivityPresets.SampleActivity3["my preset"]),
+                    interval: Temporal.Duration.from({ hours: 1 })
+                  })
+                }
+            """);
+    final var expectedGoalDefinition1 = new SchedulingDSL.GoalSpecifier.RecurrenceGoalDefinition(
+        new SchedulingDSL.ActivityTemplate(
+            "SampleActivity3",
+            getSampleActivity3PresetParameters()
+        ),
+        Optional.empty(),
+        HOUR,
+        false);
+    if (result instanceof SchedulingDSLCompilationService.SchedulingDSLCompilationResult.Success<SchedulingDSL.GoalSpecifier> r) {
+      assertEquals(expectedGoalDefinition1, r.value());
+    } else if (result instanceof SchedulingDSLCompilationService.SchedulingDSLCompilationResult.Error<SchedulingDSL.GoalSpecifier> r) {
+      fail(r.toString());
+    }
+  }
+
 }