# Downline utils usage guide

Storage-agnostic Python port of the legacy `DownlineUtilBase` PHP helper. Install the package locally from your Django backend folder with:

```
pip install -e ../packages/downline_utils
```

Then wire it to your data layer via a small repository class and use it to compute placement positions and downline metrics.

## Quick start

```python
from downline_utils import DownlineService, DownlineRepository, ChildPlacement


class DjangoDownlineRepository(DownlineRepository):
    def get_children(self, upline_id: int, *, network: str):
        # Example: replace `Member` with your model and fields
        qs = Member.objects.filter(**{network: upline_id}).values("id", "position")
        return [ChildPlacement(uid=row["id"], position=row["position"]) for row in qs]

    def get_parent(self, uid: int, *, network: str):
        parent = (
            Member.objects.filter(id=uid).values_list(network, flat=True).first()
        )
        return int(parent) if parent is not None else None


repo = DjangoDownlineRepository()
service = DownlineService(repo)

placement = service.calc_next_upline_position_tblr(
    uid=42, network="upline_id", network_position="position", width=2
)
# placement.upline -> upline id to attach to
# placement.position -> slot under that upline (1-based)
```

## Placement strategies

- `calc_next_upline_position_tblr`: top-to-bottom, left-to-right (breadth-first).
- `calc_next_upline_position_tbrl`: top-to-bottom, right-to-left (breadth-first).
- `calc_next_upline_position_level`: balanced by weakest leg per level (left-first).
- `calc_next_upline_position_balanced_rl`: balanced by weakest leg per level (right-first).
- `calc_next_upline_position_balanced_lr`: alias to `calc_next_upline_position_level`.
- `calc_next_upline_position_balanced_llr_rrl`: balanced by weakest leg per level; if a left leg is chosen, search balanced left-to-right; if right, balanced right-to-left.
- `calc_next_network_preferred_position`: follow a preferred slot; when none, falls back to `calc_next_upline_position_level`.

All methods return a `Placement(upline, position)` describing where to attach the next node. `width` is the maximum children per upline and must be `>= 1`.

## Downline queries

- `get_total_downline(uid, network)`: total descendants.
- `get_total_downline_by_leg_level(downline_id, network, level, aggregate=False)`: count at a specific level (or cumulative when `aggregate=True`).
- `get_downline(uid, network)`: list of all descendant ids.
- `is_downline_of(uid, upline_id, which_network)`: ancestry check.

## Integration notes

- Positions are expected to be 1-based integers; the repository should ignore or normalize invalid positions.
- Sorting of children is governed by the `position` you return. If ties are possible, sort deterministically before constructing `ChildPlacement`.
- Keep the service pure: validation, persistence, and side effects belong in your calling code.
- For large networks, consider caching `get_children` and `get_parent` results per request to reduce DB round trips.
