public static void main(String[] args) throws IOException {
if (args.length == 0) {
throw new RuntimeException("One output directory argument is required");
}
= Paths.get(args[0]);
Path outputDirectory if (!Files.exists(outputDirectory)) {
.createDirectory(outputDirectory);
Files} else {
if (!Files.isDirectory(outputDirectory)) {
throw new IOException("Provided path is not a directory");
}
}
= PersonPlugin.getPersonPlugin();
Plugin personPlugin
= VaccinePlugin.getVaccinePlugin();
Plugin vaccinePlugin
= ModelPlugin.getModelPlugin();
Plugin modelPlugin
= WellState.builder().setSeed(452363456L).build();
WellState wellState = StochasticsPluginData.builder().setMainRNGState(wellState)
StochasticsPluginData stochasticsPluginData .build();
= StochasticsPlugin.getStochasticsPlugin(stochasticsPluginData);
Plugin stochasticsPlugin
= FamilyPluginData.builder()//
FamilyPluginData familyPluginData .setFamilyCount(30)//
.setMaxFamilySize(5)//
.build();
= FamilyPlugin.getFamilyPlugin(familyPluginData); Plugin familyPlugin
4 Reports Plugin
The reports plugin implements an experiment context consumer that records output into tab-delimited (or other delimiter) text files via the java.nio library using blocking file writes. Three new concepts form the core of the reports:
- Report Label – a unique identifier for each report
- Report Header – the header content for the report
- Report Item – the data content(strings) for each line in the report
The report label is a unique identifier used to mark every report item that the plugin processes and helps associate those items to the specific files where they will be recorded. Report items are a flexible list of values that have an associated report label as well as a report header used to build the header of the file. A report file is built from the report items that are associated with a specific report label in the order received. The first report item is used to build the header of the report file. All other lines of the file are an ordered, delimited listing of the string values contained in each report item. No attempt is made to ensure that the header matches the report lines or that all report lines have equal field lengths.
While any data manager or actor can release report items, in practice most reports are managed solely by special purpose, passive reports that simply observe events and do not act on any data manager. These reports often subscribe to multiple event types and aggregate several events into a single report item. This reduces the amount of information recorded in output files and thus output files are often more complex than simple event trace files.
Reports enjoy most of the privileges of actors and use a ReportContext to work with the simulation. They can get information directly from data managers, subscribe to events and plan. However, they cannot mutate data and thus their presence should have little to no impact on the state of the simulation. Like actors, reports are confined to simulation instances and there will often be multiple report instances contributing to the same output file from simultaneously running scenarios.
4.1 Plugin Data Initialization
There is no plugin initialization data class. Reports are contributed by other plugins via their initializers.
4.2 Plugin Behavior
The Plugin contains no data managers, actors or reports.
4.3 Experiment Context Consumer
So far we have seen that reports tend to be produced by specialized classes and that those classes can be added to the simulation via the plugin initialization data. This covers the production and release of the report items from each simulation instance but not what happens to the report items afterward. The output files that receive the report items must work with multiple threads. We manage this with a threadsafe experiment context consumer, the NIOReportItemHandler, that is added to the experiment. The NIOReportItemHandler is created via a builder pattern that allows the modeler to associate report ids to file paths.
4.4 Example Reports (Lesson 12)
We reach back to the previous lessons where we introduced plugins for people, families and vaccines for a demonstration of reports. The reports will center on the vaccination of families in various forms and are implemented by three dedicated report classes in the vaccine plugin:
- FamilyVaccineReport – Immediate reporting based on observed events
- HourlyVaccineReport – Hourly reporting based on observed events
- StatelessVaccineReport – Hourly reports based on inspection of current state
4.5 General Setup
This example uses the following plugins:
- Person Plugin – provides containment for person identifiers
- Stochastics Plugin – (GCM plugin) provides random number generation
- Reports Plugin – (GCM plugin) provides reporting mechanisms
- Family Plugin – defines families and associates people with families
- Vaccine Plugin – maintains vaccine assignments with people and families and defines the three reports
- Model Plugin – provides an actor for loading the initial population and an actor for scheduling vaccinations
The general flow of action in the simulation is that the PopulationLoader actor will add people and families to the simulation based on the initial plugin data provided in the family plugin. The VaccineScheduler actor will then schedule people at random times to be vaccinated. As people and families are created, people join families and people are vaccinated, the various data mangers will generate the relevant events for observation by the three reports. The reports will observe these events and correspondingly generate report items that will flow out of the simulation into the experiment level report mechanisms that will result in report files being written.
Let’s examine Example_12. In Code Block 4.1 we see that the plugins are generated with the person, vaccine and model plugins requiring no input data. The stochastics plugin is generated with a fixed seed value. Next, the family plugin is created with initial data specifying that 30 families will be created and that each family will have a random number of members up to 5 people.
Code Block 4.2 continues with the association of report labels with specific report files. Recall that reports generally use a unique report label and mark each report item with that label. The three reports are added to the simulation by the vaccine plugin.
= NIOReportItemHandler.builder()//
NIOReportItemHandler nioReportItemHandler .addReport(ModelLabel.FAMILY_VACCINE_REPORT, outputDirectory.resolve("family_vaccine_report.csv"))//
.addReport(ModelLabel.HOURLY_VACCINE_REPORT, outputDirectory.resolve("hourly_vaccine_report.csv"))//
.addReport(ModelLabel.STATELESS_VACCINE_REPORT, outputDirectory.resolve("stateless_vaccine_report.csv"))//
.build();
Each report label is now associated with a particular file path. Although each file is a tab-delimited text file, we use the .xls file extension so that they can be automatically opened as a spreadsheet. Had we skipped adding these last specifications, the report items would flow out of the simulation and into the experiment but would not find an associated file and thus be ignored.
Finally, in Code Block 4.3 and Code Block 4.4, we create a single experiment dimension that will override the maximum family size with four values and thus create four scenarios for the experiment.
Dimension familySizeDimension = getFamilySizeDimension();
.builder()//
Experiment.addPlugin(vaccinePlugin)//
.addPlugin(familyPlugin)//
.addPlugin(personPlugin)//
.addPlugin(modelPlugin)//
.addPlugin(stochasticsPlugin)//
.addDimension(familySizeDimension)//
.addExperimentContextConsumer(nioReportItemHandler)//
.build()//
.execute();
private static Dimension getFamilySizeDimension() {
.Builder builder = FunctionalDimensionData.builder();//
FunctionalDimensionData
List<Integer> maxFamilySizes = new ArrayList<>();
.add(3);
maxFamilySizes.add(5);
maxFamilySizes.add(7);
maxFamilySizes.add(10);
maxFamilySizes
for (int i = 0; i < maxFamilySizes.size(); i++) {
Integer maxFamilySize = maxFamilySizes.get(i);
.addValue("Level_" + i, (context) -> {
builder.Builder pluginDataBuilder = context
FamilyPluginData.getPluginDataBuilder(FamilyPluginData.Builder.class);
.setMaxFamilySize(maxFamilySize);
pluginDataBuilder
ArrayList<String> result = new ArrayList<>();
.add(Double.toString(maxFamilySize));
result
return result;
});//
}
.addMetaDatum("max_family_size");//
builder
= builder.build();
FunctionalDimensionData functionalDimensionData return new FunctionalDimension(functionalDimensionData);
}
4.6 The Family Vaccine Report
The first report documents the changes in the number of families that are vaccinated over time as individual people receive the vaccine. The field headers for the report are:
- scenario – the id of the scenario
- max_family_size – the maximum family size dictated by the scenario
- time – the time in days for each item in the report
- unvacinated_families – the number of families that have no members vaccinated
- partially_vaccinated_families – the number of families that have at least one, but not all members vaccinated
- fully_vaccinated_families – the number of families that have all members vaccinated
- unvaccinated_individuals – the number of people who are unvaccinated and have no family assignment
- vaccinated_individuals – the number of people who are vaccinated and have no family assignment
The experiment report mechanisms are responsible for reporting the scenario and the max_family_size fields since they are part of the experiment design. The remaining fields are contributed by the report. Note that family membership is not guaranteed and that some people may not be associated with any family id. The report accounts for these people in the last two fields.
There are four events that drive the report:
- the addition of a person to the simulation
- the addition of a family to the simulation
- the assignment of a person to a family
- the vaccination of a person
Note that the model logic does not allow for the removal of a person from the simulation, the removal of person from a family or loss of vaccination coverage for a person. In a more nuanced model, there would likely be more events that would influence the report.
The FamilyVaccineReport has several private fields and classes for maintaining the five counts of the reports. In Code Block 4.5 we have two convenience enumerations for families and individuals that help with the creation of the report header and with maintaining counts.
private static enum FamilyVaccineStatus {
NONE("unvaccinated_families"), //
PARTIAL("partially_vaccinated_families"), //
FULL("fully_vaccinated_families");//
private final String description;
private FamilyVaccineStatus(final String description) {
this.description = description;
}
}
private static enum IndividualVaccineStatus {
NONE("unvaccinated_individuals"), //
FULL("vaccinated_individuals");//
private final String description;
private IndividualVaccineStatus(final String description) {
this.description = description;
}
}
Code Block 4.6 shows the remaining private fields.
- report id – remains fixed from construction and is used to mark every report item
- reportHeader – is constructed once and used in the construction of every report item
- actorContext – a convenience reference kept by the actor to retrieve the simulation time
- vaccinationDataManager – a convenience reference to retrieve the vaccination status of each person
- familyDataManager – a convenience reference to retrieve the family members associated with a given person who has just been vaccinated
- statusToFamiliesMap – a map from family vaccine status to a mutable counter
- familyToStatusMap – a map for recording the current family vaccine status for each family
- statusToIndividualMap – a map from individual vaccine status to a mutable counter
- individualToStatusMap – a map for recording the current individual vaccine status for each person not assigned to a family
private final ReportLabel reportLabel;
private ReportHeader reportHeader;
private ReportContext reportContext;
private VaccinationDataManager vaccinationDataManager;
private FamilyDataManager familyDataManager;
private final Map<FamilyVaccineStatus, MutableInteger> statusToFamiliesMap = new LinkedHashMap<>();
private final Map<FamilyId, FamilyVaccineStatus> familyToStatusMap = new LinkedHashMap<>();
private final Map<IndividualVaccineStatus, MutableInteger> statusToIndividualsMap = new LinkedHashMap<>();
private final Map<PersonId, IndividualVaccineStatus> individualToStatusMap = new LinkedHashMap<>();
The report’s methods start with its constructor in Code Block 4.7. The report label is recorded and the report header field is built from the support enumerations.
public FamilyVaccineReport(final ReportLabel reportLabel) {
this.reportLabel = reportLabel;
final ReportHeader.Builder builder = ReportHeader.builder();
.add("time");
builder.setReportLabel(reportLabel);
builderfor (final FamilyVaccineStatus familyVaccineStatus : FamilyVaccineStatus.values()) {
.add(familyVaccineStatus.description);
builder}
for (final IndividualVaccineStatus individualVaccineStatus : IndividualVaccineStatus.values()) {
.add(individualVaccineStatus.description);
builder}
= builder.build();
reportHeader }
Next is the initialization method that was passed to the simulation. This is invoked by the simulation just once at the begining of time flow and gives the report a chance to register for events and to initialize the private fields from Code Block 4.6. The report records the actor context and subscribes to the four events of interest in Code Block 4.8. These subscriptions reference local private methods that will be discussed later.
public void init(final ReportContext reportContext) {
this.reportContext = reportContext;
/*
* Subscribe to all the relevant events
*/
.subscribe(VaccinationEvent.class, this::handleVaccinationEvent);
reportContext.subscribe(FamilyAdditionEvent.class, this::handleFamilyAdditionEvent);
reportContext.subscribe(FamilyMemberShipAdditionEvent.class, this::handleFamilyMemberShipAdditionEvent);
reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); reportContext
In Code Block 4.9 we continue with the retrieval of the person, family and vaccination data managers. The maps containing the counts are initialized to zero.
= reportContext.getDataManager(FamilyDataManager.class);
familyDataManager = reportContext.getDataManager(VaccinationDataManager.class);
vaccinationDataManager = reportContext.getDataManager(PersonDataManager.class);
PersonDataManager personDataManager
for (final FamilyVaccineStatus familyVaccineStatus : FamilyVaccineStatus.values()) {
.put(familyVaccineStatus, new MutableInteger());
statusToFamiliesMap}
for (final IndividualVaccineStatus individualVaccineStatus : IndividualVaccineStatus.values()) {
.put(individualVaccineStatus, new MutableInteger());
statusToIndividualsMap}
Code Block 4.10 and Code Block 4.11 use the data managers to fill the count structures with the current state of the population.
for (final FamilyId familyId : familyDataManager.getFamilyIds()) {
final int familySize = familyDataManager.getFamilySize(familyId);
final List<PersonId> familyMembers = familyDataManager.getFamilyMembers(familyId);
int vaccinatedCount = 0;
for (final PersonId personId : familyMembers) {
if (vaccinationDataManager.isPersonVaccinated(personId)) {
++;
vaccinatedCount}
}
;
FamilyVaccineStatus status
if (vaccinatedCount == 0) {
= FamilyVaccineStatus.NONE;
status } else if (vaccinatedCount == familySize) {
= FamilyVaccineStatus.FULL;
status } else {
= FamilyVaccineStatus.PARTIAL;
status }
.get(status).increment();
statusToFamiliesMap.put(familyId, status);
familyToStatusMap
}
for (final PersonId personId : personDataManager.getPeople()) {
if (familyDataManager.getFamilyId(personId).isEmpty()) {
;
IndividualVaccineStatus statusif (vaccinationDataManager.isPersonVaccinated(personId)) {
= IndividualVaccineStatus.FULL;
status } else {
= IndividualVaccineStatus.NONE;
status }
.get(status).increment();
statusToIndividualsMap.put(personId, status);
individualToStatusMap}
}
Initialization finishes with the release of a single report item that summarizes the state of family vaccination at time zero.
.releaseOutput(reportHeader);
reportContextreleaseReportItem();
The methods for handling each event are shown in Code Block 4.13. All four methods select some relevant family id or person id and process changes to the counting data structures using the refreshFamilyStatus() and refreshInidividual() methods. The accounting for reports that are synthesizing multiple events can be somewhat tricky. No assumptions are made as to how people are created, vaccinated and added to families so that changes to those processes in future versions of the model do not cause errors in the report.
private void handleFamilyAdditionEvent(final ReportContext reportContext,
final FamilyAdditionEvent familyAdditionEvent) {
refreshFamilyStatus(familyAdditionEvent.getFamilyId());
}
private void handleFamilyMemberShipAdditionEvent(final ReportContext reportContext,
final FamilyMemberShipAdditionEvent familyMemberShipAdditionEvent) {
.remove(familyMemberShipAdditionEvent.getPersonId());
individualToStatusMaprefreshFamilyStatus(familyMemberShipAdditionEvent.getFamilyId());
}
private void handlePersonAdditionEvent(final ReportContext reportContext,
final PersonAdditionEvent personAdditionEvent) {
final PersonId personId = personAdditionEvent.getPersonId();
final Optional<FamilyId> optional = familyDataManager.getFamilyId(personId);
if (optional.isEmpty()) {
refreshIndividualStatus(personId);
} else {
final FamilyId familyId = optional.get();
refreshFamilyStatus(familyId);
}
}
private void handleVaccinationEvent(final ReportContext reportContext, final VaccinationEvent vaccinationEvent) {
final PersonId personId = vaccinationEvent.getPersonId();
final Optional<FamilyId> optional = familyDataManager.getFamilyId(personId);
if (optional.isEmpty()) {
refreshIndividualStatus(personId);
} else {
final FamilyId familyId = optional.get();
refreshFamilyStatus(familyId);
}
}
The refresh methods in Code Block 4.14 and Code Block 4.15 compare the current vaccination state of the families and individuals against the corresponding states tracked in the counting maps. If a change in the counts has occurred the counts are corrected and a new report item is released.
private void refreshFamilyStatus(final FamilyId familyId) {
final int familySize = familyDataManager.getFamilySize(familyId);
final List<PersonId> familyMembers = familyDataManager.getFamilyMembers(familyId);
int vaccinatedCount = 0;
for (final PersonId personId : familyMembers) {
if (vaccinationDataManager.isPersonVaccinated(personId)) {
++;
vaccinatedCount}
}
;
FamilyVaccineStatus newStatus
if (vaccinatedCount == 0) {
= FamilyVaccineStatus.NONE;
newStatus } else if (vaccinatedCount == familySize) {
= FamilyVaccineStatus.FULL;
newStatus } else {
= FamilyVaccineStatus.PARTIAL;
newStatus }
final FamilyVaccineStatus currentStatus = familyToStatusMap.get(familyId);
if (currentStatus == newStatus) {
return;
}
if (currentStatus != null) {
.get(currentStatus).decrement();
statusToFamiliesMap}
.get(newStatus).increment();
statusToFamiliesMap.put(familyId, newStatus);
familyToStatusMapreleaseReportItem();
}
private void refreshIndividualStatus(final PersonId personId) {
;
IndividualVaccineStatus newStatusif (vaccinationDataManager.isPersonVaccinated(personId)) {
= IndividualVaccineStatus.FULL;
newStatus } else {
= IndividualVaccineStatus.NONE;
newStatus }
final IndividualVaccineStatus currentStatus = individualToStatusMap.get(personId);
if (currentStatus == newStatus) {
return;
}
if (currentStatus != null) {
.get(currentStatus).decrement();
statusToIndividualsMap}
.get(newStatus).increment();
statusToIndividualsMap.put(personId, newStatus);
individualToStatusMapreleaseReportItem();
}
Releasing the report items that summarizes the family vaccination counts requires building a new report item with the fixed report label and report header values determined in the constructor. We then go on to add the time and count values in the order dictated by the helper enumerations so that they follow the header values established in the report header. Once the report item is complete it is released as output via the report context. The simulation will in turn release the report item to the experiment where it will be distributed to the NIOReportItemHandler and then on the specific file manager(s) that record the items.
private void releaseReportItem() {
final ReportItem.Builder builder = ReportItem.builder()//
.setReportLabel(reportLabel)//
.addValue(reportContext.getTime());
for (final FamilyVaccineStatus familyVaccineStatus : statusToFamiliesMap.keySet()) {
= statusToFamiliesMap.get(familyVaccineStatus);
MutableInteger mutableInteger .addValue(mutableInteger.getValue());
builder}
for (final IndividualVaccineStatus individualVaccineStatus : statusToIndividualsMap.keySet()) {
= statusToIndividualsMap.get(individualVaccineStatus);
MutableInteger mutableInteger .addValue(mutableInteger.getValue());
builder}
final ReportItem reportItem = builder.build();
.releaseOutput(reportItem);
reportContext}
The resulting output in Figure 4.1 contains the four scenarios showing the buildup of the population with all families and individuals being unvaccinated. Over time the number of vaccinated families increase and each simulation ends when all people have been vaccinated. The increase of max family size over the experiment causes there to be more people and thus the number of days to reach full vaccination also increases as expected.
scenario | max_family_size | time | unvacinated_families | partially_vaccinated_families | fully_vaccinated_families | unvaccinated_individuals | vaccinated_individuals |
---|---|---|---|---|---|---|---|
0 | 3.0 | 0.0 | 0 | 0 | 0 | 0 | 0 |
0 | 3.0 | 0.0 | 1 | 0 | 0 | 0 | 0 |
0 | 3.0 | 0.0 | 1 | 0 | 0 | 1 | 0 |
0 | 3.0 | 0.0 | 1 | 0 | 0 | 2 | 0 |
0 | 3.0 | 0.0 | 2 | 0 | 0 | 2 | 0 |
… | |||||||
0 | 3.0 | 0.0 | 15 | 0 | 0 | 30 | 0 |
0 | 3.0 | 0.0 | 16 | 0 | 0 | 30 | 0 |
0 | 3.0 | 0.0 | 16 | 0 | 0 | 31 | 0 |
0 | 3.0 | 0.0 | 16 | 0 | 0 | 32 | 0 |
0 | 3.0 | 0.0 | 17 | 0 | 0 | 32 | 0 |
… | |||||||
0 | 3.0 | 1.0603090039611633 | 28 | 2 | 0 | 63 | 0 |
0 | 3.0 | 1.114413651330966 | 27 | 3 | 0 | 63 | 0 |
0 | 3.0 | 1.1516487861564502 | 26 | 4 | 0 | 63 | 0 |
0 | 3.0 | 1.1871612468129367 | 25 | 5 | 0 | 63 | 0 |
0 | 3.0 | 1.2426374003057261 | 25 | 4 | 1 | 63 | 0 |
… | |||||||
3 | 10.0 | 8.981789652547315 | 0 | 4 | 26 | 163 | 3 |
3 | 10.0 | 9.047774924066472 | 0 | 3 | 27 | 163 | 3 |
3 | 10.0 | 9.101648563188967 | 0 | 2 | 28 | 163 | 3 |
3 | 10.0 | 9.18260688675053 | 0 | 1 | 29 | 163 | 3 |
3 | 10.0 | 9.210064962863902 | 0 | 0 | 30 | 163 | 3 |
4.7 Periodic Reports
Producing a new report item each time a relevant event changes the internal tracking variable of a report actor will often produce too much output. An alternative is to periodically release one or more report items, usually on an hourly or daily basis. The reports plugin defines an abstract report class, the PeriodicReport, that manages the periodic flushing of the state of the report. This allows descendant report classes to concentrate on responding to events while leaving the periodic production of report items to the base class.
The PeriodicReport defines a constructor that requires both a report label and a reporting period. If the constructor is overridden, the super() constructor must be invoked. The init() method is declared final in the PeriodicReport class and the descendant report class should implement the prepare() method to conduct initialization. Several protected methods are introduced:
- prepare is called by the init() method of the periodic report, it provides the descendant report class with an opportunity to initialize
- getReportLabel and getReportPeriod retrieve the label and report period passed in construction
- addTimeFieldHeaders is used to help create the report header
- fillTimeFields is used to help create report items
- flush is an abstract method for flushing the content of the report actor that must be implemented by the descendant report class
Our next example report is the HourlyVaccineReport that descends from the PeriodicReport. It produces the same output as the FamilyVaccineReport, but does so on an hourly basis. This outputs a report item every hour whether or not there were stimulating events. The implementation of this report is nearly identical to the previous report and we will concentrate on highlighting the differences between the two approaches.
In Code Block 4.17 we see that the constructor invokes the super constructor. The construction of the report header is aided by the protected method addTimeFieldHeaders() which should be invoked as the first inputs to the report header builder. Note as well that we do not store the report label locally.
public HourlyVaccineReport(ReportLabel reportLabel, ReportPeriod reportPeriod) {
super(reportLabel, reportPeriod);
.Builder builder = ReportHeader.builder();
ReportHeaderaddTimeFieldHeaders(builder);
.setReportLabel(reportLabel);
builderfor (FamilyVaccineStatus familyVaccineStatus : FamilyVaccineStatus.values()) {
.add(familyVaccineStatus.description);
builder}
for (IndividualVaccineStatus individualVaccineStatus : IndividualVaccineStatus.values()) {
.add(individualVaccineStatus.description);
builder}
= builder.build();
reportHeader }
The prepare() method is nearly identical to the previous report’s init() method.
protected void prepare(ReportContext reportContext) {
/*
* Subscribe to all the relevant events
*/
.subscribe(VaccinationEvent.class, this::handleVaccinationEvent);
reportContext.subscribe(FamilyAdditionEvent.class, this::handleFamilyAdditionEvent);
reportContext.subscribe(FamilyMemberShipAdditionEvent.class, this::handleFamilyMemberShipAdditionEvent);
reportContext.subscribe(PersonAdditionEvent.class, this::handlePersonAdditionEvent); reportContext
The releaseReportItem() method of the previous report is now replaced by the flush() method override in Code Block 4.19.
protected void flush(ReportContext reportContext) {
.Builder builder = ReportItem.builder()//
ReportItem.setReportLabel(getReportLabel());
fillTimeFields(builder);
for (FamilyVaccineStatus familyVaccineStatus : statusToFamiliesMap.keySet()) {
= statusToFamiliesMap.get(familyVaccineStatus);
MutableInteger mutableInteger .addValue(mutableInteger.getValue());
builder}
for (IndividualVaccineStatus individualVaccineStatus : statusToIndividualsMap.keySet()) {
= statusToIndividualsMap.get(individualVaccineStatus);
MutableInteger mutableInteger .addValue(mutableInteger.getValue());
builder}
= builder.build();
ReportItem reportItem .releaseOutput(reportItem);
reportContext}
The corresponding invocations of the releaseReportItem() that would have generated a new report item each time an event changed the internal counting variables are dropped. The flush() method will be invoked each time the parent report class determines that the planned next period has occurred. Note also that the time fields of the report item are filled by invoking the fillTimeFields() method which will add the correct time value for the period being reported rather than the current time. Otherwise, the implementations are identical.
The resulting output in Figure 4.2 contains the four scenarios showing the buildup of the population with all families and individuals being unvaccinated. It shows the same overall pattern as the previous report, but treats the reporting of time in integer days and hours. Note that some of the output values repeat over the days and hours since there were no vaccinations during those periods.
scenario | max_family_size | day | hour | unvacinated_families | partially_vaccinated_families | fully_vaccinated_families | unvaccinated_individuals | vaccinated_individuals |
---|---|---|---|---|---|---|---|---|
0 | 3.0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 3.0 | 0 | 1 | 30 | 0 | 0 | 63 | 0 |
0 | 3.0 | 0 | 2 | 30 | 0 | 0 | 63 | 0 |
0 | 3.0 | 0 | 3 | 30 | 0 | 0 | 63 | 0 |
0 | 3.0 | 0 | 4 | 30 | 0 | 0 | 63 | 0 |
… | ||||||||
0 | 3.0 | 2 | 11 | 9 | 17 | 4 | 62 | 1 |
0 | 3.0 | 2 | 12 | 9 | 17 | 4 | 62 | 1 |
0 | 3.0 | 2 | 13 | 8 | 17 | 5 | 62 | 1 |
0 | 3.0 | 2 | 14 | 8 | 17 | 5 | 62 | 1 |
0 | 3.0 | 2 | 15 | 8 | 17 | 5 | 62 | 1 |
… | ||||||||
2 | 7.0 | 3 | 3 | 8 | 21 | 1 | 128 | 1 |
2 | 7.0 | 3 | 4 | 8 | 21 | 1 | 128 | 1 |
2 | 7.0 | 3 | 5 | 8 | 21 | 1 | 128 | 1 |
2 | 7.0 | 3 | 6 | 8 | 21 | 1 | 128 | 1 |
2 | 7.0 | 3 | 7 | 8 | 21 | 1 | 128 | 1 |
… | ||||||||
3 | 10.0 | 9 | 2 | 0 | 3 | 27 | 163 | 3 |
3 | 10.0 | 9 | 3 | 0 | 2 | 28 | 163 | 3 |
3 | 10.0 | 9 | 4 | 0 | 2 | 28 | 163 | 3 |
3 | 10.0 | 9 | 5 | 0 | 1 | 29 | 163 | 3 |
3 | 10.0 | 9 | 6 | 0 | 0 | 30 | 163 | 3 |
Our final example, the StatelessVaccineReport , Code Block 4.20, continues from the HourlyVaccineReport but eschews the stateful counting mechanisms. Like the previous report, it is a periodic report actor but it does not store any state and does not subscribe to any events. Instead, it simply derives the report item on each flush() invocation.
protected void flush(ReportContext reportContext) {
= reportContext.getDataManager(FamilyDataManager.class);
FamilyDataManager familyDataManager = reportContext.getDataManager(VaccinationDataManager.class);
VaccinationDataManager vaccinationDataManager = reportContext.getDataManager(PersonDataManager.class);
PersonDataManager personDataManager
Map<VaccineStatus, MutableInteger> statusMap = new LinkedHashMap<>();
for (VaccineStatus vaccineStatus : VaccineStatus.values()) {
.put(vaccineStatus, new MutableInteger());
statusMap}
// determine the family vaccine status for every family
for (FamilyId familyId : familyDataManager.getFamilyIds()) {
= getFamilyStatus(familyId, vaccinationDataManager, familyDataManager);
VaccineStatus vaccineStatus .get(vaccineStatus).increment();
statusMap}
// ensure that any person not assigned to a family is still counted
for (PersonId personId : personDataManager.getPeople()) {
if (familyDataManager.getFamilyId(personId).isEmpty()) {
= getIndividualStatus(personId, vaccinationDataManager);
VaccineStatus vaccineStatus .get(vaccineStatus).increment();
statusMap}
}
.Builder builder = ReportItem.builder()//
ReportItem.setReportLabel(getReportLabel());
fillTimeFields(builder);
for (VaccineStatus vaccineStatus : VaccineStatus.values()) {
int value = statusMap.get(vaccineStatus).getValue();
.addValue(value);
builder}
= builder.build();
ReportItem reportItem .releaseOutput(reportItem);
reportContext
}
This approach may seem wasteful since there is the potential for a great deal of recalculation, but since this is done on a daily basis, it may be well worth the reduction in memory if the report was actively tracking millions of families.