chore: update .gitignore and add developer documentation
This commit is contained in:
@@ -1,6 +1,8 @@
|
|||||||
## Build outputs
|
## Build outputs
|
||||||
**/bin/
|
**/bin/
|
||||||
**/obj/
|
**/obj/
|
||||||
|
**/publish/
|
||||||
|
TestResults/
|
||||||
|
|
||||||
## Rider / JetBrains / VS Code / Visual Studio
|
## Rider / JetBrains / VS Code / Visual Studio
|
||||||
.idea/
|
.idea/
|
||||||
@@ -22,6 +24,8 @@ assets/
|
|||||||
|
|
||||||
## Secrets & local environment files
|
## Secrets & local environment files
|
||||||
.env
|
.env
|
||||||
|
.env.*
|
||||||
|
.env.bak*
|
||||||
*.env.local
|
*.env.local
|
||||||
appsettings.Development.json
|
appsettings.Development.json
|
||||||
|
|
||||||
|
|||||||
@@ -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 <Name> --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<T>` / `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 `/// <inheritdoc />` (custom helper methods still need their own explicit docs).
|
||||||
|
- Strongly-typed data classes only for state passing, API payloads, and MQTT messages — no
|
||||||
|
`Dictionary<string, object>`, `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<T>`/`IOptionsMonitor<T>`, and `FinlyticBackend` fails fast
|
||||||
|
at startup if `JWT_SECRET_KEY`/`ADMIN_DEFAULT_PASSWORD` are missing or weak (see `Program.cs`).
|
||||||
Reference in New Issue
Block a user