From 09893af60911ca959e703c50b4a29a7c118a9368 Mon Sep 17 00:00:00 2001 From: zack-rma Date: Tue, 7 Jul 2026 13:54:05 -0700 Subject: [PATCH 1/2] Initial update to ratings endpoint to support passing retrieved data into create endpoint --- .../cwms/cda/data/dao/JsonRatingUtils.java | 60 ++++++++- .../java/cwms/cda/data/dao/RatingSetDao.java | 8 +- .../cwms/cda/data/rating/remove_office.xsl | 12 ++ .../api/rating/RatingsControllerTestIT.java | 39 ++++++ .../cwms/cda/data/dto/rating/rating_post.json | 126 ++++++++++++++++++ 5 files changed, 240 insertions(+), 5 deletions(-) create mode 100644 cwms-data-api/src/test/resources/cwms/cda/data/dto/rating/rating_post.json diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/JsonRatingUtils.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/JsonRatingUtils.java index 8996891c01..6532264cc9 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/JsonRatingUtils.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/JsonRatingUtils.java @@ -4,6 +4,8 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import hec.data.RatingException; import hec.data.cwmsRating.RatingSet; @@ -54,15 +56,67 @@ public static String jsonToXml(String json) throws IOException, TransformerExcep ObjectMapper om = new ObjectMapper(); JsonNode jsonNode = om.readTree(json); - + ObjectWriter writer; XmlMapper mapper = new XmlMapper(); - ObjectWriter writer = mapper.writer() - .withRootName("ratings"); + JsonNode ratings = jsonNode.findPath("ratings"); + writer = mapper.writer() + .withRootName("ratings"); + if (!ratings.isEmpty()) { + jsonNode = transformJsonStructure(ratings); + } String xml = writer.writeValueAsString(jsonNode); return cleanupXml(xml); } + private static JsonNode transformJsonStructure(JsonNode ratingInput) { + String ratingSpecs = "rating-specs"; + String simpleRating = "simple-rating"; + String ratingTemplates = "rating-templates"; + String ratings = "ratings"; + String name = "name"; + String parametersString = "parameters-string"; + ObjectMapper mapper = new ObjectMapper(); + ObjectNode transformed = mapper.createObjectNode(); + + if (ratingInput.has(ratingSpecs)) { + JsonNode ratingSpec = ratingInput.get(ratingSpecs).get(0); + ObjectNode temp = mapper.createObjectNode(); + ratingSpec.forEachEntry(temp::set); + if (temp.has("name")) { + temp.set("rating-spec-id", temp.get("name")); + temp.remove("name"); + } + temp.set("active", JsonNodeFactory.instance.booleanNode(true)); + transformed.set("rating-spec", temp); + } + if (ratingInput.has(ratingTemplates)) { + JsonNode ratingTemplate = ratingInput.get(ratingTemplates).get(0); + ObjectNode temp = mapper.createObjectNode(); + ratingTemplate.forEachEntry(temp::set); + if (temp.has(parametersString)) { + temp.set("parameters-id", temp.get(parametersString)); + temp.remove(parametersString); + } + if (temp.has(name)) { + temp.set("template-id", temp.get(name)); + temp.remove(name); + } + transformed.set("rating-template", temp); + } + if (ratingInput.has(ratings) && ratingInput.get(ratings).get(0).has(simpleRating)) { + JsonNode rating = ratingInput.get(ratings).get(0).get(simpleRating); + ObjectNode temp = mapper.createObjectNode(); + rating.forEachEntry(temp::set); + if (temp.has(name)) { + temp.set("rating-spec-id", temp.get(name)); + temp.remove(name); + } + transformed.set(simpleRating, temp); + } + return transformed; + } + private static String cleanupXml(String xml) throws TransformerException { // Doing this in steps b/c I'm not good enough at xslt to make it happen at once. diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/RatingSetDao.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/RatingSetDao.java index 48309a6220..2580c44d28 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/RatingSetDao.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/RatingSetDao.java @@ -28,7 +28,6 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.dataformat.xml.XmlMapper; import cwms.cda.data.dto.VerticalDatumInfo; -import cwms.cda.data.dto.rating.RatingSpec; import hec.data.RatingException; import hec.data.cwmsRating.RatingSet; import java.io.IOException; @@ -36,7 +35,6 @@ import java.sql.Timestamp; import java.time.Instant; import java.util.List; - import mil.army.usace.hec.cwms.rating.io.jdbc.ConnectionProvider; import mil.army.usace.hec.cwms.rating.io.jdbc.RatingJdbcFactory; import org.jetbrains.annotations.Nullable; @@ -66,6 +64,12 @@ private static String extractOfficeId(String ratingSet) throws JsonProcessingExc if (!values.isEmpty()) { //Getting the last instance since the order is template, spec, rating office = values.get(values.size() - 1).textValue(); + } else { + values = node.findValues("office"); + if (!values.isEmpty()) { + //Getting the last instance since the order is template, spec, rating + office = values.get(values.size() - 1).textValue(); + } } return office; } diff --git a/cwms-data-api/src/main/resources/cwms/cda/data/rating/remove_office.xsl b/cwms-data-api/src/main/resources/cwms/cda/data/rating/remove_office.xsl index 0b900f70e8..230921319c 100644 --- a/cwms-data-api/src/main/resources/cwms/cda/data/rating/remove_office.xsl +++ b/cwms-data-api/src/main/resources/cwms/cda/data/rating/remove_office.xsl @@ -18,9 +18,21 @@ + + + + + + + + + + + + diff --git a/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTestIT.java index e97f4dd7d5..61d605b38d 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTestIT.java @@ -546,5 +546,44 @@ void test_1206_rating_create_xml() throws IOException { .statusCode(is(HttpServletResponse.SC_CREATED)); } + @Test + void test_1758_rating_post() throws IOException { + TestAccounts.KeyUser user = TestAccounts.KeyUser.SPK_NORMAL; + String ratingId = "FSMI.Stage;Flow.EXSA.PRODUCTION"; + ratingId = ratingId.replaceAll("FSMI", EXISTING_LOC); + String body = readResourceFile("cwms/cda/data/dto/rating/rating_post.json"); + body = body.replaceAll("FSMI", EXISTING_LOC); + // Create the set + given() + .log().ifValidationFails(LogDetail.ALL,true) + .contentType(Formats.JSONV2) + .accept(Formats.JSONV2) + .body(body) + .header("Authorization", user.toHeaderValue()) + .queryParam(OFFICE, SPK) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/ratings") + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)); + + given() + .log().ifValidationFails(LogDetail.ALL,true) + .accept(Formats.JSONV2) + .queryParam(OFFICE, SPK) + .when() + .redirects().follow(true) + .redirects().max(3) + .get("/ratings/" + ratingId) + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_OK)) + .contentType(is(Formats.JSONV2)) + .body("office-id", is(SPK)); + } } diff --git a/cwms-data-api/src/test/resources/cwms/cda/data/dto/rating/rating_post.json b/cwms-data-api/src/test/resources/cwms/cda/data/dto/rating/rating_post.json new file mode 100644 index 0000000000..cd08eb04f0 --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/data/dto/rating/rating_post.json @@ -0,0 +1,126 @@ +{ + "ratings": { + "query-info": { + "time-of-query": "2026-06-01T16:52:08Z", + "process-query": "PT3.394S", + "format-output": "PT3.050S", + "requested-format": "JSON", + "requested-office-id": "SPK", + "requested-start-time": "2026-05-31T16:52:08Z", + "requested-end-time": "2026-06-01T16:52:08Z", + "requested-items": [ + { + "name": "FSMI.Stage;Flow.EXSA.PRODUCTION", + "unit": "NATIVE", + "datum": "NATIVE" + } + ], + "templates-retrieved": 1, + "total-specifications-retrieved": 1, + "unique-specifications-retrieved": 1, + "total-ratings-retrieved": 1, + "unique-ratings-retrieved": 1 + }, + "rating-templates": [ + { + "office": "SPK", + "name": "Stage;Flow.EXSA", + "parameters-string": "Stage;Flow", + "version": "EXSA", + "ind-parameters": [ + { + "name": "Stage", + "value-lookup-in-range": "LINEAR", + "value-lookup-below-range": "NEAREST", + "value-lookup-above-range": "NEAREST" + } + ], + "dep-parameter": "Flow", + "description": null + } + ], + "rating-specs": [ + { + "office": "SPK", + "name": "FSMI.Stage;Flow.EXSA.PRODUCTION", + "location": "FSMI", + "version": "PRODUCTION", + "source-agency": null, + "time-lookup-in-range": "PREVIOUS", + "time-lookup-before-first": "NEAREST", + "time-lookup-after-last": "PREVIOUS", + "rounding": { + "ind-parameters": [ + "2223456784" + ], + "dep-parameter": "2223456784" + }, + "description": "FSMI Expanded, Shift-Adjusted PRODUCTION Stream Rating" + } + ], + "ratings": [ + { + "simple-rating": { + "office": "SPK", + "name": "FSMI.Stage;Flow.EXSA.PRODUCTION", + "ind-parameters": [ + "Stage (ft)" + ], + "dep-parameter": "Flow (cfs)", + "effective-date": "2007-07-11T20:38:00Z", + "description": null, + "values": [ + [ + 17, + 114000 + ], + [ + 18.75, + 140000 + ], + [ + 20.47, + 165558 + ], + [ + 30, + 313800 + ], + [ + 30.4, + 320500 + ], + [ + 30.84, + 328800 + ], + [ + 31, + 332200 + ], + [ + 31.29, + 339300 + ], + [ + 31.4, + 342500 + ], + [ + 31.62, + 349500 + ], + [ + 32.26, + 372000 + ], + [ + 36, + 520000 + ] + ] + } + } + ] + } +} \ No newline at end of file From ffd0802b1dc2dcc89991b4c58ece883fcabc514a Mon Sep 17 00:00:00 2001 From: zack-rma Date: Tue, 21 Jul 2026 15:24:44 -0700 Subject: [PATCH 2/2] Cleaned up JSON to XML mapping for ratings. Added template storage to test. --- .../cwms/cda/data/dao/JsonRatingUtils.java | 74 ++++++++++++++++--- .../api/rating/RatingsControllerTestIT.java | 18 +++++ .../cwms/cda/api/spk/rating_template.json | 18 +++++ 3 files changed, 99 insertions(+), 11 deletions(-) create mode 100644 cwms-data-api/src/test/resources/cwms/cda/api/spk/rating_template.json diff --git a/cwms-data-api/src/main/java/cwms/cda/data/dao/JsonRatingUtils.java b/cwms-data-api/src/main/java/cwms/cda/data/dao/JsonRatingUtils.java index 6532264cc9..087d86bf05 100644 --- a/cwms-data-api/src/main/java/cwms/cda/data/dao/JsonRatingUtils.java +++ b/cwms-data-api/src/main/java/cwms/cda/data/dao/JsonRatingUtils.java @@ -76,20 +76,13 @@ private static JsonNode transformJsonStructure(JsonNode ratingInput) { String ratings = "ratings"; String name = "name"; String parametersString = "parameters-string"; + String depParameter = "dep-parameter"; + String indParameter = "ind-parameters"; + String rounding = "rounding"; ObjectMapper mapper = new ObjectMapper(); ObjectNode transformed = mapper.createObjectNode(); + JsonNode templateId = mapper.createObjectNode(); - if (ratingInput.has(ratingSpecs)) { - JsonNode ratingSpec = ratingInput.get(ratingSpecs).get(0); - ObjectNode temp = mapper.createObjectNode(); - ratingSpec.forEachEntry(temp::set); - if (temp.has("name")) { - temp.set("rating-spec-id", temp.get("name")); - temp.remove("name"); - } - temp.set("active", JsonNodeFactory.instance.booleanNode(true)); - transformed.set("rating-spec", temp); - } if (ratingInput.has(ratingTemplates)) { JsonNode ratingTemplate = ratingInput.get(ratingTemplates).get(0); ObjectNode temp = mapper.createObjectNode(); @@ -100,10 +93,44 @@ private static JsonNode transformJsonStructure(JsonNode ratingInput) { } if (temp.has(name)) { temp.set("template-id", temp.get(name)); + templateId = temp.get(name); temp.remove(name); } transformed.set("rating-template", temp); } + if (ratingInput.has(ratingSpecs)) { + JsonNode ratingSpec = ratingInput.get(ratingSpecs).get(0); + ObjectNode temp = mapper.createObjectNode(); + ratingSpec.forEachEntry(temp::set); + if (temp.has("name")) { + temp.set("rating-spec-id", temp.get("name")); + temp.remove("name"); + } + temp.set("template-id", templateId); + temp.set("location-id", temp.get("location")); + temp.set("in-range-method", temp.get("time-lookup-in-range")); + temp.set("out-range-low-method", temp.get("time-lookup-before-first")); + temp.set("out-range-high-method", temp.get("time-lookup-after-last")); + temp.set("auto-update", JsonNodeFactory.instance.booleanNode(false)); + temp.set("auto-activate", JsonNodeFactory.instance.booleanNode(false)); + temp.set("auto-migrate-extension", JsonNodeFactory.instance.booleanNode(false)); + if (temp.has(rounding)) { + if (temp.get(rounding).has(depParameter)) { + temp.set("dep-rounding-spec", temp.get(rounding).get(depParameter)); + + } + if (temp.get(rounding).has(indParameter)) { + temp.set("ind-rounding-specs", temp.get(rounding).get(indParameter)); + } + temp.remove(rounding); + } + temp.remove("location"); + temp.remove("time-lookup-in-range"); + temp.remove("time-lookup-after-last"); + temp.remove("time-lookup-before-first"); + temp.set("active", JsonNodeFactory.instance.booleanNode(true)); + transformed.set("rating-spec", temp); + } if (ratingInput.has(ratings) && ratingInput.get(ratings).get(0).has(simpleRating)) { JsonNode rating = ratingInput.get(ratings).get(0).get(simpleRating); ObjectNode temp = mapper.createObjectNode(); @@ -112,6 +139,30 @@ private static JsonNode transformJsonStructure(JsonNode ratingInput) { temp.set("rating-spec-id", temp.get(name)); temp.remove(name); } + if (temp.has(depParameter) && temp.has(indParameter)) { + StringBuilder units = new StringBuilder(); + String depParam = temp.get(depParameter).asText(); + JsonNode indParams = temp.get(indParameter); + for (JsonNode indParam : indParams) { + String param = indParam.asText(); + if (param.contains("(")) { + if (units.length() > 0) { + units.append(";"); + } + units.append(param, param.indexOf("(") + 1, param.indexOf(")")); + } + } + if (depParam.contains("(")) { + if (units.length() > 0) { + units.append(";"); + } + units.append(depParam, depParam.indexOf("(") + 1, depParam.indexOf(")")); + } + temp.set("units-id", JsonNodeFactory.instance.textNode(units.toString())); + temp.remove(depParameter); + temp.remove(indParameter); + } + temp.set("active", JsonNodeFactory.instance.booleanNode(true)); transformed.set(simpleRating, temp); } return transformed; @@ -134,6 +185,7 @@ private static String cleanupXml(String xml) throws TransformerException { for (String attributeName : additionalAttributes) { String template = officeXsl.replace("office-id", attributeName); + template = template.replace("office", attributeName); xml = applyTransform(xml, new StreamSource(new StringReader(template))); } diff --git a/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTestIT.java b/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTestIT.java index 61d605b38d..3aa135c59e 100644 --- a/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTestIT.java +++ b/cwms-data-api/src/test/java/cwms/cda/api/rating/RatingsControllerTestIT.java @@ -553,6 +553,24 @@ void test_1758_rating_post() throws IOException { ratingId = ratingId.replaceAll("FSMI", EXISTING_LOC); String body = readResourceFile("cwms/cda/data/dto/rating/rating_post.json"); body = body.replaceAll("FSMI", EXISTING_LOC); + String template = readResourceFile("cwms/cda/api/spk/rating_template.json"); + + //Create Template + given() + .log().ifValidationFails(LogDetail.ALL,true) + .contentType(Formats.JSONV2) + .body(template) + .header("Authorization", user.toHeaderValue()) + .queryParam(OFFICE, SPK) + .when() + .redirects().follow(true) + .redirects().max(3) + .post("/ratings/template") + .then() + .log().ifValidationFails(LogDetail.ALL,true) + .assertThat() + .statusCode(is(HttpServletResponse.SC_CREATED)); + // Create the set given() .log().ifValidationFails(LogDetail.ALL,true) diff --git a/cwms-data-api/src/test/resources/cwms/cda/api/spk/rating_template.json b/cwms-data-api/src/test/resources/cwms/cda/api/spk/rating_template.json new file mode 100644 index 0000000000..eaf9ade0f5 --- /dev/null +++ b/cwms-data-api/src/test/resources/cwms/cda/api/spk/rating_template.json @@ -0,0 +1,18 @@ +{ + "rating-template": { + "office-id": "SPK", + "parameters-id": "Stage;Flow", + "version": "EXSA", + "ind-parameter-specs": { + "ind-parameter-spec": [{ + "position": "1", + "parameter": "Stage", + "in-range-method": "LINEAR", + "out-range-low-method": "NEAREST", + "out-range-high-method": "NEAREST" + }] + }, + "dep-parameter": "Flow", + "description": "" + } +} \ No newline at end of file