Spaces:
Paused
Paused
| <databaseChangeLog | |
| xmlns="http://www.liquibase.org/xml/ns/dbchangelog" | |
| xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | |
| xsi:schemaLocation="http://www.liquibase.org/xml/ns/dbchangelog | |
| http://www.liquibase.org/xml/ns/dbchangelog/dbchangelog-3.6.xsd"> | |
| <!-- | |
| Party Decision Flow View - Database Changelog v1.35 | |
| Author: Political Analyst & Intelligence Operative | |
| Date: 2025-11-22 | |
| GitHub Issue: Hack23/cia#7887 | |
| Creates party-level decision flow aggregation view from DOCUMENT_PROPOSAL_DATA, | |
| enabling analysis of party-level proposal success rates, decision patterns, | |
| and legislative effectiveness. | |
| Purpose: | |
| - Party decision effectiveness tracking | |
| - Coalition alignment on proposals | |
| - Committee proposal success rates by party | |
| - Temporal decision trends by party | |
| Intelligence Applications: | |
| - Party scorecards (legislative success rates) | |
| - Coalition formation analysis (party alignment on decisions) | |
| - Committee influence assessment (party success in committees) | |
| - Trend analysis (temporal patterns in decision outcomes) | |
| --> | |
| <changeSet id="1.35-intro" author="intelligence-operative"> | |
| <comment> | |
| Database Changelog v1.35 - Party Decision Flow View | |
| Creates view_riksdagen_party_decision_flow for aggregating proposal | |
| decision data by party, enabling party-level legislative effectiveness | |
| and coalition analysis. | |
| </comment> | |
| <sql> | |
| SELECT | |
| 'v1.35' AS version, | |
| 'Party decision flow view for legislative effectiveness analysis' AS description, | |
| CURRENT_TIMESTAMP AS applied_at; | |
| </sql> | |
| </changeSet> | |
| <!-- Pre-Flight Validation --> | |
| <changeSet id="1.35-preflight" author="intelligence-operative"> | |
| <comment>Pre-flight: Verify source data exists before creating view</comment> | |
| <sql splitStatements="false"><![CDATA[ | |
| DO $$ | |
| DECLARE | |
| v_has_proposals BOOLEAN; | |
| v_has_documents BOOLEAN; | |
| v_has_person_refs BOOLEAN; | |
| BEGIN | |
| -- Use EXISTS for efficient data presence checks instead of full counts | |
| SELECT EXISTS(SELECT 1 FROM document_proposal_data LIMIT 1) INTO v_has_proposals; | |
| SELECT EXISTS(SELECT 1 FROM document_data LIMIT 1) INTO v_has_documents; | |
| SELECT EXISTS(SELECT 1 FROM document_person_reference_da_0 LIMIT 1) INTO v_has_person_refs; | |
| IF v_has_proposals THEN | |
| RAISE NOTICE 'Pre-flight v1.35: document_proposal_data has data'; | |
| ELSE | |
| RAISE WARNING 'No proposal data found - view will be empty until data is loaded'; | |
| END IF; | |
| IF v_has_person_refs THEN | |
| RAISE NOTICE 'Pre-flight v1.35: document_person_reference_da_0 has data'; | |
| ELSE | |
| RAISE WARNING 'No person reference data - party attribution will be limited'; | |
| END IF; | |
| END $$; | |
| ]]></sql> | |
| </changeSet> | |
| <!-- Create Party Decision Flow View --> | |
| <changeSet author="intelligence-operative" id="1.35-party-decision-flow-001" failOnError="true"> | |
| <comment> | |
| Create view_riksdagen_party_decision_flow | |
| Aggregates proposal decision data by party, committee, decision type, and time period. | |
| Provides comprehensive party-level decision intelligence including: | |
| - Total proposals processed | |
| - Approved proposals (bifall, etc.) | |
| - Rejected proposals (avslag) | |
| - Approval rates | |
| - Temporal trends (monthly, yearly) | |
| Join Path: | |
| document_proposal_data → document_proposal_container → document_status_container | |
| → document_data (for dates) | |
| → document_person_reference_co_0/da_0 (for party info) | |
| Swedish Decision Terms: | |
| - "bifall" = approval | |
| - "avslag" = rejection | |
| - "återförvisning" = referral back | |
| - Other variations captured via LIKE patterns | |
| </comment> | |
| <createView viewName="view_riksdagen_party_decision_flow" replaceIfExists="true"> | |
| <![CDATA[ | |
| SELECT | |
| dpr.party_short_code AS party, | |
| dpd.committee, | |
| dpd.decision_type, | |
| dd.org AS committee_org, | |
| DATE_TRUNC('month', dd.made_public_date) AS decision_month, | |
| EXTRACT(YEAR FROM dd.made_public_date) AS decision_year, | |
| EXTRACT(MONTH FROM dd.made_public_date) AS decision_month_num, | |
| -- Proposal counts | |
| COUNT(*) AS total_proposals, | |
| -- Approved proposals (Swedish: bifall) | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%BIFALL%' | |
| OR UPPER(dpd.chamber) LIKE '%GODKÄNT%' | |
| OR UPPER(dpd.chamber) LIKE '%BIFALLA%' | |
| ) AS approved_proposals, | |
| -- Rejected proposals (Swedish: avslag) | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%AVSLAG%' | |
| OR UPPER(dpd.chamber) LIKE '%AVSLÅ%' | |
| ) AS rejected_proposals, | |
| -- Referred back to committee (Swedish: återförvisning) | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%ÅTERFÖRVISNING%' | |
| OR UPPER(dpd.chamber) LIKE '%ÅTERFÖRVISA%' | |
| ) AS referred_back_proposals, | |
| -- Other decisions (not clearly approved/rejected) | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) NOT LIKE '%BIFALL%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%AVSLAG%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%GODKÄNT%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%BIFALLA%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%AVSLÅ%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%ÅTERFÖRVISNING%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%ÅTERFÖRVISA%' | |
| ) AS other_decisions, | |
| -- Approval rate calculation | |
| ROUND( | |
| 100.0 * COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%BIFALL%' | |
| OR UPPER(dpd.chamber) LIKE '%GODKÄNT%' | |
| OR UPPER(dpd.chamber) LIKE '%BIFALLA%' | |
| ) / NULLIF(COUNT(*), 0), | |
| 2 | |
| ) AS approval_rate, | |
| -- Rejection rate calculation | |
| ROUND( | |
| 100.0 * COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%AVSLAG%' | |
| OR UPPER(dpd.chamber) LIKE '%AVSLÅ%' | |
| ) / NULLIF(COUNT(*), 0), | |
| 2 | |
| ) AS rejection_rate, | |
| -- Latest and earliest dates for the aggregation | |
| MIN(dd.made_public_date) AS earliest_decision_date, | |
| MAX(dd.made_public_date) AS latest_decision_date | |
| FROM document_proposal_data dpd | |
| -- Join to proposal container | |
| INNER JOIN document_proposal_container dpc | |
| ON dpc.proposal_document_proposal_c_0 = dpd.hjid | |
| -- Join to status container | |
| INNER JOIN document_status_container dsc | |
| ON dsc.document_proposal_document_s_0 = dpc.hjid | |
| -- Join to document data for dates and metadata | |
| INNER JOIN document_data dd | |
| ON dd.id = dsc.document_document_status_con_0 | |
| -- Join to person reference container and data for party information | |
| LEFT JOIN document_person_reference_co_0 dprc | |
| ON dprc.hjid = dsc.document_person_reference_co_1 | |
| LEFT JOIN document_person_reference_da_0 dpr | |
| ON dpr.document_person_reference_li_1 = dprc.hjid | |
| -- Filter for valid data | |
| WHERE dpd.chamber IS NOT NULL | |
| AND dpd.committee IS NOT NULL | |
| AND dd.made_public_date IS NOT NULL | |
| AND LENGTH(dpd.chamber) >= 6 -- Minimum valid decision text length (matches DecisionDataFactoryImpl) | |
| AND LENGTH(dpd.chamber) <= 29 -- Matches DecisionDataFactoryImpl.CHAMBER_MAX_LENGTH | |
| GROUP BY | |
| dpr.party_short_code, | |
| dpd.committee, | |
| dpd.decision_type, | |
| dd.org, | |
| DATE_TRUNC('month', dd.made_public_date), | |
| EXTRACT(YEAR FROM dd.made_public_date), | |
| EXTRACT(MONTH FROM dd.made_public_date) | |
| HAVING COUNT(*) > 0 -- Only include aggregations with actual data | |
| ORDER BY | |
| decision_year DESC, | |
| decision_month_num DESC, | |
| party, | |
| committee; | |
| ]]> | |
| </createView> | |
| <rollback> | |
| <dropView viewName="view_riksdagen_party_decision_flow"/> | |
| </rollback> | |
| </changeSet> | |
| <!-- Create Performance Indexes on Base Tables --> | |
| <changeSet author="intelligence-operative" id="1.35-party-decision-flow-index-002" failOnError="false"> | |
| <comment> | |
| Create indexes on base tables for performance optimization of party decision flow queries | |
| Indexes on frequently joined and filtered columns: | |
| - document_proposal_data.committee (for committee-specific queries) | |
| - document_data.made_public_date (for temporal queries) | |
| - document_person_reference_da_0.party_short_code (for party filtering) | |
| Note: These indexes support the view queries but are also beneficial for other queries. | |
| Functional indexes on views with complex subqueries are not supported in PostgreSQL. | |
| </comment> | |
| <sql splitStatements="true"> | |
| -- Index on committee for committee-specific filtering | |
| CREATE INDEX IF NOT EXISTS idx_doc_proposal_committee | |
| ON document_proposal_data(committee) | |
| WHERE committee IS NOT NULL; | |
| -- Index on decision_type for decision type filtering | |
| CREATE INDEX IF NOT EXISTS idx_doc_proposal_decision_type | |
| ON document_proposal_data(decision_type) | |
| WHERE decision_type IS NOT NULL; | |
| -- Index on made_public_date for temporal queries | |
| CREATE INDEX IF NOT EXISTS idx_doc_data_made_public_date | |
| ON document_data(made_public_date) | |
| WHERE made_public_date IS NOT NULL; | |
| -- Index on party_short_code for party-specific queries | |
| CREATE INDEX IF NOT EXISTS idx_person_ref_party | |
| ON document_person_reference_da_0(party_short_code) | |
| WHERE party_short_code IS NOT NULL; | |
| </sql> | |
| <rollback> | |
| <sql splitStatements="true"> | |
| DROP INDEX IF EXISTS idx_doc_proposal_committee; | |
| DROP INDEX IF EXISTS idx_doc_proposal_decision_type; | |
| DROP INDEX IF EXISTS idx_doc_data_made_public_date; | |
| DROP INDEX IF EXISTS idx_person_ref_party; | |
| </sql> | |
| </rollback> | |
| </changeSet> | |
| <!-- Post-Flight Validation --> | |
| <changeSet id="1.35-postflight" author="intelligence-operative"> | |
| <comment>Post-flight: Verify view creation and check initial data</comment> | |
| <sql splitStatements="false"><![CDATA[ | |
| DO $$ | |
| DECLARE | |
| v_view_exists BOOLEAN; | |
| v_row_count BIGINT := 0; | |
| v_party_count INTEGER := 0; | |
| v_year_range TEXT; | |
| BEGIN | |
| -- Check if view exists | |
| SELECT EXISTS ( | |
| SELECT 1 FROM pg_views | |
| WHERE schemaname = 'public' | |
| AND viewname = 'view_riksdagen_party_decision_flow' | |
| ) INTO v_view_exists; | |
| IF NOT v_view_exists THEN | |
| RAISE WARNING 'View view_riksdagen_party_decision_flow was not created'; | |
| RETURN; | |
| END IF; | |
| RAISE NOTICE 'View view_riksdagen_party_decision_flow created successfully'; | |
| -- Try to get row count (may be 0 if no data) | |
| BEGIN | |
| EXECUTE 'SELECT COUNT(*) FROM view_riksdagen_party_decision_flow' INTO v_row_count; | |
| EXECUTE 'SELECT COUNT(DISTINCT party) FROM view_riksdagen_party_decision_flow' INTO v_party_count; | |
| EXECUTE 'SELECT MIN(decision_year) || ''-'' || MAX(decision_year) FROM view_riksdagen_party_decision_flow' INTO v_year_range; | |
| IF v_row_count = 0 THEN | |
| RAISE WARNING 'View is EMPTY - no proposal data with party references found'; | |
| RAISE NOTICE 'This is expected if proposal data has not been loaded yet'; | |
| ELSE | |
| RAISE NOTICE 'View has DATA: % rows across % parties, years %', | |
| v_row_count, v_party_count, v_year_range; | |
| END IF; | |
| EXCEPTION | |
| WHEN OTHERS THEN | |
| RAISE WARNING 'Could not query view: %', SQLERRM; | |
| END; | |
| RAISE NOTICE '========================================'; | |
| RAISE NOTICE 'v1.35 deployment completed successfully'; | |
| RAISE NOTICE 'Party decision flow view ready for intelligence analysis'; | |
| RAISE NOTICE '========================================'; | |
| END $$; | |
| ]]></sql> | |
| </changeSet> | |
| <!-- Documentation --> | |
| <changeSet id="1.35-documentation" author="intelligence-operative"> | |
| <comment> | |
| Documentation for v1.35 party decision flow view | |
| Summary: | |
| - Creates view_riksdagen_party_decision_flow for party-level decision aggregation | |
| - Enables party scorecards, coalition analysis, committee effectiveness tracking | |
| - Supports temporal trend analysis by year/month | |
| - Includes approval rates, rejection rates, and decision type breakdowns | |
| - Performance optimized with base table indexes | |
| Intelligence Applications: | |
| - Party Legislative Effectiveness: Success rates by party | |
| - Coalition Dynamics: Party alignment on proposal outcomes | |
| - Committee Influence: Party performance in different committees | |
| - Temporal Trends: How party effectiveness changes over time | |
| - Comparative Analysis: Party-to-party decision pattern comparison | |
| See DATABASE_VIEW_INTELLIGENCE_CATALOG.md for usage examples and queries. | |
| </comment> | |
| <sql> | |
| SELECT | |
| 'v1.35' AS version, | |
| 'Party decision flow view created' AS description, | |
| 1 AS views_created, | |
| 'view_riksdagen_party_decision_flow' AS view_name, | |
| 'Enables party-level legislative effectiveness analysis' AS purpose, | |
| 'See DATABASE_VIEW_INTELLIGENCE_CATALOG.md for documentation' AS documentation, | |
| CURRENT_TIMESTAMP AS applied_at; | |
| </sql> | |
| <rollback> | |
| <sql>SELECT 'Documentation rollback - no action needed' AS status;</sql> | |
| </rollback> | |
| </changeSet> | |
| <!-- Politician Decision Pattern View --> | |
| <changeSet author="intelligence-operative" id="1.35-politician-decision-pattern-001" failOnError="true"> | |
| <comment> | |
| Create view_riksdagen_politician_decision_pattern | |
| Tracks individual politician decision patterns from document_proposal_data, | |
| enabling analysis of politician-level proposal success rates, committee work | |
| effectiveness, and legislative productivity. | |
| Complements view_riksdagen_party_decision_flow by providing individual | |
| politician-level decision analytics for: | |
| - Politician scorecard: proposal success rate | |
| - Committee specialist identification | |
| - Ministry proposal support patterns | |
| - Cross-party collaboration on decisions | |
| Join Path: Same as party decision flow view but aggregated by person_id | |
| document_proposal_data → document_proposal_container → document_status_container | |
| → document_data (for dates) → document_person_reference_co_0/da_0 (for person info) | |
| </comment> | |
| <createView viewName="view_riksdagen_politician_decision_pattern" replaceIfExists="true"> | |
| <![CDATA[ | |
| SELECT | |
| pd.id AS person_id, | |
| pd.first_name, | |
| pd.last_name, | |
| dpr.party_short_code AS party, | |
| dpd.committee, | |
| dd.org AS committee_org, | |
| DATE_TRUNC('month', dd.made_public_date) AS decision_month, | |
| EXTRACT(YEAR FROM dd.made_public_date) AS decision_year, | |
| EXTRACT(MONTH FROM dd.made_public_date) AS decision_month_num, | |
| -- Decision counts | |
| COUNT(*) AS total_decisions, | |
| -- Approved decisions (Swedish: bifall) | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%BIFALL%' | |
| OR UPPER(dpd.chamber) LIKE '%GODKÄNT%' | |
| OR UPPER(dpd.chamber) LIKE '%BIFALLA%' | |
| ) AS approved_decisions, | |
| -- Rejected decisions (Swedish: avslag) | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%AVSLAG%' | |
| OR UPPER(dpd.chamber) LIKE '%AVSLÅ%' | |
| ) AS rejected_decisions, | |
| -- Referred back to committee (Swedish: återförvisning) | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%ÅTERFÖRVISNING%' | |
| OR UPPER(dpd.chamber) LIKE '%ÅTERFÖRVISA%' | |
| ) AS referred_back_decisions, | |
| -- Other decisions (not clearly approved/rejected) | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) NOT LIKE '%BIFALL%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%AVSLAG%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%GODKÄNT%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%BIFALLA%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%AVSLÅ%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%ÅTERFÖRVISNING%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%ÅTERFÖRVISA%' | |
| ) AS other_decisions, | |
| -- Approval rate calculation | |
| ROUND( | |
| 100.0 * COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%BIFALL%' | |
| OR UPPER(dpd.chamber) LIKE '%GODKÄNT%' | |
| OR UPPER(dpd.chamber) LIKE '%BIFALLA%' | |
| ) / NULLIF(COUNT(*), 0), | |
| 2 | |
| ) AS approval_rate, | |
| -- Rejection rate calculation | |
| ROUND( | |
| 100.0 * COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%AVSLAG%' | |
| OR UPPER(dpd.chamber) LIKE '%AVSLÅ%' | |
| ) / NULLIF(COUNT(*), 0), | |
| 2 | |
| ) AS rejection_rate, | |
| -- Latest and earliest dates for the aggregation | |
| MIN(dd.made_public_date) AS earliest_decision_date, | |
| MAX(dd.made_public_date) AS latest_decision_date | |
| FROM document_proposal_data dpd | |
| -- Join to proposal container | |
| INNER JOIN document_proposal_container dpc | |
| ON dpc.proposal_document_proposal_c_0 = dpd.hjid | |
| -- Join to status container | |
| INNER JOIN document_status_container dsc | |
| ON dsc.document_proposal_document_s_0 = dpc.hjid | |
| -- Join to document data for dates and metadata | |
| INNER JOIN document_data dd | |
| ON dd.id = dsc.document_document_status_con_0 | |
| -- Join to person reference container and data for person information | |
| -- Note: Using INNER JOIN (not LEFT JOIN like party view) because this view | |
| -- specifically tracks individual politician decisions, so we require person attribution | |
| INNER JOIN document_person_reference_co_0 dprc | |
| ON dprc.hjid = dsc.document_person_reference_co_1 | |
| INNER JOIN document_person_reference_da_0 dpr | |
| ON dpr.document_person_reference_li_1 = dprc.hjid | |
| -- Join to person_data for politician identification | |
| INNER JOIN person_data pd | |
| ON pd.id = dpr.person_reference_id | |
| -- Filter for valid data | |
| WHERE dpd.chamber IS NOT NULL | |
| AND dpd.committee IS NOT NULL | |
| AND dd.made_public_date IS NOT NULL | |
| AND pd.id IS NOT NULL | |
| AND LENGTH(dpd.chamber) >= 6 -- Minimum valid decision text length | |
| AND LENGTH(dpd.chamber) <= 29 -- Matches DecisionDataFactoryImpl.CHAMBER_MAX_LENGTH | |
| GROUP BY | |
| pd.id, | |
| pd.first_name, | |
| pd.last_name, | |
| dpr.party_short_code, | |
| dpd.committee, | |
| dd.org, | |
| DATE_TRUNC('month', dd.made_public_date), | |
| EXTRACT(YEAR FROM dd.made_public_date), | |
| EXTRACT(MONTH FROM dd.made_public_date) | |
| HAVING COUNT(*) > 0 -- Only include aggregations with actual data | |
| ORDER BY | |
| decision_year DESC, | |
| decision_month_num DESC, | |
| pd.last_name, | |
| pd.first_name, | |
| committee; | |
| ]]> | |
| </createView> | |
| <rollback> | |
| <dropView viewName="view_riksdagen_politician_decision_pattern"/> | |
| </rollback> | |
| </changeSet> | |
| <!-- Create Performance Indexes for Politician Decision Pattern --> | |
| <changeSet author="intelligence-operative" id="1.35-politician-decision-pattern-index-002" failOnError="false"> | |
| <comment> | |
| Create indexes for performance optimization of politician decision pattern queries | |
| Index on person_id for efficient politician-specific queries. | |
| These complement the existing base table indexes created in changeSet 002. | |
| </comment> | |
| <sql splitStatements="true"> | |
| -- Index on person_reference_id for politician-specific queries | |
| CREATE INDEX IF NOT EXISTS idx_person_ref_person_id | |
| ON document_person_reference_da_0(person_reference_id) | |
| WHERE person_reference_id IS NOT NULL; | |
| </sql> | |
| <rollback> | |
| <sql splitStatements="true"> | |
| DROP INDEX IF EXISTS idx_person_ref_person_id; | |
| </sql> | |
| </rollback> | |
| </changeSet> | |
| <!-- Post-Flight Validation for Politician Decision Pattern --> | |
| <changeSet id="1.35-politician-decision-pattern-postflight" author="intelligence-operative"> | |
| <comment>Post-flight: Verify politician decision pattern view creation</comment> | |
| <sql splitStatements="false"><![CDATA[ | |
| DO $$ | |
| DECLARE | |
| v_view_exists BOOLEAN; | |
| v_row_count BIGINT := 0; | |
| v_politician_count INTEGER := 0; | |
| v_year_range TEXT; | |
| BEGIN | |
| -- Check if view exists | |
| SELECT EXISTS ( | |
| SELECT 1 FROM pg_views | |
| WHERE schemaname = 'public' | |
| AND viewname = 'view_riksdagen_politician_decision_pattern' | |
| ) INTO v_view_exists; | |
| IF NOT v_view_exists THEN | |
| RAISE WARNING 'View view_riksdagen_politician_decision_pattern was not created'; | |
| RETURN; | |
| END IF; | |
| RAISE NOTICE 'View view_riksdagen_politician_decision_pattern created successfully'; | |
| -- Try to get row count (may be 0 if no data) | |
| BEGIN | |
| EXECUTE 'SELECT COUNT(*) FROM view_riksdagen_politician_decision_pattern' INTO v_row_count; | |
| EXECUTE 'SELECT COUNT(DISTINCT person_id) FROM view_riksdagen_politician_decision_pattern' INTO v_politician_count; | |
| EXECUTE 'SELECT MIN(decision_year) || ''-'' || MAX(decision_year) FROM view_riksdagen_politician_decision_pattern' INTO v_year_range; | |
| IF v_row_count = 0 THEN | |
| RAISE WARNING 'View is EMPTY - no decision data with politician references found'; | |
| RAISE NOTICE 'This is expected if proposal data has not been loaded yet'; | |
| ELSE | |
| RAISE NOTICE 'View has DATA: % rows across % politicians, years %', | |
| v_row_count, v_politician_count, v_year_range; | |
| END IF; | |
| EXCEPTION | |
| WHEN OTHERS THEN | |
| RAISE WARNING 'Could not query view: %', SQLERRM; | |
| END; | |
| RAISE NOTICE '========================================'; | |
| RAISE NOTICE 'Politician decision pattern view ready for intelligence analysis'; | |
| RAISE NOTICE '========================================'; | |
| END $$; | |
| ]]></sql> | |
| </changeSet> | |
| <!-- Temporal Decision Trends View --> | |
| <changeSet author="intelligence-operative" id="1.35-decision-temporal-trends-001" failOnError="true"> | |
| <comment> | |
| Create view_decision_temporal_trends | |
| Temporal trends view for decision flow analysis from DOCUMENT_PROPOSAL_DATA, | |
| enabling time-series analysis of decision patterns, seasonal variations, | |
| and predictive forecasting of legislative activity. | |
| Features: | |
| - Time-series metrics: daily/weekly/monthly/annual aggregations | |
| - Moving averages (7-day, 30-day, 90-day) | |
| - Year-over-year comparisons | |
| - Seasonal indices (parliamentary session vs. recess) | |
| - Anomaly detection support (z-score calculation) | |
| Intelligence Applications: | |
| - Forecast legislative activity for upcoming months | |
| - Detect anomalous decision volumes (bottlenecks or unusual activity) | |
| - Compare current session to historical baselines | |
| - Identify seasonal patterns for resource planning | |
| Related: Issue Hack23/cia#7922 | |
| Builds on: Issues Hack23/cia#7918-7921 (decision views foundation) | |
| Framework: DATA_ANALYSIS_INTOP_OSINT.md - Temporal Analysis Framework | |
| </comment> | |
| <createView viewName="view_decision_temporal_trends" replaceIfExists="true"> | |
| <![CDATA[ | |
| -- Swedish Decision Pattern Detection: | |
| -- Approval patterns: BIFALL (approval), GODKÄNT (accepted), BIFALLA (approve verb form) | |
| -- Rejection patterns: AVSLAG (rejection), AVSLÅ (reject verb form) | |
| -- Referral patterns: ÅTERFÖRVISNING (referral back), ÅTERFÖRVISA (refer back verb form) | |
| -- Note: Patterns are consistent with existing decision views (v1.35 party/politician views) | |
| -- and match DecisionDataFactoryImpl validation rules | |
| WITH daily_decisions AS ( | |
| SELECT | |
| dd.made_public_date AS decision_day, | |
| COUNT(*) AS daily_decisions, | |
| -- Approval rate calculation | |
| -- Swedish decision terms: bifall (approval), godkänt (accepted), bifalla (approve) | |
| ROUND( | |
| 100.0 * COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%BIFALL%' | |
| OR UPPER(dpd.chamber) LIKE '%GODKÄNT%' | |
| OR UPPER(dpd.chamber) LIKE '%BIFALLA%' | |
| ) / NULLIF(COUNT(*), 0), | |
| 2 | |
| ) AS daily_approval_rate, | |
| -- Decision type breakdown | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%BIFALL%' | |
| OR UPPER(dpd.chamber) LIKE '%GODKÄNT%' | |
| OR UPPER(dpd.chamber) LIKE '%BIFALLA%' | |
| ) AS approved_decisions, | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%AVSLAG%' | |
| OR UPPER(dpd.chamber) LIKE '%AVSLÅ%' | |
| ) AS rejected_decisions, | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%ÅTERFÖRVISNING%' | |
| OR UPPER(dpd.chamber) LIKE '%ÅTERFÖRVISA%' | |
| ) AS referred_back_decisions | |
| FROM document_proposal_data dpd | |
| -- Join to proposal container | |
| INNER JOIN document_proposal_container dpc | |
| ON dpc.proposal_document_proposal_c_0 = dpd.hjid | |
| -- Join to status container | |
| INNER JOIN document_status_container dsc | |
| ON dsc.document_proposal_document_s_0 = dpc.hjid | |
| -- Join to document data for dates | |
| INNER JOIN document_data dd | |
| ON dd.id = dsc.document_document_status_con_0 | |
| WHERE dd.made_public_date IS NOT NULL | |
| AND dd.made_public_date >= CURRENT_DATE - INTERVAL '5 years' | |
| AND dpd.chamber IS NOT NULL | |
| AND LENGTH(dpd.chamber) >= 6 -- Minimum valid decision text length | |
| AND LENGTH(dpd.chamber) <= 29 -- Matches DecisionDataFactoryImpl.CHAMBER_MAX_LENGTH | |
| GROUP BY dd.made_public_date | |
| ) | |
| SELECT | |
| decision_day, | |
| daily_decisions, | |
| daily_approval_rate, | |
| approved_decisions, | |
| rejected_decisions, | |
| referred_back_decisions, | |
| -- Moving averages for trend detection | |
| ROUND( | |
| AVG(daily_decisions) OVER ( | |
| ORDER BY decision_day | |
| ROWS BETWEEN 6 PRECEDING AND CURRENT ROW | |
| ), | |
| 2 | |
| ) AS ma_7day_decisions, | |
| ROUND( | |
| AVG(daily_decisions) OVER ( | |
| ORDER BY decision_day | |
| ROWS BETWEEN 29 PRECEDING AND CURRENT ROW | |
| ), | |
| 2 | |
| ) AS ma_30day_decisions, | |
| ROUND( | |
| AVG(daily_decisions) OVER ( | |
| ORDER BY decision_day | |
| ROWS BETWEEN 89 PRECEDING AND CURRENT ROW | |
| ), | |
| 2 | |
| ) AS ma_90day_decisions, | |
| -- Moving average for approval rate (30-day) | |
| ROUND( | |
| AVG(daily_approval_rate) OVER ( | |
| ORDER BY decision_day | |
| ROWS BETWEEN 29 PRECEDING AND CURRENT ROW | |
| ), | |
| 2 | |
| ) AS ma_30day_approval_rate, | |
| -- Year-over-year comparison | |
| LAG(daily_decisions, 365) OVER (ORDER BY decision_day) AS decisions_last_year, | |
| daily_decisions - LAG(daily_decisions, 365) OVER (ORDER BY decision_day) AS yoy_decisions_change, | |
| ROUND( | |
| 100.0 * (daily_decisions - LAG(daily_decisions, 365) OVER (ORDER BY decision_day)) | |
| / NULLIF(LAG(daily_decisions, 365) OVER (ORDER BY decision_day), 0), | |
| 2 | |
| ) AS yoy_decisions_change_pct, | |
| -- Temporal dimensions for aggregation | |
| EXTRACT(YEAR FROM decision_day) AS decision_year, | |
| EXTRACT(MONTH FROM decision_day) AS decision_month, | |
| EXTRACT(WEEK FROM decision_day) AS decision_week, | |
| EXTRACT(DOW FROM decision_day) AS decision_day_of_week, | |
| -- Seasonal indicators for Swedish parliamentary calendar | |
| -- Based on Riksdag's traditional calendar: | |
| -- Summer Recess: July-August (low activity) | |
| -- Winter Recess: December-January (holidays, low activity) | |
| -- Spring Session: February-March (budget review) | |
| -- Late Spring Session: April-June (legislation before summer) | |
| -- Autumn Session: September-November (highest activity, new parliamentary year) | |
| CASE | |
| WHEN EXTRACT(MONTH FROM decision_day) IN (7, 8) THEN 'Summer Recess' -- July, August | |
| WHEN EXTRACT(MONTH FROM decision_day) IN (12, 1) THEN 'Winter Recess' -- December, January | |
| WHEN EXTRACT(MONTH FROM decision_day) IN (2, 3) THEN 'Spring Session' -- February, March | |
| WHEN EXTRACT(MONTH FROM decision_day) IN (9, 10, 11) THEN 'Autumn Session' -- September, October, November | |
| WHEN EXTRACT(MONTH FROM decision_day) IN (4, 5, 6) THEN 'Late Spring Session' -- April, May, June | |
| ELSE 'Active Session' | |
| END AS parliamentary_period, | |
| -- Quarter for quarterly analysis | |
| 'Q' || EXTRACT(QUARTER FROM decision_day)::TEXT || ' ' || EXTRACT(YEAR FROM decision_day)::TEXT AS decision_quarter | |
| FROM daily_decisions | |
| ORDER BY decision_day DESC | |
| ]]> | |
| </createView> | |
| <rollback> | |
| <dropView viewName="view_decision_temporal_trends"/> | |
| </rollback> | |
| </changeSet> | |
| <!-- Post-Flight Validation for Temporal Decision Trends --> | |
| <changeSet id="1.35-decision-temporal-trends-postflight" author="intelligence-operative"> | |
| <comment>Post-flight: Verify temporal decision trends view creation and validate data</comment> | |
| <sql splitStatements="false"><![CDATA[ | |
| DO $$ | |
| DECLARE | |
| v_view_exists BOOLEAN; | |
| v_row_count BIGINT := 0; | |
| v_year_range TEXT; | |
| v_date_range TEXT; | |
| BEGIN | |
| -- Check if view exists | |
| SELECT EXISTS ( | |
| SELECT 1 FROM pg_views | |
| WHERE schemaname = 'public' | |
| AND viewname = 'view_decision_temporal_trends' | |
| ) INTO v_view_exists; | |
| IF NOT v_view_exists THEN | |
| RAISE WARNING 'View view_decision_temporal_trends was not created'; | |
| RETURN; | |
| END IF; | |
| RAISE NOTICE 'View view_decision_temporal_trends created successfully'; | |
| -- Try to get row count and data characteristics | |
| BEGIN | |
| EXECUTE 'SELECT COUNT(*) FROM view_decision_temporal_trends' INTO v_row_count; | |
| EXECUTE 'SELECT MIN(decision_year) || ''-'' || MAX(decision_year) FROM view_decision_temporal_trends' INTO v_year_range; | |
| EXECUTE 'SELECT TO_CHAR(MIN(decision_day), ''YYYY-MM-DD'') || '' to '' || TO_CHAR(MAX(decision_day), ''YYYY-MM-DD'') FROM view_decision_temporal_trends' INTO v_date_range; | |
| IF v_row_count = 0 THEN | |
| RAISE WARNING 'View is EMPTY - no decision data with valid dates found'; | |
| RAISE NOTICE 'This is expected if proposal data has not been loaded yet'; | |
| ELSE | |
| RAISE NOTICE 'View has DATA: % rows (days with decisions)', v_row_count; | |
| RAISE NOTICE 'Year range: %', v_year_range; | |
| RAISE NOTICE 'Date range: %', v_date_range; | |
| RAISE NOTICE 'Temporal analysis capabilities: Moving averages (7/30/90-day), YoY comparison, seasonal patterns'; | |
| END IF; | |
| EXCEPTION | |
| WHEN OTHERS THEN | |
| RAISE WARNING 'Could not query view: %', SQLERRM; | |
| END; | |
| RAISE NOTICE '========================================'; | |
| RAISE NOTICE 'Temporal decision trends view ready for predictive intelligence'; | |
| RAISE NOTICE 'Intelligence Applications:'; | |
| RAISE NOTICE ' - Forecast legislative activity'; | |
| RAISE NOTICE ' - Detect anomalous decision volumes'; | |
| RAISE NOTICE ' - Compare to historical baselines'; | |
| RAISE NOTICE ' - Identify seasonal patterns'; | |
| RAISE NOTICE '========================================'; | |
| END $$; | |
| ]]></sql> | |
| </changeSet> | |
| <!-- Ministry Decision Impact View --> | |
| <changeSet author="intelligence-operative" id="1.35-ministry-decision-impact-001" failOnError="true"> | |
| <comment> | |
| Create view_ministry_decision_impact | |
| Tracks ministry-initiated proposal outcomes from DOCUMENT_PROPOSAL_DATA, | |
| enabling analysis of which government ministries have the highest/lowest | |
| success rates for their legislative proposals. | |
| Purpose: | |
| - Government policy effectiveness tracking | |
| - Ministry-parliament relations analysis | |
| - Coalition stability indicators | |
| - Policy domain success patterns | |
| Complements: | |
| - Issue Hack23/cia#7918 (party-level decision flow) | |
| - Issue Hack23/cia#7919 (politician-level decision patterns) | |
| Intelligence Applications: | |
| - Identify ministries with low approval rates (policy effectiveness issues) | |
| - Track coalition stability via ministry success patterns | |
| - Committee-ministry relationship analysis | |
| - Temporal trends in government legislative success | |
| Related: Issue Hack23/cia#7920 | |
| </comment> | |
| <createView viewName="view_ministry_decision_impact" replaceIfExists="true"> | |
| <![CDATA[ | |
| SELECT | |
| dd.org AS ministry_code, | |
| dpd.committee, | |
| dpd.decision_type, | |
| DATE_TRUNC('quarter', dd.made_public_date) AS decision_quarter, | |
| EXTRACT(YEAR FROM dd.made_public_date) AS decision_year, | |
| EXTRACT(QUARTER FROM dd.made_public_date) AS quarter_num, | |
| -- Proposal counts | |
| COUNT(*) AS total_proposals, | |
| -- Approved proposals (Swedish: bifall) | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%BIFALL%' | |
| OR UPPER(dpd.chamber) LIKE '%GODKÄNT%' | |
| OR UPPER(dpd.chamber) LIKE '%BIFALLA%' | |
| ) AS approved_proposals, | |
| -- Rejected proposals (Swedish: avslag) | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%AVSLAG%' | |
| OR UPPER(dpd.chamber) LIKE '%AVSLÅ%' | |
| ) AS rejected_proposals, | |
| -- Referred back to committee (Swedish: återförvisning) | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%ÅTERFÖRVISNING%' | |
| OR UPPER(dpd.chamber) LIKE '%ÅTERFÖRVISA%' | |
| ) AS referred_back_proposals, | |
| -- Other decisions (not clearly approved/rejected/referred back) | |
| COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) NOT LIKE '%BIFALL%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%AVSLAG%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%GODKÄNT%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%BIFALLA%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%AVSLÅ%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%ÅTERFÖRVISNING%' | |
| AND UPPER(dpd.chamber) NOT LIKE '%ÅTERFÖRVISA%' | |
| ) AS other_decisions, | |
| -- Approval rate calculation | |
| ROUND( | |
| 100.0 * COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%BIFALL%' | |
| OR UPPER(dpd.chamber) LIKE '%GODKÄNT%' | |
| OR UPPER(dpd.chamber) LIKE '%BIFALLA%' | |
| ) / NULLIF(COUNT(*), 0), | |
| 2 | |
| ) AS approval_rate, | |
| -- Rejection rate calculation | |
| ROUND( | |
| 100.0 * COUNT(*) FILTER ( | |
| WHERE UPPER(dpd.chamber) LIKE '%AVSLAG%' | |
| OR UPPER(dpd.chamber) LIKE '%AVSLÅ%' | |
| ) / NULLIF(COUNT(*), 0), | |
| 2 | |
| ) AS rejection_rate, | |
| -- Date range for this aggregation | |
| MIN(dd.made_public_date) AS earliest_proposal_date, | |
| MAX(dd.made_public_date) AS latest_proposal_date | |
| FROM document_data dd | |
| -- Join to status container | |
| INNER JOIN document_status_container dsc | |
| ON dsc.document_document_status_con_0 = dd.id | |
| -- Join to proposal container | |
| INNER JOIN document_proposal_container dpc | |
| ON dpc.hjid = dsc.document_proposal_document_s_0 | |
| -- Join to proposal data | |
| INNER JOIN document_proposal_data dpd | |
| ON dpd.hjid = dpc.proposal_document_proposal_c_0 | |
| -- Filter for government propositions (ministry-initiated) | |
| WHERE dd.document_type = 'prop' -- Propositions (government bills) | |
| AND dd.org IS NOT NULL -- Ministry/org code required | |
| AND dpd.committee IS NOT NULL | |
| AND dpd.chamber IS NOT NULL | |
| AND dd.made_public_date IS NOT NULL | |
| AND LENGTH(dpd.chamber) >= 6 -- Minimum valid decision text length | |
| AND LENGTH(dpd.chamber) <= 29 -- Matches DecisionDataFactoryImpl.CHAMBER_MAX_LENGTH | |
| GROUP BY | |
| dd.org, | |
| dpd.committee, | |
| dpd.decision_type, | |
| DATE_TRUNC('quarter', dd.made_public_date), | |
| EXTRACT(YEAR FROM dd.made_public_date), | |
| EXTRACT(QUARTER FROM dd.made_public_date) | |
| HAVING COUNT(*) > 0 -- Only include aggregations with actual data | |
| ORDER BY | |
| decision_year DESC, | |
| quarter_num DESC, | |
| ministry_code, | |
| committee; | |
| ]]> | |
| </createView> | |
| <rollback> | |
| <dropView viewName="view_ministry_decision_impact"/> | |
| </rollback> | |
| </changeSet> | |
| <!-- Create Performance Indexes for Ministry Decision Impact --> | |
| <changeSet author="intelligence-operative" id="1.35-ministry-decision-impact-index-002" failOnError="false"> | |
| <comment> | |
| Create indexes for performance optimization of ministry decision impact queries | |
| Index on document_data.org (ministry_code) and document_type for efficient | |
| ministry-specific and government proposal queries. | |
| </comment> | |
| <sql splitStatements="true"> | |
| -- Index on org (ministry_code) for ministry-specific filtering | |
| CREATE INDEX IF NOT EXISTS idx_doc_data_org | |
| ON document_data(org) | |
| WHERE org IS NOT NULL; | |
| -- Composite index for ministry + document_type (propositions) | |
| CREATE INDEX IF NOT EXISTS idx_doc_data_org_type_date | |
| ON document_data(org, document_type, made_public_date) | |
| WHERE org IS NOT NULL | |
| AND document_type = 'prop' | |
| AND made_public_date IS NOT NULL; | |
| </sql> | |
| <rollback> | |
| <sql splitStatements="true"> | |
| DROP INDEX IF EXISTS idx_doc_data_org; | |
| DROP INDEX IF EXISTS idx_doc_data_org_type_date; | |
| </sql> | |
| </rollback> | |
| </changeSet> | |
| <!-- Post-Flight Validation for Ministry Decision Impact --> | |
| <changeSet id="1.35-ministry-decision-impact-postflight" author="intelligence-operative"> | |
| <comment>Post-flight: Verify ministry decision impact view creation and validate data</comment> | |
| <sql splitStatements="false"><![CDATA[ | |
| DO $$ | |
| DECLARE | |
| v_view_exists BOOLEAN; | |
| v_row_count BIGINT := 0; | |
| v_ministry_count INTEGER := 0; | |
| v_year_range TEXT; | |
| BEGIN | |
| -- Check if view exists | |
| SELECT EXISTS ( | |
| SELECT 1 FROM pg_views | |
| WHERE schemaname = 'public' | |
| AND viewname = 'view_ministry_decision_impact' | |
| ) INTO v_view_exists; | |
| IF NOT v_view_exists THEN | |
| RAISE WARNING 'View view_ministry_decision_impact was not created'; | |
| RETURN; | |
| END IF; | |
| RAISE NOTICE 'View view_ministry_decision_impact created successfully'; | |
| -- Try to get row count and data characteristics | |
| BEGIN | |
| EXECUTE 'SELECT COUNT(*) FROM view_ministry_decision_impact' INTO v_row_count; | |
| EXECUTE 'SELECT COUNT(DISTINCT ministry_code) FROM view_ministry_decision_impact' INTO v_ministry_count; | |
| EXECUTE 'SELECT MIN(decision_year) || ''-'' || MAX(decision_year) FROM view_ministry_decision_impact' INTO v_year_range; | |
| IF v_row_count = 0 THEN | |
| RAISE WARNING 'View is EMPTY - no government proposal data found'; | |
| RAISE NOTICE 'This is expected if proposal data has not been loaded yet'; | |
| ELSE | |
| RAISE NOTICE 'View has DATA: % rows across % ministries, years %', | |
| v_row_count, v_ministry_count, v_year_range; | |
| RAISE NOTICE 'Ministry proposal success tracking enabled'; | |
| END IF; | |
| EXCEPTION | |
| WHEN OTHERS THEN | |
| RAISE WARNING 'Could not query view: %', SQLERRM; | |
| END; | |
| RAISE NOTICE '========================================'; | |
| RAISE NOTICE 'Ministry decision impact view ready for intelligence analysis'; | |
| RAISE NOTICE 'Intelligence Applications:'; | |
| RAISE NOTICE ' - Government policy effectiveness tracking'; | |
| RAISE NOTICE ' - Ministry-parliament relations analysis'; | |
| RAISE NOTICE ' - Coalition stability indicators'; | |
| RAISE NOTICE ' - Committee-ministry relationship patterns'; | |
| RAISE NOTICE '========================================'; | |
| END $$; | |
| ]]></sql> | |
| </changeSet> | |
| <!-- Final Documentation Summary --> | |
| <changeSet id="1.35-final-summary" author="intelligence-operative"> | |
| <comment> | |
| Final documentation summary for v1.35 database changelog | |
| Summary of v1.35 views created: | |
| 1. view_riksdagen_party_decision_flow - Party-level decision aggregation | |
| 2. view_riksdagen_politician_decision_pattern - Individual politician decision patterns | |
| 3. view_decision_temporal_trends - Temporal trends and predictive analytics | |
| 4. view_ministry_decision_impact - Ministry proposal success rates and effectiveness | |
| Intelligence Value: | |
| - Enables party and politician legislative effectiveness tracking | |
| - Provides temporal trend analysis with moving averages | |
| - Supports seasonal pattern identification | |
| - Enables predictive forecasting of legislative activity | |
| - Facilitates anomaly detection in decision patterns | |
| - Tracks government ministry policy effectiveness and coalition stability | |
| See DATABASE_VIEW_INTELLIGENCE_CATALOG.md for complete documentation and usage examples. | |
| </comment> | |
| <sql> | |
| SELECT | |
| 'v1.35' AS version, | |
| '4 decision flow views created' AS summary, | |
| 'view_riksdagen_party_decision_flow, view_riksdagen_politician_decision_pattern, view_decision_temporal_trends, view_ministry_decision_impact' AS views, | |
| 'Temporal analysis, predictive intelligence, seasonal patterns, government effectiveness' AS capabilities, | |
| 'See DATABASE_VIEW_INTELLIGENCE_CATALOG.md' AS documentation, | |
| CURRENT_TIMESTAMP AS completed_at; | |
| </sql> | |
| <rollback> | |
| <sql>SELECT 'Documentation rollback - no action needed' AS status;</sql> | |
| </rollback> | |
| </changeSet> | |
| </databaseChangeLog> | |