// Copyright 2015 The Project Buendia Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at: http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software distrib-
// uted under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES
// OR CONDITIONS OF ANY KIND, either express or implied. See the License for
// specific language governing permissions and limitations under the License.
package org.projectbuendia.client.models;
import android.content.ContentValues;
import android.database.Cursor;
import org.joda.time.DateTime;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.projectbuendia.client.net.Server;
import org.projectbuendia.client.json.JsonEncounter;
import org.projectbuendia.client.providers.Contracts.Observations;
import org.projectbuendia.client.utils.Logger;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import javax.annotation.Nullable;
import javax.annotation.concurrent.Immutable;
/**
* An encounter in the app model. Encounters contain one or more observations taken at a particular
* timestamp. For more information on encounters and observations, see the official OpenMRS
* documentation here:
* <a href="https://wiki.openmrs.org/display/docs/Encounters+and+observations">
* https://wiki.openmrs.org/display/docs/Encounters+and+observations"
* </a>
* <p/>
* <p>NOTE: Because of lack of typing info from the server, {@link Encounter} attempts to
* determine the most appropriate type, but this typing is not guaranteed to succeed; also,
* currently only <code>DATE</code> and <code>UUID</code> (coded) types are supported.
*/
@Immutable
public class Encounter extends Base<String> {
public final String patientUuid;
public final
@Nullable String encounterUuid;
public final DateTime timestamp;
public final Observation[] observations;
public final String[] orderUuids;
private static final Logger LOG = Logger.create();
/**
* Creates a new Encounter for the given patient.
* @param patientUuid The UUID of the patient.
* @param encounterUuid The UUID of this encounter, or null for encounters created on the client.
* @param timestamp The encounter time.
* @param observations An array of observations to include in the encounter.
* @param orderUuids A list of UUIDs of the orders executed during this encounter.
*/
public Encounter(
String patientUuid,
@Nullable String encounterUuid,
DateTime timestamp,
Observation[] observations,
String[] orderUuids) {
id = encounterUuid;
this.patientUuid = patientUuid;
this.encounterUuid = id;
this.timestamp = timestamp;
this.observations = observations == null ? new Observation[] {} : observations;
this.orderUuids = orderUuids == null ? new String[] {} : orderUuids;
}
/**
* Creates an instance of {@link Encounter} from a network
* {@link JsonEncounter} object and corresponding patient UUID.
*/
public static Encounter fromJson(String patientUuid, JsonEncounter encounter) {
List<Observation> observations = new ArrayList<Observation>();
if (encounter.observations != null) {
for (Map.Entry<Object, Object> observation : encounter.observations.entrySet()) {
observations.add(new Observation(
(String) observation.getKey(),
(String) observation.getValue(),
Observation.estimatedTypeFor((String) observation.getValue())
));
}
}
return new Encounter(patientUuid, encounter.uuid, encounter.timestamp,
observations.toArray(new Observation[observations.size()]), encounter.order_uuids);
}
/** Serializes this into a {@link JSONObject}. */
public JSONObject toJson() throws JSONException {
JSONObject json = new JSONObject();
json.put(Server.PATIENT_UUID_KEY, patientUuid);
json.put(Server.ENCOUNTER_TIMESTAMP, timestamp.getMillis()/1000);
if (observations.length > 0) {
JSONArray observationsJson = new JSONArray();
for (Observation obs : observations) {
JSONObject observationJson = new JSONObject();
observationJson.put(Server.OBSERVATION_QUESTION_UUID, obs.conceptUuid);
String valueKey = obs.type == Observation.Type.DATE ?
Server.OBSERVATION_ANSWER_DATE : Server.OBSERVATION_ANSWER_UUID;
observationJson.put(valueKey, obs.value);
observationsJson.put(observationJson);
}
json.put(Server.ENCOUNTER_OBSERVATIONS_KEY, observationsJson);
}
if (orderUuids.length > 0) {
JSONArray orderUuidsJson = new JSONArray();
for (String orderUuid : orderUuids) {
orderUuidsJson.put(orderUuid);
}
json.put(Server.ENCOUNTER_ORDER_UUIDS, orderUuidsJson);
}
return json;
}
/**
* Converts this instance of {@link Encounter} to an array of
* {@link android.content.ContentValues} objects for insertion into a database or content
* provider.
*/
public ContentValues[] toContentValuesArray() {
ContentValues[] cvs = new ContentValues[observations.length + orderUuids.length];
for (int i = 0; i < observations.length; i++) {
Observation obs = observations[i];
ContentValues cv = new ContentValues();
cv.put(Observations.CONCEPT_UUID, obs.conceptUuid);
cv.put(Observations.ENCOUNTER_MILLIS, timestamp.getMillis());
cv.put(Observations.ENCOUNTER_UUID, encounterUuid);
cv.put(Observations.PATIENT_UUID, patientUuid);
cv.put(Observations.VALUE, obs.value);
cvs[i] = cv;
}
for (int i = 0; i < orderUuids.length; i++) {
ContentValues cv = new ContentValues();
cv.put(Observations.CONCEPT_UUID, AppModel.ORDER_EXECUTED_CONCEPT_UUID);
cv.put(Observations.ENCOUNTER_MILLIS, timestamp.getMillis());
cv.put(Observations.ENCOUNTER_UUID, encounterUuid);
cv.put(Observations.PATIENT_UUID, patientUuid);
cv.put(Observations.VALUE, orderUuids[i]);
cvs[observations.length + i] = cv;
}
return cvs;
}
/** Represents a single observation within this encounter. */
public static final class Observation {
public final String conceptUuid;
public final String value;
public final Type type;
/** Data type of the observation. */
public enum Type {
DATE,
NON_DATE
}
public Observation(String conceptUuid, String value, Type type) {
this.conceptUuid = conceptUuid;
this.value = value;
this.type = type;
}
/**
* Produces a best guess for the type of a given value, since the server doesn't give us
* typing information.
*/
public static Type estimatedTypeFor(String value) {
try {
new DateTime(Long.parseLong(value));
return Type.DATE;
} catch (Exception e) {
return Type.NON_DATE;
}
}
}
/**
* An {@link CursorLoader} that loads {@link Encounter}s. Expects the {@link Cursor} to
* contain only a single encounter, represented by multiple observations, with one observation per
* row.
* <p/>
* <p>Unlike other {@link CursorLoader}s, {@link Encounter.Loader} must be instantiated
* once per patient, since {@link Encounter} contains the patient's UUID as one of its fields,
* which is not present in the database representation of an encounter.
*/
public static class Loader implements CursorLoader<Encounter> {
private String mPatientUuid;
public Loader(String patientUuid) {
mPatientUuid = patientUuid;
}
@Override public Encounter fromCursor(Cursor cursor) {
final String encounterUuid = cursor.getString(
cursor.getColumnIndex(Observations.ENCOUNTER_UUID));
final long millis = cursor.getLong(
cursor.getColumnIndex(Observations.ENCOUNTER_MILLIS));
List<Observation> observations = new ArrayList<>();
cursor.move(-1);
while (cursor.moveToNext()) {
String value = cursor.getString(cursor.getColumnIndex(Observations.VALUE));
observations.add(new Observation(
cursor.getString(cursor.getColumnIndex(Observations.CONCEPT_UUID)),
value, Observation.estimatedTypeFor(value)
));
}
return new Encounter(mPatientUuid, encounterUuid, new DateTime(millis),
observations.toArray(new Observation[observations.size()]), null);
}
}
}