DecisionDataFactoryImpl.java

  1. /*
  2.  * Copyright 2010-2025 James Pether Sörling
  3.  *
  4.  * Licensed under the Apache License, Version 2.0 (the "License");
  5.  * you may not use this file except in compliance with the License.
  6.  * You may obtain a copy of the License at
  7.  *
  8.  *   http://www.apache.org/licenses/LICENSE-2.0
  9.  *
  10.  * Unless required by applicable law or agreed to in writing, software
  11.  * distributed under the License is distributed on an "AS IS" BASIS,
  12.  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13.  * See the License for the specific language governing permissions and
  14.  * limitations under the License.
  15.  *
  16.  *  $Id$
  17.  *  $HeadURL$
  18. */
  19. package com.hack23.cia.web.impl.ui.application.views.common.dataseriesfactory.impl;

  20. import java.util.List;
  21. import java.util.Locale;
  22. import java.util.Objects;
  23. import java.util.regex.Pattern;
  24. import java.util.stream.Collectors;

  25. import org.apache.commons.lang3.StringUtils;
  26. import org.springframework.beans.factory.annotation.Autowired;
  27. import org.springframework.stereotype.Service;
  28. import org.springframework.transaction.annotation.Propagation;
  29. import org.springframework.transaction.annotation.Transactional;

  30. import com.hack23.cia.model.external.riksdagen.dokumentstatus.impl.DocumentProposalData;
  31. import com.hack23.cia.model.external.riksdagen.dokumentstatus.impl.DocumentStatusContainer;
  32. import com.hack23.cia.service.api.ApplicationManager;
  33. import com.hack23.cia.web.impl.ui.application.views.common.dataseriesfactory.api.DecisionDataFactory;
  34. import com.hack23.cia.web.impl.ui.application.views.common.dataseriesfactory.api.ProposalCommitteeeSummary;

  35. /**
  36.  * Implementation of DecisionDataFactory for processing parliamentary committee decisions.
  37.  * Handles document status data and creates summaries of committee proposals.
  38.  */
  39. @Service
  40. @Transactional(propagation = Propagation.REQUIRED, readOnly = true)
  41. public final class DecisionDataFactoryImpl implements DecisionDataFactory {

  42.     /**  Document type constants. */
  43.     private static final String PROPOSITION = "Proposition";

  44.     /** The Constant MOTION. */
  45.     private static final String MOTION = "Motion";

  46.     /** The Constant PROP_TYPE. */
  47.     private static final String PROP_TYPE = "prop";

  48.     /**  Chamber text length constraints for valid proposals. */
  49.     private static final int CHAMBER_MIN_LENGTH = "avslag".length(); // 6

  50.     /** The Constant CHAMBER_MAX_LENGTH. */
  51.     private static final int CHAMBER_MAX_LENGTH = "återförvisning till utskottet".length(); // 29

  52.     /**
  53.      * Pattern for standardizing committee decision text.
  54.      * Removes or replaces parliamentary specific tokens:
  55.      * - (UTSKOTTET)
  56.      * - Parentheses
  57.      * - Various committee spelling variants
  58.      */
  59.     private static final Pattern COMMITTEE_TEXT_CLEANUP_PATTERN = Pattern.compile(
  60.         "(\\(UTSKOTTET\\))|(\\()|(\\))|(UTBSKOTTET)|(UBTSKOTTET)|(UTKOTTET)",
  61.         Pattern.CASE_INSENSITIVE
  62.     );

  63.     /** The application manager. */
  64.     @Autowired
  65.     private ApplicationManager applicationManager;

  66.     /**
  67.      * Creates committee summaries for a specific processing location.
  68.      *
  69.      * @param processedIn the location where proposals were processed
  70.      * @return List of committee summaries
  71.      * @throws IllegalArgumentException if processedIn is blank
  72.      */
  73.     @Override
  74.     public List<ProposalCommitteeeSummary> createCommitteeSummary(final String processedIn) {
  75.         validateProcessedIn(processedIn);

  76.         return applicationManager.getDataContainer(DocumentStatusContainer.class)
  77.                 .getAll()
  78.                 .parallelStream()
  79.                 .filter(doc -> isValidDocument(doc, processedIn))
  80.                 .map(doc -> createProposalSummary(doc))
  81.                 .filter(Objects::nonNull)
  82.                 .collect(Collectors.toList());
  83.     }

  84.     /**
  85.      * Validates the processedIn parameter.
  86.      *
  87.      * @param processedIn location to validate
  88.      * @throws IllegalArgumentException if validation fails
  89.      */
  90.     private void validateProcessedIn(final String processedIn) {
  91.         if (StringUtils.isBlank(processedIn)) {
  92.             throw new IllegalArgumentException("ProcessedIn parameter cannot be blank");
  93.         }
  94.     }

  95.     /**
  96.      * Validates if a document meets the criteria for processing.
  97.      *
  98.      * @param document the document to validate
  99.      * @param processedIn the required processing location
  100.      * @return true if document is valid for processing
  101.      */
  102.     private boolean isValidDocument(final DocumentStatusContainer document, final String processedIn) {
  103.         if (document == null || document.getDocumentProposal() == null ||
  104.             document.getDocumentProposal().getProposal() == null) {
  105.             return false;
  106.         }

  107.         final DocumentProposalData proposal = document.getDocumentProposal().getProposal();
  108.         return isValidProposal(proposal, processedIn);
  109.     }

  110.     /**
  111.      * Validates proposal data for processing requirements.
  112.      *
  113.      * @param proposal the proposal to validate
  114.      * @param processedIn the required processing location
  115.      * @return true if proposal meets requirements
  116.      */
  117.     private boolean isValidProposal(final DocumentProposalData proposal, final String processedIn) {
  118.         if (proposal == null || StringUtils.isBlank(proposal.getChamber()) ||
  119.             StringUtils.isBlank(proposal.getProcessedIn()) ||
  120.             StringUtils.isBlank(proposal.getCommittee())) {
  121.             return false;
  122.         }

  123.         final int chamberLength = proposal.getChamber().length();
  124.         return chamberLength >= CHAMBER_MIN_LENGTH &&
  125.                chamberLength <= CHAMBER_MAX_LENGTH &&
  126.                proposal.getProcessedIn().contains(processedIn);
  127.     }

  128.     /**
  129.      * Creates a ProposalCommitteeeSummary from a valid document.
  130.      *
  131.      * @param document source document
  132.      * @return ProposalCommitteeeSummary or null if invalid
  133.      */
  134.     private ProposalCommitteeeSummary createProposalSummary(final DocumentStatusContainer document) {
  135.         try {
  136.             final DocumentProposalData proposal = document.getDocumentProposal().getProposal();
  137.             return new ProposalCommitteeeSummary(
  138.                 extractCommitteeShortName(proposal),
  139.                 determineDocumentType(document),
  140.                 standardizeDecisionText(proposal.getChamber()),
  141.                 document.getDocument().getHangarId(),
  142.                 proposal.getWording(),
  143.                 proposal.getWording2(),
  144.                 proposal.getDecisionType()
  145.             );
  146.         } catch (final Exception e) {
  147.             // Log error if needed
  148.             return null;
  149.         }
  150.     }

  151.     /**
  152.      * Standardizes the decision text by removing variations and normalizing format.
  153.      *
  154.      * @param chamber the original chamber text
  155.      * @return standardized decision text
  156.      */
  157.     private static String standardizeDecisionText(final String chamber) {
  158.         if (StringUtils.isBlank(chamber)) {
  159.             return "";
  160.         }

  161.         String standardized = chamber.toUpperCase(Locale.ENGLISH);
  162.         standardized = COMMITTEE_TEXT_CLEANUP_PATTERN.matcher(standardized).replaceAll("");

  163.         // Normalize committee spelling
  164.         if (standardized.contains("UTKOTTET")) {
  165.             standardized = standardized.replace("UTKOTTET", "UTSKOTTET");
  166.         }

  167.         return standardized.trim();
  168.     }

  169.     /**
  170.      * Extracts the standardized committee short name.
  171.      *
  172.      * @param proposal source proposal
  173.      * @return committee short name
  174.      */
  175.     private static String extractCommitteeShortName(final DocumentProposalData proposal) {
  176.         final String processedIn = proposal.getProcessedIn();
  177.         if (StringUtils.isBlank(processedIn)) {
  178.             return "";
  179.         }

  180.         final String shortName = processedIn
  181.             .replaceAll("\\d", "")
  182.             .replace("/:", "")
  183.             .toUpperCase(Locale.ENGLISH);

  184.         final int commaIndex = shortName.indexOf(',');
  185.         return (commaIndex >= 0) ? shortName.substring(0, commaIndex) : shortName;
  186.     }

  187.     /**
  188.      * Determines the human-readable document type.
  189.      *
  190.      * @param document source document
  191.      * @return document type string
  192.      */
  193.     private static String determineDocumentType(final DocumentStatusContainer document) {
  194.         if (PROP_TYPE.equalsIgnoreCase(document.getDocument().getDocumentType())) {
  195.             return PROPOSITION;
  196.         }

  197.         final String subType = document.getDocument().getSubType();
  198.         return (subType != null && subType.length() > MOTION.length())
  199.                ? subType
  200.                : MOTION;
  201.     }
  202. }