From 6ab84fe1de174fc74d2e4e6a8d923f8cf3a374dc Mon Sep 17 00:00:00 2001 From: Kleidukos Date: Mon, 24 Aug 2026 21:35:11 +0200 Subject: [PATCH] chore: update .gitignore and add developer documentation --- .gitignore | 4 ++ CLAUDE.md | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 CLAUDE.md diff --git a/.gitignore b/.gitignore index 818eaca..3f0a346 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ ## Build outputs **/bin/ **/obj/ +**/publish/ +TestResults/ ## Rider / JetBrains / VS Code / Visual Studio .idea/ @@ -22,6 +24,8 @@ assets/ ## Secrets & local environment files .env +.env.* +.env.bak* *.env.local appsettings.Development.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..37713da --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,137 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## What this is + +Finlytic is a personal financial analysis/trading platform: a fleet of .NET 10 microservices communicating +over MQTT, one HTTP/WebSocket gateway (`FinlyticBackend`), and a Flutter app (`FinlyticApp`) as the only +client. There is no CI/multi-developer setup — this is a single-developer repo backed by one shared +PostgreSQL instance ("OmniDB") and one MQTT broker, both external to this repo. + +## Commands + +### Backend (.NET) + +``` +dotnet build Finlytic.sln # build everything +dotnet build FinlyticEngine # build a single project +dotnet test FinlyticEngine.Tests # run one test project +dotnet test FinlyticEngine.Tests --filter "FullyQualifiedName~TradeLifecycleServiceTests" +``` + +- Solution-wide `dotnet test` will also try to run `FinlyticEngine.Tests/_Verify/PostgresVerificationTests.cs`, + which connects to a real, throwaway local Postgres container on `localhost:55987` and has no `[Skip]`/trait + to exclude it. It fails outside that one-off setup — run test projects individually, or filter it out with + `--filter "FullyQualifiedName!~_Verify"`, rather than running the whole solution's tests. +- `FinlyticBot.Tests` currently has no test files (scaffolding only). +- EF Core migrations are created via the CLI only (never hand-written) from inside the owning service project, + e.g.: `dotnet ef migrations add --project FinlyticAssets`. + +### Frontend (Flutter, `FinlyticApp/`) + +``` +flutter pub get +flutter run # or -d chrome for the web dashboard +flutter test +flutter analyze +``` + +### Docker Compose (`compose.yaml`) + +The compose file only defines this repo's own services — Postgres and the MQTT broker are external +infrastructure (see the comment block at the top of `compose.yaml` for required env vars: `DB_HOST`, +`DB_PORT`, `DB_PASSWORD`, `MQTT_HOST`, `MQTT_PORT`, plus per-service secrets like `JWT_SECRET_KEY`, +`ADMIN_DEFAULT_PASSWORD`, `ALPACA_KEY_ID`/`ALPACA_SECRET_KEY`). Set these in a local `.env` file. + +`FinlyticNews` and `FinlyticFundamentals` build from a shared Playwright base image that must exist first: + +``` +docker compose --profile build-base build finlytic-playwright-base +docker compose build +``` + +Keep the `PLAYWRIGHT_VERSION` build arg in `compose.yaml` in sync with the `Microsoft.Playwright` NuGet +package version in `FinlyticCore/FinlyticCore.csproj`. + +## Architecture + +### Service topology + +Nine .NET projects share one solution (`Finlytic.sln`): `FinlyticCore` (shared library, no entry point) plus +eight deployable services — `FinlyticAssets`, `FinlyticNews`, `FinlyticFundamentals`, `FinlyticSentiment`, +`FinlyticTechnicals`, `FinlyticEngine`, `FinlyticSimulation`, `FinlyticBot`, and `FinlyticBackend`. Each +non-Core service is its own Docker image, has its own PostgreSQL database (`finlytic_assets`, +`finlytic_news`, ... one per service — never shared tables across services), and runs its own EF Core +migrations independently at startup via `DbContext.MigrateWithBootstrapAsync` (`FinlyticCore/Database/DatabaseBootstrapper.cs`). + +- **`FinlyticBackend`** is the *only* service allowed to host Kestrel/HTTP/WebSocket endpoints. It is a thin + aggregation gateway for `FinlyticApp`: its controllers and SignalR hubs (`Hubs/`) largely proxy to the + backing microservices over MQTT RPC and rebroadcast their MQTT events to connected clients via SignalR + (`BackendMqttBridge`). All other services are `Microsoft.NET.Sdk.Worker` background services with no HTTP + surface at all — this is an enforced rule (`Rules.md` §5), not a stylistic default. See the + `compose.yaml` comment block explaining why only `finlyticbackend` has a Docker `HEALTHCHECK`. +- Every other service follows the same internal shape: `Database/` (DbContext + migrations), + `Entities/` (EF entities), `Services/` (business logic + the service's MQTT client), `Util/`, and a + `Program.cs` that wires DI, registers the service's MQTT client as a hosted service, and migrates the DB + at startup. + +### Inter-service communication (MQTT-only) + +All cross-service calls go over MQTT — never direct HTTP between backend services. `FinlyticCore/Util/MqttTopics.cs` +is the single source of truth for every topic and RPC channel name; add new channels there rather than +inlining topic strings at call sites. Two patterns are used: + +1. **RPC** (`services/request/{channel}/{correlationId}` → `services/response/{channel}/{correlationId}`), + implemented by `FinlyticCore/Util/ManagedMqttClient.cs` (`SendRpcRequestAsync` / `SubscribeRpcAsync`). + Used for synchronous-style query/command calls, e.g. `FinlyticBackend` asking `FinlyticAssets` to resolve + an ISIN, or `FinlyticEngine` asking `FinlyticSentiment` for a sentiment summary. +2. **Pub/sub event streams** (e.g. `finlytic/news/stream/{isin}`, `finlytic/engine/proposals/created`, + `finlytic/bot/trades/stream`, `finlytic/logs/{service}`) for fire-and-forget notifications. `FinlyticBackend`'s + `BackendMqttBridge` subscribes to the wildcard form of most of these (`*Wildcard` constants) purely to + relay them onward to SignalR clients. + +Every service also implements a shared `health_Ping` RPC channel and exposes runtime-configurable settings +via a `{service}_settings_GetAll` / `{service}_settings_Update` channel pair (backed by `ISettingsService` +and `IOptionsMonitor`-style dynamic config — see `FinlyticCore/Services/Settings`) — changing a setting does +not require a service restart, and channel-level logging enable/disable follows the same mechanism (see +`IFinlyticLogger` / `FinlyticLogBroadcaster` in `FinlyticCore/Services/FinlyticLogger`). + +### Domain flow (roughly upstream → downstream) + +`FinlyticAssets` (asset/ISIN resolution, Trade Republic price feed) and `FinlyticNews`/`FinlyticFundamentals` +(scraped/ingested data, using Playwright) feed `FinlyticSentiment` (FinBERT analysis) and `FinlyticTechnicals` +(indicators/strategy setups), which feed `FinlyticEngine` (trade proposal generation, AI-assisted validation +via an n8n webhook, trade lifecycle management) and `FinlyticSimulation` (backtesting/strategy reliability +scoring). `FinlyticBot` executes accepted proposals as paper trades (via Alpaca or a synthetic ledger). +`FinlyticBackend` sits above all of them as the gateway `FinlyticApp` talks to. + +### Flutter app (`FinlyticApp/`) + +Feature-folder structure under `lib/features/*` (auth, dashboard, discovery, trades, bot, simulation, news, +calendar, favorites, search, asset_detail, proposals, admin), with `lib/core/` for cross-cutting network +(Dio + interceptors), theme, and services, and `lib/shared/` for shared widgets. State management is +`flutter_bloc`. Every outgoing request must carry the JWT via a central Dio interceptor, and a 401/403 must +trigger an immediate client-side logout — this is an enforced rule (`Rules.md` §8), not optional handling. + +## Project-wide rules (`Rules.md`) + +`Rules.md` is a binding rules document for this repo, not a style guide — treat every rule as an +architectural constraint to actively check against, not background reading. Full text is in `Rules.md`; +key points, since they shape most non-trivial changes: + +- Every service interface + implementation lives in one file, named after the implementation class. +- Every method (any visibility) needs XML doc comments in English; implementations of interface/base + methods use `/// ` (custom helper methods still need their own explicit docs). +- Strongly-typed data classes only for state passing, API payloads, and MQTT messages — no + `Dictionary`, `dynamic`, or raw `JObject`/`JsonDocument` in internal logic. Shared + DTOs/entities/enums used by more than one service belong in `FinlyticCore`; service-specific models + (e.g. raw third-party API shapes) stay in that service. +- No mock/demo/fallback data anywhere, backend or frontend. Empty results are either a real empty set or an + explicit exception — the Flutter UI must show an explicit empty state, never placeholder content. +- Async all the way (no `.Result`/`.Wait()`/`.GetAwaiter().GetResult()`); async DB/MQTT/network methods take + a trailing `CancellationToken` and pass it through. +- Every `FinlyticBackend` HTTP/WebSocket endpoint requires `[Authorize]` unless explicitly `[AllowAnonymous]` + (only `/api/v1/auth/login`-style endpoints and the `/health` check are exempt). +- No hardcoded secrets; bind config via `IOptions`/`IOptionsMonitor`, and `FinlyticBackend` fails fast + at startup if `JWT_SECRET_KEY`/`ADMIN_DEFAULT_PASSWORD` are missing or weak (see `Program.cs`).