shadowprice 0.1.0__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.
- shadowprice-0.1.0/.gitattributes +4 -0
- shadowprice-0.1.0/.github/workflows/ci.yml +91 -0
- shadowprice-0.1.0/.github/workflows/release.yml +84 -0
- shadowprice-0.1.0/.gitignore +12 -0
- shadowprice-0.1.0/CHANGELOG.md +25 -0
- shadowprice-0.1.0/CITATION.cff +15 -0
- shadowprice-0.1.0/CONTRIBUTING.md +54 -0
- shadowprice-0.1.0/LICENSE +21 -0
- shadowprice-0.1.0/PKG-INFO +214 -0
- shadowprice-0.1.0/README.md +181 -0
- shadowprice-0.1.0/examples/water_network.py +171 -0
- shadowprice-0.1.0/pyproject.toml +63 -0
- shadowprice-0.1.0/src/shadowprice/__init__.py +75 -0
- shadowprice-0.1.0/src/shadowprice/_basis.py +62 -0
- shadowprice-0.1.0/src/shadowprice/_version.py +1 -0
- shadowprice-0.1.0/src/shadowprice/benefits.py +65 -0
- shadowprice-0.1.0/src/shadowprice/conversion.py +168 -0
- shadowprice-0.1.0/src/shadowprice/discounting.py +147 -0
- shadowprice-0.1.0/src/shadowprice/economic.py +211 -0
- shadowprice-0.1.0/src/shadowprice/financial.py +228 -0
- shadowprice-0.1.0/src/shadowprice/financing.py +128 -0
- shadowprice-0.1.0/src/shadowprice/lines.py +99 -0
- shadowprice-0.1.0/src/shadowprice/methods.py +120 -0
- shadowprice-0.1.0/src/shadowprice/presets/__init__.py +1 -0
- shadowprice-0.1.0/src/shadowprice/presets/adb_2017.toml +22 -0
- shadowprice-0.1.0/src/shadowprice/presets/eu_2014.toml +48 -0
- shadowprice-0.1.0/src/shadowprice/presets/wb_2016.toml +24 -0
- shadowprice-0.1.0/src/shadowprice/project.py +259 -0
- shadowprice-0.1.0/src/shadowprice/report.py +171 -0
- shadowprice-0.1.0/src/shadowprice/risk.py +275 -0
- shadowprice-0.1.0/src/shadowprice/timeline.py +94 -0
- shadowprice-0.1.0/tests/conftest.py +19 -0
- shadowprice-0.1.0/tests/test_benefits.py +23 -0
- shadowprice-0.1.0/tests/test_conversion_methods.py +69 -0
- shadowprice-0.1.0/tests/test_discounting.py +72 -0
- shadowprice-0.1.0/tests/test_economic.py +117 -0
- shadowprice-0.1.0/tests/test_financial.py +118 -0
- shadowprice-0.1.0/tests/test_readme.py +14 -0
- shadowprice-0.1.0/tests/test_report_and_example.py +37 -0
- shadowprice-0.1.0/tests/test_risk.py +108 -0
- shadowprice-0.1.0/tests/test_timeline.py +40 -0
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
|
|
9
|
+
defaults:
|
|
10
|
+
run:
|
|
11
|
+
shell: bash
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
lint:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@v4
|
|
18
|
+
- uses: actions/setup-python@v5
|
|
19
|
+
with:
|
|
20
|
+
python-version: "3.13"
|
|
21
|
+
- run: pip install ruff
|
|
22
|
+
- run: ruff check .
|
|
23
|
+
- run: ruff format --check .
|
|
24
|
+
|
|
25
|
+
test:
|
|
26
|
+
name: test (${{ matrix.os }}, Python ${{ matrix.python-version }})
|
|
27
|
+
runs-on: ${{ matrix.os }}
|
|
28
|
+
strategy:
|
|
29
|
+
fail-fast: false
|
|
30
|
+
matrix:
|
|
31
|
+
os: [ubuntu-latest, windows-latest, macos-latest]
|
|
32
|
+
python-version: ["3.11", "3.12", "3.13", "3.14"]
|
|
33
|
+
steps:
|
|
34
|
+
- uses: actions/checkout@v4
|
|
35
|
+
- uses: actions/setup-python@v5
|
|
36
|
+
with:
|
|
37
|
+
python-version: ${{ matrix.python-version }}
|
|
38
|
+
- run: pip install -e ".[dev]"
|
|
39
|
+
- run: pytest --cov=shadowprice --cov-report=term-missing
|
|
40
|
+
|
|
41
|
+
oldest-dependencies:
|
|
42
|
+
# The lowest versions declared in pyproject.toml must keep working.
|
|
43
|
+
runs-on: ubuntu-latest
|
|
44
|
+
steps:
|
|
45
|
+
- uses: actions/checkout@v4
|
|
46
|
+
- uses: actions/setup-python@v5
|
|
47
|
+
with:
|
|
48
|
+
python-version: "3.11"
|
|
49
|
+
- name: Install with oldest supported dependencies
|
|
50
|
+
run: |
|
|
51
|
+
printf 'numpy==1.24.*\npandas==2.0.*\nopenpyxl==3.1.0\n' > oldest.txt
|
|
52
|
+
pip install -e ".[dev]" -c oldest.txt
|
|
53
|
+
python -c "import numpy, pandas; print('numpy', numpy.__version__, 'pandas', pandas.__version__)"
|
|
54
|
+
- run: pytest
|
|
55
|
+
|
|
56
|
+
installed-package:
|
|
57
|
+
# Build the wheel and test it the way users install it (not editable).
|
|
58
|
+
name: installed package (${{ matrix.os }})
|
|
59
|
+
runs-on: ${{ matrix.os }}
|
|
60
|
+
strategy:
|
|
61
|
+
fail-fast: false
|
|
62
|
+
matrix:
|
|
63
|
+
os: [ubuntu-latest, windows-latest]
|
|
64
|
+
steps:
|
|
65
|
+
- uses: actions/checkout@v4
|
|
66
|
+
- uses: actions/setup-python@v5
|
|
67
|
+
with:
|
|
68
|
+
python-version: "3.13"
|
|
69
|
+
- name: Build sdist and wheel
|
|
70
|
+
run: |
|
|
71
|
+
pip install build twine
|
|
72
|
+
python -m build
|
|
73
|
+
twine check dist/*
|
|
74
|
+
- name: Install the wheel into a clean environment
|
|
75
|
+
run: |
|
|
76
|
+
python -m venv /tmp/clean || python -m venv clean-env
|
|
77
|
+
if [ -d /tmp/clean/bin ]; then PY=/tmp/clean/bin/python; \
|
|
78
|
+
elif [ -d /tmp/clean/Scripts ]; then PY=/tmp/clean/Scripts/python; \
|
|
79
|
+
else PY=clean-env/Scripts/python; fi
|
|
80
|
+
echo "PY=$PY" >> "$GITHUB_ENV"
|
|
81
|
+
WHEEL=$(ls dist/*.whl)
|
|
82
|
+
"$PY" -m pip install "${WHEEL}[excel]" pytest
|
|
83
|
+
- name: Check the installed copy is used
|
|
84
|
+
run: |
|
|
85
|
+
"$PY" -c "import shadowprice as sp; assert 'site-packages' in sp.__file__, sp.__file__; print(sp.__version__, sp.available_methods())"
|
|
86
|
+
- name: Run tests against the installed package
|
|
87
|
+
run: '"$PY" -m pytest -p no:cacheprovider'
|
|
88
|
+
- name: Run the worked example
|
|
89
|
+
run: |
|
|
90
|
+
mkdir -p run && cd run
|
|
91
|
+
"$PY" ../examples/water_network.py
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
# Publishing a GitHub Release (tag vX.Y.Z) uploads the package to PyPI.
|
|
4
|
+
# "Run workflow" in the Actions tab can upload to TestPyPI for a trial.
|
|
5
|
+
# Authentication uses PyPI trusted publishing: no passwords or tokens are stored.
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
release:
|
|
9
|
+
types: [published]
|
|
10
|
+
workflow_dispatch:
|
|
11
|
+
inputs:
|
|
12
|
+
target:
|
|
13
|
+
description: "Where to publish"
|
|
14
|
+
type: choice
|
|
15
|
+
options: [testpypi, pypi]
|
|
16
|
+
default: testpypi
|
|
17
|
+
|
|
18
|
+
jobs:
|
|
19
|
+
build:
|
|
20
|
+
name: Build distributions
|
|
21
|
+
runs-on: ubuntu-latest
|
|
22
|
+
steps:
|
|
23
|
+
- uses: actions/checkout@v4
|
|
24
|
+
- uses: actions/setup-python@v5
|
|
25
|
+
with:
|
|
26
|
+
python-version: "3.13"
|
|
27
|
+
- name: Check that the release tag matches the package version
|
|
28
|
+
if: github.event_name == 'release'
|
|
29
|
+
run: |
|
|
30
|
+
version=$(python -c "import re; print(re.search(r'\"(.+)\"', open('src/shadowprice/_version.py').read()).group(1))")
|
|
31
|
+
if [ "${GITHUB_REF_NAME}" != "v${version}" ]; then
|
|
32
|
+
echo "::error::Tag ${GITHUB_REF_NAME} does not match package version v${version}"
|
|
33
|
+
exit 1
|
|
34
|
+
fi
|
|
35
|
+
- name: Build and check
|
|
36
|
+
run: |
|
|
37
|
+
pip install build twine
|
|
38
|
+
python -m build
|
|
39
|
+
twine check --strict dist/*
|
|
40
|
+
- name: Test the built wheel
|
|
41
|
+
run: |
|
|
42
|
+
python -m venv /tmp/clean
|
|
43
|
+
/tmp/clean/bin/pip install "$(ls dist/*.whl)[excel]" pytest
|
|
44
|
+
/tmp/clean/bin/python -m pytest -q -p no:cacheprovider
|
|
45
|
+
- uses: actions/upload-artifact@v4
|
|
46
|
+
with:
|
|
47
|
+
name: dist
|
|
48
|
+
path: dist/
|
|
49
|
+
|
|
50
|
+
publish-testpypi:
|
|
51
|
+
name: Publish to TestPyPI
|
|
52
|
+
if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
|
|
53
|
+
needs: build
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
environment:
|
|
56
|
+
name: testpypi
|
|
57
|
+
url: https://test.pypi.org/p/shadowprice
|
|
58
|
+
permissions:
|
|
59
|
+
id-token: write
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/download-artifact@v4
|
|
62
|
+
with:
|
|
63
|
+
name: dist
|
|
64
|
+
path: dist/
|
|
65
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
66
|
+
with:
|
|
67
|
+
repository-url: https://test.pypi.org/legacy/
|
|
68
|
+
|
|
69
|
+
publish-pypi:
|
|
70
|
+
name: Publish to PyPI
|
|
71
|
+
if: github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
|
|
72
|
+
needs: build
|
|
73
|
+
runs-on: ubuntu-latest
|
|
74
|
+
environment:
|
|
75
|
+
name: pypi
|
|
76
|
+
url: https://pypi.org/p/shadowprice
|
|
77
|
+
permissions:
|
|
78
|
+
id-token: write
|
|
79
|
+
steps:
|
|
80
|
+
- uses: actions/download-artifact@v4
|
|
81
|
+
with:
|
|
82
|
+
name: dist
|
|
83
|
+
path: dist/
|
|
84
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0 — 2026-11-15
|
|
4
|
+
|
|
5
|
+
First public release (alpha).
|
|
6
|
+
|
|
7
|
+
* CI: tests on Linux, Windows and macOS with Python 3.11–3.14, a job with the oldest
|
|
8
|
+
supported dependencies, and a job that builds the wheel and tests the installed package.
|
|
9
|
+
* README: install without git; note on installing into an IDE's environment.
|
|
10
|
+
* Release workflow: publishing a GitHub Release uploads to PyPI via trusted publishing;
|
|
11
|
+
manual runs can upload to TestPyPI.
|
|
12
|
+
|
|
13
|
+
* Project and scenario model with incremental (with/without) analysis.
|
|
14
|
+
* Financial analysis: FNPV/FRR on investment (C) and on national capital (K), financial
|
|
15
|
+
sustainability, funding gap rate; loan schedules (annuity/linear, grace periods).
|
|
16
|
+
* Economic analysis: fiscal corrections, tariff exclusion, conversion factors under
|
|
17
|
+
domestic or world numeraire, ENPV, EIRR, B/C, ENPV profile, distribution of net benefits
|
|
18
|
+
by stakeholder.
|
|
19
|
+
* National parameters: SCF, SERF, shadow wage factor, traded-input CF, Ramsey SDR.
|
|
20
|
+
* Method presets: `eu_2014`, `adb_2017`, `wb_2016`; custom presets from TOML files.
|
|
21
|
+
* Risk analysis: sensitivity elasticities, critical variables, switching values, scenario
|
|
22
|
+
NPV, Monte Carlo (triangular, uniform, normal, discrete; Gaussian-copula correlation).
|
|
23
|
+
* Benefit helpers: willingness to pay, consumer surplus (rule of half), time savings,
|
|
24
|
+
avoided costs, emissions, price paths.
|
|
25
|
+
* Reporting: summary and flow tables, Excel export with English and Russian labels.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
cff-version: 1.2.0
|
|
2
|
+
message: "If you use this software, please cite it as below."
|
|
3
|
+
title: "shadowprice: economic cost-benefit analysis of investment projects in Python"
|
|
4
|
+
type: software
|
|
5
|
+
authors:
|
|
6
|
+
- family-names: Iskakov
|
|
7
|
+
given-names: Serik
|
|
8
|
+
version: 0.1.0
|
|
9
|
+
license: MIT
|
|
10
|
+
repository-code: "https://github.com/iskakovs/shadowprice"
|
|
11
|
+
keywords:
|
|
12
|
+
- cost-benefit analysis
|
|
13
|
+
- project appraisal
|
|
14
|
+
- shadow prices
|
|
15
|
+
- public investment
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Contributing to shadowprice
|
|
2
|
+
|
|
3
|
+
## Development setup
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
git clone https://github.com/iskakovs/shadowprice
|
|
7
|
+
cd shadowprice
|
|
8
|
+
python -m venv .venv && source .venv/bin/activate
|
|
9
|
+
pip install -e ".[dev]"
|
|
10
|
+
pytest
|
|
11
|
+
ruff check . && ruff format --check .
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Ground rules
|
|
15
|
+
|
|
16
|
+
* **Every parameter has a source.** Discount rates, conversion factors and reference
|
|
17
|
+
periods must cite the guideline, edition and section they come from.
|
|
18
|
+
* **Every formula has a test** with an expected value computed by hand or taken from a
|
|
19
|
+
published worked example.
|
|
20
|
+
* Keep the core dependencies to `numpy` and `pandas`.
|
|
21
|
+
|
|
22
|
+
## Adding a method preset
|
|
23
|
+
|
|
24
|
+
Presets live in `src/shadowprice/presets/<id>.toml`. Top-level keys must come before any
|
|
25
|
+
table (`[sdr_options]`, `[reference_periods]`).
|
|
26
|
+
|
|
27
|
+
```toml
|
|
28
|
+
id = "kz_example" # file name without .toml
|
|
29
|
+
title = "Short title"
|
|
30
|
+
source = "Full reference: issuer, document, date, section"
|
|
31
|
+
year = 2025
|
|
32
|
+
numeraire = "domestic" # "domestic" or "world"
|
|
33
|
+
financial_discount_rate = 0.05 # optional
|
|
34
|
+
social_discount_rate = 0.08 # or use [sdr_options] / sdr_rule = "ramsey"
|
|
35
|
+
default_cf = 1.0
|
|
36
|
+
critical_threshold = 0.01
|
|
37
|
+
notes = ["Anything users must know when applying the method."]
|
|
38
|
+
|
|
39
|
+
[sdr_options] # optional alternative rates
|
|
40
|
+
general = 0.08
|
|
41
|
+
|
|
42
|
+
[reference_periods] # years, [min, max]
|
|
43
|
+
water_supply_sanitation = [25, 30]
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Add a test in `tests/test_conversion_methods.py` that loads the preset and checks its
|
|
47
|
+
values, and list it in the README table.
|
|
48
|
+
|
|
49
|
+
## Pull requests
|
|
50
|
+
|
|
51
|
+
1. Open an issue describing the change.
|
|
52
|
+
2. Add tests and documentation.
|
|
53
|
+
3. Make sure `pytest` and `ruff` pass.
|
|
54
|
+
4. Add a line to `CHANGELOG.md`.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Serik Iskakov
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: shadowprice
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Economic cost-benefit analysis of investment projects: financial analysis, shadow prices, ENPV/EIRR and risk analysis following EU, ADB and World Bank methods.
|
|
5
|
+
Project-URL: Homepage, https://github.com/iskakovs/shadowprice
|
|
6
|
+
Project-URL: Issues, https://github.com/iskakovs/shadowprice/issues
|
|
7
|
+
Author: Serik Iskakov
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Keywords: EIRR,ENPV,conversion factors,cost-benefit analysis,economic analysis,infrastructure,project appraisal,public investment,shadow prices
|
|
11
|
+
Classifier: Development Status :: 3 - Alpha
|
|
12
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
21
|
+
Classifier: Topic :: Scientific/Engineering
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Requires-Dist: numpy>=1.24
|
|
24
|
+
Requires-Dist: pandas>=2.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: openpyxl>=3.1; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-cov>=5; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.5; extra == 'dev'
|
|
30
|
+
Provides-Extra: excel
|
|
31
|
+
Requires-Dist: openpyxl>=3.1; extra == 'excel'
|
|
32
|
+
Description-Content-Type: text/markdown
|
|
33
|
+
|
|
34
|
+
# shadowprice
|
|
35
|
+
|
|
36
|
+
[](https://github.com/iskakovs/shadowprice/actions/workflows/ci.yml)
|
|
37
|
+
[](https://pypi.org/project/shadowprice/)
|
|
38
|
+
[](https://pypi.org/project/shadowprice/)
|
|
39
|
+
[](LICENSE)
|
|
40
|
+
|
|
41
|
+
**Economic cost-benefit analysis of investment projects in Python** — financial analysis,
|
|
42
|
+
shadow prices and conversion factors, ENPV/EIRR/B-C and risk analysis, following the
|
|
43
|
+
methods of the European Commission, the Asian Development Bank and the World Bank.
|
|
44
|
+
|
|
45
|
+
> Status: alpha (v0.1.0). The API may change before 1.0.
|
|
46
|
+
|
|
47
|
+
## Why
|
|
48
|
+
|
|
49
|
+
Economic appraisal of public investment (roads, water, energy, municipal infrastructure,
|
|
50
|
+
PPPs) is still done almost entirely in spreadsheets: discount factors, conversion factors
|
|
51
|
+
and risk simulations are rebuilt by hand for every project, and results are hard to audit
|
|
52
|
+
or reproduce. Open-source tools cover only fragments — a road-only CBA module, the UK
|
|
53
|
+
Green Book in R, health-economics packages — but not the EU / development-bank method.
|
|
54
|
+
|
|
55
|
+
`shadowprice` turns that method into tested, reusable code:
|
|
56
|
+
|
|
57
|
+
* **Methods as data** – EU 2014 guide, ADB 2017 and World Bank 2016 rules ship as versioned
|
|
58
|
+
presets with their sources. Run the same project under different methods, or add your
|
|
59
|
+
own national rules as a TOML file.
|
|
60
|
+
* **Full pipeline** – with/without (incremental) scenarios → financial analysis →
|
|
61
|
+
fiscal corrections → conversion to economic prices → ENPV, EIRR, B/C → risk analysis →
|
|
62
|
+
distribution of net benefits.
|
|
63
|
+
* **Risk analysis without add-ins** – exact sensitivity elasticities, critical variables,
|
|
64
|
+
switching values and vectorised Monte Carlo with correlated inputs.
|
|
65
|
+
* **Auditable output** – every line keeps its conversion factor, tax share and source;
|
|
66
|
+
results export to Excel with English or Russian labels.
|
|
67
|
+
|
|
68
|
+
## Install
|
|
69
|
+
|
|
70
|
+
From PyPI:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
pip install "shadowprice[excel]"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The latest development version from GitHub (requires [git](https://git-scm.com)):
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
pip install "shadowprice[excel] @ git+https://github.com/iskakovs/shadowprice"
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Without git, from the source archive:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install "shadowprice[excel] @ https://github.com/iskakovs/shadowprice/archive/refs/heads/main.zip"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Python 3.11+. Core dependencies: `numpy` (≥ 1.24) and `pandas` (≥ 2.0); the `excel` extra adds
|
|
89
|
+
`openpyxl`. CI runs the test suite on Linux, Windows and macOS with Python 3.11–3.14, with
|
|
90
|
+
the oldest supported dependencies, and against the built wheel.
|
|
91
|
+
|
|
92
|
+
> **Using an IDE (PyCharm, VS Code)?** Install into the interpreter your project uses — e.g.
|
|
93
|
+
> from the IDE's terminal, where the project's virtual environment is already active.
|
|
94
|
+
|
|
95
|
+
## Quick start
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
import shadowprice as sp
|
|
99
|
+
from shadowprice import benefits as bf
|
|
100
|
+
|
|
101
|
+
p = sp.Project(
|
|
102
|
+
"Water network",
|
|
103
|
+
start_year=2026,
|
|
104
|
+
horizon=30,
|
|
105
|
+
currency="mln KZT",
|
|
106
|
+
sector="water_supply_sanitation",
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
# With-project scenario (market prices, constant 2026 prices)
|
|
110
|
+
w = p.scenario("with")
|
|
111
|
+
w.investment("Imported pipes", {2026: 1800, 2027: 1200}, cf="traded", tax_share=0.12, group="capex")
|
|
112
|
+
w.investment("Civil works", {2026: 900, 2027: 700}, cf="non_traded", tax_share=0.12, group="capex")
|
|
113
|
+
w.om("Operation", 215, start=2028, group="opex")
|
|
114
|
+
w.revenue("Tariffs", 570, start=2028, group="demand") # excluded from economic analysis
|
|
115
|
+
w.benefit("Willingness to pay", bf.willingness_to_pay(6.0, 160), start=2028, group="demand")
|
|
116
|
+
w.residual_straight_line(45)
|
|
117
|
+
|
|
118
|
+
# Counterfactual (business as usual); the analysis uses with - without
|
|
119
|
+
wo = p.scenario("without")
|
|
120
|
+
wo.om("Emergency repairs", 290, group="opex")
|
|
121
|
+
wo.revenue("Tariffs (current)", 430, group="demand")
|
|
122
|
+
wo.benefit("Willingness to pay (current)", bf.willingness_to_pay(4.5, 160), group="demand")
|
|
123
|
+
|
|
124
|
+
fin = p.financial(rate=0.04) # FNPV/FRR(C), funding gap
|
|
125
|
+
eco = p.economic("adb_2017", national=sp.NationalParameters(serf=1.05))
|
|
126
|
+
|
|
127
|
+
print(fin.summary())
|
|
128
|
+
print(eco.summary()) # ENPV, EIRR, B/C
|
|
129
|
+
print(sp.sensitivity(eco)) # critical variables, switching values
|
|
130
|
+
|
|
131
|
+
mc = sp.monte_carlo(
|
|
132
|
+
eco, {"capex": sp.Triangular(-0.05, 0, 0.35), "demand": sp.Normal(0, 0.15)}, n=10_000, seed=1
|
|
133
|
+
)
|
|
134
|
+
print(f"P(ENPV < 0) = {mc.prob_negative:.1%}")
|
|
135
|
+
|
|
136
|
+
sp.report.to_excel("cba.xlsx", financial=fin, economic=eco, montecarlo=mc, lang="ru")
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
A complete worked example (financing, loans, sustainability, distribution analysis) is in
|
|
140
|
+
[`examples/water_network.py`](examples/water_network.py).
|
|
141
|
+
|
|
142
|
+
## Core concepts
|
|
143
|
+
|
|
144
|
+
**Lines.** Every cash flow is a line with a *kind* — `investment`, `replacement`, `om`,
|
|
145
|
+
`revenue`, `residual` (financial analysis) or `benefit`, `external_cost` (economic only).
|
|
146
|
+
Values are positive magnitudes in market prices; the sign follows the kind.
|
|
147
|
+
|
|
148
|
+
**Scenarios.** Lines live in a `"with"` scenario and, optionally, a `"without"`
|
|
149
|
+
counterfactual. All indicators are computed on the increment, so avoided costs in the
|
|
150
|
+
counterfactual become benefits automatically.
|
|
151
|
+
|
|
152
|
+
**Economic prices.** For each line: `economic = market × (1 − tax_share) × CF`. Revenues
|
|
153
|
+
(tariffs) are transfers and are dropped unless flagged `as_benefit=True`; benefits are
|
|
154
|
+
entered directly at economic prices.
|
|
155
|
+
|
|
156
|
+
| CF tag | Domestic numeraire (ADB, WB) | World numeraire (EU 2014) |
|
|
157
|
+
|---|---|---|
|
|
158
|
+
| `"default"` | method default (1.0) | method default (1.0) |
|
|
159
|
+
| `"traded"` | SERF | 1.0 (border prices) |
|
|
160
|
+
| `"non_traded"` | 1.0 | SCF |
|
|
161
|
+
| `"labour_*"` | shadow wage factor | shadow wage factor × SCF |
|
|
162
|
+
| any other tag | `NationalParameters(factors=...)` | same |
|
|
163
|
+
| a number | used as is | used as is |
|
|
164
|
+
|
|
165
|
+
**Methods vs national parameters.** A `Method` holds a guideline's rules (discount rates,
|
|
166
|
+
numeraire, reference periods, critical-variable threshold). Country values (SCF/SERF,
|
|
167
|
+
shadow wages, specific CFs) go in `NationalParameters`. Helpers estimate them:
|
|
168
|
+
`sp.scf(...)`, `sp.shadow_wage_factor(...)`, `sp.ramsey_sdr(...)`.
|
|
169
|
+
|
|
170
|
+
**Risk groups.** Each line has a `group` (default: its kind). Sensitivity and Monte Carlo
|
|
171
|
+
vary groups as relative changes; because NPV is linear in each group, elasticities and
|
|
172
|
+
switching values are exact.
|
|
173
|
+
|
|
174
|
+
## Bundled methods
|
|
175
|
+
|
|
176
|
+
| id | Source | Numeraire | Discount rates |
|
|
177
|
+
|---|---|---|---|
|
|
178
|
+
| `eu_2014` | EC DG REGIO, *Guide to CBA of Investment Projects* (2014) | world | financial 4% real; social 5% (Cohesion) / 3% (other) |
|
|
179
|
+
| `adb_2017` | ADB, *Guidelines for the Economic Analysis of Projects* (2017) | domestic | 9% (EOCC) |
|
|
180
|
+
| `wb_2016` | World Bank, *Discounting Costs and Benefits in Economic Analysis* (2016) | domestic | Ramsey rule: SDR = 2 × per-capita growth |
|
|
181
|
+
|
|
182
|
+
`sp.method("eu_2014").reference_period("roads")` returns the recommended reference period
|
|
183
|
+
`(25, 30)`. Custom or national methods load from a TOML file: `sp.method("path/to/kz.toml")`.
|
|
184
|
+
|
|
185
|
+
## Conventions
|
|
186
|
+
|
|
187
|
+
* The first year of the reference period is `t = 0` and is not discounted.
|
|
188
|
+
* Use constant (real) prices with real discount rates; `sp.discounting.deflate` converts
|
|
189
|
+
nominal series.
|
|
190
|
+
* B/C = PV of lines with positive economic PV ÷ PV of lines with negative economic PV.
|
|
191
|
+
* When cash flows have several IRRs, the lowest is returned with a warning; rely on NPV.
|
|
192
|
+
|
|
193
|
+
## Roadmap
|
|
194
|
+
|
|
195
|
+
* **v0.2** – validation suite reproducing published worked examples (EU guide case
|
|
196
|
+
studies, ADB conversion-factor water example); Kazakh labels; live-formula Excel export;
|
|
197
|
+
estimation of national parameters (SCF/SERF, shadow wage) from trade and labour data.
|
|
198
|
+
* **v0.3** – `ppp` module: public sector comparator, shadow bid, risk transfer, value for
|
|
199
|
+
money, fiscal commitments.
|
|
200
|
+
* **v0.4** – `rab` module: regulatory asset base roll-forward, WACC, building blocks,
|
|
201
|
+
tariff path.
|
|
202
|
+
|
|
203
|
+
## Contributing
|
|
204
|
+
|
|
205
|
+
Contributions are welcome, especially new method presets and national parameters with
|
|
206
|
+
documented sources. See [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
207
|
+
|
|
208
|
+
## Citation
|
|
209
|
+
|
|
210
|
+
If you use `shadowprice` in research, please cite it (see [CITATION.cff](CITATION.cff)).
|
|
211
|
+
|
|
212
|
+
## License
|
|
213
|
+
|
|
214
|
+
MIT © 2026 Serik Iskakov
|