TracingConfigGroup.java

package org.matsim.episim;

import com.google.common.base.Joiner;
import com.google.common.base.Splitter;
import org.matsim.core.config.ReflectiveConfigGroup;

import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * Config option specific to contact tracing and measures performed in {@link org.matsim.episim.model.ProgressionModel}.
 */
public class TracingConfigGroup extends ReflectiveConfigGroup {

	private static final Splitter.MapSplitter SPLITTER = Splitter.on(";").withKeyValueSeparator("=");
	private static final Joiner.MapJoiner JOINER = Joiner.on(";").withKeyValueSeparator("=");

	private static final String PUT_TRACEABLE_PERSONS_IN_QUARANTINE = "putTraceablePersonsInQuarantineAfterDay";
	private static final String TRACING_DAYS_DISTANCE = "tracingDaysDistance";
	private static final String TRACING_PROBABILITY = "tracingProbability";
	private static final String TRACING_DELAY = "tracingDelay";
	private static final String QUARANTINE_VACCINATED = "quarantineVaccinated";
	private static final String QUARANTINE_DURATION = "quarantineDuration";
	private static final String QUARANTINE_STATUS = "quarantineStatus";
	private static final String MIN_DURATION = "minDuration";
	private static final String QUARANTINE_HOUSEHOLD = "quarantineHousehold";
	private static final String QUARANTINE_RELEASE = "quarantineRelease";
	private static final String TRACE_SUSCEPTIBLE = "traceSusceptible";
	private static final String EQUIPMENT_RATE = "equipmentRate";
	private static final String CAPACITY = "tracingCapacity";
	private static final String CAPACITY_TYPE = "capacityType";
	private static final String STRATEGY = "strategy";
	private static final String LOCATION_THRESHOLD = "locationThreshold";
	private static final String GREEN_PASS_DAYS = "greenPassValidDays";
	private static final String GREEN_PASS_BOOSTER_DAYS = "greenPassBoosterValidDays";
	private static final String IGNORED_ACTIVITIES = "ignoredActivities";
	private static final String GROUPNAME = "episimTracing";

	/**
	 * Amount of persons traceable per day.
	 */
	private final Map<LocalDate, Integer> tracingCapacity = new TreeMap<>();
	/**
	 * Probability of successfully tracing a person.
	 */
	private final Map<LocalDate, Double> tracingProbability = new TreeMap<>();

	/**
	 * Delay between showing symptoms and tracing of contact person.
	 */
	private final Map<LocalDate, Integer> tracingDelay = new TreeMap<>();

	/**
	 * Whether vaccinated persons should be put into quarantine.
	 */
	private final Map<LocalDate, Boolean> quarantineVaccinated = new TreeMap<>();

	/**
	 * Duration of quarantine in days.
	 */
	private final Map<LocalDate, Integer> quarantineDuration = new TreeMap<>();

	/**
	 * Quarantine to use after certain dates.
	 */
	private final Map<LocalDate, EpisimPerson.QuarantineStatus> quarantineStatus = new TreeMap<>();

	/**
	 * Activities that will not be traced.
	 */
	private final Set<String> ignoredActivities = new HashSet<>();

	/**
	 * Day after which tracing starts and puts persons into quarantine.
	 */
	private int putTraceablePersonsInQuarantineAfterDay = Integer.MAX_VALUE;
	/**
	 * How many days the tracing works back.
	 */
	private int tracingDayDistance = 4;

	/**
	 * Probability that a person is equipped with a tracing device.
	 */
	private double equipmentRate = 1.0;

	/**
	 * Minimum duration in seconds for a contact to be relevant for tracing.
	 */
	private double minDuration = 0.0;

	/**
	 * Members of the same household will be put always into quarantine.
	 */
	private boolean quarantineHouseholdMembers = false;

	/**
	 * Trace contacts between two susceptible persons. (Uses a lot more RAM)
	 */
	private boolean traceSusceptible = true;

	/**
	 * Defines if the capacity is either per (infected) person or per contact person.
	 */
	private CapacityType capacityType = CapacityType.PER_PERSON;

