From 813e01ae5a47f6b011ae5dbd37eb0de34c85b229 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Sun, 30 Jan 2022 11:06:52 -0700
Subject: [PATCH 1/7] merged old stash for running local lib and conus

---
 gradle/dependencies.gradle                                 | 2 ++
 settings.gradle                                            | 3 +++
 src/main/java/gov/usgs/earthquake/nshmp/DisaggEpsilon.java | 1 +
 src/main/resources/application.yml                         | 2 ++
 4 files changed, 8 insertions(+)

diff --git a/gradle/dependencies.gradle b/gradle/dependencies.gradle
index 2a81e3a09..af19505cd 100644
--- a/gradle/dependencies.gradle
+++ b/gradle/dependencies.gradle
@@ -1,6 +1,8 @@
 
 dependencies {
+
   // NSHMP
+  // implementation files('../nshmp-lib/build/libs/nshmp-lib.jar')
   implementation "ghsc:nshmp-lib:${nshmpLibVersion}"
   implementation "ghsc:nshmp-ws-utils:${nshmpWsUtilsVersion}"
 
diff --git a/settings.gradle b/settings.gradle
index 90c2faad1..d1ad3db91 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -20,6 +20,9 @@ git {
     fetch("https://code.usgs.gov/ghsc/nshmp/nshms/nshm-hawaii.git", {
       name "nshmp-haz-dep--nshm-hi-2021"
       tag "2.0.0"
+    // fetch("https://code.usgs.gov/ghsc/nshmp/nshms/nshm-conus.git", {
+    //   name "nshmp-haz-dep--nshm-conus-2018"
+    //   tag "main"
     })
   }
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/DisaggEpsilon.java b/src/main/java/gov/usgs/earthquake/nshmp/DisaggEpsilon.java
index d10e9eda7..e4d039601 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/DisaggEpsilon.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/DisaggEpsilon.java
@@ -146,6 +146,7 @@ public class DisaggEpsilon {
       log.info("Spectra: " + imtImlMaps.size());
 
       checkArgument(sites.size() == imtImlMaps.size(), "Sites and spectra lists different sizes");
+      // Spectra should be checked against IMTs supported by model GMMs
 
       Path out = calc(model, config, sites, imtImlMaps, log);
 
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index d4d11ef0c..5ca335064 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -20,4 +20,6 @@ nshmp-haz:
   # The path to the models.
   # To specify the model to use:
   #     java -jar build/libs/nshmp-haz.jar --models=<path/to/models>
+  #
   model-path: ${models:libs/nshmp-haz-dep--nshm-hi-2021}
+  # model-path: ${models:libs/nshmp-haz-dep--nshm-conus-2018}
-- 
GitLab


From f591f79ff9902c2701e4e1365b83f3378afe701d Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Mon, 31 Jan 2022 10:50:57 -0700
Subject: [PATCH 2/7] moved hazard to own package; simplified request-response

---
 .../www/{services => }/ServicesUtil.java      |  17 +-
 .../nshmp/www/{services => }/ServletUtil.java |  12 +-
 .../nshmp/www/SwaggerController.java          |   2 -
 .../www/{ => hazard}/HazardController.java    |  57 ++-
 .../{services => hazard}/HazardService.java   | 245 ++++------
 .../{services => hazard}/MaxDirection.java    |   2 +-
 .../earthquake/nshmp/www/meta/Metadata.java   |   2 +-
 .../nshmp/www/services/HazardService2.java    | 452 ------------------
 .../nshmp/www/services/RateService.java       |   9 +-
 .../www/services/SourceLogicTreesService.java |   2 +
 .../nshmp/www/services/SourceServices.java    |   5 +-
 11 files changed, 146 insertions(+), 659 deletions(-)
 rename src/main/java/gov/usgs/earthquake/nshmp/www/{services => }/ServicesUtil.java (88%)
 rename src/main/java/gov/usgs/earthquake/nshmp/www/{services => }/ServletUtil.java (94%)
 rename src/main/java/gov/usgs/earthquake/nshmp/www/{ => hazard}/HazardController.java (63%)
 rename src/main/java/gov/usgs/earthquake/nshmp/www/{services => hazard}/HazardService.java (62%)
 rename src/main/java/gov/usgs/earthquake/nshmp/www/{services => hazard}/MaxDirection.java (97%)
 delete mode 100644 src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService2.java

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/ServicesUtil.java b/src/main/java/gov/usgs/earthquake/nshmp/www/ServicesUtil.java
similarity index 88%
rename from src/main/java/gov/usgs/earthquake/nshmp/www/services/ServicesUtil.java
rename to src/main/java/gov/usgs/earthquake/nshmp/www/ServicesUtil.java
index b2da3f091..0da190978 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/ServicesUtil.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/ServicesUtil.java
@@ -1,4 +1,4 @@
-package gov.usgs.earthquake.nshmp.www.services;
+package gov.usgs.earthquake.nshmp.www;
 
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
@@ -11,9 +11,6 @@ import gov.usgs.earthquake.nshmp.calc.Hazard;
 import gov.usgs.earthquake.nshmp.calc.HazardCalcs;
 import gov.usgs.earthquake.nshmp.calc.Site;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
-import gov.usgs.earthquake.nshmp.www.ResponseBody;
-import gov.usgs.earthquake.nshmp.www.WsUtils;
-
 import io.micronaut.http.HttpResponse;
 
 public class ServicesUtil {
@@ -35,7 +32,8 @@ public class ServicesUtil {
     return HttpResponse.serverError(response);
   }
 
-  static Hazard calcHazard(
+  @Deprecated
+  public static Hazard calcHazard(
       Function<HazardModel, CalcConfig> configFunction,
       Function<CalcConfig, Site> siteFunction) throws InterruptedException, ExecutionException {
 
@@ -47,12 +45,12 @@ public class ServicesUtil {
   }
 
   @Deprecated
-  static class ServiceQueryData implements ServiceQuery {
+  public static class ServiceQueryData implements ServiceQuery {
 
     public final Double longitude;
     public final Double latitude;
 
-    ServiceQueryData(Double longitude, Double latitude) {
+    public ServiceQueryData(Double longitude, Double latitude) {
       this.longitude = longitude;
       this.latitude = latitude;
     }
@@ -70,7 +68,7 @@ public class ServicesUtil {
   }
 
   @Deprecated
-  static class ServiceRequestData {
+  public static class ServiceRequestData {
 
     public final double longitude;
     public final double latitude;
@@ -81,7 +79,7 @@ public class ServicesUtil {
     }
   }
 
-  enum Key {
+  public enum Key {
     EDITION,
     REGION,
     MODEL,
@@ -114,6 +112,7 @@ public class ServicesUtil {
     void checkValues();
   }
 
+  @Deprecated
   private static CompletableFuture<Hazard> calcHazard(
       HazardModel model,
       CalcConfig config,
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/ServletUtil.java b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
similarity index 94%
rename from src/main/java/gov/usgs/earthquake/nshmp/www/services/ServletUtil.java
rename to src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
index 81651afd7..3de64116a 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/ServletUtil.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
@@ -1,4 +1,4 @@
-package gov.usgs.earthquake.nshmp.www.services;
+package gov.usgs.earthquake.nshmp.www;
 
 import static java.lang.Runtime.getRuntime;
 
@@ -27,9 +27,7 @@ import gov.usgs.earthquake.nshmp.calc.Site;
 import gov.usgs.earthquake.nshmp.calc.ValueFormat;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
-import gov.usgs.earthquake.nshmp.www.WsUtils;
 import gov.usgs.earthquake.nshmp.www.meta.MetaUtil;
-
 import io.micronaut.context.annotation.Value;
 import io.micronaut.context.event.ShutdownEvent;
 import io.micronaut.context.event.StartupEvent;
@@ -46,10 +44,10 @@ public class ServletUtil {
 
   public static final Gson GSON;
 
-  static final ListeningExecutorService CALC_EXECUTOR;
-  static final ExecutorService TASK_EXECUTOR;
+  public static final ListeningExecutorService CALC_EXECUTOR;
+  public static final ExecutorService TASK_EXECUTOR;
 
-  static final int THREAD_COUNT;
+  public static final int THREAD_COUNT;
 
   @Value("${nshmp-haz.model-path}")
   private Path modelPath;
@@ -73,7 +71,7 @@ public class ServletUtil {
         .create();
   }
 
-  static HazardModel model() {
+  public static HazardModel model() {
     return HAZARD_MODEL;
   }
 
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java
index c2251caba..6cd6d6d04 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java
@@ -5,8 +5,6 @@ import java.util.stream.Collectors;
 
 import com.google.common.io.Resources;
 
-import gov.usgs.earthquake.nshmp.www.services.ServicesUtil;
-
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
 import io.micronaut.http.MediaType;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
similarity index 63%
rename from src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java
rename to src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
index fdc747478..54c765def 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/HazardController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
@@ -1,8 +1,7 @@
-package gov.usgs.earthquake.nshmp.www;
-
-import gov.usgs.earthquake.nshmp.www.services.HazardService;
-import gov.usgs.earthquake.nshmp.www.services.HazardService.QueryParameters;
+package gov.usgs.earthquake.nshmp.www.hazard;
 
+import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
+import gov.usgs.earthquake.nshmp.www.ServicesUtil;
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
 import io.micronaut.http.annotation.Controller;
@@ -16,9 +15,9 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.inject.Inject;
 
 /**
- * Micronaut controller for probabilistic seismic hazard calculations.
+ * Micronaut controller for probabilistic seismic hazard calculations and
+ * services.
  *
- * @see HazardService
  * @author U.S. Geological Survey
  */
 @Tag(
@@ -32,8 +31,7 @@ public class HazardController {
 
   @Operation(
       summary = "Hazard model and service metadata",
-      description = "Returns details of the installed model and service request parameters",
-      operationId = "hazard_doGetMetadata")
+      description = "Returns details of the installed model and service request parameters")
   @ApiResponse(
       description = "Hazard service metadata",
       responseCode = "200")
@@ -51,31 +49,32 @@ public class HazardController {
    */
   @Operation(
       summary = "Compute probabilisitic hazard at a site",
-      description = "Returns hazard curves computed from the installed model",
-      operationId = "hazard_doGetHazard")
+      description = "Returns hazard curves computed from the installed model")
   @ApiResponse(
       description = "Hazard curves",
       responseCode = "200")
   @Get(uri = "/{longitude}/{latitude}/{vs30}{?truncate,maxdir}")
   public HttpResponse<String> doGetHazard(
-      HttpRequest<?> request,
-
-      @Schema(minimum = "-360", maximum = "360") @PathVariable double longitude,
-
-      @Schema(minimum = "-90", maximum = "90") @PathVariable double latitude,
-
-      @Schema(minimum = "150", maximum = "3000") @PathVariable int vs30,
-
-      @QueryValue(defaultValue = "false") boolean truncate,
-
-      @QueryValue(defaultValue = "false") boolean maxdir) {
-
-    /*
-     * @Schema annotation parameter constraints only affect Swagger service
-     * index page behavior; still need to validate against model. TODO
-     */
-
-    var query = new QueryParameters(longitude, latitude, vs30, truncate, maxdir);
-    return HazardService.handleDoGetHazard(request, query);
+      HttpRequest<?> http,
+      @Schema(
+          minimum = "-360",
+          maximum = "360") @PathVariable double longitude,
+      @Schema(
+          minimum = "-90",
+          maximum = "90") @PathVariable double latitude,
+      @Schema(
+          minimum = "150",
+          maximum = "3000") @PathVariable int vs30,
+      @QueryValue(
+          defaultValue = "false") boolean truncate,
+      @QueryValue(
+          defaultValue = "false") boolean maxdir) {
+    try {
+      HazardService.Request request = new HazardService.Request(
+          http, longitude, latitude, vs30, truncate, maxdir);
+      return HazardService.processRequest(request);
+    } catch (Exception e) {
+      return ServicesUtil.handleError(e, HazardService.NAME, http.getUri().getPath());
+    }
   }
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
similarity index 62%
rename from src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService.java
rename to src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
index 89f03a87b..90cf98ef5 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
@@ -1,20 +1,24 @@
-package gov.usgs.earthquake.nshmp.www.services;
+package gov.usgs.earthquake.nshmp.www.hazard;
 
 import static com.google.common.base.Preconditions.checkState;
 import static gov.usgs.earthquake.nshmp.calc.HazardExport.curvesBySource;
+import static gov.usgs.earthquake.nshmp.data.DoubleData.checkInRange;
+import static gov.usgs.earthquake.nshmp.geo.Coordinates.checkLatitude;
+import static gov.usgs.earthquake.nshmp.geo.Coordinates.checkLongitude;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
-import java.util.function.Function;
 
 import com.google.common.base.Stopwatch;
 
 import gov.usgs.earthquake.nshmp.calc.CalcConfig;
 import gov.usgs.earthquake.nshmp.calc.Hazard;
+import gov.usgs.earthquake.nshmp.calc.HazardCalcs;
 import gov.usgs.earthquake.nshmp.calc.Site;
 import gov.usgs.earthquake.nshmp.data.MutableXySequence;
 import gov.usgs.earthquake.nshmp.data.XySequence;
@@ -23,16 +27,13 @@ import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
 import gov.usgs.earthquake.nshmp.model.SourceType;
-import gov.usgs.earthquake.nshmp.www.HazardController;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
-import gov.usgs.earthquake.nshmp.www.WsUtils;
+import gov.usgs.earthquake.nshmp.www.ServicesUtil;
+import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter;
 import gov.usgs.earthquake.nshmp.www.meta.Metadata;
 import gov.usgs.earthquake.nshmp.www.meta.Parameter;
-import gov.usgs.earthquake.nshmp.www.services.ServicesUtil.ServiceQueryData;
-import gov.usgs.earthquake.nshmp.www.services.ServicesUtil.ServiceRequestData;
 import gov.usgs.earthquake.nshmp.www.services.SourceServices.SourceModel;
-
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
 import jakarta.inject.Singleton;
@@ -46,13 +47,13 @@ import jakarta.inject.Singleton;
 @Singleton
 public final class HazardService {
 
-  private static final String NAME = "Hazard Service";
+  static final String NAME = "Hazard Service";
 
   /** HazardController.doGetUsage() handler. */
   public static HttpResponse<String> handleDoGetMetadata(HttpRequest<?> request) {
     var url = request.getUri().getPath();
     try {
-      var usage = new RequestMetadata(ServletUtil.model());
+      var usage = new UsageMetadata(ServletUtil.model());
       var response = ResponseBody.usage()
           .name(NAME)
           .url(url)
@@ -67,111 +68,82 @@ public final class HazardService {
   }
 
   /** HazardController.doGetHazard() handler. */
-  public static HttpResponse<String> handleDoGetHazard(
-      HttpRequest<?> request,
-      QueryParameters query) {
-
+  public static HttpResponse<String> processRequest(Request request) {
     try {
-      // TODO still need to validate
-      // if (query.isEmpty()) {
-      // return handleDoGetUsage(urlHelper);
-      // }
-      // query.checkParameters();
-      var data = new RequestData(query);
-      var response = process(request, data);
-      var svcResponse = ServletUtil.GSON.toJson(response);
+      Response response = process(request);
+      var body = ResponseBody.success()
+          .name(NAME)
+          .url(request.http.getUri().getPath())
+          .request(request)
+          .response(response)
+          .build();
+      String svcResponse = ServletUtil.GSON.toJson(body);
       return HttpResponse.ok(svcResponse);
     } catch (Exception e) {
-      return ServicesUtil.handleError(e, NAME, request.getUri().getPath());
+      return ServicesUtil.handleError(e, NAME, request.http.getUri().getPath());
     }
   }
 
-  static ResponseBody<RequestData, ResponseData> process(
-      HttpRequest<?> request,
-      RequestData data)
+  /*
+   * Developer notes:
+   *
+   * Future calculation configuration options: vertical GMs
+   *
+   * NSHM Hazard Tool will not pass truncation and maxdir args/flags as the apps
+   * apply truncation and scaling on the client.
+   */
+
+  static Response process(Request request)
       throws InterruptedException, ExecutionException {
 
-    var configFunction = new ConfigFunction();
-    var siteFunction = new SiteFunction(data);
     var stopwatch = Stopwatch.createStarted();
-    var hazard = ServicesUtil.calcHazard(configFunction, siteFunction);
+    var hazard = calcHazard(request);
 
     return new ResultBuilder()
+        .request(request)
         .hazard(hazard)
-        .requestData(data)
         .timer(stopwatch)
-        .url(request)
         .build();
   }
 
-  static class ConfigFunction implements Function<HazardModel, CalcConfig> {
-    @Override
-    public CalcConfig apply(HazardModel model) {
-      var configBuilder = CalcConfig.copyOf(model.config());
-      return configBuilder.build();
-    }
-  }
+  public static Hazard calcHazard(Request request)
+      throws InterruptedException, ExecutionException {
 
-  static class SiteFunction implements Function<CalcConfig, Site> {
-    final RequestData data;
+    HazardModel model = ServletUtil.model();
 
-    private SiteFunction(RequestData data) {
-      this.data = data;
-    }
+    // will we be passing in options for config??
+    CalcConfig config = CalcConfig.copyOf(model.config()).build();
 
-    @Override // TODO this needs to pick up SiteData
-    public Site apply(CalcConfig config) {
-      return Site.builder()
-          .location(Location.create(data.longitude, data.latitude))
-          .vs30(data.vs30)
-          .build();
-    }
+    // TODO this needs to pick up SiteData
+    Site site = Site.builder()
+        .location(Location.create(request.longitude, request.latitude))
+        .vs30(request.vs30)
+        .build();
+    CompletableFuture<Hazard> future = futureHazard(model, config, site);
+    return future.get();
   }
 
-  public static class QueryParameters {
+  private static CompletableFuture<Hazard> futureHazard(
+      HazardModel model,
+      CalcConfig config,
+      Site site) {
 
-    final double longitude;
-    final double latitude;
-    final int vs30;
-    final boolean truncate;
-    final boolean maxdir;
-
-    public QueryParameters(
-        double longitude,
-        double latitude,
-        int vs30,
-        boolean truncate,
-        boolean maxdir) {
-
-      this.longitude = longitude;
-      this.latitude = latitude;
-      this.vs30 = vs30;
-      this.truncate = truncate;
-      this.maxdir = maxdir;
-    }
-
-    // void checkParameters() {
-    // checkParameter(longitude, "longitude");
-    // checkParameter(latitude, "latitude");
-    // checkParameter(vs30, "vs30");
-    // }
+    return CompletableFuture.supplyAsync(
+        () -> HazardCalcs.hazard(model, config, site, ServletUtil.CALC_EXECUTOR),
+        ServletUtil.TASK_EXECUTOR);
   }
 
-  // private static void checkParameter(Object param, String id) {
-  // checkNotNull(param, "Missing parameter: %s", id);
-  // // TODO check range here
-  // }
-
-  /* Service request and model metadata */
-  static class RequestMetadata {
+  private static class UsageMetadata {
 
     final SourceModel model;
     final DoubleParameter longitude;
     final DoubleParameter latitude;
     final DoubleParameter vs30;
 
-    RequestMetadata(HazardModel model) {
+    UsageMetadata(HazardModel model) {
       this.model = new SourceModel(model);
+      // perhaps move out to shared factory with parameter instances
+      //
       // TODO need min max from model
       longitude = new DoubleParameter(
           "Longitude",
@@ -186,27 +158,36 @@ public final class HazardService {
           Coordinates.LAT_RANGE.upperEndpoint());
 
       vs30 = new DoubleParameter(
-          "Latitude",
+          "Vs30",
           "m/s",
           150,
           1500);
     }
   }
 
-  static class RequestData {
+  public static final class Request {
 
+    transient HttpRequest<?> http;
     final double longitude;
     final double latitude;
     final double vs30;
     final boolean truncate;
     final boolean maxdir;
 
-    RequestData(QueryParameters query) {
-      this.longitude = query.longitude;
-      this.latitude = query.latitude;
-      this.vs30 = query.vs30;
-      this.truncate = query.truncate;
-      this.maxdir = query.maxdir;
+    public Request(
+        HttpRequest<?> http,
+        double longitude,
+        double latitude,
+        int vs30,
+        boolean truncate,
+        boolean maxdir) {
+
+      this.http = http;
+      this.longitude = checkLongitude(longitude);
+      this.latitude = checkLatitude(latitude);
+      this.vs30 = checkInRange(Site.VS30_RANGE, Site.Key.VS30, vs30);
+      this.truncate = truncate;
+      this.maxdir = maxdir;
     }
   }
 
@@ -220,40 +201,21 @@ public final class HazardService {
     }
   }
 
-  private static String imtShortLabel(Imt imt) {
-    if (imt.equals(Imt.PGA) || imt.equals(Imt.PGV)) {
-      return imt.name();
-    } else if (imt.isSA()) {
-      return imt.period() + " s";
-    }
-    return imt.toString();
-  }
-
-  @Deprecated
-  static class RequestDataOld extends ServiceRequestData {
-    final double vs30;
-
-    RequestDataOld(Query query, double vs30) {
-      super(query);
-      this.vs30 = vs30;
-    }
-  }
-
-  private static final class ResponseData {
+  private static final class Response {
     final ResponseMetadata metadata;
-    final List<HazardResponse> hazardCurves;
+    final List<ImtCurves> hazardCurves;
 
-    ResponseData(ResponseMetadata metadata, List<HazardResponse> hazardCurves) {
+    Response(ResponseMetadata metadata, List<ImtCurves> hazardCurves) {
       this.metadata = metadata;
       this.hazardCurves = hazardCurves;
     }
   }
 
-  private static final class HazardResponse {
+  private static final class ImtCurves {
     final Parameter imt;
     final List<Curve> data;
 
-    HazardResponse(Imt imt, List<Curve> data) {
+    ImtCurves(Imt imt, List<Curve> data) {
       this.imt = new Parameter(imtShortLabel(imt), imt.name());
       this.data = data;
     }
@@ -273,9 +235,8 @@ public final class HazardService {
 
   private static final class ResultBuilder {
 
-    String url;
     Stopwatch timer;
-    RequestData request;
+    Request request;
 
     Map<Imt, Map<SourceType, MutableXySequence>> componentMaps;
     Map<Imt, MutableXySequence> totalMap;
@@ -311,23 +272,18 @@ public final class HazardService {
       return this;
     }
 
-    ResultBuilder url(HttpRequest<?> request) {
-      url = request.getUri().getPath();
-      return this;
-    }
-
     ResultBuilder timer(Stopwatch timer) {
       this.timer = timer;
       return this;
     }
 
-    ResultBuilder requestData(RequestData request) {
+    ResultBuilder request(Request request) {
       this.request = request;
       return this;
     }
 
-    ResponseBody<RequestData, ResponseData> build() {
-      var hazards = new ArrayList<HazardResponse>();
+    Response build() {
+      var hazards = new ArrayList<ImtCurves>();
 
       for (Imt imt : totalMap.keySet()) {
         var curves = new ArrayList<Curve>();
@@ -345,18 +301,15 @@ public final class HazardService {
               updateCurve(request, typeMap.get(type), imt)));
         }
 
-        hazards.add(new HazardResponse(imt, List.copyOf(curves)));
+        hazards.add(new ImtCurves(imt, List.copyOf(curves)));
       }
 
       Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer);
-      var response = new ResponseData(new ResponseMetadata(server), List.copyOf(hazards));
+      var response = new Response(
+          new ResponseMetadata(server),
+          List.copyOf(hazards));
 
-      return ResponseBody.<RequestData, ResponseData> success()
-          .name(NAME)
-          .url(url)
-          .request(request)
-          .response(response)
-          .build();
+      return response;
     }
   }
 
@@ -364,7 +317,7 @@ public final class HazardService {
 
   /* Convert to linear and possibly truncate and scale to max-direction. */
   private static XySequence updateCurve(
-      RequestData request,
+      Request request,
       XySequence curve,
       Imt imt) {
 
@@ -398,25 +351,13 @@ public final class HazardService {
     return limit;
   }
 
-  @Deprecated
-  public static class Query extends ServiceQueryData {
-    Integer vs30;
-
-    public Query(Double longitude, Double latitude, Integer vs30) {
-      super(longitude, latitude);
-      this.vs30 = vs30;
-    }
-
-    @Override
-    public boolean isNull() {
-      return super.isNull() && vs30 == null;
-    }
-
-    @Override
-    public void checkValues() {
-      super.checkValues();
-      WsUtils.checkValue(ServicesUtil.Key.VS30, vs30);
+  private static String imtShortLabel(Imt imt) {
+    if (imt.equals(Imt.PGA) || imt.equals(Imt.PGV)) {
+      return imt.name();
+    } else if (imt.isSA()) {
+      return imt.period() + " s";
     }
+    return imt.toString();
   }
 
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/MaxDirection.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/MaxDirection.java
similarity index 97%
rename from src/main/java/gov/usgs/earthquake/nshmp/www/services/MaxDirection.java
rename to src/main/java/gov/usgs/earthquake/nshmp/www/hazard/MaxDirection.java
index e35c59355..99c40c42d 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/MaxDirection.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/MaxDirection.java
@@ -1,4 +1,4 @@
-package gov.usgs.earthquake.nshmp.www.services;
+package gov.usgs.earthquake.nshmp.www.hazard;
 
 import static gov.usgs.earthquake.nshmp.gmm.Imt.PGA;
 import static gov.usgs.earthquake.nshmp.gmm.Imt.SA0P01;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/meta/Metadata.java b/src/main/java/gov/usgs/earthquake/nshmp/www/meta/Metadata.java
index 1120be850..ed8a2973d 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/meta/Metadata.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/meta/Metadata.java
@@ -4,7 +4,7 @@ import com.google.common.base.Stopwatch;
 import com.google.common.base.Throwables;
 
 import gov.usgs.earthquake.nshmp.geo.Coordinates;
-import gov.usgs.earthquake.nshmp.www.services.ServletUtil;
+import gov.usgs.earthquake.nshmp.www.ServletUtil;
 
 /**
  * Service metadata, parameterization, and constraint strings, in JSON format.
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService2.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService2.java
deleted file mode 100644
index 63fb4261b..000000000
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/HazardService2.java
+++ /dev/null
@@ -1,452 +0,0 @@
-package gov.usgs.earthquake.nshmp.www.services;
-
-import static com.google.common.base.Preconditions.checkState;
-import static gov.usgs.earthquake.nshmp.calc.HazardExport.curvesBySource;
-
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.EnumMap;
-import java.util.List;
-import java.util.Map;
-import java.util.concurrent.ExecutionException;
-import java.util.function.Function;
-
-import com.google.common.base.Stopwatch;
-
-import gov.usgs.earthquake.nshmp.calc.CalcConfig;
-import gov.usgs.earthquake.nshmp.calc.Hazard;
-import gov.usgs.earthquake.nshmp.calc.Site;
-import gov.usgs.earthquake.nshmp.data.MutableXySequence;
-import gov.usgs.earthquake.nshmp.data.XySequence;
-import gov.usgs.earthquake.nshmp.geo.Coordinates;
-import gov.usgs.earthquake.nshmp.geo.Location;
-import gov.usgs.earthquake.nshmp.gmm.Imt;
-import gov.usgs.earthquake.nshmp.model.HazardModel;
-import gov.usgs.earthquake.nshmp.model.SourceType;
-import gov.usgs.earthquake.nshmp.www.HazardController;
-import gov.usgs.earthquake.nshmp.www.ResponseBody;
-import gov.usgs.earthquake.nshmp.www.WsUtils;
-import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter;
-import gov.usgs.earthquake.nshmp.www.meta.Metadata;
-import gov.usgs.earthquake.nshmp.www.meta.Parameter;
-import gov.usgs.earthquake.nshmp.www.services.ServicesUtil.ServiceQueryData;
-import gov.usgs.earthquake.nshmp.www.services.SourceServices.SourceModel;
-
-import io.micronaut.http.HttpRequest;
-import io.micronaut.http.HttpResponse;
-import jakarta.inject.Singleton;
-
-/**
- * Probabilistic seismic hazard calculation handler for
- * {@link HazardController}.
- *
- * @author U.S. Geological Survey
- */
-@Singleton
-public final class HazardService2 {
-
-  private static final String NAME = "Hazard Service";
-
-  /** HazardController.doGetUsage() handler. */
-  public static HttpResponse<String> handleDoGetMetadata(HttpRequest<?> request) {
-    var url = request.getUri().getPath();
-    try {
-      var usage = new RequestMetadata(ServletUtil.model());// SourceServices.ResponseData();
-      var response = ResponseBody.usage()
-          .name(NAME)
-          .url(url)
-          .request(url)
-          .response(usage)
-          .build();
-      var svcResponse = ServletUtil.GSON.toJson(response);
-      return HttpResponse.ok(svcResponse);
-    } catch (Exception e) {
-      return ServicesUtil.handleError(e, NAME, url);
-    }
-  }
-
-  /** HazardController.doGetHazard() handler. */
-  public static HttpResponse<String> handleDoGetHazard(
-      HttpRequest<?> request,
-      RequestData args) {
-
-    try {
-      // TODO still need to validate
-      // if (query.isEmpty()) {
-      // return handleDoGetUsage(urlHelper);
-      // }
-      // query.checkParameters();
-
-      // var data = new RequestData(query);
-
-      ResponseBody<RequestData, ResponseData> response = process(request, args);
-      String svcResponse = ServletUtil.GSON.toJson(response);
-      return HttpResponse.ok(svcResponse);
-
-    } catch (Exception e) {
-      return ServicesUtil.handleError(e, NAME, request.getUri().getPath());
-    }
-  }
-
-  static ResponseBody<RequestData, ResponseData> process(
-      HttpRequest<?> request,
-      RequestData data) throws InterruptedException, ExecutionException {
-
-    var configFunction = new ConfigFunction();
-    var siteFunction = new SiteFunction(data);
-    var stopwatch = Stopwatch.createStarted();
-    var hazard = ServicesUtil.calcHazard(configFunction, siteFunction);
-
-    return new ResultBuilder()
-        .hazard(hazard)
-        .requestData(data)
-        .timer(stopwatch)
-        .url(request)
-        .build();
-  }
-
-  static class ConfigFunction implements Function<HazardModel, CalcConfig> {
-    @Override
-    public CalcConfig apply(HazardModel model) {
-      var configBuilder = CalcConfig.copyOf(model.config());
-      return configBuilder.build();
-    }
-  }
-
-  static class SiteFunction implements Function<CalcConfig, Site> {
-    final RequestData data;
-
-    private SiteFunction(RequestData data) {
-      this.data = data;
-    }
-
-    @Override // TODO this needs to pick up SiteData
-    public Site apply(CalcConfig config) {
-      return Site.builder()
-          .location(Location.create(data.longitude, data.latitude))
-          .vs30(data.vs30)
-          .build();
-    }
-  }
-
-  // public static class QueryParameters {
-  //
-  // final double longitude;
-  // final double latitude;
-  // final int vs30;
-  // final boolean truncate;
-  // final boolean maxdir;
-  //
-  // public QueryParameters(
-  // double longitude,
-  // double latitude,
-  // int vs30,
-  // boolean truncate,
-  // boolean maxdir) {
-  //
-  // this.longitude = longitude;
-  // this.latitude = latitude;
-  // this.vs30 = vs30;
-  // this.truncate = truncate;
-  // this.maxdir = maxdir;
-  // }
-  //
-  // // void checkParameters() {
-  // // checkParameter(longitude, "longitude");
-  // // checkParameter(latitude, "latitude");
-  // // checkParameter(vs30, "vs30");
-  // // }
-  // }
-
-  // private static void checkParameter(Object param, String id) {
-  // checkNotNull(param, "Missing parameter: %s", id);
-  // // TODO check range here
-  // }
-
-  /* Service request and model metadata */
-  static class RequestMetadata {
-
-    final SourceModel model;
-    final DoubleParameter longitude;
-    final DoubleParameter latitude;
-    final DoubleParameter vs30;
-
-    RequestMetadata(HazardModel model) {
-      this.model = new SourceModel(model);
-      // TODO need min max from model
-      longitude = new DoubleParameter(
-          "Longitude",
-          "°",
-          Coordinates.LON_RANGE.lowerEndpoint(),
-          Coordinates.LON_RANGE.upperEndpoint());
-
-      latitude = new DoubleParameter(
-          "Latitude",
-          "°",
-          Coordinates.LAT_RANGE.lowerEndpoint(),
-          Coordinates.LAT_RANGE.upperEndpoint());
-
-      vs30 = new DoubleParameter(
-          "Latitude",
-          "m/s",
-          150,
-          1500);
-    }
-  }
-
-  // static class RequestData {
-  //
-  // final double longitude;
-  // final double latitude;
-  // final double vs30;
-  // final boolean truncate;
-  // final boolean maxdir;
-  //
-  // RequestData(QueryParameters query) {
-  // this.longitude = query.longitude;
-  // this.latitude = query.latitude;
-  // this.vs30 = query.vs30;
-  // this.truncate = query.truncate;
-  // this.maxdir = query.maxdir;
-  // }
-  // }
-
-  private static final class ResponseMetadata {
-    final String xlabel = "Ground Motion (g)";
-    final String ylabel = "Annual Frequency of Exceedence";
-    final Object server;
-
-    ResponseMetadata(Object server) {
-      this.server = server;
-    }
-  }
-
-  private static String imtShortLabel(Imt imt) {
-    if (imt.equals(Imt.PGA) || imt.equals(Imt.PGV)) {
-      return imt.name();
-    } else if (imt.isSA()) {
-      return imt.period() + " s";
-    }
-    return imt.toString();
-  }
-
-  // @Deprecated
-  // static class RequestDataOld extends ServiceRequestData {
-  // final double vs30;
-  //
-  // RequestDataOld(Query query, double vs30) {
-  // super(query);
-  // this.vs30 = vs30;
-  // }
-  // }
-
-  private static final class ResponseData {
-    final ResponseMetadata metadata;
-    final List<HazardResponse> hazardCurves;
-
-    ResponseData(ResponseMetadata metadata, List<HazardResponse> hazardCurves) {
-      this.metadata = metadata;
-      this.hazardCurves = hazardCurves;
-    }
-  }
-
-  private static final class HazardResponse {
-    final Parameter imt;
-    final List<Curve> data;
-
-    HazardResponse(Imt imt, List<Curve> data) {
-      this.imt = new Parameter(imtShortLabel(imt), imt.name());
-      this.data = data;
-    }
-  }
-
-  private static final class Curve {
-    final String component;
-    final XySequence values;
-
-    Curve(String component, XySequence values) {
-      this.component = component;
-      this.values = values;
-    }
-  }
-
-  private static final String TOTAL_KEY = "Total";
-
-  private static final class ResultBuilder {
-
-    String url;
-    Stopwatch timer;
-    RequestData request;
-
-    Map<Imt, Map<SourceType, MutableXySequence>> componentMaps;
-    Map<Imt, MutableXySequence> totalMap;
-
-    ResultBuilder hazard(Hazard hazardResult) {
-      // TODO necessary??
-      checkState(totalMap == null, "Hazard has already been added to this builder");
-
-      componentMaps = new EnumMap<>(Imt.class);
-      totalMap = new EnumMap<>(Imt.class);
-
-      var typeTotalMaps = curvesBySource(hazardResult);
-
-      for (var imt : hazardResult.curves().keySet()) {
-
-        /* Total curve for IMT. */
-        XySequence.addToMap(imt, totalMap, hazardResult.curves().get(imt));
-
-        /* Source component curves for IMT. */
-        var typeTotalMap = typeTotalMaps.get(imt);
-        var componentMap = componentMaps.get(imt);
-
-        if (componentMap == null) {
-          componentMap = new EnumMap<>(SourceType.class);
-          componentMaps.put(imt, componentMap);
-        }
-
-        for (var type : typeTotalMap.keySet()) {
-          XySequence.addToMap(type, componentMap, typeTotalMap.get(type));
-        }
-      }
-
-      return this;
-    }
-
-    ResultBuilder url(HttpRequest<?> request) {
-      url = request.getUri().getPath();
-      return this;
-    }
-
-    ResultBuilder timer(Stopwatch timer) {
-      this.timer = timer;
-      return this;
-    }
-
-    ResultBuilder requestData(RequestData request) {
-      this.request = request;
-      return this;
-    }
-
-    ResponseBody<RequestData, ResponseData> build() {
-      var hazards = new ArrayList<HazardResponse>();
-
-      for (Imt imt : totalMap.keySet()) {
-        var curves = new ArrayList<Curve>();
-
-        // total curve
-        curves.add(new Curve(
-            TOTAL_KEY,
-            updateCurve(request, totalMap.get(imt), imt)));
-
-        // component curves
-        var typeMap = componentMaps.get(imt);
-        for (SourceType type : typeMap.keySet()) {
-          curves.add(new Curve(
-              type.toString(),
-              updateCurve(request, typeMap.get(type), imt)));
-        }
-
-        hazards.add(new HazardResponse(imt, List.copyOf(curves)));
-      }
-
-      Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer);
-      var response = new ResponseData(new ResponseMetadata(server), List.copyOf(hazards));
-
-      return ResponseBody.<RequestData, ResponseData> success()
-          .name(NAME)
-          .url(url)
-          .request(request)
-          .response(response)
-          .build();
-    }
-  }
-
-  private static final double TRUNCATION_LIMIT = 1e-4;
-
-  /* Convert to linear and possibly truncate and scale to max-direction. */
-  private static XySequence updateCurve(
-      RequestData request,
-      XySequence curve,
-      Imt imt) {
-
-    /*
-     * If entire curve is <1e-4, this method will return a curve consisting of
-     * just the first point in the supplied curve.
-     *
-     * TODO We probably want to move the TRUNCATION_LIMIT out to a config.
-     */
-
-    double[] yValues = curve.yValues().toArray();
-    int limit = request.truncate ? truncationLimit(yValues) : yValues.length;
-    yValues = Arrays.copyOf(yValues, limit);
-
-    double scale = request.maxdir ? MaxDirection.FACTORS.get(imt) : 1.0;
-    double[] xValues = curve.xValues()
-        .limit(yValues.length)
-        .map(Math::exp)
-        .map(x -> x * scale)
-        .toArray();
-
-    return XySequence.create(xValues, yValues);
-  }
-
-  private static int truncationLimit(double[] yValues) {
-    int limit = 1;
-    double y = yValues[0];
-    while (y > TRUNCATION_LIMIT && limit < yValues.length) {
-      y = yValues[limit++];
-    }
-    return limit;
-  }
-
-  @Deprecated
-  public static class Query extends ServiceQueryData {
-    Integer vs30;
-
-    public Query(Double longitude, Double latitude, Integer vs30) {
-      super(longitude, latitude);
-      this.vs30 = vs30;
-    }
-
-    @Override
-    public boolean isNull() {
-      return super.isNull() && vs30 == null;
-    }
-
-    @Override
-    public void checkValues() {
-      super.checkValues();
-      WsUtils.checkValue(ServicesUtil.Key.VS30, vs30);
-    }
-  }
-
-  public static final class RequestData {
-
-    final double longitude;
-    final double latitude;
-    final int vs30;
-    final boolean truncate;
-    final boolean maxdir;
-
-    public RequestData(
-        double longitude,
-        double latitude,
-        int vs30,
-        boolean truncate,
-        boolean maxdir) {
-
-      this.longitude = longitude;
-      this.latitude = latitude;
-      this.vs30 = vs30;
-      this.truncate = truncate;
-      this.maxdir = maxdir;
-    }
-
-    // void checkParameters() {
-    // checkParameter(longitude, "longitude");
-    // checkParameter(latitude, "latitude");
-    // checkParameter(vs30, "vs30");
-    // }
-  }
-
-}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
index 0312e7359..8c1a50c43 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
@@ -17,14 +17,15 @@ import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
 import gov.usgs.earthquake.nshmp.www.RateController;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ServicesUtil;
+import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
+import gov.usgs.earthquake.nshmp.www.ServicesUtil.Key;
+import gov.usgs.earthquake.nshmp.www.ServicesUtil.ServiceQueryData;
+import gov.usgs.earthquake.nshmp.www.ServicesUtil.ServiceRequestData;
 import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter;
 import gov.usgs.earthquake.nshmp.www.meta.Metadata;
 import gov.usgs.earthquake.nshmp.www.meta.Metadata.DefaultParameters;
-import gov.usgs.earthquake.nshmp.www.services.ServicesUtil.Key;
-import gov.usgs.earthquake.nshmp.www.services.ServicesUtil.ServiceQueryData;
-import gov.usgs.earthquake.nshmp.www.services.ServicesUtil.ServiceRequestData;
-
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
 import jakarta.inject.Singleton;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java
index 3d0dfe0a7..4a7882cdb 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java
@@ -2,6 +2,8 @@ package gov.usgs.earthquake.nshmp.www.services;
 
 import gov.usgs.earthquake.nshmp.model.Models;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ServicesUtil;
+import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.SourceLogicTreesController;
 
 import io.micronaut.http.HttpRequest;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
index 83910b2fc..bd22e1c37 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
@@ -12,9 +12,10 @@ import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ServicesUtil;
+import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
 import gov.usgs.earthquake.nshmp.www.meta.Metadata;
-
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
 import jakarta.inject.Singleton;
@@ -105,7 +106,7 @@ public class SourceServices {
     Set<Gmm> gmms;
     Map<NehrpSiteClass, Double> siteClasses;
 
-    SourceModel(HazardModel model) {
+    public SourceModel(HazardModel model) {
       name = model.name();
       gmms = model.gmms();
       siteClasses = model.siteClasses();
-- 
GitLab


From f7b0ecd57c19d533335813a5542d94778a699a6f Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Tue, 1 Feb 2022 16:26:51 -0700
Subject: [PATCH 3/7] refactor of hazard services

---
 .../earthquake/nshmp/www/ServicesUtil.java    |  20 --
 .../earthquake/nshmp/www/ServletUtil.java     |  44 +++++
 .../nshmp/www/SwaggerController.java          |   6 +-
 .../nshmp/www/hazard/HazardController.java    |  48 +++--
 .../nshmp/www/hazard/HazardService.java       | 174 +++++++++---------
 .../earthquake/nshmp/www/meta/MetaUtil.java   |   2 +-
 .../nshmp/www/services/RateService.java       |  14 +-
 .../www/services/SourceLogicTreesService.java |  11 +-
 .../nshmp/www/services/SourceServices.java    |  19 +-
 9 files changed, 204 insertions(+), 134 deletions(-)

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/ServicesUtil.java b/src/main/java/gov/usgs/earthquake/nshmp/www/ServicesUtil.java
index 0da190978..3bdcab696 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/ServicesUtil.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/ServicesUtil.java
@@ -4,34 +4,14 @@ import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 import java.util.function.Function;
 
-import com.google.gson.GsonBuilder;
-
 import gov.usgs.earthquake.nshmp.calc.CalcConfig;
 import gov.usgs.earthquake.nshmp.calc.Hazard;
 import gov.usgs.earthquake.nshmp.calc.HazardCalcs;
 import gov.usgs.earthquake.nshmp.calc.Site;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
-import io.micronaut.http.HttpResponse;
 
 public class ServicesUtil {
 
-  public static HttpResponse<String> handleError(
-      Throwable e,
-      String name,
-      String url) {
-    var msg = e.getMessage() + " (see logs)";
-    var svcResponse = ResponseBody.error()
-        .name(name)
-        .url(url)
-        .request(url)
-        .response(msg)
-        .build();
-    var gson = new GsonBuilder().setPrettyPrinting().create();
-    var response = gson.toJson(svcResponse);
-    e.printStackTrace();
-    return HttpResponse.serverError(response);
-  }
-
   @Deprecated
   public static Hazard calcHazard(
       Function<HazardModel, CalcConfig> configFunction,
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
index 3de64116a..c3221f244 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
@@ -14,6 +14,9 @@ import java.util.HashMap;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.gson.Gson;
@@ -31,6 +34,7 @@ import gov.usgs.earthquake.nshmp.www.meta.MetaUtil;
 import io.micronaut.context.annotation.Value;
 import io.micronaut.context.event.ShutdownEvent;
 import io.micronaut.context.event.StartupEvent;
+import io.micronaut.http.HttpResponse;
 import io.micronaut.runtime.event.annotation.EventListener;
 import jakarta.inject.Singleton;
 
@@ -43,12 +47,15 @@ import jakarta.inject.Singleton;
 public class ServletUtil {
 
   public static final Gson GSON;
+  public static final Gson GSON2;
 
   public static final ListeningExecutorService CALC_EXECUTOR;
   public static final ExecutorService TASK_EXECUTOR;
 
   public static final int THREAD_COUNT;
 
+  private static final Logger LOGGER = LoggerFactory.getLogger(ServletUtil.class);
+
   @Value("${nshmp-haz.model-path}")
   private Path modelPath;
 
@@ -69,6 +76,17 @@ public class ServletUtil {
         .serializeNulls()
         .setPrettyPrinting()
         .create();
+
+    // removed old IMT and ValueFormat enum serialization
+    GSON2 = new GsonBuilder()
+        .registerTypeAdapter(Double.class, new WsUtils.DoubleSerializer())
+        .registerTypeAdapter(Site.class, new MetaUtil.SiteSerializer())
+        .registerTypeHierarchyAdapter(Path.class, new PathConverter())
+        .disableHtmlEscaping()
+        .serializeNulls()
+        .setPrettyPrinting()
+        .create();
+
   }
 
   public static HazardModel model() {
@@ -139,4 +157,30 @@ public class ServletUtil {
     }
   }
 
+  public static HttpResponse<String> error(
+      Logger logger,
+      Throwable e,
+      String name,
+      String url) {
+    var msg = e.getMessage() + " (see logs)";
+    var svcResponse = ResponseBody.error()
+        .name(name)
+        .url(url)
+        .request(url)
+        .response(msg)
+        .build();
+    var response = GSON2.toJson(svcResponse);
+    logger.error("Servlet error", e);
+    return HttpResponse.serverError(response);
+  }
+
+  public static String imtShortLabel(Imt imt) {
+    if (imt.equals(Imt.PGA) || imt.equals(Imt.PGV)) {
+      return imt.name();
+    } else if (imt.isSA()) {
+      return imt.period() + " s";
+    }
+    return imt.toString();
+  }
+
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java
index 6cd6d6d04..dc4d6fb3c 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/SwaggerController.java
@@ -3,6 +3,8 @@ package gov.usgs.earthquake.nshmp.www;
 import java.nio.charset.StandardCharsets;
 import java.util.stream.Collectors;
 
+import org.slf4j.LoggerFactory;
+
 import com.google.common.io.Resources;
 
 import io.micronaut.http.HttpRequest;
@@ -38,7 +40,9 @@ public class SwaggerController {
           .collect(Collectors.joining("\n"));
       return HttpResponse.ok(yml);
     } catch (Exception e) {
-      return ServicesUtil.handleError(e, "Swagger", request.getUri().getPath());
+      return ServletUtil.error(
+          LoggerFactory.getLogger("Swagger"),
+          e, "Swagger", request.getUri().toString());
     }
   }
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
index 54c765def..4dd72d3f5 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
@@ -1,7 +1,11 @@
 package gov.usgs.earthquake.nshmp.www.hazard;
 
+import java.util.Set;
+
+import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
-import gov.usgs.earthquake.nshmp.www.ServicesUtil;
+import gov.usgs.earthquake.nshmp.www.ServletUtil;
+import io.micronaut.core.annotation.Nullable;
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
 import io.micronaut.http.annotation.Controller;
@@ -15,8 +19,8 @@ import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.inject.Inject;
 
 /**
- * Micronaut controller for probabilistic seismic hazard calculations and
- * services.
+ * Micronaut web service controller for probabilistic seismic hazard
+ * calculations.
  *
  * @author U.S. Geological Survey
  */
@@ -36,8 +40,15 @@ public class HazardController {
       description = "Hazard service metadata",
       responseCode = "200")
   @Get
-  public HttpResponse<String> doGetMetadata(HttpRequest<?> request) {
-    return HazardService.handleDoGetMetadata(request);
+  public HttpResponse<String> doGetMetadata(HttpRequest<?> http) {
+    try {
+      return HazardService.getMetadata(http);
+    } catch (Exception e) {
+      return ServletUtil.error(
+          HazardService.LOG, e,
+          HazardService.NAME,
+          http.getUri().toString());
+    }
   }
 
   /**
@@ -46,6 +57,13 @@ public class HazardController {
    * @param vs30 Site Vs30 value in m/s [150..3000]
    * @param truncate Truncate curves at return periods below ~10,000 years
    * @param maxdir Apply max-direction scaling
+   * @param imt Optional IMTs at which to compute hazard. If none are supplied
+   *        then the default set of supported IMTs for the installed model is
+   *        used. Note that a model may not support all the values listed below
+   *        (see disagreggation metadata). Responses for numerous IMT's are
+   *        quite large, on the order of MB. Multiple IMTs may be comma
+   *        delimited, e.g. @code{?imt=PGA,SA0p2,SA1P0}.
+   *
    */
   @Operation(
       summary = "Compute probabilisitic hazard at a site",
@@ -53,7 +71,7 @@ public class HazardController {
   @ApiResponse(
       description = "Hazard curves",
       responseCode = "200")
-  @Get(uri = "/{longitude}/{latitude}/{vs30}{?truncate,maxdir}")
+  @Get(uri = "/{longitude}/{latitude}/{vs30}{?truncate,maxdir,imt}")
   public HttpResponse<String> doGetHazard(
       HttpRequest<?> http,
       @Schema(
@@ -66,15 +84,23 @@ public class HazardController {
           minimum = "150",
           maximum = "3000") @PathVariable int vs30,
       @QueryValue(
-          defaultValue = "false") boolean truncate,
+          defaultValue = "false") @Nullable Boolean truncate,
       @QueryValue(
-          defaultValue = "false") boolean maxdir) {
+          defaultValue = "false") @Nullable Boolean maxdir,
+      @QueryValue @Nullable Set<Imt> imt) {
     try {
+      Set<Imt> imts = HazardService.readImts(http);
       HazardService.Request request = new HazardService.Request(
-          http, longitude, latitude, vs30, truncate, maxdir);
-      return HazardService.processRequest(request);
+          http,
+          longitude, latitude, vs30,
+          truncate, maxdir,
+          imts);
+      return HazardService.getHazard(request);
     } catch (Exception e) {
-      return ServicesUtil.handleError(e, HazardService.NAME, http.getUri().getPath());
+      return ServletUtil.error(
+          HazardService.LOG, e,
+          HazardService.NAME,
+          http.getUri().toString());
     }
   }
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
index 90cf98ef5..556dffea9 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
@@ -5,15 +5,21 @@ import static gov.usgs.earthquake.nshmp.calc.HazardExport.curvesBySource;
 import static gov.usgs.earthquake.nshmp.data.DoubleData.checkInRange;
 import static gov.usgs.earthquake.nshmp.geo.Coordinates.checkLatitude;
 import static gov.usgs.earthquake.nshmp.geo.Coordinates.checkLongitude;
+import static java.util.stream.Collectors.toCollection;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.EnumMap;
+import java.util.EnumSet;
 import java.util.List;
 import java.util.Map;
+import java.util.Set;
 import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutionException;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.common.base.Stopwatch;
 
 import gov.usgs.earthquake.nshmp.calc.CalcConfig;
@@ -28,7 +34,6 @@ import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
 import gov.usgs.earthquake.nshmp.model.SourceType;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
-import gov.usgs.earthquake.nshmp.www.ServicesUtil;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter;
 import gov.usgs.earthquake.nshmp.www.meta.Metadata;
@@ -39,49 +44,49 @@ import io.micronaut.http.HttpResponse;
 import jakarta.inject.Singleton;
 
 /**
- * Probabilistic seismic hazard calculation handler for
- * {@link HazardController}.
+ * Hazard service.
  *
+ * @see HazardController
  * @author U.S. Geological Survey
  */
 @Singleton
 public final class HazardService {
 
   static final String NAME = "Hazard Service";
+  static final Logger LOG = LoggerFactory.getLogger(HazardService.class);
 
   /** HazardController.doGetUsage() handler. */
-  public static HttpResponse<String> handleDoGetMetadata(HttpRequest<?> request) {
-    var url = request.getUri().getPath();
-    try {
-      var usage = new UsageMetadata(ServletUtil.model());
-      var response = ResponseBody.usage()
-          .name(NAME)
-          .url(url)
-          .request(url)
-          .response(usage)
-          .build();
-      var svcResponse = ServletUtil.GSON.toJson(response);
-      return HttpResponse.ok(svcResponse);
-    } catch (Exception e) {
-      return ServicesUtil.handleError(e, NAME, url);
-    }
+  public static HttpResponse<String> getMetadata(HttpRequest<?> request) {
+    var url = request.getUri().toString();
+    var usage = new UsageMetadata(ServletUtil.model());
+    var body = ResponseBody.usage()
+        .name(NAME)
+        .url(url)
+        .request(url)
+        .response(usage)
+        .build();
+    var json = ServletUtil.GSON.toJson(body);
+    return HttpResponse.ok(json);
   }
 
   /** HazardController.doGetHazard() handler. */
-  public static HttpResponse<String> processRequest(Request request) {
-    try {
-      Response response = process(request);
-      var body = ResponseBody.success()
-          .name(NAME)
-          .url(request.http.getUri().getPath())
-          .request(request)
-          .response(response)
-          .build();
-      String svcResponse = ServletUtil.GSON.toJson(body);
-      return HttpResponse.ok(svcResponse);
-    } catch (Exception e) {
-      return ServicesUtil.handleError(e, NAME, request.http.getUri().getPath());
-    }
+  public static HttpResponse<String> getHazard(Request request)
+      throws InterruptedException, ExecutionException {
+    var stopwatch = Stopwatch.createStarted();
+    var hazard = calcHazard(request);
+    var response = new ResponseBuilder()
+        .timer(stopwatch)
+        .request(request)
+        .hazard(hazard)
+        .build();
+    var body = ResponseBody.success()
+        .name(NAME)
+        .url(request.http.getUri().toString())
+        .request(request)
+        .response(response)
+        .build();
+    String json = ServletUtil.GSON.toJson(body);
+    return HttpResponse.ok(json);
   }
 
   /*
@@ -93,47 +98,32 @@ public final class HazardService {
    * apply truncation and scaling on the client.
    */
 
-  static Response process(Request request)
-      throws InterruptedException, ExecutionException {
-
-    var stopwatch = Stopwatch.createStarted();
-    var hazard = calcHazard(request);
-
-    return new ResultBuilder()
-        .request(request)
-        .hazard(hazard)
-        .timer(stopwatch)
-        .build();
-  }
-
   public static Hazard calcHazard(Request request)
       throws InterruptedException, ExecutionException {
 
     HazardModel model = ServletUtil.model();
 
-    // will we be passing in options for config??
-    CalcConfig config = CalcConfig.copyOf(model.config()).build();
+    // modify config to include service endpoint arguments
+    CalcConfig config = CalcConfig.copyOf(model.config())
+        .imts(request.imts)
+        .build();
 
-    // TODO this needs to pick up SiteData
+    // TODO this needs to pick up SiteData, centralize
     Site site = Site.builder()
         .location(Location.create(request.longitude, request.latitude))
         .vs30(request.vs30)
         .build();
-    CompletableFuture<Hazard> future = futureHazard(model, config, site);
-    return future.get();
-  }
 
-  private static CompletableFuture<Hazard> futureHazard(
-      HazardModel model,
-      CalcConfig config,
-      Site site) {
-
-    return CompletableFuture.supplyAsync(
-        () -> HazardCalcs.hazard(model, config, site, ServletUtil.CALC_EXECUTOR),
+    CompletableFuture<Hazard> future = CompletableFuture.supplyAsync(
+        () -> HazardCalcs.hazard(
+            model, config, site,
+            ServletUtil.CALC_EXECUTOR),
         ServletUtil.TASK_EXECUTOR);
+
+    return future.get();
   }
 
-  private static class UsageMetadata {
+  static class UsageMetadata {
 
     final SourceModel model;
     final DoubleParameter longitude;
@@ -142,8 +132,6 @@ public final class HazardService {
 
     UsageMetadata(HazardModel model) {
       this.model = new SourceModel(model);
-      // perhaps move out to shared factory with parameter instances
-      //
       // TODO need min max from model
       longitude = new DoubleParameter(
           "Longitude",
@@ -167,12 +155,13 @@ public final class HazardService {
 
   public static final class Request {
 
-    transient HttpRequest<?> http;
+    final transient HttpRequest<?> http;
     final double longitude;
     final double latitude;
     final double vs30;
     final boolean truncate;
     final boolean maxdir;
+    final Set<Imt> imts;
 
     public Request(
         HttpRequest<?> http,
@@ -180,7 +169,8 @@ public final class HazardService {
         double latitude,
         int vs30,
         boolean truncate,
-        boolean maxdir) {
+        boolean maxdir,
+        Set<Imt> imts) {
 
       this.http = http;
       this.longitude = checkLongitude(longitude);
@@ -188,13 +178,16 @@ public final class HazardService {
       this.vs30 = checkInRange(Site.VS30_RANGE, Site.Key.VS30, vs30);
       this.truncate = truncate;
       this.maxdir = maxdir;
+      this.imts = imts.isEmpty()
+          ? ServletUtil.model().config().hazard.imts
+          : imts;
     }
   }
 
   private static final class ResponseMetadata {
+    final Object server;
     final String xlabel = "Ground Motion (g)";
     final String ylabel = "Annual Frequency of Exceedence";
-    final Object server;
 
     ResponseMetadata(Object server) {
       this.server = server;
@@ -216,7 +209,7 @@ public final class HazardService {
     final List<Curve> data;
 
     ImtCurves(Imt imt, List<Curve> data) {
-      this.imt = new Parameter(imtShortLabel(imt), imt.name());
+      this.imt = new Parameter(ServletUtil.imtShortLabel(imt), imt.name());
       this.data = data;
     }
   }
@@ -233,27 +226,36 @@ public final class HazardService {
 
   private static final String TOTAL_KEY = "Total";
 
-  private static final class ResultBuilder {
+  private static final class ResponseBuilder {
 
     Stopwatch timer;
     Request request;
-
     Map<Imt, Map<SourceType, MutableXySequence>> componentMaps;
     Map<Imt, MutableXySequence> totalMap;
 
-    ResultBuilder hazard(Hazard hazardResult) {
+    ResponseBuilder timer(Stopwatch timer) {
+      this.timer = timer;
+      return this;
+    }
+
+    ResponseBuilder request(Request request) {
+      this.request = request;
+      return this;
+    }
+
+    ResponseBuilder hazard(Hazard hazard) {
       // TODO necessary??
       checkState(totalMap == null, "Hazard has already been added to this builder");
 
       componentMaps = new EnumMap<>(Imt.class);
       totalMap = new EnumMap<>(Imt.class);
 
-      var typeTotalMaps = curvesBySource(hazardResult);
+      var typeTotalMaps = curvesBySource(hazard);
 
-      for (var imt : hazardResult.curves().keySet()) {
+      for (var imt : hazard.curves().keySet()) {
 
         /* Total curve for IMT. */
-        XySequence.addToMap(imt, totalMap, hazardResult.curves().get(imt));
+        XySequence.addToMap(imt, totalMap, hazard.curves().get(imt));
 
         /* Source component curves for IMT. */
         var typeTotalMap = typeTotalMaps.get(imt);
@@ -272,16 +274,6 @@ public final class HazardService {
       return this;
     }
 
-    ResultBuilder timer(Stopwatch timer) {
-      this.timer = timer;
-      return this;
-    }
-
-    ResultBuilder request(Request request) {
-      this.request = request;
-      return this;
-    }
-
     Response build() {
       var hazards = new ArrayList<ImtCurves>();
 
@@ -301,13 +293,13 @@ public final class HazardService {
               updateCurve(request, typeMap.get(type), imt)));
         }
 
-        hazards.add(new ImtCurves(imt, List.copyOf(curves)));
+        hazards.add(new ImtCurves(imt, curves));
       }
 
       Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer);
       var response = new Response(
           new ResponseMetadata(server),
-          List.copyOf(hazards));
+          hazards);
 
       return response;
     }
@@ -351,13 +343,15 @@ public final class HazardService {
     return limit;
   }
 
-  private static String imtShortLabel(Imt imt) {
-    if (imt.equals(Imt.PGA) || imt.equals(Imt.PGV)) {
-      return imt.name();
-    } else if (imt.isSA()) {
-      return imt.period() + " s";
-    }
-    return imt.toString();
+  /* Read the 'imt' query values; can be comma-delimited. */
+  static Set<Imt> readImts(HttpRequest<?> http) {
+    return http.getParameters()
+        .getAll("imt")// TODO where are key strings?
+        .stream()
+        .map(s -> s.split(","))
+        .flatMap(Arrays::stream)
+        .map(Imt::valueOf)
+        .collect(toCollection(() -> EnumSet.noneOf(Imt.class)));
   }
 
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/meta/MetaUtil.java b/src/main/java/gov/usgs/earthquake/nshmp/www/meta/MetaUtil.java
index 74c7c7ab4..a1a2c0684 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/meta/MetaUtil.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/meta/MetaUtil.java
@@ -39,7 +39,7 @@ public final class MetaUtil {
       JsonObject json = new JsonObject();
       json.add("location", loc);
       json.addProperty("vs30", site.vs30());
-      json.addProperty("vsInfered", site.vsInferred());
+      json.addProperty("vsInferred", site.vsInferred());
       json.addProperty("z1p0", Double.isNaN(site.z1p0()) ? null : site.z1p0());
       json.addProperty("z2p5", Double.isNaN(site.z2p5()) ? null : site.z2p5());
 
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
index 8c1a50c43..6b042038a 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
@@ -6,6 +6,9 @@ import java.util.Optional;
 import java.util.concurrent.ExecutionException;
 import java.util.stream.Collectors;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.common.base.Stopwatch;
 import com.google.common.util.concurrent.ListenableFuture;
 
@@ -17,12 +20,11 @@ import gov.usgs.earthquake.nshmp.geo.Location;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
 import gov.usgs.earthquake.nshmp.www.RateController;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
-import gov.usgs.earthquake.nshmp.www.ServicesUtil;
-import gov.usgs.earthquake.nshmp.www.ServletUtil;
-import gov.usgs.earthquake.nshmp.www.WsUtils;
 import gov.usgs.earthquake.nshmp.www.ServicesUtil.Key;
 import gov.usgs.earthquake.nshmp.www.ServicesUtil.ServiceQueryData;
 import gov.usgs.earthquake.nshmp.www.ServicesUtil.ServiceRequestData;
+import gov.usgs.earthquake.nshmp.www.ServletUtil;
+import gov.usgs.earthquake.nshmp.www.WsUtils;
 import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter;
 import gov.usgs.earthquake.nshmp.www.meta.Metadata;
 import gov.usgs.earthquake.nshmp.www.meta.Metadata.DefaultParameters;
@@ -39,6 +41,8 @@ import jakarta.inject.Singleton;
 @Singleton
 public final class RateService {
 
+  static final Logger LOG = LoggerFactory.getLogger(RateService.class);
+
   /*
    * Developer notes:
    *
@@ -63,7 +67,7 @@ public final class RateService {
       var json = ServletUtil.GSON.toJson(response);
       return HttpResponse.ok(json);
     } catch (Exception e) {
-      return ServicesUtil.handleError(e, service.name, request.getUri().getPath());
+      return ServletUtil.error(LOG, e, service.name, request.getUri().getPath());
     }
   }
 
@@ -92,7 +96,7 @@ public final class RateService {
       var svcResponse = ServletUtil.GSON.toJson(response);
       return HttpResponse.ok(svcResponse);
     } catch (Exception e) {
-      return ServicesUtil.handleError(e, service.name, request.getUri().getPath());
+      return ServletUtil.error(LOG, e, service.name, request.getUri().getPath());
     }
   }
 
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java
index 4a7882cdb..344514c8d 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java
@@ -1,11 +1,12 @@
 package gov.usgs.earthquake.nshmp.www.services;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import gov.usgs.earthquake.nshmp.model.Models;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
-import gov.usgs.earthquake.nshmp.www.ServicesUtil;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.SourceLogicTreesController;
-
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
 import jakarta.inject.Singleton;
@@ -18,6 +19,8 @@ import jakarta.inject.Singleton;
 @Singleton
 public class SourceLogicTreesService {
 
+  static final Logger LOG = LoggerFactory.getLogger(SourceLogicTreesService.class);
+
   private static final String NAME = "Source Logic Trees";
 
   /** SourceLogicTreesController.doGetMetadata() handler */
@@ -34,7 +37,7 @@ public class SourceLogicTreesService {
           .build();
       return HttpResponse.ok(ServletUtil.GSON.toJson(response));
     } catch (Exception e) {
-      return ServicesUtil.handleError(e, NAME, url);
+      return ServletUtil.error(LOG, e, NAME, url);
     }
   }
 
@@ -53,7 +56,7 @@ public class SourceLogicTreesService {
           .build();
       return HttpResponse.ok(ServletUtil.GSON.toJson(response));
     } catch (Exception e) {
-      return ServicesUtil.handleError(e, NAME, url);
+      return ServletUtil.error(LOG, e, NAME, url);
     }
   }
 
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
index bd22e1c37..75cf76b28 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
@@ -1,8 +1,14 @@
 package gov.usgs.earthquake.nshmp.www.services;
 
+import static java.util.stream.Collectors.toList;
+
+import java.util.List;
 import java.util.Map;
 import java.util.Set;
 
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
 import com.google.common.base.Stopwatch;
 import com.google.gson.Gson;
 import com.google.gson.GsonBuilder;
@@ -12,10 +18,10 @@ import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.gmm.NehrpSiteClass;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
-import gov.usgs.earthquake.nshmp.www.ServicesUtil;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
 import gov.usgs.earthquake.nshmp.www.meta.Metadata;
+import gov.usgs.earthquake.nshmp.www.meta.Parameter;
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
 import jakarta.inject.Singleton;
@@ -34,6 +40,8 @@ public class SourceServices {
   private static final String SERVICE_DESCRIPTION =
       "Utilities for querying earthquake source models";
 
+  static final Logger LOG = LoggerFactory.getLogger(RateService.class);
+
   public static final Gson GSON;
 
   static {
@@ -57,7 +65,7 @@ public class SourceServices {
       var json = GSON.toJson(response);
       return HttpResponse.ok(json);
     } catch (Exception e) {
-      return ServicesUtil.handleError(e, NAME, url);
+      return ServletUtil.error(LOG, e, NAME, url);
     }
   }
 
@@ -105,11 +113,18 @@ public class SourceServices {
     String name;
     Set<Gmm> gmms;
     Map<NehrpSiteClass, Double> siteClasses;
+    List<Parameter> imts;
 
     public SourceModel(HazardModel model) {
       name = model.name();
       gmms = model.gmms();
       siteClasses = model.siteClasses();
+      imts = model.gmms().stream()
+          .map(Gmm::supportedImts)
+          .flatMap(Set::stream)
+          .sorted()
+          .map(imt -> new Parameter(ServletUtil.imtShortLabel(imt), imt.name()))
+          .collect(toList());
     }
 
     // public static List<SourceModel> getList() {
-- 
GitLab


From a7ca58755c4c63b0185aa0288328387bad0d633a Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Tue, 1 Feb 2022 16:27:21 -0700
Subject: [PATCH 4/7] migrated disagg services to micronaut

---
 .../nshmp/www/hazard/DisaggController.java    | 155 +++++++++
 .../nshmp/www/hazard/DisaggService.java       | 320 ++++++++++++++++++
 2 files changed, 475 insertions(+)
 create mode 100644 src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java
 create mode 100644 src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java
new file mode 100644
index 000000000..ac2647922
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java
@@ -0,0 +1,155 @@
+package gov.usgs.earthquake.nshmp.www.hazard;
+
+import static com.google.common.base.Preconditions.checkArgument;
+
+import java.util.Map;
+import java.util.Set;
+
+import gov.usgs.earthquake.nshmp.gmm.Imt;
+import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
+import gov.usgs.earthquake.nshmp.www.ServletUtil;
+import io.micronaut.core.annotation.Nullable;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import io.micronaut.http.annotation.Controller;
+import io.micronaut.http.annotation.Get;
+import io.micronaut.http.annotation.PathVariable;
+import io.micronaut.http.annotation.QueryValue;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.media.Schema;
+import io.swagger.v3.oas.annotations.responses.ApiResponse;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.inject.Inject;
+
+/**
+ * Micronaut web service controller for disaggregation of probabilistic seismic
+ * hazard.
+ *
+ * @author U.S. Geological Survey
+ */
+@Tag(
+    name = "Disaggregation",
+    description = "USGS NSHMP hazard disaggregation service")
+@Controller("/disagg")
+public class DisaggController {
+
+  @Inject
+  private NshmpMicronautServlet servlet;
+
+  @Operation(
+      summary = "Disaggregation model and service metadata",
+      description = "Returns details of the installed model and service request parameters")
+  @ApiResponse(
+      description = "Disaggregation service metadata",
+      responseCode = "200")
+  @Get
+  public HttpResponse<String> doGetMetadata(HttpRequest<?> http) {
+    try {
+      return DisaggService.getMetadata(http);
+    } catch (Exception e) {
+      return ServletUtil.error(
+          DisaggService.LOG, e,
+          DisaggService.NAME,
+          http.getUri().toString());
+    }
+  }
+
+  /**
+   * @param longitude Longitude in the range [-360..360]°.
+   * @param latitude Latitude in the range [-90..90]°.
+   * @param vs30 Site Vs30 value in the range [150..3000] m/s.
+   * @param returnPeriod The return period of the target ground motion, or
+   *        intensity measure level (IML), in the range [1..20000] years.
+   * @param imt Optional IMTs at which to run disaggregations. If none are
+   *        supplied then the default set of supported IMTs for the installed
+   *        model is used. Note that a model may not support all the values
+   *        listed below (see disagreggation metadata). Responses for numerous
+   *        IMT's are quite large, on the order of MB. Multiple IMTs may be
+   *        comma delimited, e.g. @code{?imt=PGA,SA0p2,SA1P0}.
+   */
+  @Operation(
+      summary = "Disaggregate hazard at a specified return period",
+      description = "Returns a hazard disaggregation computed from the installed model")
+  @ApiResponse(
+      description = "Disaggregation",
+      responseCode = "200")
+  @Get(uri = "rp/{longitude}/{latitude}/{vs30}/{returnPeriod}{?imt}")
+  public HttpResponse<String> doGetDisaggReturnPeriod(
+      HttpRequest<?> http,
+      @Schema(
+          minimum = "-360",
+          maximum = "360") @PathVariable double longitude,
+      @Schema(
+          minimum = "-90",
+          maximum = "90") @PathVariable double latitude,
+      @Schema(
+          minimum = "150",
+          maximum = "3000") @PathVariable double vs30,
+      @Schema(
+          minimum = "150",
+          maximum = "3000") @PathVariable double returnPeriod,
+      @Schema() @QueryValue @Nullable Set<Imt> imt) {
+    try {
+      Set<Imt> imts = HazardService.readImts(http);
+      DisaggService.RequestRp request = new DisaggService.RequestRp(
+          http,
+          longitude, latitude, vs30,
+          returnPeriod,
+          imts);
+      return DisaggService.getDisaggRp(request);
+    } catch (Exception e) {
+      return ServletUtil.error(
+          DisaggService.LOG, e,
+          DisaggService.NAME,
+          http.getUri().toString());
+    }
+  }
+
+  /**
+   * @param longitude Longitude in the range [-360..360]°.
+   * @param latitude Latitude in decimal degrees [-90..90]°.
+   * @param vs30 Site Vs30 value in the range [150..3000] m/s.
+   */
+  @Operation(
+      summary = "Disaggregate hazard at specified IMLs",
+      description = "Returns a hazard disaggregation computed from the installed model")
+  @ApiResponse(
+      description = "Disaggregation",
+      responseCode = "200")
+  @Get(uri = "iml/{longitude}/{latitude}/{vs30}")
+  public HttpResponse<String> doGetDisaggIml(
+      HttpRequest<?> http,
+      @Schema(
+          minimum = "-360",
+          maximum = "360") @PathVariable double longitude,
+      @Schema(
+          minimum = "-90",
+          maximum = "90") @PathVariable double latitude,
+      @Schema(
+          minimum = "150",
+          maximum = "3000") @PathVariable double vs30) {
+
+    /*
+     * Developer notes:
+     *
+     * It is awkward to support IMT=#; numerous unique keys that may or may not
+     * be present yields a clunky swagger interface. The disagg-iml endpoint
+     * requires one or more IMT=# query arguments. Documented in example.
+     */
+
+    try {
+      Map<Imt, Double> imtImlMap = http.getParameters().asMap(Imt.class, Double.class);
+      checkArgument(!imtImlMap.isEmpty(), "No IMLs supplied");
+      DisaggService.RequestIml request = new DisaggService.RequestIml(
+          http,
+          longitude, latitude, vs30,
+          imtImlMap);
+      return DisaggService.getDisaggIml(request);
+    } catch (Exception e) {
+      return ServletUtil.error(
+          DisaggService.LOG, e,
+          DisaggService.NAME,
+          http.getUri().toString());
+    }
+  }
+}
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
new file mode 100644
index 000000000..cac2e3537
--- /dev/null
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
@@ -0,0 +1,320 @@
+package gov.usgs.earthquake.nshmp.www.hazard;
+
+import static java.util.stream.Collectors.toList;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.ExecutionException;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.base.Stopwatch;
+import com.google.common.collect.Range;
+
+import gov.usgs.earthquake.nshmp.calc.CalcConfig;
+import gov.usgs.earthquake.nshmp.calc.Disaggregation;
+import gov.usgs.earthquake.nshmp.calc.Hazard;
+import gov.usgs.earthquake.nshmp.calc.HazardCalcs;
+import gov.usgs.earthquake.nshmp.calc.Site;
+import gov.usgs.earthquake.nshmp.geo.Location;
+import gov.usgs.earthquake.nshmp.gmm.Imt;
+import gov.usgs.earthquake.nshmp.model.HazardModel;
+import gov.usgs.earthquake.nshmp.www.ResponseBody;
+import gov.usgs.earthquake.nshmp.www.ServletUtil;
+import gov.usgs.earthquake.nshmp.www.hazard.HazardService.UsageMetadata;
+import gov.usgs.earthquake.nshmp.www.meta.Metadata;
+import gov.usgs.earthquake.nshmp.www.meta.Parameter;
+import io.micronaut.http.HttpRequest;
+import io.micronaut.http.HttpResponse;
+import jakarta.inject.Singleton;
+
+/**
+ * Disaggregation service.
+ *
+ * @see DisaggController
+ * @author U.S. Geological Survey
+ */
+@Singleton
+public final class DisaggService {
+
+  /*
+   * Developer notes:
+   *
+   * Same query structure as hazard service, but either return period and imt(s)
+   * OR imt=iml pairs
+   */
+
+  static final String NAME = "Disaggregation Service";
+  static final Logger LOG = LoggerFactory.getLogger(DisaggService.class);
+
+  private static Range<Double> rpRange = Range.closed(1.0, 20000.0);
+  private static Range<Double> imlRange = Range.closed(0.0001, 8.0);
+
+  /** HazardController.doGetMetadata() handler. */
+  public static HttpResponse<String> getMetadata(HttpRequest<?> request) {
+    var url = request.getUri().toString();
+    var usage = new UsageMetadata(ServletUtil.model());
+    var response = ResponseBody.usage()
+        .name(NAME)
+        .url(url)
+        .request(url)
+        .response(usage)
+        .build();
+    var svcResponse = ServletUtil.GSON.toJson(response);
+    return HttpResponse.ok(svcResponse);
+  }
+
+  /** HazardController.doGetDisaggIml() handler. */
+  public static HttpResponse<String> getDisaggIml(RequestIml request)
+      throws InterruptedException, ExecutionException {
+    var stopwatch = Stopwatch.createStarted();
+    var disagg = calcDisaggIml(request);
+    var response = new ResponseBuilder()
+        .timer(stopwatch)
+        .request(request)
+        .disagg(disagg)
+        .build();
+    var body = ResponseBody.success()
+        .name(NAME)
+        .url(request.http.getUri().toString())
+        .request(request)
+        .response(response)
+        .build();
+    String svcResponse = ServletUtil.GSON2.toJson(body);
+    return HttpResponse.ok(svcResponse);
+  }
+
+  /** HazardController.doGetDisaggRp() handler. */
+  public static HttpResponse<String> getDisaggRp(RequestRp request)
+      throws InterruptedException, ExecutionException {
+    var stopwatch = Stopwatch.createStarted();
+    var disagg = calcDisaggRp(request);
+    var response = new ResponseBuilder()
+        .timer(stopwatch)
+        .request(request)
+        .disagg(disagg)
+        .build();
+    var body = ResponseBody.success()
+        .name(NAME)
+        .url(request.http.getUri().toString())
+        .request(request)
+        .response(response)
+        .build();
+    String svcResponse = ServletUtil.GSON2.toJson(body);
+    return HttpResponse.ok(svcResponse);
+  }
+
+  /*
+   * Developer notes:
+   *
+   * If disaggIml, we need to do the calculation for single XySeqs if disaggRp,
+   * we don't know the imls so must compute hazard over the full curve
+   *
+   */
+
+  static Disaggregation calcDisaggIml(RequestIml request)
+      throws InterruptedException, ExecutionException {
+
+    HazardModel model = ServletUtil.model();
+
+    // modify config to include service endpoint arguments
+    CalcConfig config = CalcConfig.copyOf(model.config())
+        .imts(request.imls.keySet())
+        .build();
+
+    // TODO this needs to pick up SiteData, centralize
+    Site site = Site.builder()
+        .location(Location.create(request.longitude, request.latitude))
+        .vs30(request.vs30)
+        .build();
+
+    // use HazardService.calcHazard() instead?
+    CompletableFuture<Hazard> hazFuture = CompletableFuture.supplyAsync(
+        () -> HazardCalcs.hazard(
+            model, config, site,
+            ServletUtil.CALC_EXECUTOR),
+        ServletUtil.TASK_EXECUTOR);
+
+    Hazard hazard = hazFuture.get();
+
+    CompletableFuture<Disaggregation> disaggfuture = CompletableFuture.supplyAsync(
+        () -> Disaggregation.atImls(
+            hazard, request.imls,
+            ServletUtil.CALC_EXECUTOR),
+        ServletUtil.TASK_EXECUTOR);
+
+    Disaggregation disagg = disaggfuture.get();
+
+    return disagg;
+  }
+
+  static Disaggregation calcDisaggRp(RequestRp request)
+      throws InterruptedException, ExecutionException {
+
+    HazardModel model = ServletUtil.model();
+
+    // modify config to include service endpoint arguments
+    CalcConfig config = CalcConfig.copyOf(model.config())
+        .imts(request.imts)
+        .build();
+
+    // TODO this needs to pick up SiteData, centralize
+    Site site = Site.builder()
+        .location(Location.create(request.longitude, request.latitude))
+        .vs30(request.vs30)
+        .build();
+
+    CompletableFuture<Hazard> hazFuture = CompletableFuture.supplyAsync(
+        () -> HazardCalcs.hazard(
+            model, config, site,
+            ServletUtil.CALC_EXECUTOR),
+        ServletUtil.TASK_EXECUTOR);
+
+    Hazard hazard = hazFuture.get();
+
+    CompletableFuture<Disaggregation> disaggfuture = CompletableFuture.supplyAsync(
+        () -> Disaggregation.atReturnPeriod(
+            hazard, request.returnPeriod,
+            ServletUtil.CALC_EXECUTOR),
+        ServletUtil.TASK_EXECUTOR);
+
+    Disaggregation disagg = disaggfuture.get();
+
+    return disagg;
+  }
+
+  static final class RequestIml {
+
+    final transient HttpRequest<?> http;
+    final double longitude;
+    final double latitude;
+    final double vs30;
+    final Map<Imt, Double> imls;
+
+    RequestIml(
+        HttpRequest<?> http,
+        double longitude,
+        double latitude,
+        double vs30,
+        Map<Imt, Double> imls) {
+
+      this.http = http;
+      this.longitude = longitude;
+      this.latitude = latitude;
+      this.vs30 = vs30;
+      this.imls = imls;
+    }
+  }
+
+  static final class RequestRp {
+
+    final transient HttpRequest<?> http;
+    final double longitude;
+    final double latitude;
+    final double vs30;
+    final double returnPeriod;
+    final Set<Imt> imts;
+
+    RequestRp(
+        HttpRequest<?> http,
+        double longitude,
+        double latitude,
+        double vs30,
+        double returnPeriod,
+        Set<Imt> imts) {
+
+      this.http = http;
+      this.longitude = longitude;
+      this.latitude = latitude;
+      this.vs30 = vs30;
+      this.returnPeriod = returnPeriod;
+      this.imts = imts.isEmpty()
+          ? ServletUtil.model().config().hazard.imts
+          : imts;
+    }
+  }
+
+  private static final class ResponseMetadata {
+    final Object server;
+    final String rlabel = "Closest Distance, rRup (km)";
+    final String mlabel = "Magnitude (Mw)";
+    final String εlabel = "% Contribution to Hazard";
+    final Object εbins;
+
+    ResponseMetadata(Object server, Object εbins) {
+      this.server = server;
+      this.εbins = εbins;
+    }
+  }
+
+  private static final class Response {
+    final ResponseMetadata metadata;
+    final List<ImtDisagg> disaggs;
+
+    Response(ResponseMetadata metadata, List<ImtDisagg> disaggs) {
+      this.metadata = metadata;
+      this.disaggs = disaggs;
+    }
+  }
+
+  private static final class ImtDisagg {
+    final Parameter imt;
+    final Object data;
+
+    ImtDisagg(Imt imt, Object data) {
+      this.imt = new Parameter(
+          ServletUtil.imtShortLabel(imt),
+          imt.name());
+      this.data = data;
+    }
+  }
+
+  private static final class ResponseBuilder {
+
+    Stopwatch timer;
+    Optional<RequestRp> requestRp = Optional.empty();
+    Optional<RequestIml> requestIml = Optional.empty();
+    Disaggregation disagg;
+
+    ResponseBuilder timer(Stopwatch timer) {
+      this.timer = timer;
+      return this;
+    }
+
+    ResponseBuilder request(Object request) {
+      if (request instanceof RequestRp) {
+        requestRp = Optional.of((RequestRp) request);
+        return this;
+      }
+      requestIml = Optional.of((RequestIml) request);
+      return this;
+    }
+
+    ResponseBuilder disagg(Disaggregation disagg) {
+      this.disagg = disagg;
+      return this;
+    }
+
+    Response build() {
+
+      Set<Imt> imts = requestRp.isPresent()
+          ? requestRp.orElseThrow().imts
+          : requestIml.orElseThrow().imls.keySet();
+
+      List<ImtDisagg> disaggs = imts.stream()
+          .map(imt -> new ImtDisagg(imt, disagg.toJson(imt)))
+          .collect(toList());
+
+      Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer);
+
+      return new Response(
+          new ResponseMetadata(server, disagg.εBins()),
+          disaggs);
+    }
+  }
+
+}
-- 
GitLab


From 515827a88fedd18e3b06933f86c1425a8bdb9b9b Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Tue, 1 Feb 2022 16:56:06 -0700
Subject: [PATCH 5/7] nested response classes

---
 .../earthquake/nshmp/www/ServletUtil.java     |  18 ++
 .../nshmp/www/hazard/DisaggService.java       | 118 ++++++------
 .../nshmp/www/hazard/HazardService.java       | 182 +++++++++---------
 .../earthquake/nshmp/www/meta/Metadata.java   |  34 +---
 .../nshmp/www/services/RateService.java       |   5 +-
 .../nshmp/www/services/SourceServices.java    |   6 +-
 6 files changed, 174 insertions(+), 189 deletions(-)

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
index c3221f244..a357ab271 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
@@ -17,6 +17,7 @@ import java.util.concurrent.Executors;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
+import com.google.common.base.Stopwatch;
 import com.google.common.util.concurrent.ListeningExecutorService;
 import com.google.common.util.concurrent.MoreExecutors;
 import com.google.gson.Gson;
@@ -183,4 +184,21 @@ public class ServletUtil {
     return imt.toString();
   }
 
+  public static Object serverData(int threads, Stopwatch timer) {
+    return new Server(threads, timer);
+  }
+
+  private static class Server {
+
+    final int threads;
+    final String timer;
+    final String version;
+
+    Server(int threads, Stopwatch timer) {
+      this.threads = threads;
+      this.timer = timer.toString();
+      this.version = "TODO where to get version?";
+    }
+  }
+
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
index cac2e3537..55a955b99 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
@@ -25,8 +25,7 @@ import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
-import gov.usgs.earthquake.nshmp.www.hazard.HazardService.UsageMetadata;
-import gov.usgs.earthquake.nshmp.www.meta.Metadata;
+import gov.usgs.earthquake.nshmp.www.hazard.HazardService.Metadata;
 import gov.usgs.earthquake.nshmp.www.meta.Parameter;
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
@@ -57,7 +56,7 @@ public final class DisaggService {
   /** HazardController.doGetMetadata() handler. */
   public static HttpResponse<String> getMetadata(HttpRequest<?> request) {
     var url = request.getUri().toString();
-    var usage = new UsageMetadata(ServletUtil.model());
+    var usage = new Metadata(ServletUtil.model());
     var response = ResponseBody.usage()
         .name(NAME)
         .url(url)
@@ -73,7 +72,7 @@ public final class DisaggService {
       throws InterruptedException, ExecutionException {
     var stopwatch = Stopwatch.createStarted();
     var disagg = calcDisaggIml(request);
-    var response = new ResponseBuilder()
+    var response = new Response.Builder()
         .timer(stopwatch)
         .request(request)
         .disagg(disagg)
@@ -93,7 +92,7 @@ public final class DisaggService {
       throws InterruptedException, ExecutionException {
     var stopwatch = Stopwatch.createStarted();
     var disagg = calcDisaggRp(request);
-    var response = new ResponseBuilder()
+    var response = new Response.Builder()
         .timer(stopwatch)
         .request(request)
         .disagg(disagg)
@@ -238,83 +237,82 @@ public final class DisaggService {
     }
   }
 
-  private static final class ResponseMetadata {
-    final Object server;
-    final String rlabel = "Closest Distance, rRup (km)";
-    final String mlabel = "Magnitude (Mw)";
-    final String εlabel = "% Contribution to Hazard";
-    final Object εbins;
-
-    ResponseMetadata(Object server, Object εbins) {
-      this.server = server;
-      this.εbins = εbins;
-    }
-  }
-
   private static final class Response {
-    final ResponseMetadata metadata;
+    final Response.Metadata metadata;
     final List<ImtDisagg> disaggs;
 
-    Response(ResponseMetadata metadata, List<ImtDisagg> disaggs) {
+    Response(Response.Metadata metadata, List<ImtDisagg> disaggs) {
       this.metadata = metadata;
       this.disaggs = disaggs;
     }
-  }
 
-  private static final class ImtDisagg {
-    final Parameter imt;
-    final Object data;
+    private static final class Metadata {
+      final Object server;
+      final String rlabel = "Closest Distance, rRup (km)";
+      final String mlabel = "Magnitude (Mw)";
+      final String εlabel = "% Contribution to Hazard";
+      final Object εbins;
 
-    ImtDisagg(Imt imt, Object data) {
-      this.imt = new Parameter(
-          ServletUtil.imtShortLabel(imt),
-          imt.name());
-      this.data = data;
+      Metadata(Object server, Object εbins) {
+        this.server = server;
+        this.εbins = εbins;
+      }
     }
-  }
 
-  private static final class ResponseBuilder {
+    private static final class Builder {
 
-    Stopwatch timer;
-    Optional<RequestRp> requestRp = Optional.empty();
-    Optional<RequestIml> requestIml = Optional.empty();
-    Disaggregation disagg;
+      Stopwatch timer;
+      Optional<RequestRp> requestRp = Optional.empty();
+      Optional<RequestIml> requestIml = Optional.empty();
+      Disaggregation disagg;
 
-    ResponseBuilder timer(Stopwatch timer) {
-      this.timer = timer;
-      return this;
-    }
+      Builder timer(Stopwatch timer) {
+        this.timer = timer;
+        return this;
+      }
 
-    ResponseBuilder request(Object request) {
-      if (request instanceof RequestRp) {
-        requestRp = Optional.of((RequestRp) request);
+      Builder request(Object request) {
+        if (request instanceof RequestRp) {
+          requestRp = Optional.of((RequestRp) request);
+          return this;
+        }
+        requestIml = Optional.of((RequestIml) request);
         return this;
       }
-      requestIml = Optional.of((RequestIml) request);
-      return this;
-    }
 
-    ResponseBuilder disagg(Disaggregation disagg) {
-      this.disagg = disagg;
-      return this;
-    }
+      Builder disagg(Disaggregation disagg) {
+        this.disagg = disagg;
+        return this;
+      }
 
-    Response build() {
+      Response build() {
 
-      Set<Imt> imts = requestRp.isPresent()
-          ? requestRp.orElseThrow().imts
-          : requestIml.orElseThrow().imls.keySet();
+        Set<Imt> imts = requestRp.isPresent()
+            ? requestRp.orElseThrow().imts
+            : requestIml.orElseThrow().imls.keySet();
 
-      List<ImtDisagg> disaggs = imts.stream()
-          .map(imt -> new ImtDisagg(imt, disagg.toJson(imt)))
-          .collect(toList());
+        List<ImtDisagg> disaggs = imts.stream()
+            .map(imt -> new ImtDisagg(imt, disagg.toJson(imt)))
+            .collect(toList());
 
-      Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer);
+        Object server = ServletUtil.serverData(ServletUtil.THREAD_COUNT, timer);
 
-      return new Response(
-          new ResponseMetadata(server, disagg.εBins()),
-          disaggs);
+        return new Response(
+            new Response.Metadata(server, disagg.εBins()),
+            disaggs);
+      }
     }
   }
 
+  private static final class ImtDisagg {
+    final Parameter imt;
+    final Object data;
+
+    ImtDisagg(Imt imt, Object data) {
+      this.imt = new Parameter(
+          ServletUtil.imtShortLabel(imt),
+          imt.name());
+      this.data = data;
+    }
+  }
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
index 556dffea9..fb51309a0 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
@@ -36,7 +36,6 @@ import gov.usgs.earthquake.nshmp.model.SourceType;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter;
-import gov.usgs.earthquake.nshmp.www.meta.Metadata;
 import gov.usgs.earthquake.nshmp.www.meta.Parameter;
 import gov.usgs.earthquake.nshmp.www.services.SourceServices.SourceModel;
 import io.micronaut.http.HttpRequest;
@@ -55,10 +54,12 @@ public final class HazardService {
   static final String NAME = "Hazard Service";
   static final Logger LOG = LoggerFactory.getLogger(HazardService.class);
 
+  private static final String TOTAL_KEY = "Total";
+
   /** HazardController.doGetUsage() handler. */
   public static HttpResponse<String> getMetadata(HttpRequest<?> request) {
     var url = request.getUri().toString();
-    var usage = new UsageMetadata(ServletUtil.model());
+    var usage = new Metadata(ServletUtil.model());
     var body = ResponseBody.usage()
         .name(NAME)
         .url(url)
@@ -74,7 +75,7 @@ public final class HazardService {
       throws InterruptedException, ExecutionException {
     var stopwatch = Stopwatch.createStarted();
     var hazard = calcHazard(request);
-    var response = new ResponseBuilder()
+    var response = new Response.Builder()
         .timer(stopwatch)
         .request(request)
         .hazard(hazard)
@@ -123,14 +124,14 @@ public final class HazardService {
     return future.get();
   }
 
-  static class UsageMetadata {
+  static class Metadata {
 
     final SourceModel model;
     final DoubleParameter longitude;
     final DoubleParameter latitude;
     final DoubleParameter vs30;
 
-    UsageMetadata(HazardModel model) {
+    Metadata(HazardModel model) {
       this.model = new SourceModel(model);
       // TODO need min max from model
       longitude = new DoubleParameter(
@@ -184,124 +185,124 @@ public final class HazardService {
     }
   }
 
-  private static final class ResponseMetadata {
-    final Object server;
-    final String xlabel = "Ground Motion (g)";
-    final String ylabel = "Annual Frequency of Exceedence";
-
-    ResponseMetadata(Object server) {
-      this.server = server;
-    }
-  }
-
   private static final class Response {
-    final ResponseMetadata metadata;
+
+    final Metadata metadata;
     final List<ImtCurves> hazardCurves;
 
-    Response(ResponseMetadata metadata, List<ImtCurves> hazardCurves) {
+    Response(Metadata metadata, List<ImtCurves> hazardCurves) {
       this.metadata = metadata;
       this.hazardCurves = hazardCurves;
     }
-  }
-
-  private static final class ImtCurves {
-    final Parameter imt;
-    final List<Curve> data;
-
-    ImtCurves(Imt imt, List<Curve> data) {
-      this.imt = new Parameter(ServletUtil.imtShortLabel(imt), imt.name());
-      this.data = data;
-    }
-  }
 
-  private static final class Curve {
-    final String component;
-    final XySequence values;
+    private static final class Metadata {
+      final Object server;
+      final String xlabel = "Ground Motion (g)";
+      final String ylabel = "Annual Frequency of Exceedence";
 
-    Curve(String component, XySequence values) {
-      this.component = component;
-      this.values = values;
+      Metadata(Object server) {
+        this.server = server;
+      }
     }
-  }
 
-  private static final String TOTAL_KEY = "Total";
+    private static final class Builder {
 
-  private static final class ResponseBuilder {
+      Stopwatch timer;
+      Request request;
+      Map<Imt, Map<SourceType, MutableXySequence>> componentMaps;
+      Map<Imt, MutableXySequence> totalMap;
 
-    Stopwatch timer;
-    Request request;
-    Map<Imt, Map<SourceType, MutableXySequence>> componentMaps;
-    Map<Imt, MutableXySequence> totalMap;
+      Builder timer(Stopwatch timer) {
+        this.timer = timer;
+        return this;
+      }
 
-    ResponseBuilder timer(Stopwatch timer) {
-      this.timer = timer;
-      return this;
-    }
+      Builder request(Request request) {
+        this.request = request;
+        return this;
+      }
 
-    ResponseBuilder request(Request request) {
-      this.request = request;
-      return this;
-    }
+      Builder hazard(Hazard hazard) {
+        // TODO necessary??
+        checkState(totalMap == null, "Hazard has already been added to this builder");
 
-    ResponseBuilder hazard(Hazard hazard) {
-      // TODO necessary??
-      checkState(totalMap == null, "Hazard has already been added to this builder");
+        componentMaps = new EnumMap<>(Imt.class);
+        totalMap = new EnumMap<>(Imt.class);
 
-      componentMaps = new EnumMap<>(Imt.class);
-      totalMap = new EnumMap<>(Imt.class);
+        var typeTotalMaps = curvesBySource(hazard);
 
-      var typeTotalMaps = curvesBySource(hazard);
+        for (var imt : hazard.curves().keySet()) {
 
-      for (var imt : hazard.curves().keySet()) {
+          /* Total curve for IMT. */
+          XySequence.addToMap(imt, totalMap, hazard.curves().get(imt));
 
-        /* Total curve for IMT. */
-        XySequence.addToMap(imt, totalMap, hazard.curves().get(imt));
+          /* Source component curves for IMT. */
+          var typeTotalMap = typeTotalMaps.get(imt);
+          var componentMap = componentMaps.get(imt);
 
-        /* Source component curves for IMT. */
-        var typeTotalMap = typeTotalMaps.get(imt);
-        var componentMap = componentMaps.get(imt);
+          if (componentMap == null) {
+            componentMap = new EnumMap<>(SourceType.class);
+            componentMaps.put(imt, componentMap);
+          }
 
-        if (componentMap == null) {
-          componentMap = new EnumMap<>(SourceType.class);
-          componentMaps.put(imt, componentMap);
+          for (var type : typeTotalMap.keySet()) {
+            XySequence.addToMap(type, componentMap, typeTotalMap.get(type));
+          }
         }
 
-        for (var type : typeTotalMap.keySet()) {
-          XySequence.addToMap(type, componentMap, typeTotalMap.get(type));
-        }
+        return this;
       }
 
-      return this;
-    }
-
-    Response build() {
-      var hazards = new ArrayList<ImtCurves>();
-
-      for (Imt imt : totalMap.keySet()) {
-        var curves = new ArrayList<Curve>();
+      Response build() {
+        var hazards = new ArrayList<ImtCurves>();
 
-        // total curve
-        curves.add(new Curve(
-            TOTAL_KEY,
-            updateCurve(request, totalMap.get(imt), imt)));
+        for (Imt imt : totalMap.keySet()) {
+          var curves = new ArrayList<Curve>();
 
-        // component curves
-        var typeMap = componentMaps.get(imt);
-        for (SourceType type : typeMap.keySet()) {
+          // total curve
           curves.add(new Curve(
-              type.toString(),
-              updateCurve(request, typeMap.get(type), imt)));
+              TOTAL_KEY,
+              updateCurve(request, totalMap.get(imt), imt)));
+
+          // component curves
+          var typeMap = componentMaps.get(imt);
+          for (SourceType type : typeMap.keySet()) {
+            curves.add(new Curve(
+                type.toString(),
+                updateCurve(request, typeMap.get(type), imt)));
+          }
+
+          hazards.add(new ImtCurves(imt, curves));
         }
 
-        hazards.add(new ImtCurves(imt, curves));
+        Object server = ServletUtil.serverData(ServletUtil.THREAD_COUNT, timer);
+        var response = new Response(
+            new Response.Metadata(server),
+            hazards);
+
+        return response;
       }
+    }
+
+  }
+
+  private static final class ImtCurves {
+    final Parameter imt;
+    final List<Curve> data;
+
+    ImtCurves(Imt imt, List<Curve> data) {
+      this.imt = new Parameter(ServletUtil.imtShortLabel(imt), imt.name());
+      this.data = data;
+    }
+  }
 
-      Object server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer);
-      var response = new Response(
-          new ResponseMetadata(server),
-          hazards);
+  private static final class Curve {
+    final String component;
+    final XySequence values;
 
-      return response;
+    Curve(String component, XySequence values) {
+      this.component = component;
+      this.values = values;
     }
   }
 
@@ -353,5 +354,4 @@ public final class HazardService {
         .map(Imt::valueOf)
         .collect(toCollection(() -> EnumSet.noneOf(Imt.class)));
   }
-
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/meta/Metadata.java b/src/main/java/gov/usgs/earthquake/nshmp/www/meta/Metadata.java
index ed8a2973d..84227bdce 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/meta/Metadata.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/meta/Metadata.java
@@ -28,43 +28,11 @@ public final class Metadata {
       this.status = Status.USAGE.toString();
       this.description = description;
       this.syntax = syntax;
-      this.server = serverData(1, Stopwatch.createStarted());
+      this.server = ServletUtil.serverData(1, Stopwatch.createStarted());
       this.parameters = parameters;
     }
   }
 
-  public static Object serverData(int threads, Stopwatch timer) {
-    return new Server(threads, timer);
-  }
-
-  private static class Server {
-
-    final int threads;
-    final String timer;
-    final String version;
-
-    Server(int threads, Stopwatch timer) {
-      this.threads = threads;
-      this.timer = timer.toString();
-      this.version = "TODO where to get version?";
-    }
-
-    // static Component NSHMP_HAZ_COMPONENT = new Component(
-    // NSHMP_HAZ_URL,
-    // Versions.NSHMP_HAZ_VERSION);
-    //
-    // static final class Component {
-    //
-    // final String url;
-    // final String version;
-    //
-    // Component(String url, String version) {
-    // this.url = url;
-    // this.version = version;
-    // }
-    // }
-  }
-
   public static class DefaultParameters {
 
     // final EnumParameter<Edition> edition;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
index 6b042038a..05f2389c6 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
@@ -26,7 +26,6 @@ import gov.usgs.earthquake.nshmp.www.ServicesUtil.ServiceRequestData;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
 import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter;
-import gov.usgs.earthquake.nshmp.www.meta.Metadata;
 import gov.usgs.earthquake.nshmp.www.meta.Metadata.DefaultParameters;
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
@@ -276,7 +275,7 @@ public final class RateService {
     final List<Sequence> data;
 
     ResponseData(ResponseMetadata metadata, EqRate rates, Stopwatch timer) {
-      server = Metadata.serverData(ServletUtil.THREAD_COUNT, timer);
+      server = ServletUtil.serverData(ServletUtil.THREAD_COUNT, timer);
       this.metadata = metadata;
       this.data = buildSequence(rates);
     }
@@ -335,7 +334,7 @@ public final class RateService {
     private Usage(Service service, DefaultParameters parameters) {
       description = service.description;
       this.syntax = service.syntax;
-      server = Metadata.serverData(1, Stopwatch.createStarted());
+      server = ServletUtil.serverData(1, Stopwatch.createStarted());
       this.parameters = parameters;
     }
   }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
index 75cf76b28..f55efc1d7 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
@@ -20,7 +20,6 @@ import gov.usgs.earthquake.nshmp.model.HazardModel;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
-import gov.usgs.earthquake.nshmp.www.meta.Metadata;
 import gov.usgs.earthquake.nshmp.www.meta.Parameter;
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
@@ -80,7 +79,9 @@ public class SourceServices {
 
     public ResponseData() {
       this.description = "Installed source model listing";
-      this.server = Metadata.serverData(ServletUtil.THREAD_COUNT, Stopwatch.createStarted());
+      this.server = ServletUtil.serverData(
+          ServletUtil.THREAD_COUNT,
+          Stopwatch.createStarted());
       // this.parameters = new Parameters();
     }
   }
@@ -122,6 +123,7 @@ public class SourceServices {
       imts = model.gmms().stream()
           .map(Gmm::supportedImts)
           .flatMap(Set::stream)
+          .distinct()
           .sorted()
           .map(imt -> new Parameter(ServletUtil.imtShortLabel(imt), imt.name()))
           .collect(toList());
-- 
GitLab


From cdef4f7e1f1adae52a0c0c6e857969acda47bd45 Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Tue, 1 Feb 2022 17:32:07 -0700
Subject: [PATCH 6/7] formatting and docs

---
 .../nshmp/www/hazard/DisaggController.java           | 12 ++++++------
 .../nshmp/www/hazard/HazardController.java           | 12 ++++++------
 .../earthquake/nshmp/www/hazard/HazardService.java   |  4 ++--
 3 files changed, 14 insertions(+), 14 deletions(-)

diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java
index ac2647922..a440b55a5 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java
@@ -60,12 +60,12 @@ public class DisaggController {
    * @param vs30 Site Vs30 value in the range [150..3000] m/s.
    * @param returnPeriod The return period of the target ground motion, or
    *        intensity measure level (IML), in the range [1..20000] years.
-   * @param imt Optional IMTs at which to run disaggregations. If none are
-   *        supplied then the default set of supported IMTs for the installed
-   *        model is used. Note that a model may not support all the values
-   *        listed below (see disagreggation metadata). Responses for numerous
-   *        IMT's are quite large, on the order of MB. Multiple IMTs may be
-   *        comma delimited, e.g. @code{?imt=PGA,SA0p2,SA1P0}.
+   * @param imt Optional IMTs at which to compute hazard. If none are supplied,
+   *        then the supported set for the installed model is used. Note that a
+   *        model may not support all the values listed below (see
+   *        disagreggation metadata). Responses for numerous IMT's are quite
+   *        large, on the order of MB. Multiple IMTs may be comma delimited,
+   *        e.g. ?imt=PGA,SA0p2,SA1P0.
    */
   @Operation(
       summary = "Disaggregate hazard at a specified return period",
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
index 4dd72d3f5..1452b2b67 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
@@ -57,12 +57,12 @@ public class HazardController {
    * @param vs30 Site Vs30 value in m/s [150..3000]
    * @param truncate Truncate curves at return periods below ~10,000 years
    * @param maxdir Apply max-direction scaling
-   * @param imt Optional IMTs at which to compute hazard. If none are supplied
-   *        then the default set of supported IMTs for the installed model is
-   *        used. Note that a model may not support all the values listed below
-   *        (see disagreggation metadata). Responses for numerous IMT's are
-   *        quite large, on the order of MB. Multiple IMTs may be comma
-   *        delimited, e.g. @code{?imt=PGA,SA0p2,SA1P0}.
+   * @param imt Optional IMTs at which to compute hazard. If none are supplied,
+   *        then the supported set for the installed model is used. Note that a
+   *        model may not support all the values listed below (see
+   *        disagreggation metadata). Responses for numerous IMT's are quite
+   *        large, on the order of MB. Multiple IMTs may be comma delimited,
+   *        e.g. ?imt=PGA,SA0p2,SA1P0.
    *
    */
   @Operation(
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
index fb51309a0..f300a1384 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
@@ -66,7 +66,7 @@ public final class HazardService {
         .request(url)
         .response(usage)
         .build();
-    var json = ServletUtil.GSON.toJson(body);
+    var json = ServletUtil.GSON2.toJson(body);
     return HttpResponse.ok(json);
   }
 
@@ -86,7 +86,7 @@ public final class HazardService {
         .request(request)
         .response(response)
         .build();
-    String json = ServletUtil.GSON.toJson(body);
+    String json = ServletUtil.GSON2.toJson(body);
     return HttpResponse.ok(json);
   }
 
-- 
GitLab


From f6a2da6f4b80692e71e5e227f77ada443b6cccff Mon Sep 17 00:00:00 2001
From: Peter Powers <pmpowers@usgs.gov>
Date: Tue, 1 Feb 2022 18:03:02 -0700
Subject: [PATCH 7/7] spotless formatting

---
 settings.gradle                                             | 6 +++---
 .../java/gov/usgs/earthquake/nshmp/www/ServletUtil.java     | 1 +
 .../usgs/earthquake/nshmp/www/hazard/DisaggController.java  | 1 +
 .../gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java | 1 +
 .../usgs/earthquake/nshmp/www/hazard/HazardController.java  | 1 +
 .../gov/usgs/earthquake/nshmp/www/hazard/HazardService.java | 1 +
 .../gov/usgs/earthquake/nshmp/www/services/RateService.java | 1 +
 .../nshmp/www/services/SourceLogicTreesService.java         | 1 +
 .../usgs/earthquake/nshmp/www/services/SourceServices.java  | 1 +
 9 files changed, 11 insertions(+), 3 deletions(-)

diff --git a/settings.gradle b/settings.gradle
index d1ad3db91..0daffad9b 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -20,9 +20,9 @@ git {
     fetch("https://code.usgs.gov/ghsc/nshmp/nshms/nshm-hawaii.git", {
       name "nshmp-haz-dep--nshm-hi-2021"
       tag "2.0.0"
-    // fetch("https://code.usgs.gov/ghsc/nshmp/nshms/nshm-conus.git", {
-    //   name "nshmp-haz-dep--nshm-conus-2018"
-    //   tag "main"
+      // fetch("https://code.usgs.gov/ghsc/nshmp/nshms/nshm-conus.git", {
+      //   name "nshmp-haz-dep--nshm-conus-2018"
+      //   tag "main"
     })
   }
 }
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
index a357ab271..3e3b7425d 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/ServletUtil.java
@@ -32,6 +32,7 @@ import gov.usgs.earthquake.nshmp.calc.ValueFormat;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.model.HazardModel;
 import gov.usgs.earthquake.nshmp.www.meta.MetaUtil;
+
 import io.micronaut.context.annotation.Value;
 import io.micronaut.context.event.ShutdownEvent;
 import io.micronaut.context.event.StartupEvent;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java
index a440b55a5..680d946ad 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggController.java
@@ -8,6 +8,7 @@ import java.util.Set;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
+
 import io.micronaut.core.annotation.Nullable;
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
index 55a955b99..ac428621e 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/DisaggService.java
@@ -27,6 +27,7 @@ import gov.usgs.earthquake.nshmp.www.ResponseBody;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.hazard.HazardService.Metadata;
 import gov.usgs.earthquake.nshmp.www.meta.Parameter;
+
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
 import jakarta.inject.Singleton;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
index 1452b2b67..0fe36de8a 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardController.java
@@ -5,6 +5,7 @@ import java.util.Set;
 import gov.usgs.earthquake.nshmp.gmm.Imt;
 import gov.usgs.earthquake.nshmp.www.NshmpMicronautServlet;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
+
 import io.micronaut.core.annotation.Nullable;
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
index f300a1384..f668b016e 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/hazard/HazardService.java
@@ -38,6 +38,7 @@ import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter;
 import gov.usgs.earthquake.nshmp.www.meta.Parameter;
 import gov.usgs.earthquake.nshmp.www.services.SourceServices.SourceModel;
+
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
 import jakarta.inject.Singleton;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
index 05f2389c6..d4b81df81 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/RateService.java
@@ -27,6 +27,7 @@ import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
 import gov.usgs.earthquake.nshmp.www.meta.DoubleParameter;
 import gov.usgs.earthquake.nshmp.www.meta.Metadata.DefaultParameters;
+
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
 import jakarta.inject.Singleton;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java
index 344514c8d..01e2d4c21 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceLogicTreesService.java
@@ -7,6 +7,7 @@ import gov.usgs.earthquake.nshmp.model.Models;
 import gov.usgs.earthquake.nshmp.www.ResponseBody;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.SourceLogicTreesController;
+
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
 import jakarta.inject.Singleton;
diff --git a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
index f55efc1d7..b473fd92f 100644
--- a/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
+++ b/src/main/java/gov/usgs/earthquake/nshmp/www/services/SourceServices.java
@@ -21,6 +21,7 @@ import gov.usgs.earthquake.nshmp.www.ResponseBody;
 import gov.usgs.earthquake.nshmp.www.ServletUtil;
 import gov.usgs.earthquake.nshmp.www.WsUtils;
 import gov.usgs.earthquake.nshmp.www.meta.Parameter;
+
 import io.micronaut.http.HttpRequest;
 import io.micronaut.http.HttpResponse;
 import jakarta.inject.Singleton;
-- 
GitLab