oda-data 2.7.0__tar.gz → 2.7.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. {oda_data-2.7.0 → oda_data-2.7.1}/PKG-INFO +1 -1
  2. oda_data-2.7.1/pyproject.toml +217 -0
  3. oda_data-2.7.0/pyproject.toml → oda_data-2.7.1/pyproject.toml.orig +1 -1
  4. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/api/sources.py +73 -2
  5. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/research/sector_imputations.py +23 -1
  6. {oda_data-2.7.0 → oda_data-2.7.1}/README.md +0 -0
  7. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/__init__.py +0 -0
  8. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/api/__init__.py +0 -0
  9. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/api/constants.py +0 -0
  10. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/api/oecd.py +0 -0
  11. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/api/representations.py +0 -0
  12. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/cache/__init__.py +0 -0
  13. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/cache/_migrate.py +0 -0
  14. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/cache/api.py +0 -0
  15. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/cache/config.py +0 -0
  16. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/cache/types.py +0 -0
  17. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/__init__.py +0 -0
  18. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/channels.py +0 -0
  19. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/common.py +0 -0
  20. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/crs_channel_mapping.csv +0 -0
  21. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/dtypes.py +0 -0
  22. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/schema.py +0 -0
  23. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/clean_data/validation.py +0 -0
  24. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/config.py +0 -0
  25. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/__init__.py +0 -0
  26. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/common.py +0 -0
  27. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/crs/__init__.py +0 -0
  28. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/crs/common.py +0 -0
  29. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/crs/crs_functions.py +0 -0
  30. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/crs/crs_indicators.json +0 -0
  31. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/crs/oecd.py +0 -0
  32. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/crs/one.py +0 -0
  33. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac1/__init__.py +0 -0
  34. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac1/common.py +0 -0
  35. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac1/dac1_functions.py +0 -0
  36. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac1/dac1_indicators.json +0 -0
  37. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac1/oecd.py +0 -0
  38. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac1/one.py +0 -0
  39. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac2a/__init__.py +0 -0
  40. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac2a/common.py +0 -0
  41. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac2a/dac2a_functions.py +0 -0
  42. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac2a/dac2a_indicators.json +0 -0
  43. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac2a/oecd.py +0 -0
  44. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/dac2a/one.py +0 -0
  45. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/indicator.py +0 -0
  46. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/research/__init__.py +0 -0
  47. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/research/eu.py +0 -0
  48. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/indicators/research/policy_markers.py +0 -0
  49. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/logger.py +0 -0
  50. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/oda_data_indicators.csv +0 -0
  51. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/crs_flow_types.json +0 -0
  52. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/crs_modalities.json +0 -0
  53. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/crs_policy_markers.json +0 -0
  54. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/crs_purpose.json +0 -0
  55. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/crs_type_of_finance.json +0 -0
  56. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/dac1_aid_flow_types.json +0 -0
  57. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/dac2a_aid_type.json +0 -0
  58. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/flow_types.json +0 -0
  59. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/indicators.json +0 -0
  60. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/key_columns.json +0 -0
  61. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/provider_groupings.json +0 -0
  62. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/recipient_groupings.json +0 -0
  63. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/settings/v1_indicators_mapping.json +0 -0
  64. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/__init__.py +0 -0
  65. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/cache.py +0 -0
  66. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/compatibility.py +0 -0
  67. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/gni.py +0 -0
  68. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/groupings.py +0 -0
  69. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/names/__init__.py +0 -0
  70. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/names/add.py +0 -0
  71. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/names/create_mapping.py +0 -0
  72. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/names/crs_names.json +0 -0
  73. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/names/dac1_names.json +0 -0
  74. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/names/dac2a_names.json +0 -0
  75. {oda_data-2.7.0 → oda_data-2.7.1}/src/oda_data/tools/sector_lists.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.3
2
2
  Name: oda_data
3
- Version: 2.7.0
3
+ Version: 2.7.1
4
4
  Summary: A python package to work with Official Development Assistance data from the OECD DAC.
5
5
  Author: Jorge Rivera, Miguel Haro Ruiz
