Calcify is an open-source financial application for inventory and currency management, built on Clean Architecture to ensure maintainability, scalability, and mathematical precision using Decimal (preventing floating-point errors).
Calcify follows a strict 4-layer Clean Architecture pattern:
| Layer | Depends on | Banned imports |
|---|---|---|
domain/ |
Python stdlib | Flask, SQLAlchemy, any framework |
use_cases/ |
Domain entities, abstract repo interfaces | ORM models, Flask |
infrastructure/ |
SQLAlchemy, domain entities, interfaces | Flask globals (request, g, session) |
presentation/ |
Flask, domain entities, repo implementations | ORM models directly |
Calcify/
├── app.py # Application Factory (Flask + Babel)
├── domain/ # Business logic (pure Python, no dependencies)
│ ├── models.py # Currency, Product, Transaction, CurrencyRate entities
│ ├── exceptions.py # InvalidExchangeRateError
│ └── services/ # CurrencyConverter (Decimal math, ROUND_HALF_UP)
├── use_cases/ # Orchestration: sales, currency conversion, backup export
├── infrastructure/ # Data layer (SQLAlchemy, SQLite, Alembic migrations)
│ ├── database/
│ │ ├── models.py # ORM models (5 tables)
│ │ ├── session.py # OS-agnostic database path resolution
│ │ └── auto_migrate.py # Automatic schema migrations on startup
│ └── repositories/
│ ├── interfaces.py # Abstract repository interfaces
│ └── sqlalchemy_repos.py # Concrete implementations with domain mapping
├── presentation/ # REST API and Web (Flask Blueprints)
│ ├── api/
│ │ ├── auth.py # Session-based auth, @login_required decorator
│ │ └── routes.py # 16 REST endpoints + 2 locale endpoints
│ ├── web/
│ │ └── routes.py # Web routes (/, /login)
│ └── templates/
│ ├── base.html # Base template with Tailwind CSS
│ ├── index.html # SPA main view
│ └── login.html # Login page
├── static/ # Frontend assets
│ ├── css/ # 7 view-specific CSS files (cyberpunk theme)
│ ├── js/ # 11 plain JS modules (no ES modules, global vars)
│ └── dist/tailwind.css # Built Tailwind CSS
├── tests/ # 256 tests (159 backend + 97 frontend)
│ ├── domain/ # Currency converter, model tests
│ ├── use_cases/ # Sales, conversion, backup tests
│ ├── infrastructure/ # Migrations, session, repository tests
│ ├── presentation/ # App factory, API routes, auth tests
│ └── frontend/ # Jest + jsdom tests for all JS views
├── translations/ # Babel i18n catalogs (Spanish + English)
├── requirements.txt # Python dependencies
├── package.json # Frontend build scripts (Tailwind CSS, Jest)
├── reset_password.py # CLI tool for PIN recovery
├── setup_security.py # Bootstrap security (admin PIN, secret key)
└── run_coverage.py # Cross-platform coverage report
- Python 3.13 or higher
- pip (Python package manager)
-
Clone or download the project. Open a terminal in the project root.
-
Create a virtual environment:
Windows:
python -m venv venv
Linux:
python3 -m venv venv
-
Activate the virtual environment:
Windows:
venv\Scripts\activate
Linux:
source venv/bin/activate -
Install dependencies:
pip install -r requirements.txt
-
The database is created and migrated automatically on first startup — no manual setup required.
Start the server:
Windows:
python app.pyLinux:
python3 app.pyOpen your browser at http://localhost:5000.
On the first launch, Calcify will prompt you in the terminal to create a master PIN. This PIN is used for all subsequent logins.
If you forget your PIN, run the following command and follow the on-screen instructions:
python reset_password.pyEnter your master PIN on the login screen.
Navigate to Configuration. Register your currencies (e.g., USD, VES, EUR) and set the Base Currency. Add exchange rates for each currency.
Register products with cost price, currency, and margin percentage. The system automatically calculates the sale price.
Real-time currency conversion based on your registered exchange rates. All calculations use Decimal precision.
Register inventory outflows. This creates transactions and updates stock quantities automatically.
View daily transaction summaries. Filter by date and export to CSV.
In the Configuration panel, download a JSON file containing all your data.
Calcify uses fixed-precision arithmetic to prevent banker's rounding errors.
Converted Amount = (Original Amount × Target Inverse Rate) / Source Inverse Rate
- Calculations:
DecimalwithROUND_HALF_UPto 4 decimal places - Display: Rounded to 2 decimal places for user-facing values
If a rate is zero, the system raises InvalidExchangeRateError.
The project maintains 90% backend coverage (1039/1153 statements).
# All backend tests
python -m pytest
# With coverage report
python run_coverage.py
# Frontend tests (Jest + jsdom)
npm test| Suite | Count | Coverage Target |
|---|---|---|
| Domain | 14 | 100% |
| Use cases | 19 | 95-100% |
| Infrastructure | 25 | 81-100% |
| Presentation | 92 | 81-95% |
| Frontend (Jest) | 97 | Defensive JS |
Coverage goals: 100% in domain/ and use_cases/ layers.
Calcify supports English (default) and Spanish, with a system designed for easy addition of new languages.
- Backend: Flask-Babel extracts translatable strings from Python code (
_("string")) and Jinja2 templates ({{ _("string") }}). - Frontend: A global
__(key)function instatic/js/i18n.jslooks up translations from the_tobject, which is injected by the Flask context processor into every page. - Locale selection: The system checks
session["locale"]first, then the browser'sAccept-Languageheader, falling back to English.
-
Extract translatable strings:
pybabel extract -F babel.cfg -o messages.pot . -
Initialize a new language catalog (replace
frwith your language code):pybabel init -i messages.pot -d translations -l fr
-
Translate: Edit
translations/fr/LC_MESSAGES/messages.po— fill in themsgstrfields with your translations. -
Compile:
pybabel compile -d translations
-
Register the locale in
app.py: Add your language code to thebest_matchlist inget_locale():return request.accept_languages.best_match(["en", "es", "fr"]) or "en"
After adding new translatable strings to the codebase:
# Convenience script (extract + update + compile)
bash scripts/update_translations.shOr manually:
pybabel extract -F babel.cfg -o messages.pot .
pybabel update -i messages.pot -d translations
pybabel compile -d translationsWhen you add a new translatable string to JavaScript:
- Add the string to the
js_translationsdict inapp.py:inject_i18n_globals(). - Use
__("your_key")in JavaScript files. - Add the corresponding
msgidto each.pofile with the translatedmsgstr. - Regenerate the
.mofiles withpybabel compile -d translations.
Contributions are welcome! Here's how to get started:
If you find a bug or have a feature request, open an issue on the project repository with:
- A clear description of the problem or suggestion
- Steps to reproduce (for bugs)
- Expected vs actual behavior
- Fork and clone the repository.
- Set up the virtual environment as described in Installation.
- Install development dependencies:
pip install -r requirements.txt
- Run the test suite to confirm everything works:
python -m pytest npm test
- Architecture: Follow the strict 4-layer Clean Architecture — never import Flask/SQLAlchemy in
domain/. - Monetary values: Always use
decimal.DecimalwithROUND_HALF_UP. Never usefloatfor financial amounts. - Type hints: Every function must have complete type annotations.
- Testing: All changes should maintain or improve the 90% coverage threshold. Run
python run_coverage.pyto verify. - Translations: Wrap all user-facing strings in
_()(Python/Jinja2) or__()(JavaScript) for i18n support.
- Create a feature branch from
main. - Write tests first (TDD — Red/Green/Refactor).
- Implement your changes.
- Ensure all tests pass:
python -m pytest && npm test. - Verify coverage:
python run_coverage.py. - Submit a pull request with a clear description of the changes.