**energoton_py** is a Python package that automates task planning. Init workers with energy capacity, create tasks with energy cost, and the package will find the optimal work plan, considering priorities and relations between tasks.

```pip install energoton```

## About
**What's energoton?**  
Energoton is a formal model, like automatons. Imagine an abstract machine with limited energy and ability to solve abstract tasks, by spending this energy. That's energoton. It walks through the given pool of tasks, modeling the process of work, and finds the best plans.

**Okay, how can I use it?**  
<u>Example 1</u>: You have a team of 5 developers, on average 15 standing tasks and you need to plan scrum sprints every week, considering priorities. Energotons can do it for you.

<u>Example 2</u>: You're developing a game NPC, which is supposed to be able to plan their actions, considering alternatives. Create a bunch of tasks, linked to each other through "Alternative" relations, and energoton will find the best of variants.

<u>Example 3</u>: You have an ETL service, which solves queued tasks (scraping, transform, aggregation, etc.). As tasks are monotonous, you know their cost, so you can make the service work smarter by ordering tasks, instead of going through them blindly.

Shortly, energotons are abstract enough for a wide range of tasks - if there is planning, they'll will give you a shoulder.

## Use Basics
**Task**  
Task is an atomic piece of work to do. Its only required attribute is `cost` - a simple `int`, which represents the amount of energy that must be spent to solve the task.

```python
t = Task(
    cost=4,  # e.g. the task takes 4 hours to solve
    custom_fields={"date_due": "2025-03-15"},
    name="Task name",
)

# your custom_fields are available by their keys
t["date_due"]
```

**Energoton**  
Energoton is a worker that can solve tasks by spending energy. Easy to guess, its required attribute is energy capacity.

```python
e1 = DeterministicEnergoton(
    capacity=8,  # e.g. 8 hours work day
    name="Energoton 1",
)
e2 = NonDeterministicEnergoton(
    capacity=8,
    name="Energoton 2",
)
```
The package supports two types of energotons, designed for different cases:  
  
* _DeterministicEnergoton_ will work on a task only if it has enough energy to solve it.
* _NonDeterministicEnergoton_ can solve a given task partially, putting its energy into the task as a contribution.
  
**Pool**  
A pool represents a group of tasks. It can be a sprint, a project, or just a complex task, which consists of several subtasks. Pools can be embedded into each other for a more complex hierarchy.

```python
t1 = Task(cost=4)
t2 = Task(cost=6)
t3 = Task(cost=3)

pool = Pool(
    children=[t1, t2],
    name="Pool 1"
)

root_pool = Pool()
root_pool.add(pool)
root_pool.add(t3)

t1 = root_pool.get(t1.id)
root_pool.pop(pool.id)
```

**Planner**  
Now, when we are shortly introduced to the main elements, we can start planning our work:
```python
# tasks to be done
Pool(
    children=[
        Task(3),
        Task(5),
        Task(1),
        Task(4),
    ]
)

planner = Planner(pool=pool)

# planner can return several plans,
# e.g. different combinations of
# tasks with the same priorities
plans = planner.build_plans(
    # e.g. a team of 3 employees
    energotons=[
        DeterministicEnergoton(8),
        DeterministicEnergoton(8),
        DeterministicEnergoton(8),
    ])
```
**Cycles**  
Work cycle represents an abstract time unit, during which energotons are working. It may be a work day, a sprint, an entire month - you're free to choose the scale. After a cycle is ended, the planner will recharge energotons, so they could continue working. You can control charges.
```python
e = DeterministicEnergoton(
    # 5 work days, 8 hours each,
    # Tue is a day-off
    capacity=[8, 0, 8, 8, 8]
)

planner = Planner(pool=some_pool)

plans = planner.build_plans(energotons=[e], cycles=5)
```

**Piece of work**  
A plan returned by the planner consists of `dict` objects, each of which represents a piece of planned work with the following fields:
* *task* - target task
* *energy_spent* - amount of energy that's going to be spent
* *assignee* - energoton that's going to spend this energy
* *cycle* - number of the work cycle, at which work is planned to be done

**Advanced Features**  
The package includes more interesting functionality e.g. **Relations**, which allow to run planning in more complex cases. To see how they work (with detailed comments), check out the [samples](https://github.com/IlyaFaer/energoton/tree/main/samples).

## Code Quality and Performance
The package source code is written in a quite bad fashion. The reason for that is straightforward - performance. Since the package does a lot of iterative calculations, a huge recoil shows up in the form of seconds and even tens of seconds wasted for dynamic typing resolution. To speed things up, wiping up my tears, I wrote the code in this unpleasant way. Accept it, as I did.

## Early Version Warning
This package is in **early beta** development stage. It may contain errors, performance issues, inconveniences and can introduce breaking changes. Use it cautiously and report any findings and ideas.

## Development Plan
As the package is quite raw, some time will be spent to sharpen it, fix mistakes, improve performance and docs. Star the repository and stay tuned, as there is more to come.  

When the package is stable, a PyPl release is going to be made.  
  
Next, there is an idea to implement a REST API on a fast programming language (it'll be Go, most likely) with a database and build a Docker image, to make it possible for users to easily unroll a language-independent microservice based on energotons.  

## What's Next
* Read the Energoton formal model [specification](https://docs.google.com/document/d/1qSr1LRrfzFkJYoJUsLwi7DLwz6v3poVYMY_cnEyMLn8/edit?usp=sharing) for comprehensive understanding
* Study [the License](https://github.com/IlyaFaer/energoton_py/blob/main/LICENSE.md) to check the appropriate use cases
