Syriantaxes
A Python package for calculating taxes and compensations for Syrian salaries based on Syrian tax laws.
- Progressive salary tax, with social security deducted first
- Flat tax on compensations
- The reverse problems: the gross salary or compensation that gives a wanted net
- Splitting a net package into salary and compensation
Decimalin,Decimalout; fully typed (mypy strict), with 100% test coverage
Installation
pip install syriantaxes
Or using uv:
uv add syriantaxes
The idea
Everything the law fixes for a period (the brackets, rates, minimum salary and rounding) goes into
one immutable TaxRules value. Every calculation is a plain function of those rules and an amount.
When the law changes, you make new rules; your code stays the same.
from decimal import ROUND_CEILING, Decimal
from syriantaxes import Bracket, SocialSecurity, TaxRules, round_to
rules = TaxRules(
brackets=(
Bracket(lower=Decimal(0), rate=Decimal(0)),
Bracket(lower=Decimal(837_000), rate=Decimal("0.11")),
Bracket(lower=Decimal(850_000), rate=Decimal("0.13")),
Bracket(lower=Decimal(1_100_000), rate=Decimal("0.15")),
),
min_salary=Decimal(837_000),
compensation_tax_rate=Decimal("0.05"),
social_security=SocialSecurity(
rate=Decimal("0.07"),
min_salary=Decimal(750_000),
rounding=round_to(Decimal(1), ROUND_CEILING),
),
rounding=round_to(Decimal(100), ROUND_CEILING), # taxes are rounded up to 100
)
The rules are checked when they are created:
- the brackets must start at 0 and go up;
- every rate must be at least 0 and below 1;
- amounts can't be negative.
Salary
Each bracket runs from its lower up to the next bracket's lower; the last one has no top.
from syriantaxes import gross_salary, salary_breakdown, salary_tax
breakdown = salary_breakdown(rules, Decimal(1_000_000), insured_salary=Decimal(750_000))
print(breakdown.social_security, breakdown.taxable, breakdown.tax, breakdown.net)
# 52500 947500 14200 933300
print(salary_tax(rules, Decimal(1_000_000))) # 11900
print(gross_salary(rules, Decimal(1_000_000))) # 1101183
insured_salaryis the salary that social security is based on; it defaults to the gross salary.gross_salaryreturns the first whole gross salary whose net (after social security and tax) is at least the target. It solves the brackets exactly and then accounts for rounding, so there is no search range to tune.
Compensation
from syriantaxes import compensation_tax, gross_compensation
print(compensation_tax(rules, Decimal(1_000_000))) # 50000
print(gross_compensation(rules, Decimal(1_000_000))) # 1052700
Splitting a net package
split_net_package pays compensation_share of a net total as compensation and the rest as salary.
If the salary part would be below the minimum salary, the salary is set to the minimum and the
compensation makes up the difference.
from syriantaxes import split_net_package
package = split_net_package(rules, Decimal(4_000_000), Decimal("0.75"))
print(package.salary, package.compensation) # 1101183 3157900
Rounding
A rounding rule is any function from Decimal to Decimal. round_to(step, method) makes one that
rounds to a multiple of step, using any of the decimal module's rounding modes. Without a
rounding rule, amounts are rounded half-even to the cent (to_cents).
Inputs: Decimal only
Syriantaxes doesn't convert inputs: turn ints, floats or strings into Decimal yourself. Build
decimals from strings or ints, never from floats: Decimal("0.11") is exactly 0.11, while
Decimal(0.11) isn't.
Errors
SalaryBelowMinimumError(aValueError):gross_salaryandsplit_net_packageraise it when the target is belowrules.min_salary.ValueError: raised for invalid rules or arguments, such as a rate of 1, or an insured salary below the social security minimum.
Upgrading from 0.x
1.0 is a new API. The old functions map like this:
| 0.x | 1.0 |
|---|---|
calculate_brackets_tax(amount, brackets, min_allowed_salary, rounder, ss_obj, ss_salary=...) |
salary_tax(rules, gross, insured_salary=...) |
calculate_fixed_tax(amount, rate, rounder) |
compensation_tax(rules, amount) |
calculate_gross_compensation(target, rate, rounder) |
gross_compensation(rules, net) |
calculate_gross_salary(target, brackets, min_allowed_salary, rounder) |
gross_salary(rules, net) |
calculate_gross_components(target, rate, brackets, min_allowed_salary, tax_rate, rounder=...) |
split_net_package(rules, net_total, compensation_share) |
Bracket(min, max, rate) |
Bracket(lower, rate) |
Rounder(RoundingMethod.CEILING, 100) |
round_to(Decimal(100), ROUND_CEILING) |
Behavior changes:
gross_salarynever returns a gross whose net is below the target. Where tax is rounded to the cent, 0.x could return a gross 1 lower, whose net fell up to 0.15 short of the target.- When more than one gross gives the exact target net,
gross_salaryreturns the smallest. - Custom social security objects are replaced by
SocialSecurityrules. Custom rounders are plain functions.
Development
The project uses uv:
uv sync
uv run pytest --cov
uv run mypy
uv run ruff check
uv run ruff format --check
License
This project is licensed under the MIT License - see the LICENSE file for details.
Author
Abdullah Altatan - a.altatan@gmail.com
Metadata
Release files for syriantaxes 1.0.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| syriantaxes-1.0.0.tar.gz | 82.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| syriantaxes-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 102.1 kB
Release files / syriantaxes-1.0.0.tar.gz
| Download URL | syriantaxes-1.0.0.tar.gz |
|---|---|
| Size | 82.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a795ac5d02ab33c5a1633c1f6699bc086598f7c76c718020754da3447b94dcd0
|
|
BLAKE2b-256 checksum How to use checksums |
44d4aff0d6fab509506360c754d1a6b20c8af0777ee0b3f7c49ad9d9cbbb23f7
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|
Release files / syriantaxes-1.0.0-py3-none-any.whl
| Download URL | syriantaxes-1.0.0-py3-none-any.whl |
|---|---|
| Size | 19.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
49d1380cbd70cbc333a183e2a420a6ed51d19fd8fa37473786e32c1a1bc7c3b6
|
|
BLAKE2b-256 checksum How to use checksums |
7fa681d4881f248a2864392def6ee21119caf366fe63029a1f4f2719be375ed9
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.13.1 {"installer":{"name":"uv","version":"0.13.1","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":null,"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}
|