Conversation
…+CREATE views - Restored full_schema.sql to pre-1.76 state so CI tests migration path - Changed all CREATE OR REPLACE VIEW to DROP VIEW CASCADE + CREATE VIEW - Added 10 missing columns back to coalition evolution view - Root cause: PostgreSQL cannot drop columns via CREATE OR REPLACE VIEW Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
…ependent view view_riksdagen_voting_anomaly_detection has same columns (internal changes only), so CREATE OR REPLACE VIEW works and avoids CASCADE dropping view_election_cycle_anomaly_pattern which has a JPA entity dependency. Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
View Documentation Validation FailedThis PR modifies view-related files, and the validation has detected incomplete documentation coverage. The following 1 views are in the schema but missing from documentation: Other Views
Please ensure all views are documented in DATABASE_VIEW_INTELLIGENCE_CATALOG.md before merging. See the workflow artifacts for the complete validation report. |
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Snapshot WarningsEnsure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice. Scanned FilesNone |
…IGENCE_CATALOG.md Added missing documentation for the Pattern Recognition framework view: - Framework table entry (23 supporting views, 12/13 risk rules) - View Inventory table entry - Detailed section with purpose, key columns, sample query, source views Validation now passes: 102.80% coverage, 0 missing views. Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR updates the PostgreSQL schema artifacts and documentation around the “intelligence” view layer, including a regenerated full_schema.sql dump and adjustments in Liquibase changelog 1.76 for view recreation semantics.
Changes:
- Regenerates
full_schema.sql, reordering/relocating multiple view definitions (including intelligence/risk-related views). - Updates
db-changelog-1.76.xmlto drop/recreate specific views (usingDROP VIEW ... CASCADE) rather than relying onCREATE OR REPLACE. - Updates
DATABASE_VIEW_INTELLIGENCE_CATALOG.mdto reflect expanded framework/view coverage (incl. election cycle / pattern recognition content).
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| service.data.impl/src/main/resources/full_schema.sql | Regenerated pg_dump baseline; view definitions moved/reordered, including politician risk/anomaly views. |
| service.data.impl/src/main/resources/db-changelog-1.76.xml | Adjusts Liquibase DDL strategy for several views (drop + recreate with CASCADE). |
| DATABASE_VIEW_INTELLIGENCE_CATALOG.md | Documentation updates for intelligence framework/view inventory and operational guidance. |
| WITH politician_vote_metrics AS ( | ||
| SELECT p.id AS person_id, | ||
| count(DISTINCT vd.embedded_id_ballot_id) AS total_votes, | ||
| count(DISTINCT vd.embedded_id_ballot_id) FILTER (WHERE ((vd.vote)::text = 'Frånvarande'::text)) AS absent_votes, | ||
| count(DISTINCT vd.embedded_id_ballot_id) FILTER (WHERE (((vd.vote)::text <> (vd.party)::text) AND ((vd.vote)::text <> 'Frånvarande'::text))) AS rebel_votes | ||
| FROM (public.person_data p | ||
| LEFT JOIN public.vote_data vd ON ((((vd.embedded_id_intressent_id)::text = (p.id)::text) AND (vd.vote_date >= (CURRENT_DATE - '2 years'::interval))))) |
There was a problem hiding this comment.
In politician_vote_metrics, rebel_votes is computed as vd.vote <> vd.party, but vd.party is the politician’s party code (e.g., S/M) while vd.vote is a vote choice (Ja/Nej/Avstår/Frånvarande). This condition will be true for almost every non-absent vote, inflating rebel rates and the overall risk score. Compute rebel votes by comparing the member’s vote to the party’s majority/consensus vote for the same ballot (similar to view_riksdagen_voting_anomaly_detection), rather than comparing to the party code.
| WITH politician_vote_metrics AS ( | |
| SELECT p.id AS person_id, | |
| count(DISTINCT vd.embedded_id_ballot_id) AS total_votes, | |
| count(DISTINCT vd.embedded_id_ballot_id) FILTER (WHERE ((vd.vote)::text = 'Frånvarande'::text)) AS absent_votes, | |
| count(DISTINCT vd.embedded_id_ballot_id) FILTER (WHERE (((vd.vote)::text <> (vd.party)::text) AND ((vd.vote)::text <> 'Frånvarande'::text))) AS rebel_votes | |
| FROM (public.person_data p | |
| LEFT JOIN public.vote_data vd ON ((((vd.embedded_id_intressent_id)::text = (p.id)::text) AND (vd.vote_date >= (CURRENT_DATE - '2 years'::interval))))) | |
| WITH party_ballot_party_vote AS ( | |
| SELECT vd.embedded_id_ballot_id, | |
| vd.party, | |
| mode() WITHIN GROUP (ORDER BY vd.vote) AS party_vote | |
| FROM public.vote_data vd | |
| WHERE ((vd.vote)::text <> 'Frånvarande'::text) | |
| GROUP BY vd.embedded_id_ballot_id, vd.party | |
| ), politician_vote_metrics AS ( | |
| SELECT p.id AS person_id, | |
| count(DISTINCT vd.embedded_id_ballot_id) AS total_votes, | |
| count(DISTINCT vd.embedded_id_ballot_id) FILTER (WHERE ((vd.vote)::text = 'Frånvarande'::text)) AS absent_votes, | |
| count(DISTINCT vd.embedded_id_ballot_id) FILTER ( | |
| WHERE ( | |
| (pbpv.party_vote IS NOT NULL) | |
| AND ((vd.vote)::text <> (pbpv.party_vote)::text) | |
| AND ((vd.vote)::text <> 'Frånvarande'::text) | |
| ) | |
| ) AS rebel_votes | |
| FROM (public.person_data p | |
| LEFT JOIN public.vote_data vd ON ( | |
| ((vd.embedded_id_intressent_id)::text = (p.id)::text) | |
| AND (vd.vote_date >= (CURRENT_DATE - '2 years'::interval)) | |
| ) | |
| LEFT JOIN party_ballot_party_vote pbpv ON ( | |
| (pbpv.embedded_id_ballot_id = vd.embedded_id_ballot_id) | |
| AND ((pbpv.party)::text = (vd.party)::text) | |
| ) | |
| ) |
| count(DISTINCT vd.embedded_id_ballot_id) FILTER (WHERE ((vd.vote)::text = 'Frånvarande'::text)) AS absent_votes, | ||
| count(DISTINCT vd.embedded_id_ballot_id) FILTER (WHERE (((vd.vote)::text <> (vd.party)::text) AND ((vd.vote)::text <> 'Frånvarande'::text))) AS rebel_votes |
There was a problem hiding this comment.
absent_votes is filtered with vd.vote = 'Frånvarande', but many existing views/changelogs treat vote_data.vote values as uppercase (e.g., FRÅNVARANDE) and sometimes normalize with UPPER(vote). If the stored values are uppercase, this will undercount absences and skew risk scores. Prefer a case-normalized comparison (e.g., UPPER(vd.vote) = 'FRÅNVARANDE').
| count(DISTINCT vd.embedded_id_ballot_id) FILTER (WHERE ((vd.vote)::text = 'Frånvarande'::text)) AS absent_votes, | |
| count(DISTINCT vd.embedded_id_ballot_id) FILTER (WHERE (((vd.vote)::text <> (vd.party)::text) AND ((vd.vote)::text <> 'Frånvarande'::text))) AS rebel_votes | |
| count(DISTINCT vd.embedded_id_ballot_id) FILTER (WHERE (upper((vd.vote)::text) = 'FRÅNVARANDE'::text)) AS absent_votes, | |
| count(DISTINCT vd.embedded_id_ballot_id) FILTER (WHERE (((vd.vote)::text <> (vd.party)::text) AND (upper((vd.vote)::text) <> 'FRÅNVARANDE'::text))) AS rebel_votes |
| FROM public.vote_data | ||
| WHERE (((vote_data.vote)::text = ANY (ARRAY[('Ja'::character varying)::text, ('Nej'::character varying)::text, ('Avstår'::character varying)::text])) AND (vote_data.party IS NOT NULL) AND (vote_data.vote_date >= (CURRENT_DATE - '20 years'::interval))) | ||
| GROUP BY vote_data.embedded_id_ballot_id, vote_data.party, vote_data.vote |
There was a problem hiding this comment.
This view filters vote_data.vote using title-case literals ('Ja','Nej','Avstår'), while other parts of the schema use uppercase vote values (JA/NEJ/AVSTÅR/FRÅNVARANDE) and/or UPPER(vote) normalization. If the underlying data is uppercase, these predicates will silently exclude all rows. Normalize the comparison (e.g., UPPER(vote_data.vote) IN ('JA','NEJ','AVSTÅR')).
| WITH person_party_periods AS ( | ||
| SELECT vd.embedded_id_intressent_id AS person_id, | ||
| vd.party, |
There was a problem hiding this comment.
The new view_riksdagen_party_transition_history logic collapses party history by grouping on (person_id, party) (see person_party_periods), which will merge multiple non-contiguous stints in the same party (e.g., S→M→S) into one row and lose the real transition sequence. This can cause missed or misdated transitions. Consider segmenting consecutive party periods by ordering votes per person and grouping by change-points (e.g., via LAG(party) + running group id), then deriving periods from those segments.
| CREATE VIEW public.view_riksdagen_party_defector_analysis AS | ||
| WITH defector_performance AS ( | ||
| SELECT pt.person_id, | ||
| pt.first_name, |
There was a problem hiding this comment.
view_riksdagen_party_defector_analysis (re)created here uses case-sensitive literals for vote values later in the view (e.g., comparing to 'Frånvarande'). Elsewhere in the schema/changelogs, vote_data.vote is handled as uppercase (JA/NEJ/AVSTÅR/FRÅNVARANDE) and/or normalized with UPPER(vote). Without normalization this view can silently compute wrong attendance metrics or return empty results depending on stored casing. Normalize vote comparisons (e.g., UPPER(vd.vote)) consistently.
|
@copilot apply changes based on the comments in this thread |
…ation, and handle non-contiguous party stints - Changeset 1.76-006: Use UPPER() for vote comparisons in view_riksdagen_voting_anomaly_detection (Ja/Nej/Avstår → JA/NEJ/AVSTÅR) - Changeset 1.76-007: Replace GROUP BY (person_id, party) with LAG-based change-point detection to correctly identify non-contiguous party stints (e.g., S→M→S) - Changeset 1.76-008: Use UPPER() for 'Frånvarande' comparisons in view_riksdagen_party_defector_analysis - New changeset 1.76-011: Fix view_politician_risk_summary rebel_votes to compare against party consensus vote instead of party code (S/M), with UPPER() normalization Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
…ERAL join in party_transition_history - Replace O(n²) correlated subquery in GROUP BY with ROW_NUMBER window function for detecting distinct transitions across non-contiguous party stints - Replace correlated vote_data subquery for transition_type with pre-aggregated CTE + LATERAL JOIN for better query planning and index utilization Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Applied all review comments in commits e37c68d and 6b5f28f:
|
| WITH party_ballot_consensus AS ( | ||
| SELECT vd.embedded_id_ballot_id, | ||
| vd.party, | ||
| UPPER((mode() WITHIN GROUP (ORDER BY vd.vote))::text) AS party_consensus_vote | ||
| FROM public.vote_data vd | ||
| WHERE (UPPER((vd.vote)::text) <> 'FRÅNVARANDE'::text) | ||
| AND vd.party IS NOT NULL | ||
| AND vd.vote_date >= (CURRENT_DATE - '2 years'::interval) | ||
| GROUP BY vd.embedded_id_ballot_id, vd.party |
There was a problem hiding this comment.
In party_ballot_consensus, the consensus vote is computed with mode() WITHIN GROUP (ORDER BY vd.vote) and only uppercased after the mode is chosen. If vote values can be mixed-cased (e.g., 'Ja' vs 'JA'), the mode calculation will treat them as different values and can pick the wrong consensus. Normalize before computing the mode (e.g., compute the mode over UPPER(vd.vote) or otherwise aggregate on an uppercased value).
|
@copilot apply changes based on the comments in this thread |
Compute mode() over UPPER(vd.vote) instead of uppercasing after mode().
Mixed-case values ('Ja' vs 'JA') are now treated as the same value
during consensus calculation, preventing wrong party consensus picks.
Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
Fixed in 34378db — |
PostgreSQL does not allow window functions in GROUP BY. Split the distinct_transitions CTE into three steps: 1. filtered_changes - applies WHERE filter on LAG values 2. grouped_changes - computes islands-and-gaps group identifier 3. distinct_transitions - GROUP BY the pre-computed group column Validated with `mvn liquibase:validate` and `mvn liquibase:update` against PostgreSQL 16. All 11 changesets apply successfully. Co-authored-by: pethers <1726836+pethers@users.noreply.github.com>
|
Fixed the Root cause: The Fix: Split into three CTEs using the standard islands-and-gaps pattern:
Validated with Maven Liquibase plugin per the schema maintenance guide:
|
|



Description
Production deploy failed at changeset
1.76-005(ERROR: cannot drop columns from view) and1.76-007(ERROR: window functions are not allowed in GROUP BY). Multiple view SQL correctness issues identified during review.Fixes applied to
db-changelog-1.76.xml:CREATE OR REPLACE VIEW→DROP VIEW IF EXISTS ... CASCADE+CREATE VIEWfor views with column changesforecast_trend,stability_score, etc.) required by JPA entityUPPER()normalization for vote comparisons inview_riksdagen_voting_anomaly_detectionwindow functions are not allowed in GROUP BY— splitdistinct_transitionsCTE into three-step islands-and-gaps pattern:Also replaced
GROUP BY (person_id, party)withLAG()-based change-point detection to preserve non-contiguous stints (S→M→S).UPPER()normalization for'Frånvarande'inview_riksdagen_party_defector_analysisview_politician_risk_summary—rebel_votescompared vote values (JA/NEJ) against party codes (S/M), inflating rebel rates to ~100%. Now computes party consensus viamode() WITHIN GROUP (ORDER BY UPPER(vd.vote)), normalizing case before aggregation.full_schema.sql: Restored to pre-1.76 state so CI tests the actual migration path.DATABASE_VIEW_INTELLIGENCE_CATALOG.md: Added missingview_election_cycle_anomaly_pattern.Validated:
mvn liquibase:validate+mvn liquibase:update— all 11 changesets apply cleanly against PostgreSQL 16.Type of Change
Primary Changes
Political Analysis
Technical Changes
Impact Analysis
Political Analysis Impact
mode()consensus computed on normalized values prevents mixed-case splitting.Technical Impact
Testing
Validated: pre-1.76 schema → Liquibase applies all 11 changesets → all views created with correct columns.
mvn liquibase:validateandmvn liquibase:updatepass.Documentation
Screenshots
N/A
Related Issues
Checklist
Additional Notes
view_election_cycle_decision_intelligenceDROP CASCADE+CREATEview_riksdagen_party_coalition_evolutionDROP CASCADE+CREATE+ restore columnsview_riksdagen_voting_anomaly_detectionUPPER()normalizationview_riksdagen_party_transition_historyLAG()-based change-point detectionview_riksdagen_party_defector_analysis'Frånvarande'UPPER()normalizationview_politician_risk_summaryrebel_votescompared vote to party code;mode()on mixed-casemode()overUPPER(vd.vote)Pre-existing test failures (
ViewDecisionTemporalTrendsDAOITest,ViewRiksdagenPartyDecisionFlowDAOITest) confirmed on main — unrelated JPA column mismatches.Security Considerations
Release Notes
Fix production Liquibase migration failures (
cannot drop columns from view,window functions are not allowed in GROUP BY). UsesDROP VIEW CASCADE+CREATE VIEWfor column changes, restores 10 missing coalition columns, fixesrebel_votescalculation (was comparing votes against party codes instead of party consensus viamode()), normalizes vote casing withUPPER()pre-aggregation, and corrects party transition history to handle non-contiguous stints viaLAG()-based change-point detection.🔒 GitHub Advanced Security automatically protects Copilot coding agent pull requests. You can protect all pull requests by enabling Advanced Security for your repositories. Learn more about Advanced Security.