Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Release to val #364

Merged
merged 7 commits into from
Feb 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ jobs:
- deploy
env:
baseurl: ${{ needs.deploy.outputs.app-url }}
if: ${{ github.ref != 'refs/heads/production' && github.ref != 'refs/heads/val' }}
if: ${{ github.ref != 'refs/heads/production' }}
steps:
- name: Checkout
uses: actions/checkout@v3
Expand Down
13 changes: 7 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "macpro-om-template",
"name": "macpro-mako",
"type": "module",
"description": "TBD.",
"version": "0.0.0-managed-by-semantic-release",
Expand All @@ -21,18 +21,20 @@
"test-gui": "vitest --ui",
"test-tsc": "tsc --skipLibCheck --noEmit"
},
"repository": "https://github.com/Enterprise-CMCS/macpro-om-template",
"repository": "https://github.com/Enterprise-CMCS/macpro-mako",
"workspaces": [
"src/services/*",
"src/libs",
"src/cli",
"src/packages/*"
],
"license": "CC0-1.0",
"homepage": "https://github.com/Enterprise-CMCS/macpro-om-template#readme",
"homepage": "https://github.com/Enterprise-CMCS/macpro-mako#readme",
"devDependencies": {
"@enterprise-cmcs/macpro-serverless-running-stages": "^1.0.4",
"@enterprise-cmcs/serverless-waf-plugin": "^1.3.0",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/git": "^10.0.1",
"@serverless/compose": "^1.3.0",
"@stratiformdigital/serverless-iam-helper": "^3.2.0",
"@stratiformdigital/serverless-online": "^3.1.0",
Expand All @@ -45,7 +47,7 @@
"aws-sdk-client-mock": "^2.0.1",
"esbuild": "^0.19.3",
"prettier": "2.7.1",
"semantic-release": "^19.0.5",
"semantic-release": "^21.0.1",
"serverless": "^3.38.0",
"serverless-disable-functions": "^1.0.0",
"serverless-esbuild": "^1.47.0",
Expand All @@ -64,8 +66,7 @@
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/github",
"@semantic-release/npm"
"@semantic-release/github"
]
},
"dependencies": {
Expand Down
1 change: 1 addition & 0 deletions src/packages/shared-types/action-types/new-submission.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { attachmentSchema } from "../attachments";
// This is the event schema for ne submissions from our system
export const onemacSchema = z.object({
authority: z.string(),
seaActionType: z.string().optional(), // Used by waivers.
origin: z.string(),
additionalInformation: z.string().nullable().default(null),
submitterName: z.string(),
Expand Down
10 changes: 10 additions & 0 deletions src/packages/shared-types/attachments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ export const attachmentTitleMap: Record<string, string> = {
other: "Other",
// RAI WITHDRAW
supportingDocumentation: "Supporting Documentation",
bCapWaiverApplication:
"1915(b) Comprehensive (Capitated) Waiver Application Pre-print",
bCapCostSpreadsheets:
"1915(b) Comprehensive (Capitated) Waiver Cost Effectiveness Spreadsheets",
bCapIndependentAssessment:
"1915(b) Comprehensive (Capitated) Waiver Independent Assessment",
b4WaiverApplication:
"1915(b)(4) FFS Selective Contracting (Streamlined) Waiver Application Pre-print",
b4IndependentAssessment:
"1915(b)(4) FFS Selective Contracting (Streamlined) Independent Assessment",
};
export type AttachmentKey = keyof typeof attachmentTitleMap;
export type AttachmentTitle = typeof attachmentTitleMap[AttachmentKey];
Expand Down
3 changes: 3 additions & 0 deletions src/packages/shared-types/planType.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
export enum PlanType {
MED_SPA = "medicaid spa",
CHIP_SPA = "chip spa",
WAIVER = "waiver",
"1915b" = "1915(b)",
"1915c" = "1915(c)",
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ export const getAvailableActions = (
result: opensearch.main.Document
) => {
const checks = PackageCheck(result);
return checks.isSpa
? rules.filter((r) => r.check(checks, user)).map((r) => r.action)
: [];
return [
...((checks.isWaiver || checks.isSpa)
? rules.filter((r) => r.check(checks, user)).map((r) => r.action)
: []),
];
};
14 changes: 13 additions & 1 deletion src/packages/shared-utils/package-actions/rules.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
Action,
ActionRule,
PlanType,
SEATOOL_STATUS,
finalDispositionStatuses,
} from "../../shared-types";
Expand All @@ -10,7 +11,18 @@ const arIssueRai: ActionRule = {
action: Action.ISSUE_RAI,
check: (checker, user) =>
checker.isInActivePendingStatus &&
(!checker.hasLatestRai || checker.hasRequestedRai) &&
(
// Doesn't have any RAIs
!checker.hasLatestRai ||
(
// The latest RAI is complete
checker.hasCompletedRai &&
// The package is not a medicaid spa (med spas only get 1 rai)
!checker.planTypeIs([PlanType.MED_SPA]) &&
// The package does not have RAI Response Withdraw enabled
!checker.hasEnabledRaiWithdraw
)
) &&
isCmsWriteUser(user),
};

Expand Down
8 changes: 7 additions & 1 deletion src/packages/shared-utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,11 @@
"version": "0.0.0",
"private": true,
"license": "MIT",
"devDependencies": {}
"scripts": {
"test": "vitest"
},
"devDependencies": {},
"dependencies": {
"@18f/us-federal-holidays": "^4.0.0"
}
}
12 changes: 7 additions & 5 deletions src/packages/shared-utils/packageCheck.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const PackageCheck = ({
}: opensearch.main.Document) => {
const planChecks = {
isSpa: checkPlan(planType, [PlanType.MED_SPA, PlanType.CHIP_SPA]),
isWaiver: checkPlan(planType, []),
isWaiver: checkPlan(planType, [PlanType["1915b"]]),
/** Keep excess methods to a minimum with `is` **/
planTypeIs: (validPlanTypes: PlanType[]) =>
checkPlan(planType, validPlanTypes),
Expand All @@ -52,12 +52,14 @@ export const PackageCheck = ({
checkStatus(seatoolStatus, authorizedStatuses),
};
const raiChecks = {
/** Latest RAI is requested and status is Pending-RAI **/
hasRequestedRai: !!raiRequestedDate && !raiReceivedDate && !raiWithdrawnDate,
/** Latest RAI is not null **/
/** There is an RAI and it does not have a response **/
hasRequestedRai: !!raiRequestedDate && !raiReceivedDate,
/** There is an RAI **/
hasLatestRai: !!raiRequestedDate,
/** Latest RAI has been responded to **/
/** There is an RAI, it has a response, and it has not been withdrawn **/
hasRaiResponse: !!raiRequestedDate && !!raiReceivedDate && !raiWithdrawnDate,
/** Latest RAI has a response and/or has been withdrawn **/
hasCompletedRai: !!raiRequestedDate && !!raiReceivedDate,
/** RAI Withdraw has been enabled **/
hasEnabledRaiWithdraw: raiWithdrawEnabled,
};
Expand Down
26 changes: 26 additions & 0 deletions src/packages/shared-utils/seatool-date-helper.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import moment from "moment-timezone";
import * as fedHolidays from '@18f/us-federal-holidays';


// This manually accounts for the offset between the client's timezone and UTC.
export const offsetForUtc = (date: Date): Date => {
Expand All @@ -18,4 +20,28 @@ export const seaToolFriendlyTimestamp = (date?: Date): number => {
// This takes an epoch string and converts it to a standard format for display
export const formatSeatoolDate = (date: string): string => {
return moment(date).tz("UTC").format("MM/DD/yyyy")
}

export const getNextBusinessDayTimestamp = (date: Date = new Date()): number => {
let localeStringDate = date.toLocaleString("en-US", { timeZone: "America/New_York", dateStyle: "short" });
let localeStringHours24 = date.toLocaleString("en-US", { timeZone: "America/New_York", hour: 'numeric', hour12: false });
let localeDate = new Date(localeStringDate);

console.log(`Evaluating ${localeStringDate} at ${localeStringHours24}`);

const after5pmEST = parseInt(localeStringHours24,10) >= 17
const isHoliday = fedHolidays.isAHoliday(localeDate)
const isWeekend = !(localeDate.getDay() % 6)
if(after5pmEST || isHoliday || isWeekend) {
let nextDate = localeDate;
nextDate.setDate(nextDate.getDate() + 1);
nextDate.setHours(12,0,0,0)
console.log("Current date is not valid. Will try " + nextDate)
return getNextBusinessDayTimestamp(nextDate)
}

// Return the next business day's epoch for midnight UTC
let ret = offsetForUtc(localeDate).getTime();
console.log('Current date is a valid business date. Will return ' + ret);
return ret;
}
42 changes: 42 additions & 0 deletions src/packages/shared-utils/tests/seatool-date-helper.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { it, describe, expect } from "vitest";
import { getNextBusinessDayTimestamp } from "../seatool-date-helper";

describe("The getNextBusinessDayTimestamp function", () => {
it("identifies weekenends", () => {
let testDate = new Date(2024, 0, 27, 12, 0, 0); // Saturday, noon, utc
let nextDate = getNextBusinessDayTimestamp(testDate);
expect(nextDate).toEqual(Date.UTC(2024, 0, 29)); // Monday, midnight, utc
});

it("identifies holidays", () => {
let testDate = new Date(2024, 0, 15, 12, 0, 0); // MLK Day, a Monday
let nextDate = getNextBusinessDayTimestamp(testDate);
expect(nextDate).toEqual(Date.UTC(2024, 0, 16)); // Tuesday, midnight, utc
});

it("identifies submissions after 5pm eastern", () => {
let testDate = new Date(2024, 0, 17, 23, 0, 0); // Wednesday 11pm utc, Wednesday 6pm eastern
let nextDate = getNextBusinessDayTimestamp(testDate);
expect(nextDate).toEqual(Date.UTC(2024, 0, 18)); // Thursday, midnight, utc
});

it("identifies submissions before 5pm eastern", () => {
let testDate = new Date(2024, 0, 17, 10, 0, 0); // Wednesday 10am utc, Wednesday 5am eastern
let nextDate = getNextBusinessDayTimestamp(testDate);
expect(nextDate).toEqual(Date.UTC(2024, 0, 17)); // Wednesday, midnight, utc
});

it("handles combinations of rule violations", () => {
let testDate = new Date(2024, 0, 12, 23, 0, 0); // Friday 11pm utc, Friday 6pm eastern
let nextDate = getNextBusinessDayTimestamp(testDate);
// Submission is after 5pm, Saturday is a weekend, Sunday is a weekend, and Monday is MLK Day
expect(nextDate).toEqual(Date.UTC(2024, 0, 16)); // Tuesday, midnight utc
});

it("identifies valid business days", () => {
let testDate = new Date(2024, 0, 9, 15, 0, 0); // Tuesday 3pm utc, Tuesday 8am eastern
let nextDate = getNextBusinessDayTimestamp(testDate);
expect(nextDate).toEqual(Date.UTC(2024, 0, 9)); // Tuesday, midnight utc
});

});
76 changes: 53 additions & 23 deletions src/services/api/handlers/packageActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import {
import { produceMessage } from "../libs/kafka";
import { response } from "../libs/handler";
import { SEATOOL_STATUS } from "shared-types/statusHelper";
import { seaToolFriendlyTimestamp } from "shared-utils";
import { formatSeatoolDate, seaToolFriendlyTimestamp } from "shared-utils";
import { buildStatusMemoQuery } from "../libs/statusMemo";

const TOPIC_NAME = process.env.topicName as string;
Expand Down Expand Up @@ -119,29 +119,57 @@ export async function withdrawRai(body: RaiWithdraw, document: any) {
const transaction = new sql.Transaction(pool);
try {
await transaction.begin();
// Issue RAI
const query1 = `
UPDATE SEA.dbo.RAI
SET RAI_WITHDRAWN_DATE = DATEADD(s, CONVERT(int, LEFT('${today}', 10)), CAST('19700101' AS DATETIME))
// How we withdraw an RAI Response varies based on authority or not
// Medicaid is handled differently from the rest.
if (body.authority == "MEDICAID") {
// Set Received Date to null
await transaction.request().query(`
UPDATE SEA.dbo.RAI
SET
RAI_RECEIVED_DATE = NULL,
RAI_WITHDRAWN_DATE = DATEADD(s, CONVERT(int, LEFT('${today}', 10)), CAST('19700101' AS DATETIME))
WHERE ID_Number = '${result.data.id}' AND RAI_REQUESTED_DATE = DATEADD(s, CONVERT(int, LEFT('${raiToWithdraw}', 10)), CAST('19700101' AS DATETIME))
`;
const result1 = await transaction.request().query(query1);
console.log(result1);
`);
// Set Status to Pending - RAI
await transaction.request().query(`
UPDATE SEA.dbo.State_Plan
SET
SPW_Status_ID = (SELECT SPW_Status_ID FROM SEA.dbo.SPW_Status WHERE SPW_Status_DESC = '${SEATOOL_STATUS.PENDING_RAI}'),
Status_Date = dateadd(s, convert(int, left(${today}, 10)), cast('19700101' as datetime))
WHERE ID_Number = '${result.data.id}'
`);
} else {
// Set Withdrawn_Date on the existing RAI
await transaction.request().query(`
UPDATE SEA.dbo.RAI
SET
RAI_WITHDRAWN_DATE = DATEADD(s, CONVERT(int, LEFT('${today}', 10)), CAST('19700101' AS DATETIME))
WHERE ID_Number = '${result.data.id}' AND RAI_REQUESTED_DATE = DATEADD(s, CONVERT(int, LEFT('${raiToWithdraw}', 10)), CAST('19700101' AS DATETIME))
`);
// Set Status to Pending
await transaction.request().query(`
UPDATE SEA.dbo.State_Plan
SET
SPW_Status_ID = (SELECT SPW_Status_ID FROM SEA.dbo.SPW_Status WHERE SPW_Status_DESC = '${SEATOOL_STATUS.PENDING}'),
Status_Date = dateadd(s, convert(int, left(${today}, 10)), cast('19700101' as datetime))
WHERE ID_Number = '${result.data.id}'
`);
}

// Update Status
const query2 = `
UPDATE SEA.dbo.State_Plan
SET
SPW_Status_ID = (SELECT SPW_Status_ID FROM SEA.dbo.SPW_Status WHERE SPW_Status_DESC = '${SEATOOL_STATUS.PENDING}'),
Status_Date = dateadd(s, convert(int, left(${today}, 10)), cast('19700101' as datetime))
WHERE ID_Number = '${result.data.id}'
`;
const result2 = await transaction.request().query(query2);
console.log(result2);

const statusMemoUpdate = await transaction
.request()
.query(buildStatusMemoQuery(result.data.id, "RAI Response Withdrawn"));
// Set a detailed message in the Status Memo
const statusMemoUpdate = await transaction.request().query(
buildStatusMemoQuery(
result.data.id,
`RAI Response Withdrawn. Response was received ${formatSeatoolDate(
document.raiReceivedDate
)} and withdrawn ${new Date().toLocaleString("en-US", {
timeZone: "America/New_York",
year: "numeric",
month: "2-digit",
day: "2-digit",
})}`
)
);
console.log(statusMemoUpdate);

// write to kafka here
Expand Down Expand Up @@ -190,7 +218,9 @@ export async function respondToRai(body: RaiResponse, document: any) {
// Issue RAI
const query1 = `
UPDATE SEA.dbo.RAI
SET RAI_RECEIVED_DATE = DATEADD(s, CONVERT(int, LEFT('${today}', 10)), CAST('19700101' AS DATETIME))
SET
RAI_RECEIVED_DATE = DATEADD(s, CONVERT(int, LEFT('${today}', 10)), CAST('19700101' AS DATETIME)),
RAI_WITHDRAWN_DATE = NULL
WHERE ID_Number = '${body.id}' AND RAI_REQUESTED_DATE = DATEADD(s, CONVERT(int, LEFT('${raiToRespondTo}', 10)), CAST('19700101' AS DATETIME))
`;
const result1 = await transaction.request().query(query1);
Expand Down
Loading
Loading