diff --git a/assets/js/embeds/chart-bubbles/index.js b/assets/js/embeds/chart-bubbles/index.js new file mode 100644 index 000000000..b154ab09b --- /dev/null +++ b/assets/js/embeds/chart-bubbles/index.js @@ -0,0 +1,161 @@ +// Get JSON data from data-public-entities-bubbles attribute +const data = JSON.parse( + document.getElementById("vis").attributes["data-public-entities-bubbles"] + .value +); + +function bubbleChart() { + const width = 300; + const height = 300; + + // location to centre the bubbles + const centre = { x: width / 2, y: height / 2 }; + + // strength to apply to the position forces + const forceStrength = 0.03; + + // these will be set in createNodes and chart functions + let svg = null; + let bubbles = null; + let nodes = []; + + // charge is dependent on size of the bubble, so bigger towards the middle + function charge(d) { + return Math.pow(d.radius, 2.0) * 0.01; + } + + // create a force simulation and add forces to it + const simulation = d3 + .forceSimulation() + .force("charge", d3.forceManyBody().strength(charge)) + .force("center", d3.forceCenter(centre.x, centre.y)) + .force( + "x", + d3 + .forceX() + .strength(forceStrength) + .x(centre.x) + ) + .force( + "y", + d3 + .forceY() + .strength(forceStrength) + .y(centre.y) + ) + .force( + "collision", + d3.forceCollide().radius((d) => d.radius + 1) + ); + + // force simulation starts up automatically, which we don't want as there aren't any nodes yet + simulation.stop(); + + // set up colour scale + const fillColour = d3 + .scaleOrdinal() + .domain([1, 2]) + .range(["#76b643", "#ed9e31"]); + + // data manipulation function takes raw data from csv and converts it into an array of node objects + // each node will store data and visualisation values to draw a bubble + // rawData is expected to be an array of data objects, read in d3.csv + // function returns the new node array, with a node for each element in the rawData input + function createNodes(rawData) { + // use max size in the data as the max in the scale's domain + // note we have to ensure that size is a number + const maxSize = d3.max(rawData, (d) => +d[1]); + + // size bubbles based on area + const radiusScale = d3 + .scaleSqrt() + .domain([0, maxSize]) + .range([0, 80]); + + // use map() to convert raw data into node data + const myNodes = rawData.map((d) => ({ + ...d, + radius: radiusScale(+d[1]), + size: +d[1], + x: Math.random() * 900, + y: Math.random() * 800, + })); + + return myNodes; + } + + // main entry point to bubble chart, returned by parent closure + // prepares rawData for visualisation and adds an svg element to the provided selector and starts the visualisation process + let chart = function chart(selector, rawData) { + // convert raw data into nodes data + nodes = createNodes(rawData); + + // create svg element inside provided selector + svg = d3 + .select(selector) + .append("svg") + .attr("width", width) + .attr("height", height); + + // bind nodes data to circle elements + const elements = svg + .selectAll(".bubble") + .data(nodes, (d) => d[3]) + .enter() + .append("g"); + + // html tooltip showing text on mouseover + elements + .append("title") + .text((d) => `${d[2]} R${parseInt(d[1]).toLocaleString()}`); + + bubbles = elements + .append("circle") + .classed("bubble", true) + .attr("r", (d) => d.radius) + .attr("fill", (d) => fillColour(d[0])) + .on("mouseover", function(d) { + d3.select(this) + .attr("stroke", "rgba(0, 0, 0, 0.3)") + .attr("stroke-width", 2) + .style("cursor", "pointer"); + }) + .on("mouseout", function(d) { + d3.select(this).attr("stroke", "none"); + }) + .on("click", function(d) { + window.location.href = `/${ + window.location.pathname.split("/")[1] + }/national/public-entities/${d[4]}`; + }); + + // set simulation's nodes to our newly created nodes array + // simulation starts running automatically once nodes are set + simulation + .nodes(nodes) + .on("tick", ticked) + .restart(); + }; + + // callback function called after every tick of the force simulation + // here we do the actual repositioning of the circles based on current x and y value of their bound node data + // x and y values are modified by the force simulation + function ticked() { + bubbles.attr("cx", (d) => d.x).attr("cy", (d) => d.y); + } + + // return chart function from closure + return chart; +} + +// new bubble chart instance +let myBubbleChart = bubbleChart(); + +// function called once promise is resolved and data is loaded from csv +// calls bubble chart function to display inside #vis div +//function display(data) { +myBubbleChart("#vis", data); +//} + +// load data +//d3.data('nodes-data.csv').then(display); diff --git a/assets/js/scripts.js b/assets/js/scripts.js index 286492b67..5da64bb41 100644 --- a/assets/js/scripts.js +++ b/assets/js/scripts.js @@ -57,3 +57,4 @@ import './components/Share/scripts.js'; import './scenes/homepage/Hero/scripts.js'; import './embeds/equitable-share/index.js'; +import './embeds/chart-bubbles/index.js'; diff --git a/assets/scss/components/universal/Page/styles.scss b/assets/scss/components/universal/Page/styles.scss index 8b6a3c3d2..a359280ff 100644 --- a/assets/scss/components/universal/Page/styles.scss +++ b/assets/scss/components/universal/Page/styles.scss @@ -59,6 +59,7 @@ } } +.Page-pre-title, .Page-title, .Page-mainHeading { color: #3f3f3f; @@ -77,6 +78,18 @@ } } +.Page-pre-title { + font-size: 1em; + color: #979797; + margin-bottom: 0; + margin-top: 25px; + font-weight: normal; + + a { + color: #5e5d5d; + } +} + .Page-subHeading { margin: 0 0 10px; font-size: $font-size-medium; diff --git a/assets/scss/styles.scss b/assets/scss/styles.scss index 3753a7200..d8656dad0 100644 --- a/assets/scss/styles.scss +++ b/assets/scss/styles.scss @@ -21,6 +21,7 @@ @import 'utilities/scss/animation/background-pulse'; @import 'utilities/scss/animation/pop'; +@import 'utilities/scss/utility-classes/styles'; @import 'utilities/scss/utility-classes/u-sReadOnly'; @import 'utilities/scss/utility-classes/u-listReset'; @import 'utilities/scss/utility-classes/u-listItemReset'; diff --git a/assets/scss/utilities/scss/utility-classes/styles.scss b/assets/scss/utilities/scss/utility-classes/styles.scss new file mode 100644 index 000000000..6e1ca0abb --- /dev/null +++ b/assets/scss/utilities/scss/utility-classes/styles.scss @@ -0,0 +1,102 @@ +.is-width-100-percent { + width: 100%; +} + +.is-text-transform-uppercase { + text-transform: uppercase; +} + +.is-padding-left-40 { + padding-left: 40px; +} + +.is-padding-right-40 { + padding-right: 40px; +} + +.is-grid-2fr-1fr { + display: grid; + grid-template-columns: 2fr 1fr; + grid-template-rows: 1fr; + grid-column-gap: 1em; + grid-row-gap: 0px; +} + +.is-font-size-1-5-em { + font-size: 1.5em; +} + +.is-font-size-1-em { + font-size: 1em; +} + +.is-text-align-center { + text-align: center; +} + +.is-orange-circle { + background-color: #ed9e31; + border-radius: 50%; + width: 1em; + height: 1em; + display: inline-block; + vertical-align: -0.15em; + margin-right: 0.25em; +} + +.Section.is-padding-less { + @media ($mobile-breakpoint) { + padding: 20px; + } + + @media ($tablet-breakpoint) { + padding: 20px; + } +} + +.is-margin-0 { + margin: 0; +} + +.has-border-bottom-1 { + tr { + &:not(:last-child) { + td { + border-bottom: 1px solid #e5e5e5; + } + } + + th { + border-bottom: 1px solid #e5e5e5; + } + } +} + +.has-line-height-2 { + tr { + td, th { + line-height: 2em; + } + } +} + +.has-font-size-0-75-em { + th, + td { + font-size: 0.75em; + } +} + +.is-padding-bottom-0 { + padding-bottom: 0 !important; +} + +.is-text-no-wrap { + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.is-text-overlofw-ellipsis { + white-space: nowrap; +} diff --git a/budgetportal/templates/public_entity.html b/budgetportal/templates/public_entity.html index c4b14ec1d..ae43145e7 100644 --- a/budgetportal/templates/public_entity.html +++ b/budgetportal/templates/public_entity.html @@ -1,354 +1,101 @@ {% extends 'page-shell.html' %} -{% load define_action %} {% load humanize %} +{% load define_action %} {% block page_content %} - - {% if government.slug == 'south-africa' %} - {% assign 'National' as department_location %} - {% else %} - {% assign government.name as department_location %} - {% endif %} - - - {% if government.slug == 'south-africa' %} - {% assign 'Estimates of National Expenditure' as source_type %} - {% assign 'Estimates of National Expenditure' as source_type_revenue %} - {% assign 'ENE' as source_type_revenue_short %} - {% assign "Adjusted Estimates of National Expenditure" as source_type_adjusted %} - {% assign "AENE" as source_type_adjusted_short %} - {% assign "/learning-resources/guides/estimates-of-national-expenditure" as guide %} - {% else %} - {% assign 'Estimates of Provincial Expenditure' as source_type %} - {% assign 'Estimates of Provincial Revenue and Expenditure' as source_type_revenue %} - {% assign 'EPRE' as source_type_revenue_short %} - {% assign "Adjusted Estimates of Provincial Revenue and Expenditure" as source_type_adjusted %} - {% assign "AEPRE" as source_type_adjusted_short %} - {% assign "/learning-resources/guides/estimates-of-provincial-expenditure" as guide %} - {% endif %} - - - {% if department_budget %} - {% assign department_budget.name as chapter_name %} - {% assign department_budget.document.url as pdf_link %} - {% assign department_budget.tables.url as excel_link %} - {% else %} - {% assign "" as chapter_name %} - {% assign "" as pdf_link %} - {% assign "" as excel_link %} - {% endif %} - - - {% if department_adjusted_budget %} - {% assign department_adjusted_budget.document.url as pdf_link_adjusted %} - {% assign department_adjusted_budget.tables.url as excel_link_adjusted %} - {% else %} - {% assign "" as pdf_link_adjusted %} - {% assign "" as excel_link_adjusted %} - {% endif %} - - - {% if treasury_datasets %} - {% for item in treasury_datasets %} - {% assign item.1.formats as modified_formats %} {# | sort: 'format' %} #} - {% assign modified_formats.0.url as pdf %} - {% assign modified_formats.1.url as excel %} - {% endfor %} - {% else %} - {% assign pdf_link as pdf %} - {% assign excel_link as excel %} - {% endif %} - - - {% include 'components/department-budgets/ArrowButtons/index.html' with fixed="true" link_1="#section-plan" link_2="#section-implement" link_3="#section-review" %} - - {% if sphere.slug == "national" and slug == "parliament" %} - {% assign "true" as parliament %} - {% else %} - {% assign "" as parliament %} - {% endif %} - - {% assign department_location|add:" "|add:name|add:" Department Budget "|add:selected_financial_year as subtitle %} - - -
-
- -

