CoalitionPredictionPageModContentFactoryImpl.java
/*
* Copyright 2010-2025 James Pether Sörling
*
* Licensed 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.
*
* $Id$
* $HeadURL$
*/
package com.hack23.cia.web.impl.ui.application.views.user.parliament.pagemode;
import java.text.DecimalFormat;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.access.annotation.Secured;
import org.springframework.stereotype.Component;
import com.hack23.cia.model.internal.application.system.impl.ApplicationEventGroup;
import com.hack23.cia.service.impl.CoalitionPredictionService;
import com.hack23.cia.service.impl.CoalitionPredictionService.CoalitionScenario;
import com.hack23.cia.web.impl.ui.application.action.ViewAction;
import com.hack23.cia.web.impl.ui.application.views.common.menufactory.api.pagecommands.PageCommandParliamentRankingConstants;
import com.hack23.cia.web.impl.ui.application.views.common.pagemode.CardInfoRowUtil;
import com.hack23.cia.web.impl.ui.application.views.common.sizing.ContentRatio;
import com.vaadin.server.Sizeable.Unit;
import com.vaadin.ui.HorizontalLayout;
import com.vaadin.ui.Label;
import com.vaadin.ui.Layout;
import com.vaadin.ui.MenuBar;
import com.vaadin.ui.Panel;
import com.vaadin.ui.ProgressBar;
import com.vaadin.ui.VerticalLayout;
import com.vaadin.ui.themes.ValoTheme;
/**
* Coalition prediction page mod content factory implementation.
*
* Provides UI for coalition formation predictions, party alignment analysis,
* and coalition stability assessment based on historical voting patterns.
*/
@Component
public final class CoalitionPredictionPageModContentFactoryImpl extends AbstractParliamentPageModContentFactoryImpl {
@Autowired
private CoalitionPredictionService coalitionPredictionService;
/**
* Instantiates a new coalition prediction page mod content factory impl.
*/
public CoalitionPredictionPageModContentFactoryImpl() {
super();
}
@Secured({ "ROLE_ANONYMOUS", "ROLE_USER", "ROLE_ADMIN" })
@Override
public Layout createContent(final String parameters, final MenuBar menuBar, final Panel panel) {
final VerticalLayout panelContent = createPanelContent();
setupMenuAndHeader(menuBar, panel, panelContent);
final String selectedYear = extractSelectedYear(parameters, ParliamentChartConstants.DEFAULT_YEAR);
addCoalitionScenarios(panelContent, selectedYear);
addPartyAlignmentMatrix(panelContent, selectedYear);
recordPageVisit(parameters, selectedYear);
return panelContent;
}
/**
* Setup menu and header.
*
* @param menuBar the menu bar
* @param panel the panel
* @param panelContent the panel content
*/
private void setupMenuAndHeader(final MenuBar menuBar, final Panel panel, final VerticalLayout panelContent) {
getParliamentMenuItemFactory().createParliamentTopicMenu(menuBar);
CardInfoRowUtil.createPageHeader(panel, panelContent,
ParliamentPageTitleConstants.COALITION_PREDICTION_TITLE,
ParliamentPageTitleConstants.COALITION_PREDICTION_SUBTITLE,
ParliamentPageTitleConstants.COALITION_PREDICTION_DESC);
}
/**
* Adds the coalition scenarios section.
*
* @param panelContent the panel content
* @param selectedYear the selected year
*/
private void addCoalitionScenarios(final VerticalLayout panelContent, final String selectedYear) {
final List<CoalitionScenario> scenarios = coalitionPredictionService.predictCoalitions(selectedYear);
final VerticalLayout scenariosLayout = new VerticalLayout();
scenariosLayout.setSpacing(true);
scenariosLayout.setMargin(true);
scenariosLayout.setWidth(100, Unit.PERCENTAGE);
final Label header = new Label("Coalition Formation Scenarios (Ranked by Probability)");
header.addStyleName(ValoTheme.LABEL_H2);
scenariosLayout.addComponent(header);
if (scenarios == null || scenarios.isEmpty()) {
final Label noScenariosLabel = new Label("No viable coalition scenarios found for the selected year.");
noScenariosLabel.addStyleName(ValoTheme.LABEL_LIGHT);
scenariosLayout.addComponent(noScenariosLabel);
} else {
int rank = 1;
for (final CoalitionScenario scenario : scenarios) {
scenariosLayout.addComponent(createScenarioRow(rank++, scenario));
}
}
panelContent.addComponent(scenariosLayout);
panelContent.setExpandRatio(scenariosLayout, ContentRatio.LARGE);
}
/**
* Creates a single coalition scenario row.
*
* @param rank the rank
* @param scenario the scenario
* @return the horizontal layout
*/
private HorizontalLayout createScenarioRow(final int rank, final CoalitionScenario scenario) {
final HorizontalLayout row = new HorizontalLayout();
row.setSpacing(true);
row.setWidth(100, Unit.PERCENTAGE);
// Rank badge
final Label rankLabel = new Label("#" + rank);
rankLabel.addStyleName(ValoTheme.LABEL_H3);
rankLabel.setWidth(50, Unit.PIXELS);
row.addComponent(rankLabel);
// Coalition parties
final Label partiesLabel = new Label(String.join(" + ", scenario.getParties()));
partiesLabel.addStyleName(ValoTheme.LABEL_BOLD);
partiesLabel.setWidth(200, Unit.PIXELS);
row.addComponent(partiesLabel);
// Probability bar
final ProgressBar probabilityBar = new ProgressBar();
// Vaadin ProgressBar API requires float value; casting from double results in minimal precision loss.
// This is acceptable for UI display purposes as the bar visualization does not require high precision.
probabilityBar.setValue((float) scenario.getProbability());
probabilityBar.setCaption(String.format(Locale.ENGLISH, "%.1f%% probability", scenario.getProbability() * 100));
probabilityBar.setWidth(200, Unit.PIXELS);
row.addComponent(probabilityBar);
// Stability index
final Label stabilityLabel = new Label(
String.format(Locale.ENGLISH, "Stability: %d/100", scenario.getStabilityIndex()));
stabilityLabel.addStyleName(
scenario.getStabilityIndex() > 70 ? ValoTheme.LABEL_SUCCESS : ValoTheme.LABEL_FAILURE);
stabilityLabel.setWidth(150, Unit.PIXELS);
row.addComponent(stabilityLabel);
// Seats
final Label seatsLabel = new Label(String.format(Locale.ENGLISH, "%d seats", scenario.getTotalSeats()));
seatsLabel.setWidth(100, Unit.PIXELS);
row.addComponent(seatsLabel);
// Bloc relationship
final Label blocLabel = new Label(scenario.getBlocRelationship());
blocLabel.addStyleName(ValoTheme.LABEL_TINY);
blocLabel.setWidth(150, Unit.PIXELS);
row.addComponent(blocLabel);
return row;
}
/**
* Adds the party alignment matrix section.
*
* @param panelContent the panel content
* @param selectedYear the selected year
*/
private void addPartyAlignmentMatrix(final VerticalLayout panelContent, final String selectedYear) {
final Map<String, Map<String, Double>> alignmentMatrix =
coalitionPredictionService.getAlignmentMatrix(selectedYear);
final VerticalLayout matrixLayout = new VerticalLayout();
matrixLayout.setSpacing(true);
matrixLayout.setMargin(true);
matrixLayout.setWidth(100, Unit.PERCENTAGE);
final Label header = new Label("Party Alignment Matrix (Voting Pattern Compatibility)");
header.addStyleName(ValoTheme.LABEL_H2);
matrixLayout.addComponent(header);
final Label description = new Label(
"Alignment rates show how often parties vote the same way. Higher rates indicate coalition compatibility.");
description.addStyleName(ValoTheme.LABEL_SMALL);
matrixLayout.addComponent(description);
// Create a simple text-based matrix
for (final Map.Entry<String, Map<String, Double>> entry : alignmentMatrix.entrySet()) {
final String party = entry.getKey();
final Map<String, Double> alignments = entry.getValue();
final HorizontalLayout partyRow = new HorizontalLayout();
partyRow.setSpacing(true);
final Label partyLabel = new Label(party);
partyLabel.addStyleName(ValoTheme.LABEL_BOLD);
partyLabel.setWidth(50, Unit.PIXELS);
partyRow.addComponent(partyLabel);
for (final Map.Entry<String, Double> alignment : alignments.entrySet()) {
final Label alignmentLabel = new Label(
String.format(Locale.ENGLISH, "%s: %.1f%%", alignment.getKey(), alignment.getValue() * 100));
alignmentLabel.setWidth(120, Unit.PIXELS);
partyRow.addComponent(alignmentLabel);
}
matrixLayout.addComponent(partyRow);
}
panelContent.addComponent(matrixLayout);
panelContent.setExpandRatio(matrixLayout, ContentRatio.SMALL_GRID);
}
/**
* Record page visit.
*
* @param parameters the parameters
* @param selectedYear the selected year
*/
private void recordPageVisit(final String parameters, final String selectedYear) {
getPageActionEventHelper().createPageEvent(
ViewAction.VISIT_PARLIAMENT_RANKING_VIEW,
ApplicationEventGroup.USER,
NAME,
parameters,
selectedYear
);
}
@Override
public boolean matches(final String page, final String parameters) {
return PageCommandParliamentRankingConstants.COMMAND_COALITION_PREDICTION.matches(page, parameters);
}
}