From 062719bfa4c0f428bb6539b1e62bf13c4b8d599a Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Wed, 10 Jun 2026 01:54:25 +0200 Subject: [PATCH 01/15] feat(#10): add first workflow --- .github/workflow/dependabot.yml | 0 .github/workflow/tests.yml | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 .github/workflow/dependabot.yml create mode 100644 .github/workflow/tests.yml diff --git a/.github/workflow/dependabot.yml b/.github/workflow/dependabot.yml new file mode 100644 index 0000000..e69de29 diff --git a/.github/workflow/tests.yml b/.github/workflow/tests.yml new file mode 100644 index 0000000..d47b0f8 --- /dev/null +++ b/.github/workflow/tests.yml @@ -0,0 +1,32 @@ +name: Tests + +on: + push: + branches: + - main + - develop + pull_request: + branches: + - main + - develop + +jobs: + tests: + runs-on: ubuntu-latest + + steps: + - name: Check out repository + uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: "3.11" + + - name: Install project + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" + + - name: Run tests + run: pytest \ No newline at end of file From 96bdc8cf4b3047f249dd5c644289ef4bcac80b34 Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Wed, 10 Jun 2026 02:11:48 +0200 Subject: [PATCH 02/15] feat(#10): add governance files --- CODE_OF_CONDUCT.md | 0 CONTRIBUTING.md | 0 LICENSE | 0 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 CODE_OF_CONDUCT.md create mode 100644 CONTRIBUTING.md create mode 100644 LICENSE diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000..e69de29 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e69de29 diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..e69de29 From 5c20488e2d96b317b55126647dcc168b38d8ee49 Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Thu, 11 Jun 2026 16:39:38 +0200 Subject: [PATCH 03/15] feat(#10): add licence file --- LICENSE | 3 +++ 1 file changed, 3 insertions(+) diff --git a/LICENSE b/LICENSE index e69de29..a23e6f9 100644 --- a/LICENSE +++ b/LICENSE @@ -0,0 +1,3 @@ +Apache License +Version 2.0, January 2004 +Copyright 2026 L. Gusiev \ No newline at end of file From 4697a1db474a42519d9792c477bd696fd0df7e87 Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Thu, 11 Jun 2026 22:49:04 +0200 Subject: [PATCH 04/15] feat(#10): complete code of conduct --- CODE_OF_CONDUCT.md | 100 +++++++++++++++++++++++++++++++++++++++++++++ README.md | 4 ++ 2 files changed, 104 insertions(+) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index e69de29..44fd766 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,100 @@ +# Code of Conduct + +## My Pledge + +ARGUS is a technical learning, portfolio and open-source project focused on market analytics, data workflows and future AI-assisted monitoring. + +I want this project to be a respectful and constructive space for learning, collaboration and technical discussion. + +Everyone who participates in this project is expected to communicate with professionalism, patience and respect. + +## Expected Behavior + +Examples of behavior that helps this project: + +- using welcoming and respectful language +- giving constructive feedback in issues, pull requests and reviews +- explaining technical suggestions clearly +- being patient with questions, mistakes and learning processes +- keeping discussions focused on the project and its goals +- respecting different levels of experience +- reporting bugs or problems with useful context +- proposing changes without attacking people + +## Unacceptable Behavior + +The following behavior is not acceptable: + +- insults, harassment or personal attacks +- discriminatory language or behavior +- aggressive, hostile or mocking comments +- sexualized language or unwanted attention +- trolling, spam or repeated off-topic comments +- publishing private information without permission +- deliberately disruptive behavior in issues, pull requests or discussions +- pressuring maintainers to accept changes immediately + +## Scope + +This Code of Conduct applies to all project spaces, including: + +- GitHub issues +- pull requests +- code reviews +- GitHub discussions +- the project wiki +- documentation contributions +- any future community spaces connected to ARGUS + +It also applies when someone represents the project in public project-related communication. + +## Maintainer Responsibilities + +Project maintainers are responsible for keeping the project space respectful and productive. + +Maintainers may take appropriate action in response to unacceptable behavior, including: + +- asking someone to change their behavior +- editing or removing comments +- closing issues or pull requests +- limiting participation +- blocking users from the project if necessary + +Maintainers should apply these rules fairly and focus on protecting a constructive technical environment. + +## Enforcement Principles + +Enforcement should be proportional to the situation. + +Possible responses include: + +1. **Clarification** + A maintainer explains why a behavior is inappropriate and asks for a change. + +2. **Warning** + A maintainer gives a clear warning if the behavior continues or is serious. + +3. **Temporary restriction** + A user may be temporarily limited from participating in discussions or pull requests. + +4. **Permanent restriction** + A user may be blocked from the project in cases of harassment, repeated abuse or serious misconduct. + +## Project Culture + +ARGUS is built around careful technical growth. + +The project values: + +- clear thinking +- reliable code +- honest documentation +- constructive review +- long-term learning +- respectful collaboration + +Strong technical opinions are welcome. Personal attacks are not. + +## Attribution + +This Code of Conduct is inspired by common open-source community guidelines and adapted for the ARGUS project. diff --git a/README.md b/README.md index 5a520e8..0cfe173 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,10 @@ ARGUS is currently focused on building a clean local foundation: --- +## License + +This project is licensed under the [Apache License 2.0](LICENSE). + ## Project Direction ARGUS is designed to grow step by step from a local Python application into a market analytics and monitoring system. From 6e5727cb551f8fc55f03e42a7899fb666e514018 Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Fri, 12 Jun 2026 10:34:08 +0200 Subject: [PATCH 05/15] feat(#10): add guideline for contributing --- CONTRIBUTING.md | 346 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 346 insertions(+) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index e69de29..32d00f4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -0,0 +1,346 @@ +# Contributing to ARGUS + +Thank you for your interest in contributing to ARGUS. + +ARGUS is a Python-based market analytics project focused on clean data workflows, reliable code, useful metrics and future AI-assisted monitoring. + +This project is still growing, so contributions should help the project become more stable, understandable and useful step by step. + +> [!IMPORTANT] +> ARGUS values reliability, clear communication and long-term skill building. +> Contributions should improve the project without creating unnecessary complexity. + +--- + +## Project Mindset + +ARGUS is not only about adding features quickly. + +The project is built around: + +- clean Python code +- understandable architecture +- reliable tests +- useful documentation +- careful data handling +- practical analytics +- continuous learning + +Good contributions should make the project easier to use, test, maintain or extend. + +--- + +## What You Can Contribute + +Helpful contributions include: + +- bug fixes +- tests +- documentation improvements +- small refactorings +- validation improvements +- analytics metrics +- chart improvements +- data-source clients +- CI/CD improvements +- issue clarification +- architecture notes +- examples and usage instructions + +> [!NOTE] +> Large features should usually start with an issue or short discussion before implementation. + +--- + +## Before You Start + +Before working on a contribution: + +1. Check existing issues and pull requests. +2. Make sure your idea fits the current roadmap. +3. Keep the scope small and focused. +4. Ask if the direction is unclear. + +Avoid opening large pull requests that mix unrelated changes. + +Good examples: + +- one PR for a new metric +- one PR for a bug fix +- one PR for documentation cleanup +- one PR for CI improvements + +Bad examples: + +- one PR that changes the UI, rewrites services, updates docs and adds a new data client at the same time + +--- + +## Development Setup + +Clone the repository: + +```bash +git clone https://github.com/BytecodeBrewer/argus.git +cd argus +``` + +Create a virtual environment: + +```bash +python -m venv .venv +``` + +Activate it. + +On Windows PowerShell: + +```powershell +.venv\Scripts\Activate.ps1 +``` + +On macOS/Linux: + +```bash +source .venv/bin/activate +``` + +Install the project with development dependencies: + +```bash +pip install -e ".[dev]" +``` + +--- + +## Branch Workflow + +Create a new branch for your work: + +```bash +git checkout -b +``` + +Example: + +```bash +git checkout -b 12-add-volatility-metric +``` + +Use focused branch names that describe the work. + +--- + +## Commit Expectations + +Commits should be small, understandable and related to the current task. + +Good commit messages: + +```text +Add rolling volatility metric +Fix currency validation edge case +Update README setup instructions +Add tests for trend metrics +``` + +Avoid unclear messages: + +```text +fix +stuff +changes +update +final +``` + +> [!TIP] +> A good commit tells future readers what changed and why it belongs to the task. + +--- + +## Testing + +Before opening a pull request, run the test suite: + +```bash +pytest +``` + +A pull request should not be opened as ready for review if tests are failing without explanation. + +If a test fails and you do not know why, mention it clearly in the pull request. + +> [!IMPORTANT] +> CI checks must pass before a pull request can be merged. + +--- + +## Pull Request Expectations + +A good pull request should include: + +- a clear title +- a short explanation of what changed +- a link to the related issue if available +- notes about tests +- screenshots for UI changes if useful +- a short explanation of any trade-offs + +Pull requests should be focused and reviewable. + +Recommended PR structure: + +```md +## What changed? + +## Why? + +## Tests + +## Notes +``` + +--- + +## Reliability Expectations + +Contributors are expected to work reliably. + +This means: + +- do not submit random or unfinished code without context +- do not ignore failing tests +- do not introduce secrets, API keys or local machine paths +- do not rewrite unrelated parts of the project without discussion +- communicate if you are unsure +- keep changes understandable for future contributors +- respect the existing architecture unless there is a clear reason to change it + +Reliability does not mean knowing everything already. + +It means being honest, careful and consistent. + +--- + +## Learning Mindset + +ARGUS welcomes contributors who want to improve their technical skills. + +You do not need to be an expert to contribute. + +Helpful behavior includes: + +- asking clear questions +- explaining your reasoning +- being open to review feedback +- improving your code after feedback +- learning from tests, errors and architecture discussions +- documenting what you learned when it helps others + +> [!NOTE] +> This project values skill growth. +> A thoughtful small contribution is better than a large unclear one. + +--- + +## Code Style + +Keep code simple and readable. + +General guidelines: + +- prefer clear names over clever shortcuts +- keep functions focused +- separate data loading, transformation and presentation logic +- avoid unnecessary global state +- avoid hidden side effects +- add tests for important behavior +- document assumptions when they matter + +For analytics code: + +- keep metric functions reusable +- avoid coupling metrics directly to UI code +- avoid coupling metrics directly to one specific API client +- prefer clear pandas transformations + +--- + +## Secrets and API Keys + +Never commit secrets. + +Do not commit: + +- API keys +- tokens +- passwords +- `.env` files +- local config files with private data + +Use a local `.env` file for secrets. + +```env +api_key=your_api_key_here +``` + +> [!WARNING] +> If you accidentally commit a secret, revoke it immediately and inform the maintainer. + +--- + +## Documentation + +Documentation changes are welcome. + +Useful documentation includes: + +- setup instructions +- roadmap notes +- architecture explanations +- metric definitions +- data-source assumptions +- troubleshooting notes + +Repository-level files such as `README.md`, `CONTRIBUTING.md`, `CODE_OF_CONDUCT.md` and `LICENSE` belong in the repository root. + +Technical notes, research and deeper explanations belong in `docs/`. + +--- + +## Communication + +Please communicate respectfully and constructively. + +When giving feedback: + +- focus on the code or idea, not the person +- explain the reason behind suggestions +- be specific +- stay open to alternatives + +When receiving feedback: + +- assume good intent +- ask questions if something is unclear +- improve the contribution step by step + +All contributors are expected to follow the project’s Code of Conduct. + +--- + +## Maintainer Notes + +The maintainer may ask for changes before merging a pull request. + +A contribution may be declined if it: + +- does not fit the current roadmap +- adds too much complexity too early +- breaks existing functionality +- lacks necessary tests +- duplicates existing work +- does not follow the project’s quality expectations + +This helps keep ARGUS stable, learnable and maintainable. \ No newline at end of file From 3a84f9703be02677a7a3fb7751cc430b624882ae Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Sat, 13 Jun 2026 16:52:12 +0200 Subject: [PATCH 06/15] style(#10): removed unused imports --- .github/workflow/tests.yml | 8 ++++++- .gitignore | 4 ++-- pyproject.toml | 1 + src/argus.egg-info/PKG-INFO | 11 +++++++++ src/argus.egg-info/SOURCES.txt | 32 +++++++++++++++++++++++++ src/argus.egg-info/dependency_links.txt | 1 + src/argus.egg-info/requires.txt | 8 +++++++ src/argus.egg-info/top_level.txt | 2 ++ src/argus/clients/mock_client.py | 1 - tests/test_exchangerate_client.py | 5 ++-- tests/test_timeseries_service.py | 1 - tests/test_trend_metrics.py | 1 - tests/test_validation_domain.py | 5 +--- 13 files changed, 67 insertions(+), 13 deletions(-) create mode 100644 src/argus.egg-info/PKG-INFO create mode 100644 src/argus.egg-info/SOURCES.txt create mode 100644 src/argus.egg-info/dependency_links.txt create mode 100644 src/argus.egg-info/requires.txt create mode 100644 src/argus.egg-info/top_level.txt diff --git a/.github/workflow/tests.yml b/.github/workflow/tests.yml index d47b0f8..b9ed639 100644 --- a/.github/workflow/tests.yml +++ b/.github/workflow/tests.yml @@ -29,4 +29,10 @@ jobs: pip install -e ".[dev]" - name: Run tests - run: pytest \ No newline at end of file + run: pytest + + - name: Check code formatting + run: pip install ruff + run: ruff check . + run: ruff format --check . + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 9ff1e03..91c88b2 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,8 @@ __pycache__ # IDE settings .vscode/ -# Temporary files (exercise files) -temp/ +# ruff cache +.ruff_cache # Virtual environment .env \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 3acf581..092c74a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,6 +13,7 @@ dependencies = [ [project.optional-dependencies] dev = [ "pytest", + "ruff", ] [build-system] diff --git a/src/argus.egg-info/PKG-INFO b/src/argus.egg-info/PKG-INFO new file mode 100644 index 0000000..ab30d01 --- /dev/null +++ b/src/argus.egg-info/PKG-INFO @@ -0,0 +1,11 @@ +Metadata-Version: 2.4 +Name: argus +Version: 0.1.0 +Requires-Python: >=3.11 +Requires-Dist: requests +Requires-Dist: python-dotenv +Requires-Dist: pandas +Requires-Dist: numpy +Requires-Dist: matplotlib +Provides-Extra: dev +Requires-Dist: pytest; extra == "dev" diff --git a/src/argus.egg-info/SOURCES.txt b/src/argus.egg-info/SOURCES.txt new file mode 100644 index 0000000..f3d9589 --- /dev/null +++ b/src/argus.egg-info/SOURCES.txt @@ -0,0 +1,32 @@ +README.md +pyproject.toml +src/argus/__init__.py +src/argus/config.py +src/argus/main.py +src/argus/analytics/__init__.py +src/argus/analytics/charts/__init__.py +src/argus/analytics/charts/trend_chart.py +src/argus/analytics/metrics/__init__.py +src/argus/analytics/metrics/trend_metrics.py +src/argus/clients/__init__.py +src/argus/clients/exchangerate_client.py +src/argus/clients/mock_client.py +src/argus/domain/__init__.py +src/argus/domain/validation.py +src/argus/gui/app.py +src/argus/services/__init__.py +src/argus/services/calculator_service.py +src/argus/services/conversion_service.py +src/argus/services/timeseries_service.py +src/fx_converter_lab.egg-info/PKG-INFO +src/fx_converter_lab.egg-info/SOURCES.txt +src/fx_converter_lab.egg-info/dependency_links.txt +src/fx_converter_lab.egg-info/requires.txt +src/fx_converter_lab.egg-info/top_level.txt +src/legacy/debug_main.py +src/legacy/cli/__init__.py +src/legacy/cli/interface.py +tests/test_exchangerate_client.py +tests/test_timeseries_service.py +tests/test_trend_metrics.py +tests/test_validation_domain.py \ No newline at end of file diff --git a/src/argus.egg-info/dependency_links.txt b/src/argus.egg-info/dependency_links.txt new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/argus.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/src/argus.egg-info/requires.txt b/src/argus.egg-info/requires.txt new file mode 100644 index 0000000..9c60f7d --- /dev/null +++ b/src/argus.egg-info/requires.txt @@ -0,0 +1,8 @@ +requests +python-dotenv +pandas +numpy +matplotlib + +[dev] +pytest diff --git a/src/argus.egg-info/top_level.txt b/src/argus.egg-info/top_level.txt new file mode 100644 index 0000000..00c26f1 --- /dev/null +++ b/src/argus.egg-info/top_level.txt @@ -0,0 +1,2 @@ +argus +legacy diff --git a/src/argus/clients/mock_client.py b/src/argus/clients/mock_client.py index 92b7eb9..9281a9e 100644 --- a/src/argus/clients/mock_client.py +++ b/src/argus/clients/mock_client.py @@ -1,4 +1,3 @@ -import pandas as pd import numpy as np mock_resp_data = { diff --git a/tests/test_exchangerate_client.py b/tests/test_exchangerate_client.py index e92bd5f..71a607b 100644 --- a/tests/test_exchangerate_client.py +++ b/tests/test_exchangerate_client.py @@ -1,7 +1,6 @@ -import pytest import requests as req from unittest.mock import Mock -from argus.clients.exchangerate_client import * +from argus.clients.exchangerate_client import get_rates, check_error def test_check_currency_timeout(monkeypatch): def test_get_resp(url, timeout): @@ -96,7 +95,7 @@ def test_get_resp(url, timeout): monkeypatch.setattr("requests.get", test_get_resp) data = get_rates("EUR", "USD") - assert data == None + assert data is None def test_check_error(capsys): check_error("unsupported-code") diff --git a/tests/test_timeseries_service.py b/tests/test_timeseries_service.py index e8594be..4eced35 100644 --- a/tests/test_timeseries_service.py +++ b/tests/test_timeseries_service.py @@ -1,4 +1,3 @@ -import pytest import pandas as pd import pandas.testing as pdt import numpy as np diff --git a/tests/test_trend_metrics.py b/tests/test_trend_metrics.py index 7ca85c8..a61c0b4 100644 --- a/tests/test_trend_metrics.py +++ b/tests/test_trend_metrics.py @@ -1,4 +1,3 @@ -import pytest import pandas as pd import pandas.testing as pdt import numpy as np diff --git a/tests/test_validation_domain.py b/tests/test_validation_domain.py index c1c17c2..1bd7f94 100644 --- a/tests/test_validation_domain.py +++ b/tests/test_validation_domain.py @@ -1,7 +1,4 @@ -import pytest -import requests as req -from unittest.mock import Mock -from argus.domain.validation import * +from argus.domain.validation import is_valid_op, is_valid_curr_code, parse_amount, normalize_input_string def test_op_is_valid(): data = is_valid_op('+') From 122d478fe94cc4803af59423fdbf80ffb37626bb Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Sat, 13 Jun 2026 17:25:53 +0200 Subject: [PATCH 07/15] style(#10): reformat the project and ignore settings --- .gitignore | 5 +- .vscode/settings.json | 3 - pyproject.toml | 6 +- src/argus.egg-info/PKG-INFO | 11 -- src/argus.egg-info/SOURCES.txt | 32 --- src/argus.egg-info/dependency_links.txt | 1 - src/argus.egg-info/requires.txt | 8 - src/argus.egg-info/top_level.txt | 2 - src/argus/analytics/charts/trend_chart.py | 25 ++- src/argus/analytics/metrics/trend_metrics.py | 14 +- src/argus/clients/exchangerate_client.py | 44 +++-- src/argus/clients/mock_client.py | 54 +++-- src/argus/config.py | 2 +- src/argus/domain/validation.py | 198 ++++++++++++++++--- src/argus/gui/app.py | 30 ++- src/argus/main.py | 4 +- src/argus/services/calculator_service.py | 43 ++-- src/argus/services/conversion_service.py | 21 +- src/argus/services/timeseries_service.py | 17 +- src/legacy/cli/interface.py | 43 ++-- src/legacy/debug_main.py | 3 +- tests/test_exchangerate_client.py | 34 ++-- tests/test_timeseries_service.py | 27 +-- tests/test_trend_metrics.py | 50 ++--- tests/test_validation_domain.py | 31 ++- 25 files changed, 442 insertions(+), 266 deletions(-) delete mode 100644 .vscode/settings.json delete mode 100644 src/argus.egg-info/PKG-INFO delete mode 100644 src/argus.egg-info/SOURCES.txt delete mode 100644 src/argus.egg-info/dependency_links.txt delete mode 100644 src/argus.egg-info/requires.txt delete mode 100644 src/argus.egg-info/top_level.txt diff --git a/.gitignore b/.gitignore index 91c88b2..9a1d8a1 100644 --- a/.gitignore +++ b/.gitignore @@ -2,10 +2,11 @@ __pycache__ .pytest_cache *.pyc -/src/fx_converter_lab.egg-info +/src/argus.egg-info + # IDE settings -.vscode/ +.vscode # ruff cache .ruff_cache diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index c9ebf2d..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "python-envs.defaultEnvManager": "ms-python.python:system" -} \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 092c74a..8823e30 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,4 +28,8 @@ package-dir = {"" = "src"} [tool.pytest.ini_options] testpaths = ["tests"] -pythonpath = ["src"] \ No newline at end of file +pythonpath = ["src"] + +[tool.ruff] +line-length = 88 +target-version = "py312" \ No newline at end of file diff --git a/src/argus.egg-info/PKG-INFO b/src/argus.egg-info/PKG-INFO deleted file mode 100644 index ab30d01..0000000 --- a/src/argus.egg-info/PKG-INFO +++ /dev/null @@ -1,11 +0,0 @@ -Metadata-Version: 2.4 -Name: argus -Version: 0.1.0 -Requires-Python: >=3.11 -Requires-Dist: requests -Requires-Dist: python-dotenv -Requires-Dist: pandas -Requires-Dist: numpy -Requires-Dist: matplotlib -Provides-Extra: dev -Requires-Dist: pytest; extra == "dev" diff --git a/src/argus.egg-info/SOURCES.txt b/src/argus.egg-info/SOURCES.txt deleted file mode 100644 index f3d9589..0000000 --- a/src/argus.egg-info/SOURCES.txt +++ /dev/null @@ -1,32 +0,0 @@ -README.md -pyproject.toml -src/argus/__init__.py -src/argus/config.py -src/argus/main.py -src/argus/analytics/__init__.py -src/argus/analytics/charts/__init__.py -src/argus/analytics/charts/trend_chart.py -src/argus/analytics/metrics/__init__.py -src/argus/analytics/metrics/trend_metrics.py -src/argus/clients/__init__.py -src/argus/clients/exchangerate_client.py -src/argus/clients/mock_client.py -src/argus/domain/__init__.py -src/argus/domain/validation.py -src/argus/gui/app.py -src/argus/services/__init__.py -src/argus/services/calculator_service.py -src/argus/services/conversion_service.py -src/argus/services/timeseries_service.py -src/fx_converter_lab.egg-info/PKG-INFO -src/fx_converter_lab.egg-info/SOURCES.txt -src/fx_converter_lab.egg-info/dependency_links.txt -src/fx_converter_lab.egg-info/requires.txt -src/fx_converter_lab.egg-info/top_level.txt -src/legacy/debug_main.py -src/legacy/cli/__init__.py -src/legacy/cli/interface.py -tests/test_exchangerate_client.py -tests/test_timeseries_service.py -tests/test_trend_metrics.py -tests/test_validation_domain.py \ No newline at end of file diff --git a/src/argus.egg-info/dependency_links.txt b/src/argus.egg-info/dependency_links.txt deleted file mode 100644 index 8b13789..0000000 --- a/src/argus.egg-info/dependency_links.txt +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/argus.egg-info/requires.txt b/src/argus.egg-info/requires.txt deleted file mode 100644 index 9c60f7d..0000000 --- a/src/argus.egg-info/requires.txt +++ /dev/null @@ -1,8 +0,0 @@ -requests -python-dotenv -pandas -numpy -matplotlib - -[dev] -pytest diff --git a/src/argus.egg-info/top_level.txt b/src/argus.egg-info/top_level.txt deleted file mode 100644 index 00c26f1..0000000 --- a/src/argus.egg-info/top_level.txt +++ /dev/null @@ -1,2 +0,0 @@ -argus -legacy diff --git a/src/argus/analytics/charts/trend_chart.py b/src/argus/analytics/charts/trend_chart.py index 4fd14f0..57c5193 100644 --- a/src/argus/analytics/charts/trend_chart.py +++ b/src/argus/analytics/charts/trend_chart.py @@ -2,14 +2,15 @@ import pandas as pd from argus.services.timeseries_service import prepare_trend_analysis -def create_trendchart(curr:str,dates:pd.DataFrame): + +def create_trendchart(curr: str, dates: pd.DataFrame): df = pd.DataFrame() - df,min_max_rates = prepare_trend_analysis(curr,dates) + df, min_max_rates = prepare_trend_analysis(curr, dates) min_date = min_max_rates["min_date"][0] min_rate = min_max_rates["min_rate"][0] max_date = min_max_rates["max_date"][0] max_rate = min_max_rates["max_rate"][0] - + # Rate and Rolling Average needs seperat x-Achse von Daily Percentage Chnage erhalten fig, ax1 = plt.subplots(figsize=(5, 3.5), dpi=100) @@ -18,21 +19,27 @@ def create_trendchart(curr:str,dates:pd.DataFrame): ax1.plot(df["date"], df["roll_avg"], color="blue", label="Rolling Average") # Scatter and Annote Min/Max Rate - ax1.scatter(min_date, min_rate,color="red") - ax1.scatter(max_date, max_rate,color="green") + ax1.scatter(min_date, min_rate, color="red") + ax1.scatter(max_date, max_rate, color="green") ax1.annotate("Min", (min_date, min_rate)) ax1.annotate("Max", (max_date, max_rate)) # Rotate date values for better visibillity - ax1.tick_params(axis='x', rotation=45) + ax1.tick_params(axis="x", rotation=45) # Subplot 2 ax2 = ax1.twinx() bar_colors = ["green" if x >= 0 else "red" for x in df["daily_pct_change"]] - ax2.bar(df["date"], df["daily_pct_change"], color=bar_colors,alpha=0.4,label="Daily Change") + ax2.bar( + df["date"], + df["daily_pct_change"], + color=bar_colors, + alpha=0.4, + label="Daily Change", + ) ax2.legend(loc="upper left") ax2.set_ylabel("Percentage Scale") - # Adjust the layout + # Adjust the layout fig.tight_layout() - return fig \ No newline at end of file + return fig diff --git a/src/argus/analytics/metrics/trend_metrics.py b/src/argus/analytics/metrics/trend_metrics.py index 6b50d43..9493ff3 100644 --- a/src/argus/analytics/metrics/trend_metrics.py +++ b/src/argus/analytics/metrics/trend_metrics.py @@ -1,26 +1,24 @@ import pandas as pd + def add_daily_percentage_change(df: pd.DataFrame) -> pd.DataFrame: result = df.copy() result["daily_pct_change"] = result["rate"].pct_change() * 100 return result + def add_rolling_average(df: pd.DataFrame) -> pd.DataFrame: result = df.copy() - result["roll_avg"] = result["rate"].rolling(window=3,min_periods=1).mean() + result["roll_avg"] = result["rate"].rolling(window=3, min_periods=1).mean() return result + def get_min_max_rates(df: pd.DataFrame) -> dict: - min_max = { - "min_date":[], - "min_rate":[], - "max_date":[], - "max_rate":[] - } + min_max = {"min_date": [], "min_rate": [], "max_date": [], "max_rate": []} min_id = df["rate"].idxmin() max_id = df["rate"].idxmax() min_max["min_date"].append(df.loc[min_id, "date"]) min_max["min_rate"].append(df.loc[min_id, "rate"]) min_max["max_date"].append(df.loc[max_id, "date"]) min_max["max_rate"].append(df.loc[max_id, "rate"]) - return min_max \ No newline at end of file + return min_max diff --git a/src/argus/clients/exchangerate_client.py b/src/argus/clients/exchangerate_client.py index 269f49e..71e9207 100644 --- a/src/argus/clients/exchangerate_client.py +++ b/src/argus/clients/exchangerate_client.py @@ -1,19 +1,20 @@ import requests as req -from argus.config import (EXCHANGE_RATE_BASE_URL ,EXCHANGE_RATE_API_KEY,REQUEST_TIMEOUT_SECONDS) +from argus.config import ( + EXCHANGE_RATE_BASE_URL, + EXCHANGE_RATE_API_KEY, + REQUEST_TIMEOUT_SECONDS, +) + def get_rates(curr1, curr2): url = f"{EXCHANGE_RATE_BASE_URL}/{EXCHANGE_RATE_API_KEY}/pair/{curr1}/{curr2}" - data = { - "result": "", - "error_type": "", - "conversion_rate": None - } - + data = {"result": "", "error_type": "", "conversion_rate": None} + try: resp = req.get(url, timeout=REQUEST_TIMEOUT_SECONDS) resp.raise_for_status() payload = resp.json() - + except req.exceptions.Timeout: print("API hat zu lange gebraucht.") return None @@ -30,7 +31,7 @@ def get_rates(curr1, curr2): except KeyError: print("Unerwartete API-Antwortstruktur.") return None - + if payload.get("result") == "success": data["result"] = "success" data["conversion_rate"] = payload.get("conversion_rate") @@ -40,18 +41,25 @@ def get_rates(curr1, curr2): data["error_type"] = payload.get("error_type") check_error(data["error_type"]) return None - + def check_error(err_type): match err_type: - case 'unsupported-code' | 'malformed-request': + case "unsupported-code" | "malformed-request": print("Ungültige Anfrage! Bitter versuchen Sie es später erneut.") - case 'invalid-key': - print("Ungültiger API-Key! Checken Sie Ihren API-Key und versuchen Sie es erneut.") - case 'inactive-account': - print("Inaktives Konto! Bitte auf exchangerate-api.com gehen und Konto aktivieren.") - case 'quota-reached': - print("Anfrage-Limit erreicht! Bitte später erneut versuchen oder auf exchangerate-api.com upgraden.") + case "invalid-key": + print( + "Ungültiger API-Key! Checken Sie Ihren API-Key und versuchen Sie es erneut." + ) + case "inactive-account": + print( + "Inaktives Konto! Bitte auf exchangerate-api.com gehen und Konto aktivieren." + ) + case "quota-reached": + print( + "Anfrage-Limit erreicht! Bitte später erneut versuchen oder auf exchangerate-api.com upgraden." + ) + # Testen, ob die API funktioniert -#data = get_rates("EUR", "USD") \ No newline at end of file +# data = get_rates("EUR", "USD") diff --git a/src/argus/clients/mock_client.py b/src/argus/clients/mock_client.py index 9281a9e..31cc6db 100644 --- a/src/argus/clients/mock_client.py +++ b/src/argus/clients/mock_client.py @@ -1,25 +1,47 @@ import numpy as np mock_resp_data = { - "date": ["2026-06-01","2026-06-02","2026-06-03", - "2026-06-04","2026-06-05","2026-06-06", - "2026-06-07","2026-06-08","2026-06-09", - "2026-06-10","2026-06-11","2026-06-12", - "2026-06-13","2026-06-14","2026-06-15" - - ], - "rate": [1.08,1.10,1.14, - 1.12,1.10,1.07, - 1.08,1.08,1.08, - 1.10,1.12,1.12, - 1.15,1.12,1.13 - ], + "date": [ + "2026-06-01", + "2026-06-02", + "2026-06-03", + "2026-06-04", + "2026-06-05", + "2026-06-06", + "2026-06-07", + "2026-06-08", + "2026-06-09", + "2026-06-10", + "2026-06-11", + "2026-06-12", + "2026-06-13", + "2026-06-14", + "2026-06-15", + ], + "rate": [ + 1.08, + 1.10, + 1.14, + 1.12, + 1.10, + 1.07, + 1.08, + 1.08, + 1.08, + 1.10, + 1.12, + 1.12, + 1.15, + 1.12, + 1.13, + ], } + def get_mock_timeseries(curr: str, date: str) -> int | float: - if(curr=="USD"): + if curr == "USD": index = mock_resp_data["date"].index(date) rate = mock_resp_data["rate"][index] - return rate + return rate else: - return np.nan \ No newline at end of file + return np.nan diff --git a/src/argus/config.py b/src/argus/config.py index 0e56780..81f45f7 100644 --- a/src/argus/config.py +++ b/src/argus/config.py @@ -10,4 +10,4 @@ EXCHANGE_RATE_BASE_URL = "https://v6.exchangerate-api.com/v6" -REQUEST_TIMEOUT_SECONDS = 10 \ No newline at end of file +REQUEST_TIMEOUT_SECONDS = 10 diff --git a/src/argus/domain/validation.py b/src/argus/domain/validation.py index dc1626c..8e08d16 100644 --- a/src/argus/domain/validation.py +++ b/src/argus/domain/validation.py @@ -1,49 +1,185 @@ VALID_CURRENCY_CODES = { - "AED", "AFN", "ALL", "AMD", "ANG", "AOA", "ARS", "AUD", "AWG", "AZN", - "BAM", "BBD", "BDT", "BGN", "BHD", "BIF", "BMD", "BND", "BOB", "BRL", - "BSD", "BTN", "BWP", "BYN", "BZD", - "CAD", "CDF", "CHF", "CLF", "CLP", "CNH", "CNY", "COP", "CRC", "CUP", - "CVE", "CZK", - "DJF", "DKK", "DOP", "DZD", - "EGP", "ERN", "ETB", "EUR", - "FJD", "FKP", "FOK", - "GBP", "GEL", "GGP", "GHS", "GIP", "GMD", "GNF", "GTQ", "GYD", - "HKD", "HNL", "HRK", "HTG", "HUF", - "IDR", "ILS", "IMP", "INR", "IQD", "ISK", - "JEP", "JMD", "JOD", "JPY", - "KES", "KGS", "KHR", "KID", "KMF", "KRW", "KWD", "KYD", "KZT", - "LAK", "LBP", "LKR", "LRD", "LSL", "LYD", - "MAD", "MDL", "MGA", "MKD", "MMK", "MNT", "MOP", "MRU", "MUR", "MVR", - "MWK", "MXN", "MYR", "MZN", - "NAD", "NGN", "NIO", "NOK", "NPR", "NZD", + "AED", + "AFN", + "ALL", + "AMD", + "ANG", + "AOA", + "ARS", + "AUD", + "AWG", + "AZN", + "BAM", + "BBD", + "BDT", + "BGN", + "BHD", + "BIF", + "BMD", + "BND", + "BOB", + "BRL", + "BSD", + "BTN", + "BWP", + "BYN", + "BZD", + "CAD", + "CDF", + "CHF", + "CLF", + "CLP", + "CNH", + "CNY", + "COP", + "CRC", + "CUP", + "CVE", + "CZK", + "DJF", + "DKK", + "DOP", + "DZD", + "EGP", + "ERN", + "ETB", + "EUR", + "FJD", + "FKP", + "FOK", + "GBP", + "GEL", + "GGP", + "GHS", + "GIP", + "GMD", + "GNF", + "GTQ", + "GYD", + "HKD", + "HNL", + "HRK", + "HTG", + "HUF", + "IDR", + "ILS", + "IMP", + "INR", + "IQD", + "ISK", + "JEP", + "JMD", + "JOD", + "JPY", + "KES", + "KGS", + "KHR", + "KID", + "KMF", + "KRW", + "KWD", + "KYD", + "KZT", + "LAK", + "LBP", + "LKR", + "LRD", + "LSL", + "LYD", + "MAD", + "MDL", + "MGA", + "MKD", + "MMK", + "MNT", + "MOP", + "MRU", + "MUR", + "MVR", + "MWK", + "MXN", + "MYR", + "MZN", + "NAD", + "NGN", + "NIO", + "NOK", + "NPR", + "NZD", "OMR", - "PAB", "PEN", "PGK", "PHP", "PKR", "PLN", "PYG", + "PAB", + "PEN", + "PGK", + "PHP", + "PKR", + "PLN", + "PYG", "QAR", - "RON", "RSD", "RUB", "RWF", - "SAR", "SBD", "SCR", "SDG", "SEK", "SGD", "SHP", "SLE", "SOS", "SRD", - "SSP", "STN", "SYP", "SZL", - "THB", "TJS", "TMT", "TND", "TOP", "TRY", "TTD", "TVD", "TWD", "TZS", - "UAH", "UGX", "USD", "UYU", "UZS", - "VES", "VND", "VUV", + "RON", + "RSD", + "RUB", + "RWF", + "SAR", + "SBD", + "SCR", + "SDG", + "SEK", + "SGD", + "SHP", + "SLE", + "SOS", + "SRD", + "SSP", + "STN", + "SYP", + "SZL", + "THB", + "TJS", + "TMT", + "TND", + "TOP", + "TRY", + "TTD", + "TVD", + "TWD", + "TZS", + "UAH", + "UGX", + "USD", + "UYU", + "UZS", + "VES", + "VND", + "VUV", "WST", - "XAF", "XCD", "XDR", "XOF", "XPF", + "XAF", + "XCD", + "XDR", + "XOF", + "XPF", "YER", - "ZAR", "ZMW", "ZWL", + "ZAR", + "ZMW", + "ZWL", } -VALID_OPS = {'+', '-', '*', '/', '%', '**'} +VALID_OPS = {"+", "-", "*", "/", "%", "**"} -def normalize_input_string(input:str) -> str: + +def normalize_input_string(input: str) -> str: return input.strip().upper() + def parse_amount(value: str) -> float | None: try: return float(value) except ValueError: return None -def is_valid_curr_code(code:str) -> bool: + +def is_valid_curr_code(code: str) -> bool: return code in VALID_CURRENCY_CODES -def is_valid_op(op:str) -> bool: - return op in VALID_OPS \ No newline at end of file + +def is_valid_op(op: str) -> bool: + return op in VALID_OPS diff --git a/src/argus/gui/app.py b/src/argus/gui/app.py index fd1709f..141fec5 100644 --- a/src/argus/gui/app.py +++ b/src/argus/gui/app.py @@ -6,6 +6,7 @@ from argus.services.conversion_service import convert, check_currency from argus.domain.validation import parse_amount + def on_close() -> None: if trend_chart_widget is not None: trend_chart_widget.destroy() @@ -57,11 +58,22 @@ def show_trend() -> None: global trend_chart_widget mock_dates = { - "date": ["2026-06-01","2026-06-02","2026-06-03", - "2026-06-04","2026-06-05","2026-06-06", - "2026-06-07","2026-06-08","2026-06-09", - "2026-06-10","2026-06-11","2026-06-12", - "2026-06-13","2026-06-14","2026-06-15" + "date": [ + "2026-06-01", + "2026-06-02", + "2026-06-03", + "2026-06-04", + "2026-06-05", + "2026-06-06", + "2026-06-07", + "2026-06-08", + "2026-06-09", + "2026-06-10", + "2026-06-11", + "2026-06-12", + "2026-06-13", + "2026-06-14", + "2026-06-15", ] } mock_dates = pd.DataFrame(mock_dates) @@ -76,7 +88,7 @@ def show_trend() -> None: content.pack(side="top", fill=tk.BOTH, expand=True) if trend_canvas is None: - fig = create_trendchart(mock_curr,mock_dates) + fig = create_trendchart(mock_curr, mock_dates) fig.set_size_inches(7, 4) trend_canvas = FigureCanvasTkAgg(fig, master=content) @@ -132,7 +144,9 @@ def act_convert() -> None: return if amount is None: - result_conv_label.config(text="Please enter a valid amount in the 'Amount' field") + result_conv_label.config( + text="Please enter a valid amount in the 'Amount' field" + ) return response = convert(amount, resp1, resp2) @@ -248,4 +262,4 @@ def app() -> None: show_menu() if __name__ == "__main__": - app() \ No newline at end of file + app() diff --git a/src/argus/main.py b/src/argus/main.py index dd69583..a6fd534 100644 --- a/src/argus/main.py +++ b/src/argus/main.py @@ -1,8 +1,8 @@ from argus.gui.app import app + def main() -> None: app() -main() - \ No newline at end of file +main() diff --git a/src/argus/services/calculator_service.py b/src/argus/services/calculator_service.py index 6715f4b..40c1022 100644 --- a/src/argus/services/calculator_service.py +++ b/src/argus/services/calculator_service.py @@ -1,6 +1,7 @@ from argus.domain.validation import is_valid_op -def check_op(op:str) -> bool: + +def check_op(op: str) -> bool: # Tipp: Liste verwenden, wenn mehr als 2 Optionen für etwas besteht if is_valid_op(op): return True @@ -8,23 +9,23 @@ def check_op(op:str) -> bool: return False -def calc(num1:float,num2:float,op:str) -> float | None: - # Tipp: Auf Ifelse verzichten, wenn davon mehr als 3 Stück entstehen - match op: - case '+': - return num1 + num2 - case '-': - return num1 - num2 - case '*': - return num1 * num2 - case '/': - try: - return num1 / num2 - except ZeroDivisionError: - return None - case '%': - return num1 % num2 - case '**': - return num1 ** num2 - case _: - return None \ No newline at end of file +def calc(num1: float, num2: float, op: str) -> float | None: + # Tipp: Auf Ifelse verzichten, wenn davon mehr als 3 Stück entstehen + match op: + case "+": + return num1 + num2 + case "-": + return num1 - num2 + case "*": + return num1 * num2 + case "/": + try: + return num1 / num2 + except ZeroDivisionError: + return None + case "%": + return num1 % num2 + case "**": + return num1**num2 + case _: + return None diff --git a/src/argus/services/conversion_service.py b/src/argus/services/conversion_service.py index 02fd0e8..7d16d76 100644 --- a/src/argus/services/conversion_service.py +++ b/src/argus/services/conversion_service.py @@ -1,26 +1,29 @@ from argus.clients import exchangerate_client as ex_client -from argus.domain.validation import normalize_input_string,is_valid_curr_code +from argus.domain.validation import normalize_input_string, is_valid_curr_code + # This function has to be moved to dmoain -def check_currency(question:str) -> str | None: +def check_currency(question: str) -> str | None: resp = normalize_input_string(question) if is_valid_curr_code(resp): return resp - + return None -def get_conv_rate(resp1:str,resp2:str) -> float | None: + +def get_conv_rate(resp1: str, resp2: str) -> float | None: data = ex_client.get_rates(resp1, resp2) - + if data is None: return None - + return float(data["conversion_rate"]) -def convert(amount:float,resp1:str,resp2:str) -> float | None: - data = get_conv_rate(resp1,resp2) + +def convert(amount: float, resp1: str, resp2: str) -> float | None: + data = get_conv_rate(resp1, resp2) if data is not None: return amount * data else: - return None \ No newline at end of file + return None diff --git a/src/argus/services/timeseries_service.py b/src/argus/services/timeseries_service.py index 206f4b3..6ef34ae 100644 --- a/src/argus/services/timeseries_service.py +++ b/src/argus/services/timeseries_service.py @@ -1,16 +1,21 @@ import pandas as pd from argus.clients.mock_client import get_mock_timeseries -from argus.analytics.metrics.trend_metrics import add_rolling_average,add_daily_percentage_change,get_min_max_rates +from argus.analytics.metrics.trend_metrics import ( + add_rolling_average, + add_daily_percentage_change, + get_min_max_rates, +) -def prepare_trend_analysis(mock_curr:str,df:pd.DataFrame) -> tuple[pd.DataFrame,dict]: + +def prepare_trend_analysis( + mock_curr: str, df: pd.DataFrame +) -> tuple[pd.DataFrame, dict]: df["rate"] = 0.0 # For each date one API call to get the rate for i in range(len(df)): - date = str(df.loc[i,"date"]) + date = str(df.loc[i, "date"]) df.loc[i, "rate"] = get_mock_timeseries(mock_curr, date) df = add_daily_percentage_change(df) df = add_rolling_average(df) min_max_rates = get_min_max_rates(df) - return df,min_max_rates - - + return df, min_max_rates diff --git a/src/legacy/cli/interface.py b/src/legacy/cli/interface.py index bbe748d..fa9931a 100644 --- a/src/legacy/cli/interface.py +++ b/src/legacy/cli/interface.py @@ -1,5 +1,5 @@ -from argus.services.calculator_service import check_op,calc -from argus.services.conversion_service import convert,check_currency +from argus.services.calculator_service import check_op, calc +from argus.services.conversion_service import convert, check_currency from argus.domain.validation import parse_amount @@ -23,18 +23,21 @@ def display_convert() -> None: continue break while True: - response = convert(amount,resp1,resp2) + response = convert(amount, resp1, resp2) if response is None: print("Error with the API request! Please try again later.") continue break - + result = response - print (f"The exchange rate from {resp1} to {resp2} for {amount} {resp1} is {result} {resp2}") - - if return_to_menu() == 'y': + print( + f"The exchange rate from {resp1} to {resp2} for {amount} {resp1} is {result} {resp2}" + ) + + if return_to_menu() == "y": return + def display_calc() -> None: while True: num1 = parse_amount(input("First number: ")) @@ -54,24 +57,26 @@ def display_calc() -> None: print("Please enter again!") continue break - - result = calc(num1,num2,op) + + result = calc(num1, num2, op) print(f"Berechnung: {num1} {op} {num2} = {result}") - - if return_to_menu() == 'y': + + if return_to_menu() == "y": return + def return_to_menu() -> str: while True: repeat = input("Would you like to return to the menu? (y/n) ") match repeat: - case 'y': - return 'y' - case 'n': - return 'n' + case "y": + return "y" + case "n": + return "n" case _: print("Please enter 'y' or 'n'!") + def dev_interface() -> None: initConv = "Welcome to the Calculator with Currency Conversion!" print(initConv) @@ -79,11 +84,11 @@ def dev_interface() -> None: print("Menu: \n(1) Calculator \n(2) Exchnage Rate \n(3) Exit") option = input("Please select an option: ") match option: - case '1': + case "1": display_calc() - case '2': + case "2": display_convert() - case '3': + case "3": break case _: - print("Please enter only '1','2' or '3'!") \ No newline at end of file + print("Please enter only '1','2' or '3'!") diff --git a/src/legacy/debug_main.py b/src/legacy/debug_main.py index 2c3dd50..8d82035 100644 --- a/src/legacy/debug_main.py +++ b/src/legacy/debug_main.py @@ -4,5 +4,6 @@ def start_cli() -> None: dev_interface() + if __name__ == "__main__": - start_cli() \ No newline at end of file + start_cli() diff --git a/tests/test_exchangerate_client.py b/tests/test_exchangerate_client.py index 71a607b..51e8174 100644 --- a/tests/test_exchangerate_client.py +++ b/tests/test_exchangerate_client.py @@ -2,6 +2,7 @@ from unittest.mock import Mock from argus.clients.exchangerate_client import get_rates, check_error + def test_check_currency_timeout(monkeypatch): def test_get_resp(url, timeout): raise req.exceptions.Timeout() @@ -11,6 +12,7 @@ def test_get_resp(url, timeout): data = get_rates("EUR", "USD") assert data is None + def test_check_currency_connection_error(monkeypatch): def test_get_resp(url, timeout): raise req.exceptions.ConnectionError() @@ -20,6 +22,7 @@ def test_get_resp(url, timeout): data = get_rates("EUR", "USD") assert data is None + def test_check_currency_request_exception(monkeypatch): def test_get_resp(url, timeout): raise req.exceptions.RequestException("Testfehler") @@ -29,6 +32,7 @@ def test_get_resp(url, timeout): data = get_rates("EUR", "USD") assert data is None + def test_check_currency_value_error(monkeypatch): test_resp = Mock() test_resp.raise_for_status.return_value = None @@ -42,6 +46,7 @@ def test_get_resp(url, timeout): data = get_rates("EUR", "USD") assert data is None + def test_check_currency_key_error(monkeypatch): test_resp = Mock() test_resp.raise_for_status.return_value = None @@ -59,13 +64,14 @@ def test_get_resp(url, timeout): data = get_rates("EUR", "USD") assert data is None + def test_check_currency_valid(monkeypatch): test_resp = Mock() test_resp.raise_for_status.return_value = None test_resp.json.return_value = { "result": "success", "error_type": "", - "conversion_rate": 1.2 + "conversion_rate": 1.2, } def test_get_resp(url, timeout): @@ -74,11 +80,8 @@ def test_get_resp(url, timeout): monkeypatch.setattr("requests.get", test_get_resp) data = get_rates("EUR", "USD") - assert data == { - "result": "success", - "error_type": "", - "conversion_rate": 1.2 - } + assert data == {"result": "success", "error_type": "", "conversion_rate": 1.2} + def test_check_currency_invalid(monkeypatch): test_resp = Mock() @@ -86,7 +89,7 @@ def test_check_currency_invalid(monkeypatch): test_resp.json.return_value = { "result": "error", "error_type": "unsupported-code", - "conversion_rate": None + "conversion_rate": None, } def test_get_resp(url, timeout): @@ -97,6 +100,7 @@ def test_get_resp(url, timeout): data = get_rates("EUR", "USD") assert data is None + def test_check_error(capsys): check_error("unsupported-code") captured = capsys.readouterr() @@ -104,13 +108,21 @@ def test_check_error(capsys): check_error("invalid-key") captured = capsys.readouterr() - assert captured.out == "Ungültiger API-Key! Checken Sie Ihren API-Key und versuchen Sie es erneut.\n" + assert ( + captured.out + == "Ungültiger API-Key! Checken Sie Ihren API-Key und versuchen Sie es erneut.\n" + ) check_error("inactive-account") captured = capsys.readouterr() - assert captured.out == "Inaktives Konto! Bitte auf exchangerate-api.com gehen und Konto aktivieren.\n" + assert ( + captured.out + == "Inaktives Konto! Bitte auf exchangerate-api.com gehen und Konto aktivieren.\n" + ) check_error("quota-reached") captured = capsys.readouterr() - assert captured.out == "Anfrage-Limit erreicht! Bitte später erneut versuchen oder auf exchangerate-api.com upgraden.\n" - + assert ( + captured.out + == "Anfrage-Limit erreicht! Bitte später erneut versuchen oder auf exchangerate-api.com upgraden.\n" + ) diff --git a/tests/test_timeseries_service.py b/tests/test_timeseries_service.py index 4eced35..dbc5675 100644 --- a/tests/test_timeseries_service.py +++ b/tests/test_timeseries_service.py @@ -2,28 +2,29 @@ import pandas.testing as pdt import numpy as np from argus.services.timeseries_service import prepare_trend_analysis + + def test_is_pct_change_added(): test_curr = "USD" test_dates = { - "date": ["2026-06-01","2026-06-02","2026-06-03"], + "date": ["2026-06-01", "2026-06-02", "2026-06-03"], } test_dates = pd.DataFrame(test_dates) expect_result = { - "date": ["2026-06-01","2026-06-02","2026-06-03"], - "rate": [1.08,1.1,1.14], - "daily_pct_change": [np.nan,1.85185185185186,3.6363636363636154], - "roll_avg": [1.08,1.09,1.1066666666666667] + "date": ["2026-06-01", "2026-06-02", "2026-06-03"], + "rate": [1.08, 1.1, 1.14], + "daily_pct_change": [np.nan, 1.85185185185186, 3.6363636363636154], + "roll_avg": [1.08, 1.09, 1.1066666666666667], } expect_dict = { - "min_date":["2026-06-01"], - "min_rate":[1.08], - "max_date":["2026-06-03"], - "max_rate":[1.14] + "min_date": ["2026-06-01"], + "min_rate": [1.08], + "max_date": ["2026-06-03"], + "max_rate": [1.14], } - result_df,result_dict = prepare_trend_analysis(test_curr,test_dates) + result_df, result_dict = prepare_trend_analysis(test_curr, test_dates) expect_df = pd.DataFrame(expect_result) - - pdt.assert_frame_equal(result_df,expect_df) - assert result_dict == expect_dict \ No newline at end of file + pdt.assert_frame_equal(result_df, expect_df) + assert result_dict == expect_dict diff --git a/tests/test_trend_metrics.py b/tests/test_trend_metrics.py index a61c0b4..fe175bd 100644 --- a/tests/test_trend_metrics.py +++ b/tests/test_trend_metrics.py @@ -1,58 +1,62 @@ import pandas as pd import pandas.testing as pdt import numpy as np -from argus.analytics.metrics.trend_metrics import add_daily_percentage_change,add_rolling_average,get_min_max_rates +from argus.analytics.metrics.trend_metrics import ( + add_daily_percentage_change, + add_rolling_average, + get_min_max_rates, +) + def test_is_pct_change_added(): test_timesseries = { - "date": ["2026-05-01","2026-05-02","2026-05-03"], - "rate": [1.00,1.10,1.21] + "date": ["2026-05-01", "2026-05-02", "2026-05-03"], + "rate": [1.00, 1.10, 1.21], } expect_result = { - "date": ["2026-05-01","2026-05-02","2026-05-03"], - "rate": [1.00,1.10,1.21], - "daily_pct_change": [np.nan,10.0,10.0] - + "date": ["2026-05-01", "2026-05-02", "2026-05-03"], + "rate": [1.00, 1.10, 1.21], + "daily_pct_change": [np.nan, 10.0, 10.0], } test_df = pd.DataFrame(test_timesseries) result_df = add_daily_percentage_change(test_df) expect_df = pd.DataFrame(expect_result) - pdt.assert_frame_equal(result_df,expect_df) + pdt.assert_frame_equal(result_df, expect_df) + def test_is_roll_avg_added(): test_timesseries = { - "date": ["2026-05-01","2026-05-02","2026-05-03"], - "rate": [1.00,1.10,1.21] + "date": ["2026-05-01", "2026-05-02", "2026-05-03"], + "rate": [1.00, 1.10, 1.21], } expect_result = { - "date": ["2026-05-01","2026-05-02","2026-05-03"], - "rate": [1.00,1.10,1.21], - "roll_avg": [1.00,1.05,1.1033333333333333333333333333333] - + "date": ["2026-05-01", "2026-05-02", "2026-05-03"], + "rate": [1.00, 1.10, 1.21], + "roll_avg": [1.00, 1.05, 1.1033333333333333333333333333333], } test_df = pd.DataFrame(test_timesseries) result_df = add_rolling_average(test_df) expect_df = pd.DataFrame(expect_result) - pdt.assert_frame_equal(result_df,expect_df) + pdt.assert_frame_equal(result_df, expect_df) + def test_get_min_max_(): test_timesseries = { - "date": ["2026-05-01","2026-05-02","2026-05-03"], - "rate": [1.00,1.10,1.21] + "date": ["2026-05-01", "2026-05-02", "2026-05-03"], + "rate": [1.00, 1.10, 1.21], } min_max = { - "min_date":["2026-05-01"], - "min_rate":[1.00], - "max_date":["2026-05-03"], - "max_rate":[1.21] + "min_date": ["2026-05-01"], + "min_rate": [1.00], + "max_date": ["2026-05-03"], + "max_rate": [1.21], } test_df = pd.DataFrame(test_timesseries) result_dict = get_min_max_rates(test_df) - - assert result_dict == min_max \ No newline at end of file + assert result_dict == min_max diff --git a/tests/test_validation_domain.py b/tests/test_validation_domain.py index 1bd7f94..a5bd41f 100644 --- a/tests/test_validation_domain.py +++ b/tests/test_validation_domain.py @@ -1,37 +1,48 @@ -from argus.domain.validation import is_valid_op, is_valid_curr_code, parse_amount, normalize_input_string +from argus.domain.validation import ( + is_valid_op, + is_valid_curr_code, + parse_amount, + normalize_input_string, +) + def test_op_is_valid(): - data = is_valid_op('+') + data = is_valid_op("+") assert data is True + def test_op_is_not_valid(): - data = is_valid_op('LOL') + data = is_valid_op("LOL") assert data is False + def test_curr_is_valid(): - data = is_valid_curr_code('AOA') + data = is_valid_curr_code("AOA") assert data is True + def test_curr_is_not_valid(): - data = is_valid_curr_code('LOL') + data = is_valid_curr_code("LOL") assert data is False + def test_parse_amount_valid(): - data = parse_amount('20.2') + data = parse_amount("20.2") assert data == 20.2 + def test_parse_amount_not_valid(): - data = parse_amount('fuck') + data = parse_amount("fuck") assert data is None -def test_normalizing_string(): - data = normalize_input_string(' lOl ') - assert data == 'LOL' +def test_normalizing_string(): + data = normalize_input_string(" lOl ") + assert data == "LOL" From 56f1f3801d05f81e384448f44d341abf76444900 Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Sat, 13 Jun 2026 18:34:17 +0200 Subject: [PATCH 08/15] ci(#10): modify workflow for commit msg --- .githooks/commit-msg | 21 +++++++++ .github/dependabot.yml | 11 +++++ .github/workflow/dependabot.yml | 0 .../{workflow/tests.yml => workflows/ci.yml} | 0 .github/workflows/commit-message.yml | 44 +++++++++++++++++++ 5 files changed, 76 insertions(+) create mode 100755 .githooks/commit-msg create mode 100644 .github/dependabot.yml delete mode 100644 .github/workflow/dependabot.yml rename .github/{workflow/tests.yml => workflows/ci.yml} (100%) create mode 100644 .github/workflows/commit-message.yml diff --git a/.githooks/commit-msg b/.githooks/commit-msg new file mode 100755 index 0000000..3d6aa49 --- /dev/null +++ b/.githooks/commit-msg @@ -0,0 +1,21 @@ +#!/bin/sh + +commit_msg_file="$1" +commit_msg="$(head -n 1 "$commit_msg_file")" + +pattern='^(feat|fix|docs|style|refactor|test|chore|ci|build|perf)\(#[0-9]+\): .+' + +if echo "$commit_msg" | grep -Eq "$pattern"; then + exit 0 +fi + +echo "Invalid commit message:" +echo " $commit_msg" +echo "" +echo "Expected format:" +echo " feat(#1): add trend metrics" +echo "" +echo "Allowed types:" +echo " feat, fix, docs, style, refactor, test, chore, ci, build, perf" + +exit 1 \ No newline at end of file diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..f2e002d --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,11 @@ +version: 2 + +updates: + - package-ecosystem: "pip" + directory: "/" + schedule: + interval: "weekly" + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" \ No newline at end of file diff --git a/.github/workflow/dependabot.yml b/.github/workflow/dependabot.yml deleted file mode 100644 index e69de29..0000000 diff --git a/.github/workflow/tests.yml b/.github/workflows/ci.yml similarity index 100% rename from .github/workflow/tests.yml rename to .github/workflows/ci.yml diff --git a/.github/workflows/commit-message.yml b/.github/workflows/commit-message.yml new file mode 100644 index 0000000..cb30102 --- /dev/null +++ b/.github/workflows/commit-message.yml @@ -0,0 +1,44 @@ +name: Commit Message + +on: + pull_request: + +permissions: + contents: read + pull-requests: read + +jobs: + validate-commit-messages: + name: Validate commit messages + runs-on: ubuntu-latest + + steps: + - name: Check commit message + uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Fetch base branch + run: git fetch origin "${{ github.base_ref }}" + - name: Validate commit message + run: | + pattern='^(feat|fix|docs|style|refactor|test|chore|ci|build|perf)\(#[0-9]+\): .{1,50}$' + invalid=0 + + while IFS= read -r commit_message; do + echo "Checking: $commit_message" + + if ! echo "$commit_message" | grep -Eq "$pattern"; then + echo "::error::Invalid commit message: $commit_message" + invalid=1 + fi + done < <(git log --format=%s "origin/${{ github.base_ref }}..HEAD") + + if [ "$invalid" -ne 0 ]; then + echo "" + echo "Expected format:" + echo " feat(#1): add feature" + echo "" + echo "Allowed types:" + echo " feat, fix, docs, style, refactor, test, chore, ci, build, perf" + exit 1 + fi \ No newline at end of file From ffd8ebac70f09e76c4dafe64ee080d2af2f17366 Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Sat, 13 Jun 2026 18:38:17 +0200 Subject: [PATCH 09/15] ci(#10): fix the ci workflow --- .github/workflows/ci.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b9ed639..3c383ec 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,8 +31,12 @@ jobs: - name: Run tests run: pytest - - name: Check code formatting + - name: Install ruff run: pip install ruff + + - name: Check code quality run: ruff check . + + - name: Check code formatting run: ruff format --check . \ No newline at end of file From 99aa01bce7ce5038849f9fe1817aac9fc6046054 Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Sat, 13 Jun 2026 19:01:50 +0200 Subject: [PATCH 10/15] ci(#10): improve workflow triggers --- .github/workflows/ci.yml | 9 ++------- .github/workflows/commit-message.yml | 2 ++ 2 files changed, 4 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3c383ec..7cb6c89 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,10 +1,8 @@ +# This workflow ensures that the code is tested and meets quality standards on every push and pull request name: Tests on: push: - branches: - - main - - develop pull_request: branches: - main @@ -31,12 +29,9 @@ jobs: - name: Run tests run: pytest - - name: Install ruff - run: pip install ruff - - name: Check code quality run: ruff check . - + - name: Check code formatting run: ruff format --check . \ No newline at end of file diff --git a/.github/workflows/commit-message.yml b/.github/workflows/commit-message.yml index cb30102..b305699 100644 --- a/.github/workflows/commit-message.yml +++ b/.github/workflows/commit-message.yml @@ -1,3 +1,4 @@ +# This workflow is needed to validate commit messages in pull requests, if the githook was bypassed name: Commit Message on: @@ -10,6 +11,7 @@ permissions: jobs: validate-commit-messages: name: Validate commit messages + shell: bash runs-on: ubuntu-latest steps: From 1b69e051a0f95a91940b775ebe9621fe8fb896cd Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Sat, 13 Jun 2026 19:56:31 +0200 Subject: [PATCH 11/15] ci(#10): add templates --- .github/ISSUE_TEMPLATES/bug_report.md | 24 ++++++++++++++++++++++++ .github/ISSUE_TEMPLATES/feature.md | 22 ++++++++++++++++++++++ .github/dependabot.yml | 6 +++++- .github/pull_request_template.md | 27 +++++++++++++++++++++++++++ .github/workflows/commit-message.yml | 7 ++++++- 5 files changed, 84 insertions(+), 2 deletions(-) create mode 100644 .github/ISSUE_TEMPLATES/bug_report.md create mode 100644 .github/ISSUE_TEMPLATES/feature.md create mode 100644 .github/pull_request_template.md diff --git a/.github/ISSUE_TEMPLATES/bug_report.md b/.github/ISSUE_TEMPLATES/bug_report.md new file mode 100644 index 0000000..00e7986 --- /dev/null +++ b/.github/ISSUE_TEMPLATES/bug_report.md @@ -0,0 +1,24 @@ +## Problem + +Describe what is broken. + +## Expected behavior + +Describe what should happen instead. + +## Steps to reproduce + +1. +2. +3. + +## Environment + +- OS: +- Python version: +- Branch: +- Command used: + +## Error output + +```text diff --git a/.github/ISSUE_TEMPLATES/feature.md b/.github/ISSUE_TEMPLATES/feature.md new file mode 100644 index 0000000..f9b8b9a --- /dev/null +++ b/.github/ISSUE_TEMPLATES/feature.md @@ -0,0 +1,22 @@ +## Goal + +Describe what should be achieved. + +## Why + +Explain why this task is useful for the project. + +## Scope + +- +- +- + +## Acceptance criteria + +- [ ] +- [ ] +- [ ] + +> [!NOTE] +> Priority: diff --git a/.github/dependabot.yml b/.github/dependabot.yml index f2e002d..a3ee804 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,7 +5,11 @@ updates: directory: "/" schedule: interval: "weekly" + commit-message: + prefix: "build(#10): update dependencies" - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" \ No newline at end of file + interval: "weekly" + commit-message: + prefix: "ci(#10): update GitHub Actions" \ No newline at end of file diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 0000000..e0b7352 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,27 @@ +## What changed? + +- +- +- + +## Related issue + +Closes # + +## Tests + +- [ ] I ran `pytest` +- [ ] Existing tests pass +- [ ] I added or updated tests where necessary +- [ ] Not needed because this only affects documentation or repository setup + +## Checklist + +- [ ] The change is focused +- [ ] No secrets or API keys are included +- [ ] Documentation was updated if needed +- [ ] CI is expected to pass + +## Notes + +- \ No newline at end of file diff --git a/.github/workflows/commit-message.yml b/.github/workflows/commit-message.yml index b305699..17ef7e6 100644 --- a/.github/workflows/commit-message.yml +++ b/.github/workflows/commit-message.yml @@ -11,7 +11,6 @@ permissions: jobs: validate-commit-messages: name: Validate commit messages - shell: bash runs-on: ubuntu-latest steps: @@ -22,6 +21,7 @@ jobs: - name: Fetch base branch run: git fetch origin "${{ github.base_ref }}" - name: Validate commit message + shell: bashs run: | pattern='^(feat|fix|docs|style|refactor|test|chore|ci|build|perf)\(#[0-9]+\): .{1,50}$' invalid=0 @@ -29,6 +29,11 @@ jobs: while IFS= read -r commit_message; do echo "Checking: $commit_message" + if echo "$commit_message" | grep -Eq '^(Merge|Revert)'; then + echo "Skipping technical commit: $commit_message" + continue + fi + if ! echo "$commit_message" | grep -Eq "$pattern"; then echo "::error::Invalid commit message: $commit_message" invalid=1 From cae45df41a8c84a44d1ad797a217c1e0f230e9cd Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Sat, 13 Jun 2026 20:41:37 +0200 Subject: [PATCH 12/15] chore(#10): correct the licence and project settings --- .../bug_report.md | 0 .../feature.md | 0 .github/dependabot.yml | 6 +- CONTRIBUTING.md | 14 +- LICENSE | 202 +++++++++++++++++- README.md | 12 +- pyproject.toml | 2 +- 7 files changed, 216 insertions(+), 20 deletions(-) rename .github/{ISSUE_TEMPLATES => ISSUE_TEMPLATE}/bug_report.md (100%) rename .github/{ISSUE_TEMPLATES => ISSUE_TEMPLATE}/feature.md (100%) diff --git a/.github/ISSUE_TEMPLATES/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md similarity index 100% rename from .github/ISSUE_TEMPLATES/bug_report.md rename to .github/ISSUE_TEMPLATE/bug_report.md diff --git a/.github/ISSUE_TEMPLATES/feature.md b/.github/ISSUE_TEMPLATE/feature.md similarity index 100% rename from .github/ISSUE_TEMPLATES/feature.md rename to .github/ISSUE_TEMPLATE/feature.md diff --git a/.github/dependabot.yml b/.github/dependabot.yml index a3ee804..f2e002d 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -5,11 +5,7 @@ updates: directory: "/" schedule: interval: "weekly" - commit-message: - prefix: "build(#10): update dependencies" - package-ecosystem: "github-actions" directory: "/" schedule: - interval: "weekly" - commit-message: - prefix: "ci(#10): update GitHub Actions" \ No newline at end of file + interval: "weekly" \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 32d00f4..0b53865 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -189,16 +189,12 @@ A good pull request should include: Pull requests should be focused and reviewable. -Recommended PR structure: +Before opening a pull request, run: -```md -## What changed? - -## Why? - -## Tests - -## Notes +```bash +pytest +ruff check . +ruff format --check . ``` --- diff --git a/LICENSE b/LICENSE index a23e6f9..ad6e96a 100644 --- a/LICENSE +++ b/LICENSE @@ -1,3 +1,201 @@ Apache License -Version 2.0, January 2004 -Copyright 2026 L. Gusiev \ No newline at end of file + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [2026] [BytecodeBrewer] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. \ No newline at end of file diff --git a/README.md b/README.md index 0cfe173..99b45f5 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,7 @@ Each roadmap phase is treated as a separate development sprint. The roadmap desc ```text docs/ src/ - fx_converter_lab/ + argus/ analytics/ charts/ metrics/ @@ -211,7 +211,7 @@ Recommended for development: Clone the repository: ```bash -git clone https://github.com/BytecodeBrewer/fx-converter-lab.git +git clone https://github.com/BytecodeBrewer/argus.git cd fx-converter-lab ``` @@ -241,6 +241,12 @@ Install the project in editable mode: pip install -e . ``` +For development and tests, install the development dependencies: + +```bash +pip install -e ".[dev]" +``` + > [!TIP] > Editable install keeps the project linked to your local source files. > This means code changes are picked up without reinstalling the project after every edit. @@ -284,7 +290,7 @@ The `.env` file must stay local and should never be committed. Start the current Tkinter GUI: ```bash -python -m fx_converter_lab.main +python -m argus.main ``` This starts the local ARGUS prototype with calculator, currency conversion and basic analytics views. diff --git a/pyproject.toml b/pyproject.toml index 8823e30..b1d94aa 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -32,4 +32,4 @@ pythonpath = ["src"] [tool.ruff] line-length = 88 -target-version = "py312" \ No newline at end of file +target-version = "py311" \ No newline at end of file From 2c0094c473aaed3bdb3a6e44d60e44e8eea65052 Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Sat, 13 Jun 2026 20:53:40 +0200 Subject: [PATCH 13/15] chore(#10): correct templates and readme --- .github/ISSUE_TEMPLATE/bug_report.md | 2 ++ README.md | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index 00e7986..dc01479 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -22,3 +22,5 @@ Describe what should happen instead. ## Error output ```text + +``` diff --git a/README.md b/README.md index 99b45f5..2914fa1 100644 --- a/README.md +++ b/README.md @@ -212,7 +212,7 @@ Clone the repository: ```bash git clone https://github.com/BytecodeBrewer/argus.git -cd fx-converter-lab +cd argus ``` Create a virtual environment: From 6c9cb851769f728bf35b4f14ead36bf31e9ff977 Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Sat, 13 Jun 2026 21:02:13 +0200 Subject: [PATCH 14/15] chore(#10): correct the project version and license details --- LICENSE | 2 +- pyproject.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index ad6e96a..4b3f66a 100644 --- a/LICENSE +++ b/LICENSE @@ -186,7 +186,7 @@ Apache License same "printed page" as the copyright notice for easier identification within third-party archives. - Copyright [2026] [BytecodeBrewer] + Copyright 2026 BytecodeBrewer Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. diff --git a/pyproject.toml b/pyproject.toml index b1d94aa..665f963 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "argus" -version = "0.9.0" +version = "0.1.0" requires-python = ">=3.11" dependencies = [ "requests", From a6b170160958f453d641d5602dba3603a92d97e7 Mon Sep 17 00:00:00 2001 From: Lev Gusiev Date: Sat, 13 Jun 2026 21:11:54 +0200 Subject: [PATCH 15/15] ci(#10): fix commit message workflow --- .github/workflows/commit-message.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/commit-message.yml b/.github/workflows/commit-message.yml index 17ef7e6..7005242 100644 --- a/.github/workflows/commit-message.yml +++ b/.github/workflows/commit-message.yml @@ -21,7 +21,7 @@ jobs: - name: Fetch base branch run: git fetch origin "${{ github.base_ref }}" - name: Validate commit message - shell: bashs + shell: bash run: | pattern='^(feat|fix|docs|style|refactor|test|chore|ci|build|perf)\(#[0-9]+\): .{1,50}$' invalid=0