Skip to content

Commit 145f323

Browse files
authored
Merge branch 'develop' into feature/QPPSF-7911_Slow-dynamo
2 parents cf43bb0 + bd1f549 commit 145f323

20 files changed

+325
-42
lines changed

.github/workflows/ecr-publish.yml

Lines changed: 154 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
name: Deploy to Amazon ECR
1+
name: Deploy to Amazon ECS
22

33
on:
44
push:
@@ -35,6 +35,7 @@ jobs:
3535
uses: aws-actions/amazon-ecr-login@v1
3636

3737
- name: Dev - Build and deploy to Amazon ECR
38+
id: build-image-dev
3839
if: github.ref == 'refs/heads/develop'
3940
env:
4041
ECR_REPOSITORY: qppsf/conversion-tool/dev
@@ -47,24 +48,49 @@ jobs:
4748
docker build -t $ECR_REPOSITORY:$IMAGE_TAG .
4849
docker tag $ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
4950
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
51+
echo "::set-output name=image-dev::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
52+
53+
- name: Get task definition for dev
54+
if: github.ref == 'refs/heads/develop' && success()
55+
run: |
56+
aws ecs describe-task-definition --task-definition qppsf-conversion-tool-td-dev --query taskDefinition > task-definition.json
57+
58+
- name: Fill in image ID for ECS task-definition
59+
id: task-def-dev
60+
if: github.ref == 'refs/heads/develop' && success()
61+
uses: aws-actions/amazon-ecs-render-task-definition@v1
62+
with:
63+
task-definition: task-definition.json
64+
container-name: conversion-tool
65+
image: ${{ steps.build-image-dev.outputs.image-dev }}
66+
67+
- name: Deploy Amazon ECS task definition
68+
if: github.ref == 'refs/heads/develop' && success()
69+
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
70+
with:
71+
task-definition: ${{ steps.task-def-dev.outputs.task-definition }}
72+
service: conversion-tool-service-dev
73+
cluster: qppsf-conversion-tool-dev
74+
wait-for-service-stability: true
5075

5176
- name: Dev - Notify slack success
5277
if: github.ref == 'refs/heads/develop' && success()
5378
uses: voxmedia/github-action-slack-notify-build@v1
5479
with:
5580
channel: p-qpp-sub-alerts
56-
status: Conversion tools - Successful Docker build and AWS ECR deployment
81+
status: Conversion tools - Successful Docker build and AWS ECS deployment
5782
color: good
5883

5984
- name: Dev - Notify slack fail
6085
if: github.ref == 'refs/heads/develop' && failure()
6186
uses: voxmedia/github-action-slack-notify-build@v1
6287
with:
6388
channel: p-qpp-sub-alerts
64-
status: Conversion tools - Failed Docker build or AWS ECR deployment
89+
status: Conversion tools - Failed Docker build or AWS ECS deployment
6590
color: danger
6691

67-
- name: Impl - Build and deploy to Amazon ECR
92+
- name: Impl - Build and deploy to Amazon ECR
93+
id: build-image-impl
6894
if: startsWith(github.ref,'refs/heads/release/')
6995
env:
7096
ECR_REPOSITORY: qppsf/conversion-tool/impl
@@ -77,24 +103,71 @@ jobs:
77103
docker build -t $ECR_REPOSITORY:$IMAGE_TAG .
78104
docker tag $ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
79105
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
106+
echo "::set-output name=image-impl::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
107+
108+
- name: Get task definition for Impl
109+
if: startsWith(github.ref,'refs/heads/release/') && success()
110+
run: |
111+
aws ecs describe-task-definition --task-definition qppsf-conversion-tool-td-impl --query taskDefinition > task-definition.json
112+
113+
- name: Fill in image ID for ECS task-definition
114+
id: task-def-impl
115+
if: startsWith(github.ref,'refs/heads/release/') && success()
116+
uses: aws-actions/amazon-ecs-render-task-definition@v1
117+
with:
118+
task-definition: task-definition.json
119+
container-name: conversion-tool
120+
image: ${{ steps.build-image-impl.outputs.image-impl }}
121+
122+
- name: Deploy Amazon ECS task definition
123+
if: startsWith(github.ref,'refs/heads/release/') && success()
124+
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
125+
with:
126+
task-definition: ${{ steps.task-def-impl.outputs.task-definition }}
127+
service: conversion-tool-service-impl
128+
cluster: qppsf-conversion-tool-impl
129+
wait-for-service-stability: true
80130