6
6
  Author-email: Jorge Rivera <jorge.rivera@one.org>, Miguel Haro Ruiz <miguel.haroruiz@one.org>
@@ -0,0 +1,217 @@
1
+ [project]
2
+ name = "oda_data"
3
+ version = "2.7.1"
4
+ description = "A python package to work with Official Development Assistance data from the OECD DAC."
5
+ requires-python = ">=3.11"
6
+ readme = "README.md"
7
+ classifiers = [
8
+ "Development Status :: 5 - Production/Stable",
9
+ "Intended Audience :: Developers",
10
+ "Intended Audience :: Science/Research",
11
+ "License :: OSI Approved :: MIT License",
12
+ "Programming Language :: Python :: 3",
13
+ "Programming Language :: Python :: 3.11",
14
+ "Programming Language :: Python :: 3.12",
15
+ "Programming Language :: Python :: 3.13",
16
+ "Programming Language :: Python :: 3 :: Only",
17
+ "Topic :: Scientific/Engineering",
18
+ "Topic :: Software Development :: Libraries :: Python Modules",
19
+ ]
20
+ dependencies = [
21
+ "cachetools>=6.2.0",
22
+ "filelock>=3.20.0",
23
+ "oda-reader>=1.6.0",
24
+ "platformdirs>=4.0.0",
25
+ "pandas[excel,parquet,performance]>=2.2.0",
26
+ "pydeflate>=2.3.3",
27
+ "reader>=3.18",
28
+ "thefuzz>=0.22.1",
29
+ ]
30
+
31
+ [project.license]
32
+ text = "MIT"
33
+
34
+ [[project.authors]]
35
+ name = "Jorge Rivera"
36
+ email = "jorge.rivera@one.org"
37
+
38
+ [[project.authors]]
39
+ name = "Miguel Haro Ruiz"
40
+ email = "miguel.haroruiz@one.org"
41
+
42
+ [project.urls]
43
+ Homepage = "https://github.com/ONEcampaign/oda_data_package"
44
+ Repository = "https://github.com/ONEcampaign/oda_data_package"
45
+ Documentation = "https://github.com/ONEcampaign/oda_data_package"
46
+
47
+ [build-system]
48
+ requires = ["uv_build>=0.11.8,<0.12"]
49
+ build-backend = "uv_build"
50
+
51
+ [dependency-groups]
52
+ dev = [
53
+ "pre-commit>=4.5.0",
54
+ "ruff>=0.14.9",
55
+ "ty>=0.0.33",
56
+ "mkdocs-material>=9.7.0",
57
+ ]
58
+ test = [
59
+ "pytest>=8.0.0",
60
+ "pytest-mock>=3.12.0",
61
+ "pytest-cov>=4.1.0",
62
+ "pytest-xdist>=3.5.0",
63
+ "freezegun>=1.4.0",
64
+ ]
65
+
66
+ [tool.ruff]
67
+ line-length = 88
68
+ target-version = "py311"
69
+ extend-include = ["*.ipynb"]
70
+ exclude = [
71
+ ".git",
72
+ ".venv",
73
+ "venv",
74
+ "__pycache__",
75
+ "build",
76
+ "dist",
77
+ "*.egg-info",
78
+ ".pytest_cache",
79
+ "htmlcov",
80
+ ]
81
+
82
+ [tool.ruff.lint]
83
+ select = [
84
+ "E",
85
+ "W",
86
+ "F",
87
+ "I",
88
+ "N",
89
+ "UP",
90
+ "B",
91
+ "C4",
92
+ "SIM",
93
+ "PL",
94
+ "RUF",
95
+ "ERA",
96
+ "PERF",
97
+ "ANN",
98
+ ]
99
+ ignore = [
100
+ "E501",
101
+ "PLR0913",
102
+ "PLR0912",
103
+ "PLR0914",
104
+ "PLR0915",
105
+ "PLR2004",
106
+ "PLC0415",
107
+ "ANN401",
108
+ ]
109
+ fixable = ["ALL"]
110
+ unfixable = []
111
+
112
+ [tool.ruff.lint.per-file-ignores]
113
+ "tests/**/*.py" = [
114
+ "ANN",
115
+ "E501",
116
+ "PLR2004",
117
+ "RUF002",
118
+ "RUF003",
119
+ "PLW0603",
120
+ "ERA001",
121
+ ]
122
+ "src/oda_data/__init__.py" = [
123
+ "PLW0603",
124
+ "PLW0602",
125
+ "F811",
126
+ ]
127
+ "src/oda_data/cache/config.py" = ["PLW0603"]
128
+ "src/oda_data/cache/api.py" = ["PLW0603"]
129
+ "src/oda_data/tools/cache.py" = ["PLW2901"]
130
+ "src/oda_data/api/oecd.py" = ["PLR0912"]
131
+ "src/oda_data/indicators/crs/crs_functions.py" = ["B023"]
132
+ "src/oda_data/indicators/research/sector_imputations.py" = ["B023"]
133
+ "src/oda_data/tools/sector_lists.py" = ["N816"]
134
+
135
+ [tool.ruff.lint.pydocstyle]
136
+ convention = "google"
137
+
138
+ [tool.ruff.format]
139
+ quote-style = "double"
140
+ indent-style = "space"
141
+ skip-magic-trailing-comma = false
142
+ line-ending = "auto"
143
+
144
+ [tool.ty.environment]
145
+ python-version = "3.11"
146
+
147
+ [tool.ty.src]
148
+ include = ["src/oda_data"]
149
+
150
+ [tool.ty.analysis]
151
+ replace-imports-with-any = [
152
+ "pandas",
153
+ "pandas.**",
154
+ "numpy",
155
+ "numpy.**",
156
+ ]
157
+
158
+ [tool.ty.rules]
159
+ possibly-missing-attribute = "ignore"
160
+ possibly-unresolved-reference = "ignore"
161
+
162
+ [tool.pytest.ini_options]
163
+ testpaths = ["tests"]
164
+ python_files = ["test_*.py"]
165
+ python_classes = ["Test*"]
166
+ python_functions = ["test_*"]
167
+ addopts = """
168
+ -v
169
+ --strict-markers
170
+ --tb=short
171
+ --cov=src/oda_data
172
+ --cov-report=term-missing
173
+ --cov-report=html
174
+ --cov-branch
175
+ -m "not integration and not network"
176
+ """
177
+ markers = [
178
+ """slow: marks tests as slow (deselect with '-m "not slow"')""",
179
+ "integration: marks tests that call real OECD API (deselected by default)",
180
+ "network: marks tests requiring live network access (opt-in via RUN_NETWORK_TESTS=1; deselected by default)",
181
+ "unit: marks tests as unit tests",
182
+ "cache: marks tests related to caching functionality",
183
+ "validation: marks tests related to input validation",
184
+ "cleaning: marks tests related to data cleaning",
185
+ ]
186
+ norecursedirs = [
187
+ ".git",
188
+ ".venv",
189
+ "venv",
190
+ "__pycache__",
191
+ "*.egg-info",
192
+ ".pytest_cache",
193
+ "htmlcov",
194
+ ]
195
+
196
+ [tool.coverage.run]
197
+ source = ["src/oda_data"]
198
+ branch = true
199
+ omit = [
200
+ "*/tests/*",
201
+ "*/__init__.py",
202
+ "*/config.py",
203
+ "*/logger.py",
204
+ ]
205
+
206
+ [tool.coverage.report]
207
+ precision = 2
208
+ show_missing = true
209
+ exclude_lines = [
210
+ "pragma: no cover",
211
+ "def __repr__",
212
+ "raise AssertionError",
213
+ "raise NotImplementedError",
214
+ "if __name__ == .__main__.:",
215
+ "if TYPE_CHECKING:",
216
+ "@abstractmethod",
217
+ ]
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "oda_data"
3
- version = "2.7.0"
3
+ version = "2.7.1"
4
4
  description = "A python package to work with Official Development Assistance data from the OECD DAC."