	/**
	 * Tracing and containment strategy.
	 */
	private Strategy strategy = Strategy.INDIVIDUAL_ONLY;

	/**
	 * Quarantine release strategy.
	 */
	private QuarantineRelease quarantineRelease = QuarantineRelease.SUSCEPTIBLE;

	/**
	 * How many infections are required for location based tracing to trigger.
	 */
	private int locationThreshold = 4;

	/**
	 * Overwrite green pass valid days, -1 means default value.
	 */
	private int greenPassValidDays = -1;

	/**
	 * Valid days for persons with booster.
	 */
	private int greenPassBoosterValidDays = -1;

	/**
	 * Default constructor.
	 */
	public TracingConfigGroup() {
		super(GROUPNAME);
	}

	@StringGetter(PUT_TRACEABLE_PERSONS_IN_QUARANTINE)
	public int getPutTraceablePersonsInQuarantineAfterDay() {
		return putTraceablePersonsInQuarantineAfterDay;
	}

	@StringSetter(PUT_TRACEABLE_PERSONS_IN_QUARANTINE)
	public void setPutTraceablePersonsInQuarantineAfterDay(int putTraceablePersonsInQuarantineAfterDay) {
		// yyyy change argument to date.  kai, jun'20
		this.putTraceablePersonsInQuarantineAfterDay = putTraceablePersonsInQuarantineAfterDay;
	}

	@StringGetter(TRACING_DAYS_DISTANCE)
	public int getTracingDayDistance() {
		return tracingDayDistance;
	}

	@StringSetter(TRACING_DAYS_DISTANCE)
	public void setTracingPeriod_days(int tracingDayDistance) {
		this.tracingDayDistance = tracingDayDistance;
	}

	public Map<LocalDate, Integer> getTracingDelay() {
		return tracingDelay;
	}

	@StringSetter(TRACING_DELAY)
	void setTracingDelay(String delay) {
		Map<String, String> map = SPLITTER.split(delay);
		setTracingDelay_days(map.entrySet().stream().collect(Collectors.toMap(
				e -> LocalDate.parse(e.getKey()), e -> Integer.parseInt(e.getValue())
		)));
	}

	@StringGetter(TRACING_DELAY)
	String getTracingDelayString() {
		return JOINER.join(tracingDelay);
	}

	/**
	 * Set tracing delay valid throughout whole simulation.
	 */
	public void setTracingDelay_days(int tracingDelay) {
		setTracingDelay_days(Map.of(LocalDate.of(1970, 1, 1), tracingDelay));
	}

	/**
	 * Set tracing delay in days for individual points in time.
	 */
	public void setTracingDelay_days(Map<LocalDate, Integer> tracingDelay) {
		this.tracingDelay.clear();
		this.tracingDelay.putAll(tracingDelay);
	}

	@StringGetter(TRACING_PROBABILITY)
	public String getTracingProbabilityString() {
		return JOINER.join(tracingProbability);
	}

	public Map<LocalDate, Double> getTracingProbability() {
		return tracingProbability;
	}

	@StringSetter(TRACING_PROBABILITY)
	void setTracingProbability(String capacity) {
		Map<String, String> map = SPLITTER.split(capacity);
		setTracingProbability(map.entrySet().stream().collect(Collectors.toMap(
				e -> LocalDate.parse(e.getKey()), e -> Double.parseDouble(e.getValue())
		)));
	}

	public void setTracingProbability(Map<LocalDate, Double> tracingProbability) {
		this.tracingProbability.clear();
		this.tracingProbability.putAll(tracingProbability);
	}

	/**
	 * Sets one tracing probability valid throughout whole simulation.
	 */
	public void setTracingProbability(double tracingProbability) {
		setTracingProbability(Map.of(LocalDate.of(1970, 1, 1), tracingProbability));
	}

	/**
	 * Sets the tracing capacity for the whole simulation period.
	 *
	 * @param capacity number of persons to trace per day.
	 * @see #setTracingCapacity_pers_per_day(Map)
	 */
	public void setTracingCapacity_pers_per_day(int capacity) {
		setTracingCapacity_pers_per_day(Map.of(LocalDate.of(1970, 1, 1), capacity));
	}

