Skip to content

Architecture

The Norm Editor is a multi-service application. This page describes the services, how they communicate, and the request routing that ties them together. For the production topology see Deployment.


Services

graph TB
    Browser([Browser])
    NGINX[nginx]
    WEB[web ยท Vue 3 SPA]
    BACKEND[backend ยท Node/Express]
    NLP[nlp-api ยท Flask]
    UNWRAP[unwrap-api ยท Flask]
    WRAPUP[wrap-up-api ยท Flask]
    TRIPLY[(TriplyDB)]

    Browser --> NGINX
    NGINX -->|/*| WEB
    NGINX -->|/api/predict| NLP
    NGINX -->|/api/process_graph| UNWRAP
    NGINX -->|/api/process_and_save| WRAPUP
    NGINX -->|/api/*| BACKEND
    BACKEND <-->|SPARQL + graph API| TRIPLY

    style WEB fill:#4a90e2,color:#fff
    style BACKEND fill:#50c878
    style NLP fill:#e17000,color:#fff
    style UNWRAP fill:#ffd700
    style WRAPUP fill:#ffd700
    style TRIPLY fill:#c0b3ff
Service Stack Role
nginx nginx Single entry point; all routing lives here
web Vue 3 + Quasar (SPA), Pinia, D3 The editor UI
backend Node.js, Express, @triply/triplydb, N3, SuperAgent TriplyDB gateway: list/read/write sources and tasks
nlp-api Python, Flask, HuggingFace Transformers, PyTorch BERTje token classification for act frames
unwrap-api Python, Flask, RDFLib FLINT RDF โ†’ editor JSON
wrap-up-api Python, Flask, RDFLib editor JSON โ†’ FLINT RDF

Why two conversion services

The editor's in-memory model and the FLINT RDF model are different shapes. Rather than embed RDF logic in the frontend, the project isolates it in two Python services built on RDFLib:

  • wrap-up-api serialises an interpretation (as editor JSON) into FLINT RDF for storage.
  • unwrap-api parses FLINT RDF back into editor JSON for loading.

They are tested as a pair: each has a suite of fixtures (.json โ†” expected .ttl) checked with graph isomorphism, so a JSON interpretation that is wrapped and then unwrapped returns to the same structure. See Backend & API services.


Request routing

nginx is the only component exposed to the browser. It routes by path prefix:

Path Upstream
/api/process_graph unwrap-api
/api/predict nlp-api
/api/process_and_save wrap-up-api
/api/* backend
/* web (Vue 3 SPA)

Routing is defined in two files that must be kept in sync:

File Used by Upstream format
nginx/default.conf Docker Compose (volume-mounted) service-name:port
nginx/aca.conf Azure (baked into the nginx image) service-name.{ACA_DOMAIN} (full FQDN)

In Azure, nginx/docker-entrypoint.sh substitutes the runtime nameserver and domain into aca.conf at container start, and every proxy_pass resolves its upstream through a variable so DNS is looked up at request time โ€” required because ACA's internal DNS is only available then. Adding a new API route means editing both files, rebuilding the nginx image, and redeploying.


Configuration surface

Service endpoints (Triply, backend, and the three Python services) are provided through environment variables and a generated config.json. The canonical values for local Docker Compose are in docker-compose.yml; see the Environment Variables reference for the full list.


Repositories

The component is assembled from several repositories on the open-regels.nl GitLab instance, corresponding to the gui/, backend/, nlp_api/, unwrap_api/, and wrap_up_api/ directories, plus the nginx/ and infra/ deployment assets.