5
5
  requires-python = ">=3.11"
6
6
 
@@ -308,6 +308,17 @@ class DACSource(Source):
308
308
  )
309
309
  self.de_sectors = check_strings(sectors)
310
310
 
311
+ def _extra_hash_components(self) -> list[tuple]:
312
+ """Extra (name, predicate, value) tuples folded into the cache-key hash.
313
+
314
+ They bypass the read-filter path, never reaching a parquet predicate
315
+ or a query string. Folding them into the hash lets a constructor
316
+ flag that changes row-level behaviour, without being expressed as a
317
+ filter tuple, still produce a different cache key. Subclasses
318
+ override this; the default is a no-op.
319
+ """
320
+ return []
321
+
311
322
  def _get_filtered_download_filters(self) -> dict:
312
323
  """Generates a dictionary of filters for downloading the dataset.
313
324
 
@@ -438,7 +449,9 @@ class DACSource(Source):
438
449
  """
439
450
  # Create filters and generate cache key
440
451
  filters = self._get_read_filters(additional_filters=additional_filters)
441
- param_hash = generate_param_hash(filters if filters else [])
452
+ param_hash = generate_param_hash(
453
+ (filters if filters else []) + self._extra_hash_components()
454
+ )
442
455
 
443
456
  # When refresh=True, skip the upper cache tiers entirely so the bulk
