github.com/lauslim12/expert-systems@v0.0.0-20221115131159-018513aad29c/pkg/inference/inference.go (about) 1 package inference 2 3 // Disease is the representation of the diseases data in this Expert System. 4 type Disease struct { 5 ID string `json:"id"` // Disease ID 6 Name string `json:"name"` // Name of the disease 7 Description string `json:"description"` // Description of the disease 8 Treatment string `json:"treatment"` // Treatment of the disease 9 Prevention string `json:"prevention"` // Prevention of the disease 10 Source []SourceAndLink `json:"source"` // Sources of information regarding the disease 11 Symptoms []Symptom `json:"symptoms"` // Valid symptoms of the disease 12 } 13 14 // Inferred is the object that will be returned after all of the calculations. 15 type Inferred struct { 16 Verdict bool `json:"verdict"` // Verdict whether one is infected or not 17 Probability float64 `json:"probability"` // Probability of infection 18 Disease Disease `json:"disease"` // Disease data 19 } 20 21 // SymptomAndWeight is a struct representative of the members of 'symptoms' array in 'Input' struct. 22 type SymptomAndWeight struct { 23 SymptomID string `json:"symptomId"` // ID of the relevant symptom 24 Weight float64 `json:"weight"` // User-confidence weights for the Certainty Factor Algorithm 25 } 26 27 // Input is used as a representative of a user's input. 28 type Input struct { 29 DiseaseID string `json:"diseaseId"` // ID of the relevant disease 30 Locale string `json:"locale"` // Locale of the required information (can be 'en' or 'id) 31 Symptoms []SymptomAndWeight `json:"symptoms"` // Symptoms and weights 32 } 33 34 // SourceAndLink represents the source name and its link for the information regarding a disease. 35 type SourceAndLink struct { 36 Name string `json:"name"` // Name of the website 37 Link string `json:"link"` // Link to the website 38 } 39 40 // Symptom is an object that represents the symptoms data in this library. 41 type Symptom struct { 42 ID string `json:"id"` // ID of the symptom 43 Name string `json:"name"` // Name of the symptom 44 Weight float64 `json:"weight"` // Expert-calculated weight from data and the relevant experts' opinion 45 } 46 47 // NewInput creates a new input instance that's already validated. 48 // If default ID is not inside, then we are going to assume Tuberculosis. 49 // If locale is not inside, then we are going to assume English. 50 func NewInput(input *Input) *Input { 51 if input.DiseaseID == "" { 52 input.DiseaseID = "D01" 53 } 54 55 if input.Locale == "" { 56 input.Locale = "en" 57 } 58 59 if input.Symptoms == nil { 60 input.Symptoms = []SymptomAndWeight{} 61 } 62 63 newInput := &Input{ 64 DiseaseID: input.DiseaseID, 65 Locale: input.Locale, 66 Symptoms: input.Symptoms, 67 } 68 69 return newInput 70 } 71 72 // GetDiseaseByID is used to fetch a disease data by its ID. 73 func GetDiseaseByID(ID string, diseases []Disease) *Disease { 74 for _, disease := range diseases { 75 if disease.ID == ID { 76 return &disease 77 } 78 } 79 80 return nil 81 } 82 83 // ForwardChaining is used to perform inference by using the Forward Chaining Algorithm. 84 // A weight of zero means that the user is NOT sick. 85 // This forward chaining will be true only and only if the user has experienced 7 symptoms. 86 // This is because of our knowledge base - the average of symptoms had by each patient. 87 func ForwardChaining(input *Input, disease *Disease) bool { 88 numberOfPositives := 0 89 90 for _, userSymptom := range input.Symptoms { 91 for _, diseaseSymptom := range disease.Symptoms { 92 if userSymptom.Weight > 0.0 && userSymptom.SymptomID == diseaseSymptom.ID { 93 numberOfPositives += 1 94 } 95 } 96 } 97 98 return numberOfPositives > 7 99 } 100 101 // CertaintyFactor is used to perform analysis and to find the certainty probability. 102 // First, match the user symptoms' and the available expert symptom' from the knowledge base. 103 // Second, calculate the real probability. 104 func CertaintyFactor(input *Input, symptoms []Symptom) float64 { 105 certainties := make([]float64, 0) 106 probability := 0.0 107 108 // Match and calculate certainty between the expert and the user. 109 for _, userSymptom := range input.Symptoms { 110 for _, expertSymptom := range symptoms { 111 if userSymptom.SymptomID == expertSymptom.ID { 112 certainties = append(certainties, userSymptom.Weight*expertSymptom.Weight) 113 } 114 } 115 } 116 117 // If invalid input, return zero probability. 118 if len(certainties) == 0 { 119 return probability 120 } 121 122 // Calculate probability from the certainty array. 123 probability = certainties[0] 124 for i := 1; i < len(certainties); i += 1 { 125 probability = probability + certainties[i]*(1-probability) 126 } 127 128 return probability 129 } 130 131 // Infer is used to calculate based on an input to decide whether the user is infected or not. 132 // We will use Forward Chaining and Certainty Factor algorithms in order to decide that. 133 // Algorithm: Get knowledge base -> Forward Chaining -> Certainty Factor -> Result. 134 func Infer(input *Input) *Inferred { 135 // Initial preparation: if no locale, set it to be English as default. 136 processedInput := NewInput(input) 137 138 // 0. Fetch all data from the knowledge base. 139 diseases := getDiseases(processedInput.Locale) 140 141 // 1. Get disease from the identifier in the input request body. 142 disease := GetDiseaseByID(processedInput.DiseaseID, diseases) 143 144 // 2. Infer if the user is diagnosed with TB or not with Forward Chaining. 145 isSick := ForwardChaining(processedInput, disease) 146 147 // 3. Calculate certainty factor based on the symptoms. 148 certaintyProbability := CertaintyFactor(processedInput, disease.Symptoms) 149 150 // 4. Create result structure. 151 inferred := &Inferred{ 152 Verdict: isSick, 153 Probability: certaintyProbability, 154 Disease: *disease, 155 } 156 157 // 5. Return result. 158 return inferred 159 }