Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions dcaf/streams/cashflows.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"""
Core cashflow abstractions for discounted cash-flow analysis.

Provides CashFlow (immutable data point), CashFlowStream (functional container),
Provides CashFlow (immutable data point), CashFlowStream (functional-style container),
CashFlowGroup (grouped container), and structured cashflow classification fields.
"""

Expand Down Expand Up @@ -254,9 +254,16 @@ class CashFlowStream(BaseStream[CashFlow]):

Preserves insertion order and supports sequence-style operations (iteration,
indexing, slicing, ``len()``) alongside domain-specific helpers for building,
filtering, transforming, grouping, and discounting cashflows. All
mutating-style operations return a new ``CashFlowStream``; the original is
never modified.
filtering, transforming, grouping, and discounting cashflows. Mutating-style
methods such as :meth:`append`, :meth:`extend`, :meth:`filter`, :meth:`apply`,
:meth:`sort`, and :meth:`scale` return a new ``CashFlowStream`` without modifying
the source stream. The container itself is not immutable: callers can mutate its
public :attr:`entries` list directly.

Attributes
----------
entries : list[CashFlow]
Public mutable list containing the stream's frozen ``CashFlow`` values.

Examples
--------
Expand Down
15 changes: 11 additions & 4 deletions dcaf/streams/generation.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"""
Generation stream module for physical energy quantities.

Provides Generation (single data point), GenerationStream (container),
and GenerationGroup (grouped container) for modeling MWh production.
Provides Generation (immutable data point), GenerationStream (functional-style
container), and GenerationGroup (grouped container) for modeling MWh production.
"""

import datetime as dt
Expand Down Expand Up @@ -204,8 +204,15 @@ class GenerationStream(BaseStream[Generation]):
Mirrors the ``CashFlowStream`` pattern for physical energy quantities.
Supports iteration, indexing, slicing, and ``len()``, alongside
domain-specific helpers for building, filtering, transforming, and
converting generation to revenue or cost cashflows. All mutating-style
operations return a new ``GenerationStream``; the original is never modified.
converting generation to revenue or cost cashflows. Mutating-style methods
return a new ``GenerationStream`` without modifying the source stream. The
container itself is not immutable: callers can mutate its public :attr:`entries`
list directly.

Attributes
----------
entries : list[Generation]
Public mutable list containing the stream's frozen ``Generation`` values.

Examples
--------
Expand Down
6 changes: 4 additions & 2 deletions docs/api/streams.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
# Streams

Lower-level financial and generation primitives. `CashFlow`/`CashFlowStream` and
`Generation`/`GenerationStream` are the immutable building blocks the
Lower-level financial and generation primitives. `CashFlow` and `Generation` are
frozen entry values. `CashFlowStream` and `GenerationStream` hold those values in
public, mutable `entries` lists, while their stream-producing methods return new
containers instead of mutating the source. These are the building blocks the
[project builder](project.md) produces under the hood. Reach for them directly when
you need fine-grained control — custom data sources, bespoke incentive structures,
or transformations the builder does not expose.
Expand Down
29 changes: 22 additions & 7 deletions docs/concepts/streams.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ transformation the builder doesn't expose. Streams are also the clearest way to
*what the builder produces under the hood*.
```

DCAF's primitives are two parallel families of immutable, dated values:
DCAF's stream primitives are two parallel families of dated entry values and containers:

| Single value | Container | Grouped container |
|--------------|-----------|-------------------|
Expand All @@ -22,8 +22,8 @@ A `CashFlow` is a frozen record: an `amount`, a `date`, a `label`, an `is_cash`
(cash vs. accrual items like depreciation), a `pro_forma_category` (how it appears in
a statement), and a `tax_treatment` (`TAXABLE`, `DEDUCTIBLE`, or `NONE`).

A `CashFlowStream` is a functional container. Every operation returns a *new* stream,
so chains never mutate the original:
A `CashFlowStream` is a functional-style container. Stream-producing methods return a
*new* stream, so chains using those methods do not mutate the original:

```python
from datetime import date
Expand Down Expand Up @@ -68,11 +68,26 @@ gen.sum() # total MWh
`GenerationGroup` — a dict-like container of sub-streams supporting `aggregate(...)`,
`apply_to_groups(...)`, `filter_groups(...)`, and `ungroup()` to flatten back.

## Immutability and composition
## Entry and container mutation semantics

All data classes are frozen and all operations return new instances. This makes
streams safe to share, easy to reason about, and naturally composable — the same
properties the [`EnergyProject`](energy_project.md) builder relies on.
`CashFlow` and `Generation` are frozen values. Their attributes cannot be reassigned.

`CashFlowStream` and `GenerationStream` are not immutable containers. Their public
`entries` attributes are mutable lists, so callers can modify a stream directly through
that attribute. Methods such as `append()`, `extend()`, `filter()`, `apply()`, `sort()`,
and `scale()` follow a non-mutating contract: they return new streams without changing
the source stream.

```python
original = CashFlowStream([CashFlow(100.0, date(2026, 1, 1))])
scaled = original.scale(2.0)

print(original.entries[0].amount) # 100.0: scale() did not mutate original
print(scaled.entries[0].amount) # 200.0

original.entries.append(CashFlow(50.0, date(2026, 2, 1)))
print(len(original.entries)) # 2: entries itself is mutable
```

```{seealso}
- [Streams API reference](../api/streams.md) for full signatures.
Expand Down