+
+
+ +

+ {{ department }} +

+

{{ name }}

+
+
+
+

Entity information:

+

{{ intro }}

+ + + + + + + + + + + + + + + + + +
PFMA Schedule:{{ public_entity.pfma }}
Total expenditure ({{ selected_financial_year }}):R{{ public_entity.amount|intcomma }}
Total expenditure as % of {{ department }}:{{ percentage_of_total_department_amount|stringformat:".2f%%" }}
+ Total expenditure as % of all entities: + {{ percentage_of_total_amount|stringformat:".2f%%" }}
+
+
+

{{ department }} expenditure:

+
+

+ {{ department }} +
+ R{{ total_department_amount|intcomma }} +

+ + +

+ {{ name }} -

- - {{ department_location }} Public Entity Budget for {{ selected_financial_year }} - - {% if website_url %} - - {{ website_url }} - - {% endif %} - {% include 'components/department-budgets/IntroSection/index.html' with description=intro datasets=treasury_datasets location=department_location %} - -
- The Budget Cycle -
- -
- {% include 'components/department-budgets/ArrowButtons/index.html' with link_1="#section-plan" link_2="#section-implement" link_3="#section-review" %} -
- -
-
- Plan -
-
- -
-

{{ selected_financial_year }} Budget

-

- The {{ source_type_revenue }} ({{ source_type_revenue_short }}) is a book published along with the - tabling of the budget for the new financial year. -

