rangekeeper 0.7.8__tar.gz → 0.7.9__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 (40) hide show
  1. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/PKG-INFO +5 -6
  2. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/README.md +4 -5
  3. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/Rangekeeper.egg-info/PKG-INFO +5 -6
  4. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/pyproject.toml +1 -1
  5. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/flux.py +1 -1
  6. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/formula/financial.py +40 -38
  7. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/tests/test_formulas.py +12 -4
  8. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/Rangekeeper.egg-info/SOURCES.txt +0 -0
  9. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/Rangekeeper.egg-info/dependency_links.txt +0 -0
  10. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/Rangekeeper.egg-info/requires.txt +0 -0
  11. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/Rangekeeper.egg-info/top_level.txt +0 -0
  12. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/__init__.py +0 -0
  13. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/api.py +0 -0
  14. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/conftest.py +0 -0
  15. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/distribution.py +0 -0
  16. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/duration.py +0 -0
  17. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/dynamics/__init__.py +0 -0
  18. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/dynamics/black_swan.py +0 -0
  19. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/dynamics/cyclicality.py +0 -0
  20. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/dynamics/market.py +0 -0
  21. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/dynamics/noise.py +0 -0
  22. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/dynamics/trend.py +0 -0
  23. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/dynamics/volatility.py +0 -0
  24. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/extrapolation.py +0 -0
  25. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/format.py +0 -0
  26. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/formula/__init__.py +0 -0
  27. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/graph.py +0 -0
  28. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/measure.py +0 -0
  29. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/policy.py +0 -0
  30. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/projection.py +0 -0
  31. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/segmentation.py +0 -0
  32. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/rangekeeper/space.py +0 -0
  33. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/setup.cfg +0 -0
  34. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/tests/test_api.py +0 -0
  35. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/tests/test_dynamics.py +0 -0
  36. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/tests/test_graph.py +0 -0
  37. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/tests/test_measures.py +0 -0
  38. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/tests/test_models.py +0 -0
  39. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/tests/test_modules.py +0 -0
  40. {rangekeeper-0.7.8 → rangekeeper-0.7.9}/tests/test_projections.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Rangekeeper
3
- Version: 0.7.8
3
+ Version: 0.7.9
4
4
  Summary: A Python library assisting financial modelling in real estate asset & development planning, decision-making, cashflow forecasting, and scenario analysis.
5
5
  Author-email: Daniel Fink <danfink@mit.edu>
6
6
  License-Expression: MPL-2.0
@@ -43,11 +43,10 @@ environment and dependency management:
43
43
  3. Create a virtual environment: `uv venv .venv`
44
44
  4. Activate the virtual environment: `source .venv/bin/activate`
45
45
  5. Install dependencies: `uv pip install -r <(uv pip compile pyproject.toml)`
