Files
FarmaFinder/STATE.md
T
Antoni Nuñez Romeu 3f5f88bbf5
Run Tests on Branches / Backend Tests (push) Successful in 3m31s
Run Tests on Branches / Frontend Tests (push) Successful in 3m26s
Cleanup modified & misisng files
2026-07-02 20:37:29 +02:00

116 lines
8.5 KiB
Markdown

# Pharmacy Integration Platform (PIP) — State Log
## Goal
Build a Python/FastAPI backend that abstracts pharmacy ERPs behind a unified REST API. Currently implementing Phase 3 (Inventory & Medication APIs).
## Constraints & Preferences
- Backend only, no UI/mobile. Python 3.13, FastAPI, SQLAlchemy async, Alembic, PostgreSQL, Redis, RabbitMQ.
- Clean Architecture, SOLID, Repository Pattern, DI, DDD where valuable, Event Driven, CQRS where needed.
- Versioned API at `/api/v1/`. JWT+OAuth2 auth. RBAC with role hierarchy and scopes. API Keys for third parties.
- No code secrets. Structured JSON logs. OpenTelemetry, Prometheus, Grafana.
- Docker Compose with health checks; Kubernetes-migratable.
- Monitoring (Prometheus, Grafana, OTEL Collector) omitted from Docker stack; remote monitoring stack exists. PLAN.md provided for external integration.
- Incremental phases — wait for validation before advancing.
## Progress
### Done
- **Phase 1 complete**: full project structure, domain entities, ORM models, repositories, auth, security, API endpoints, middleware, Docker, CLI, Alembic, tests scaffolding
- **Phase 2 complete**: Connector Framework + SDK + Concrete Connectors + API Endpoints + Event Publishers + Tests
- **Phase 3 (medication/inventory)**: Medication & Inventory API endpoints — all E2E validated:
- Medications: CRUD, search by name/ingredient/nregistro/atc, nregistro lookup, duplicate 409
- Catalog: per-pharmacy upsert, list, get
- Inventory: per-pharmacy upsert with history tracking, list, get, history endpoint
- Reservations: create, idempotency key, list, get, confirm, cancel, insufficient stock 422
### In Progress
- Phase 3 remaining items: Sync Job management, Audit logging integration, API Key auth flow, Rate limiting
### Blocked
- (none)
## Key Decisions
- Monitoring removed from Docker stack; `PLAN.md` provides instructions for external Prometheus/Grafana integration
- `docker-compose.runtime.yml` is the lean compose file for local dev
- Connector Registry uses singleton pattern with lazy loading via `importlib` for builtin connector types
- SDK architecture: `SDKConnector` base class composes auth handler + retry policy + circuit breaker + httpx client; concrete connectors extend it
- `_BUILTIN_CONNECTORS` registry maps ERPType values to `module.Class` strings for dynamic import
- Circuit breaker: 5 failures → open → 30s recovery → half_open (1 probe call) → close on success / reopen on failure
- OAuth2 handler fetches client_credentials tokens with 30s expiry buffer
- Replaced `passlib` with direct `bcrypt` library due to Python 3.13 incompatibility
- Connector events published to RabbitMQ with `connector.{event}` routing keys via `ConnectorService._publish_event()`
- `test_connection` sets connector status to ACTIVE on success, ERROR on failure; `deploy` refuses if already active
- Catalog entries are per-pharmacy, keyed by `(pharmacy_id, medication_id)` — upsert pattern
- Inventory records use same composite key pattern as catalog — upsert with history tracking
- Reservations support idempotency via `idempotency_key`, auto-expire, stock reservation/release on confirm/cancel
- Event publishing uses `event_type.replace(".", "_")` to map dotted event types to RabbitMQ routing keys
- Auth token endpoint expects JSON body (`LoginRequest`), not OAuth2 form data
- `InventoryHistoryEntity` has `inventory_record_id` field — must be set from service when creating history records
- Reservation `expires_at` uses `timedelta` (not timestamp arithmetic — float type error with Python 3.13 datetime)
## Critical Bugs Fixed (this session)
1. `inventory_repository.py:add_history()` was using `entity.id` (None) instead of `entity.inventory_record_id` for the FK
2. `inventory_service.py:upsert_inventory_record()` wasn't passing `inventory_record_id=upserted.id` to `InventoryHistoryEntity`
3. `inventory_service.py:_release_reserved_stock()` wasn't passing `inventory_record_id` either
4. `inventory_service.py:create_reservation()` used `now.__class__(now.timestamp() + ttl_minutes * 60, tz=timezone.utc)` which fails with TypeError on float — replaced with `now + timedelta(minutes=ttl_minutes)`
## Next Steps
- Implement Sync Job management (create, list, get, trigger, cancel sync jobs)
- Add Audit logging integration
- Implement API Key auth flow
- Add Rate limiting
- Replace `Base.metadata.create_all` with Alembic migrations
- Add more connector implementations (NixFara, Unycop, CSV, FTP, SQL)
- Enhance test coverage
- Set up CI/CD pipeline
## Critical Context
- `bcrypt` library used directly (not via passlib) — must keep `bcrypt>=4.2.0` in dependencies
- OTel collector reference in docker-compose points to `otel-collector:4317` which doesn't exist in runtime stack — harmless warning logs
- System router has `/system` prefix; health at `/api/v1/system/health`
- Auth: register at `/api/v1/auth/register`, token at `/api/v1/auth/token` (JSON body, not form data)
- Generic exception handler in `main.py` swallows all errors as `{"error": {"code": "INTERNAL_ERROR", "message": "Internal server error"}}` — no traceback in response; check container logs or test repo/service directly for debugging
- Inventory endpoint uses `PUT /pharmacies/{pharmacy_id}/inventory` for upsert
- Reservation endpoints: `POST .../reservations` (create), `POST .../reservations/{id}/confirm`, `POST .../reservations/{id}/cancel`## Recent changes (OTel migration in progress)
- **Observability now lives on srv84-macos.** The in-stack otel-collector / prometheus / grafana services have been stripped from `pip-platform/docker-compose.yml`, the orphaned `pip-platform/monitoring/otel-collector-config.yaml` has been deleted, and `pip-platform/docker-compose.runtime.yml` was previously pointing at a non-existent `otel-collector:4317` — fixed to `host.docker.localhost:4317` to route through Grafana Alloy. See PLAN.md for the external-integration rationale.
- **`pip-platform/monitoring/prometheus/prometheus.yml` is now documentation-only** — kept as a reference; the active scraper is the Prometheus on srv84-macos.
- **Monitoring stack (~/Projects/Monitoring/)**: Telegram bot token was externalized from `alertmanager/config.yml` (was plaintext literal) to `${TG_BOT_TOKEN}` resolved by an `envsubst` entrypoint on the remote. `OTel→Alloy` swap is local-canonical; srv84-macos merge still pending (see PLAN.md).
## Relevant Files
### Domain
- `pip-platform/src/domain/entities/entities.py` — All domain entities (including `inventory_record_id` on `InventoryHistoryEntity`)
- `pip-platform/src/domain/entities/enums.py` — All enums
### Repositories
- `pip-platform/src/infrastructure/database/repositories/medication_repository.py` — Medication CRUD + search
- `pip-platform/src/infrastructure/database/repositories/medication_catalog_repository.py` — Per-pharmacy catalog upsert
- `pip-platform/src/infrastructure/database/repositories/inventory_repository.py` — Inventory upsert + history
- `pip-platform/src/infrastructure/database/repositories/reservation_repository.py` — Reservation CRUD + idempotency
- `pip-platform/src/infrastructure/database/repositories/__init__.py` — All repo exports
### Services
- `pip-platform/src/services/medication/medication_service.py` — Medication + catalog business logic + events
- `pip-platform/src/services/inventory/inventory_service.py` — Inventory + reservation business logic (stock reservation/release, confirm, cancel, idempotency) + events
- `pip-platform/src/services/connector_service.py` — Connector lifecycle + event publishing
### API Endpoints
- `pip-platform/src/api/v1/endpoints/medications.py` — Medication CRUD + search
- `pip-platform/src/api/v1/endpoints/catalog.py` — Per-pharmacy catalog list/get/upsert
- `pip-platform/src/api/v1/endpoints/inventory.py` — Per-pharmacy inventory list/get/upsert + history
- `pip-platform/src/api/v1/endpoints/reservations.py` — Per-pharmacy reservation create/confirm/cancel/list/get
- `pip-platform/src/api/v1/endpoints/connectors.py` — Connector CRUD + test/deploy/deactivate/configs
- `pip-platform/src/api/v1/endpoints/erp_systems.py` — ERP System CRUD
- `pip-platform/src/api/v1/router.py` — All routers wired
### Infrastructure
- `pip-platform/src/infrastructure/messaging/rabbitmq.py` — RabbitMQ client with routing keys
- `pip-platform/src/connectors/registry.py` — ConnectorRegistry singleton
- `pip-platform/src/connectors/sdk/base_sdk.py` — SDKConnector base class
- `pip-platform/src/services/auth/security.py` — Fixed bcrypt usage (no passlib)
### Docker
- `pip-platform/docker-compose.runtime.yml` — Lean runtime stack