- - {% assign "View the "|add:source_type_revenue_short|add:" chapter for "|add:chapter_name|add:" (PDF)" as text1 %} - {% assign "View tables in the "|add:source_type_revenue_short|add:" chapter (Excel)" as text2 %} - - {% if pdf_link or excel_link %} - - {% endif %} -
- -
- {% include 'scenes/department/ProgrammesSection/index.html' with source_type=source_type year=selected_financial_year pdf=pdf excel=excel guide=guide %} +

- -
- {% include 'scenes/department/EconClassPackedCircles/econ-class-packed-circles.html' with source_type=source_type year=selected_financial_year pdf=pdf excel=excel guide=guide %} -
- -
- {% include 'scenes/department/ProgramEconSmallMultiples/programme-econ-small-muls.html' with source_type=source_type year=selected_financial_year pdf=pdf excel=excel guide=guide %} -
- -

{{ selected_financial_year }} Adjusted Budget

-

- The {{ source_type_adjusted }} ({{ source_type_adjusted_short }}) is a book published along with the - tabling of the adjusted budget. -

- - {% assign "View the "|add:source_type_adjusted_short|add:" chapter for "|add:chapter_name|add:" (PDF)." as text1 %} - {% assign "View tables in the "|add:source_type_adjusted_short|add:" chapter (Excel)." as text2 %} - - {% if pdf_link_adjusted or excel_link_adjusted %} - - {% else %} - - {% if excel_link_adjusted %} -
-
- {% include 'components/Icon/index.html' with type="info" %} - - Please note - -
-
The {{ source_type_adjusted_short }} chapter - for {{ chapter_name }} (PDF) is not available yet -
-
- {% elif pdf_link_adjusted %} -
-
- {% include 'components/Icon/index.html' with type="info" %} - - Please note - -
-
The tables in {{ source_type_revenue_short }} - chapter (Excel) is not available yet -
-
- {% else %} - {% assign "The "|add:source_type_revenue_short|add:" chapter for "|add:chapter_name|add:" (PDF) is not available yet" as info2 %} - {% assign "The tables in "|add:source_type_revenue_short|add:" chapter (Excel)" as info3 %} - -
-
- {% include 'components/Icon/index.html' with type="info" %} - - Please note - -
-
The adjusted budget documents are not - available yet on vulekamali.gov.za -
-
- {% endif %} - {% endif %} - - - {% if government.slug == 'south-africa' %} -
- {% include 'scenes/department/AdjustedSection/index.html' with type="adjusted" items=adjusted_budget_summary source_type=source_type source_type_adjusted=source_type_adjusted year=selected_financial_year pdf=pdf excel=excel pdf_adjusted=pdf_link_adjusted excel_adjusted=excel_link_adjusted csv=adjusted_budget_summary.department_data_csv dataset=adjusted_budget_summary.dataset_detail_page parliament=parliament title="Programme budgets" subtitle=subtitle description="Activities of this department" %} -
- {% endif %} - - {% if procurement_resource_links %} -
- {% include 'scenes/department/ResourceLinks/resource-links.html' with resource_links=procurement_resource_links section_title="Procurement resources" more_link="/datasets/procurement-portals-and-resources" %} -
- {% endif %} - -
-
- Implement -
-
- - {% if infra_enabled %} -
-

