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

8.5 KiB

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