diff --git a/citizen-intelligence-agency/src/main/java/com/hack23/cia/web/impl/ui/application/views/user/politician/pagemode/PoliticianOverviewPageModContentFactoryImpl.java b/citizen-intelligence-agency/src/main/java/com/hack23/cia/web/impl/ui/application/views/user/politician/pagemode/PoliticianOverviewPageModContentFactoryImpl.java
index 5437b0f7c2..062779e801 100644
--- a/citizen-intelligence-agency/src/main/java/com/hack23/cia/web/impl/ui/application/views/user/politician/pagemode/PoliticianOverviewPageModContentFactoryImpl.java
+++ b/citizen-intelligence-agency/src/main/java/com/hack23/cia/web/impl/ui/application/views/user/politician/pagemode/PoliticianOverviewPageModContentFactoryImpl.java
@@ -20,6 +20,7 @@
import java.util.Date;
import java.util.Locale;
+import java.util.stream.Collectors;
import org.apache.commons.lang3.StringUtils;
import org.springframework.security.access.annotation.Secured;
@@ -28,6 +29,8 @@
import com.hack23.cia.model.external.riksdagen.person.impl.PersonData;
import com.hack23.cia.model.internal.application.data.politician.impl.ViewRiksdagenPolitician;
import com.hack23.cia.model.internal.application.data.politician.impl.ViewRiksdagenPoliticianBallotSummary;
+import com.hack23.cia.model.internal.application.data.politician.impl.ViewRiksdagenPoliticianExperienceSummary;
+import com.hack23.cia.model.internal.application.data.politician.impl.ViewRiksdagenPoliticianExperienceSummary.PoliticalRole;
import com.hack23.cia.model.internal.application.system.impl.ApplicationEventGroup;
import com.hack23.cia.web.impl.ui.application.action.ViewAction;
import com.hack23.cia.web.impl.ui.application.views.common.sizing.ContentRatio;
@@ -76,6 +79,8 @@ public Layout createContent(final String parameters, final MenuBar menuBar, fina
final ViewRiksdagenPolitician viewRiksdagenPolitician = getItem(parameters);
final ViewRiksdagenPoliticianBallotSummary viewRiksdagenPoliticianBallotSummary = getViewRiksdagenPoliticianBallotSummary(
parameters);
+ final ViewRiksdagenPoliticianExperienceSummary experienceSummary = getViewRiksdagenPoliticianExperienceSummary(parameters);
+
getPoliticianMenuItemFactory().createPoliticianMenuBar(menuBar, pageId);
@@ -87,9 +92,8 @@ public Layout createContent(final String parameters, final MenuBar menuBar, fina
final PersonData personData = getApplicationManager().getDataContainer(PersonData.class)
.load(viewRiksdagenPolitician.getPersonId());
- createOverviewContent(panelContent, personData, viewRiksdagenPolitician, viewRiksdagenPoliticianBallotSummary,
- pageId);
-
+ createOverviewContent(panelContent, personData, viewRiksdagenPolitician,
+ viewRiksdagenPoliticianBallotSummary, experienceSummary, pageId);
getPageActionEventHelper().createPageEvent(ViewAction.VISIT_POLITICIAN_VIEW, ApplicationEventGroup.USER,
UserViews.POLITICIAN_VIEW_NAME, parameters, pageId);
@@ -112,6 +116,11 @@ protected ViewRiksdagenPoliticianBallotSummary getViewRiksdagenPoliticianBallotS
return null;
}
}
+
+ protected ViewRiksdagenPoliticianExperienceSummary getViewRiksdagenPoliticianExperienceSummary(final String parameters) {
+ final String pageId = getPageId(parameters);
+ return getApplicationManager().getDataContainer(ViewRiksdagenPoliticianExperienceSummary.class).load(pageId);
+ }
/**
* Creates the overview content in a card style similar to the scoreboard
@@ -125,9 +134,11 @@ protected ViewRiksdagenPoliticianBallotSummary getViewRiksdagenPoliticianBallotS
* @param pageId the page id
*/
private void createOverviewContent(final VerticalLayout panelContent, final PersonData personData,
- final ViewRiksdagenPolitician viewRiksdagenPolitician,
- final ViewRiksdagenPoliticianBallotSummary viewRiksdagenPoliticianBallotSummary, final String pageId) {
-
+ final ViewRiksdagenPolitician viewRiksdagenPolitician,
+ final ViewRiksdagenPoliticianBallotSummary viewRiksdagenPoliticianBallotSummary,
+ final ViewRiksdagenPoliticianExperienceSummary experienceSummary,
+ final String pageId) {
+
// Link to politician detail page
final Link createPoliticianPageLink = getPageLinkFactory().createPoliticianPageLink(personData);
createPoliticianPageLink.addStyleName("card-subtitle");
@@ -186,9 +197,14 @@ private void createOverviewContent(final VerticalLayout panelContent, final Pers
// 1. Political Role & Influence
final VerticalLayout politicalRoleLayout = createSectionLayout("Political Role & Influence");
- addPoliticalRoleMetrics(politicalRoleLayout, viewRiksdagenPolitician, viewRiksdagenPoliticianBallotSummary);
+ addPoliticalRoleMetrics(politicalRoleLayout, viewRiksdagenPolitician, viewRiksdagenPoliticianBallotSummary,experienceSummary);
sectionsGrid.addComponent(politicalRoleLayout);
sectionsGrid.setExpandRatio(politicalRoleLayout, 1.0f);
+
+ final VerticalLayout experienceLayout = createSectionLayout("Experience & Expertise");
+ addExperienceMetrics(experienceLayout, experienceSummary);
+ sectionsGrid.addComponent(experienceLayout);
+ sectionsGrid.setExpandRatio(experienceLayout, 1.0f);
// 2. Parliamentary Performance
final VerticalLayout performanceLayout = createSectionLayout("Parliamentary Performance");
@@ -221,6 +237,44 @@ private void createOverviewContent(final VerticalLayout panelContent, final Pers
getPoliticianMenuItemFactory().createOverviewPage(overviewLayout, pageId);
}
+
+ private void addExperienceMetrics(VerticalLayout layout, ViewRiksdagenPoliticianExperienceSummary experienceSummary) {
+ if (experienceSummary != null) {
+ // Career Overview
+ layout.addComponent(createInfoRow("Career Phase:",
+ experienceSummary.getCareerPhase().toString().replace("_", " "),
+ VaadinIcons.CALENDAR_CLOCK,
+ "Current career stage"));
+
+ // Experience Level
+ layout.addComponent(createInfoRow("Experience Level:",
+ experienceSummary.getExperienceLevel().toString().replace("_", " "),
+ VaadinIcons.CHART_TIMELINE,
+ "Overall political experience classification"));
+
+ // Leadership Profile
+ layout.addComponent(createInfoRow("Leadership Role:",
+ experienceSummary.getLeadershipProfile().toString().replace("_", " "),
+ VaadinIcons.USER_STAR,
+ "Leadership experience level"));
+
+ // Specialization
+ layout.addComponent(createInfoRow("Expertise:",
+ experienceSummary.getSpecializationLevel().toString().replace("_", " "),
+ VaadinIcons.SPECIALIST,
+ "Area of specialization"));
+
+ // Political Analysis Comment
+ if (StringUtils.isNotBlank(experienceSummary.getPoliticalAnalysisComment())) {
+ layout.addComponent(createInfoRow("Analysis:",
+ experienceSummary.getPoliticalAnalysisComment(),
+ VaadinIcons.COMMENT,
+ "Political career analysis"));
+ }
+ }
+ }
+
+
/**
* Adds the political role metrics.
*
@@ -229,7 +283,7 @@ private void createOverviewContent(final VerticalLayout panelContent, final Pers
* @param ballotSummary the ballot summary
*/
private void addPoliticalRoleMetrics(VerticalLayout layout, ViewRiksdagenPolitician politician,
- ViewRiksdagenPoliticianBallotSummary ballotSummary) {
+ ViewRiksdagenPoliticianBallotSummary ballotSummary, ViewRiksdagenPoliticianExperienceSummary experienceSummary) {
layout.addComponent(createInfoRow("Current Role:", ballotSummary.getStatus(), VaadinIcons.INSTITUTION,
"Current position in parliament"));
@@ -238,9 +292,43 @@ private void addPoliticalRoleMetrics(VerticalLayout layout, ViewRiksdagenPolitic
layout.addComponent(createInfoRow("Career Length:",
calculateServiceYears(politician.getFirstAssignmentDate(), politician.getLastAssignmentDate()),
VaadinIcons.TIMER, "Years in parliament"));
- layout.addComponent(
- createInfoRow("Influence Score:", String.format(Locale.ENGLISH,"%.1f", ballotSummary.getVotingConsistencyScore()),
- VaadinIcons.CHART_GRID, "Overall parliamentary influence"));
+
+ // Top Knowledge Areas
+ if (experienceSummary.getKnowledgeAreas() != null && !experienceSummary.getKnowledgeAreas().isEmpty()) {
+ String topAreas = experienceSummary.getKnowledgeAreas().stream()
+ .filter(ka -> ka.getArea() != null && !ka.getArea().equals("Other"))
+ .sorted((ka1, ka2) -> ka2.getWeightedExp().compareTo(ka1.getWeightedExp()))
+ .limit(3)
+ .map(ka -> String.format(Locale.ENGLISH,"%s ",
+ ka.getArea()))
+ .collect(Collectors.joining(", "));
+
+ if (!topAreas.isEmpty()) {
+ layout.addComponent(createInfoRow("Key Policy Areas:",
+ topAreas,
+ VaadinIcons.CLIPBOARD_TEXT,
+ "Main areas of expertise with weighted importance"));
+ }
+ }
+
+ // Top Roles
+ if (experienceSummary.getRoles() != null && !experienceSummary.getRoles().isEmpty()) {
+ String topRoles = experienceSummary.getRoles().stream()
+ .filter(role -> role.getRole() != null && !role.getRole().equals("Other"))
+ .sorted((r1, r2) -> r2.getWeightedExp().compareTo(r1.getWeightedExp()))
+ .limit(3)
+ .map(role -> String.format(Locale.ENGLISH,"%s",
+ role.getRole()))
+ .collect(Collectors.joining(", "));
+
+ if (!topRoles.isEmpty()) {
+ layout.addComponent(createInfoRow("Key Political Roles:",
+ topRoles,
+ VaadinIcons.USERS,
+ "Most significant positions with weighted importance"));
+ }
+ }
+
}
/**
@@ -260,8 +348,6 @@ private void addParliamentaryPerformanceMetrics(VerticalLayout layout, ViewRiksd
VaadinIcons.TROPHY, "Votes on winning side"));
layout.addComponent(createInfoRow("Activity Level:", politician.getDocActivityLevel(), VaadinIcons.CHART_LINE,
"Overall engagement level"));
- layout.addComponent(createInfoRow("Total Votes:", String.valueOf(ballotSummary.getTotalVotes()),
- VaadinIcons.USER_CARD, "Total votes cast"));
}
/**
@@ -276,6 +362,10 @@ private void addLegislativeMetrics(VerticalLayout layout, ViewRiksdagenPoliticia
VaadinIcons.FILE_TEXT, "Average documents per year"));
layout.addComponent(createInfoRow("Individual Motions:", String.valueOf(politician.getIndividualMotions()),
VaadinIcons.USER, "Personal motions submitted"));
+
+ layout.addComponent(createInfoRow("Party Motions:", String.valueOf(politician.getPartyMotions()),
+ VaadinIcons.USER, "Party motions signed"));
+
layout.addComponent(createInfoRow("Committee Motions:", String.valueOf(politician.getCommitteeMotions()),
VaadinIcons.GROUP, "Committee-based motions"));
layout.addComponent(createInfoRow("Document Impact:", politician.getDocActivityProfile(), VaadinIcons.CHART_3D,
diff --git a/model.internal.application.user.impl/pom.xml b/model.internal.application.user.impl/pom.xml
index e8a478e41f..6c54b40017 100644
--- a/model.internal.application.user.impl/pom.xml
+++ b/model.internal.application.user.impl/pom.xml
@@ -32,6 +32,11 @@
model.common.impl
${project.version}
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.18.2
+
org.hibernate
hibernate-core
diff --git a/model.internal.application.user.impl/src/main/java/com/hack23/cia/model/internal/application/data/politician/impl/ViewRiksdagenPoliticianExperienceSummary.java b/model.internal.application.user.impl/src/main/java/com/hack23/cia/model/internal/application/data/politician/impl/ViewRiksdagenPoliticianExperienceSummary.java
new file mode 100644
index 0000000000..91a9bbeb84
--- /dev/null
+++ b/model.internal.application.user.impl/src/main/java/com/hack23/cia/model/internal/application/data/politician/impl/ViewRiksdagenPoliticianExperienceSummary.java
@@ -0,0 +1,928 @@
+package com.hack23.cia.model.internal.application.data.politician.impl;
+
+import java.util.List;
+import java.util.Locale;
+
+import javax.persistence.Basic;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.EnumType;
+import javax.persistence.Enumerated;
+import javax.persistence.Id;
+import javax.persistence.PostLoad;
+import javax.persistence.Table;
+import javax.persistence.Transient;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlType;
+
+import org.apache.commons.lang3.builder.EqualsBuilder;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.apache.commons.lang3.builder.ToStringBuilder;
+import org.apache.commons.lang3.builder.ToStringStyle;
+
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.core.type.TypeReference;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.hack23.cia.model.common.api.ModelObject;
+
+
+/**
+ * The Class ViewRiksdagenPoliticianExperienceSummary.
+ */
+@Entity
+@Table(name = "view_riksdagen_politician_experience_summary")
+@XmlAccessorType(XmlAccessType.FIELD)
+@XmlType(name = "ViewRiksdagenPoliticianExperienceSummary")
+@XmlRootElement(name = "ViewRiksdagenPoliticianExperienceSummary")
+public class ViewRiksdagenPoliticianExperienceSummary implements ModelObject {
+
+ /** The Constant serialVersionUID. */
+ private static final long serialVersionUID = 1L;
+
+ /** The person id. */
+ @Id
+ @Column(name = "person_id")
+ private String personId;
+
+ /** The first name. */
+ @Basic
+ @Column(name = "first_name")
+ private String firstName;
+
+ /** The last name. */
+ @Basic
+ @Column(name = "last_name")
+ private String lastName;
+
+ /** The total days. */
+ @Basic
+ @Column(name = "total_days")
+ private Long totalDays;
+
+ /** The total weighted exp. */
+ @Basic
+ @Column(name = "total_weighted_exp")
+ private Long totalWeightedExp;
+
+ /** The govt days. */
+ @Basic
+ @Column(name = "govt_days")
+ private Long govtDays;
+
+ /** The riksdag days. */
+ @Basic
+ @Column(name = "riksdag_days")
+ private Long riksdagDays;
+
+ /** The party days. */
+ @Basic
+ @Column(name = "party_days")
+ private Long partyDays;
+
+ /** The committee days. */
+ @Basic
+ @Column(name = "committee_days")
+ private Long committeeDays;
+
+ /** The total substitute days. */
+ @Basic
+ @Column(name = "total_substitute_days")
+ private Long totalSubstituteDays;
+
+ /** The total leadership days. */
+ @Basic
+ @Column(name = "total_leadership_days")
+ private Long totalLeadershipDays;
+
+ /** The knowledge areas json. */
+ @Basic
+ @Column(name = "knowledge_areas_json", columnDefinition = "text")
+ private String knowledgeAreasJson;
+
+ /** The roles json. */
+ @Basic
+ @Column(name = "roles_json", columnDefinition = "text")
+ private String rolesJson;
+
+ /** The experience level. */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "experience_level")
+ private ExperienceLevel experienceLevel;
+
+ /** The experience breadth. */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "experience_breadth")
+ private ExperienceBreadth experienceBreadth;
+
+ /** The leadership profile. */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "leadership_profile")
+ private LeadershipProfile leadershipProfile;
+
+ /** The role stability. */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "role_stability")
+ private RoleStability roleStability;
+
+ /** The career phase. */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "career_phase")
+ private CareerPhase careerPhase;
+
+ /** The specialization level. */
+ @Enumerated(EnumType.STRING)
+ @Column(name = "specialization_level")
+ private SpecializationLevel specializationLevel;
+
+ /** The political analysis comment. */
+ @Basic
+ @Column(name = "political_analysis_comment", length = 4000)
+ private String politicalAnalysisComment;
+
+ /** The knowledge areas. */
+ @Transient
+ private List knowledgeAreas;
+
+ /** The roles. */
+ @Transient
+ private List roles;
+
+ /**
+ * Experience level enum.
+ */
+ public enum ExperienceLevel {
+ /** The extensive experience. */
+ EXTENSIVE_EXPERIENCE,
+ /** The significant government. */
+ SIGNIFICANT_GOVERNMENT,
+ /** The long serving parliament. */
+ LONG_SERVING_PARLIAMENT,
+ /** The active committees. */
+ ACTIVE_COMMITTEES,
+ /** The party leadership. */
+ PARTY_LEADERSHIP,
+ /** The mixed experience. */
+ MIXED_EXPERIENCE
+ }
+
+ /**
+ * Experience breadth enum.
+ */
+ public enum ExperienceBreadth {
+ /** The high. */
+ HIGH,
+ /** The medium. */
+ MEDIUM,
+ /** The low. */
+ LOW
+ }
+
+ /**
+ * Leadership profile enum.
+ */
+ public enum LeadershipProfile {
+ /** The significant leadership. */
+ SIGNIFICANT_LEADERSHIP,
+ /** The moderate leadership. */
+ MODERATE_LEADERSHIP,
+ /** The some leadership. */
+ SOME_LEADERSHIP,
+ /** The no leadership. */
+ NO_LEADERSHIP
+ }
+
+ /**
+ * Role stability enum.
+ */
+ public enum RoleStability {
+ /** The primarily substitute. */
+ PRIMARILY_SUBSTITUTE,
+ /** The frequent substitute. */
+ FREQUENT_SUBSTITUTE,
+ /** The occasional substitute. */
+ OCCASIONAL_SUBSTITUTE,
+ /** The regular roles. */
+ REGULAR_ROLES
+ }
+
+ /**
+ * Career phase enum.
+ */
+ public enum CareerPhase {
+ /** The senior statesperson. */
+ SENIOR_STATESPERSON,
+ /** The established politician. */
+ ESTABLISHED_POLITICIAN,
+ /** The experienced politician. */
+ EXPERIENCED_POLITICIAN,
+ /** The mid career. */
+ MID_CAREER,
+ /** The early career. */
+ EARLY_CAREER
+ }
+
+ /**
+ * Specialization level enum.
+ */
+ public enum SpecializationLevel {
+ /** The highly specialized. */
+ HIGHLY_SPECIALIZED,
+ /** The moderately specialized. */
+ MODERATELY_SPECIALIZED,
+ /** The broadly experienced. */
+ BROADLY_EXPERIENCED
+ }
+
+ /**
+ * The Class KnowledgeArea.
+ */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class KnowledgeArea {
+ /** The area. */
+ private String area;
+ /** The days. */
+ private Long days;
+ /** The weighted exp. */
+ private Long weightedExp;
+
+ /**
+ * Gets the area.
+ *
+ * @return the area
+ */
+ public String getArea() {
+ return area;
+ }
+
+ /**
+ * Sets the area.
+ *
+ * @param area the new area
+ */
+ public void setArea(String area) {
+ this.area = area;
+ }
+
+ /**
+ * Gets the days.
+ *
+ * @return the days
+ */
+ public Long getDays() {
+ return days;
+ }
+
+ /**
+ * Sets the days.
+ *
+ * @param days the new days
+ */
+ public void setDays(Long days) {
+ this.days = days;
+ }
+
+ /**
+ * Gets the weighted exp.
+ *
+ * @return the weighted exp
+ */
+ public Long getWeightedExp() {
+ return weightedExp;
+ }
+
+ /**
+ * Sets the weighted exp.
+ *
+ * @param weightedExp the new weighted exp
+ */
+ public void setWeightedExp(Long weightedExp) {
+ this.weightedExp = weightedExp;
+ }
+ }
+
+ /**
+ * The Class PoliticalRole.
+ */
+ @JsonIgnoreProperties(ignoreUnknown = true)
+ public static class PoliticalRole {
+ /** The type. */
+ private String type;
+ /** The role. */
+ private String role;
+ /** The org. */
+ private String org;
+ /** The days. */
+ private Long days;
+ /** The weighted exp. */
+ private Long weightedExp;
+ /** The substitute days. */
+ private Long substituteDays;
+ /** The leadership days. */
+ private Long leadershipDays;
+
+ /**
+ * Gets the type.
+ *
+ * @return the type
+ */
+ public String getType() {
+ return type;
+ }
+
+ /**
+ * Sets the type.
+ *
+ * @param type the new type
+ */
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ /**
+ * Gets the role.
+ *
+ * @return the role
+ */
+ public String getRole() {
+ return role;
+ }
+
+ /**
+ * Sets the role.
+ *
+ * @param role the new role
+ */
+ public void setRole(String role) {
+ this.role = role;
+ }
+
+ /**
+ * Gets the org.
+ *
+ * @return the org
+ */
+ public String getOrg() {
+ return org;
+ }
+
+ /**
+ * Sets the org.
+ *
+ * @param org the new org
+ */
+ public void setOrg(String org) {
+ this.org = org;
+ }
+
+ /**
+ * Gets the days.
+ *
+ * @return the days
+ */
+ public Long getDays() {
+ return days;
+ }
+
+ /**
+ * Sets the days.
+ *
+ * @param days the new days
+ */
+ public void setDays(Long days) {
+ this.days = days;
+ }
+
+ /**
+ * Gets the weighted exp.
+ *
+ * @return the weighted exp
+ */
+ public Long getWeightedExp() {
+ return weightedExp;
+ }
+
+ /**
+ * Sets the weighted exp.
+ *
+ * @param weightedExp the new weighted exp
+ */
+ public void setWeightedExp(Long weightedExp) {
+ this.weightedExp = weightedExp;
+ }
+
+ /**
+ * Gets the substitute days.
+ *
+ * @return the substitute days
+ */
+ public Long getSubstituteDays() {
+ return substituteDays;
+ }
+
+ /**
+ * Sets the substitute days.
+ *
+ * @param substituteDays the new substitute days
+ */
+ public void setSubstituteDays(Long substituteDays) {
+ this.substituteDays = substituteDays;
+ }
+
+ /**
+ * Gets the leadership days.
+ *
+ * @return the leadership days
+ */
+ public Long getLeadershipDays() {
+ return leadershipDays;
+ }
+
+ /**
+ * Sets the leadership days.
+ *
+ * @param leadershipDays the new leadership days
+ */
+ public void setLeadershipDays(Long leadershipDays) {
+ this.leadershipDays = leadershipDays;
+ }
+ }
+
+ /**
+ * Load json data.
+ */
+ @PostLoad
+ private void loadJsonData() {
+ final ObjectMapper mapper = new ObjectMapper();
+ try {
+ if (knowledgeAreasJson != null) {
+ knowledgeAreas = mapper.readValue(knowledgeAreasJson,
+ new TypeReference>() {});
+ }
+ if (rolesJson != null) {
+ roles = mapper.readValue(rolesJson,
+ new TypeReference>() {});
+ }
+ } catch (final JsonProcessingException e) {
+ throw new RuntimeException("Error parsing JSON data", e);
+ }
+ }
+
+ /**
+ * Gets the formatted experience summary.
+ *
+ * @return the formatted experience summary
+ */
+ public String getFormattedExperienceSummary() {
+ return String.format(Locale.ENGLISH,"%s %s: %s - %s, %s, %s",
+ firstName,
+ lastName,
+ experienceLevel,
+ careerPhase,
+ leadershipProfile,
+ specializationLevel);
+ }
+
+ // Standard getters and setters for all fields
+
+
+ /**
+ * Gets the person id.
+ *
+ * @return the person id
+ */
+ public String getPersonId() {
+ return personId;
+ }
+
+ /**
+ * Sets the person id.
+ *
+ * @param personId the new person id
+ */
+ public void setPersonId(String personId) {
+ this.personId = personId;
+ }
+
+
+ /**
+ * Gets the first name.
+ *
+ * @return the first name
+ */
+ public String getFirstName() {
+ return firstName;
+ }
+
+ /**
+ * Sets the first name.
+ *
+ * @param firstName the new first name
+ */
+ public void setFirstName(String firstName) {
+ this.firstName = firstName;
+ }
+
+ /**
+ * Gets the last name.
+ *
+ * @return the last name
+ */
+ public String getLastName() {
+ return lastName;
+ }
+
+ /**
+ * Sets the last name.
+ *
+ * @param lastName the new last name
+ */
+ public void setLastName(String lastName) {
+ this.lastName = lastName;
+ }
+
+ /**
+ * Gets the total days.
+ *
+ * @return the total days
+ */
+ public Long getTotalDays() {
+ return totalDays;
+ }
+
+ /**
+ * Sets the total days.
+ *
+ * @param totalDays the new total days
+ */
+ public void setTotalDays(Long totalDays) {
+ this.totalDays = totalDays;
+ }
+
+ /**
+ * Gets the total weighted exp.
+ *
+ * @return the total weighted exp
+ */
+ public Long getTotalWeightedExp() {
+ return totalWeightedExp;
+ }
+
+ /**
+ * Sets the total weighted exp.
+ *
+ * @param totalWeightedExp the new total weighted exp
+ */
+ public void setTotalWeightedExp(Long totalWeightedExp) {
+ this.totalWeightedExp = totalWeightedExp;
+ }
+
+ /**
+ * Gets the govt days.
+ *
+ * @return the govt days
+ */
+ public Long getGovtDays() {
+ return govtDays;
+ }
+
+ /**
+ * Sets the govt days.
+ *
+ * @param govtDays the new govt days
+ */
+ public void setGovtDays(Long govtDays) {
+ this.govtDays = govtDays;
+ }
+
+ /**
+ * Gets the riksdag days.
+ *
+ * @return the riksdag days
+ */
+ public Long getRiksdagDays() {
+ return riksdagDays;
+ }
+
+ /**
+ * Sets the riksdag days.
+ *
+ * @param riksdagDays the new riksdag days
+ */
+ public void setRiksdagDays(Long riksdagDays) {
+ this.riksdagDays = riksdagDays;
+ }
+
+ /**
+ * Gets the party days.
+ *
+ * @return the party days
+ */
+ public Long getPartyDays() {
+ return partyDays;
+ }
+
+ /**
+ * Sets the party days.
+ *
+ * @param partyDays the new party days
+ */
+ public void setPartyDays(Long partyDays) {
+ this.partyDays = partyDays;
+ }
+
+ /**
+ * Gets the committee days.
+ *
+ * @return the committee days
+ */
+ public Long getCommitteeDays() {
+ return committeeDays;
+ }
+
+ /**
+ * Sets the committee days.
+ *
+ * @param committeeDays the new committee days
+ */
+ public void setCommitteeDays(Long committeeDays) {
+ this.committeeDays = committeeDays;
+ }
+
+ /**
+ * Gets the total substitute days.
+ *
+ * @return the total substitute days
+ */
+ public Long getTotalSubstituteDays() {
+ return totalSubstituteDays;
+ }
+
+ /**
+ * Sets the total substitute days.
+ *
+ * @param totalSubstituteDays the new total substitute days
+ */
+ public void setTotalSubstituteDays(Long totalSubstituteDays) {
+ this.totalSubstituteDays = totalSubstituteDays;
+ }
+
+ /**
+ * Gets the total leadership days.
+ *
+ * @return the total leadership days
+ */
+ public Long getTotalLeadershipDays() {
+ return totalLeadershipDays;
+ }
+
+ /**
+ * Sets the total leadership days.
+ *
+ * @param totalLeadershipDays the new total leadership days
+ */
+ public void setTotalLeadershipDays(Long totalLeadershipDays) {
+ this.totalLeadershipDays = totalLeadershipDays;
+ }
+
+ /**
+ * Gets the knowledge areas json.
+ *
+ * @return the knowledge areas json
+ */
+ public String getKnowledgeAreasJson() {
+ return knowledgeAreasJson;
+ }
+
+ /**
+ * Sets the knowledge areas json.
+ *
+ * @param knowledgeAreasJson the new knowledge areas json
+ */
+ public void setKnowledgeAreasJson(String knowledgeAreasJson) {
+ this.knowledgeAreasJson = knowledgeAreasJson;
+ }
+
+ /**
+ * Gets the roles json.
+ *
+ * @return the roles json
+ */
+ public String getRolesJson() {
+ return rolesJson;
+ }
+
+ /**
+ * Sets the roles json.
+ *
+ * @param rolesJson the new roles json
+ */
+ public void setRolesJson(String rolesJson) {
+ this.rolesJson = rolesJson;
+ }
+
+ /**
+ * Gets the experience level.
+ *
+ * @return the experience level
+ */
+ public ExperienceLevel getExperienceLevel() {
+ return experienceLevel;
+ }
+
+ /**
+ * Sets the experience level.
+ *
+ * @param experienceLevel the new experience level
+ */
+ public void setExperienceLevel(ExperienceLevel experienceLevel) {
+ this.experienceLevel = experienceLevel;
+ }
+
+ /**
+ * Gets the experience breadth.
+ *
+ * @return the experience breadth
+ */
+ public ExperienceBreadth getExperienceBreadth() {
+ return experienceBreadth;
+ }
+
+ /**
+ * Sets the experience breadth.
+ *
+ * @param experienceBreadth the new experience breadth
+ */
+ public void setExperienceBreadth(ExperienceBreadth experienceBreadth) {
+ this.experienceBreadth = experienceBreadth;
+ }
+
+ /**
+ * Gets the leadership profile.
+ *
+ * @return the leadership profile
+ */
+ public LeadershipProfile getLeadershipProfile() {
+ return leadershipProfile;
+ }
+
+ /**
+ * Sets the leadership profile.
+ *
+ * @param leadershipProfile the new leadership profile
+ */
+ public void setLeadershipProfile(LeadershipProfile leadershipProfile) {
+ this.leadershipProfile = leadershipProfile;
+ }
+
+ /**
+ * Gets the role stability.
+ *
+ * @return the role stability
+ */
+ public RoleStability getRoleStability() {
+ return roleStability;
+ }
+
+ /**
+ * Sets the role stability.
+ *
+ * @param roleStability the new role stability
+ */
+ public void setRoleStability(RoleStability roleStability) {
+ this.roleStability = roleStability;
+ }
+
+ /**
+ * Gets the career phase.
+ *
+ * @return the career phase
+ */
+ public CareerPhase getCareerPhase() {
+ return careerPhase;
+ }
+
+ /**
+ * Sets the career phase.
+ *
+ * @param careerPhase the new career phase
+ */
+ public void setCareerPhase(CareerPhase careerPhase) {
+ this.careerPhase = careerPhase;
+ }
+
+ /**
+ * Gets the specialization level.
+ *
+ * @return the specialization level
+ */
+ public SpecializationLevel getSpecializationLevel() {
+ return specializationLevel;
+ }
+
+ /**
+ * Sets the specialization level.
+ *
+ * @param specializationLevel the new specialization level
+ */
+ public void setSpecializationLevel(SpecializationLevel specializationLevel) {
+ this.specializationLevel = specializationLevel;
+ }
+
+ /**
+ * Gets the political analysis comment.
+ *
+ * @return the political analysis comment
+ */
+ public String getPoliticalAnalysisComment() {
+ return politicalAnalysisComment;
+ }
+
+ /**
+ * Sets the political analysis comment.
+ *
+ * @param politicalAnalysisComment the new political analysis comment
+ */
+ public void setPoliticalAnalysisComment(String politicalAnalysisComment) {
+ this.politicalAnalysisComment = politicalAnalysisComment;
+ }
+
+ /**
+ * Gets the knowledge areas.
+ *
+ * @return the knowledge areas
+ */
+ public List getKnowledgeAreas() {
+ return knowledgeAreas;
+ }
+
+ /**
+ * Sets the knowledge areas.
+ *
+ * @param knowledgeAreas the new knowledge areas
+ */
+ public void setKnowledgeAreas(List knowledgeAreas) {
+ this.knowledgeAreas = knowledgeAreas;
+ }
+
+ /**
+ * Gets the roles.
+ *
+ * @return the roles
+ */
+ public List getRoles() {
+ return roles;
+ }
+
+ /**
+ * Sets the roles.
+ *
+ * @param roles the new roles
+ */
+ public void setRoles(List roles) {
+ this.roles = roles;
+ }
+
+ /**
+ * Gets the serialversionuid.
+ *
+ * @return the serialversionuid
+ */
+ public static long getSerialversionuid() {
+ return serialVersionUID;
+ }
+
+ /**
+ * To string.
+ *
+ * @return the string
+ */
+ @Override
+ public String toString() {
+ return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
+ }
+
+ /**
+ * Equals.
+ *
+ * @param obj the obj
+ * @return true, if successful
+ */
+ @Override
+ public boolean equals(final Object obj) {
+ return EqualsBuilder.reflectionEquals(this, obj);
+ }
+
+ /**
+ * Hash code.
+ *
+ * @return the int
+ */
+ @Override
+ public int hashCode() {
+ return HashCodeBuilder.reflectionHashCode(this);
+ }
+
+}
\ No newline at end of file
diff --git a/service.data.impl/src/main/resources/META-INF/persistence.xml b/service.data.impl/src/main/resources/META-INF/persistence.xml
index ea1cdc1f09..fd4243cc0b 100644
--- a/service.data.impl/src/main/resources/META-INF/persistence.xml
+++ b/service.data.impl/src/main/resources/META-INF/persistence.xml
@@ -113,7 +113,8 @@
com.hack23.cia.model.internal.application.data.politician.impl.ViewRiksdagenPolitician
- com.hack23.cia.model.internal.application.data.politician.impl.ViewRiksdagenPoliticianBallotSummary
+ com.hack23.cia.model.internal.application.data.politician.impl.ViewRiksdagenPoliticianBallotSummary
+ com.hack23.cia.model.internal.application.data.politician.impl.ViewRiksdagenPoliticianExperienceSummary
com.hack23.cia.model.internal.application.data.document.impl.ViewRiksdagenPoliticianDocument
com.hack23.cia.model.internal.application.data.document.impl.ViewRiksdagenDocumentTypeDailySummary
diff --git a/service.data.impl/src/main/resources/db-changelog-1.28.xml b/service.data.impl/src/main/resources/db-changelog-1.28.xml
new file mode 100644
index 0000000000..ec981203c4
--- /dev/null
+++ b/service.data.impl/src/main/resources/db-changelog-1.28.xml
@@ -0,0 +1,1944 @@
+
+
+
+
+
+
+ Other */
+ ELSE 'Other'
+END AS knowledge_area
+ FROM assignment_data a
+),
+per_role_stats AS (
+ SELECT
+ p.id AS person_id,
+ MAX(p.first_name) AS first_name,
+ MAX(p.last_name) AS last_name,
+ r.assignment_type,
+ r.role_code,
+ r.org_code,
+ r.knowledge_area,
+ COUNT(*) AS total_assignments,
+ SUM(r.days_in_role) AS total_days,
+ SUM(r.days_in_role * r.role_weight) AS weighted_experience,
+ SUM(CASE WHEN r.is_substitute=1 THEN r.days_in_role ELSE 0 END) AS substitute_days,
+ SUM(CASE WHEN r.is_leadership=1 THEN r.days_in_role ELSE 0 END) AS leadership_days
+ FROM role_day_spans r
+ JOIN person_data p ON p.id = r.person_id
+ GROUP BY p.id, r.assignment_type, r.role_code, r.org_code, r.knowledge_area
+),
+experience_summary AS (
+ SELECT
+ person_id,
+ MAX(first_name) AS first_name,
+ MAX(last_name) AS last_name,
+ SUM(total_days) AS total_days,
+ SUM(weighted_experience) AS total_weighted_exp,
+ SUM(CASE WHEN assignment_type = 'Departement' THEN total_days ELSE 0 END) AS govt_days,
+ SUM(CASE WHEN assignment_type = 'kammaruppdrag' THEN total_days ELSE 0 END) AS riksdag_days,
+ SUM(CASE WHEN assignment_type = 'partiuppdrag' THEN total_days ELSE 0 END) AS party_days,
+ SUM(CASE WHEN assignment_type IN ('uppdrag','Riksdagsorgan') THEN total_days ELSE 0 END) AS committee_days,
+ SUM(substitute_days) AS total_substitute_days,
+ SUM(leadership_days) AS total_leadership_days,
+
+ JSON_AGG(
+ JSON_BUILD_OBJECT(
+ 'area', knowledge_area,
+ 'days', total_days,
+ 'weightedExp', weighted_experience
+ )
+ ORDER BY weighted_experience DESC
+ ) AS knowledge_areas,
+
+ JSON_AGG(
+ JSON_BUILD_OBJECT(
+ 'type', assignment_type,
+ 'role', role_code,
+ 'org', org_code,
+ 'days', total_days,
+ 'weightedExp', weighted_experience,
+ 'substituteDays', substitute_days,
+ 'leadershipDays', leadership_days
+ )
+ ORDER BY weighted_experience DESC
+ ) AS roles
+ FROM per_role_stats
+ GROUP BY person_id
+),
+political_analysis AS (
+ SELECT
+ es.*,
+ /* Basic experience classification */
+ CASE
+ WHEN govt_days > 2000 AND riksdag_days > 4000 AND committee_days > 2000
+ THEN 'EXTENSIVE_EXPERIENCE'
+ WHEN govt_days > 2000
+ THEN 'SIGNIFICANT_GOVERNMENT'
+ WHEN riksdag_days > 4000
+ THEN 'LONG_SERVING_PARLIAMENT'
+ WHEN committee_days > 1500
+ THEN 'ACTIVE_COMMITTEES'
+ WHEN party_days > 1500
+ THEN 'PARTY_LEADERSHIP'
+ ELSE 'MIXED_EXPERIENCE'
+ END AS experience_level,
+
+ /* Experience breadth score */
+ CASE
+ WHEN (govt_days > 0)::int +
+ (riksdag_days > 0)::int +
+ (party_days > 0)::int +
+ (committee_days > 0)::int >= 3
+ THEN 'HIGH'
+ WHEN (govt_days > 0)::int +
+ (riksdag_days > 0)::int +
+ (party_days > 0)::int +
+ (committee_days > 0)::int = 2
+ THEN 'MEDIUM'
+ ELSE 'LOW'
+ END AS experience_breadth,
+
+ /* Leadership tendency */
+ CASE
+ WHEN total_leadership_days > 1000 THEN 'SIGNIFICANT_LEADERSHIP'
+ WHEN total_leadership_days > 500 THEN 'MODERATE_LEADERSHIP'
+ WHEN total_leadership_days > 0 THEN 'SOME_LEADERSHIP'
+ ELSE 'NO_LEADERSHIP'
+ END AS leadership_profile,
+
+ /* Role stability */
+ CASE
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0.5 THEN 'PRIMARILY_SUBSTITUTE'
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0.25 THEN 'FREQUENT_SUBSTITUTE'
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0 THEN 'OCCASIONAL_SUBSTITUTE'
+ ELSE 'REGULAR_ROLES'
+ END AS role_stability,
+
+ /* Career phase */
+ CASE
+ WHEN total_weighted_exp > 100000 THEN 'SENIOR_STATESPERSON'
+ WHEN total_weighted_exp > 50000 THEN 'ESTABLISHED_POLITICIAN'
+ WHEN total_weighted_exp > 20000 THEN 'EXPERIENCED_POLITICIAN'
+ WHEN total_weighted_exp > 5000 THEN 'MID_CAREER'
+ ELSE 'EARLY_CAREER'
+ END AS career_phase,
+
+ /* Specialization score */
+CASE
+ WHEN (
+ SELECT COUNT(DISTINCT area)
+ FROM jsonb_array_elements(knowledge_areas::jsonb) AS ka(area)
+ ) <= 2 AND total_days > 1000 THEN 'HIGHLY_SPECIALIZED'
+ WHEN (
+ SELECT COUNT(DISTINCT area)
+ FROM jsonb_array_elements(knowledge_areas::jsonb) AS ka(area)
+ ) <= 4 THEN 'MODERATELY_SPECIALIZED'
+ ELSE 'BROADLY_EXPERIENCED'
+END AS specialization_level
+
+ FROM experience_summary es
+)
+SELECT
+ person_id,
+ first_name,
+ last_name,
+ total_days,
+ total_weighted_exp,
+ govt_days,
+ riksdag_days,
+ party_days,
+ committee_days,
+ total_substitute_days,
+ total_leadership_days,
+ knowledge_areas::text AS knowledge_areas_json,
+ roles::text AS roles_json,
+ experience_level,
+ experience_breadth,
+ leadership_profile,
+ role_stability,
+ career_phase,
+ specialization_level,
+
+ /* Generate detailed political analyst comment */
+ CONCAT_WS(' || ',
+ /* Basic Experience Summary */
+ CASE experience_level
+ WHEN 'EXTENSIVE_EXPERIENCE' THEN 'Distinguished career spanning government, parliament, and committees'
+ WHEN 'SIGNIFICANT_GOVERNMENT' THEN 'Notable government service record'
+ WHEN 'LONG_SERVING_PARLIAMENT' THEN 'Long-standing parliamentary experience'
+ WHEN 'ACTIVE_COMMITTEES' THEN 'Significant committee work experience'
+ WHEN 'PARTY_LEADERSHIP' THEN 'Strong party leadership background'
+ ELSE 'Diverse political experience'
+ END,
+
+ /* Experience Breadth Analysis */
+ CASE experience_breadth
+ WHEN 'HIGH' THEN 'Demonstrates broad political competence across multiple domains'
+ WHEN 'MEDIUM' THEN 'Shows focused expertise in select political areas'
+ ELSE 'Specialized in specific political domain'
+ END,
+
+ /* Leadership Commentary */
+ CASE leadership_profile
+ WHEN 'SIGNIFICANT_LEADERSHIP' THEN 'Extensive leadership experience with over 1000 days in key positions'
+ WHEN 'MODERATE_LEADERSHIP' THEN 'Proven leadership capabilities with over 500 days in leadership roles'
+ WHEN 'SOME_LEADERSHIP' THEN 'Some leadership experience'
+ ELSE 'Primarily collaborative roles'
+ END,
+
+ /* Role Stability Analysis */
+ CASE role_stability
+ WHEN 'PRIMARILY_SUBSTITUTE' THEN 'Frequently serves in substitute positions, showing adaptability'
+ WHEN 'FREQUENT_SUBSTITUTE' THEN 'Regular substitute experience complementing primary roles'
+ WHEN 'OCCASIONAL_SUBSTITUTE' THEN 'Mainly stable positions with occasional substitute duties'
+ ELSE 'Consistent role appointments'
+ END,
+
+ /* Career Phase Insight */
+ CASE career_phase
+ WHEN 'SENIOR_STATESPERSON' THEN 'Senior political figure with extensive influence'
+ WHEN 'ESTABLISHED_POLITICIAN' THEN 'Well-established political career'
+ WHEN 'EXPERIENCED_POLITICIAN' THEN 'Seasoned political operator'
+ WHEN 'MID_CAREER' THEN 'Building significant political experience'
+ ELSE 'Developing political career'
+ END,
+
+ /* Specialization Commentary */
+ CASE specialization_level
+ WHEN 'HIGHLY_SPECIALIZED' THEN 'Deep expertise in specific policy areas'
+ WHEN 'MODERATELY_SPECIALIZED' THEN 'Balanced mix of specialized knowledge and broader experience'
+ ELSE 'Broad political portfolio'
+ END
+ ) AS political_analysis_comment
+
+FROM political_analysis
+ORDER BY total_weighted_exp DESC;
+ ]]>
+
+
+
+
+
+
+
+
+
+
+ Other */
+ ELSE 'Other'
+ END AS knowledge_area,
+
+ /* --- Knowledge Area Weighting --- */
+ CASE
+ /* Critical State Functions - Level 1 (5.0) */
+ WHEN a.org_code IN ('FiU', 'KU') OR
+ a.org_code ILIKE ANY(ARRAY['Finansdepartementet', 'Statsrådsberedningen'])
+ THEN 5.0 -- Finance, Constitutional, Prime Minister's Office
+
+ /* Security & Foreign Affairs - Level 2 (4.0) */
+ WHEN a.org_code IN ('UU', 'FÖU', 'JuU') OR
+ a.org_code ILIKE ANY(ARRAY['Utrikesdepartementet', 'Försvarsdepartementet',
+ 'Justitiedepartementet'])
+ THEN 4.0 -- Foreign Affairs, Defense, Justice
+
+ /* Economic & Social Policy - Level 3 (3.0) */
+ WHEN a.org_code IN ('NU', 'SoU', 'AU', 'SfU') OR
+ a.org_code ILIKE ANY(ARRAY['Näringsdepartementet', 'Socialdepartementet',
+ 'Arbetsmarknadsdepartementet'])
+ THEN 3.0 -- Business/Industry, Social Affairs, Labor, Social Insurance
+
+ /* Infrastructure & Environment - Level 4 (2.5) */
+ WHEN a.org_code IN ('MJU', 'BoU', 'TU') OR
+ a.org_code ILIKE ANY(ARRAY['Miljödepartementet', 'Infrastrukturdepartementet',
+ 'Klimat- och näringslivsdepartementet'])
+ THEN 2.5 -- Environment, Housing, Transport, Infrastructure
+
+ /* Education & Culture - Level 5 (2.0) */
+ WHEN a.org_code IN ('UbU', 'KrU') OR
+ a.org_code ILIKE ANY(ARRAY['Utbildningsdepartementet', 'Kulturdepartementet'])
+ THEN 2.0 -- Education, Culture
+
+ /* EU & International - Level 6 (1.5) */
+ WHEN a.org_code IN ('EUN', 'CU') OR
+ a.org_code ILIKE '%EU%'
+ THEN 1.5 -- EU Affairs, International Cooperation
+
+ ELSE 0.5 -- Unspecified/Other
+ END AS area_weight
+
+ FROM assignment_data a
+),
+per_role_stats AS (
+ SELECT
+ p.id AS person_id,
+ MAX(p.first_name) AS first_name,
+ MAX(p.last_name) AS last_name,
+ r.assignment_type,
+ r.role_code,
+ r.org_code,
+ r.knowledge_area,
+ COUNT(*) AS total_assignments,
+ SUM(r.days_in_role) AS total_days,
+ SUM(r.days_in_role * r.role_weight * r.area_weight) AS weighted_experience, -- Here
+ SUM(CASE WHEN r.is_substitute=1 THEN r.days_in_role ELSE 0 END) AS substitute_days,
+ SUM(CASE WHEN r.is_leadership=1 THEN r.days_in_role ELSE 0 END) AS leadership_days
+ FROM role_day_spans r
+ JOIN person_data p ON p.id = r.person_id
+ GROUP BY p.id, r.assignment_type, r.role_code, r.org_code, r.knowledge_area
+),
+experience_summary AS (
+ SELECT
+ person_id,
+ MAX(first_name) AS first_name,
+ MAX(last_name) AS last_name,
+ SUM(total_days) AS total_days,
+ SUM(weighted_experience) AS total_weighted_exp,
+ SUM(CASE WHEN assignment_type = 'Departement' THEN total_days ELSE 0 END) AS govt_days,
+ SUM(CASE WHEN assignment_type = 'kammaruppdrag' THEN total_days ELSE 0 END) AS riksdag_days,
+ SUM(CASE WHEN assignment_type = 'partiuppdrag' THEN total_days ELSE 0 END) AS party_days,
+ SUM(CASE WHEN assignment_type IN ('uppdrag','Riksdagsorgan') THEN total_days ELSE 0 END) AS committee_days,
+ SUM(substitute_days) AS total_substitute_days,
+ SUM(leadership_days) AS total_leadership_days,
+
+ JSON_AGG(
+ JSON_BUILD_OBJECT(
+ 'area', knowledge_area,
+ 'days', total_days,
+ 'weightedExp', weighted_experience
+ )
+ ORDER BY weighted_experience DESC
+ ) AS knowledge_areas,
+
+ JSON_AGG(
+ JSON_BUILD_OBJECT(
+ 'type', assignment_type,
+ 'role', role_code,
+ 'org', org_code,
+ 'days', total_days,
+ 'weightedExp', weighted_experience,
+ 'substituteDays', substitute_days,
+ 'leadershipDays', leadership_days
+ )
+ ORDER BY weighted_experience DESC
+ ) AS roles
+ FROM per_role_stats
+ GROUP BY person_id
+),
+political_analysis AS (
+ SELECT
+ es.*,
+ /* Basic experience classification */
+ CASE
+ WHEN govt_days > 2000 AND riksdag_days > 4000 AND committee_days > 2000
+ THEN 'EXTENSIVE_EXPERIENCE'
+ WHEN govt_days > 2000
+ THEN 'SIGNIFICANT_GOVERNMENT'
+ WHEN riksdag_days > 4000
+ THEN 'LONG_SERVING_PARLIAMENT'
+ WHEN committee_days > 1500
+ THEN 'ACTIVE_COMMITTEES'
+ WHEN party_days > 1500
+ THEN 'PARTY_LEADERSHIP'
+ ELSE 'MIXED_EXPERIENCE'
+ END AS experience_level,
+
+ /* Experience breadth score */
+ CASE
+ WHEN (govt_days > 0)::int +
+ (riksdag_days > 0)::int +
+ (party_days > 0)::int +
+ (committee_days > 0)::int >= 3
+ THEN 'HIGH'
+ WHEN (govt_days > 0)::int +
+ (riksdag_days > 0)::int +
+ (party_days > 0)::int +
+ (committee_days > 0)::int = 2
+ THEN 'MEDIUM'
+ ELSE 'LOW'
+ END AS experience_breadth,
+
+ /* Leadership tendency */
+ CASE
+ WHEN total_leadership_days > 1000 THEN 'SIGNIFICANT_LEADERSHIP'
+ WHEN total_leadership_days > 500 THEN 'MODERATE_LEADERSHIP'
+ WHEN total_leadership_days > 0 THEN 'SOME_LEADERSHIP'
+ ELSE 'NO_LEADERSHIP'
+ END AS leadership_profile,
+
+ /* Role stability */
+ CASE
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0.5 THEN 'PRIMARILY_SUBSTITUTE'
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0.25 THEN 'FREQUENT_SUBSTITUTE'
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0 THEN 'OCCASIONAL_SUBSTITUTE'
+ ELSE 'REGULAR_ROLES'
+ END AS role_stability,
+
+ /* Career phase */
+ CASE
+ WHEN total_weighted_exp > 100000 THEN 'SENIOR_STATESPERSON'
+ WHEN total_weighted_exp > 50000 THEN 'ESTABLISHED_POLITICIAN'
+ WHEN total_weighted_exp > 20000 THEN 'EXPERIENCED_POLITICIAN'
+ WHEN total_weighted_exp > 5000 THEN 'MID_CAREER'
+ ELSE 'EARLY_CAREER'
+ END AS career_phase,
+
+ /* Specialization score */
+CASE
+ WHEN (
+ SELECT COUNT(DISTINCT area)
+ FROM jsonb_array_elements(knowledge_areas::jsonb) AS ka(area)
+ ) <= 2 AND total_days > 1000 THEN 'HIGHLY_SPECIALIZED'
+ WHEN (
+ SELECT COUNT(DISTINCT area)
+ FROM jsonb_array_elements(knowledge_areas::jsonb) AS ka(area)
+ ) <= 4 THEN 'MODERATELY_SPECIALIZED'
+ ELSE 'BROADLY_EXPERIENCED'
+END AS specialization_level
+
+ FROM experience_summary es
+)
+SELECT
+ person_id,
+ first_name,
+ last_name,
+ total_days,
+ total_weighted_exp,
+ govt_days,
+ riksdag_days,
+ party_days,
+ committee_days,
+ total_substitute_days,
+ total_leadership_days,
+ knowledge_areas::text AS knowledge_areas_json,
+ roles::text AS roles_json,
+ experience_level,
+ experience_breadth,
+ leadership_profile,
+ role_stability,
+ career_phase,
+ specialization_level,
+
+ /* Generate detailed political analyst comment */
+ CONCAT_WS(' || ',
+ /* Basic Experience Summary */
+ CASE experience_level
+ WHEN 'EXTENSIVE_EXPERIENCE' THEN 'Distinguished career spanning government, parliament, and committees'
+ WHEN 'SIGNIFICANT_GOVERNMENT' THEN 'Notable government service record'
+ WHEN 'LONG_SERVING_PARLIAMENT' THEN 'Long-standing parliamentary experience'
+ WHEN 'ACTIVE_COMMITTEES' THEN 'Significant committee work experience'
+ WHEN 'PARTY_LEADERSHIP' THEN 'Strong party leadership background'
+ ELSE 'Diverse political experience'
+ END,
+
+ /* Experience Breadth Analysis */
+ CASE experience_breadth
+ WHEN 'HIGH' THEN 'Demonstrates broad political competence across multiple domains'
+ WHEN 'MEDIUM' THEN 'Shows focused expertise in select political areas'
+ ELSE 'Specialized in specific political domain'
+ END,
+
+ /* Leadership Commentary */
+ CASE leadership_profile
+ WHEN 'SIGNIFICANT_LEADERSHIP' THEN 'Extensive leadership experience with over 1000 days in key positions'
+ WHEN 'MODERATE_LEADERSHIP' THEN 'Proven leadership capabilities with over 500 days in leadership roles'
+ WHEN 'SOME_LEADERSHIP' THEN 'Some leadership experience'
+ ELSE 'Primarily collaborative roles'
+ END,
+
+ /* Role Stability Analysis */
+ CASE role_stability
+ WHEN 'PRIMARILY_SUBSTITUTE' THEN 'Frequently serves in substitute positions, showing adaptability'
+ WHEN 'FREQUENT_SUBSTITUTE' THEN 'Regular substitute experience complementing primary roles'
+ WHEN 'OCCASIONAL_SUBSTITUTE' THEN 'Mainly stable positions with occasional substitute duties'
+ ELSE 'Consistent role appointments'
+ END,
+
+ /* Career Phase Insight */
+ CASE career_phase
+ WHEN 'SENIOR_STATESPERSON' THEN 'Senior political figure with extensive influence'
+ WHEN 'ESTABLISHED_POLITICIAN' THEN 'Well-established political career'
+ WHEN 'EXPERIENCED_POLITICIAN' THEN 'Seasoned political operator'
+ WHEN 'MID_CAREER' THEN 'Building significant political experience'
+ ELSE 'Developing political career'
+ END,
+
+ /* Specialization Commentary */
+ CASE specialization_level
+ WHEN 'HIGHLY_SPECIALIZED' THEN 'Deep expertise in specific policy areas'
+ WHEN 'MODERATELY_SPECIALIZED' THEN 'Balanced mix of specialized knowledge and broader experience'
+ ELSE 'Broad political portfolio'
+ END
+ ) AS political_analysis_comment
+
+FROM political_analysis
+ORDER BY total_weighted_exp DESC;
+ ]]>
+
+
+
+
+
+
+
+
+
+ Other */
+ ELSE 'Other'
+ END AS knowledge_area,
+
+ /* --- Knowledge Area Weighting --- */
+ CASE
+ /* Critical State Functions - Level 1 (5.0) */
+ WHEN a.org_code IN ('FiU', 'KU') OR
+ a.org_code ILIKE ANY(ARRAY['Finansdepartementet', 'Statsrådsberedningen'])
+ THEN 5.0 -- Finance, Constitutional, Prime Minister's Office
+
+ /* Security & Foreign Affairs - Level 2 (4.0) */
+ WHEN a.org_code IN ('UU', 'FÖU', 'JuU') OR
+ a.org_code ILIKE ANY(ARRAY['Utrikesdepartementet', 'Försvarsdepartementet',
+ 'Justitiedepartementet'])
+ THEN 4.0 -- Foreign Affairs, Defense, Justice
+
+ /* Economic & Social Policy - Level 3 (3.0) */
+ WHEN a.org_code IN ('NU', 'SoU', 'AU', 'SfU') OR
+ a.org_code ILIKE ANY(ARRAY['Näringsdepartementet', 'Socialdepartementet',
+ 'Arbetsmarknadsdepartementet'])
+ THEN 3.0 -- Business/Industry, Social Affairs, Labor, Social Insurance
+
+ /* Infrastructure & Environment - Level 4 (2.5) */
+ WHEN a.org_code IN ('MJU', 'BoU', 'TU') OR
+ a.org_code ILIKE ANY(ARRAY['Miljödepartementet', 'Infrastrukturdepartementet',
+ 'Klimat- och näringslivsdepartementet'])
+ THEN 2.5 -- Environment, Housing, Transport, Infrastructure
+
+ /* Education & Culture - Level 5 (2.0) */
+ WHEN a.org_code IN ('UbU', 'KrU') OR
+ a.org_code ILIKE ANY(ARRAY['Utbildningsdepartementet', 'Kulturdepartementet'])
+ THEN 2.0 -- Education, Culture
+
+ /* EU & International - Level 6 (1.5) */
+ WHEN a.org_code IN ('EUN', 'CU') OR
+ a.org_code ILIKE '%EU%'
+ THEN 1.5 -- EU Affairs, International Cooperation
+
+ ELSE 0.5 -- Unspecified/Other
+ END AS area_weight
+
+ FROM assignment_data a
+),
+per_role_stats AS (
+ SELECT
+ p.id AS person_id,
+ MAX(p.first_name) AS first_name,
+ MAX(p.last_name) AS last_name,
+ r.assignment_type,
+ r.role_code,
+ r.org_code,
+ r.knowledge_area,
+ COUNT(*) AS total_assignments,
+ SUM(r.days_in_role) AS total_days,
+ SUM(r.days_in_role * r.role_weight * r.area_weight) AS weighted_experience, -- Here
+ SUM(CASE WHEN r.is_substitute=1 THEN r.days_in_role ELSE 0 END) AS substitute_days,
+ SUM(CASE WHEN r.is_leadership=1 THEN r.days_in_role ELSE 0 END) AS leadership_days
+ FROM role_day_spans r
+ JOIN person_data p ON p.id = r.person_id
+ GROUP BY p.id, r.assignment_type, r.role_code, r.org_code, r.knowledge_area
+),
+experience_summary AS (
+ SELECT
+ person_id,
+ MAX(first_name) AS first_name,
+ MAX(last_name) AS last_name,
+ SUM(total_days) AS total_days,
+ SUM(weighted_experience) AS total_weighted_exp,
+ SUM(CASE WHEN assignment_type = 'Departement' THEN total_days ELSE 0 END) AS govt_days,
+ SUM(CASE WHEN assignment_type = 'kammaruppdrag' THEN total_days ELSE 0 END) AS riksdag_days,
+ SUM(CASE WHEN assignment_type = 'partiuppdrag' THEN total_days ELSE 0 END) AS party_days,
+ SUM(CASE WHEN assignment_type IN ('uppdrag','Riksdagsorgan') THEN total_days ELSE 0 END) AS committee_days,
+ SUM(substitute_days) AS total_substitute_days,
+ SUM(leadership_days) AS total_leadership_days,
+
+ JSON_AGG(
+ JSON_BUILD_OBJECT(
+ 'area', knowledge_area,
+ 'days', total_days,
+ 'weightedExp', weighted_experience
+ )
+ ORDER BY weighted_experience DESC
+ ) AS knowledge_areas,
+
+ JSON_AGG(
+ JSON_BUILD_OBJECT(
+ 'type', assignment_type,
+ 'role', role_code,
+ 'org', org_code,
+ 'days', total_days,
+ 'weightedExp', weighted_experience,
+ 'substituteDays', substitute_days,
+ 'leadershipDays', leadership_days
+ )
+ ORDER BY weighted_experience DESC
+ ) AS roles
+ FROM per_role_stats
+ GROUP BY person_id
+),
+political_analysis AS (
+ SELECT
+ es.*,
+ /* Basic experience classification */
+ CASE
+ WHEN govt_days > 2000 AND riksdag_days > 4000 AND committee_days > 2000
+ THEN 'EXTENSIVE_EXPERIENCE'
+ WHEN govt_days > 2000
+ THEN 'SIGNIFICANT_GOVERNMENT'
+ WHEN riksdag_days > 4000
+ THEN 'LONG_SERVING_PARLIAMENT'
+ WHEN committee_days > 1500
+ THEN 'ACTIVE_COMMITTEES'
+ WHEN party_days > 1500
+ THEN 'PARTY_LEADERSHIP'
+ ELSE 'MIXED_EXPERIENCE'
+ END AS experience_level,
+
+ /* Experience breadth score */
+ CASE
+ WHEN (govt_days > 0)::int +
+ (riksdag_days > 0)::int +
+ (party_days > 0)::int +
+ (committee_days > 0)::int >= 3
+ THEN 'HIGH'
+ WHEN (govt_days > 0)::int +
+ (riksdag_days > 0)::int +
+ (party_days > 0)::int +
+ (committee_days > 0)::int = 2
+ THEN 'MEDIUM'
+ ELSE 'LOW'
+ END AS experience_breadth,
+
+ /* Leadership tendency */
+ CASE
+ WHEN total_leadership_days > 1000 THEN 'SIGNIFICANT_LEADERSHIP'
+ WHEN total_leadership_days > 500 THEN 'MODERATE_LEADERSHIP'
+ WHEN total_leadership_days > 0 THEN 'SOME_LEADERSHIP'
+ ELSE 'NO_LEADERSHIP'
+ END AS leadership_profile,
+
+ /* Role stability */
+ CASE
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0.5 THEN 'PRIMARILY_SUBSTITUTE'
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0.25 THEN 'FREQUENT_SUBSTITUTE'
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0 THEN 'OCCASIONAL_SUBSTITUTE'
+ ELSE 'REGULAR_ROLES'
+ END AS role_stability,
+
+ /* Career phase */
+ CASE
+ WHEN total_weighted_exp > 100000 THEN 'SENIOR_STATESPERSON'
+ WHEN total_weighted_exp > 50000 THEN 'ESTABLISHED_POLITICIAN'
+ WHEN total_weighted_exp > 20000 THEN 'EXPERIENCED_POLITICIAN'
+ WHEN total_weighted_exp > 5000 THEN 'MID_CAREER'
+ ELSE 'EARLY_CAREER'
+ END AS career_phase,
+
+ /* Specialization score */
+CASE
+ WHEN (
+ SELECT COUNT(DISTINCT area)
+ FROM jsonb_array_elements(knowledge_areas::jsonb) AS ka(area)
+ ) <= 2 AND total_days > 1000 THEN 'HIGHLY_SPECIALIZED'
+ WHEN (
+ SELECT COUNT(DISTINCT area)
+ FROM jsonb_array_elements(knowledge_areas::jsonb) AS ka(area)
+ ) <= 4 THEN 'MODERATELY_SPECIALIZED'
+ ELSE 'BROADLY_EXPERIENCED'
+END AS specialization_level
+
+ FROM experience_summary es
+)
+SELECT
+ person_id,
+ first_name,
+ last_name,
+ total_days,
+ total_weighted_exp,
+ govt_days,
+ riksdag_days,
+ party_days,
+ committee_days,
+ total_substitute_days,
+ total_leadership_days,
+ knowledge_areas::text AS knowledge_areas_json,
+ roles::text AS roles_json,
+ experience_level,
+ experience_breadth,
+ leadership_profile,
+ role_stability,
+ career_phase,
+ specialization_level,
+
+ /* Generate detailed political analyst comment */
+ CONCAT_WS(' || ',
+ /* Basic Experience Summary */
+ CASE experience_level
+ WHEN 'EXTENSIVE_EXPERIENCE' THEN 'Distinguished career spanning government, parliament, and committees'
+ WHEN 'SIGNIFICANT_GOVERNMENT' THEN 'Notable government service record'
+ WHEN 'LONG_SERVING_PARLIAMENT' THEN 'Long-standing parliamentary experience'
+ WHEN 'ACTIVE_COMMITTEES' THEN 'Significant committee work experience'
+ WHEN 'PARTY_LEADERSHIP' THEN 'Strong party leadership background'
+ ELSE 'Diverse political experience'
+ END,
+
+ /* Experience Breadth Analysis */
+ CASE experience_breadth
+ WHEN 'HIGH' THEN 'Demonstrates broad political competence across multiple domains'
+ WHEN 'MEDIUM' THEN 'Shows focused expertise in select political areas'
+ ELSE 'Specialized in specific political domain'
+ END,
+
+ /* Leadership Commentary */
+ CASE leadership_profile
+ WHEN 'SIGNIFICANT_LEADERSHIP' THEN 'Extensive leadership experience with over 1000 days in key positions'
+ WHEN 'MODERATE_LEADERSHIP' THEN 'Proven leadership capabilities with over 500 days in leadership roles'
+ WHEN 'SOME_LEADERSHIP' THEN 'Some leadership experience'
+ ELSE 'Primarily collaborative roles'
+ END,
+
+ /* Role Stability Analysis */
+ CASE role_stability
+ WHEN 'PRIMARILY_SUBSTITUTE' THEN 'Frequently serves in substitute positions, showing adaptability'
+ WHEN 'FREQUENT_SUBSTITUTE' THEN 'Regular substitute experience complementing primary roles'
+ WHEN 'OCCASIONAL_SUBSTITUTE' THEN 'Mainly stable positions with occasional substitute duties'
+ ELSE 'Consistent role appointments'
+ END,
+
+ /* Career Phase Insight */
+ CASE career_phase
+ WHEN 'SENIOR_STATESPERSON' THEN 'Senior political figure with extensive influence'
+ WHEN 'ESTABLISHED_POLITICIAN' THEN 'Well-established political career'
+ WHEN 'EXPERIENCED_POLITICIAN' THEN 'Seasoned political operator'
+ WHEN 'MID_CAREER' THEN 'Building significant political experience'
+ ELSE 'Developing political career'
+ END,
+
+ /* Specialization Commentary */
+ CASE specialization_level
+ WHEN 'HIGHLY_SPECIALIZED' THEN 'Deep expertise in specific policy areas'
+ WHEN 'MODERATELY_SPECIALIZED' THEN 'Balanced mix of specialized knowledge and broader experience'
+ ELSE 'Broad political portfolio'
+ END
+ ) AS political_analysis_comment
+
+FROM political_analysis
+ORDER BY total_weighted_exp DESC;
+ ]]>
+
+
+
+
+
+
+
+
+
+ Other */
+ ELSE 'Other'
+ END AS knowledge_area,
+
+ /* --- Knowledge Area Weighting --- */
+ CASE
+ /* Critical State Functions - Level 1 (50.0) */
+ WHEN a.org_code IN ('FiU', 'KU') OR
+ a.org_code ILIKE ANY(ARRAY['Finansdepartementet', 'Statsrådsberedningen'])
+ THEN 50.0 -- Finance, Constitutional, Prime Minister's Office
+
+ /* Security & Foreign Affairs - Level 2 (4.0) */
+ WHEN a.org_code IN ('UU', 'FÖU', 'JuU') OR
+ a.org_code ILIKE ANY(ARRAY['Utrikesdepartementet', 'Försvarsdepartementet',
+ 'Justitiedepartementet'])
+ THEN 40.0 -- Foreign Affairs, Defense, Justice
+
+ /* Economic & Social Policy - Level 3 (3.0) */
+ WHEN a.org_code IN ('NU', 'SoU', 'AU', 'SfU') OR
+ a.org_code ILIKE ANY(ARRAY['Näringsdepartementet', 'Socialdepartementet',
+ 'Arbetsmarknadsdepartementet'])
+ THEN 30.0 -- Business/Industry, Social Affairs, Labor, Social Insurance
+
+ /* Infrastructure & Environment - Level 4 (2.5) */
+ WHEN a.org_code IN ('MJU', 'BoU', 'TU') OR
+ a.org_code ILIKE ANY(ARRAY['Miljödepartementet', 'Infrastrukturdepartementet',
+ 'Klimat- och näringslivsdepartementet'])
+ THEN 25.0 -- Environment, Housing, Transport, Infrastructure
+
+ /* Education & Culture - Level 5 (2.0) */
+ WHEN a.org_code IN ('UbU', 'KrU') OR
+ a.org_code ILIKE ANY(ARRAY['Utbildningsdepartementet', 'Kulturdepartementet'])
+ THEN 20.0 -- Education, Culture
+
+ /* EU & International - Level 6 (1.5) */
+ WHEN a.org_code IN ('EUN', 'CU') OR
+ a.org_code ILIKE '%EU%'
+ THEN 15.0 -- EU Affairs, International Cooperation
+
+ ELSE 0.5 -- Unspecified/Other
+ END AS area_weight
+
+ FROM assignment_data a
+),
+per_role_stats AS (
+ SELECT
+ p.id AS person_id,
+ MAX(p.first_name) AS first_name,
+ MAX(p.last_name) AS last_name,
+ r.assignment_type,
+ r.role_code,
+ r.org_code,
+ r.knowledge_area,
+ COUNT(*) AS total_assignments,
+ SUM(r.days_in_role) AS total_days,
+ SUM(r.days_in_role * r.role_weight * r.area_weight) AS weighted_experience, -- Here
+ SUM(CASE WHEN r.is_substitute=1 THEN r.days_in_role ELSE 0 END) AS substitute_days,
+ SUM(CASE WHEN r.is_leadership=1 THEN r.days_in_role ELSE 0 END) AS leadership_days
+ FROM role_day_spans r
+ JOIN person_data p ON p.id = r.person_id
+ GROUP BY p.id, r.assignment_type, r.role_code, r.org_code, r.knowledge_area
+),
+experience_summary AS (
+ SELECT
+ person_id,
+ MAX(first_name) AS first_name,
+ MAX(last_name) AS last_name,
+ SUM(total_days) AS total_days,
+ SUM(weighted_experience) AS total_weighted_exp,
+ SUM(CASE WHEN assignment_type = 'Departement' THEN total_days ELSE 0 END) AS govt_days,
+ SUM(CASE WHEN assignment_type = 'kammaruppdrag' THEN total_days ELSE 0 END) AS riksdag_days,
+ SUM(CASE WHEN assignment_type = 'partiuppdrag' THEN total_days ELSE 0 END) AS party_days,
+ SUM(CASE WHEN assignment_type IN ('uppdrag','Riksdagsorgan') THEN total_days ELSE 0 END) AS committee_days,
+ SUM(substitute_days) AS total_substitute_days,
+ SUM(leadership_days) AS total_leadership_days,
+
+ JSON_AGG(
+ JSON_BUILD_OBJECT(
+ 'area', knowledge_area,
+ 'days', total_days,
+ 'weightedExp', weighted_experience
+ )
+ ORDER BY weighted_experience DESC
+ ) AS knowledge_areas,
+
+ JSON_AGG(
+ JSON_BUILD_OBJECT(
+ 'type', assignment_type,
+ 'role', role_code,
+ 'org', org_code,
+ 'days', total_days,
+ 'weightedExp', weighted_experience,
+ 'substituteDays', substitute_days,
+ 'leadershipDays', leadership_days
+ )
+ ORDER BY weighted_experience DESC
+ ) AS roles
+ FROM per_role_stats
+ GROUP BY person_id
+),
+political_analysis AS (
+ SELECT
+ es.*,
+ /* Basic experience classification */
+ CASE
+ WHEN govt_days > 2000 AND riksdag_days > 4000 AND committee_days > 2000
+ THEN 'EXTENSIVE_EXPERIENCE'
+ WHEN govt_days > 2000
+ THEN 'SIGNIFICANT_GOVERNMENT'
+ WHEN riksdag_days > 4000
+ THEN 'LONG_SERVING_PARLIAMENT'
+ WHEN committee_days > 1500
+ THEN 'ACTIVE_COMMITTEES'
+ WHEN party_days > 1500
+ THEN 'PARTY_LEADERSHIP'
+ ELSE 'MIXED_EXPERIENCE'
+ END AS experience_level,
+
+ /* Experience breadth score */
+ CASE
+ WHEN (govt_days > 0)::int +
+ (riksdag_days > 0)::int +
+ (party_days > 0)::int +
+ (committee_days > 0)::int >= 3
+ THEN 'HIGH'
+ WHEN (govt_days > 0)::int +
+ (riksdag_days > 0)::int +
+ (party_days > 0)::int +
+ (committee_days > 0)::int = 2
+ THEN 'MEDIUM'
+ ELSE 'LOW'
+ END AS experience_breadth,
+
+ /* Leadership tendency */
+ CASE
+ WHEN total_leadership_days > 1000 THEN 'SIGNIFICANT_LEADERSHIP'
+ WHEN total_leadership_days > 500 THEN 'MODERATE_LEADERSHIP'
+ WHEN total_leadership_days > 0 THEN 'SOME_LEADERSHIP'
+ ELSE 'NO_LEADERSHIP'
+ END AS leadership_profile,
+
+ /* Role stability */
+ CASE
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0.5 THEN 'PRIMARILY_SUBSTITUTE'
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0.25 THEN 'FREQUENT_SUBSTITUTE'
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0 THEN 'OCCASIONAL_SUBSTITUTE'
+ ELSE 'REGULAR_ROLES'
+ END AS role_stability,
+
+ /* Career phase */
+ CASE
+ WHEN total_weighted_exp > 100000 THEN 'SENIOR_STATESPERSON'
+ WHEN total_weighted_exp > 50000 THEN 'ESTABLISHED_POLITICIAN'
+ WHEN total_weighted_exp > 20000 THEN 'EXPERIENCED_POLITICIAN'
+ WHEN total_weighted_exp > 5000 THEN 'MID_CAREER'
+ ELSE 'EARLY_CAREER'
+ END AS career_phase,
+
+ /* Specialization score */
+CASE
+ WHEN (
+ SELECT COUNT(DISTINCT area)
+ FROM jsonb_array_elements(knowledge_areas::jsonb) AS ka(area)
+ ) <= 2 AND total_days > 1000 THEN 'HIGHLY_SPECIALIZED'
+ WHEN (
+ SELECT COUNT(DISTINCT area)
+ FROM jsonb_array_elements(knowledge_areas::jsonb) AS ka(area)
+ ) <= 4 THEN 'MODERATELY_SPECIALIZED'
+ ELSE 'BROADLY_EXPERIENCED'
+END AS specialization_level
+
+ FROM experience_summary es
+)
+SELECT
+ person_id,
+ first_name,
+ last_name,
+ total_days,
+ total_weighted_exp,
+ govt_days,
+ riksdag_days,
+ party_days,
+ committee_days,
+ total_substitute_days,
+ total_leadership_days,
+ knowledge_areas::text AS knowledge_areas_json,
+ roles::text AS roles_json,
+ experience_level,
+ experience_breadth,
+ leadership_profile,
+ role_stability,
+ career_phase,
+ specialization_level,
+
+ /* Generate detailed political analyst comment */
+ CONCAT_WS(' || ',
+ /* Basic Experience Summary */
+ CASE experience_level
+ WHEN 'EXTENSIVE_EXPERIENCE' THEN 'Distinguished career spanning government, parliament, and committees'
+ WHEN 'SIGNIFICANT_GOVERNMENT' THEN 'Notable government service record'
+ WHEN 'LONG_SERVING_PARLIAMENT' THEN 'Long-standing parliamentary experience'
+ WHEN 'ACTIVE_COMMITTEES' THEN 'Significant committee work experience'
+ WHEN 'PARTY_LEADERSHIP' THEN 'Strong party leadership background'
+ ELSE 'Diverse political experience'
+ END,
+
+ /* Experience Breadth Analysis */
+ CASE experience_breadth
+ WHEN 'HIGH' THEN 'Demonstrates broad political competence across multiple domains'
+ WHEN 'MEDIUM' THEN 'Shows focused expertise in select political areas'
+ ELSE 'Specialized in specific political domain'
+ END,
+
+ /* Leadership Commentary */
+ CASE leadership_profile
+ WHEN 'SIGNIFICANT_LEADERSHIP' THEN 'Extensive leadership experience with over 1000 days in key positions'
+ WHEN 'MODERATE_LEADERSHIP' THEN 'Proven leadership capabilities with over 500 days in leadership roles'
+ WHEN 'SOME_LEADERSHIP' THEN 'Some leadership experience'
+ ELSE 'Primarily collaborative roles'
+ END,
+
+ /* Role Stability Analysis */
+ CASE role_stability
+ WHEN 'PRIMARILY_SUBSTITUTE' THEN 'Frequently serves in substitute positions, showing adaptability'
+ WHEN 'FREQUENT_SUBSTITUTE' THEN 'Regular substitute experience complementing primary roles'
+ WHEN 'OCCASIONAL_SUBSTITUTE' THEN 'Mainly stable positions with occasional substitute duties'
+ ELSE 'Consistent role appointments'
+ END,
+
+ /* Career Phase Insight */
+ CASE career_phase
+ WHEN 'SENIOR_STATESPERSON' THEN 'Senior political figure with extensive influence'
+ WHEN 'ESTABLISHED_POLITICIAN' THEN 'Well-established political career'
+ WHEN 'EXPERIENCED_POLITICIAN' THEN 'Seasoned political operator'
+ WHEN 'MID_CAREER' THEN 'Building significant political experience'
+ ELSE 'Developing political career'
+ END,
+
+ /* Specialization Commentary */
+ CASE specialization_level
+ WHEN 'HIGHLY_SPECIALIZED' THEN 'Deep expertise in specific policy areas'
+ WHEN 'MODERATELY_SPECIALIZED' THEN 'Balanced mix of specialized knowledge and broader experience'
+ ELSE 'Broad political portfolio'
+ END
+ ) AS political_analysis_comment
+
+FROM political_analysis
+ORDER BY total_weighted_exp DESC;
+ ]]>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Other */
+ ELSE 'Other'
+ END AS knowledge_area,
+
+ /* --- Knowledge Area Weighting --- */
+CASE
+ /* Ministerial & Executive Level - Level 1 (100.0) */
+ WHEN a.org_code ILIKE 'Statsrådsberedningen'
+ THEN 100.0 -- Prime Minister's Office
+
+ /* Core State Functions Ministries - Level 2 (80.0) */
+ WHEN a.org_code ILIKE ANY(ARRAY['Finansdepartementet',
+ 'Justitiedepartementet',
+ 'Utrikesdepartementet',
+ 'Försvarsdepartementet'])
+ THEN 80.0 -- Finance, Justice, Foreign Affairs, Defense Ministries
+
+ /* Other Ministries - Level 3 (60.0) */
+ WHEN a.org_code ILIKE ANY(ARRAY['Näringsdepartementet',
+ 'Socialdepartementet',
+ 'Arbetsmarknadsdepartementet',
+ 'Miljödepartementet',
+ 'Utbildningsdepartementet',
+ 'Kulturdepartementet',
+ 'Infrastrukturdepartementet',
+ 'Klimat- och näringslivsdepartementet'])
+ THEN 60.0 -- Other Ministries
+
+ /* Key Parliamentary Committees - Level 4 (40.0) */
+ WHEN a.org_code IN ('FiU', 'KU', 'UU', 'FÖU', 'JuU')
+ THEN 4.0 -- Finance, Constitutional, Foreign Affairs, Defense, Justice Committees
+
+ /* Economic & Social Committees - Level 5 (30.0) */
+ WHEN a.org_code IN ('NU', 'SoU', 'AU', 'SfU')
+ THEN 3.0 -- Business, Social Affairs, Labor, Social Insurance Committees
+
+ /* Infrastructure & Environment Committees - Level 6 (25.0) */
+ WHEN a.org_code IN ('MJU', 'BoU', 'TU')
+ THEN 2.5 -- Environment, Housing, Transport Committees
+
+ /* Education & Culture Committees - Level 7 (20.0) */
+ WHEN a.org_code IN ('UbU', 'KrU')
+ THEN 2.0 -- Education, Culture Committees
+
+ /* EU Affairs & Special Committees - Level 8 (15.0) */
+ WHEN a.org_code IN ('EUN', 'CU') OR
+ a.org_code ILIKE '%EU%'
+ THEN 1.5 -- EU Affairs, Special Committees
+
+ /* Other Committees/Unspecified - Level 9 (5.0) */
+ ELSE 1.0 -- Other Committees/Areas
+END AS area_weight
+ FROM assignment_data a
+),
+per_role_stats AS (
+ SELECT
+ p.id AS person_id,
+ MAX(p.first_name) AS first_name,
+ MAX(p.last_name) AS last_name,
+ r.assignment_type,
+ r.role_code,
+ r.org_code,
+ r.knowledge_area,
+ COUNT(*) AS total_assignments,
+ SUM(r.days_in_role) AS total_days,
+ SUM(r.days_in_role * r.role_weight * r.area_weight) AS weighted_experience, -- Here
+ SUM(CASE WHEN r.is_substitute=1 THEN r.days_in_role ELSE 0 END) AS substitute_days,
+ SUM(CASE WHEN r.is_leadership=1 THEN r.days_in_role ELSE 0 END) AS leadership_days
+ FROM role_day_spans r
+ JOIN person_data p ON p.id = r.person_id
+ GROUP BY p.id, r.assignment_type, r.role_code, r.org_code, r.knowledge_area
+),
+experience_summary AS (
+ SELECT
+ person_id,
+ MAX(first_name) AS first_name,
+ MAX(last_name) AS last_name,
+ SUM(total_days) AS total_days,
+ SUM(weighted_experience) AS total_weighted_exp,
+ SUM(CASE WHEN assignment_type = 'Departement' THEN total_days ELSE 0 END) AS govt_days,
+ SUM(CASE WHEN assignment_type = 'kammaruppdrag' THEN total_days ELSE 0 END) AS riksdag_days,
+ SUM(CASE WHEN assignment_type = 'partiuppdrag' THEN total_days ELSE 0 END) AS party_days,
+ SUM(CASE WHEN assignment_type IN ('uppdrag','Riksdagsorgan') THEN total_days ELSE 0 END) AS committee_days,
+ SUM(substitute_days) AS total_substitute_days,
+ SUM(leadership_days) AS total_leadership_days,
+
+ JSON_AGG(
+ JSON_BUILD_OBJECT(
+ 'area', knowledge_area,
+ 'days', total_days,
+ 'weightedExp', weighted_experience
+ )
+ ORDER BY weighted_experience DESC
+ ) AS knowledge_areas,
+
+ JSON_AGG(
+ JSON_BUILD_OBJECT(
+ 'type', assignment_type,
+ 'role', role_code,
+ 'org', org_code,
+ 'days', total_days,
+ 'weightedExp', weighted_experience,
+ 'substituteDays', substitute_days,
+ 'leadershipDays', leadership_days
+ )
+ ORDER BY weighted_experience DESC
+ ) AS roles
+ FROM per_role_stats
+ GROUP BY person_id
+),
+political_analysis AS (
+ SELECT
+ es.*,
+ /* Basic experience classification */
+ CASE
+ WHEN govt_days > 2000 AND riksdag_days > 4000 AND committee_days > 2000
+ THEN 'EXTENSIVE_EXPERIENCE'
+ WHEN govt_days > 2000
+ THEN 'SIGNIFICANT_GOVERNMENT'
+ WHEN riksdag_days > 4000
+ THEN 'LONG_SERVING_PARLIAMENT'
+ WHEN committee_days > 1500
+ THEN 'ACTIVE_COMMITTEES'
+ WHEN party_days > 1500
+ THEN 'PARTY_LEADERSHIP'
+ ELSE 'MIXED_EXPERIENCE'
+ END AS experience_level,
+
+ /* Experience breadth score */
+ CASE
+ WHEN (govt_days > 0)::int +
+ (riksdag_days > 0)::int +
+ (party_days > 0)::int +
+ (committee_days > 0)::int >= 3
+ THEN 'HIGH'
+ WHEN (govt_days > 0)::int +
+ (riksdag_days > 0)::int +
+ (party_days > 0)::int +
+ (committee_days > 0)::int = 2
+ THEN 'MEDIUM'
+ ELSE 'LOW'
+ END AS experience_breadth,
+
+ /* Leadership tendency */
+ CASE
+ WHEN total_leadership_days > 1000 THEN 'SIGNIFICANT_LEADERSHIP'
+ WHEN total_leadership_days > 500 THEN 'MODERATE_LEADERSHIP'
+ WHEN total_leadership_days > 0 THEN 'SOME_LEADERSHIP'
+ ELSE 'NO_LEADERSHIP'
+ END AS leadership_profile,
+
+ /* Role stability */
+ CASE
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0.5 THEN 'PRIMARILY_SUBSTITUTE'
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0.25 THEN 'FREQUENT_SUBSTITUTE'
+ WHEN total_substitute_days::float / NULLIF(total_days, 0) > 0 THEN 'OCCASIONAL_SUBSTITUTE'
+ ELSE 'REGULAR_ROLES'
+ END AS role_stability,
+
+ /* Career phase */
+ CASE
+ WHEN total_weighted_exp > 100000 THEN 'SENIOR_STATESPERSON'
+ WHEN total_weighted_exp > 50000 THEN 'ESTABLISHED_POLITICIAN'
+ WHEN total_weighted_exp > 20000 THEN 'EXPERIENCED_POLITICIAN'
+ WHEN total_weighted_exp > 5000 THEN 'MID_CAREER'
+ ELSE 'EARLY_CAREER'
+ END AS career_phase,
+
+ /* Specialization score */
+CASE
+ WHEN (
+ SELECT COUNT(DISTINCT area)
+ FROM jsonb_array_elements(knowledge_areas::jsonb) AS ka(area)
+ ) <= 2 AND total_days > 1000 THEN 'HIGHLY_SPECIALIZED'
+ WHEN (
+ SELECT COUNT(DISTINCT area)
+ FROM jsonb_array_elements(knowledge_areas::jsonb) AS ka(area)
+ ) <= 4 THEN 'MODERATELY_SPECIALIZED'
+ ELSE 'BROADLY_EXPERIENCED'
+END AS specialization_level
+
+ FROM experience_summary es
+)
+SELECT
+ person_id,
+ first_name,
+ last_name,
+ total_days,
+ total_weighted_exp,
+ govt_days,
+ riksdag_days,
+ party_days,
+ committee_days,
+ total_substitute_days,
+ total_leadership_days,
+ knowledge_areas::text AS knowledge_areas_json,
+ roles::text AS roles_json,
+ experience_level,
+ experience_breadth,
+ leadership_profile,
+ role_stability,
+ career_phase,
+ specialization_level,
+
+ /* Generate detailed political analyst comment */
+ CONCAT_WS(' || ',
+ /* Basic Experience Summary */
+ CASE experience_level
+ WHEN 'EXTENSIVE_EXPERIENCE' THEN 'Distinguished career spanning government, parliament, and committees'
+ WHEN 'SIGNIFICANT_GOVERNMENT' THEN 'Notable government service record'
+ WHEN 'LONG_SERVING_PARLIAMENT' THEN 'Long-standing parliamentary experience'
+ WHEN 'ACTIVE_COMMITTEES' THEN 'Significant committee work experience'
+ WHEN 'PARTY_LEADERSHIP' THEN 'Strong party leadership background'
+ ELSE 'Diverse political experience'
+ END,
+
+ /* Experience Breadth Analysis */
+ CASE experience_breadth
+ WHEN 'HIGH' THEN 'Demonstrates broad political competence across multiple domains'
+ WHEN 'MEDIUM' THEN 'Shows focused expertise in select political areas'
+ ELSE 'Specialized in specific political domain'
+ END,
+
+ /* Leadership Commentary */
+ CASE leadership_profile
+ WHEN 'SIGNIFICANT_LEADERSHIP' THEN 'Extensive leadership experience with over 1000 days in key positions'
+ WHEN 'MODERATE_LEADERSHIP' THEN 'Proven leadership capabilities with over 500 days in leadership roles'
+ WHEN 'SOME_LEADERSHIP' THEN 'Some leadership experience'
+ ELSE 'Primarily collaborative roles'
+ END,
+
+ /* Role Stability Analysis */
+ CASE role_stability
+ WHEN 'PRIMARILY_SUBSTITUTE' THEN 'Frequently serves in substitute positions, showing adaptability'
+ WHEN 'FREQUENT_SUBSTITUTE' THEN 'Regular substitute experience complementing primary roles'
+ WHEN 'OCCASIONAL_SUBSTITUTE' THEN 'Mainly stable positions with occasional substitute duties'
+ ELSE 'Consistent role appointments'
+ END,
+
+ /* Career Phase Insight */
+ CASE career_phase
+ WHEN 'SENIOR_STATESPERSON' THEN 'Senior political figure with extensive influence'
+ WHEN 'ESTABLISHED_POLITICIAN' THEN 'Well-established political career'
+ WHEN 'EXPERIENCED_POLITICIAN' THEN 'Seasoned political operator'
+ WHEN 'MID_CAREER' THEN 'Building significant political experience'
+ ELSE 'Developing political career'
+ END,
+
+ /* Specialization Commentary */
+ CASE specialization_level
+ WHEN 'HIGHLY_SPECIALIZED' THEN 'Deep expertise in specific policy areas'
+ WHEN 'MODERATELY_SPECIALIZED' THEN 'Balanced mix of specialized knowledge and broader experience'
+ ELSE 'Broad political portfolio'
+ END
+ ) AS political_analysis_comment
+
+FROM political_analysis
+ORDER BY total_weighted_exp DESC;
+ ]]>
+
+
+
+
+
+
+
+
diff --git a/service.data.impl/src/main/resources/db-changelog.xml b/service.data.impl/src/main/resources/db-changelog.xml
index 197b94ef05..bb0e08bd73 100644
--- a/service.data.impl/src/main/resources/db-changelog.xml
+++ b/service.data.impl/src/main/resources/db-changelog.xml
@@ -33,6 +33,7 @@
+