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 @@ +