A Python library for splitting IATI activity transactions by countries, regions, and sectors while maintaining correct percentage allocations. This library ensures accurate division of transaction values across multiple dimensions while preserving percentage integrity.
This library implements the IATI transaction splitting methodology as described in:
- IATI Country Data Methodology
- IATI Transaction Breakdown
- IATI Standard Process Discussion
- HDX IATI COVID-19 Dashboard
-
No Double Counting
- If one activity spans multiple countries, the sum of split transactions should equal the original amount
- Example: For an activity in countries A and B, sum(country A transactions) + sum(country B transactions) = original amount
-
Value-Only Splitting
- Only the transaction value field is modified during splitting
- Currency information remains unchanged
- Percentages are used to split values
-
Transaction vs Activity Level Declarations
- Fields can be declared at transaction level or activity level
- Transaction-level declarations take precedence
This library provides functionality to:
- Split transactions by countries with percentage allocations
- Split transactions by regions with percentage allocations
- Split transactions by sectors with vocabulary support
- Automatically normalise incorrect percentages
- Prevent double counting in splits
- Generate consistent JSON output
# Clone the repository
git clone https://github.com/IATI/activity-details-split-by-fields.git
cd activity-details-split-by-fields
# Install the package
pip install -e .
When fields are declared at transaction level:
- recipient-country OR recipient-region (only one of these)
- The whole transaction is applied to that country/region
- No splitting occurs
- sector (one per vocabulary, but can have multiple vocabularies)
- Transaction is repeated for each vocabulary with the same value
When fields are not declared at transaction level:
- Look for fields at activity level
- Use percentages to create new values
- Can have multiple:
- recipient-countries
- regions
- sectors
- All should have percentages
- If percentages don't sum to 100%, they are normalised
When some fields are at transaction level and others at activity level:
- Transaction-level declarations take precedence
- Activity-level declarations are used for missing fields
from iati_activity_details_split_by_fields.iati_activity import IATIActivity
from iati_activity_details_split_by_fields.iati_activity_transaction import IATIActivityTransaction
from iati_activity_details_split_by_fields.iati_activity_recipient_country import IATIActivityRecipientCountry
# Split $1000 between two countries
activity = IATIActivity(
transactions=[IATIActivityTransaction(value=1000)],
recipient_countries=[
IATIActivityRecipientCountry(code="FR", percentage=60), # France: 60%
IATIActivityRecipientCountry(code="GB", percentage=40), # UK: 40%
]
)
results = activity.get_transactions_split_as_json()
from iati_activity_details_split_by_fields.iati_activity_recipient_region import IATIActivityRecipientRegion
# Split $1000 between regions
activity = IATIActivity(
transactions=[IATIActivityTransaction(value=1000)],
recipient_regions=[
IATIActivityRecipientRegion(code="ASIA", percentage=70), # Asia: 70%
IATIActivityRecipientRegion(code="AFRICA", percentage=30), # Africa: 30%
]
)
results = activity.get_transactions_split_as_json()
from iati_activity_details_split_by_fields.iati_activity_sector import IATIActivitySector
# Split $1000 between sectors
activity = IATIActivity(
transactions=[IATIActivityTransaction(value=1000)],
sectors=[
IATIActivitySector(vocabulary="DAC", code="HEALTH", percentage=60), # Health: 60%
IATIActivitySector(vocabulary="DAC", code="EDUCATION", percentage=40), # Education: 40%
]
)
results = activity.get_transactions_split_as_json()
# Example: $1000 transaction split between:
# - Two countries (50% each)
# - Two sectors (50% each)
activity = IATIActivity(
transactions=[IATIActivityTransaction(value=1000)],
recipient_countries=[
IATIActivityRecipientCountry(code="A", percentage=50),
IATIActivityRecipientCountry(code="B", percentage=50),
],
sectors=[
IATIActivitySector(vocabulary="DAC", code="SECTOR_A", percentage=50),
IATIActivitySector(vocabulary="DAC", code="SECTOR_B", percentage=50),
]
)
# Results will be:
# 1. Country A, Sector A: $250 (25%)
# 2. Country A, Sector B: $250 (25%)
# 3. Country B, Sector A: $250 (25%)
# 4. Country B, Sector B: $250 (25%)
The library automatically normalizes percentages that don't sum to 100%. For example:
- If country percentages sum to 70%, they're normalized to 100%
- A 30% share becomes (30/70 * 100) = 42.86%
- A 40% share becomes (40/70 * 100) = 57.14%
Transactions are split in this order:
- Countries (if any)
- Regions (if any)
- Sectors (if any)
All outputs follow this structure:
{
"value": float, # Split transaction value
"recipient_country_code": str, # Country code or None (NOT a list)
"recipient_region_code": str, # Region code or None (NOT a list)
"sectors": [ # List of sectors (can be empty)
{
"vocabulary": str, # Sector vocabulary (defaults to DAC-5 if value present)
"code": str # Sector code
}
]
}
Main class for handling transaction splits.
get_transactions_split()
: Returns list of split transactionsget_transactions_split_as_json()
: Returns list of split transactions in JSON format
Represents a single transaction.
value
: Transaction amountsectors
: List of sectorsrecipient_country_code
: Country code (single value)recipient_region_code
: Region code (single value)
Represents a country allocation.
code
: Country codepercentage
: Allocation percentage
Represents a region allocation.
code
: Region codepercentage
: Allocation percentage
Represents a sector allocation.
vocabulary
: Sector vocabularycode
: Sector codepercentage
: Allocation percentage
# Install development dependencies
pip install -e .[dev]
# Run all tests
pytest tests/ -v
# Run specific test file
pytest tests/test_at_activity_level.py -v
# Run tests with specific keyword
pytest tests/ -v -k "country"
# Run all lint checks
black iati_activity_details_split_by_fields/*.py tests/*.py
isort iati_activity_details_split_by_fields/*.py tests/*.py
flake8 iati_activity_details_split_by_fields/*.py tests/*.py
mypy --install-types --non-interactive -p iati_activity_details_split_by_fields
- Fork the repository
- Create a feature branch
- Add your changes
- Run tests
- Create a pull request