Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
4d57de6
Initial config of the project structure and depend
arielfernandes Mar 4, 2026
a9981a0
create Models and test db
arielfernandes Mar 5, 2026
ac89232
Test database
arielfernandes Mar 9, 2026
ab23e90
setup lifespan, routers and fix models registry
arielfernandes Mar 9, 2026
d596895
test API integration tests and latency benchmarks
arielfernandes Mar 9, 2026
3df13f0
API routers, settings and config database
arielfernandes Mar 9, 2026
89f7626
config update .gitignore and add .env.example
arielfernandes Mar 9, 2026
6f79dec
create README.md
arielfernandes Mar 9, 2026
006098e
add exclude README.md
arielfernandes Mar 9, 2026
f175269
unit tests for prediction service and services
arielfernandes Mar 9, 2026
44c9c11
prediction endpoint and schemas
arielfernandes Mar 9, 2026
c08cd2c
.gitignore
arielfernandes Mar 9, 2026
ae855bb
add API tests for predict endpoint
arielfernandes Mar 9, 2026
cb37fee
test: add load tests for API endpoints
arielfernandes Mar 9, 2026
8919899
add pytest markers for load tests
arielfernandes Mar 9, 2026
d76575f
uv.lock
arielfernandes Mar 9, 2026
e4fd2fb
refactor database connection
arielfernandes Mar 9, 2026
c01e801
stabilize docker infra and nginx proxy
arielfernandes Mar 9, 2026
52fe7ed
remove sensitive files and DB from tracking
arielfernandes Mar 9, 2026
a779d56
uv add psycopg2-binary
arielfernandes Mar 9, 2026
cab6830
fix init_db
arielfernandes Mar 9, 2026
e7e4058
Merge branch 'ariel-fernandes' into ariel-fernandes-001
arielfernandes Mar 9, 2026
fe5fa36
link deploy and example test predict
arielfernandes Mar 9, 2026
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
1 change: 1 addition & 0 deletions backend-solution/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DATABASE_URL=
27 changes: 27 additions & 0 deletions backend-solution/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
.venv/
__pycache__/
*.pyc

.env