Department infrastructure projects

- -

Largest infrastructure projects by this department.

- -
- - - - - - - {% for project in projects %} - - - - - - {% empty %} - - - - {% endfor %} -
Project nameEstimated completion date
- - {{ project.name }} - - {{ project.estimated_completion_date|default:"Not available" }}
No projects available for this department.
-
- - - -
- {% endif %} - - {% if in_year_monitoring_resource_links %} -
- {% include 'scenes/department/ResourceLinks/resource-links.html' with resource_links=in_year_monitoring_resource_links section_title="In-year monitoring resources" %} -
- {% endif %} - - {% if performance_resource_links %} -
- {% include 'scenes/department/ResourceLinks/resource-links.html' with resource_links=performance_resource_links section_title="Performance monitoring resources" %} -
- {% endif %} - - {% if eqprs_data_enabled %} -
- {% endif %} - - {% if not infra_enabled and not in_year_monitoring_resource_links and not performance_resource_links %} -
-
- {% include 'components/Icon/index.html' with type="info" size="l" %} - - Please note - -
-
Implementation data coming soon.
-
- {% endif %} - -
-
- Review -
-
- - {% if expenditure_over_time %} -
- {% include 'scenes/department/ExpenditureSection/index.html' with items=expenditure_over_time.expenditure cpi=global_values.cpi_dataset_url source_type=source_type year=selected_financial_year dataset=expenditure_over_time.dataset_detail_page pdf=pdf_link excel=excel csv=expenditure_over_time.department_data_csv guide=guide color="purple" title="Planned compared to historical expenditure" subtitle=subtitle description="Expenditure changes over time" %} -
- {% endif %} - - {% with ""|add:department_location|add:" "|add:name|add:" Department" as text %} - - {% if budget_actual %} -
- {% include 'scenes/department/ExpenditurePhaseSection/index.html' with items=budget_actual.expenditure cpi=global_values.cpi_dataset_url source_type="Expenditure Time Series" year=selected_financial_year dataset=budget_actual.dataset_detail_page csv=budget_actual.department_data_csv color="purple" description="Budgeted and Actual Expenditure comparison" subtitle=review_subtitle notices=budget_actual.notices website_url=website_url %} -
- {% endif %} - - {% if budget_actual_programmes %} -
- {% include 'scenes/department/ExpenditureMultiplesSection/index.html' with items=budget_actual_programmes.programmes cpi=global_values.cpi_dataset_url source_type="Expenditure Time Series" year=selected_financial_year dataset=budget_actual_programmes.dataset_detail_page csv=budget_actual_programmes.department_data_csv color="purple" subtitle=review_subtitle description="Budgeted and Actual Expenditure comparison by Programme" notices=budget_actual_programmes.notices %} -
- {% endif %} - {% endwith %} -
-
-
- {% include 'components/department-budgets/ContributedData/index.html' with datasets=contributed_datasets %} -
- -
- {% include 'components/universal/Participate/index.html' with title="Timelines for this department and ways to participate" description="National Treasury, departments and commitees are busy with different things depending on the time of year:" %} - - {% if comments_enabled %} -
-

