package gov.nysenate.openleg.model.spotcheck; import com.google.common.collect.*; import gov.nysenate.openleg.service.spotcheck.base.SpotCheckReportService; import java.time.LocalDateTime; import java.util.Collection; import java.util.HashMap; import java.util.Map; import java.util.Optional; /** * A SpotCheckReport is basically a collection of observations that have 1 or more mismatches associated * within them. The ContentKey is templated to allow for reports on specific content types. * * @see SpotCheckReportService * @param <ContentKey> */ public class SpotCheckReport<ContentKey> { /** Identifier for this report. */ protected SpotCheckReportId reportId; /** All observations associated with this report. */ protected Map<ContentKey, SpotCheckObservation<ContentKey>> observations = new HashMap<>(); /** miscellaneous notes pertaining to this report */ protected String notes; /** --- Constructors --- */ public SpotCheckReport() {} public SpotCheckReport(SpotCheckReportId reportId) { this.reportId = reportId; } public SpotCheckReport(SpotCheckReportId reportId, String notes) { this(reportId); this.notes = notes; } /** --- Methods --- */ public SpotCheckReportSummary getSummary() { SpotCheckReportSummary summary = new SpotCheckReportSummary(reportId, notes); summary.addCountsFromObservations(observations.values()); return summary; } /** * Get a count of open mismatches * @param ignored boolean - returns the count of ignored mismatches if true, which are not included if false * @return long */ public long getOpenMismatchCount(boolean ignored) { return observations.values().stream() .map(obs -> obs.getMismatches().values().stream() .filter(mismatch -> !mismatch.isIgnored() ^ ignored) .filter(mismatch -> mismatch.getStatus() != SpotCheckMismatchStatus.RESOLVED) .count() ) .reduce(0L, (a, b) -> a + b); } /** * Get the number of mismatches across all observations grouped by the mismatch status. * @param ignored boolean - get the status count of ignored mismatches if true, which are not included if false * @return Map<SpotCheckMismatchStatus, Long> */ public Map<SpotCheckMismatchStatus, Long> getMismatchStatusCounts(boolean ignored) { if (observations != null) { Map<SpotCheckMismatchStatus, Long> counts = new HashMap<>(); for (SpotCheckMismatchStatus status : SpotCheckMismatchStatus.values()) { counts.put(status, 0L); } observations.values().stream() .flatMap(e -> e.getMismatchStatusCounts(ignored).entrySet().stream()) .forEach(e -> counts.merge(e.getKey(), e.getValue(), Long::sum)); return counts; } throw new IllegalStateException("The observations on this report have not yet been set."); } /** * Gets a count of mismatch types grouped by statuses across all observations. * @param ignored boolean - get type/status counts of ignored mismatches if true, which are not included if false * @return Map<SpotCheckMismatchType, Map<SpotCheckMismatchStatus, Long>> */ public Map<SpotCheckMismatchType, Map<SpotCheckMismatchStatus, Long>> getMismatchTypeStatusCounts(boolean ignored) { if (observations != null) { Map<SpotCheckMismatchType, Map<SpotCheckMismatchStatus, Long>> counts = new HashMap<>(); observations.values().stream() .flatMap(e -> e.getMismatchStatusTypes(ignored).entrySet().stream()) .forEach(e -> { if (!counts.containsKey(e.getKey())) { counts.put(e.getKey(), new HashMap<>()); } counts.get(e.getKey()).merge(e.getValue(), 1L, (a,b) -> a + 1L); }); return counts; } throw new IllegalStateException("The observations on this report have not yet been set."); } /** * Gets a count of mismatch statuses grouped by type across all observations. * @param ignored boolean - get status/type counts of ignored mismatches if true, which are not included if false * @return Map<SpotCheckMismatchStatus, Map<SpotCheckMismatchType, Long>> */ public Map<SpotCheckMismatchStatus, Map<SpotCheckMismatchType, Long>> getMismatchStatusTypeCounts(boolean ignored) { if (observations != null) { Table<SpotCheckMismatchStatus, SpotCheckMismatchType, Long> countTable = HashBasedTable.create(); observations.values().stream() .flatMap(obs -> obs.getMismatchStatusTypes(ignored).entrySet().stream()) .forEach(entry -> { long currentValue = Optional.ofNullable(countTable.get(entry.getValue(), entry.getKey())) .orElse(0l); countTable.put(entry.getValue(), entry.getKey(), currentValue + 1); }); return countTable.rowMap(); } throw new IllegalStateException("The observations on this report have not yet been set."); } /** * Get the number of mismatches across all observations grouped by the mismatch type. * @param ignored boolean - get type counts of ignored mismatches if true, which are not included if false * @return Map<SpotCheckMismatchType, Long> */ public Map<SpotCheckMismatchType, Long> getMismatchTypeCounts(boolean ignored) { if (observations != null) { Map<SpotCheckMismatchType, Long> counts = new HashMap<>(); observations.values().stream() .flatMap(e -> e.getMismatchTypes(ignored).stream()) .forEach(e -> counts.merge(e, 1L, (a,b) -> a + 1L)); return counts; } throw new IllegalStateException("The observations on this report have not yet been set."); } /** * Add the observation to the map. */ public void addObservation(SpotCheckObservation<ContentKey> observation) { if (observation == null) { throw new IllegalArgumentException("Supplied observation cannot be null"); } this.observations.put(observation.getKey(), observation); } /** * Add the collection of observations to the map. */ public void addObservations(Collection<SpotCheckObservation<ContentKey>> observations) { if (observations == null) { throw new IllegalArgumentException("Supplied observation collection cannot be null"); } observations.forEach(this::addObservation); } /** * Get the number of content items observed */ public int getObservedCount() { return Optional.ofNullable(this.observations).map(Map::size).orElse(0); } /** --- Delegates --- */ public LocalDateTime getReportDateTime() { return reportId.getReportDateTime(); } public LocalDateTime getReferenceDateTime() { return reportId.getReferenceDateTime(); } public SpotCheckRefType getReferenceType() { return reportId.getReferenceType(); } /** --- Basic Getters/Setters --- */ public SpotCheckReportId getReportId() { return reportId; } public void setReportId(SpotCheckReportId reportId) { this.reportId = reportId; } public Map<ContentKey, SpotCheckObservation<ContentKey>> getObservations() { return observations; } public void setObservations(Map<ContentKey, SpotCheckObservation<ContentKey>> observations) { this.observations = observations; } public String getNotes() { return notes; } public void setNotes(String notes) { this.notes = notes; } }