package gov.nysenate.openleg.service.spotcheck.daybreak; import com.google.common.collect.Range; import com.google.common.collect.Sets; import gov.nysenate.openleg.dao.base.LimitOffset; import gov.nysenate.openleg.dao.bill.reference.daybreak.DaybreakDao; import gov.nysenate.openleg.dao.spotcheck.SpotCheckReportDao; import gov.nysenate.openleg.model.base.PublishStatus; import gov.nysenate.openleg.model.base.SessionYear; import gov.nysenate.openleg.model.base.Version; import gov.nysenate.openleg.model.bill.BaseBillId; import gov.nysenate.openleg.model.bill.Bill; import gov.nysenate.openleg.model.bill.BillId; import gov.nysenate.openleg.model.spotcheck.daybreak.DaybreakBill; import gov.nysenate.openleg.model.spotcheck.*; import gov.nysenate.openleg.service.bill.data.BillDataService; import gov.nysenate.openleg.service.spotcheck.base.BaseSpotCheckReportService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.time.LocalDate; import java.time.LocalDateTime; import java.util.*; import java.util.stream.Collectors; import static gov.nysenate.openleg.model.spotcheck.SpotCheckMismatchType.OBSERVE_DATA_MISSING; import static gov.nysenate.openleg.model.spotcheck.SpotCheckMismatchType.REFERENCE_DATA_MISSING; import static java.util.stream.Collectors.toSet; /** * SpotCheckReportService implementation that utilizes the DaybreakCheckService to generate * and save reports for bill data. */ @Service("daybreakReport") public class DaybreakReportService extends BaseSpotCheckReportService<BillId> { private static final Logger logger = LoggerFactory.getLogger(DaybreakReportService.class); @Autowired private DaybreakCheckService daybreakCheckService; @Autowired private DaybreakDao daybreakDao; @Autowired private SpotCheckReportDao<BillId> reportDao; @Autowired private BillDataService billDataService; /** --- Implemented Methods --- */ @Override public SpotCheckRefType getSpotcheckRefType() { return SpotCheckRefType.LBDC_DAYBREAK; } @Override protected SpotCheckReportDao<BillId> getReportDao() { return reportDao; } /** {@inheritDoc} */ @Override public SpotCheckReport<BillId> generateReport(LocalDateTime start, LocalDateTime end) throws ReferenceDataNotFoundEx { // Create a new report instance SpotCheckReport<BillId> report = new SpotCheckReport<>(); // Fetch the daybreak bills that are within the given date range logger.info("Fetching daybreak bills..."); Range<LocalDate> dateRange = Range.closed(start.toLocalDate(), end.toLocalDate()); List<DaybreakBill> daybreakBills = daybreakDao.getCurrentDaybreakBills(dateRange); if (daybreakBills.isEmpty()) { throw new ReferenceDataNotFoundEx("The collection of daybreak bills within the given date range is empty."); } // All daybreak bills should have the same reference date. SpotCheckReferenceId refId = daybreakBills.get(0).getReferenceId(); // The report date/time should be truncated to the second to make it easier to query report.setReportId(new SpotCheckReportId(SpotCheckRefType.LBDC_DAYBREAK, refId.getRefActiveDateTime(), LocalDateTime.now())); logger.info("Using Daybreak {} to generate report", refId); // Create a set of the base bill ids from the daybreak bills Set<BaseBillId> daybreakBillIds = daybreakBills.stream() .map(DaybreakBill::getBaseBillId).collect(Collectors.toSet()); // And a set of all of our bill ids (excluding resolutions) present in the backing store Set<BaseBillId> openlegBillIds = billDataService.getBillIds(SessionYear.current(), LimitOffset.ALL).stream() .filter(id -> !id.getBillType().isResolution()) .collect(toSet()); // Check for differences between the set of daybreak and openleg base bill ids. Sets.symmetricDifference(daybreakBillIds, openlegBillIds).stream() .forEach(id -> { // id exists in daybreak or openleg sets, never both. SpotCheckObservation<BillId> sourceMissingObs = new SpotCheckObservation<>(refId, id); SpotCheckMismatch mismatch = null; if (openlegBillIds.contains(id)) { // openleg has the bill but daybreak does not, add reference missing mismatch if bill is published. Bill bill = billDataService.getBill(id); if (billIsPublished(bill)) { logger.info("Missing Daybreak bill {}", id); mismatch = new SpotCheckMismatch(REFERENCE_DATA_MISSING, id, ""); recordMismatch(report, sourceMissingObs, mismatch); } } else { // daybreak has the bill but openleg does not, add observe missing mismatch. logger.info("Missing OpenLeg bill {}", id); mismatch = new SpotCheckMismatch(OBSERVE_DATA_MISSING, "", ""); recordMismatch(report, sourceMissingObs, mismatch); } }); // Perform actual spot checks for the bills common to both sets daybreakBills.stream() .filter(daybreakBill -> openlegBillIds.contains(daybreakBill.getBaseBillId())) .forEach(daybreakBill -> { Bill bill = billDataService.getBill(daybreakBill.getBaseBillId()); report.addObservation(daybreakCheckService.check(bill, daybreakBill)); }); // Set the report as being checked daybreakDao.updateDaybreakReportSetChecked(report.getReferenceDateTime().toLocalDate(), true); // Done with this report! return report; } /** --- Internal Methods --- */ private boolean billIsPublished(Bill bill) { Version activeVersion = bill.getActiveVersion(); Optional<PublishStatus> pubStatus = bill.getPublishStatus(activeVersion); return pubStatus.isPresent() && pubStatus.get().isPublished(); } private void recordMismatch(SpotCheckReport<BillId> report, SpotCheckObservation<BillId> observation, SpotCheckMismatch mismatch) { observation.addMismatch(mismatch); report.addObservation(observation); } }