@Override
  public EvaluatedPersonData evaluate(PersonDataDefinition definition, EvaluationContext context)
      throws EvaluationException {

    FirstEncounterAtFacilityDataDefinition def =
        (FirstEncounterAtFacilityDataDefinition) definition;
    EvaluatedPersonData c = new EvaluatedPersonData(def, context);

    if (context.getBaseCohort() == null || context.getBaseCohort().isEmpty()) {
      return c;
    }

    // find the facility number
    MOHFacility facility = (MOHFacility) context.getParameterValue("facility");

    // fail quickly if the facility does not exist
    if (facility == null) {
      log.warn("No facility provided; returning empty data.");
      return c;
    }

    // use HQL to do our bidding
    String hql =
        "from Encounter"
            + " where voided=false"
            + " and patientId in (:patientIds)"
            + " and location in (:locationList)"
            + " and encounterDatetime <= :onOrBefore"
            + " order by encounterDatetime asc";

    Map<String, Object> m = new HashMap<String, Object>();
    m.put("patientIds", context.getBaseCohort());
    m.put("locationList", facility.getLocations());
    m.put("onOrBefore", context.getEvaluationDate());

    DataSetQueryService qs = Context.getService(DataSetQueryService.class);
    List<Object> queryResult = qs.executeHqlQuery(hql, m);

    ListMap<Integer, Encounter> encForPatients = new ListMap<Integer, Encounter>();
    for (Object o : queryResult) {
      Encounter enc = (Encounter) o;
      encForPatients.putInList(enc.getPatientId(), enc);
    }

    for (Integer pId : encForPatients.keySet()) {
      List<Encounter> l = encForPatients.get(pId);
      c.addData(pId, l.get(0));
    }

    return c;
  }
  public PatientDataResult calculateResult(
      List<PatientDataResult> results, EvaluationContext context) {

    PatientAttributeResult alert = new PatientAttributeResult(null, null);

    ProgramWorkflowState state = (ProgramWorkflowState) context.getParameterValue("state");

    StringBuffer alerts = new StringBuffer();

    double height = 0;
    double weight = 0;

    for (PatientDataResult result : results) {

      if (result.getName().equals("CD4Test")) {
        AllObservationValuesResult cd4 = (AllObservationValuesResult) result;

        if (cd4.getValue() != null) {
          int decline = calculateDecline(cd4.getValue());

          if (decline > 50
              && (state.toString().contains("GROUP") || state.toString().contains("FOLLOWING"))) {
            alerts.append("CD4 decline(");
            alerts.append(decline);
            alerts.append(").\n");
          }

          Obs lastCd4 = null;

          if (cd4.getValue().size() > 0) {
            lastCd4 = cd4.getValue().get(cd4.getValue().size() - 1);
          }

          if (lastCd4 == null) {
            alerts.append("No CD4 recorded.\n");
          } else {
            Date dateCd4 = lastCd4.getObsDatetime();
            Date date = Calendar.getInstance().getTime();

            int diff = calculateMonthsDifference(date, dateCd4);

            if (diff > 12) {
              alerts.append("Very late CD4(" + diff + " months ago).\n");
            } else if ((diff > 6) && state.toString().contains("FOLLOWING")) {
              alerts.append("Late CD4(" + diff + " months ago).\n");
            }

            if (state.toString().contains("FOLLOWING")
                && lastCd4.getValueNumeric() != null
                && lastCd4.getValueNumeric() < 500) {
              alerts.append("Eligible for Treatment.\n");
            }
          }
        }
      }
      if (result.getName().equals("viralLoadTest")) {
        AllObservationValuesResult viraload = (AllObservationValuesResult) result;

        if (viraload.getValue() != null) {
          Obs lastviraload = null;

          if (viraload.getValue().size() > 0) {
            lastviraload = viraload.getValue().get(viraload.getValue().size() - 1);
          }

          if (state.toString().contains("GROUP") && (lastviraload == null)) {
            alerts.append("No VL recorded.\n");
          } else {
            try {
              Date dateVl = lastviraload.getObsDatetime();
              Date date = Calendar.getInstance().getTime();

              int diff = calculateMonthsDifference(date, dateVl);

              if (state.toString().contains("GROUP")) {
                if (diff > 12) {
                  alerts.append("Late VL(" + diff + " months ago).\n");
                }

                if (lastviraload.getValueNumeric() != null
                    && lastviraload.getValueNumeric() > 1000) {
                  alerts.append("VL Failure " + lastviraload.getValueNumeric() + ".\n");
                }
              }
            } catch (Exception e) {
            }
          }
        }
      }
      if (result.getName().equals("weightObs")) {
        AllObservationValuesResult wt = (AllObservationValuesResult) result;

        if (wt.getValue() != null) {
          int decline = calculatePercentageDecline(wt.getValue());

          if (decline > 5) {
            alerts.append("WT decline(");
            alerts.append(decline);
            alerts.append("%, ");
            int kilosLost = calculateDecline(wt.getValue());
            alerts.append(kilosLost);
            alerts.append("kg)\n");
          }

          if (wt.getValue().size() > 0) {
            weight = wt.getValue().get(wt.getValue().size() - 1).getValueNumeric();
          }
        }

        if (wt.getValue() == null || wt.getValue().size() == 0) {
          alerts.append("No weight recorded.\n");
        }
      }

      if (result.getName().equals("RecentHeight")) {
        ObservationResult heightOb = (ObservationResult) result;

        if (heightOb.getValue() == null || heightOb.getValue().trim().length() == 0) {
          alerts.append("No height recorded.\n");
        } else {
          height = Double.parseDouble(heightOb.getValue());
        }
      }

      if (result.getName().equals("lastEncInMonth")) {
        DateValueResult encinmonths = (DateValueResult) result;
        if (encinmonths.getValue() != null) {
          Date dateVl = encinmonths.getDateOfObservation();
          Date date = Calendar.getInstance().getTime();
          int diff = calculateMonthsDifference(date, dateVl);
          if (diff > 12) {
            alerts.append("LTFU determine status.\n");
          }
        }
      }

      if (result.getName().equals("IO") && result.getValue() != null) {
        alerts.append("OI reported last visit: " + result.getValue() + "\n");
      }

      if (result.getName().equals("SideEffects") && result.getValue() != null) {
        alerts.append("Side effects reported last visit: " + result.getValue() + "\n");
      }
    }

    if (height > 0 && weight > 0) {
      double bmi = weight / (height / 100 * height / 100);
      int decimalPlace = 1;
      BigDecimal bd = new BigDecimal(Double.toString(bmi));
      bd = bd.setScale(decimalPlace, BigDecimal.ROUND_HALF_UP);

      if (bmi < 16) {
        alerts.append("Very low BMI (" + bd.doubleValue() + ").\n");
      } else if (bmi < 18.5) {
        alerts.append("Low BMI (" + bd.doubleValue() + ").\n");
      }
    }

    alert.setValue(alerts.toString().trim());
    return alert;
  }