Discuss this budget with others

-
-
-
-
- {% endif %} -
-
-
- - +
+ + + +
+

Detailed financial information:

+
+ + + + + + + + + + + + + + + + {% for expenditure in public_entity_expenditure %} + + + + + + + + + + + + {% endfor %} + +
Consol indiClassification 1Classification 2Classification 3Classification 4Classification 5Classification 6Budget phaseAmount
{{ expenditure.consol_indi }}{{ expenditure.economic_classification1|truncatechars:40 }}{{ expenditure.economic_classification2|truncatechars:40 }}{{ expenditure.economic_classification3|truncatechars:40 }}{{ expenditure.economic_classification4|truncatechars:40 }}{{ expenditure.economic_classification5|truncatechars:40 }}{{ expenditure.economic_classification6|truncatechars:40 }}{{ expenditure.budget_phase|truncatechars:40 }}{{ expenditure.amount|intcomma }}
+
{% endblock %} diff --git a/budgetportal/views.py b/budgetportal/views.py index f772a3b3d..f12216e23 100644 --- a/budgetportal/views.py +++ b/budgetportal/views.py @@ -1,3 +1,4 @@ +import simplejson import json import logging from csv import DictWriter @@ -39,7 +40,7 @@ Video, ShowcaseItem, ) -from .models.government import PublicEntity +from .models.government import PublicEntity, PublicEntityExpenditure from .summaries import ( InYearSpending, DepartmentProgrammesEcon4, @@ -509,7 +510,9 @@ def department_page( context["public_entities"] = [] - for public_entity in PublicEntity.objects.filter(department__slug=department_slug, government=department.government): + for public_entity in PublicEntity.objects.filter( + department__slug=department_slug, government=department.government + ): context["public_entities"].append( { "name": public_entity.name, @@ -523,62 +526,79 @@ def department_page( def public_entity_page( request, financial_year_id, sphere_slug, government_slug, public_entity_slug ): - raise() - public_entity = None + # Get public entity by public_entity_slug + selected_public_entity = PublicEntity.objects.filter( + slug=public_entity_slug + ).first() selected_year = get_object_or_404(FinancialYear, slug=financial_year_id) - years = FinancialYear.get_available_years() - for year in years: - if year.slug == financial_year_id: - selected_year = year - sphere = selected_year.spheres.filter(slug=sphere_slug).first() - government = sphere.governments.filter(slug=government_slug).first() - public_entity = government.public_entities.filter( - slug=public_entity_slug - ).first() - - financial_years_context = [] - for year in years: - closest_match, closest_is_exact = year.get_closest_match(public_entity) - financial_years_context.append( - { - "id": year.slug, - "is_selected": year.slug == financial_year_id, - "closest_match": { - "url_path": closest_match.get_url_path(), - "is_exact_match": closest_is_exact, - }, - } + # Total up public entityies amount + total_amount = 0 + for government in ( + selected_year.spheres.filter(slug="national").first().governments.all() + ): + for public_entity in government.public_entities.all(): + total_amount += public_entity.amount + + # Total up public entities in same department + total_department_amount = 0 + department_public_entities = [] + chart_data = [] + for department_public_entity in PublicEntity.objects.filter( + department=selected_public_entity.department + ): + total_department_amount += department_public_entity.amount + department_public_entities.append(department_public_entity) + # if department_public_entity is selected_public_entity then color_group = 2 else 1 + colour_group = 2 if department_public_entity == selected_public_entity else 1 + chart_data.append( + [ + colour_group, + simplejson.dumps(department_public_entity.amount, use_decimal=True), + department_public_entity.name, + simplejson.dumps(department_public_entity.id), + department_public_entity.slug, + ] ) - govt_label = "National" + # Get public entity expenditure + public_entity_expenditure = PublicEntityExpenditure.objects.filter( + public_entity=selected_public_entity + ) + + # Public entity amount percentage of total department amount + percentage_of_total_department_amount = ( + selected_public_entity.amount / total_department_amount + ) * 100 + + # Public entity amount percentage of total amount + percentage_of_total_amount = (selected_public_entity.amount / total_amount) * 100 context = { - "comments_enabled": settings.COMMENTS_ENABLED, - "financial_years": financial_years_context, - "government": { - "name": public_entity.government.name, - "label": govt_label, - "slug": str(public_entity.government.slug), - }, - "intro": public_entity.intro, - "name": public_entity.name, - "slug": str(public_entity.slug), - "sphere": { - "name": public_entity.government.sphere.name, - "slug": public_entity.government.sphere.slug, - }, + "public_entity_id": selected_public_entity.id, + "intro": selected_public_entity.intro, + "name": selected_public_entity.name, + "department": selected_public_entity.department.name, + "department_slug": selected_public_entity.department.slug, + "slug": str(selected_public_entity.slug), "selected_financial_year": financial_year_id, "selected_tab": "public_entities", - "title": "%s budget %s - vulekamali" - % (public_entity.name, selected_year.slug), - "description": "%s public entity: %s budget data for the %s financial year %s" + "title": "%s expenditure %s - vulekamali" + % (selected_public_entity.name, selected_year.slug), + "description": "%s public entity: Expenditure data for the %s financial year %s" % ( - govt_label, - public_entity.name, + selected_public_entity.name, selected_year.slug, COMMON_DESCRIPTION_ENDING, ), + "public_entity": selected_public_entity, + "total_amount": total_amount, + "total_department_amount": total_department_amount, + "percentage_of_total_amount": percentage_of_total_amount, + "percentage_of_total_department_amount": percentage_of_total_department_amount, + "department_public_entities": department_public_entities, + "chart_data": chart_data, + "public_entity_expenditure": public_entity_expenditure, } context["navbar"] = MainMenuItem.objects.prefetch_related("children").all() context["latest_year"] = FinancialYear.get_latest_year().slug @@ -586,7 +606,7 @@ def public_entity_page( str(settings.ROOT_DIR.path("_data/global_values.yaml")) ) context["admin_url"] = reverse( - "admin:budgetportal_department_change", args=(public_entity.pk,) + "admin:budgetportal_department_change", args=(selected_public_entity.pk,) ) return render(request, "public_entity.html", context) @@ -1157,7 +1177,7 @@ def department_list(request, financial_year_id): def latest_public_entity_list(request): - department = request.GET.get('department', None) + department = request.GET.get("department", None) url = reverse("public-entity-list", args=(FinancialYear.get_latest_year().slug,)) url = f"{url}?department={department}" if department else url return redirect(url, permanent=False) diff --git a/import_plublic_entities_expenditure.py b/import_plublic_entities_expenditure.py new file mode 100644 index 000000000..10ac82437 --- /dev/null +++ b/import_plublic_entities_expenditure.py @@ -0,0 +1,186 @@ +import csv +from budgetportal.models import ( + FinancialYear, + Sphere, + Government, + Department, +) + +from budgetportal.models.government import PublicEntity, PublicEntityExpenditure + + +def make_financial_year(year): + # Check if the input is a valid year + try: + year = int(year) + except ValueError: + return "Invalid year format" + + financial_year_str = f"{year - 1}-{str(year)[2:]}" + + return financial_year_str + + +# Open the CSV file +with open("ENT_ENE_CashFlow_202324 - Data.csv", newline="") as csvfile: + # Create a DictReader object with named columns + csvreader = csv.DictReader(csvfile) + + foundGovernments = [] + notFoundFinancialYears = [] + notFoundGovernments = [] + notFoundDepartments = [] + notFoundSpheres = [] + alreadyFoundDepartments = [] + notFoundPublicEntities = [] + + count = 0 + # Loop through each row in the CSV file + for row in csvreader: + if count >= 100000: + break + + # Increment the counter + count += 1 + # Access each column by its name + vote = row["Vote"] + department = row["Department"] + entity_name = row["EntityName"] + consol_indi = row["ConsolIndi"] + pfma = row["PFMA"] + type_ = row["Type"] + economic_classification1 = row["EconomicClassification1"] + economic_classification2 = row["EconomicClassification2"] + economic_classification3 = row["EconomicClassification3"] + economic_classification4 = row["EconomicClassification4"] + economic_classification5 = row["EconomicClassification5"] + economic_classification6 = row["EconomicClassification6"] + function_group1 = row["FunctionGroup1"] + function_group2 = row["FunctionGroup2"] + financial_year = row["FinancialYear"] + budget_phase = row["BudgetPhase"] + amount_r_thou = row["Amount (R-Thou)"] + amount = row["Amount"] + + financial_year_slug = make_financial_year(financial_year) + financialYears = FinancialYear.objects.filter(slug=financial_year_slug) + + if financialYears: + selectedFinancialYear = financialYears.first() + spheres = Sphere.objects.filter( + slug="national", financial_year=selectedFinancialYear + ) + + if spheres: + selectedSphere = spheres.first() + governments = Government.objects.filter(sphere=selectedSphere) + + if governments: + selectedGovernment = governments.first() + foundGovernments.append(selectedGovernment) + + departments = Department.objects.filter( + name=department, government=selectedGovernment + ) + selectedDepartment = None + + if departments: + selectedDepartment = departments.first() + else: + notFoundDepartments.append( + f"Department {department} for financial year {financial_year_slug} does not exist" + ) + try: + selectedDepartment = Department.objects.create( + name=department, + government=selectedGovernment, + vote_number=vote, + ) + except: + alreadyFoundDepartments.append( + f"Department {department} for financial year {financial_year_slug} already exists" + ) + if selectedDepartment: + publicEntities = PublicEntity.objects.filter( + name=entity_name, + department=selectedDepartment, + government=selectedGovernment, + pfma=pfma, + functiongroup1=function_group1, + ) + selectedPublicEntity = None + + if publicEntities: + selectedPublicEntity = publicEntities.first() + else: + selectedPublicEntity = PublicEntity.objects.create( + name=entity_name, + department=selectedDepartment, + government=selectedGovernment, + pfma=pfma, + functiongroup1=function_group1, + ) + + if selectedPublicEntity: + selectedPublicEntityExpenditure = ( + PublicEntityExpenditure.objects.create( + public_entity=selectedPublicEntity, + amount=amount, + budget_phase=budget_phase, + expenditure_type=type_, + economic_classification1=economic_classification1, + economic_classification2=economic_classification2, + economic_classification3=economic_classification3, + economic_classification4=economic_classification4, + economic_classification5=economic_classification5, + economic_classification6=economic_classification6, + consol_indi=consol_indi, + ) + ) + + else: + notFoundPublicEntities.append( + f"Public entity {entity_name} for department {department} for financial year {financial_year_slug} does not exist/not created" + ) + + else: + notFoundGovernments.append( + f"Government for financial year {financial_year_slug} does not exist" + ) + else: + notFoundSpheres.append( + f"National sphere for financial year {financial_year_slug} does not exist" + ) + else: + notFoundFinancialYears.append( + f"Financial year {financial_year_slug} does not exist" + ) + + print("-------------------------------------") + print(foundGovernments) + print("-------------------------------------") + print(notFoundFinancialYears) + print("-------------------------------------") + print(notFoundSpheres) + print("-------------------------------------") + print(notFoundGovernments) + print("-------------------------------------") + print(notFoundDepartments) + print("-------------------------------------") + print(alreadyFoundDepartments) + print("-------------------------------------") + print(notFoundPublicEntities) + + +# Add up publicExpenditure for each publicEntity +for publicEntity in PublicEntity.objects.all(): + totalExpenditure = 0 + for publicEntityExpenditure in PublicEntityExpenditure.objects.filter( + public_entity=publicEntity + ): + totalExpenditure += publicEntityExpenditure.amount + publicEntity.amount = totalExpenditure + publicEntity.save() + print( + f"Public entity {publicEntity.name} has total expenditure of {publicEntity.amount}" + )