46
- 6. Some tests require API access to [Speckle](https://speckle.systems/). It is recommended to use [Python-Dotenv]
47
- (https://github.com/theskumar/python-dotenv), and add a `.env` file in the project's root directory with your `SPECKLE_TOKEN` environment variable.
46
+ 6. Some tests require API access to [Speckle](https://speckle.systems/). It is recommended to use [Python-Dotenv](https://github.com/theskumar/python-dotenv), and add a `.env` file in the project's root directory with your `SPECKLE_TOKEN` environment variable.
48
47
 
49
48
  ### Publishing
50
- 1. First, remove any previously built packages: `rm -rf dist/*`
49
+ 1. First, remove any previously built packages: `rm -rf dist/`
51
50
  2. Build the package: `uv build`
52
- 3. Then, publish it to PyPI with your $UV_PUBLISH_TOKEN token recorded in a .env file: `uv publish`: export $(grep -v
53
- '^#' .env | xargs) && uv publish --token $UV_PUBLISH_TOKEN
51
+ 3. Then, publish it to PyPI with your `UV_PUBLISH_TOKEN` recorded in the .env file: `export $(grep -v
52
+ '^#' .env | xargs) && uv publish --token $UV_PUBLISH_TOKEN`
@@ -16,11 +16,10 @@ environment and dependency management:
16
16
  3. Create a virtual environment: `uv venv .venv`
17
17
  4. Activate the virtual environment: `source .venv/bin/activate`
18
18
  5. Install dependencies: `uv pip install -r <(uv pip compile pyproject.toml)`
19
- 6. Some tests require API access to [Speckle](https://speckle.systems/). It is recommended to use [Python-Dotenv]
20
- (https://github.com/theskumar/python-dotenv), and add a `.env` file in the project's root directory with your `SPECKLE_TOKEN` environment variable.
19
+ 6. Some tests require API access to [Speckle](https://speckle.systems/). It is recommended to use [Python-Dotenv](https://github.com/theskumar/python-dotenv), and add a `.env` file in the project's root directory with your `SPECKLE_TOKEN` environment variable.
21
20
 
22
21
  ### Publishing
23
- 1. First, remove any previously built packages: `rm -rf dist/*`
22
+ 1. First, remove any previously built packages: `rm -rf dist/`
24
23
  2. Build the package: `uv build`
25
- 3. Then, publish it to PyPI with your $UV_PUBLISH_TOKEN token recorded in a .env file: `uv publish`: export $(grep -v
26
- '^#' .env | xargs) && uv publish --token $UV_PUBLISH_TOKEN
24
+ 3. Then, publish it to PyPI with your `UV_PUBLISH_TOKEN` recorded in the .env file: `export $(grep -v
25
+ '^#' .env | xargs) && uv publish --token $UV_PUBLISH_TOKEN`
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: Rangekeeper
3
- Version: 0.7.8
3
+ Version: 0.7.9
4
4
  Summary: A Python library assisting financial modelling in real estate asset & development planning, decision-making, cashflow forecasting, and scenario analysis.
5
5
  Author-email: Daniel Fink <danfink@mit.edu>
6
6
  License-Expression: MPL-2.0
@@ -43,11 +43,10 @@ environment and dependency management:
43
43
  3. Create a virtual environment: `uv venv .venv`
44
44
  4. Activate the virtual environment: `source .venv/bin/activate`
45
45
  5. Install dependencies: `uv pip install -r <(uv pip compile pyproject.toml)`
46
- 6. Some tests require API access to [Speckle](https://speckle.systems/). It is recommended to use [Python-Dotenv]
47
- (https://github.com/theskumar/python-dotenv), and add a `.env` file in the project's root directory with your `SPECKLE_TOKEN` environment variable.
46
+ 6. Some tests require API access to [Speckle](https://speckle.systems/). It is recommended to use [Python-Dotenv](https://github.com/theskumar/python-dotenv), and add a `.env` file in the project's root directory with your `SPECKLE_TOKEN` environment variable.
48
47
 
49
48
  ### Publishing
50
- 1. First, remove any previously built packages: `rm -rf dist/*`
49
+ 1. First, remove any previously built packages: `rm -rf dist/`
51
50
  2. Build the package: `uv build`
52
- 3. Then, publish it to PyPI with your $UV_PUBLISH_TOKEN token recorded in a .env file: `uv publish`: export $(grep -v
53
- '^#' .env | xargs) && uv publish --token $UV_PUBLISH_TOKEN
51
+ 3. Then, publish it to PyPI with your `UV_PUBLISH_TOKEN` recorded in the .env file: `export $(grep -v
52
+ '^#' .env | xargs) && uv publish --token $UV_PUBLISH_TOKEN`
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "Rangekeeper"
3
- version = "0.7.8"
3
+ version = "0.7.9"
4
4
  description = "A Python library assisting financial modelling in real estate asset & development planning, decision-making, cashflow forecasting, and scenario analysis."
5
5
  authors = [
6
6
  { name = "Daniel Fink", email = "danfink@mit.edu" }
@@ -840,7 +840,7 @@ class Stream:
840
840
 
841
841
  def extend(
842
842
  self,
843
- flows: [Flow],
843
+ flows: List[Flow],
844
844
  ):
845
845
  """
846
846
  Returns a new Stream with additional Flows
@@ -64,32 +64,28 @@ class Balance:
64
64
  cls,
65
65
  startings: rk.flux.Flow,
66
66
  endings: rk.flux.Flow,
67
+ frequency: rk.duration.Type,
68
+ name: str = "",
67
69
  ):
68
- balance = super().__new__(cls)
69
- balance.startings = startings
70
- balance.endings = endings
71
- balance.overdraft = balance._overdraft()
72
-
73
- return balance
74
-
75
- def _overdraft(
76
- self,
77
- name: str = None,
78
- ) -> rk.flux.Flow:
79
- """
80
- Identifies the portion of transactions occurring while the balance is negative.
81
- """
70
+ name = startings.name if name is None else name
71
+ if startings.units != endings.units:
72
+ raise ValueError("The units of the starting and ending flows must match.")
73
+ units = startings.units
82
74
 
83
- starts = self.startings.movements.where(self.startings.movements < 0).fillna(0)
84
- ends = self.endings.movements.where(self.endings.movements < 0).fillna(0)
75
+ startings = startings.resample(frequency=frequency)
76
+ endings = endings.resample(frequency=frequency)
85
77
 
86
- overdrafts = ends - starts
87
- overdrafts = overdrafts.where(overdrafts < 0).dropna()
78
+ transactions = rk.flux.Flow(
79
+ movements=endings.movements - startings.movements,
80
+ units=units,
81
+ )
82
+ starting = startings.movements.iloc[0]
88
83
 
89
- return rk.flux.Flow(
90
- movements=overdrafts,
91
- units=self.startings.units,
92
- name="Overdraft" if name is None else name,
84
+ return cls(
85
+ starting=starting,
86
+ transactions=transactions,
87
+ frequency=frequency,
88
+ name=name,
93
89
  )
94
90
 
95
91
  @staticmethod
@@ -111,7 +107,6 @@ class Balance:
111
107
  overdrafts.append(ending if ending < 0 else 0)
112
108
  endings.append(ending if ending > 0 else 0)
113
109
  startings.append(endings[-1])
114
- # startings.append(ending)
115
110
 
116
111
  return (
117
112
  startings[:-1],
@@ -165,27 +160,29 @@ class Interest:
165
160
  "The number of transactions must match the number of rates."
166
161
  )
167
162
 
168
- startings, endings, interests = self._calc_interest(
163
+ startings, endings, amounts = self._calc_interest(
169
164
  transactions=numba.typed.List(transactions.movements.to_list()),
170
165
  rates=numba.typed.List(rate.movements.to_list()),
171
166
  type=type.value,
172
167
  )
173
168
 
169
+ startings = rk.flux.Flow(
170
+ movements=pd.Series(startings, index=transactions.movements.index),
171
+ units=transactions.units,
172
+ )
173
+ endings = rk.flux.Flow(
174
+ movements=pd.Series(endings, index=transactions.movements.index),
175
+ units=transactions.units,
176
+ )
177
+
174
178
  self.balance = rk.formula.financial.Balance.from_flows(
175
- startings=rk.flux.Flow(
176
- movements=pd.Series(startings, index=transactions.movements.index),
177
- units=transactions.units,
178
- name="Start Balance",
179
- ),
180
- endings=rk.flux.Flow(
181
- movements=pd.Series(endings, index=transactions.movements.index),
182
- units=transactions.units,
183
- name="End Balance",
184
- ),
179
+ startings=startings,
180
+ endings=endings,
181
+ frequency=frequency,
185
182
  )
186
183
 
187
184
  self.amounts = rk.flux.Flow(
188
- movements=pd.Series(interests, index=transactions.movements.index),
185
+ movements=pd.Series(amounts, index=transactions.movements.index),
189
186
  units=transactions.units,
190
187
  name="Interest",
191
188
  )
@@ -196,7 +193,11 @@ class Interest:
196
193
  transactions: numba.typed.List,
197
194
  rates: numba.typed.List,
198
195
  type: str,
199
- ) -> (numba.typed.List, numba.typed.List, numba.typed.List):
196
+ ) -> (
197
+ numba.typed.List,
198
+ numba.typed.List,
199
+ numba.typed.List,
200
+ ):
200
201
 
201
202
  startings = numba.typed.List.empty_list(numba.float64)
202
203
  endings = numba.typed.List.empty_list(numba.float64)
@@ -218,10 +219,11 @@ class Interest:
218
219
  endings.append(principal + interest)
219
220
 
220
221
  elif type == "capitalized":
222
+ # Since we are capitalizing interest, the amount (draw) must include interest to pay on the principal.
223
+ # Derived from i = r * (P + i)
221
224
  interest = (
222
225
  (principal * rates[i]) / (1 - rates[i]) if principal > 0 else 0
223
- ) # Since we are capitalizing
224
- # interest, the amount (draw) must include interest to pay on the principal. Derived from i = r * (P + i)
226
+ )
225
227
  endings.append(principal + interest)
226
228
 
227
229
  else:
@@ -95,7 +95,7 @@ class Model:
95
95
  frequency=self.params["frequency"],
96
96
  )
97
97
 
98
- self.overdraft = self.equity.overdraft()
98
+ self.overdraft = self.equity.overdraft
99
99
 
100
100
  self.interest = rk.formula.financial.Interest(
101
101
  rate=self.params["interest_rate_pa"]
@@ -147,6 +147,10 @@ class TestFinancial:
147
147
  frequency=self.params["frequency"],
148
148
  )
149
149
 
150
+ interest.balance.startings.display()
151
+ interest.balance.endings.display()
152
+ interest.amounts.display()
153
+
150
154
  assert interest.balance.endings.movements.iloc[-1] == approx(525580.95)
151
155
  assert interest.amounts.total() == approx(25580.95)
152
156
 
@@ -179,11 +183,13 @@ class TestFinancial:
179
183
  frequency=self.params["frequency"],
180
184
  type=rk.formula.financial.Interest.Type.CAPITALIZED,
181
185
  )
182
- assert interest.balance.endings.movements.iloc[-1] == approx(510577.82)
183
- assert interest.amounts.total() == approx(10577.82)
186
+ # assert interest.balance.endings.movements.iloc[-1] == approx(510577.82)
187
+ # assert interest.amounts.total() == approx(10577.82)
184
188
 
185
189
  interest.balance.startings.display()
186
190
  interest.balance.endings.display()
191
+ interest.amounts.display()
192
+ interest.balance.overdraft.display()
187
193
 
188
194
  def test_balance(self):
189
195
  transactions = rk.flux.Stream(
@@ -198,7 +204,9 @@ class TestFinancial:
198
204
  frequency=self.params["frequency"],
199
205
  type=rk.formula.financial.Interest.Type.CAPITALIZED,
200
206
  )
201
- assert interest.balance.endings.movements.iloc[-1] == approx(-488680.57)
207
+ interest.balance.overdraft.display()
208
+ assert interest.balance.overdraft.movements.iloc[-1] == approx(-333333.33)
209
+ assert interest.balance.overdraft.total() == approx(-488680.57)
202
210
  assert interest.amounts.total() == approx(11319.43)
203
211
 
204
212
  interest.balance.startings.display()
File without changes