444
457
  # fetcher's refresh=True actually re-downloads and the upper tiers see
@@ -640,6 +653,7 @@ class CRSData(DACSource):
640
653
  years: list[int] | range | int | None = None,
641
654
  providers: list[int] | int | None = None,
642
655
  recipients: list[int] | int | None = None,
656
+ exclude_multilateral_core: bool = True,
643
657
  ) -> None:
644
658
  """
645
659
  Initialize CRSData.
@@ -648,9 +662,17 @@ class CRSData(DACSource):
648
662
  years (list[int] | range): List or range of years to filter.
649
663
  providers (Optional[list[int]]): List of provider codes.
650
664
  recipients (Optional[list[int]]): List of recipient codes.
665
+ exclude_multilateral_core (bool): Whether to exclude CRS rows that report
666
+ a donor's core (unearmarked) contribution to a multilateral
667
+ organisation (``bi_multi == 2``). The OECD's CRS changelog states
668
+ these rows must be excluded to avoid double-counting bilateral
669
+ flows. Defaults to True. Rows with a missing ``bi_multi`` value
670
+ are always kept. Pass False to get the raw CRS totals including
671
+ those core-contribution rows.
651
672
  """
652
673
  super().__init__()
653
674
  self._init_filters(years=years, providers=providers, recipients=recipients)
675
+ self.exclude_multilateral_core = exclude_multilateral_core
654
676
  self._param_hash = None
655
677
 
656
678
  def _create_bulk_fetcher(self) -> Callable[[Path], None]:
@@ -660,6 +682,55 @@ class CRSData(DACSource):
660
682
  """
661
683
  return create_crs_bulk_fetcher()
662
684
 
685
+ def _extra_hash_components(self) -> list[tuple]:
686
+ return [("exclude_multilateral_core", "==", self.exclude_multilateral_core)]
687
+
688
+ def _exclude_core_contributions(self, df: pd.DataFrame) -> pd.DataFrame:
689
+ """Drop rows reporting a donor's core contribution to a multilateral organisation.
690
+
691
+ No-op when ``exclude_multilateral_core`` is False, or when the ``bi_multi``
692
+ column isn't present (e.g. it was excluded from a column projection).
693
+ Rows with a missing ``bi_multi`` value are kept. A plain ``!= 2`` filter
694
+ would drop them, since a null comparison is null rather than true.
695
+ """
696
+ if not self.exclude_multilateral_core:
697
+ return df
698
+ column = self.schema.BI_MULTI
699
+ if column not in df.columns:
700
+ return df
701
+ return df.loc[(df[column] != 2) | df[column].isna()].reset_index(drop=True)
702
+
703
+ def _fetch_from_bulk_cache(
704
+ self,
705
+ filters: list[tuple] | None,
706
+ columns: list | None,
707
+ *,
708
+ refresh: bool = False,
709
+ ) -> pd.DataFrame:
710
+ """Read from the bulk cache, then apply the core-contribution exclusion.
711
+
712
+ ``bi_multi`` is read even when not in the requested ``columns``, so the
713
+ exclusion can be applied before column projection, then dropped again if
714
+ the caller didn't ask for it.
715
+ """
716
+ read_columns = columns
717
+ added_bi_multi = False
718
+ if (
719
+ self.exclude_multilateral_core
720
+ and columns is not None
721
+ and self.schema.BI_MULTI not in columns
722
+ ):
723
+ read_columns = [*columns, self.schema.BI_MULTI]
724
+ added_bi_multi = True
725
+
726
+ df = super()._fetch_from_bulk_cache(filters, read_columns, refresh=refresh)
727
+ df = self._exclude_core_contributions(df)
728
+
729
+ if added_bi_multi:
730
+ df = df.drop(columns=[self.schema.BI_MULTI])
731
+
732
+ return df
733
+
663
734
  def download(self) -> pd.DataFrame:
664
735
  """Downloads CRS data via API (filtered query).