	/**
	 * Sets the tracing capacity for individual days. If a day has no entry the previous will be still valid.
	 *
	 * @param capacity map of dates to changes in capacity.
	 */
	public void setTracingCapacity_pers_per_day(Map<LocalDate, Integer> capacity) {
		tracingCapacity.clear();
		tracingCapacity.putAll(capacity);
	}

	public Map<LocalDate, Integer> getTracingCapacity() {
		return tracingCapacity;
	}

	@StringSetter(CAPACITY)
	void setTracingCapacity(String capacity) {

		Map<String, String> map = SPLITTER.split(capacity);
		setTracingCapacity_pers_per_day(map.entrySet().stream().collect(Collectors.toMap(
				e -> LocalDate.parse(e.getKey()), e -> Integer.parseInt(e.getValue())
		)));
	}

	@StringGetter(CAPACITY)
	String getTracingCapacityString() {
		return JOINER.join(tracingCapacity);
	}

	public void setQuarantineVaccinated(Map<LocalDate, Boolean> value) {
		quarantineVaccinated.clear();
		quarantineVaccinated.putAll(value);
	}

	public Map<LocalDate, Boolean> getQuarantineVaccinated() {
		return quarantineVaccinated;
	}

	@StringSetter(QUARANTINE_VACCINATED)
	void setQuarantineVaccinated(String value) {
		Map<String, String> map = SPLITTER.split(value);
		setQuarantineVaccinated(map.entrySet().stream().collect(Collectors.toMap(
				e -> LocalDate.parse(e.getKey()), e -> Boolean.parseBoolean(e.getValue())
		)));
	}

	@StringGetter(QUARANTINE_VACCINATED)
	String getQuarantineVaccinatedString() {
		return JOINER.join(quarantineVaccinated);
	}


	public void setQuarantineDuration(Map<LocalDate, Integer> value) {
		quarantineDuration.clear();
		quarantineDuration.putAll(value);
	}

	public Map<LocalDate, Integer> getQuarantineDuration() {
		return quarantineDuration;
	}

	@StringSetter(QUARANTINE_DURATION)
	void setQuarantineDuration(String value) {
		Map<String, String> map = SPLITTER.split(value);
		setQuarantineDuration(map.entrySet().stream().collect(Collectors.toMap(
				e -> LocalDate.parse(e.getKey()), e -> Integer.parseInt(e.getValue())
		)));
	}

	@StringGetter(QUARANTINE_DURATION)
	String getQuarantineDurationString() {
		return JOINER.join(quarantineDuration);
	}


	public Map<LocalDate, EpisimPerson.QuarantineStatus> getQuarantineStatus() {
		return quarantineStatus;
	}

	public void setQuarantineStatus(Map<LocalDate, EpisimPerson.QuarantineStatus> value) {
		quarantineStatus.clear();
		quarantineStatus.putAll(value);
	}

	@StringSetter(QUARANTINE_STATUS)
	void setQuarantineStatus(String value) {
		Map<String, String> map = SPLITTER.split(value);
		setQuarantineStatus(map.entrySet().stream().collect(Collectors.toMap(
				e -> LocalDate.parse(e.getKey()), e -> EpisimPerson.QuarantineStatus.valueOf(e.getValue())
		)));
	}

	@StringGetter(QUARANTINE_STATUS)
	String getQuarantineStatusString() {
		return JOINER.join(quarantineStatus);
	}

	@StringGetter(EQUIPMENT_RATE)
	public double getEquipmentRate() {
		return equipmentRate;
	}

	@StringSetter(EQUIPMENT_RATE)
	public void setEquipmentRate(double equipmentRate) {
		this.equipmentRate = equipmentRate;
	}

	@StringGetter(QUARANTINE_RELEASE)
	public QuarantineRelease getQuarantineRelease() {
		return quarantineRelease;
	}

	@StringSetter(QUARANTINE_RELEASE)
	public void setQuarantineRelease(QuarantineRelease quarantineRelease) {
		this.quarantineRelease = quarantineRelease;
	}

