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  }