665
736
 
@@ -672,7 +743,7 @@ class CRSData(DACSource):
672
743
  filters=self._get_filtered_download_filters(),
673
744
  )
674
745
  logger.info("CRS data downloaded successfully.")
675
- return clean_raw_df(df)
746
+ return self._exclude_core_contributions(clean_raw_df(df))
676
747
 
677
748
 
678
749
  class MultiSystemData(DACSource):
@@ -145,9 +145,15 @@ def spending_by_purpose(
145
145
  oda_only: bool = False,
146
146
  currency: str = "USD",
147
147
  base_year: int | None = None,
148
+ exclude_multilateral_core: bool = True,
148
149
  ) -> pd.DataFrame:
149
150
  """Retrieves and processes spending data by purpose.
150
151
 
152
+ By default, CRS rows that report a donor's core (unearmarked) contribution to a
153
+ multilateral organisation (`bi_multi == 2`) are excluded. Those contributions are
154
+ redistributed across purposes separately, by `imputed_multilateral_by_purpose`.
155
+ A caller that leaves them in this bilateral total would double count them.
156
+
151
157
  Args:
152
158
  years (list | int | range, optional): Years to filter the data. Defaults to None.
153
159
  providers (list | int | None, optional): Providers to filter the data. Defaults to None.
@@ -155,6 +161,10 @@ def spending_by_purpose(
155
161
  oda_only (bool, optional): Whether to include only ODA-related data. Defaults to False.
156
162
  currency (str, optional): Target currency. Defaults to "USD".
157
163
  base_year (int | None, optional): Base year for conversion. Defaults to None.
164
+ exclude_multilateral_core (bool, optional): Whether to exclude CRS rows
165
+ reporting a donor's core contribution to a multilateral organisation
166
+ (`bi_multi == 2`). Defaults to True. Pass False for a total that
167
+ includes those core-contribution rows.
158
168
 
159
169
  Returns:
160
170
  pd.DataFrame: Dataframe with spending by purpose.
@@ -175,7 +185,11 @@ def spending_by_purpose(
175
185
  filters = [("category", "in", [10, 60])] if oda_only else []
176
186
 
177
187
  # Set up the CRS data object
178
- crs = CRSData(providers=providers, years=years)
188
+ crs = CRSData(
189
+ providers=providers,
190
+ years=years,
191
+ exclude_multilateral_core=exclude_multilateral_core,
192
+ )
179
193
 
180
194
  # Read the data and group by provider and purpose
181
195
  data = (
@@ -340,6 +354,14 @@ def imputed_multilateral_by_purpose(
340
354
  ) -> pd.DataFrame:
341
355
  """Computes imputed multilateral spending by purpose.
342
356
 
357
+ The multilateral spending shares this imputes onto (via
358
+ `multilateral_spending_shares_by_channel_and_purpose_smoothed`, which calls
359
+ `spending_by_purpose`) exclude CRS rows reporting a donor's core contribution to a
360
+ multilateral organisation (`bi_multi == 2`) by default. Those core contributions
361
+ are already captured here, on the `core_multilateral_contributions_by_provider`
362
+ leg, via MultiSystem data. Including them on the CRS leg too would double count
363
+ them.
364
+
343
365
  Args:
344
366
  years (list | int | range, optional): Years to filter the data. Defaults to None.
345
367
  providers (list | int | None, optional): Providers to filter the data. Defaults to None.
File without changes