Skip to content

Commit

Permalink
(update machine learning component
Browse files Browse the repository at this point in the history
  • Loading branch information
donaldkibet committed May 15, 2024
1 parent 3372df4 commit e3db32d
Show file tree
Hide file tree
Showing 8 changed files with 176 additions and 23 deletions.
Original file line number Diff line number Diff line change
@@ -1,4 +1,81 @@
<div style="margin-top: 0.5rem;" *ngIf="showMachineLearningButton">
<div style="margin-top: 0.5rem">
<div
*ngIf="riskScore"
style="margin-bottom: 0.5rem"
role="status"
class="cds--inline-notification cds--inline-notification--low-contrast"
[ngClass]="{
'cds--inline-notification--error':
riskScore === 'Highest Risk Client' || riskScore === 'High Risk Client',
'cds--inline-notification--success':
riskScore === 'Medium Risk Client' || riskScore === 'Low Risk',
'cds--inline-notification--warning': riskScore === 'No Risk Score Available'
}"
>
<div class="cds--inline-notification__details">
<svg
*ngIf="
riskScore === 'Highest Risk Client' ||
riskScore === 'High Risk Client'
"
focusable="false"
preserveAspectRatio="xMidYMid meet"
fill="currentColor"
width="20"
height="20"
viewBox="0 0 32 32"
aria-hidden="true"
class="cds--inline-notification__icon"
xmlns="http://www.w3.org/2000/svg"
>
<path
fill="none"
d="M16,26a1.5,1.5,0,1,1,1.5-1.5A1.5,1.5,0,0,1,16,26Zm-1.125-5h2.25V12h-2.25Z"
data-icon-path="inner-path"
></path>
<path
d="M16.002,6.1714h-.004L4.6487,27.9966,4.6506,28H27.3494l.0019-.0034ZM14.875,12h2.25v9h-2.25ZM16,26a1.5,1.5,0,1,1,1.5-1.5A1.5,1.5,0,0,1,16,26Z"
></path>
<path
d="M29,30H3a1,1,0,0,1-.8872-1.4614l13-25a1,1,0,0,1,1.7744,0l13,25A1,1,0,0,1,29,30ZM4.6507,28H27.3493l.002-.0033L16.002,6.1714h-.004L4.6487,27.9967Z"
></path>
<title>notification</title>
</svg>

<svg
*ngIf="riskScore === 'Medium Risk Client' || riskScore === 'Low Risk'"
focusable="false"
preserveAspectRatio="xMidYMid meet"
fill="currentColor"
width="20"
height="20"
viewBox="0 0 20 20"
aria-hidden="true"
class="cds--inline-notification__icon"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M10,1c-4.9,0-9,4.1-9,9s4.1,9,9,9s9-4,9-9S15,1,10,1z M8.7,13.5l-3.2-3.2l1-1l2.2,2.2l4.8-4.8l1,1L8.7,13.5z"
></path>
<path
fill="none"
d="M8.7,13.5l-3.2-3.2l1-1l2.2,2.2l4.8-4.8l1,1L8.7,13.5z"
data-icon-path="inner-path"
opacity="0"
></path>
<title>notification</title>
</svg>
<div class="cds--inline-notification__text-wrapper">
<div class="cds--inline-notification__title" dir="ltr">
{{ riskScore }}
</div>
<div class="cds--inline-notification__subtitle" dir="ltr">
{{ riskScoreMessage }}
</div>
</div>
</div>
</div>

<button
(click)="getRiskScore()"
class="cds--btn cds--btn--primary cds--layout--size-sm"
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { Component, Input, OnInit } from '@angular/core';

