Skip to content

Commit

Permalink
feat: incident response action form (wip)
Browse files Browse the repository at this point in the history
  • Loading branch information
deeonwuli committed Oct 10, 2024
1 parent 4ac190c commit 4f42711
Show file tree
Hide file tree
Showing 14 changed files with 309 additions and 200 deletions.
18 changes: 8 additions & 10 deletions src/data/repositories/IncidentActionD2Repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import {
import { ActionPlanFormData, ResponseActionFormData } from "../../domain/entities/ConfigurableForm";
import { getProgramStage } from "./utils/MetadataHelper";
import { Future } from "../../domain/entities/generic/Future";
import { D2TrackerEvent } from "@eyeseetea/d2-api/api/trackerEvents";
import { Status, Verification } from "../../domain/entities/incident-action-plan/ResponseAction";

export const incidentActionPlanIds = {
Expand Down Expand Up @@ -54,7 +53,7 @@ export const incidentResponseActionsIds = {
verification: "M62NkbKXhqZ",
};

export type IncidentResponseActionsDataValues = {
export type IncidentResponseActionDataValues = {
id: string;
mainTask: Maybe<string>;
subActivities: Maybe<string>;
Expand Down Expand Up @@ -101,7 +100,7 @@ export class IncidentActionD2Repository implements IncidentActionRepository {

getIncidentResponseActions(
diseaseOutbreakId: Id
): FutureData<Maybe<IncidentResponseActionsDataValues>> {
): FutureData<IncidentResponseActionDataValues[]> {
return apiToFuture(
this.api.tracker.events.get({
program: RTSL_ZEBRA_PROGRAM_ID,
Expand All @@ -111,11 +110,10 @@ export class IncidentActionD2Repository implements IncidentActionRepository {
fields: this.fields,
})
).map(events => {
const responseActions: IncidentResponseActionsDataValues =
mapDataElementsToIncidentResponseActions(
events.instances[0]?.event ?? diseaseOutbreakId,
events.instances[0]?.dataValues ?? []
);
if (events.instances.length === 0) return [];

const responseActions: IncidentResponseActionDataValues[] =
mapDataElementsToIncidentResponseActions(events.instances);

return responseActions;
});
Expand Down Expand Up @@ -152,7 +150,7 @@ export class IncidentActionD2Repository implements IncidentActionRepository {
return Future.error(new Error(`Enrollment not found for Disease Outbreak`));
}

const events: D2TrackerEvent = mapIncidentActionToDataElements(
const events = mapIncidentActionToDataElements(
formData,
programStageId,
diseaseOutbreakId,
Expand All @@ -163,7 +161,7 @@ export class IncidentActionD2Repository implements IncidentActionRepository {
return apiToFuture(
this.api.tracker.post(
{ importStrategy: "CREATE_AND_UPDATE" },
{ events: [events] }
{ events: Array.isArray(events) ? events : [events] }
)
).flatMap(saveResponse => {
if (saveResponse.status === "ERROR" || !diseaseOutbreakId) {
Expand Down
66 changes: 40 additions & 26 deletions src/data/repositories/consts/IncidentActionConstants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { ActionPlanAttrs } from "../../../domain/entities/incident-action-plan/ActionPlan";
import {
ActionPlanAttrs,
ActionPlanIAPType,
ActionPlanPhoecLevel,
} from "../../../domain/entities/incident-action-plan/ActionPlan";
import {
ResponseAction,
Status,
Verification,
ResponseActionStatusType,
ResponseActionVerificationType,
} from "../../../domain/entities/incident-action-plan/ResponseAction";
import { GetValue } from "../../../utils/ts-utils";
import { GetValue, Maybe } from "../../../utils/ts-utils";

export const actionPlanConstants = {
iapType: "RTSL_ZEB_DET_IAP_TYPE",
Expand All @@ -26,30 +30,26 @@ export function isStringInIncidentActionPlanCodes(code: string): code is Inciden
return (Object.values(actionPlanConstants) as string[]).includes(code);
}

type IAPType = "Initial" | "Update" | "Final";

export const iapTypeCodeMap: Record<IAPType, string> = {
const iapTypeCodeMap: Record<ActionPlanIAPType, string> = {
Initial: "RTSL_ZEB_OS_IAP_TYPE_INITIAL",
Update: "RTSL_ZEB_OS_IAP_TYPE_UPDATE",
Final: "RTSL_ZEB_OS_IAP_TYPE_FINAL",
};

export function getIAPTypeByCode(iapTypeCode: string): IAPType | undefined {
return (Object.keys(iapTypeCodeMap) as IAPType[]).find(
export function getIAPTypeByCode(iapTypeCode: string): Maybe<ActionPlanIAPType> {
return (Object.keys(iapTypeCodeMap) as ActionPlanIAPType[]).find(
key => iapTypeCodeMap[key] === iapTypeCode
);
}

type PhoecLevel = "Response" | "Watch" | "Alert";

export const phoecLevelCodeMap: Record<PhoecLevel, string> = {
const phoecLevelCodeMap: Record<ActionPlanPhoecLevel, string> = {
Response: "RTSL_ZEB_OS_PHOEC_ACT_LEVEL_RESPONSE",
Watch: "RTSL_ZEB_OS_PHOEC_ACT_LEVEL_WATCH",
Alert: "RTSL_ZEB_OS_PHOEC_ACT_LEVEL_ALERT",
};

export function getPhoecLevelByCode(phoecLevelCode: string): PhoecLevel | undefined {
return (Object.keys(phoecLevelCodeMap) as PhoecLevel[]).find(
export function getPhoecLevelByCode(phoecLevelCode: string): ActionPlanPhoecLevel | undefined {
return (Object.keys(phoecLevelCodeMap) as ActionPlanPhoecLevel[]).find(
key => phoecLevelCodeMap[key] === phoecLevelCode
);
}
Expand All @@ -76,6 +76,32 @@ export function isStringInIncidentResponseActionCodes(
return (Object.values(responseActionConstants) as string[]).includes(code);
}

const statusCodeMap: Record<ResponseActionStatusType, string> = {
"Not done": "RTSL_ZEB_OS_STATUS_NOT_DONE",
Pending: "RTSL_ZEB_OS_STATUS_PENDING",
"In Progress": "RTSL_ZEB_OS_STATUS_IN_PROGRESS",
Complete: "RTSL_ZEB_OS_STATUS_COMPLETE",
} as const;

export function getStatusTypeByCode(iapTypeCode: string): Maybe<ResponseActionStatusType> {
return (Object.keys(statusCodeMap) as ResponseActionStatusType[]).find(
key => statusCodeMap[key] === iapTypeCode
);
}

const verificationCodeMap: Record<ResponseActionVerificationType, string> = {
Verified: "RTSL_ZEB_OS_VERIFICATION_VERIFIED",
Unverified: "RTSL_ZEB_OS_VERIFICATION_UNVERIFIED",
};

export function getVerificationTypeByCode(
iapTypeCode: string
): Maybe<ResponseActionVerificationType> {
return (Object.keys(verificationCodeMap) as ResponseActionVerificationType[]).find(
key => verificationCodeMap[key] === iapTypeCode
);
}

export function getValueFromIncidentActionPlan(
incidentActionPlan: ActionPlanAttrs
): Record<ActionPlanCodes, string> {
Expand Down Expand Up @@ -106,15 +132,3 @@ export function getValueFromIncidentResponseAction(
RTSL_ZEB_DET_VERIFICATION: incidentResponseAction.verification,
};
}

export const statusMap: Record<string, Status> = {
RTSL_ZEB_OS_STATUS_NOT_DONE: Status.RTSL_ZEB_OS_STATUS_NOT_DONE,
RTSL_ZEB_OS_STATUS_PENDING: Status.RTSL_ZEB_OS_STATUS_PENDING,
RTSL_ZEB_OS_STATUS_IN_PROGRESS: Status.RTSL_ZEB_OS_STATUS_IN_PROGRESS,
RTSL_ZEB_OS_STATUS_COMPLETE: Status.RTSL_ZEB_OS_STATUS_COMPLETE,
};

export const verificationMap: Record<string, Verification> = {
RTSL_ZEB_OS_VERIFICATION_UNVERIFIED: Verification.RTSL_ZEB_OS_VERIFICATION_UNVERIFIED,
RTSL_ZEB_OS_VERIFICATION_VERIFIED: Verification.RTSL_ZEB_OS_VERIFICATION_VERIFIED,
};
4 changes: 2 additions & 2 deletions src/data/repositories/test/IncidentActionTestRepository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { Maybe } from "../../../utils/ts-utils";
import { FutureData } from "../../api-futures";
import {
IncidentActionPlanDataValues,
IncidentResponseActionsDataValues,
IncidentResponseActionDataValues,
} from "../IncidentActionD2Repository";

export class IncidentActionTestRepository implements IncidentActionRepository {
Expand All @@ -14,7 +14,7 @@ export class IncidentActionTestRepository implements IncidentActionRepository {

getIncidentResponseActions(
_diseaseOutbreakId: Id
): FutureData<Maybe<IncidentResponseActionsDataValues>> {
): FutureData<Maybe<IncidentResponseActionDataValues[]>> {
throw new Error("Method not implemented.");
}

Expand Down
124 changes: 62 additions & 62 deletions src/data/repositories/utils/IncidentActionMapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { D2TrackerEvent, DataValue } from "@eyeseetea/d2-api/api/trackerEvents";
import {
IncidentActionPlanDataValues,
incidentActionPlanIds,
IncidentResponseActionsDataValues,
IncidentResponseActionDataValues,
incidentResponseActionsIds,
} from "../IncidentActionD2Repository";
import { Id } from "../../../domain/entities/Ref";
Expand All @@ -22,12 +22,13 @@ import {
isStringInIncidentActionPlanCodes,
isStringInIncidentResponseActionCodes,
ResponseActionCodes,
responseActionConstants,
statusMap,
verificationMap,
} from "../consts/IncidentActionConstants";
import { RTSL_ZEBRA_ORG_UNIT_ID, RTSL_ZEBRA_PROGRAM_ID } from "../consts/DiseaseOutbreakConstants";
import { ResponseAction } from "../../../domain/entities/incident-action-plan/ResponseAction";
import {
ResponseAction,
Status,
Verification,
} from "../../../domain/entities/incident-action-plan/ResponseAction";

export function mapDataElementsToIncidentActionPlan(
id: Id,
Expand Down Expand Up @@ -64,43 +65,37 @@ export function mapDataElementsToIncidentActionPlan(
}

export function mapDataElementsToIncidentResponseActions(
id: Id,
dataValues: DataValue[]
): IncidentResponseActionsDataValues {
const fromMap = (key: keyof typeof responseActionConstants) => getValueFromMap(key, dataValues);

const mainTask = getValueById(dataValues, incidentResponseActionsIds.mainTask);
const subActivities = getValueById(dataValues, incidentResponseActionsIds.subActivities);
const subPillar = getValueById(dataValues, incidentResponseActionsIds.subPillar);
const searchAssignRO = getValueById(dataValues, incidentResponseActionsIds.searchAssignRO);
const dueDate = getValueById(dataValues, incidentResponseActionsIds.dueDate);
const timeLine = getValueById(dataValues, incidentResponseActionsIds.timeLine);

const status = statusMap[fromMap("status")];
const verification = verificationMap[fromMap("verification")];

const incidentActionPlan: IncidentResponseActionsDataValues = {
id: id,
mainTask: mainTask,
subActivities: subActivities,
subPillar: subPillar,
searchAssignRO: searchAssignRO,
dueDate: dueDate,
timeLine: timeLine,
status: status,
verification: verification,
};
instances: D2TrackerEvent[]
): IncidentResponseActionDataValues[] {
const incidentResponseActions: IncidentResponseActionDataValues[] = instances.map(instance => {
const { event, dataValues } = instance;

const mainTask = getValueById(dataValues, incidentResponseActionsIds.mainTask);
const subActivities = getValueById(dataValues, incidentResponseActionsIds.subActivities);
const subPillar = getValueById(dataValues, incidentResponseActionsIds.subPillar);
const searchAssignRO = getValueById(dataValues, incidentResponseActionsIds.searchAssignRO);
const dueDate = getValueById(dataValues, incidentResponseActionsIds.dueDate);
const timeLine = getValueById(dataValues, incidentResponseActionsIds.timeLine);
const status = getValueById(dataValues, incidentResponseActionsIds.status) as Status;
const verification = getValueById(
dataValues,
incidentResponseActionsIds.verification
) as Verification;

return {
id: event,
mainTask,
subActivities,
subPillar,
searchAssignRO,
dueDate,
timeLine,
status,
verification,
};
});

return incidentActionPlan;
}

function getValueFromMap(
key: keyof typeof responseActionConstants,
dataValues: DataValue[]
): string {
return (
dataValues.find(dataValue => dataValue.value === responseActionConstants[key])?.value ?? ""
);
return incidentResponseActions;
}

export function mapIncidentActionToDataElements(
Expand Down Expand Up @@ -167,30 +162,35 @@ function mapIncidentResponseActionToDataElements(
programStageId: Id,
teiId: Id,
enrollmentId: Id,
incidentResponseAction: ResponseAction,
incidentResponseActions: ResponseAction[],
programStageDataElementsMetadata: D2ProgramStageDataElementsMetadata[]
): D2TrackerEvent {
const dataElementValues: Record<ResponseActionCodes, string> =
getValueFromIncidentResponseAction(incidentResponseAction);

const dataValues: DataValue[] = programStageDataElementsMetadata.map(programStage => {
if (!isStringInIncidentResponseActionCodes(programStage.dataElement.code)) {
throw new Error(
`DataElement code ${programStage.dataElement.code} not found in Incident Action Plan Codes`
): D2TrackerEvent[] {
return incidentResponseActions.map(incidentResponseAction => {
const dataElementValues: Record<ResponseActionCodes, string> =
getValueFromIncidentResponseAction(incidentResponseAction);

const dataValues: DataValue[] = programStageDataElementsMetadata.map(programStage => {
if (!isStringInIncidentResponseActionCodes(programStage.dataElement.code)) {
throw new Error(
`DataElement code ${programStage.dataElement.code} not found in Incident Action Plan Codes`
);
}
const typedCode: IncidentResponseActionKeyCode = programStage.dataElement.code;

return getPopulatedDataElement(
programStage.dataElement.id,
dataElementValues[typedCode]
);
}
const typedCode: IncidentResponseActionKeyCode = programStage.dataElement.code;

return getPopulatedDataElement(programStage.dataElement.id, dataElementValues[typedCode]);
});

return getIncidentActionTrackerEvent(
programStageId,
incidentResponseAction.id,
enrollmentId,
dataValues,
teiId
);
});

return getIncidentActionTrackerEvent(
programStageId,
incidentResponseAction.id,
enrollmentId,
dataValues,
teiId
);
}

function getPopulatedDataElement(dataElement: Id, value: Maybe<string>): DataValue {
Expand Down
2 changes: 1 addition & 1 deletion src/domain/entities/ConfigurableForm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export type ActionPlanFormData = BaseFormData & {
export type ResponseActionFormData = BaseFormData & {
type: "incident-response-action";
eventTrackerDetails: DiseaseOutbreakEvent;
entity: Maybe<ResponseAction>;
entity: ResponseAction[];
options: IncidentResponseActionOptions;
};

Expand Down
3 changes: 3 additions & 0 deletions src/domain/entities/incident-action-plan/ActionPlan.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
import { Struct } from "../generic/Struct";
import { Id } from "../Ref";

export type ActionPlanIAPType = "Initial" | "Update" | "Final";
export type ActionPlanPhoecLevel = "Response" | "Watch" | "Alert";

export type ActionPlanAttrs = {
id: Id;
iapType: string;
Expand Down
4 changes: 2 additions & 2 deletions src/domain/repositories/IncidentActionRepository.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { FutureData } from "../../data/api-futures";
import {
IncidentActionPlanDataValues,
IncidentResponseActionsDataValues,
IncidentResponseActionDataValues,
} from "../../data/repositories/IncidentActionD2Repository";
import { Maybe } from "../../utils/ts-utils";
import { ActionPlanFormData, ResponseActionFormData } from "../entities/ConfigurableForm";
Expand All @@ -11,7 +11,7 @@ export interface IncidentActionRepository {
getIncidentActionPlan(diseaseOutbreakId: Id): FutureData<Maybe<IncidentActionPlanDataValues>>;
getIncidentResponseActions(
diseaseOutbreakId: Id
): FutureData<Maybe<IncidentResponseActionsDataValues>>;
): FutureData<Maybe<IncidentResponseActionDataValues[]>>;
saveIncidentAction(
formData: ActionPlanFormData | ResponseActionFormData,
diseaseOutbreakId: Id
Expand Down
Loading

0 comments on commit 4f42711

Please sign in to comment.