	@StringGetter(MIN_DURATION)
	public double getMinDuration() {
		return minDuration;
	}

	@StringSetter(MIN_DURATION)
	public void setMinContactDuration_sec(double minDuration) {
		this.minDuration = minDuration;
	}

	@StringSetter(QUARANTINE_HOUSEHOLD)
	public void setQuarantineHouseholdMembers(boolean quarantineHouseholdMembers) {
		this.quarantineHouseholdMembers = quarantineHouseholdMembers;
	}

	@StringGetter(QUARANTINE_HOUSEHOLD)
	public boolean getQuarantineHousehold() {
		return quarantineHouseholdMembers;
	}

	@StringGetter(TRACE_SUSCEPTIBLE)
	public boolean getTraceSusceptible() {
		return traceSusceptible;
	}

	@StringSetter(TRACE_SUSCEPTIBLE)
	public void setTraceSusceptible(boolean traceSusceptible) {
		this.traceSusceptible = traceSusceptible;
	}

	@StringGetter(CAPACITY_TYPE)
	public CapacityType getCapacityType() {
		return capacityType;
	}

	@StringSetter(CAPACITY_TYPE)
	public void setCapacityType(CapacityType capacityType) {
		this.capacityType = capacityType;
	}

	@StringGetter(STRATEGY)
	public Strategy getStrategy() {
		return strategy;
	}

	@StringSetter(STRATEGY)
	public void setStrategy(Strategy strategy) {
		this.strategy = strategy;
	}

	@StringGetter(LOCATION_THRESHOLD)
	public int getLocationThreshold() {
		return locationThreshold;
	}

	@StringSetter(LOCATION_THRESHOLD)
	public void setLocationThreshold(int locationThreshold) {
		this.locationThreshold = locationThreshold;
	}

	@StringGetter(GREEN_PASS_DAYS)
	public int getGreenPassValidDays() {
		return greenPassValidDays;
	}

	@StringSetter(GREEN_PASS_DAYS)
	public void setGreenPassValidDays(int greenPassValidDays) {
		this.greenPassValidDays = greenPassValidDays;
	}

	@StringSetter(GREEN_PASS_BOOSTER_DAYS)
	public void setGreenPassBoosterValidDays(int greenPassBoosterValidDays) {
		this.greenPassBoosterValidDays = greenPassBoosterValidDays;
	}

	@StringGetter(GREEN_PASS_BOOSTER_DAYS)
	public int getGreenPassBoosterValidDays() {
		return greenPassBoosterValidDays;
	}

	public Set<String> getIgnoredActivities() {
		return ignoredActivities;
	}

	public void setIgnoredActivities(Collection<String> value) {
		ignoredActivities.clear();
		ignoredActivities.addAll(value);
	}

	@StringSetter(IGNORED_ACTIVITIES)
	public void setIgnoredActivities(String value) {
		setIgnoredActivities(Splitter.on(";").splitToList(value));
	}

	@StringGetter(IGNORED_ACTIVITIES)
	String getIgnoredActivitiesString() {
		return Joiner.on(";").join(ignoredActivities);
	}

	public enum CapacityType {PER_PERSON, PER_CONTACT_PERSON}

	public enum Strategy {

		/**
		 * No tracing.
		 */
		NONE,

		/**
		 * Trace contacts of individual persons.
		 */
		INDIVIDUAL_ONLY,

		/**
		 * Put persons at location with lot of infections into quarantine.
		 */
		LOCATION,

		/**
		 * Trace and test all contacts for persons that have been at specific location.
		 */
		LOCATION_WITH_TESTING,

		/**
		 * Follow back all contacts of infected persons.
		 */
		IDENTIFY_SOURCE,

		/**
		 * Randomly put persons into quarantine based on infection numbers.
		 */
		RANDOM
	}


	/**
	 * Defines, which person are released from quarantine.
	 */
	public enum QuarantineRelease {

		/**
		 * Release persons only if they are still susceptible.
		 */
		SUSCEPTIBLE,

		/**
		 * Release persons without showing symptoms.
		 */
		NON_SYMPTOMATIC

	}

}