import { LeafNode } from '../../form-entry/form-factory/form-node';
import { ObsAdapterHelper } from '../../form-entry/value-adapters/obs-adapter-helper';
import { generatePredictionPayload } from './model-helper';
Expand All @@ -17,9 +16,11 @@ interface Payload {
export class MachineLearningComponent implements OnInit {
@Input() node: LeafNode;
showMachineLearningButton = false;
isLoading: boolean;
hasError: boolean;
errorMessage: string;
isLoading = false;
hasError = false;
errorMessage = '';
riskScoreMessage = '';
riskScore = '';

constructor(
private obsAdapter: ObsAdapterHelper,
Expand All @@ -28,7 +29,7 @@ export class MachineLearningComponent implements OnInit {

ngOnInit() {
this.showMachineLearningButton =
this.node.question.extras?.questionOptions['machineLearning'] || false;
this.node.question.extras?.questionOptions?.machineLearning || false;
}

getRiskScore() {
Expand All @@ -50,12 +51,20 @@ export class MachineLearningComponent implements OnInit {

this.machineLearningService.fetchPredictionScore(riskPayload).subscribe(
(res) => {
if (!res) {
this.isLoading = false;
this.errorMessage = 'An error occurred while fetching risk score';
this.setRiskScore(this.errorMessage);
this.setAutoGenerateRiskScore(0, this.errorMessage);
return;
}

const {
message,
riskScore
} = this.machineLearningService.predictRisk(res);
this.setRiskScore(message);
this.setAutoGenerateRiskScore(riskScore);
this.setAutoGenerateRiskScore(riskScore, message);
this.isLoading = false;
},
(error) => {
Expand All @@ -75,7 +84,7 @@ export class MachineLearningComponent implements OnInit {
return this.obsAdapter
.getObsNodePayload(this.node.form.rootNode)
.reduce((acc, item) => {
acc[item.concept] = item['value'];
acc[item.concept] = item.value;
return acc;
}, {});
}
Expand All @@ -87,7 +96,7 @@ export class MachineLearningComponent implements OnInit {
return { key, value: payload[valueKey] ?? '' };
})
.reduce((acc, item) => {
acc[item.key] = this.extractNumbersOrUuid(item['value']);
acc[item.key] = this.extractNumbersOrUuid(item.value);
return acc;
}, {});
}
Expand All @@ -107,7 +116,7 @@ export class MachineLearningComponent implements OnInit {
}

private generateKeyValue(): Payload {
let questionConcepts = {};
let questionConcepts: Payload = {};
this.node.form.schema.pages.forEach((page) => {
page.sections.forEach((section) => {
section.questions.forEach((question) => {
Expand All @@ -125,7 +134,7 @@ export class MachineLearningComponent implements OnInit {
riskScore.control.setValue(score);
}

extractNumbersOrUuid(inputStr) {
private extractNumbersOrUuid(inputStr: string) {
inputStr = String(inputStr); // Convert to string just in case
const uuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-5][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
if (uuidPattern.test(inputStr)) {
Expand All @@ -136,7 +145,7 @@ export class MachineLearningComponent implements OnInit {
}
}

announceRequiredFields() {
private announceRequiredFields() {
const requiredFields = [
'populationType',
'facilityHTStrategy',
Expand All @@ -155,7 +164,7 @@ export class MachineLearningComponent implements OnInit {
});
}

hasAllrequiredFields() {
private hasAllrequiredFields() {
const requiredFields = [
'populationType',
'facilityHTStrategy',
Expand All @@ -174,32 +183,45 @@ export class MachineLearningComponent implements OnInit {
);
}

getRiskLevel = (probabilityForPositivity) => {
private getRiskLevel = (
probabilityForPositivity: number,
message: string
) => {
const highRiskThreshold = 0.1079255;
const mediumRiskThreshold = 0.02795569;
const lowRiskThreshold = 0.005011473;

if (probabilityForPositivity === 0) {
this.riskScore = 'No Risk Score Available';
return '';
}

if (probabilityForPositivity > highRiskThreshold) {
this.riskScore = 'Highest Risk Client';
return '167164AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
} else if (
probabilityForPositivity <= highRiskThreshold &&
probabilityForPositivity > mediumRiskThreshold
) {
this.riskScore = 'High Risk Client';
return '166674AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
} else if (
probabilityForPositivity <= mediumRiskThreshold &&
probabilityForPositivity > lowRiskThreshold
) {
this.riskScore = 'Medium Risk Client';
return '1499AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
} else if (probabilityForPositivity <= lowRiskThreshold) {
this.riskScore = 'Low Risk Client';
return '166675AAAAAAAAAAAAAAAAAAAAAAAAAAAAAA';
}
};

// Set the autocalculate risk score
private setAutoGenerateRiskScore(riskScore: number) {
private setAutoGenerateRiskScore(riskScore: number, message: string) {
this.riskScoreMessage = message;
const genRisKQuestion = this.node.form.searchNodeByQuestionId('genRisK');
const riskLevel = this.getRiskLevel(riskScore);
const riskLevel = this.getRiskLevel(riskScore, message);
genRisKQuestion?.[0]?.control?.setValue(riskLevel);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export class MachineLearningService {
// Check if the prediction is available
const probabilityForPositivity = res?.result?.predictions['probability(1)'];
if (!probabilityForPositivity) {
return { message: 'No results found', riskScore: null };
return { message: 'No results found', riskScore: 0 };
}

// Define risk thresholds
Expand All @@ -40,7 +40,7 @@ export class MachineLearningService {
low:
'This client has a low probability of a HIV positive test result. Testing may not be recommended'
};
0.1079255<=0.936823 && 0.1079255>0.02795569

// Determine risk level and corresponding message
let riskLevel;
if (probabilityForPositivity > highRiskThreshold) {
Expand All @@ -55,7 +55,10 @@ export class MachineLearningService {
probabilityForPositivity > lowRiskThreshold
) {
riskLevel = 'medium';
} else if (probabilityForPositivity <= lowRiskThreshold) {
} else if (
probabilityForPositivity <= lowRiskThreshold &&
probabilityForPositivity !== 0
) {
riskLevel = 'low';
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ import { MinLengthValidationModel } from '../question-models/min-length-validati
import { WorkspaceLauncherQuestion } from '../question-models';
import { DecimalValidationModel } from '../question-models/decimal-validation.model';
import { DisallowDecimalsValidationModel } from '../question-models/disallow-decimals-validation.model';
import { MachineLearningQuestion } from '../question-models/machine-learning.model';

@Injectable()
export class QuestionFactory {
dataSources: any = {};
Expand Down Expand Up @@ -745,6 +747,28 @@ export class QuestionFactory {
return question;
}

toMachineLearningQuestion(schemaQuestion: any): MachineLearningQuestion {
const question = new MachineLearningQuestion({
type: '',
key: schemaQuestion.id,
label: schemaQuestion.label
});
question.questionIndex = this.quetionIndex;
question.prefix = schemaQuestion.prefix;
question.renderingType = 'machine-learning';
question.validators = this.addValidators(schemaQuestion);
question.extras = schemaQuestion;
question.dataSource = schemaQuestion.questionOptions.dataSource;
const mappings = {
label: 'label',
required: 'required',
id: 'key'
};
question.componentConfigs = schemaQuestion.componentConfigs || [];
this.copyProperties(mappings, schemaQuestion, question);
return question;
}

toDecimalQuestion(schemaQuestion: any): TextInputQuestion {
const question = new TextInputQuestion({
placeholder: '',
Expand Down Expand Up @@ -938,6 +962,8 @@ export class QuestionFactory {
return this.toFileUploadQuestion(schema);
case 'workspace-launcher':
return this.toWorkspaceLauncher(schema);
case 'machine-learning':
return this.toMachineLearningQuestion(schema);
default:
console.warn('New Schema Question Type found.........' + renderType);
return this.toTextQuestion(schema);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -375,9 +375,12 @@
[id]="node.question.key + 'id'"
[readOnly]="node.question.extras.readOnly"
/>
<machine-learning-risk-score
[node]="node"
></machine-learning-risk-score>

<div *ngSwitchCase="'machine-learning'">
<machine-learning-risk-score
[node]="node"
></machine-learning-risk-score>
</div>

<div *ngSwitchCase="'radio'">
<ofe-radio-button
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { QuestionBase } from './question-base';
import { AfeControlType } from '../../abstract-controls-extension/afe-control-type';
import { BaseOptions } from './models';

export class MachineLearningQuestion extends QuestionBase {
constructor(options: BaseOptions) {
super(options);
this.renderingType = 'machine-learning';
this.label = options.label || '';
this.controlType = AfeControlType.AfeFormControl;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,16 @@ export class EncounterAdapter implements ValueAdapter {
const dateValue = moment(node.control.value).utcOffset(
rootNode.form.valueProcessingInfo.utcOffset || '+0300'
);
// If the dateValue is same as new Date() then we are not going to send the encounterDatetime
// to the server. This is to avoid sending the encounterDatetime to the server when the user
// has not changed the encounterDatetime
console.log(
dateValue.format() + ' === ' + moment(new Date()).format()
);

if (dateValue.isSame(moment(new Date()), 'day')) {
break;
}
payload['encounterDatetime'] = dateValue.format();
break;
case 'encounterProvider':
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ export class ObsAdapterHelper {

const obsUuids = [];
for (const m of obs) {
obsUuids.push(m.value.uuid);
m.value?.uuid && obsUuids.push(m.value?.uuid);
}

this.setNodeFormControlValue(node, obsUuids);
Expand Down

0 comments on commit e3db32d

Please sign in to comment.