{"id":3210,"date":"2025-07-21T03:54:51","date_gmt":"2025-07-21T01:54:51","guid":{"rendered":"https:\/\/j-cred.co.za\/?page_id=3210"},"modified":"2025-07-21T09:39:20","modified_gmt":"2025-07-21T07:39:20","slug":"mi","status":"publish","type":"page","link":"https:\/\/j-cred.co.za\/zh\/mi\/","title":{"rendered":"Municipal Insights"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"3210\" class=\"elementor elementor-3210\" data-elementor-settings=\"{&quot;ha_cmc_init_switcher&quot;:&quot;no&quot;}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-0e99634 e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-column-slider-no wpr-equal-height-no e-con e-parent\" data-id=\"0e99634\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;background_background&quot;:&quot;classic&quot;,&quot;_ha_eqh_enable&quot;:false}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t<div class=\"elementor-element elementor-element-dacf995 e-grid e-con-full wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-column-slider-no wpr-equal-height-no e-con e-child\" data-id=\"dacf995\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;_ha_eqh_enable&quot;:false}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-690a577 elementor-widget elementor-widget-image\" data-id=\"690a577\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img fetchpriority=\"high\" decoding=\"async\" width=\"1032\" height=\"323\" src=\"https:\/\/j-cred.co.za\/wp-content\/uploads\/2025\/07\/J-Cred-G2C-Logo.webp\" class=\"attachment-full size-full wp-image-3223\" alt=\"\" srcset=\"https:\/\/j-cred.co.za\/wp-content\/uploads\/2025\/07\/J-Cred-G2C-Logo.webp 1032w, https:\/\/j-cred.co.za\/wp-content\/uploads\/2025\/07\/J-Cred-G2C-Logo-300x94.webp 300w, https:\/\/j-cred.co.za\/wp-content\/uploads\/2025\/07\/J-Cred-G2C-Logo-1024x320.webp 1024w, https:\/\/j-cred.co.za\/wp-content\/uploads\/2025\/07\/J-Cred-G2C-Logo-768x240.webp 768w, https:\/\/j-cred.co.za\/wp-content\/uploads\/2025\/07\/J-Cred-G2C-Logo-18x6.webp 18w\" sizes=\"(max-width: 1032px) 100vw, 1032px\" \/>\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-4a7c49f elementor-widget elementor-widget-image\" data-id=\"4a7c49f\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"image.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<img decoding=\"async\" width=\"1875\" height=\"203\" src=\"https:\/\/j-cred.co.za\/wp-content\/uploads\/2025\/07\/KTO-Digital-Innovate.-Disrupt.-Transform.webp\" class=\"attachment-full size-full wp-image-3326\" alt=\"\" srcset=\"https:\/\/j-cred.co.za\/wp-content\/uploads\/2025\/07\/KTO-Digital-Innovate.-Disrupt.-Transform.webp 1875w, https:\/\/j-cred.co.za\/wp-content\/uploads\/2025\/07\/KTO-Digital-Innovate.-Disrupt.-Transform-300x32.webp 300w, https:\/\/j-cred.co.za\/wp-content\/uploads\/2025\/07\/KTO-Digital-Innovate.-Disrupt.-Transform-1024x111.webp 1024w, https:\/\/j-cred.co.za\/wp-content\/uploads\/2025\/07\/KTO-Digital-Innovate.-Disrupt.-Transform-768x83.webp 768w, https:\/\/j-cred.co.za\/wp-content\/uploads\/2025\/07\/KTO-Digital-Innovate.-Disrupt.-Transform-1536x166.webp 1536w, https:\/\/j-cred.co.za\/wp-content\/uploads\/2025\/07\/KTO-Digital-Innovate.-Disrupt.-Transform-18x2.webp 18w\" sizes=\"(max-width: 1875px) 100vw, 1875px\" \/>\t\t\t\t\t\t\t\t\t\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-3e26e8db e-flex e-con-boxed wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-column-slider-no wpr-equal-height-no e-con e-parent\" data-id=\"3e26e8db\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;_ha_eqh_enable&quot;:false}\">\n\t\t\t\t\t<div class=\"e-con-inner\">\n\t\t\t\t<div class=\"elementor-element elementor-element-87a8a35 elementor-widget elementor-widget-heading\" data-id=\"87a8a35\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"heading.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<h1 class=\"elementor-heading-title elementor-size-default\">Municipal Overview \/ Insights<\/h1>\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t<div class=\"elementor-element elementor-element-660f56e e-grid e-con-full wpr-particle-no wpr-jarallax-no wpr-parallax-no wpr-sticky-section-no wpr-column-slider-no wpr-equal-height-no e-con e-child\" data-id=\"660f56e\" data-element_type=\"container\" data-e-type=\"container\" data-settings=\"{&quot;_ha_eqh_enable&quot;:false}\">\n\t\t\t\t<div class=\"elementor-element elementor-element-09020f7 elementor-widget elementor-widget-video\" data-id=\"09020f7\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;youtube_url&quot;:&quot;https:\\\/\\\/youtu.be\\\/HeQiX_e8ubg&quot;,&quot;video_type&quot;:&quot;youtube&quot;,&quot;controls&quot;:&quot;yes&quot;}\" data-widget_type=\"video.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-wrapper elementor-open-inline\">\n\t\t\t<div class=\"elementor-video\"><\/div>\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-38209e5 elementor-widget elementor-widget-video\" data-id=\"38209e5\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;youtube_url&quot;:&quot;https:\\\/\\\/youtu.be\\\/WVZBVJTh0u0&quot;,&quot;video_type&quot;:&quot;youtube&quot;,&quot;controls&quot;:&quot;yes&quot;}\" data-widget_type=\"video.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-wrapper elementor-open-inline\">\n\t\t\t<div class=\"elementor-video\"><\/div>\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-22f2ff2 elementor-widget elementor-widget-video\" data-id=\"22f2ff2\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;youtube_url&quot;:&quot;https:\\\/\\\/www.youtube.com\\\/watch?v=A15Fvwcx_OY&quot;,&quot;video_type&quot;:&quot;youtube&quot;,&quot;controls&quot;:&quot;yes&quot;}\" data-widget_type=\"video.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\t\t<div class=\"elementor-wrapper elementor-open-inline\">\n\t\t\t<div class=\"elementor-video\"><\/div>\t\t<\/div>\n\t\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<div class=\"elementor-element elementor-element-5514051 elementor-widget elementor-widget-html\" data-id=\"5514051\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!DOCTYPE html>\r\n<html lang=\"en\">\r\n<head>\r\n    <meta charset=\"UTF-8\">\r\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\r\n    <title>J-Cred G2C Municipal Insights Portal<\/title>\r\n    <!-- Tailwind CSS CDN -->\r\n    <script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\r\n    <!-- Google Fonts - Inter -->\r\n    <link href=\"https:\/\/fonts.googleapis.com\/css2?family=Inter:wght@400;600;700;800&display=swap\" rel=\"stylesheet\">\r\n    <style>\r\n        body {\r\n            font-family: 'Inter', sans-serif;\r\n            \/* Changed background to white *\/\r\n            background-color: #ffffff; \r\n        }\r\n        \/* Simple spinner animation *\/\r\n        .spinner {\r\n            border-top-color: #3B82F6;\r\n            -webkit-animation: spinner .8s linear infinite;\r\n            animation: spinner .8s linear infinite;\r\n        }\r\n        @-webkit-keyframes spinner {\r\n            0% { -webkit-transform: rotate(0deg); }\r\n            100% { -webkit-transform: rotate(360deg); }\r\n        }\r\n        @keyframes spinner {\r\n            0% { transform: rotate(0deg); }\r\n            100% { transform: rotate(360deg); }\r\n        }\r\n        \/* Style for the ASCII graph *\/\r\n        .ascii-graph {\r\n            font-family: 'monospace'; \/* Ensures characters align correctly *\/\r\n            white-space: pre; \/* Preserves whitespace and line breaks *\/\r\n            text-align: left;\r\n            margin-top: 1rem;\r\n            background-color: #f9f9f9;\r\n            padding: 1rem;\r\n            border-radius: 0.5rem;\r\n            overflow-x: auto; \/* Allow horizontal scrolling if graph is too wide *\/\r\n            color: #333; \/* Darker text for better contrast *\/\r\n            line-height: 1.4; \/* Improve line spacing *\/\r\n        }\r\n    <\/style>\r\n<\/head>\r\n<body class=\"bg-white p-4 sm:p-8\">\r\n    <div class=\"max-w-6xl mx-auto\">\r\n        <!-- Removed Logo section as requested\r\n        \r\n        <h1 class=\"text-3xl sm:text-4xl font-extrabold text-blue-700 mb-6 text-center\">\r\n            J-Cred G2C Municipal Insights Portal\r\n        <\/h1>\r\n        <p class=\"text-lg text-center text-gray-600 mb-8\">\r\n            Leveraging National Treasury data to highlight financial challenges and the impact of J-Cred G2C's solutions.\r\n        <\/p> -->\r\n\r\n        <!-- Landing Page Section -->\r\n        <div id=\"landing-page\" class=\"bg-white rounded-lg shadow-xl p-8 sm:p-12 text-center mb-8\">\r\n            <h2 class=\"text-2xl sm:text-3xl font-bold text-gray-800 mb-6\">\r\n                Unlocking Municipal Financial Health  Insights Leveraging National Treasury Data.\r\n            <\/h2>\r\n            <p class=\"text-lg text-gray-700 mb-8\">\r\n                This is a window into the financial landscape of South African municipalities. By leveraging official National Treasury data, you can gain critical insights into revenue collection challenges, outstanding consumer debt, audit outcomes, and the potential for significant financial recovery. Understand key areas for improvement and how data-driven strategies can enhance compliance and service delivery.\r\n            <\/p>\r\n            <!-- Moved Get Started button to the top of the landing page -->\r\n            <button id=\"get-started-button\" class=\"bg-blue-600 text-white font-bold py-3 px-8 rounded-full shadow-lg hover:bg-blue-700 transition duration-300 mb-10\">\r\n                Get Started: Explore Municipal Insights\r\n            <\/button>\r\n\r\n            <!-- Overview of SA's Economic Landscape -->\r\n            <div class=\"mt-10 mb-10 text-left\">\r\n                <h3 class=\"text-xl font-bold text-gray-800 mb-4 border-b pb-2\">South Africa's Financial Landscape at a Glance<\/h3>\r\n                <p class=\"text-gray-700 mb-2\">\r\n                    According to the **National Treasury's latest Local Government Revenue and Expenditure Report (as of March 31, 2025)**, the aggregate municipal consumer debt in South Africa has escalated to a staggering **R416.1 billion**. This represents a significant increase from **R347.6 billion** reported in the same period of 2023\/24. Approximately **72%** of this debt is primarily composed of household debt, a substantial portion of which often needs to be written off.\r\n                <\/p>\r\n                <p class=\"text-gray-700\">\r\n                    The Auditor-General continues to highlight recurring audit findings related to poor revenue management, inadequate debt collection, and non-compliance in indigent household management across municipalities, underscoring critical areas for financial intervention and improved governance.\r\n                <\/p>\r\n                <h4 class=\"text-lg font-bold text-gray-800 mt-6 mb-2\">Aggregate Consumer Debt Trend (R Billions)<\/h4>\r\n                <div id=\"aggregate-debt-graph\" class=\"ascii-graph text-sm\">\r\n                    <!-- Graph will be inserted here by JavaScript -->\r\n                <\/div>\r\n                <div id=\"aggregate-debt-graph-loading\" class=\"hidden flex justify-center items-center py-4\">\r\n                    <div class=\"spinner rounded-full h-8 w-8 border-b-2 border-blue-500\"><\/div>\r\n                    <p class=\"ml-3 text-gray-600 text-sm\">Loading aggregate debt trend...<\/p>\r\n                <\/div>\r\n                <div id=\"aggregate-debt-graph-error\" class=\"hidden bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative text-sm\" role=\"alert\">\r\n                    <strong class=\"font-bold\">Error:<\/strong>\r\n                    <span id=\"aggregate-debt-error-text\" class=\"block sm:inline ml-2\"><\/span>\r\n                <\/div>\r\n            <\/div>\r\n\r\n            <!-- Key Municipalities Snapshot -->\r\n            <div class=\"mt-10 mb-10 text-left\">\r\n                <h3 class=\"text-xl font-bold text-gray-800 mb-4 border-b pb-2\">Key Municipalities: Snapshot of Financial Health (FY <span id=\"snapshot-year\"><\/span>)<\/h3>\r\n                <div id=\"snapshot-loading-spinner\" class=\"hidden flex justify-center items-center py-4\">\r\n                    <div class=\"spinner rounded-full h-8 w-8 border-b-2 border-blue-500\"><\/div>\r\n                    <p class=\"ml-3 text-gray-600 text-sm\">Loading snapshot data...<\/p>\r\n                <\/div>\r\n                <div id=\"snapshot-error-message\" class=\"hidden bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative text-sm\" role=\"alert\">\r\n                    <strong class=\"font-bold\">Error:<\/strong>\r\n                    <span id=\"snapshot-error-text\" class=\"block sm:inline ml-2\"><\/span>\r\n                <\/div>\r\n                <div id=\"municipalities-snapshot\" class=\"grid grid-cols-1 md:grid-cols-2 gap-4\">\r\n                    <!-- Dynamic snapshot cards will go here -->\r\n                <\/div>\r\n                <p class=\"text-sm text-gray-500 mt-4\">\r\n                    *This is a snapshot for illustrative purposes. Select 'Get Started' to explore comprehensive data for all municipalities and financial years.\r\n                <\/p>\r\n            <\/div>\r\n            \r\n            <!-- Removed Get Started button from here -->\r\n        <\/div>\r\n\r\n        <!-- Main Portal Content Section (Initially Hidden) -->\r\n        <div id=\"main-portal-content\" class=\"hidden\">\r\n            <!-- Back to Home Button -->\r\n            <div class=\"text-left mb-4\">\r\n                <button id=\"back-to-home-button\" class=\"bg-gray-200 text-gray-800 font-semibold py-2 px-4 rounded-full shadow-md hover:bg-gray-300 transition duration-300\">\r\n                    &larr; Back to Home\r\n                <\/button>\r\n            <\/div>\r\n\r\n            <!-- Loading Spinner and Error Message Container -->\r\n            <div id=\"status-messages\" class=\"text-center mb-6\">\r\n                <div id=\"loading-spinner\" class=\"hidden flex justify-center items-center py-8\">\r\n                    <div class=\"spinner rounded-full h-12 w-12 border-b-2 border-blue-500\"><\/div>\r\n                    <p class=\"ml-4 text-gray-600\">Loading data...<\/p>\r\n                <\/div>\r\n                <div id=\"error-message\" class=\"hidden bg-red-100 border border-red-400 text-red-700 px-4 py-3 rounded relative\" role=\"alert\">\r\n                    <strong class=\"font-bold\">Error:<\/strong>\r\n                    <span id=\"error-text\" class=\"block sm:inline ml-2\"><\/span>\r\n                <\/div>\r\n            <\/div>\r\n\r\n            <!-- Year Selector -->\r\n            <div class=\"mb-6\">\r\n                <label for=\"year-select\" class=\"block text-gray-700 text-sm font-bold mb-2\">\r\n                    Select Financial Year:\r\n                <\/label>\r\n                <select id=\"year-select\" class=\"shadow appearance-none border rounded-lg w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline focus:ring-2 focus:ring-blue-500\">\r\n                    <option value=\"\">-- Choose a Year --<\/option>\r\n                <\/select>\r\n            <\/div>\r\n\r\n            <!-- Municipality Selector -->\r\n            <div class=\"mb-6\">\r\n                <label for=\"municipality-select\" class=\"block text-gray-700 text-sm font-bold mb-2\">\r\n                    Select a Municipality:\r\n                <\/label>\r\n                <select id=\"municipality-select\" class=\"shadow appearance-none border rounded-lg w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline focus:ring-2 focus:ring-blue-500 disabled:bg-gray-200\" disabled>\r\n                    <option value=\"\">-- Choose a Municipality --<\/option>\r\n                <\/select>\r\n            <\/div>\r\n\r\n            <!-- Data Display Area -->\r\n            <div id=\"data-display-area\" class=\"grid grid-cols-1 md:grid-cols-2 gap-6\">\r\n                <!-- Cards will be dynamically inserted here by JavaScript -->\r\n            <\/div>\r\n\r\n            <!-- Removed Call to Action from main portal -->\r\n        <\/div>\r\n    <\/div>\r\n\r\n    <script>\r\n        \/\/ --- Constants and Utility Functions ---\r\n        const API_BASE_URL = 'https:\/\/municipaldata.treasury.gov.za\/api\/cubes\/';\r\n        const TIMEOUT_DURATION = 15000; \/\/ 15 seconds\r\n\r\n        \/\/ Hardcoded list of municipalities for stability\r\n        const initialMunicipalities = [\r\n            { demarcation_code: 'CPT', municipality_name: 'City of Cape Town', province_name: 'Western Cape' },\r\n            { demarcation_code: 'JHB', municipality_name: 'City of Johannesburg', province_name: 'Gauteng' },\r\n            { demarcation_code: 'TSH', municipality_name: 'City of Tshwane', province_name: 'Gauteng' },\r\n            { demarcation_code: 'EKU', municipality_name: 'Ekurhuleni', province_name: 'Gauteng' },\r\n            { demarcation_code: 'ETH', municipality_name: 'eThekwini', province_name: 'KwaZulu-Natal' },\r\n            { demarcation_code: 'BUF', municipality_name: 'Buffalo City', province_name: 'Eastern Cape' },\r\n            { demarcation_code: 'MAN', municipality_name: 'Mangaung', province_name: 'Free State' },\r\n            { demarcation_code: 'NMA', municipality_name: 'Nelson Mandela Bay', province_name: 'Eastern Cape' },\r\n            { demarcation_code: 'DC1', municipality_name: 'Alfred Nzo', province_name: 'Eastern Cape' }, \/\/ Example District Municipality\r\n            { demarcation_code: 'GT423', municipality_name: 'Mogale City', province_name: 'Gauteng' }, \/\/ Example Local Municipality\r\n            { demarcation_code: 'LIM343', municipality_name: 'Polokwane', province_name: 'Limpopo' }, \/\/ Example for Limpopo\r\n            { demarcation_code: 'MP302', municipality_name: 'Mbombela', province_name: 'Mpumalanga' }, \/\/ Example for Mpumalanga\r\n            { demarcation_code: 'NC082', municipality_name: 'Sol Plaatje', province_name: 'Northern Cape' }, \/\/ Example for Northern Cape\r\n            { demarcation_code: 'NW372', municipality_name: 'Rustenburg', province_name: 'North West' }, \/\/ Example for North West\r\n            { demarcation_code: 'WC022', municipality_name: 'Stellenbosch', province_name: 'Western Cape' }, \/\/ Example for Western Cape\r\n        ].sort((a, b) => a.municipality_name.localeCompare(b.municipality_name));\r\n\r\n        \/\/ Function to fetch data from the Municipal Money API with a timeout\r\n        async function fetchData(endpoint, params = {}) {\r\n            const queryParams = new URLSearchParams(params).toString();\r\n            const url = `${API_BASE_URL}${endpoint}?${queryParams}`;\r\n\r\n            const controller = new AbortController();\r\n            const id = setTimeout(() => controller.abort(), TIMEOUT_DURATION);\r\n\r\n            try {\r\n                const response = await fetch(url, { signal: controller.signal });\r\n                clearTimeout(id);\r\n\r\n                if (!response.ok) {\r\n                    const errorText = await response.text();\r\n                    \/\/ Explicitly log 500 errors to indicate server-side issue\r\n                    if (response.status === 500) {\r\n                        console.error(`API Server Error (500) for ${endpoint}:`, errorText);\r\n                        throw new Error(`API Server Error (500) for ${endpoint}. Please try again later or select a different year\/municipality.`);\r\n                    }\r\n                    throw new Error(`HTTP error! status: ${response.status} - ${response.statusText || errorText}`);\r\n                }\r\n                const data = await response.json();\r\n                return data.data || data; \r\n            }\r\n            catch (error) {\r\n                clearTimeout(id);\r\n                if (error.name === 'AbortError') {\r\n                    console.warn(`Fetch to ${endpoint} timed out after ${TIMEOUT_DURATION \/ 1000} seconds.`);\r\n                    return null; \/\/ Return null on timeout\r\n                }\r\n                console.error(`Error fetching data from ${endpoint}:`, error);\r\n                return null;\r\n            }\r\n        }\r\n\r\n        \/\/ Helper to get financial years for the last N years\r\n        function getLastNYears(n) {\r\n            const currentCalendarYear = new Date().getFullYear();\r\n            const currentMonth = new Date().getMonth() + 1; \/\/ 1-indexed\r\n\r\n            const years = [];\r\n            \/\/ If current month is July (7) or later, the financial year ending this calendar year (e.g., June 2025) might have data.\r\n            \/\/ Otherwise, the latest *full* financial year would be previous calendar year (e.g., June 2024).\r\n            const latestPossibleFYEndYear = (currentMonth >= 7) ? currentCalendarYear : currentCalendarYear - 1;\r\n\r\n            for (let i = 0; i < n; i++) {\r\n                years.push(latestPossibleFYEndYear - i);\r\n            }\r\n            return years;\r\n        }\r\n\r\n        \/\/ --- DOM Elements ---\r\n        const landingPage = document.getElementById('landing-page');\r\n        const mainPortalContent = document.getElementById('main-portal-content');\r\n        const getStartedButton = document.getElementById('get-started-button');\r\n        const backToHomeButton = document.getElementById('back-to-home-button'); \/\/ New button element\r\n\r\n        const yearSelect = document.getElementById('year-select');\r\n        const municipalitySelect = document.getElementById('municipality-select');\r\n        const loadingSpinner = document.getElementById('loading-spinner');\r\n        const errorMessageDiv = document.getElementById('error-message');\r\n        const errorTextSpan = document.getElementById('error-text');\r\n        const dataDisplayArea = document.getElementById('data-display-area');\r\n\r\n        \/\/ Snapshot specific elements\r\n        const snapshotYearSpan = document.getElementById('snapshot-year');\r\n        const snapshotLoadingSpinner = document.getElementById('snapshot-loading-spinner');\r\n        const snapshotErrorMessageDiv = document.getElementById('snapshot-error-message');\r\n        const snapshotErrorTextSpan = document.getElementById('snapshot-error-text');\r\n        const municipalitiesSnapshotDiv = document.getElementById('municipalities-snapshot');\r\n        const aggregateDebtGraphDiv = document.getElementById('aggregate-debt-graph');\r\n        const aggregateDebtGraphLoading = document.getElementById('aggregate-debt-graph-loading');\r\n        const aggregateDebtGraphError = document.getElementById('aggregate-debt-graph-error');\r\n        const aggregateDebtErrorText = document.getElementById('aggregate-debt-error-text');\r\n\r\n\r\n        let selectedYear = null;\r\n        let selectedMunicipalityCode = null;\r\n        let municipalityData = null;\r\n\r\n        \/\/ --- State Management Functions ---\r\n        function showLoading() {\r\n            loadingSpinner.classList.remove('hidden');\r\n            errorMessageDiv.classList.add('hidden');\r\n            dataDisplayArea.innerHTML = ''; \/\/ Clear previous data\r\n        }\r\n\r\n        function hideLoading() {\r\n            loadingSpinner.classList.add('hidden');\r\n        }\r\n\r\n        function showErrors(message) {\r\n            errorMessageDiv.classList.remove('hidden');\r\n            errorTextSpan.textContent = message;\r\n            hideLoading();\r\n        }\r\n\r\n        function hideErrors() {\r\n            errorMessageDiv.classList.add('hidden');\r\n        }\r\n\r\n        \/\/ --- Data Fetching and Display Logic ---\r\n\r\n        \/\/ Populate Year Selector\r\n        function populateYearSelector() {\r\n            const years = getLastNYears(5);\r\n            \/\/ Clear existing options except the first one\r\n            while (yearSelect.options.length > 1) {\r\n                yearSelect.remove(1);\r\n            }\r\n            years.forEach(year => {\r\n                const option = document.createElement('option');\r\n                option.value = year;\r\n                option.textContent = `FY ${year}`;\r\n                yearSelect.appendChild(option);\r\n            });\r\n        }\r\n\r\n        \/\/ Populate Municipality Selector\r\n        function populateMunicipalitySelector() {\r\n            \/\/ Clear existing options except the first one\r\n            while (municipalitySelect.options.length > 1) {\r\n                municipalitySelect.remove(1);\r\n            }\r\n\r\n            initialMunicipalities.forEach(muni => {\r\n                if (muni.demarcation_code) {\r\n                    const option = document.createElement('option');\r\n                    option.value = muni.demarcation_code;\r\n                    option.textContent = `${muni.municipality_name} (${muni.province_name})`;\r\n                    municipalitySelect.appendChild(option);\r\n                }\r\n            });\r\n            municipalitySelect.disabled = false;\r\n        }\r\n\r\n        \/\/ Fetch detailed data for selected municipality and year\r\n        async function fetchAndDisplayMunicipalityDetails() {\r\n            if (!selectedMunicipalityCode || !selectedYear) {\r\n                dataDisplayArea.innerHTML = '';\r\n                return;\r\n            }\r\n\r\n            showLoading();\r\n            hideErrors();\r\n            municipalityData = {\r\n                agedDebt: {},\r\n                auditOutcomes: {},\r\n                uifw: {},\r\n                revenue: {},\r\n                municipalityInfo: initialMunicipalities.find(m => m.demarcation_code === selectedMunicipalityCode)\r\n            };\r\n\r\n            const yearsForDetails = [selectedYear, selectedYear - 1, selectedYear - 2].filter(y => y > 0);\r\n            const fetchPromises = [];\r\n\r\n            \/\/ Aged Debtor Analysis\r\n            fetchPromises.push(\r\n                fetchData('aged_debtor_analysis_v2\/facts', {\r\n                    cut: `demarcation_code:${selectedMunicipalityCode}|financial_year_end.year:${selectedYear}`,\r\n                    drilldown: 'item.label',\r\n                })\r\n                .then(result => {\r\n                    if (result) {\r\n                        municipalityData.agedDebt[selectedYear] = result.reduce((acc, item) => {\r\n                            acc[item.item_label] = Number(item.amount) || 0; \/\/ Ensure number conversion here\r\n                            return acc;\r\n                        }, {});\r\n                    }\r\n                })\r\n                .catch(err => {\r\n                    console.error(\"Aged Debt Fetch Error:\", err);\r\n                    \/\/ Don't rethrow, just log and let Promise.allSettled handle the status\r\n                })\r\n            );\r\n\r\n            \/\/ Audit Opinions for selected year and previous two\r\n            yearsForDetails.forEach(year => {\r\n                fetchPromises.push(\r\n                    fetchData('audit_opinions\/facts', {\r\n                        cut: `demarcation_code:${selectedMunicipalityCode}|financial_year_end.year:${year}`,\r\n                        drilldown: 'opinion.label',\r\n                    })\r\n                    .then(result => {\r\n                        if (result && result.length > 0) {\r\n                            municipalityData.auditOutcomes[year] = result[0].opinion_label;\r\n                        }\r\n                    })\r\n                    .catch(err => {\r\n                        console.error(`Audit Opinion FY ${year} Fetch Error:`, err);\r\n                    })\r\n                );\r\n            });\r\n\r\n            \/\/ UIFW Expenditure for selected year and previous two\r\n            yearsForDetails.forEach(year => {\r\n                fetchPromises.push(\r\n                    fetchData('uifw_expenditure\/facts', {\r\n                        cut: `demarcation_code:${selectedMunicipalityCode}|financial_year_end.year:${year}`,\r\n                        drilldown: 'item.label',\r\n                    })\r\n                    .then(result => {\r\n                        if (result) {\r\n                            municipalityData.uifw[year] = result.reduce((acc, item) => {\r\n                                acc[item.item_label] = Number(item.amount) || 0; \/\/ Ensure number conversion here\r\n                                return acc;\r\n                            }, {});\r\n                        }\r\n                    })\r\n                    .catch(err => {\r\n                        console.error(`UIFW FY ${year} Fetch Error:`, err);\r\n                    })\r\n                );\r\n            });\r\n\r\n            \/\/ Revenue (Total Operating Revenue) for selected year and previous two\r\n            yearsForDetails.forEach(year => {\r\n                fetchPromises.push(\r\n                    fetchData('income_expenditure_v2\/facts', {\r\n                        cut: `demarcation_code:${selectedMunicipalityCode}|financial_year_end.year:${year}|item.code:4000`,\r\n                        drilldown: 'item.label',\r\n                    })\r\n                    .then(result => {\r\n                        if (result && result.length > 0) {\r\n                            municipalityData.revenue[year] = Number(result[0].amount) || 0; \/\/ Ensure number conversion here\r\n                        }\r\n                    })\r\n                    .catch(err => {\r\n                        console.error(`Revenue FY ${year} Fetch Error:`, err);\r\n                    })\r\n                );\r\n            });\r\n\r\n            \/\/ Using Promise.allSettled to ensure all promises are resolved (either fulfilled or rejected)\r\n            \/\/ before proceeding, allowing some data to load even if others fail.\r\n            const results = await Promise.allSettled(fetchPromises);\r\n            \r\n            const rejectedPromises = results.filter(result => result.status === 'rejected');\r\n            if (rejectedPromises.length > 0) {\r\n                \/\/ Display a general error message for any failed fetches\r\n                showErrors(`Some data could not be loaded for FY ${selectedYear}. Please try a different year or municipality. Details: ${rejectedPromises[0].reason.message}`);\r\n            } else {\r\n                hideErrors();\r\n            }\r\n\r\n            renderDataCards();\r\n            hideLoading();\r\n        }\r\n\r\n        \/\/ --- Calculations for J-Cred Impact Projection ---\r\n        function calculateJCredImpact() {\r\n            if (!municipalityData || !municipalityData.agedDebt || !selectedYear) return null;\r\n\r\n            const latestYearData = municipalityData.agedDebt[selectedYear]; \r\n            if (!latestYearData) return null;\r\n\r\n            const totalCurrentDebt = Number(latestYearData['Total consumer debtors']) || 0; \/\/ Ensure number\r\n            const debtOver90Days = Number(latestYearData['Consumer debtors - more than 90 days']) || 0; \/\/ Ensure number\r\n\r\n            if (totalCurrentDebt === 0) return null;\r\n\r\n            \/\/ Hypothetical recovery rates for demonstration\r\n            const recoveryRateOverall = 0.05; \/\/ 5% of total debt\r\n            const recoveryRate90Plus = 0.15; \/\/ 15% of 90+ day debt\r\n\r\n            const potentialRecoveryOverall = totalCurrentDebt * recoveryRateOverall;\r\n            const potentialRecovery90Plus = debtOver90Days * recoveryRate90Plus;\r\n\r\n            \/\/ Use the higher of the two or a combined approach for projection\r\n            const projectedRecovery = Math.max(potentialRecoveryOverall, potentialRecovery90Plus);\r\n\r\n            \/\/ Calculate debt growth rate (simple for last 2 years relative to selected year)\r\n            const yearMinus1 = selectedYear - 1;\r\n            const yearMinus2 = selectedYear - 2;\r\n            const debtYearMinus1 = Number(municipalityData.agedDebt[yearMinus1]?.['Total consumer debtors']) || 0; \/\/ Ensure number\r\n            const debtYearMinus2 = Number(municipalityData.agedDebt[yearMinus2]?.['Total consumer debtors']) || 0; \/\/ Ensure number\r\n\r\n            let growthRate = 'N\/A';\r\n            if (debtYearMinus2 > 0 && debtYearMinus1 > 0) {\r\n                growthRate = (((debtYearMinus1 - debtYearMinus2) \/ debtYearMinus2) * 100).toFixed(2) + '%';\r\n            } else if (debtYearMinus1 > 0 && debtYearMinus2 === 0) {\r\n                growthRate = 'New Debt (from zero)';\r\n            } else if (debtYearMinus1 === 0 && debtYearMinus2 > 0) {\r\n                growthRate = 'Debt Reduced to Zero';\r\n            }\r\n\r\n\r\n            return {\r\n                totalCurrentDebt: totalCurrentDebt,\r\n                debtOver90Days: debtOver90Days,\r\n                projectedRecovery: projectedRecovery,\r\n                growthRate: growthRate\r\n            };\r\n        }\r\n\r\n        \/\/ --- Rendering Data Cards ---\r\n        function renderDataCards() {\r\n            dataDisplayArea.innerHTML = ''; \/\/ Clear existing cards\r\n\r\n            const jCredImpact = calculateJCredImpact();\r\n            const muniInfo = municipalityData.municipalityInfo;\r\n            const yearsForDisplay = [selectedYear, selectedYear - 1, selectedYear - 2].filter(y => y > 0);\r\n\r\n            \/\/ Helper to create a DataCard HTML string\r\n            const createDataCardHTML = (title, contentHTML) => `\r\n                <div class=\"bg-white rounded-lg shadow-md p-6 mb-6\">\r\n                    <h3 class=\"text-xl font-semibold text-gray-800 mb-4 border-b pb-2\">${title}<\/h3>\r\n                    ${contentHTML}\r\n                <\/div>\r\n            `;\r\n\r\n            \/\/ Helper function to safely format numbers\r\n            const formatNumber = (value) => {\r\n                return typeof value === 'number' ? value.toLocaleString('en-ZA') : 'N\/A';\r\n            };\r\n\r\n            \/\/ Municipality Overview Card\r\n            let overviewContent = `\r\n                <p class=\"text-gray-700\"><span class=\"font-semibold\">Name:<\/span> ${muniInfo?.municipality_name || 'N\/A'}<\/p>\r\n                <p class=\"text-gray-700\"><span class=\"font-semibold\">Province:<\/span> ${muniInfo?.province_name || 'N\/A'}<\/p>\r\n                <p class=\"text-gray-700\"><span class=\"font-semibold\">Demarcation Code:<\/span> ${muniInfo?.demarcation_code || 'N\/A'}<\/p>\r\n                <p class=\"text-sm text-gray-500 mt-2\">*Data sourced from National Treasury's Municipal Money API. Data displayed for FY ending ${selectedYear}.<\/p>\r\n            `;\r\n            dataDisplayArea.innerHTML += createDataCardHTML(\"Municipality Overview\", overviewContent);\r\n\r\n            \/\/ Revenue Leakage Card\r\n            let revenueLeakageContent = '';\r\n            if (jCredImpact) {\r\n                revenueLeakageContent = `\r\n                    <p class=\"text-gray-700 mb-2\">\r\n                        <span class=\"font-semibold\">Total Outstanding Consumer Debt (FY ${selectedYear}):<\/span>\r\n                        <span class=\"text-red-600 font-bold text-lg\">R${formatNumber(jCredImpact.totalCurrentDebt)}<\/span>\r\n                    <\/p>\r\n                    <p class=\"text-gray-700 mb-2\">\r\n                        <span class=\"font-semibold\">Debt Over 90 Days (FY ${selectedYear}):<\/span>\r\n                        <span class=\"text-orange-600 font-bold text-lg\">R${formatNumber(jCredImpact.debtOver90Days)}<\/span>\r\n                    <\/p>\r\n                    <p class=\"text-gray-700 mb-4\">\r\n                        <span class=\"font-semibold\">Debt Growth (Last 2 years):<\/span> ${jCredImpact.growthRate}\r\n                    <\/p>\r\n                    <h4 class=\"font-semibold text-gray-800 mt-4 mb-2\">Debt Ageing Breakdown (FY ${selectedYear}):<\/h4>\r\n                    ${Object.keys(municipalityData.agedDebt[selectedYear] || {}).length > 0 ?\r\n                        Object.entries(municipalityData.agedDebt[selectedYear]).filter(([key]) => key !== 'Total consumer debtors').map(([key, value]) => `\r\n                            <p class=\"text-sm text-gray-600\">- ${key}: R${formatNumber(value)}<\/p>\r\n                        `).join('')\r\n                        : '<p class=\"text-sm text-gray-600\">No detailed ageing data available for this year.<\/p>'\r\n                    }\r\n                    <p class=\"mt-4 text-gray-700 text-sm\">\r\n                        **Insight:** High levels of aged debt indicate significant revenue leakage, directly impacting service delivery and financial sustainability.\r\n                    <\/p>\r\n                `;\r\n            } else {\r\n                revenueLeakageContent = `<p class=\"text-gray-600\">No aged debtor data available for FY ${selectedYear}.<\/p>`;\r\n            }\r\n            dataDisplayArea.innerHTML += createDataCardHTML(\"Revenue Leakage: Outstanding Consumer Debt\", revenueLeakageContent);\r\n\r\n            \/\/ Audit Outcomes Card\r\n            let auditContent = yearsForDisplay.map(year => `\r\n                <p class=\"text-gray-700 mb-2\">\r\n                    <span class=\"font-semibold\">FY ${year}:<\/span>\r\n                    <span class=\"font-bold ${municipalityData.auditOutcomes[year]?.includes('Unqualified') ? 'text-green-600' : 'text-red-600'}\">\r\n                        ${municipalityData.auditOutcomes[year] || 'N\/A'}\r\n                    <\/span>\r\n                <\/p>\r\n            `).join('');\r\n            if (yearsForDisplay.every(year => !municipalityData.auditOutcomes[year])) {\r\n                auditContent += '<p class=\"text-sm text-gray-600\">No audit opinion data available for the selected years.<\/p>';\r\n            }\r\n            auditContent += `\r\n                <p class=\"mt-4 text-gray-700 text-sm\">\r\n                    **Insight:** Consistent findings on financial management and compliance often stem from inadequate revenue collection and indigent management processes. J-Cred G2C provides the controls the AG seeks.\r\n                <\/p>\r\n            `;\r\n            dataDisplayArea.innerHTML += createDataCardHTML(\"Audit Outcomes & Compliance\", auditContent);\r\n\r\n            \/\/ UIFW Expenditure Card\r\n            let uifwContent = yearsForDisplay.map(year => {\r\n                \/\/ Ensure each part of the sum is a number before adding\r\n                const unauthorised = Number(municipalityData.uifw[year]?.['Total unauthorised expenditure']) || 0;\r\n                const irregular = Number(municipalityData.uifw[year]?.['Total irregular expenditure']) || 0;\r\n                const fruitless = Number(municipalityData.uifw[year]?.['Total fruitless and wasteful expenditure']) || 0;\r\n                \r\n                const totalUIFW = unauthorised + irregular + fruitless;\r\n\r\n                return `\r\n                    <p class=\"text-gray-700 mb-2\">\r\n                        <span class=\"font-semibold\">FY ${year} Total UIFW:<\/span>\r\n                        <span class=\"text-red-600 font-bold\">R${formatNumber(totalUIFW)}<\/span>\r\n                    <\/p>\r\n                `;\r\n            }).join('');\r\n            if (yearsForDisplay.every(year => !municipalityData.uifw[year])) {\r\n                uifwContent += '<p class=\"text-sm text-gray-600\">No UIFW expenditure data available for the selected years.<\/p>';\r\n            }\r\n            uifwContent += `\r\n                <p class=\"mt-4 text-gray-700 text-sm\">\r\n                    **Insight:** High UIFW expenditure often points to weak internal controls and non-compliance, which our solution directly addresses.\r\n                <\/p>\r\n            `;\r\n            dataDisplayArea.innerHTML += createDataCardHTML(\"Unauthorised, Irregular, Fruitless & Wasteful (UIFW) Expenditure\", uifwContent);\r\n\r\n            \/\/ Indigent Insights Card\r\n            let indigentContent = `\r\n                <p class=\"text-gray-700 mb-4\">\r\n                    While specific indigent household numbers are not publicly available via API due to privacy, the national challenge of inaccurate and outdated indigent registers leads to significant irregular expenditure and inequitable service allocation.\r\n                <\/p>\r\n                <p class=\"text-gray-700 text-sm\">\r\n                    **J-Cred G2C Solution:** Our platform automates the accurate identification and verification of indigent households, ensuring only deserving beneficiaries receive free basic services. This reduces fraud, improves compliance, and strengthens social responsibility.\r\n                <\/p>\r\n            `;\r\n            dataDisplayArea.innerHTML += createDataCardHTML(\"Indigent Management & Equity\", indigentContent);\r\n\r\n            \/\/ J-Cred Impact Projection Card\r\n            let impactContent = '';\r\n            if (jCredImpact) {\r\n                impactContent = `\r\n                    <p class=\"text-gray-700 mb-2\">\r\n                        <span class=\"font-semibold\">Current Total Outstanding Debt (FY ${selectedYear}):<\/span>\r\n                        <span class=\"text-blue-700 font-bold text-lg\">R${formatNumber(jCredImpact.totalCurrentDebt)}<\/span>\r\n                    <\/p>\r\n                    <p class=\"text-gray-700 mb-4\">\r\n                        <span class=\"font-semibold\">Projected Revenue Recovery with J-Cred G2C (estimated):<\/span>\r\n                        <span class=\"text-green-600 font-bold text-xl\">R${formatNumber(jCredImpact.projectedRecovery)}<\/span>\r\n                        <span class=\"text-sm text-gray-500\">(based on a conservative recovery rate)<\/span>\r\n                    <\/p>\r\n                    <p class=\"mt-4 text-gray-700 text-sm\">\r\n                        **This projected recovery represents direct funds that can be reinvested into critical service delivery, infrastructure, and community programs.**\r\n                    <\/p>\r\n                    <p class=\"mt-2 text-gray-700 text-sm\">\r\n                        **How:** By strategically targeting collectable debt using our intelligent profiling, automating compliant workflows, and improving overall financial controls.\r\n                    <\/p>\r\n                `;\r\n            } else {\r\n                impactContent = `<p class=\"text-gray-600\">No aged debtor data available for FY ${selectedYear} to calculate projection.<\/p>`;\r\n            }\r\n            dataDisplayArea.innerHTML += createDataCardHTML(\"J-Cred G2C: Your Potential for Revenue Recovery\", impactContent);\r\n        }\r\n\r\n        \/\/ --- Event Listeners ---\r\n        yearSelect.addEventListener('change', (event) => {\r\n            selectedYear = parseInt(event.target.value);\r\n            \/\/ No need to populate municipalities again, as it's hardcoded\r\n            fetchAndDisplayMunicipalityDetails(); \/\/ Fetch data for the new year\r\n        });\r\n\r\n        municipalitySelect.addEventListener('change', (event) => {\r\n            selectedMunicipalityCode = event.target.value;\r\n            fetchAndDisplayMunicipalityDetails();\r\n        });\r\n\r\n        \/\/ --- Initial Setup ---\r\n        document.addEventListener('DOMContentLoaded', () => {\r\n            \/\/ Initially show the landing page\r\n            landingPage.classList.remove('hidden');\r\n            mainPortalContent.classList.add('hidden');\r\n\r\n            \/\/ Add event listener for the Get Started button\r\n            getStartedButton.addEventListener('click', () => {\r\n                landingPage.classList.add('hidden');\r\n                mainPortalContent.classList.remove('hidden');\r\n                populateYearSelector();\r\n                populateMunicipalitySelector(); \/\/ Populate initially with all hardcoded municipalities\r\n                \/\/ Automatically select the latest year when entering the main portal\r\n                if (yearSelect.options.length > 1) {\r\n                    yearSelect.value = getLastNYears(1)[0]; \/\/ Selects the most recent FY\r\n                    yearSelect.dispatchEvent(new Event('change')); \/\/ Trigger change to load data\r\n                }\r\n            });\r\n\r\n            \/\/ Add event listener for the Back to Home button\r\n            backToHomeButton.addEventListener('click', () => {\r\n                mainPortalContent.classList.add('hidden');\r\n                landingPage.classList.remove('hidden');\r\n                \/\/ Optionally reset selectors and data when going back to home\r\n                yearSelect.value = '';\r\n                municipalitySelect.value = '';\r\n                municipalitySelect.disabled = true;\r\n                dataDisplayArea.innerHTML = '';\r\n                selectedYear = null;\r\n                selectedMunicipalityCode = null;\r\n                municipalityData = null;\r\n                hideErrors();\r\n                hideLoading();\r\n            });\r\n\r\n            \/\/ Initial fetch for snapshot data on landing page load\r\n            populateSnapshotData();\r\n        });\r\n\r\n        \/\/ --- Snapshot Data Fetching and Display ---\r\n        async function populateSnapshotData() {\r\n            const snapshotYears = getLastNYears(1); \/\/ Get data for the latest FY only for snapshot\r\n            const latestSnapshotYear = snapshotYears[0];\r\n            snapshotYearSpan.textContent = latestSnapshotYear; \/\/ Update the year in the heading\r\n\r\n            aggregateDebtGraphLoading.classList.remove('hidden');\r\n            aggregateDebtGraphError.classList.add('hidden');\r\n            municipalitiesSnapshotDiv.innerHTML = ''; \/\/ Clear previous snapshot cards\r\n\r\n            \/\/ Hardcoded aggregate debt data from National Treasury reports\r\n            \/\/ R416.1 billion as of March 31, 2025 (FY 2025)\r\n            \/\/ R347.6 billion as of March 31, 2024 (FY 2024)\r\n            \/\/ R255.4 billion as of June 30, 2022 (FY 2022) - roughly March 2022\r\n            const hardcodedAggregateDebt = {\r\n                [latestSnapshotYear]: 416100000000, \/\/ R416.1 Billion for FY ending 2025\r\n                [latestSnapshotYear - 1]: 347600000000, \/\/ R347.6 Billion for FY ending 2024\r\n                [latestSnapshotYear - 2]: 255400000000 \/\/ Roughly R255.4 Billion for FY ending 2023 (using 2022 data as proxy)\r\n            };\r\n\r\n            \/\/ Hardcoded snapshot data for a few key municipalities for illustration\r\n            const hardcodedSnapshotMuniData = [\r\n                { demarcation_code: 'CPT', municipality_name: 'City of Cape Town', province_name: 'Western Cape', totalDebt: 15000000000, auditOpinion: 'Unqualified' }, \/\/ Example data\r\n                { demarcation_code: 'JHB', municipality_name: 'City of Johannesburg', province_name: 'Gauteng', totalDebt: 30000000000, auditOpinion: 'Qualified' }, \/\/ Example data\r\n                { demarcation_code: 'ETH', municipality_name: 'eThekwini', province_name: 'KwaZulu-Natal', totalDebt: 20000000000, auditOpinion: 'Unqualified with findings' }, \/\/ Example data\r\n                { demarcation_code: 'TSH', municipality_name: 'City of Tshwane', province_name: 'Gauteng', totalDebt: 25000000000, auditOpinion: 'Adverse' }, \/\/ Example data\r\n            ];\r\n\r\n\r\n            \/\/ Process aggregate debt results first\r\n            let graphContent = '';\r\n            const yearsInGraph = Object.keys(hardcodedAggregateDebt).sort(); \/\/ Sort years for graph display\r\n            const maxDebt = Math.max(...Object.values(hardcodedAggregateDebt).filter(val => typeof val === 'number' && !isNaN(val)), 1); \/\/ Avoid division by zero\r\n            const scaleFactor = 40 \/ maxDebt; \/\/ Scale to max 40 characters\r\n\r\n            \/\/ Improved ASCII Graph Generation\r\n            \/\/ Find max bar length for consistent width\r\n            let maxLabelLength = 0;\r\n            yearsInGraph.forEach(year => {\r\n                const label = `FY ${year}`;\r\n                if (label.length > maxLabelLength) {\r\n                    maxLabelLength = label.length;\r\n                }\r\n            });\r\n\r\n            \/\/ Add a header for the graph\r\n            graphContent += `Debt (R Billions)\\n`;\r\n            graphContent += `\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\\n`; \/\/ Separator\r\n\r\n            yearsInGraph.forEach(year => {\r\n                const debt = hardcodedAggregateDebt[year];\r\n                const formattedDebt = formatNumber(debt \/ 1000000000); \/\/ Convert to Billions\r\n                const label = `FY ${year}`;\r\n                const paddedLabel = label.padEnd(maxLabelLength + 2); \/\/ Pad for alignment\r\n\r\n                if (typeof debt === 'number' && !isNaN(debt) && debt > 0) {\r\n                    const barLength = Math.round(debt * scaleFactor);\r\n                    \/\/ Use different block characters for a more solid bar\r\n                    const bar = '\u2588'.repeat(Math.max(1, barLength)); \r\n                    graphContent += `${paddedLabel} \u2502 ${bar} R${formattedDebt}B\\n`;\r\n                } else {\r\n                    graphContent += `${paddedLabel} \u2502 No Data\\n`;\r\n                }\r\n            });\r\n            graphContent += `\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\\n`; \/\/ Footer separator\r\n            \r\n            aggregateDebtGraphDiv.textContent = graphContent; \/\/ Use textContent for pre-formatted text\r\n            \r\n            \/\/ Hide loading\/error for aggregate graph since it's hardcoded\r\n            aggregateDebtGraphLoading.classList.add('hidden');\r\n            aggregateDebtGraphError.classList.add('hidden');\r\n\r\n\r\n            \/\/ Process individual municipality snapshot results (hardcoded)\r\n            let hasMuniSnapshotData = false;\r\n            municipalitiesSnapshotDiv.innerHTML = ''; \/\/ Clear previous snapshot cards\r\n            hardcodedSnapshotMuniData.forEach(muni => {\r\n                hasMuniSnapshotData = true;\r\n                const auditColorClass = muni.auditOpinion.includes('Unqualified') ? 'text-green-600' : 'text-red-600';\r\n                const cardHtml = `\r\n                    <div class=\"bg-gray-50 p-4 rounded-lg shadow-sm\">\r\n                        <h4 class=\"font-semibold text-gray-800\">${muni.municipality_name}<\/h4>\r\n                        <p class=\"text-sm text-gray-600\">${muni.province_name}<\/p>\r\n                        <p class=\"text-sm mt-2\">Debt: <span class=\"font-medium text-red-500\">R${formatNumber(muni.totalDebt)}<\/span><\/p>\r\n                        <p class=\"text-sm\">Audit: <span class=\"font-medium ${auditColorClass}\">${muni.auditOpinion}<\/span><\/p>\r\n                    <\/div>\r\n                `;\r\n                municipalitiesSnapshotDiv.innerHTML += cardHtml;\r\n            });\r\n\r\n            if (!hasMuniSnapshotData) {\r\n                snapshotErrorTextSpan.textContent = \"No snapshot data available for the selected year or municipalities. API might be unresponsive or data not yet published.\";\r\n                snapshotErrorMessageDiv.classList.remove('hidden');\r\n            } else {\r\n                snapshotErrorMessageDiv.classList.add('hidden');\r\n            }\r\n\r\n            snapshotLoadingSpinner.classList.add('hidden'); \/\/ Hide loading for snapshot cards\r\n        }\r\n\r\n        \/\/ Helper function to safely format numbers (defined globally for use in renderDataCards and snapshot)\r\n        const formatNumber = (value) => {\r\n            \/\/ Check if value is a valid number before formatting\r\n            if (typeof value === 'number' && !isNaN(value)) {\r\n                return value.toLocaleString('en-ZA');\r\n            }\r\n            return 'N\/A';\r\n        };\r\n\r\n    <\/script>\r\n<\/body>\r\n<\/html>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Municipal Overview \/ Insights https:\/\/youtu.be\/HeQiX_e8ubghttps:\/\/youtu.be\/WVZBVJTh0u0https:\/\/www.youtube.com\/watch?v=A15Fvwcx_OY J-Cred G2C Municipal Insights Portal Unlocking Municipal Financial Health Insights Leveraging National Treasury Data. This is a window into the financial landscape of South African municipalities. By leveraging official National Treasury data, you can gain critical insights into revenue collection challenges, outstanding consumer debt, audit outcomes, and the potential for significant financial recovery. Understand key areas for improvement and how data-driven strategies can enhance compliance and service delivery. Get Started: Explore Municipal Insights South Africa&#8217;s Financial Landscape at a Glance According to the **National Treasury&#8217;s latest Local Government Revenue and Expenditure Report (as of March 31, 2025)**, the aggregate municipal consumer debt in South Africa has escalated to a staggering **R416.1 billion**. This represents a significant increase from **R347.6 billion** reported in the same period of 2023\/24. Approximately **72%** of this debt is primarily composed of household debt, a substantial portion of which often needs to be written off. The Auditor-General continues to highlight recurring audit findings related to poor revenue management, inadequate debt collection, and non-compliance in indigent household management across municipalities, underscoring critical areas for financial intervention and improved governance. Aggregate Consumer Debt Trend (R Billions) Loading aggregate debt trend&#8230; Error: Key Municipalities: Snapshot of Financial Health (FY ) Loading snapshot data&#8230; Error: *This is a snapshot for illustrative purposes. Select &#8216;Get Started&#8217; to explore comprehensive data for all municipalities and financial years. &larr; Back to Home Loading data&#8230; Error: Select Financial Year: &#8212; Choose a Year &#8212; Select a Municipality: &#8212; Choose a Municipality &#8212;<\/p>","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"_eb_attr":"","footnotes":""},"class_list":["post-3210","page","type-page","status-publish","hentry"],"blocksy_meta":{"disable_header":"yes","styles_descriptor":{"styles":{"desktop":"","tablet":"","mobile":""},"google_fonts":[],"version":6}},"_links":{"self":[{"href":"https:\/\/j-cred.co.za\/zh\/wp-json\/wp\/v2\/pages\/3210","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/j-cred.co.za\/zh\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/j-cred.co.za\/zh\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/j-cred.co.za\/zh\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/j-cred.co.za\/zh\/wp-json\/wp\/v2\/comments?post=3210"}],"version-history":[{"count":62,"href":"https:\/\/j-cred.co.za\/zh\/wp-json\/wp\/v2\/pages\/3210\/revisions"}],"predecessor-version":[{"id":3414,"href":"https:\/\/j-cred.co.za\/zh\/wp-json\/wp\/v2\/pages\/3210\/revisions\/3414"}],"wp:attachment":[{"href":"https:\/\/j-cred.co.za\/zh\/wp-json\/wp\/v2\/media?parent=3210"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}