81131
- name: Impl - Notify slack success
82132
if: startsWith(github.ref,'refs/heads/release/') && success()
83133
uses: voxmedia/github-action-slack-notify-build@v1
84134
with:
85135
channel: p-qpp-sub-alerts
86-
status: Conversion tools - Successful Docker build and AWS ECR deployment
136+
status: Conversion tools - Successful Docker build and AWS ECS deployment
87137
color: good
88138

89139
- name: Impl - Notify slack fail
90140
if: startsWith(github.ref,'refs/heads/release/') && failure()
91141
uses: voxmedia/github-action-slack-notify-build@v1
92142
with:
93143
channel: p-qpp-sub-alerts
94-
status: Conversion tools - Failed Docker build or AWS ECR deployment
144+
status: Conversion tools - Failed Docker build or AWS ECS deployment
95145
color: danger
96146

147+
#OKR Metrics Publish - start
148+
- name: Get OKR Event Start-Date
149+
if: github.ref == 'refs/heads/master'
150+
run: |
151+
echo "event_start_dt=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV
152+
153+
- name: Get okr-metrics properties
154+
if: github.ref == 'refs/heads/master'
155+
id: read_properties_start
156+
run: |
157+
source isg-okr.properties
158+
echo "conf_pg_id=${confluenceReleasePageId}" >> $GITHUB_ENV
159+
echo "deployment_type=${deploy_type}" >> $GITHUB_ENV
160+
161+
- name: Deployment Pipeline Start - Publish to OKR
162+
if: github.ref == 'refs/heads/master'
163+
run: |
164+
curl -v --location --request POST 'https://api.github.com/repos/cmsGov/qpp-okr/issues' \
165+
--header 'Authorization: Token ${{ secrets.OKR_AUTH_TOKEN }}'' \
166+
--header 'Content-Type: application/json' \
167+
--data-raw '{ "title": "QPPSF Deployment - Conversion Tool", "body": "\r\n```json\r\n{\r\n \"program\": \"QPP\",\r\n \"team\": \"QPPSF\",\r\n \"component\": \"Conversion-Tool\",\r\n \"tier\": \"PROD\",\r\n \"eventTime\": \"${{ env.event_start_dt }}\" ,\r\n \"eventType\": \"start\",\r\n \"confluenceReleasePageId\": \"${{ env.conf_pg_id }}\",\r\n \"releaseType\": \"${{ env.deployment_type }}\",\r\n \"correlationId\": \"GITWORKFLOW\"\r\n}\r\n```\r\nThis issue is created by Github Action","labels": ["deploy"]}'
168+
97169
- name: Prod - Build and deploy to Amazon ECR
170+
id: build-image-prod
98171
if: github.ref == 'refs/heads/master'
99172
env:
100173
ECR_REPOSITORY: qppsf/conversion-tool/prod
@@ -107,24 +180,71 @@ jobs:
107180
docker build -t $ECR_REPOSITORY:$IMAGE_TAG .
108181
docker tag $ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
109182
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
183+
echo "::set-output name=image-prod::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
184+
185+
- name: Get task definition for Prod
186+
if: github.ref == 'refs/heads/master' && success()
187+
run: |
188+
aws ecs describe-task-definition --task-definition qppsf-conversion-tool-td-prod --query taskDefinition > task-definition.json
189+
190+
- name: Fill in image ID for ECS task-definition
191+
id: task-def-prod
192+
if: github.ref == 'refs/heads/master' && success()
193+
uses: aws-actions/amazon-ecs-render-task-definition@v1
194+
with:
195+
task-definition: task-definition.json
196+
container-name: conversion-tool
197+
image: ${{ steps.build-image-prod.outputs.image-prod }}
198+
199+
- name: Deploy Amazon ECS task definition
200+
if: github.ref == 'refs/heads/master' && success()
201+
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
202+
with:
203+
task-definition: ${{ steps.task-def-prod.outputs.task-definition }}
204+
service: conversion-tool-service-prod
205+
cluster: qppsf-conversion-tool-prod
206+
wait-for-service-stability: true
110207

111208
- name: Prod - Notify slack success
112209
if: github.ref == 'refs/heads/master' && success()
113210
uses: voxmedia/github-action-slack-notify-build@v1
114211
with:
115212
channel: p-qpp-sub-alerts
116-
status: Conversion tools - Successful Docker build and AWS ECR deployment
213+
status: Conversion tools - Successful Docker build and AWS ECS deployment
117214
color: good
118215

119216
- name: Prod - Notify slack fail
120217
if: github.ref == 'refs/heads/master' && failure()
121218
uses: voxmedia/github-action-slack-notify-build@v1
122219
with:
123220
channel: p-qpp-sub-alerts
124-
status: Conversion tools - Failed Docker build or AWS ECR deployment
221+
status: Conversion tools - Failed Docker build or AWS ECS deployment
125222
color: danger
126223

