The Pipelines service handles two types of processing: document uploads (text extraction and summarisation) and GitHub repository analysis (codebase summary and architecture diagram generation).
The document processing pipeline handles file uploads, text extraction, and LLM-powered summarisation for compliance documents.
Each compliance framework defines a set of required documents (e.g., "Technical Documentation" per Annex IV of the EU AI Act). When a user opens a project's documents page, document placeholders are automatically created for every framework definition. Users can then upload files against each placeholder.
Additionally, users can upload free-form custom PDF documents that are not tied to any framework definition. These are stored in a separate custom_documents table and processed through the same pipeline.
sequenceDiagram
actor User
participant FE as Frontend
participant BE as Backend
participant VOL as Shared Volume<br/>/data/uploads/
participant PL as Pipelines
participant LLM as LLM Provider
participant DB as PostgreSQL
User->>FE: Selects file for a document slot
FE->>BE: POST /api/projects/{pid}/documents/{did}/upload<br/>(multipart/form-data)
BE->>VOL: Save file as {project_id}/{document_id}{ext}
BE->>DB: UPDATE documents SET file_path, file_name, uploaded_at
BE->>PL: POST /api/documents/process<br/>{document_id, file_path, table_name}
PL->>VOL: Read file from shared volume
PL->>PL: Extract text based on file type
PL->>PL: Truncate if > 100k chars
PL->>LLM: acompletion() with extraction prompt
LLM-->>PL: Markdown summary
PL->>DB: UPDATE {table_name} SET summary = ...
BE-->>FE: Document response (upload confirmed)
Note over FE: Summary appears after<br/>Pipelines finishes processing
erDiagram
frameworks ||--o{ document_definitions : "defines required docs"
document_definitions ||--o{ documents : "instantiated per project"
projects ||--o{ documents : "has"
projects ||--o{ custom_documents : "has"
document_definitions {
uuid id PK
uuid framework_id FK
string name
text description
string article
}
documents {
uuid id PK
uuid project_id FK
uuid definition_id FK
string file_path "null if not uploaded"
string file_name "original filename"
text summary "LLM-generated, null if not processed"
datetime uploaded_at
}
custom_documents {
uuid id PK
uuid project_id FK
string file_name "original filename"
string file_path
text summary "LLM-generated, nullable"
datetime uploaded_at
datetime created_at
}
Document instances are created lazily — when a user first visits the documents page for a project, the backend checks which document_definitions don't yet have a corresponding documents row for that project and creates them. This means every project automatically gets placeholders for all framework-required documents.
The Pipelines service (pipelines/app/tasks/document_processor.py) handles text extraction based on file type:
| File Type | Method |
|---|---|
PDF (.pdf) |
PyMuPDF (fitz) — extracts text page by page |
Text-based (.md, .txt, .csv, .json, .xml, .html) |
Direct UTF-8 read |
| Other | Attempted as UTF-8 text with error replacement |
Documents exceeding 100,000 characters are truncated with a [... document truncated for processing ...] marker before being sent to the LLM.
The extracted text is sent to LiteLLM (litellm.acompletion()) with max_tokens=4096 and a structured prompt requesting:
- A brief overview paragraph
- Key sections with their main points
- Specific requirements, obligations, or action items
- Any deadlines, thresholds, or quantitative criteria mentioned
The summary is stored in the documents.summary or custom_documents.summary column and serves three purposes:
- Documents page — displayed directly to the user as a readable summary of the uploaded file
- Chat agent context — included in the Norma agent's system prompt so the assistant can reference uploaded document content when answering questions
- Reporting suggestions — included in the context for AI-generated compliance checklist suggestions
/data/uploads/ # Shared Docker volume (norma-data)
{project_id}/
{document_id}.pdf # Framework documents, stored with original extension
{document_id}.md
...
custom/
{document_id}.pdf # Custom documents (PDF only)
Both the Backend and Pipelines services mount the norma-data volume at /data. The Backend writes files during upload; the Pipelines service reads them during processing.
When a user triggers a repository sync, the Backend calls the Pipelines service to process the connected GitHub repository.
sequenceDiagram
participant BE as Backend
participant PL as Pipelines
participant GH as GitHub API
participant LLM as LLM Provider
participant DB as PostgreSQL
BE->>PL: POST /api/github/process<br/>{integration_id, language}
PL->>GH: GET repo tree (filtered)
PL->>GH: GET file contents (relevant files)
PL->>DB: Store in github_repo_files
PL->>GH: GET issues + project items
PL->>DB: Store in github_tasks
PL->>LLM: acompletion() with tasks + files
LLM-->>PL: Structured summary
PL->>DB: UPDATE integrations SET summary
PL->>LLM: acompletion() with file tree + contents
LLM-->>PL: Mermaid architecture diagram
PL->>DB: UPDATE integrations SET architecture_mermaid
PL->>DB: Set sync_status = "idle"
The GitHub processor (pipelines/app/tasks/github_processor.py) generates two outputs:
Technical summary with four sections:
- Overview of the repository
- Tech stack and dependencies
- Current tasks and priorities
- Key observations
Architecture diagram as a Mermaid graph showing the system's main components and their relationships, accompanied by a bullet-point description.
Both outputs respect the user's language preference. Prompts are truncated at 200,000 characters to stay within LLM context limits.
The processor fetches the repository tree from GitHub and filters out files that are unlikely to be relevant to compliance analysis (e.g., lock files, build artefacts, test fixtures). Only files matching common source code and configuration patterns are fetched and stored.
| File | Purpose |
|---|---|
backend/app/api/routes/documents.py |
Upload endpoint, document listing, lazy document creation, custom document CRUD |
backend/app/api/routes/integrations.py |
Integration CRUD, sync trigger, task listing |
backend/app/models/document.py |
Document and DocumentDefinition SQLAlchemy models |
backend/app/models/custom_document.py |
CustomDocument SQLAlchemy model for free-form uploads |
backend/app/models/integration.py |
Integration, GitHubTask, GitHubRepoFile models |
backend/app/services/seed.py |
Seeds document_definitions from framework config |
pipelines/app/tasks/document_processor.py |
Text extraction and LLM summary generation |
pipelines/app/tasks/github_processor.py |
GitHub repository analysis and diagram generation |
pipelines/app/api/routes/documents.py |
Document processing endpoint called by the backend |
pipelines/app/api/routes/github.py |
GitHub processing endpoint called by the backend |