From 9ca5fa3929a6707bf50b1e01ccee5ac786099c92 Mon Sep 17 00:00:00 2001 From: Andi Huber Date: Thu, 4 Jul 2024 13:04:44 +0200 Subject: [PATCH] (#24): work on tabular reporter --- .../src/main/resources/survey/Campaign.yaml | 16 ++ .../dom/Campaign_downloadMappingTodos.java | 4 +- .../survey/dom/Campaign_generateReport.java | 10 +- .../dita/globodiet/survey/dom/Campaigns.java | 62 ++++-- .../survey/DitaGdSurveyIntegrationTest.java | 63 +++--- .../InterviewXmlParserIntegrationTest.java | 62 ++++-- .../dita/recall24/dto/InterviewSet24.java | 10 + recall24/reporter/pom.xml | 12 +- .../reporter/tabular/TabularReportUtil.java | 60 ------ .../reporter/tabular/TabularReporters.java | 179 ++++++++++++++++++ ...odoReportUtils.java => TodoReporters.java} | 32 ++-- 11 files changed, 355 insertions(+), 155 deletions(-) delete mode 100644 recall24/reporter/src/main/java/dita/recall24/reporter/tabular/TabularReportUtil.java create mode 100644 recall24/reporter/src/main/java/dita/recall24/reporter/tabular/TabularReporters.java rename recall24/reporter/src/main/java/dita/recall24/reporter/todo/{TodoReportUtils.java => TodoReporters.java} (65%) diff --git a/globodiet/schema/src/main/resources/survey/Campaign.yaml b/globodiet/schema/src/main/resources/survey/Campaign.yaml index b05e65ef..9689539a 100644 --- a/globodiet/schema/src/main/resources/survey/Campaign.yaml +++ b/globodiet/schema/src/main/resources/survey/Campaign.yaml @@ -61,6 +61,22 @@ Campaign: multiLine: 4 description: | Detailed information for this campaign. +# sidSystemId: +# column: SYSID +# column-type: nvarchar(40) +# required: true +# unique: false +# description: | +# Semantic Identifiers (SIDs) come as pair of SystemId and ObjectId. +# This specifies the SystemId part (e.g. when generating data exports). +# nutMapPath: +# column: NUTMAPPATH +# column-type: nvarchar(120) +# required: true +# unique: false +# description: | +# Specifies the named path within the configured BlobStore, that points to the QualifiedMap data used for +# food-consumtion to food-component resolution (nutrient mapping). correction: column: CORRECTION column-type: nvarchar(64000) diff --git a/globodiet/survey/src/main/java/dita/globodiet/survey/dom/Campaign_downloadMappingTodos.java b/globodiet/survey/src/main/java/dita/globodiet/survey/dom/Campaign_downloadMappingTodos.java index 9bde1c71..91cf99d6 100644 --- a/globodiet/survey/src/main/java/dita/globodiet/survey/dom/Campaign_downloadMappingTodos.java +++ b/globodiet/survey/src/main/java/dita/globodiet/survey/dom/Campaign_downloadMappingTodos.java @@ -36,7 +36,7 @@ import lombok.RequiredArgsConstructor; -import dita.recall24.reporter.todo.TodoReportUtils; +import dita.recall24.reporter.todo.TodoReporters; import io.github.causewaystuff.blobstore.applib.BlobStore; @Action( @@ -63,7 +63,7 @@ public Clob act() { var systemId = "GD-AT20240507"; //TODO get from Campaign or Survey? var yaml = new StringBuilder(); - var todoReporter = new TodoReportUtils.TodoReporter(systemId, nutMapping, interviewSet); + var todoReporter = new TodoReporters.TodoReporter(interviewSet, systemId, nutMapping); todoReporter.report( DataSink.ofStringConsumer(yaml, StandardCharsets.UTF_8)); diff --git a/globodiet/survey/src/main/java/dita/globodiet/survey/dom/Campaign_generateReport.java b/globodiet/survey/src/main/java/dita/globodiet/survey/dom/Campaign_generateReport.java index 3c532856..89504f35 100644 --- a/globodiet/survey/src/main/java/dita/globodiet/survey/dom/Campaign_generateReport.java +++ b/globodiet/survey/src/main/java/dita/globodiet/survey/dom/Campaign_generateReport.java @@ -34,8 +34,8 @@ import lombok.RequiredArgsConstructor; -import dita.recall24.reporter.tabular.TabularReportUtil; -import dita.recall24.reporter.tabular.TabularReportUtil.Aggregation; +import dita.recall24.reporter.tabular.TabularReporters; +import dita.recall24.reporter.tabular.TabularReporters.Aggregation; import io.github.causewaystuff.blobstore.applib.BlobStore; @Action( @@ -60,9 +60,11 @@ public Clob act(@Parameter final Aggregation aggregation) { var interviewSet = Campaigns.interviewSet(mixee, surveyBlobStore); //TODO flesh out reporting - var tabularReport = new TabularReportUtil.TabularReport(interviewSet, Aggregation.NONE); + var tabularReport = new TabularReporters.TabularReport(interviewSet, null, null, null, null, null, null, aggregation); - return Clob.of("report", CommonMimeType.YAML, interviewSet.toYaml()); + var name = String.format("report-%s", aggregation.name()); + + return Clob.of(name, CommonMimeType.YAML, interviewSet.toYaml()); } } diff --git a/globodiet/survey/src/main/java/dita/globodiet/survey/dom/Campaigns.java b/globodiet/survey/src/main/java/dita/globodiet/survey/dom/Campaigns.java index b49592cf..215fd46c 100644 --- a/globodiet/survey/src/main/java/dita/globodiet/survey/dom/Campaigns.java +++ b/globodiet/survey/src/main/java/dita/globodiet/survey/dom/Campaigns.java @@ -27,6 +27,7 @@ import lombok.experimental.UtilityClass; +import dita.commons.food.composition.FoodCompositionRepository; import dita.commons.qmap.QualifiedMap; import dita.commons.types.Message; import dita.globodiet.survey.util.InterviewUtils; @@ -45,7 +46,9 @@ public class Campaigns { enum DataSourceLocation { INTERVIEW, FCDB, - QMAP; + QMAP_NUT, + QMAP_FCO, + QMAP_POC; NamedPath namedPath(final Campaign campaign) { if(campaign==null || _Strings.isNullOrEmpty(campaign.getSurveyCode()) @@ -56,7 +59,9 @@ NamedPath namedPath(final Campaign campaign) { return switch(this) { case INTERVIEW -> root.add("campaigns").add(NamedPath.of(campaign.getCode().toLowerCase())); case FCDB -> root.add("fcdb").add("fcdb.yaml.7z"); - case QMAP -> root.add("qmap").add("qmap.yaml.7z"); + case QMAP_NUT -> root.add("qmap").add("nut.yaml.7z"); + case QMAP_FCO -> root.add("qmap").add("fco.yaml"); + case QMAP_POC -> root.add("qmap").add("poc.yaml"); }; } } @@ -87,22 +92,38 @@ public InterviewSet24.Dto interviewSet( return interviewSet; } - // -- NUT MAPPING (Q-MAP) + // -- FCDB - public QualifiedMap nutMapping( + public FoodCompositionRepository fcdb( final Campaign campaign, final BlobStore blobStore) { - var mapDataSource = SevenZUtils.decompress( - blobStore - .lookupBlob(DataSourceLocation.QMAP.namedPath(campaign)) - .orElseThrow() - .asDataSource()); + var fcdbDataSource = blobStore.lookupBlob(DataSourceLocation.FCDB.namedPath(campaign)) + .orElseThrow() + .asDataSource(); + var foodCompositionRepo = FoodCompositionRepository.tryFromYaml(SevenZUtils.decompress(fcdbDataSource)) + .valueAsNonNullElseFail(); + return foodCompositionRepo; + } + + // -- Q-MAP - var qMap = QualifiedMap.tryFromYaml(mapDataSource) - .valueAsNonNullElseFail(); + public QualifiedMap nutMapping( + final Campaign campaign, + final BlobStore blobStore) { + return loadQmap(DataSourceLocation.QMAP_NUT, campaign, blobStore); + } + + public QualifiedMap fcoMapping( + final Campaign campaign, + final BlobStore blobStore) { + return loadQmap(DataSourceLocation.QMAP_FCO, campaign, blobStore); + } - return qMap; + public QualifiedMap pocMapping( + final Campaign campaign, + final BlobStore blobStore) { + return loadQmap(DataSourceLocation.QMAP_POC, campaign, blobStore); } // -- HELPER @@ -137,4 +158,21 @@ private final RecallNode24.Annotation toAnnotation() { } + private QualifiedMap loadQmap( + final DataSourceLocation loc, + final Campaign campaign, + final BlobStore blobStore) { + var mapDataSource = + blobStore + .lookupBlob(loc.namedPath(campaign)) + .orElseThrow() + .asDataSource(); + switch(loc) { + case QMAP_NUT -> + mapDataSource = SevenZUtils.decompress(mapDataSource); + default -> {} + } + return QualifiedMap.tryFromYaml(mapDataSource).valueAsNonNullElseFail(); + } + } diff --git a/globodiet/survey/src/test/java/dita/globodiet/survey/DitaGdSurveyIntegrationTest.java b/globodiet/survey/src/test/java/dita/globodiet/survey/DitaGdSurveyIntegrationTest.java index f91e5b37..c26655d8 100644 --- a/globodiet/survey/src/test/java/dita/globodiet/survey/DitaGdSurveyIntegrationTest.java +++ b/globodiet/survey/src/test/java/dita/globodiet/survey/DitaGdSurveyIntegrationTest.java @@ -18,29 +18,18 @@ */ package dita.globodiet.survey; -import java.util.function.Consumer; -import java.util.stream.Stream; - import jakarta.inject.Inject; import org.springframework.beans.factory.annotation.Qualifier; -import org.springframework.lang.Nullable; import org.apache.causeway.testing.integtestsupport.applib.CausewayIntegrationTestAbstract; -import lombok.NonNull; - import dita.commons.food.composition.FoodCompositionRepository; import dita.commons.qmap.QualifiedMap; -import dita.commons.types.Message; -import dita.globodiet.survey.recall24.InterviewXmlParser; -import dita.globodiet.survey.util.InterviewUtils; -import dita.recall24.dto.Correction24; +import dita.globodiet.survey.dom.Campaign; +import dita.globodiet.survey.dom.Campaigns; import dita.recall24.dto.InterviewSet24; -import dita.recall24.dto.util.Recall24DtoUtils; import io.github.causewaystuff.blobstore.applib.BlobStore; -import io.github.causewaystuff.commons.base.types.NamedPath; -import io.github.causewaystuff.commons.compression.SevenZUtils; public abstract class DitaGdSurveyIntegrationTest extends CausewayIntegrationTestAbstract { @@ -48,36 +37,31 @@ public abstract class DitaGdSurveyIntegrationTest @Inject @Qualifier("survey") protected BlobStore surveyBlobStore; protected FoodCompositionRepository loadFcdb() { - var fcdbDataSource = SevenZUtils.decompress( - surveyBlobStore.lookupBlob(NamedPath.of("fcdb", "fcdb.yaml.7z")).orElseThrow().asDataSource()); - - var foodCompositionRepo = FoodCompositionRepository.tryFromYaml(fcdbDataSource) - .valueAsNonNullElseFail(); - - return foodCompositionRepo; + return Campaigns.fcdb(campaignForTesting(), surveyBlobStore); } protected QualifiedMap loadNutMapping() { - var mapDataSource = SevenZUtils.decompress( - surveyBlobStore.lookupBlob(NamedPath.of("qmap", "qmap.yaml.7z")).orElseThrow().asDataSource()); + return Campaigns.nutMapping(campaignForTesting(), surveyBlobStore); + } - var qMap = QualifiedMap.tryFromYaml(mapDataSource) - .valueAsNonNullElseFail(); + protected QualifiedMap loadFcoMapping() { + return Campaigns.fcoMapping(campaignForTesting(), surveyBlobStore); + } - return qMap; + protected QualifiedMap loadPocMapping() { + return Campaigns.pocMapping(campaignForTesting(), surveyBlobStore); } - protected Stream loadAndStreamInterviews( - final @NonNull NamedPath path, - final @Nullable Correction24 correction, - final @Nullable Consumer messageConsumer) { - return InterviewUtils.streamSources(surveyBlobStore, path, true) - .map(ds->InterviewXmlParser.parse(ds, messageConsumer)) - .map(Recall24DtoUtils.correct(correction)); + protected InterviewSet24.Dto loadInterviewSet() { + return Campaigns.interviewSet(campaignForTesting(), surveyBlobStore); } - protected Correction24 loadCorrection() { - return Correction24.tryFromYaml(""" + // -- HELPER + + private Campaign campaignForTesting() { + var campaign = new Campaign(); + campaign.setCode("wave1"); + campaign.setCorrection(""" respondents: - alias: "EB0070" newAlias: "EB_0070" @@ -91,8 +75,15 @@ protected Correction24 loadCorrection() { dateOfBirth: "2002-09-21" - alias: "EB_0093" sex: FEMALE - """) - .valueAsNullableElseFail(); + - alias: "EB_0088" + dateOfBirth: "1967-06-09" + - alias: "EB_0032" + dateOfBirth: "1999-03-04" + - alias: "EB_0116" + dateOfBirth: "1998-01-26" + """); + campaign.setSurveyCode("at-national-2026"); + return campaign; } } diff --git a/globodiet/survey/src/test/java/dita/globodiet/survey/recall24/InterviewXmlParserIntegrationTest.java b/globodiet/survey/src/test/java/dita/globodiet/survey/recall24/InterviewXmlParserIntegrationTest.java index 07ed479f..d0da8066 100644 --- a/globodiet/survey/src/test/java/dita/globodiet/survey/recall24/InterviewXmlParserIntegrationTest.java +++ b/globodiet/survey/src/test/java/dita/globodiet/survey/recall24/InterviewXmlParserIntegrationTest.java @@ -19,30 +19,32 @@ package dita.globodiet.survey.recall24; import java.io.File; +import java.io.IOException; +import java.util.List; import java.util.function.Consumer; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; -import org.apache.causeway.applib.graph.tree.TreeNode; import org.apache.causeway.commons.collections.Can; -import org.apache.causeway.commons.io.DataSink; + +import lombok.val; import dita.commons.qmap.QualifiedMap; import dita.commons.qmap.QualifiedMap.QualifiedMapKey; import dita.commons.qmap.QualifiedMapEntry; +import dita.commons.sid.SemanticIdentifier; +import dita.commons.sid.SemanticIdentifierSet; import dita.globodiet.survey.DitaGdSurveyIntegrationTest; import dita.globodiet.survey.DitaTestModuleGdSurvey; import dita.globodiet.survey.PrivateDataTest; -import dita.globodiet.survey.util.InterviewUtils; import dita.recall24.dto.RecallNode24; import dita.recall24.dto.Record24; -import dita.recall24.dto.util.Recall24DtoUtils; import dita.recall24.dto.util.Recall24SummaryStatistics; import dita.recall24.dto.util.Recall24SummaryStatistics.MappingTodo; -import dita.recall24.reporter.todo.TodoReportUtils; -import io.github.causewaystuff.commons.base.types.NamedPath; +import dita.recall24.reporter.tabular.TabularReporters; +import dita.recall24.reporter.tabular.TabularReporters.Aggregation; @SpringBootTest(classes = { DitaTestModuleGdSurvey.class, @@ -56,28 +58,46 @@ void parsingFromBlobStore() { final var systemId = "GD-AT20240507"; var nutMapping = loadNutMapping(); - var correction = loadCorrection(); + var fcoMapping = loadFcoMapping(); + var pocMapping = loadPocMapping(); + var interviewSet = loadInterviewSet(); var stats = new Recall24SummaryStatistics(); var recordProcessor = new RecordProcessor(stats, systemId, nutMapping); - var interviewSet = InterviewUtils - .interviewSetFromBlobStrore(NamedPath.of("at-national-2026"), surveyBlobStore, correction, null) - //.transform(new NutriDbConverters.ToNutriDbTransfomer()) - ; + //TODO flesh out reporting + var xlsxFile = new File("d:/tmp/_scratch/report-no-aggregates.xlsx"); + var tabularReport = new TabularReporters.TabularReport(interviewSet, systemId, + nutMapping, + fcoMapping, SemanticIdentifierSet.ofCollection(List.of(new SemanticIdentifier("Language", "de"))), + pocMapping, SemanticIdentifierSet.ofCollection(List.of(new SemanticIdentifier("Language", "de"))), + Aggregation.NONE); + tabularReport.report(xlsxFile); + + val pb = new ProcessBuilder(); + + pb.directory(new File("d:/tmp/_scratch")); + pb.command(List.of( + "C:/Program Files/LibreOffice/program/scalc.exe", + xlsxFile.getAbsolutePath())); + pb.inheritIO(); + + try { + pb.start().waitFor(); + } catch (InterruptedException | IOException e) { + e.printStackTrace(); + } - var todoReporter = new TodoReportUtils.TodoReporter(systemId, nutMapping, interviewSet); - todoReporter.report( - DataSink.ofFile(new File("d:/tmp/_scratch/mapping-todos.txt"))); +// var todoReporter = new TodoReportUtils.TodoReporter(systemId, nutMapping, interviewSet); +// todoReporter.report( +// DataSink.ofFile(new File("d:/tmp/_scratch/mapping-todos.txt"))); - Recall24DtoUtils.wrapAsTreeNode(interviewSet) - .streamDepthFirst() - .map(TreeNode::getValue) - .forEach((RecallNode24 node)->{ - stats.accept((dita.recall24.dto.RecallNode24) node); + interviewSet.streamDepthFirst() + .forEach((final RecallNode24 node)->{ + stats.accept(node); switch(node) { - case Record24.Consumption cRec -> recordProcessor.accept(cRec); - default -> {} + case Record24.Consumption cRec -> recordProcessor.accept(cRec); + default -> {} } }); diff --git a/recall24/dto/src/main/java/dita/recall24/dto/InterviewSet24.java b/recall24/dto/src/main/java/dita/recall24/dto/InterviewSet24.java index fc198c8c..98c78033 100644 --- a/recall24/dto/src/main/java/dita/recall24/dto/InterviewSet24.java +++ b/recall24/dto/src/main/java/dita/recall24/dto/InterviewSet24.java @@ -31,6 +31,7 @@ import org.springframework.lang.Nullable; +import org.apache.causeway.applib.graph.tree.TreeNode; import org.apache.causeway.commons.collections.Can; import org.apache.causeway.commons.internal.base._NullSafe; import org.apache.causeway.commons.io.JsonUtils; @@ -132,6 +133,15 @@ public Stream streamInterviews() { .flatMap(resp->resp.interviews().stream()); } + /** + * @implNote requires Causewaystuff tree metamodel integration + */ + public Stream streamDepthFirst() { + return Recall24DtoUtils.wrapAsTreeNode(this) + .streamDepthFirst() + .map(TreeNode::getValue); + } + /** * Returns a new tree with the transformed nodes. * @param transformer - transforms fields only (leave parent child relations untouched) diff --git a/recall24/reporter/pom.xml b/recall24/reporter/pom.xml index a97c6024..abfd9adc 100644 --- a/recall24/reporter/pom.xml +++ b/recall24/reporter/pom.xml @@ -34,12 +34,22 @@ additional - + at.ac.univie.nutrition.dita dita-recall24-dto + + org.apache.causeway.core + causeway-core-metamodel + + + + org.apache.causeway.extensions + causeway-extensions-tabular-excel + + diff --git a/recall24/reporter/src/main/java/dita/recall24/reporter/tabular/TabularReportUtil.java b/recall24/reporter/src/main/java/dita/recall24/reporter/tabular/TabularReportUtil.java deleted file mode 100644 index bde03829..00000000 --- a/recall24/reporter/src/main/java/dita/recall24/reporter/tabular/TabularReportUtil.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Licensed to the Apache Software Foundation (ASF) under one - * or more contributor license agreements. See the NOTICE file - * distributed with this work for additional information - * regarding copyright ownership. The ASF licenses this file - * to you 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 distributed 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 the - * specific language governing permissions and limitations - * under the License. - */ -package dita.recall24.reporter.tabular; - -import lombok.experimental.UtilityClass; - -import dita.recall24.dto.InterviewSet24; - -@UtilityClass -public class TabularReportUtil { - - public enum Aggregation { - /** - * Each consumption is reported as is. - */ - NONE, - /** - * Sum total of nutrient values for each meal. - */ - MEAL, - /** - * Sum total of nutrient values for each interview. - */ - INTERVIEW, - /** - * Average of nutrient values for each respondent per interview (averaged over all interviews available for a given respondent). - */ - RESPONDENT_AVERAGE, - /** - * Variant that groups by food-group. - */ - RESPONDENT_AVERAGE_GROUP_BY_FOOD_GROUP, - /** - * Variant that groups by food-group and food-subgroup. - */ - RESPONDENT_AVERAGE_GROUP_BY_FOOD_GROUP_AND_SUBGROUP - } - - public record TabularReport( - InterviewSet24.Dto interviewSet, - Aggregation aggregation) { - } - -} diff --git a/recall24/reporter/src/main/java/dita/recall24/reporter/tabular/TabularReporters.java b/recall24/reporter/src/main/java/dita/recall24/reporter/tabular/TabularReporters.java new file mode 100644 index 00000000..073d67a2 --- /dev/null +++ b/recall24/reporter/src/main/java/dita/recall24/reporter/tabular/TabularReporters.java @@ -0,0 +1,179 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 distributed 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 the + * specific language governing permissions and limitations + * under the License. + */ +package dita.recall24.reporter.tabular; + +import java.io.File; +import java.math.BigDecimal; +import java.time.format.DateTimeFormatter; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.Locale; +import java.util.Set; + +import org.apache.causeway.applib.annotation.PropertyLayout; +import org.apache.causeway.core.metamodel.tabular.simple.DataTable; +import org.apache.causeway.extensions.tabular.excel.exporter.CollectionContentsAsExcelExporter; + +import lombok.Data; +import lombok.experimental.UtilityClass; + +import dita.commons.qmap.QualifiedMap; +import dita.commons.qmap.QualifiedMapEntry; +import dita.commons.sid.SemanticIdentifier; +import dita.commons.sid.SemanticIdentifierSet; +import dita.recall24.dto.Interview24; +import dita.recall24.dto.InterviewSet24; +import dita.recall24.dto.Meal24; +import dita.recall24.dto.RecallNode24; +import dita.recall24.dto.Record24; + +@UtilityClass +public class TabularReporters { + + public enum Aggregation { + /** + * Each consumption is reported as is. + */ + NONE, + /** + * Sum total of nutrient values for each meal. + */ + MEAL, + /** + * Sum total of nutrient values for each interview. + */ + INTERVIEW, + /** + * Average of nutrient values for each respondent per interview (averaged over all interviews available for a given respondent). + */ + RESPONDENT_AVERAGE, + /** + * Variant that groups by food-group. + */ + RESPONDENT_AVERAGE_GROUP_BY_FOOD_GROUP, + /** + * Variant that groups by food-group and food-subgroup. + */ + RESPONDENT_AVERAGE_GROUP_BY_FOOD_GROUP_AND_SUBGROUP + } + + + public record TabularReport( + InterviewSet24.Dto interviewSet, + String systemId, + QualifiedMap nutMapping, + QualifiedMap fcoMapping, + SemanticIdentifierSet fcoQualifier, + QualifiedMap pocMapping, + SemanticIdentifierSet pocQualifier, + Aggregation aggregation) { + + record ConsumptionRow( + @PropertyLayout(sequence = "1.0", describedAs = "sequential\nrespondent\nindex") + int respondentOrdinal, + + @PropertyLayout(sequence = "1.1", describedAs = "anonymized\nrespondent identifier,\n" + + "unique to the\ncorresponding survey") + String respondentAlias, + + @PropertyLayout(sequence = "2.0", describedAs = "respondent's\nn-th interview\n(chronological)") + int interviewOrdinal, + + @PropertyLayout(sequence = "3.0", describedAs = "food\nconsumption\noccasion\ncode") + String fco, + + @PropertyLayout(sequence = "3.1", describedAs = "meal happened\nwhen and where") + String meal, + + @PropertyLayout(sequence = "99") + BigDecimal value) { + } + + @Data + private static class RowFactory { + final Set respondentAliasSeenBefore = new HashSet<>(); + int respondentOrdinal; + String respondentAlias; + int interviewOrdinal; + String fco; + String meal; + // + ConsumptionRow row(final Record24.Consumption cRec){ + return new ConsumptionRow( + respondentOrdinal, + respondentAlias, + interviewOrdinal, + fco, + meal, + cRec.amountConsumed()); + } + void setRespondentAlias(final String respondentAlias) { + if(respondentAliasSeenBefore.add(respondentAlias)) respondentOrdinal++; + this.respondentAlias = respondentAlias; + } + } + + public void report(final File file) { + + var rowFactory = new RowFactory(); + var consumptions = new ArrayList(); + var hourOfDayFormat = DateTimeFormatter.ofPattern("HH:mm", Locale.ROOT); + + interviewSet.streamDepthFirst() + .forEach((final RecallNode24 node)->{ + switch(node) { + case Interview24.Dto iv -> { + rowFactory.setRespondentAlias(iv.parentRespondent().alias()); + rowFactory.setInterviewOrdinal(iv.interviewOrdinal()); + } + case Meal24.Dto meal -> { + rowFactory.setFco(meal.foodConsumptionOccasionId()); + var fcoLabel = fcoMapping.lookupEntry(new SemanticIdentifier(systemId, meal.foodConsumptionOccasionId()), fcoQualifier) + .map(QualifiedMapEntry::target) + .map(SemanticIdentifier::objectId) + .orElse("?") + .replace('_', ' '); + var pocLabel = pocMapping.lookupEntry(new SemanticIdentifier(systemId, meal.foodConsumptionPlaceId()), pocQualifier) + .map(QualifiedMapEntry::target) + .map(SemanticIdentifier::objectId) + .orElse("?") + .replace('_', ' '); + var timeOfDayLabel = meal.hourOfDay().format(hourOfDayFormat); + rowFactory.setMeal(String.format("%s (%s) @ %s", fcoLabel, timeOfDayLabel, pocLabel)); + } + case Record24.Consumption cRec -> { + var mapKey = cRec.asFoodConsumption(systemId).qualifiedMapKey(); + var mapEntry = nutMapping.lookupEntry(mapKey); + if(!mapEntry.isPresent()) { + //unmapped.add(mapKey); + } + consumptions.add(rowFactory.row(cRec)); + } + default -> {} + } + }); + + var dataTable = DataTable.forDomainType(ConsumptionRow.class); + dataTable.setDataElementPojos(consumptions); + + new CollectionContentsAsExcelExporter().createExport(dataTable, file); + } + } + +} diff --git a/recall24/reporter/src/main/java/dita/recall24/reporter/todo/TodoReportUtils.java b/recall24/reporter/src/main/java/dita/recall24/reporter/todo/TodoReporters.java similarity index 65% rename from recall24/reporter/src/main/java/dita/recall24/reporter/todo/TodoReportUtils.java rename to recall24/reporter/src/main/java/dita/recall24/reporter/todo/TodoReporters.java index 31aa4932..7b4bbc9a 100644 --- a/recall24/reporter/src/main/java/dita/recall24/reporter/todo/TodoReportUtils.java +++ b/recall24/reporter/src/main/java/dita/recall24/reporter/todo/TodoReporters.java @@ -20,7 +20,6 @@ import java.util.TreeSet; -import org.apache.causeway.applib.graph.tree.TreeNode; import org.apache.causeway.commons.io.DataSink; import org.apache.causeway.commons.io.DataSource; @@ -32,34 +31,29 @@ import dita.recall24.dto.InterviewSet24; import dita.recall24.dto.RecallNode24; import dita.recall24.dto.Record24; -import dita.recall24.dto.util.Recall24DtoUtils; @UtilityClass -public class TodoReportUtils { +public class TodoReporters { public record TodoReporter( + InterviewSet24.Dto interviewSet, String systemId, - QualifiedMap nutMapping, - InterviewSet24.Dto interviewSet) { + QualifiedMap nutMapping) { - public void report( - final DataSink dataSink) { + public void report(final DataSink dataSink) { val unmapped = new TreeSet(); - val root = Recall24DtoUtils.wrapAsTreeNode(interviewSet); - root - .streamDepthFirst() - .map(TreeNode::getValue) - .forEach((RecallNode24 node)->{ + interviewSet.streamDepthFirst() + .forEach((final RecallNode24 node)->{ switch(node) { - case Record24.Consumption cRec -> { - var mapKey = cRec.asFoodConsumption(systemId).qualifiedMapKey(); - var mapEntry = nutMapping.lookupEntry(mapKey); - if(!mapEntry.isPresent()) { - unmapped.add(mapKey); + case Record24.Consumption cRec -> { + var mapKey = cRec.asFoodConsumption(systemId).qualifiedMapKey(); + var mapEntry = nutMapping.lookupEntry(mapKey); + if(!mapEntry.isPresent()) { + unmapped.add(mapKey); + } } - } - default -> {} + default -> {} } });