Files
Portfolio-Engine/README.md
samantha42 a4c5c4cb1d new readme
2026-03-24 12:49:55 +01:00

341 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Portfolio Engine
A lightweight portfolio and financial data backend written in Go. Tracks companies, currencies, and revenue reports across configurable time periods — replacing spreadsheets with a proper database, REST API, and interactive shell.
## Features
- SQLite database with foreign key enforcement
- REST API on port `8080`
- Interactive shell for managing data without an HTTP client
- Revenue tracking by period (quarterly, half-year, full year)
- Revenue broken down by custom categories (product, location, segment...)
- Health endpoint with DB latency, memory, and uptime stats
## Project Structure
```
.
├── main.go # Entry point — HTTP routes + shell loop
├── app.db # SQLite database (auto-created on first run)
├── build.sh # Build script
├── go.mod / go.sum
└── internal/
├── database/
│ └── main.go # Schema init (InitDB) — all CREATE TABLE statements
├── handlers/
│ ├── main.go # HealthHandler
│ ├── currency.go # AddCurrencyHandler, GetCurrenciesHandler
│ └── revenue.go # AddRevenueEntryHandler, GetRevenueReportHandler, GetRevenueSumHandler
├── model/
│ ├── company.go # Company struct + SQL (InsertCompany, GetAllCompanies)
│ ├── currency.go # Currency struct + SQL (InsertCurrency, GetAllCurrencies)
│ ├── periode.go # Period struct + helpers (QuarterPeriod, HalfYearPeriod, FullYearPeriod)
│ └── revenue.go # Revenue, RevenueReport, RevSum structs + SQL
├── service/
│ ├── main.go # Service wiring
│ ├── company.go # Company business logic
│ ├── currency.go # Currency business logic
│ └── revenue.go # Revenue aggregation logic
└── shell/
├── company.go # add-company, list-companies
├── currency.go # add-currency, list-currency
└── revenue.go # add-revenue, list-revenue, sum-revenue
```
## Getting Started
### Prerequisites
- Go 1.21+
- GCC (required for SQLite CGO bindings)
- Ubuntu/Debian: `sudo apt install gcc`
- macOS: comes with Xcode Command Line Tools
### Install & Run
```bash
git clone git@git.samantha42.xyz:samantha/Portifolio-Engine.git
cd Portifolio-Engine
go mod download
go run main.go
```
Or build first:
```bash
chmod +x build.sh
./build.sh
./Portifolio
```
On startup:
```
Connected to SQLite database
Tables ready
Server running on :8080
Shell ready. Type 'help' for commands.
>
```
The HTTP server and shell run concurrently — the API is live while you type shell commands.
---
## REST API
### `GET /health`
Returns server status, DB connection info, memory usage, and uptime.
```bash
curl http://localhost:8080/health
```
```json
{
"status": "ok",
"uptime": "4m32s",
"database": {
"status": "ok",
"latency": "121µs",
"open_connections": 1,
"in_use": 0,
"idle": 1
},
"memory": {
"alloc_mb": 2.31,
"sys_mb": 9.44,
"num_gc": 3
},
"go_version": "go1.22.0",
"goroutines": 4
}
```
Returns `503` with `"status": "degraded"` if the database is unreachable.
---
### `POST /add/company`
```bash
curl -X POST http://localhost:8080/add/company \
-H "Content-Type: application/json" \
-d '{"name":"Novo Nordisk","shares_outstanding":4442064180,"price":251.00,"currency_id":1}'
```
---
### `GET /companies`
```bash
curl http://localhost:8080/companies
```
---
### `POST /add/currency`
```bash
curl -X POST http://localhost:8080/add/currency \
-H "Content-Type: application/json" \
-d '{"code":"DKK","name":"Danish Krone"}'
```
---
### `GET /currencies`
```bash
curl http://localhost:8080/currencies
```
---
### `POST /add/revenue/entry`
Add a single revenue line to a report. The period is created automatically if it doesn't exist.
```bash
curl -X POST http://localhost:8080/add/revenue/entry \
-H "Content-Type: application/json" \
-d '{
"company_id": 1,
"currency_id": 1,
"period_type": "Q",
"year": 2025,
"index": 1,
"category": "product",
"label": "iPhone",
"value": 69143
}'
```
`period_type` is one of:
| Value | Meaning | `index` range |
|-------|-----------|---------------|
| `Q` | Quarter | 14 |
| `H` | Half-year | 12 |
| `Y` | Full year | 1 |
---
### `GET /revenue/report`
Get all revenue entries for a company and period.
```bash
curl "http://localhost:8080/revenue/report?company_id=1&period_type=Q&year=2025&index=1"
```
---
### `GET /revenue/sum`
Sum all revenue entries for a company across all periods of a given type in a year.
```bash
curl "http://localhost:8080/revenue/sum?company_id=1&period_type=Q&year=2025"
```
---
## Shell Commands
| Command | Description |
|------------------|-----------------------------------------------|
| `add-company` | Add a new company interactively |
| `list-companies` | List all companies with price and share count |
| `add-currency` | Add a new currency interactively |
| `list-currency` | List all currencies with their IDs |
| `add-revenue` | Add a revenue entry interactively |
| `list-revenue` | List revenue entries for a company/period |
| `sum-revenue` | Sum revenue across all periods in a year |
| `help` | Show all available commands |
| `exit` | Quit |
### Example session
```
> add-currency
Code (e.g. DKK): DKK
Name (e.g. Danish Krone): Danish Krone
✓ Currency 'Danish Krone' (DKK) added with ID 1
> add-company
Name: Novo Nordisk
Shares outstanding: 4442064180
Price: 251.00
Currency ID: 1
✓ Company 'Novo Nordisk' added.
> add-revenue
Company ID: 1
Currency ID: 1
Period type (Q/H/Y): Q
Year: 2025
Index (Q: 1-4 | H: 1-2 | Y: 1): 1
Category (product/location/total): product
Label (e.g. iPhone, Americas): Diabetes Care
Value: 54200
✓ Revenue entry added: product / Diabetes Care = 54200.00 (Q1 2025)
> sum-revenue
Company ID: 1
Period type to sum (Q/H/Y): Q
Year: 2025
Revenue Sum — FY2025
Total: 54200.00
By Category:
product 54200.00
By Label:
Diabetes Care 54200.00
> list-companies
ID NAME CURRENCY PRICE SHARES
------------------------------------------------------------
1 Novo Nordisk DKK 251.00 4442064180
```
---
## Database Schema
```sql
CREATE TABLE currencies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
code TEXT NOT NULL UNIQUE,
name TEXT NOT NULL
);
CREATE TABLE companies (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
shares_outstanding INTEGER NOT NULL,
price REAL NOT NULL,
currency_id INTEGER NOT NULL,
FOREIGN KEY (currency_id) REFERENCES currencies(id)
);
CREATE TABLE periods (
id INTEGER PRIMARY KEY AUTOINCREMENT,
type TEXT NOT NULL CHECK(type IN ('Q', 'H', 'Y')),
year INTEGER NOT NULL,
idx INTEGER NOT NULL,
start_date TEXT NOT NULL,
end_date TEXT NOT NULL,
UNIQUE(type, year, idx)
);
CREATE TABLE revenue_reports (
id INTEGER PRIMARY KEY AUTOINCREMENT,
company_id INTEGER NOT NULL,
period_id INTEGER NOT NULL,
FOREIGN KEY (company_id) REFERENCES companies(id),
FOREIGN KEY (period_id) REFERENCES periods(id),
UNIQUE(company_id, period_id)
);
CREATE TABLE revenue_entries (
id INTEGER PRIMARY KEY AUTOINCREMENT,
report_id INTEGER NOT NULL,
currency_id INTEGER NOT NULL,
category TEXT NOT NULL,
label TEXT NOT NULL,
value REAL NOT NULL,
FOREIGN KEY (report_id) REFERENCES revenue_reports(id),
FOREIGN KEY (currency_id) REFERENCES currencies(id)
);
CREATE TABLE category_defs (
id INTEGER PRIMARY KEY AUTOINCREMENT,
company_id INTEGER NOT NULL,
name TEXT NOT NULL,
FOREIGN KEY (company_id) REFERENCES companies(id),
UNIQUE(company_id, name)
);
CREATE TABLE category_labels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category_def_id INTEGER NOT NULL,
label TEXT NOT NULL,
FOREIGN KEY (category_def_id) REFERENCES category_defs(id),
UNIQUE(category_def_id, label)
);
```
---
## Roadmap
- `GET /company/{id}/revenue` — full revenue history for a company
- Price update endpoint
- Market cap calculation (price × shares outstanding)
- Multi-currency conversion
- Frontend dashboard