package gov.nysenate.openleg.processor.agenda;
import gov.nysenate.openleg.model.agenda.*;
import gov.nysenate.openleg.model.base.SessionYear;
import gov.nysenate.openleg.model.bill.*;
import gov.nysenate.openleg.model.entity.Chamber;
import gov.nysenate.openleg.model.entity.CommitteeId;
import gov.nysenate.openleg.model.entity.SessionMember;
import gov.nysenate.openleg.model.process.DataProcessUnit;
import gov.nysenate.openleg.model.sobi.SobiFragment;
import gov.nysenate.openleg.model.sobi.SobiFragmentType;
import gov.nysenate.openleg.processor.base.AbstractDataProcessor;
import gov.nysenate.openleg.processor.base.ParseError;
import gov.nysenate.openleg.processor.sobi.SobiProcessor;
import gov.nysenate.openleg.util.DateUtils;
import gov.nysenate.openleg.util.XmlHelper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;
import javax.annotation.PostConstruct;
import javax.xml.xpath.XPathExpressionException;
import java.io.IOException;
import java.time.LocalDateTime;
@Service
public class AgendaVoteProcessor extends AbstractDataProcessor implements SobiProcessor
{
private static final Logger logger = LoggerFactory.getLogger(AgendaVoteProcessor.class);
@Autowired private XmlHelper xml;
@PostConstruct
public void init() {
initBase();
}
@Override
public SobiFragmentType getSupportedType() {
return SobiFragmentType.AGENDA_VOTE;
}
@Override
public void process(SobiFragment sobiFragment) {
LocalDateTime modifiedDate = sobiFragment.getPublishedDateTime();
DataProcessUnit unit = createProcessUnit(sobiFragment);
try {
Document doc = xml.parse(sobiFragment.getText());
Node xmlAgendaVote = xml.getNode("SENATEDATA/senagendavote", doc);
Integer agendaNo = xml.getInteger("@no", xmlAgendaVote);
SessionYear session = new SessionYear(xml.getInteger("@sessyr", xmlAgendaVote));
Integer year = xml.getInteger("@year", xmlAgendaVote);
AgendaId agendaId = new AgendaId(agendaNo, year);
Agenda agenda = getOrCreateAgenda(agendaId, sobiFragment);
agenda.setModifiedDateTime(modifiedDate);
logger.info("Processing Votes for {} - {}", agendaId, sobiFragment);
NodeList xmlAddenda = xml.getNodeList("addendum", xmlAgendaVote);
for (int i = 0; i < xmlAddenda.getLength(); i++) {
Node xmlAddendum = xmlAddenda.item(i);
String addendumId = xml.getString("@id", xmlAddendum);
logger.info("\tProcessing Vote Addendum {}", (addendumId.isEmpty()) ? "''" : addendumId);
// Use the existing vote addendum if available, else create a new one
AgendaVoteAddendum addendum = agenda.getAgendaVoteAddendum(addendumId);
if (addendum == null) {
addendum = new AgendaVoteAddendum(agendaId, addendumId, modifiedDate);
agenda.putAgendaVoteAddendum(addendum);
}
addendum.setModifiedDateTime(modifiedDate);
NodeList xmlCommittees = xml.getNodeList("committees/committee", xmlAddendum);
for (int j = 0; j < xmlCommittees.getLength(); j++) {
Node xmlCommittee = xmlCommittees.item(j);
String action = xml.getString("@action", xmlCommittee);
String name = xml.getString("name/text()", xmlCommittee);
// We only get agendas for senate committees. This may or may not change in the future.
CommitteeId committeeId = new CommitteeId(Chamber.SENATE, name);
// If the action is remove, then discard the committee and move on
if (action.equals("remove")) {
addendum.removeCommittee(committeeId);
continue;
}
// Otherwise, the committee is completely replaced
String chair = xml.getString("chair/text()", xmlCommittee);
LocalDateTime meetDateTime = DateUtils.getLrsDateTime(
xml.getString("meetdate/text()", xmlCommittee) + xml.getString("meettime/text()", xmlCommittee));
AgendaVoteCommittee voteCommittee = new AgendaVoteCommittee(committeeId, chair, meetDateTime);
NodeList xmlMembers = xml.getNodeList("attendancelist/member", xmlCommittee);
for (int k = 0; k < xmlMembers.getLength(); k++) {
Node xmlMember = xmlMembers.item(k);
String memberName = xml.getString("name/text()", xmlMember);
SessionMember member = getMemberFromShortName(memberName, session, Chamber.SENATE);
Integer rank = xml.getInteger("rank/text()", xmlMember);
String party = xml.getString("party/text()", xmlMember);
String attendance = xml.getString("attendance", xmlMember);
AgendaVoteAttendance memberAttendance = new AgendaVoteAttendance(member, rank, party, attendance);
voteCommittee.addAttendance(memberAttendance);
}
NodeList xmlBills = xml.getNodeList("bills/bill", xmlCommittee);
for (int k = 0; k < xmlBills.getLength(); k++) {
Node xmlBill = xmlBills.item(k);
String printNo = xml.getString("@no", xmlBill);
BillId billId = new BillId(printNo, session);
String voteActionCode = xml.getString("action/text()", xmlBill);
AgendaVoteAction voteAction = AgendaVoteAction.valueOfCode(voteActionCode);
String referCommittee = xml.getString("refercomm/text()", xmlBill);
CommitteeId referCommitteeId = null;
if (!referCommittee.isEmpty()) {
referCommitteeId = new CommitteeId(Chamber.SENATE, referCommittee);
}
String withAmd = xml.getString("withamd/text()", xmlBill);
boolean withAmdBoolean = (withAmd != null && withAmd.equalsIgnoreCase("Y"));
// The AgendaVoteBill will contain the vote as well as additional vote metadata specific
// to committee votes.
AgendaVoteBill voteBill = new AgendaVoteBill(voteAction, referCommitteeId, withAmdBoolean);
// Create the committee bill vote.
BillVote vote = new BillVote(billId, meetDateTime.toLocalDate(), BillVoteType.COMMITTEE, 1, committeeId);
vote.setModifiedDateTime(modifiedDate);
vote.setPublishedDateTime(modifiedDate);
// Add the members and their vote to the BillVote.
NodeList xmlVotes = xml.getNodeList("votes/member", xmlBill);
for (int v = 0; v < xmlVotes.getLength(); v++) {
Node xmlVote = xmlVotes.item(v);
String voterName = xml.getString("name/text()", xmlVote);
SessionMember voterMember = getMemberFromShortName(voterName, session, Chamber.SENATE);
String voteCodeStr = xml.getString("vote/text()", xmlVote).replace(" ", "").replace("/", "");
BillVoteCode voteCode = BillVoteCode.getValue(voteCodeStr);
vote.addMemberVote(voteCode, voterMember);
}
voteBill.setBillVote(vote);
voteCommittee.addVoteBill(voteBill);
// Update the actual Bill with the vote information and persist it.
Bill bill = getOrCreateBaseBill(modifiedDate, billId, sobiFragment);
bill.getAmendment(billId.getVersion()).updateVote(vote);
}
addendum.putCommittee(voteCommittee);
}
}
}
catch (SAXException | XPathExpressionException | IOException | ParseError ex) {
logger.error("Failed to parse Agenda Vote.", ex);
unit.addException("Failed to parse Agenda Vote: " + ex.getMessage());
}
// Notify the data processor that an agenda vote fragment has finished processing
postDataUnitEvent(unit);
if (!env.isSobiBatchEnabled() || agendaIngestCache.exceedsCapacity()) {
flushAllUpdates(); // Flush all the things
}
}
@Override
public void postProcess() {
flushAllUpdates();
}
}