*.pyo
*.db
database.db

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/*

# ruff
.ruff_cache/
1 change: 1 addition & 0 deletions backend-solution/.python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.11
16 changes: 16 additions & 0 deletions backend-solution/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
FROM python:3.11-slim

WORKDIR /app

COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/

COPY pyproject.toml uv.lock* ./

RUN uv sync --no-cache

COPY . .

RUN useradd -m appuser && chown -R appuser /app
USER appuser

CMD ["uv", "run", "fastapi", "run", "app/main.py", "--host", "0.0.0.0", "--port", "8000", "--workers", "3"]
265 changes: 265 additions & 0 deletions backend-solution/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,265 @@
# Signal Processing API

API REST para armazenamento e análise de séries temporais, desenvolvida como solução para o desafio backend da Dynamox.

## Sobre o Projeto

Esta API permite armazenar séries temporais com múltiplos pontos de dados, calcular métricas estatísticas e gerenciar os registros de forma eficiente. Foi construída com foco em performance, mantendo latência média abaixo de 10ms.

## Tecnologias Utilizadas

- Python 3.11
- FastAPI
- SQLAlchemy
- Pydantic
- Pytest

## Requisitos

- Python 3.11 ou superior
- [uv](https://github.com/astral-sh/uv) (gerenciador de pacotes)

## Instalacao

Clone o repositório:

```bash
git clone https://github.com/seu-usuario/developer-challenges.git
cd developer-challenges/backend-solution
```

Instale as dependências:

```bash
uv sync
```

Configure as variáveis de ambiente:

```bash
cp .env.example .env
```

O arquivo `.env` deve conter:

```
DATABASE_URL=sqlite:///./database.db
```

## Executando a Aplicacao

```bash
uv run fastapi dev app/main.py
```

A API estará disponível em `http://localhost:8000`.

A documentação interativa (Swagger) pode ser acessada em `http://localhost:8000/docs`.

## Endpoints

### Criar uma série temporal

```
POST /time-series/
```

Exemplo de requisição:

```bash
curl -X POST http://localhost:8000/time-series/ \
-H "Content-Type: application/json" \
-d '{
"name": "Sensor Temperatura Sala 01",
"data_points": [
{"timestamp": "2024-01-15T08:00:00", "value": 22.5},
{"timestamp": "2024-01-15T08:01:00", "value": 22.7},
{"timestamp": "2024-01-15T08:02:00", "value": 23.1},
{"timestamp": "2024-01-15T08:03:00", "value": 22.9},
{"timestamp": "2024-01-15T08:04:00", "value": 23.4}
]
}'
```

Resposta:

```json
{
"id": 1,
"name": "Sensor Temperatura Sala 01",
"created_at": "2024-01-15T10:30:00",
"data_points": [
{"id": 1, "timestamp": "2024-01-15T08:00:00", "value": 22.5},
{"id": 2, "timestamp": "2024-01-15T08:01:00", "value": 22.7},
{"id": 3, "timestamp": "2024-01-15T08:02:00", "value": 23.1},
{"id": 4, "timestamp": "2024-01-15T08:03:00", "value": 22.9},
{"id": 5, "timestamp": "2024-01-15T08:04:00", "value": 23.4}
]
}
```

### Obter uma série temporal

```
GET /time-series/{id}
```

Exemplo:

```bash
curl http://localhost:8000/time-series/1
```

### Obter métricas de uma série

```
GET /time-series/{id}/metrics
```

Exemplo:

```bash
curl http://localhost:8000/time-series/1/metrics
```

Resposta:

```json
{
"id": 1,
"name": "Sensor Temperatura Sala 01",
"count": 5,
"min_value": 22.5,
"max_value": 23.4,
"mean_value": 22.92,
"std_deviation": 0.35
}
```

### Contar séries temporais

```
GET /time-series/count
```

Exemplo:

```bash
curl http://localhost:8000/time-series/count
```

Resposta:

```json
{
"total": 1
}
```

### Deletar uma série temporal

```
DELETE /time-series/{id}
```

Exemplo:

```bash
curl -X DELETE http://localhost:8000/time-series/1
```

Resposta:

```json
{
"message": "Time series deleted successfully"
}
```

### Prever valores futuros de uma série
```
GET /time-series/{id}/predict?steps=5
```

Exemplo:
```bash
curl http://localhost:8000/time-series/1/predict?steps=5
```

Resposta:
```json
{
"series_id": 1,
"series_name": "Sensor Temperatura Sala 01",
"historical_count": 5,
"steps": 5,
"predictions": [23.6, 23.8, 24.0, 24.2, 24.4]
}
```

## Executando os Testes

Testes unitários e de integração:

```bash
uv run pytest -v
```

Testes com cobertura de código:

```bash
uv run task test
```

O relatório de cobertura será gerado em `htmlcov/index.html`.

Testes de latência (benchmark):

```bash
uv run pytest -v -m benchmark
```

## Estrutura do Projeto

```
backend-solution/
├── app/
│ ├── __init__.py
│ ├── main.py # Configuração do FastAPI
│ ├── database.py # Conexão com banco de dados
│ ├── models.py # Modelos SQLAlchemy
│ ├── schemas.py # Schemas Pydantic
│ ├── settings.py # Configurações da aplicação
│ └── routers/
│ ├── __init__.py
│ └── time_series.py
├── tests/
│ ├── conftest.py # Fixtures do pytest
│ ├── test_db.py # Testes dos modelos
│ ├── test_api.py # Testes dos endpoints
│ └── test_latency.py # Testes de performance
├── .env.example
├── pyproject.toml
└── README.md
```

## Performance

A API foi testada e apresentou os seguintes resultados de latência:

| Endpoint | Latência Média |
|----------|----------------|
| GET / | ~4ms |
| GET /time-series/count | ~8ms |

Todos os endpoints atendem ao requisito de latência inferior a 350ms.

## Deploy

A API está disponível em produção:

- **API:** https://developer-challenges-production-2932.up.railway.app/docs

## Autor

Ariel Fernandes
Empty file.
58 changes: 58 additions & 0 deletions backend-solution/app/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import logging
import time
from functools import lru_cache

from sqlalchemy import create_engine
from sqlalchemy.exc import OperationalError
from sqlalchemy.orm import Session

from app.models import table_registry
from app.settings import Settings

logger = logging.getLogger(__name__)


@lru_cache
def get_settings():
return Settings()


@lru_cache
def get_engine():
settings = get_settings()
return create_engine(settings.DATABASE_URL)


def init_db():
engine = get_engine()
retries = 5

while retries > 0:
try:
table_registry.metadata.create_all(engine, checkfirst=True)
logger.info('Database synchronized successfully!')
break
except OperationalError:
retries -= 1
logger.warning(
f'Database unavailable. Trying again...'
f' ({retries} attempts remaining)'
)
time.sleep(3)

if retries == 0:
logger.error(
'Fatal failure: Could not connect to the'
' database after multiple attempts.'
)
raise ConnectionError('he database did not respond in time.')


def get_session():
engine = get_engine()
with Session(engine) as session:
try:
yield session
except Exception:
session.rollback()
raise
Loading