224+
#OKR Metrics Publish - Finish
225+
- name: Get OKR Event Finish-Date
226+
if: github.ref == 'refs/heads/master' && success()
227+
run: |
228+
echo "event_finish_dt=$(date +'%Y-%m-%dT%H:%M:%S')" >> $GITHUB_ENV
229+
230+
- name: Get okr-metrics properties
231+
if: github.ref == 'refs/heads/master' && success()
232+
id: read_properties_finish
233+
run: |
234+
source isg-okr.properties
235+
echo "conf_pg_id=${confluenceReleasePageId}" >> $GITHUB_ENV
236+
echo "deployment_type=${deploy_type}" >> $GITHUB_ENV
237+
238+
- name: Deployment Pipeline Finish - Publish to OKR
239+
if: github.ref == 'refs/heads/master' && success()
240+
run: |
241+
curl -v --location --request POST 'https://api.github.com/repos/cmsGov/qpp-okr/issues' \
242+
--header 'Authorization: Token ${{ secrets.OKR_AUTH_TOKEN }}'' \
243+
--header 'Content-Type: application/json' \
244+
--data-raw '{ "title": "QPPSF Deployment - Conversion Tool", "body": "\r\n```json\r\n{\r\n \"program\": \"QPP\",\r\n \"team\": \"QPPSF\",\r\n \"component\": \"Conversion-Tool\",\r\n \"tier\": \"PROD\",\r\n \"eventTime\": \"${{ env.event_finish_dt }}\" ,\r\n \"eventType\": \"finish\",\r\n \"confluenceReleasePageId\": \"${{ env.conf_pg_id }}\",\r\n \"releaseType\": \"${{ env.deployment_type }}\",\r\n \"correlationId\": \"GITWORKFLOW\"\r\n}\r\n```\r\nThis issue is created by Github Action","labels": ["deploy"]}'
245+
127246
- name: DevPre - Build and deploy to Amazon ECR
247+
id: build-image-devpre
128248
if: github.ref == 'refs/heads/master'
129249
env:
130250
ECR_REPOSITORY: qppsf/conversion-tool/devpre
@@ -137,21 +257,45 @@ jobs:
137257
docker build -t $ECR_REPOSITORY:$IMAGE_TAG .
138258
docker tag $ECR_REPOSITORY:$IMAGE_TAG $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
139259
docker push $ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG
260+
echo "::set-output name=image-devpre::$ECR_REGISTRY/$ECR_REPOSITORY:$IMAGE_TAG"
261+
262+
- name: Get task definition for DevPre
263+
if: github.ref == 'refs/heads/master' && success()
264+
run: |
265+
aws ecs describe-task-definition --task-definition qppsf-conversion-tool-td-devpre --query taskDefinition > task-definition.json
266+
267+
- name: Fill in image ID for ECS task-definition
268+
id: task-def-devpre
269+
if: github.ref == 'refs/heads/master' && success()
270+
uses: aws-actions/amazon-ecs-render-task-definition@v1
271+
with:
272+
task-definition: task-definition.json
273+
container-name: conversion-tool
274+
image: ${{ steps.build-image-devpre.outputs.image-devpre }}
275+
276+
- name: Deploy Amazon ECS task definition
277+
if: github.ref == 'refs/heads/master' && success()
278+
uses: aws-actions/amazon-ecs-deploy-task-definition@v1
279+
with:
280+
task-definition: ${{ steps.task-def-devpre.outputs.task-definition }}
281+
service: conversion-tool-service-devpre
282+
cluster: qppsf-conversion-tool-devpre
283+
wait-for-service-stability: true
140284

141285
- name: DevPre - Notify slack success
142286
if: github.ref == 'refs/heads/master' && success()
143287
uses: voxmedia/github-action-slack-notify-build@v1
144288
with:
145289
channel: p-qpp-sub-alerts
146-
status: Conversion tools - Successful Docker build and AWS ECR deployment
290+
status: Conversion tools - Successful Docker build and AWS ECS deployment
147291
color: good
148292

149293
- name: DevPre - Notify slack fail
150294
if: github.ref == 'refs/heads/master' && failure()
151295
uses: voxmedia/github-action-slack-notify-build@v1
152296
with:
153297
channel: p-qpp-sub-alerts
154-
status: Conversion tools - Failed Docker build or AWS ECR deployment
298+
status: Conversion tools - Failed Docker build or AWS ECS deployment
155299
color: danger
156300

