widgettdc-api / data /dIV /db-changelog-1.35.xml
Kraft102's picture
fix: sql.js Docker/Alpine compatibility layer for PatternMemory and FailureMemory
5a81b95
<?xml version="1.0" encoding="UTF-8"?>
<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>