157301
- name: Logout of Amazon ECR

commons/src/main/java/gov/cms/qpp/conversion/model/error/ProblemCode.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,10 @@ public enum ProblemCode implements LocalizedProblem {
190190
PI_RESTRICTED_MEASURES(102, "A PI section cannot contain PI_HIE_5 with PI_HIE_1, PI_LVOTC_1, PI_HIE_4, or PI_LVITC_2", false),
191191
PCF_TOO_FEW_QUALITY_MEASURE_CATEGORY(103, "PCF Submissions must have at least `(PCF Measure minimum)` "
192192
+ "of the following measures: `(Listing of valid measure ids)`", true),
193-
CPC_PLUS_NO_PI(104, "The file contains Promoting Interoperability (PI) data; PI data must not be reported for CPC+");
194-
193+
CPC_PLUS_NO_PI(104, "The file contains Promoting Interoperability (PI) data; PI data must not be reported for CPC+"),
194+
PCF_MULTI_TIN_NPI_SINGLE_PERFORMER(105, "If multiple TINs/NPIs are submitted, each must be reported within a separate performer"),
195+
PCF_NO_PI(106, "PI submissions are not allowed within PCF")
196+
;
195197

196198
private static final Map<Integer, ProblemCode> CODE_TO_VALUE = Arrays.stream(values())
197199
.collect(Collectors.toMap(ProblemCode::getCode, Function.identity()));

converter/src/main/java/gov/cms/qpp/conversion/validate/Checker.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,20 @@ Checker listValuesAreValid(LocalizedProblem code, String name, int size) {
127127
return this;
128128
}
129129

130+
Checker listValuesAreInts(LocalizedProblem code, String name) {
131+
lastAppraised = node.getValue(name);
132+
if (!shouldShortcut()) {
133+
List<String> values = Arrays.asList(((String)lastAppraised).split(","));
134+
values.forEach(value -> {
135+
String trimmedValue = value.trim();
136+
if (!trimmedValue.matches("\\d+")) {
137+
details.add(detail(code));
138+
}
139+
});
140+
}
141+
return this;
142+
}
143+
130144
/**
131145
* checks target node for the existence of a single value with the given name key
132146
*
@@ -480,6 +494,20 @@ <T> Checker oneChildPolicy(LocalizedProblem code, TemplateId type, Function<Node
480494
return this;
481495
}
482496

497+
/**
498+
* General check for an arbitrary boolean condition.
499+
*
500+
* @param code Identifies the error.
501+
* @param predicate Any boolean expression. The error code will be added if this is false.
502+
* @return The checker, for chaining method calls.
503+
*/
504+
Checker predicate(LocalizedProblem code, boolean predicate) {
505+
if (!predicate) {
506+
details.add(detail(code));
507+
}
508+
return this;
509+
}
510+
483511
/**
484512
* Marks the checked node as being incompletely validated.
485513
*

converter/src/main/java/gov/cms/qpp/conversion/validate/CpcClinicalDocumentValidator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ protected void performValidation(Node node) {
8282
.valueIsNotEmpty(ProblemCode.CPC_PCF_PLUS_NPI_REQUIRED, ClinicalDocumentDecoder.NATIONAL_PROVIDER_IDENTIFIER)
8383
.listValuesAreValid(
8484
ProblemCode.CPC_PCF_PLUS_INVALID_NPI, ClinicalDocumentDecoder.NATIONAL_PROVIDER_IDENTIFIER, 10)
85-
.valueIsNotEmpty(addressError, ClinicalDocumentDecoder.PRACTICE_SITE_ADDR)
85+
.value(addressError, ClinicalDocumentDecoder.PRACTICE_SITE_ADDR)
8686
.childMinimum(ProblemCode.CPC_PCF_CLINICAL_DOCUMENT_ONE_MEASURE_SECTION_REQUIRED,
8787
1, TemplateId.MEASURE_SECTION_V4);
8888

converter/src/main/java/gov/cms/qpp/conversion/validate/PcfClinicalDocumentValidator.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,23 @@
11
package gov.cms.qpp.conversion.validate;
22

33
import gov.cms.qpp.conversion.Context;
4+
import gov.cms.qpp.conversion.correlation.PathCorrelator;
45
import gov.cms.qpp.conversion.decode.ClinicalDocumentDecoder;
56
import gov.cms.qpp.conversion.model.Node;
67
import gov.cms.qpp.conversion.model.Program;
78
import gov.cms.qpp.conversion.model.TemplateId;
89
import gov.cms.qpp.conversion.model.Validator;
910
import gov.cms.qpp.conversion.model.error.ProblemCode;
11+
import org.jdom2.Element;
12+
import org.jdom2.Namespace;
13+
import org.jdom2.filter.Filters;
14+
import org.jdom2.located.LocatedElement;
15+
import org.jdom2.xpath.XPathExpression;
16+
import org.jdom2.xpath.XPathFactory;
17+
18+
import java.util.List;
19+
import java.util.function.Predicate;
20+
import java.util.stream.Collectors;
1021

1122

1223
/**
@@ -26,8 +37,30 @@ protected void performValidation(final Node node) {
2637

2738
checkErrors(node)
2839
.singleValue(ProblemCode.CPC_PCF_CLINICAL_DOCUMENT_ONLY_ONE_APM_ALLOWED, ClinicalDocumentDecoder.PCF_ENTITY_ID)
29-
.valueIsNotEmpty(ProblemCode.CPC_PCF_CLINICAL_DOCUMENT_EMPTY_APM, ClinicalDocumentDecoder.PCF_ENTITY_ID);
40+
.valueIsNotEmpty(ProblemCode.CPC_PCF_CLINICAL_DOCUMENT_EMPTY_APM, ClinicalDocumentDecoder.PCF_ENTITY_ID)
41+
.childExact(ProblemCode.PCF_NO_PI, 0, TemplateId.PI_SECTION_V2)
42+
.listValuesAreInts(ProblemCode.CPC_PCF_PLUS_INVALID_NPI, ClinicalDocumentDecoder.NATIONAL_PROVIDER_IDENTIFIER);
3043

44+
validateSingleTinNpiPerPerformer(node);
3145
validateApmEntityId(node, ClinicalDocumentDecoder.PCF_ENTITY_ID);
3246
}
47+
48+
private Predicate<Element> filterTinNpi(String name) {
49+
return (Element child) -> (child.getContent().stream()
50+
.filter(c -> c.getClass().equals(LocatedElement.class) && ((LocatedElement) c).getName().equals(name))
51+
.toArray().length >= 1);
52+
}
53+
54+
private void validateSingleTinNpiPerPerformer(Node node) {
55+
String performersXpath = PathCorrelator.getXpath(TemplateId.CLINICAL_DOCUMENT.toString(), "performer", Namespace.NO_NAMESPACE.getURI());
56+
XPathExpression<Element> expression = XPathFactory.instance().compile(performersXpath, Filters.element(), null, Namespace.NO_NAMESPACE);
57+
List<Element> performers = expression.evaluate(node.getElementForLocation());
58+
// Should be maximum of one TIN and one NPI per performer.
59+
for (Element performer : performers) {
60+
List<Element> tins = performer.getChildren().stream().filter(filterTinNpi("representedOrganization")).collect(Collectors.toList());
61+
List<Element> npis = performer.getChildren().stream().filter(filterTinNpi("id")).collect(Collectors.toList());
62+
forceCheckErrors(node).predicate(ProblemCode.PCF_MULTI_TIN_NPI_SINGLE_PERFORMER, tins.size() <= 1);
63+
forceCheckErrors(node).predicate(ProblemCode.PCF_MULTI_TIN_NPI_SINGLE_PERFORMER, npis.size() <= 1);
64+
}
65+
}
3366
}

converter/src/main/java/gov/cms/qpp/conversion/validate/QualityMeasureIdValidator.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,9 @@ protected void validateDenomCountToIpopCount(Node node, SubPopulation subPopulat
183183
Node denomCount = denomNode.findFirstNode(TemplateId.PI_AGGREGATE_COUNT);
184184
Node ipopCount = ipopNode.findFirstNode(TemplateId.PI_AGGREGATE_COUNT);
185185

186-
if (ClinicalDocumentDecoder.CPCPLUS_PROGRAM_NAME.equalsIgnoreCase(program)
187-
&& MeasureConfigHelper.CPC_PLUS_MEASURES.contains(measureId)) {
186+
if ((ClinicalDocumentDecoder.CPCPLUS_PROGRAM_NAME.equalsIgnoreCase(program)
187+
&& MeasureConfigHelper.CPC_PLUS_MEASURES.contains(measureId))
188+
|| ClinicalDocumentDecoder.PCF_PROGRAM_NAME.equalsIgnoreCase(program)) {
188189
validateCpcDenominatorCount(denomCount, ipopCount, subPopulation.getDenominatorUuid());
189190
} else {
190191
validateDenominatorCount(denomCount, ipopCount, subPopulation.getDenominatorUuid());

0 commit comments

Comments
 (0)