Add document converter, seeder data structure, and project wiki

- ai-service/convert.py: converts Office/PDF files to markdown with frontmatter
- database/seeders/data/: folder structure for themas, projects, documents, etc.
- database/seeders/data/raw/: drop zone for Office/PDF files to convert
- wiki/: project architecture, concepts, and knowledge graph documentation
- Remove unused Laravel example tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
znetsixe
2026-04-08 08:33:30 +02:00
parent 302c790c13
commit 926872a082
23 changed files with 1785 additions and 76 deletions

89
wiki/SCHEMA.md Normal file
View File

@@ -0,0 +1,89 @@
# Project Wiki Schema
## Purpose
LLM-maintained knowledge base for this project. The LLM writes and maintains everything. You read it (ideally in Obsidian). Knowledge compounds across sessions instead of being lost in chat history.
## Directory Structure
```
wiki/
SCHEMA.md — this file (how to maintain the wiki)
index.md — catalog of all pages with one-line summaries
log.md — chronological record of updates
overview.md — project overview and current status
metrics.md — all numbers with provenance
knowledge-graph.yaml — structured data, machine-queryable
tools/ — search, lint, query scripts
concepts/ — core ideas and mechanisms
architecture/ — design decisions, system internals
findings/ — honest results (what worked AND what didn't)
sessions/ — per-session summaries
```
## Page Conventions
### Frontmatter
Every page starts with YAML frontmatter:
```yaml
---
title: Page Title
created: YYYY-MM-DD
updated: YYYY-MM-DD
status: proven | disproven | evolving | speculative
tags: [tag1, tag2]
sources: [path/to/file.py, commit abc1234]
---
```
### Status values
- **proven**: tested and verified with evidence
- **disproven**: tested and honestly shown NOT to work (document WHY)
- **evolving**: partially working, boundary not fully mapped
- **speculative**: proposed but not yet tested
### Cross-references
Use `[[Page Name]]` Obsidian-style wikilinks.
### Contradictions
When new evidence contradicts a prior claim, DON'T delete the old claim. Add:
```
> [!warning] Superseded
> This was shown to be incorrect on YYYY-MM-DD. See [[New Finding]].
```
### Honesty rule
If something doesn't work, say so. If a result was a false positive, document how it was discovered. The wiki must be trustworthy.
## Operations
### Ingest (after a session or new source)
1. Read outputs, commits, findings
2. Update relevant pages
3. Create new pages for new concepts
4. Update `index.md`, `log.md`, `knowledge-graph.yaml`
5. Check for contradictions with existing pages
### Query
1. Use `python3 wiki/tools/query.py` for structured lookup
2. Use `wiki/tools/search.sh` for full-text
3. Read `index.md` to find relevant pages
4. File valuable answers back into the wiki
### Lint (periodically)
```bash
bash wiki/tools/lint.sh
```
Checks: orphan pages, broken wikilinks, missing frontmatter, index completeness.
## Data Layer
- `knowledge-graph.yaml` — structured YAML with every metric and data point
- `metrics.md` — human-readable dashboard
- When adding new results, update BOTH the wiki page AND the knowledge graph
- The knowledge graph is the single source of truth for numbers
## Source of Truth Hierarchy
1. **Test results** (actual outputs) — highest authority
2. **Code** (current state) — second authority
3. **Knowledge graph** (knowledge-graph.yaml) — structured metrics
4. **Wiki pages** — synthesis, may lag
5. **Chat/memory** — ephemeral, may be stale

View File

@@ -0,0 +1,112 @@
---
title: Domain Model
created: 2026-04-08
updated: 2026-04-08
status: evolving
tags: [domain, models, relationships, lifecycle]
sources: [app/Models/, app/Enums/, database/migrations/]
---
# Domain Model
21 Eloquent models organized in 8 layers, with 14 enums for type safety.
## Entity Relationship Overview
```
Thema (strategic theme)
└── Speerpunt (focus area)
└── Project (innovation project)
├── Fase (lifecycle phase)
├── Risico (risk)
├── Commitment → Actie (action)
├── Besluit (decision) → Commitment
├── Budget → Besteding (expenditure)
├── Document ← Tag (M:N)
├── LessonLearned
├── Afhankelijkheid (dependency, Project ↔ Project)
├── Overdrachtsplan (handover plan)
│ ├── Criterium
│ └── Acceptatie
└── ProjectUser (team membership, pivot)
User ──── Role (M:N)
AuditLog (append-only, all mutations)
KennisArtikel ← Tag (M:N)
```
## Layer Details
### Strategic Layer
| Model | Key Fields | Relationships |
|---|---|---|
| **Thema** | naam, beschrijving, prioriteit, periode | has many Speerpunten |
| **Speerpunt** | naam, beschrijving, eigenaar, status | belongs to Thema, has many Projects |
| **RoadmapItem** | titel, start, eind, type, status | belongs to Thema |
### Project Layer
| Model | Key Fields | Relationships |
|---|---|---|
| **Project** | naam, beschrijving, eigenaar_id, status, prioriteit, startdatum, streef_einddatum | SoftDeletes, belongs to Speerpunt + User (eigenaar), has many of everything |
| **Fase** | type (enum), status (enum), startdatum, einddatum, opmerkingen | belongs to Project |
| **Risico** | beschrijving, impact, kans, mitigatie, eigenaar | belongs to Project |
| **Afhankelijkheid** | type, beschrijving, status | self-referential N:M between Projects |
### Commitment Layer
| Model | Key Fields | Relationships |
|---|---|---|
| **Commitment** | beschrijving, eigenaar_id, deadline, status, bron | belongs to Project + Besluit, has many Acties |
| **Actie** | beschrijving, eigenaar_id, deadline, status, prioriteit | belongs to Commitment |
### Governance Layer
| Model | Key Fields | Relationships |
|---|---|---|
| **Besluit** | titel, beschrijving, datum, type, status, onderbouwing | belongs to Project, has many Commitments |
| **Budget** | bedrag, type, periode, status | belongs to Project, has many Bestedingen |
| **Besteding** | bedrag, beschrijving, datum, categorie | belongs to Budget |
### Knowledge Layer
| Model | Key Fields | Relationships |
|---|---|---|
| **Document** | titel, type, inhoud, versie, auteur, datum, **embedding** (vector) | belongs to Project + Fase, M:N Tags |
| **KennisArtikel** | titel, inhoud, tags, auteur, datum, **embedding** (vector) | M:N Tags |
| **LessonLearned** | titel, inhoud, project, fase, tags | belongs to Project + Fase |
| **Tag** | naam, categorie | M:N with Documents, KennisArtikels |
### Handover Layer
| Model | Key Fields | Relationships |
|---|---|---|
| **Overdrachtsplan** | type, status, eigenaar_rnd, eigenaar_ontvanger | belongs to Project, has many Criteria + Acceptaties |
| **Criterium** | beschrijving, status, verificatie | belongs to Overdrachtsplan |
| **Acceptatie** | datum, door, opmerkingen, status | belongs to Overdrachtsplan |
### Auth Layer
| Model | Key Fields | Relationships |
|---|---|---|
| **User** | name, email, password, phone, afdeling, functie, 2FA fields | has many ProjectUsers, can own Projects/Commitments/Risicos |
| **Role** | naam, beschrijving, permissies | M:N with Users |
| **ProjectUser** | project_id, user_id, rol (enum) | pivot table |
### System Layer
| Model | Key Fields | Relationships |
|---|---|---|
| **AuditLog** | user_id, action, entity_type, entity_id, payload (JSON) | append-only |
## Innovation Lifecycle Phases (FaseType enum)
```
signaal → verkenning → concept → experiment → pilot → besluitvorming → overdracht_bouwen → overdracht_beheer → evaluatie
```
Special statuses (ProjectStatus only, not FaseType):
- `geparkeerd` — temporarily halted
- `gestopt` — permanently stopped
- `afgerond` — completed
## Key Design Decisions
1. **Dutch naming** — all models, fields, enums use Dutch names to match domain language
2. **Soft deletes on Project only** — projects are never hard-deleted
3. **Embedding vectors on Document + KennisArtikel** — pgvector columns for semantic search
4. **ProjectUser pivot with role** — team membership is role-typed (Eigenaar, Lid, Reviewer, Stakeholder)
5. **FaseType maps 1:1 to first 9 ProjectStatus values**`FaseType::tryFrom($projectStatus->value)` is used for phase transitions

View File

@@ -0,0 +1,121 @@
---
title: Metro Map UI Architecture
created: 2026-04-08
updated: 2026-04-08
status: evolving
tags: [architecture, ui, d3, metro-map, retro]
sources: [resources/js/Components/MetroMap/MetroCanvas.vue, STYLE_GUIDE.md, resources/js/Components/Cli/CliBar.vue]
---
# Metro Map UI Architecture
The entire platform navigates via a **zoomable metro/transit map**. No sidebar, no traditional dashboard. Users explore the innovation landscape visually.
## Map Levels
| Level | What You See | Lines = | Stations = | Zoom Target |
|---|---|---|---|---|
| 1. Strategy | Full innovation landscape | Strategic themes (Thema) | Projects | Click station → Level 2 |
| 2. Project | Single project lifecycle | Lifecycle + Commitments + Documents | Phases, items | Click station → Level 3 |
| 3. Detail | Individual item | (not yet implemented) | — | Click to open |
## Technical Implementation
### MetroCanvas.vue (356 LOC)
- **Rendering**: D3.js 7.9 on SVG
- **Zoom**: `d3.zoom()` with `scaleExtent([0.3, 5])`, pan + zoom
- **Lines**: `d3.curveMonotoneX` paths connecting stations
- **Stations**: Two concentric circles (outer ring = line color, inner dot = status color)
- **Labels**: VT323 monospace font, positioned below stations
- **Interactions**: hover → glow effect + tooltip, click → emit node-click
- **Dependencies**: Dashed lines between connected stations across lines
### Data Contract (MapDataService → MetroCanvas)
```json
{
"lines": [
{ "id": "thema-1", "name": "Waterkwaliteit", "color": "#00d2ff" }
],
"nodes": [
{
"id": "project-1",
"entityId": 1,
"entityType": "project",
"name": "Sensor Netwerk",
"lineId": "thema-1",
"x": -200, "y": 0,
"order": 1,
"status": "verkenning",
"description": "...",
"owner": "Jan",
"badge": "Verkenning",
"children": 5
}
],
"connections": [
{ "from": "project-1", "to": "project-3" }
],
"level": 1
}
```
### Station Status Colors
| Status | Color | Hex |
|---|---|---|
| afgerond / completed | Neon green | `#00ff88` |
| actief / active | Vivid cyan | `#00d2ff` |
| geparkeerd | Warning yellow | `#ffd93d` |
| gestopt | Signal red | `#e94560` |
| default (pending) | Dark fill | `#16213e` |
### Visual Effects
- **Glow filter**: SVG `feGaussianBlur` with `stdDeviation=4`, applied on hover
- **Scanline pattern**: 4px repeating lines at 8% opacity (subtle CRT effect)
- **Hover transition**: 200ms radius 8→12 + glow filter
- **Zoom animation**: 500ms d3 zoom transition for `zoomTo()` method
## C64 CLI Bar (CliBar.vue)
Fixed at bottom of screen. Commodore 64 aesthetic:
- Monospace font (Press Start 2P / VT323)
- Blinking block cursor
- Dark background, cyan/green text
- Natural language input
- Responses slide up above bar with `[AI]` prefix
## Design System
### Color Palette
| Role | Hex | Usage |
|---|---|---|
| Background | `#1a1a2e` | Canvas, page background |
| Surface | `#16213e` | Cards, tooltips, panels |
| Primary | `#0f3460` | Metro lines, primary actions |
| Accent (cyan) | `#00d2ff` | Active states, highlights, CLI cursor |
| Accent (red) | `#e94560` | Warnings, deadlines |
| Accent (green) | `#00ff88` | Success, completed |
| Accent (purple) | `#7b68ee` | Knowledge, documentation |
| Text primary | `#e8e8e8` | Main text |
| Text secondary | `#8892b0` | Labels, secondary text |
### Fonts
| Use | Font | Fallback |
|---|---|---|
| Map labels | VT323 | monospace |
| CLI bar | Press Start 2P | VT323, monospace |
| Body text | IBM Plex Sans | Inter, sans-serif |
| Code | IBM Plex Mono | monospace |
## Planned Enhancements
- **Zoom-to-dimension**: scroll-zoom near a station gradually cross-fades into child dimension (no click needed)
- **Recursive dimensions**: every node can contain children forming a sub-metro-map, infinitely nestable
- **Right-click context menu**: canvas = "New node here", station = "Edit/Delete/Add child"
- **[+] FAB**: creates at current depth
- **Mobile**: list fallback with metro line colors preserved

View File

@@ -0,0 +1,109 @@
---
title: System Architecture
created: 2026-04-08
updated: 2026-04-08
status: evolving
tags: [architecture, stack, docker, layers]
sources: [docker-compose.yml, composer.json, package.json, ai-service/app/main.py]
---
# System Architecture
## Stack
| Layer | Technology | Version |
|---|---|---|
| Backend | Laravel (PHP) | 13.0 / PHP 8.3+ |
| Frontend | Vue 3 + Inertia.js | Vue 3.5, Inertia 3 |
| Build | Vite | 8.0 |
| Styling | Tailwind CSS | 4.2 |
| Visualization | D3.js | 7.9 |
| Database | PostgreSQL + pgvector | 16 |
| Cache / Queue | Redis | alpine |
| AI Service | Python FastAPI | 0.1.0 |
| Auth | Laravel Fortify + Sanctum | Fortify 1.36, Sanctum 4.0 |
| Fonts | VT323, Press Start 2P, IBM Plex Mono | — |
## Architecture Principles
1. **Service-oriented** — domain logic lives in service classes (`app/Services/`), not controllers
2. **Event-driven** — status transitions go through transactional methods with audit logging
3. **API-first** — all functionality reachable via REST endpoints
4. **Audit trail** — all mutations logged to `audit_logs` table (append-only)
5. **AI content labeled** — AI-generated content marked and requires human confirmation
6. **Inertia SPA** — server-side routing (Laravel) with client-side rendering (Vue 3), no separate API layer needed for pages
## Docker Topology
```
┌─────────────────────────────────────────────────────────┐
│ nginx:alpine ─────────────────────────────→ :80 │
│ │ │
│ ▼ │
│ laravel-app (PHP 8.4-FPM) │
│ │ │
│ ├── laravel-worker (queue:work) │
│ ├── laravel-scheduler (cron) │
│ │ │
│ ┌───▼────────────┐ ┌─────────────────┐ │
│ │ postgresql:16 │ │ redis:alpine │ │
│ │ + pgvector │ │ cache/queue │ │
│ │ :5432 │ │ :6379 │ │
│ └─────────────────┘ └─────────────────┘ │
│ │
│ ai-service (Python FastAPI) ──────────────→ :8000 │
│ └── connects to postgresql for embeddings │
└─────────────────────────────────────────────────────────┘
```
7 services total. All on `innovatieplatform` bridge network.
## Data Flow
### Page Rendering (Inertia)
```
Browser → nginx → PHP-FPM → Laravel Router → Controller
→ Service (business logic)
→ Inertia::render('Page', $data)
→ Vue component receives props
→ D3.js renders metro map canvas
```
### API Calls (Map data)
```
Vue component → axios GET /api/map/strategy
→ MapController::apiStrategy()
→ MapDataService::getStrategyMap()
→ Eloquent queries (Thema → Speerpunten → Projects)
→ JSON response {lines, nodes, connections, level}
→ D3 re-renders canvas
```
### AI Integration (planned)
```
CliBar.vue → POST /api/chat
→ Laravel proxy → ai-service:8000/api/chat
→ LangGraph agent → Anthropic Claude
→ RAG: pgvector similarity search on documents
→ Response with source attribution
→ CliBar displays with [AI] prefix
```
## Key Service Classes
| Service | LOC | Responsibility |
|---|---|---|
| `ProjectService` | 186 | Project CRUD, lifecycle transitions, park/stop, audit logging |
| `MapDataService` | 165 | Build metro map data structures (Level 1: strategy, Level 2: project) |
| `ThemaService` | 60 | Theme CRUD operations |
## Configuration
- **Session/Cache/Queue**: All Redis-backed (`config/session.php`, `config/cache.php`, `config/queue.php`)
- **Database**: PostgreSQL with pgvector extension for embedding vectors
- **Auth**: Fortify handles registration/login/password flows, Sanctum for API tokens
- **OPcache**: Production-optimized (`docker/php/opcache.ini`)
- **Gzip**: Enabled in nginx config

View File

@@ -0,0 +1,80 @@
---
title: AI Integration
created: 2026-04-08
updated: 2026-04-08
status: speculative
tags: [concept, ai, rag, langgraph, embeddings]
sources: [ai-service/app/main.py, ai-service/requirements.txt, docker-compose.yml]
---
# AI Integration
## Current State
The AI service is a **Python FastAPI stub** with placeholder endpoints. No actual AI processing is wired up yet.
### Implemented (stub only)
| Endpoint | Method | Status |
|---|---|---|
| `GET /health` | Health check | Working |
| `POST /api/chat` | Chat with context | Stub — returns placeholder text |
| `POST /api/summarize` | Generate summaries | Stub — returns placeholder text |
| `POST /api/search` | Semantic search | Stub — returns empty results |
### Request/Response Models (Pydantic)
```
ChatRequest: message, project_id?, conversation_history[]
ChatResponse: reply, project_id?
SummarizeRequest: content, project_id?, summary_type?
SummarizeResponse: summary, project_id?
SearchRequest: query, project_id?, limit?
SearchResponse: results[{id, content, score, metadata}], query
```
## Planned Architecture
```
Laravel App ↔ HTTP ↔ Python AI-Service (FastAPI)
├── LangGraph Orchestrator
│ ├── Router / Classifier
│ └── Agent graph (state machine)
├── Anthropic Claude (LLM)
├── pgvector (embeddings / similarity search)
└── Tools:
├── DB query (project data, commitments, phases)
├── Document retrieval (semantic search)
└── Embedding generation
```
## RAG Pipeline (planned)
### Sources
- Project descriptions and phase notes
- Documents (uploaded files, meeting notes)
- Lessons learned
- Decisions and their rationale
- Knowledge articles
### Embedding Strategy
- **Storage**: pgvector extension on PostgreSQL 16
- **Models**: Document and KennisArtikel already have `embedding` vector columns
- **Update triggers**: On document create/update, on project phase change
- **Chunking**: Per document type and size
### Agent Skills (from CLAUDE.md)
| Agent | Autonomy | Purpose |
|---|---|---|
| Project Assistant | Low | Answer questions about specific projects |
| Knowledge Assistant | Low | Search and surface knowledge articles |
| Document Assistant | Medium | Summarize, compare, extract from documents |
| System Tasks | High | Background indexing, embedding updates |
## Content Governance Rules
1. AI-generated content always labeled ("AI-suggestie", "Concept")
2. Human confirmation required before AI content gains system status
3. All AI interactions logged (request, response, tools used, sources cited)
4. Source attribution mandatory in AI responses
5. Confidence indicators when certainty is low

View File

@@ -0,0 +1,76 @@
---
title: Innovation Lifecycle
created: 2026-04-08
updated: 2026-04-08
status: evolving
tags: [concept, lifecycle, phases, governance]
sources: [app/Enums/FaseType.php, app/Enums/ProjectStatus.php, app/Services/ProjectService.php]
---
# Innovation Lifecycle
Every innovation project at the R&D lab follows a 9-phase lifecycle, from initial signal to final evaluation. The platform enforces this via `FaseType` and `ProjectStatus` enums with transactional phase transitions.
## Phases
```
signaal → verkenning → concept → experiment → pilot → besluitvorming → overdracht_bouwen → overdracht_beheer → evaluatie
```
| # | Phase | Dutch | Purpose |
|---|---|---|---|
| 1 | Signal | Signaal | Initial idea or observation captured |
| 2 | Exploration | Verkenning | Research and feasibility assessment |
| 3 | Concept | Concept | Design a proposed solution |
| 4 | Experiment | Experiment | Small-scale test of the concept |
| 5 | Pilot | Pilot | Larger-scale test in production environment |
| 6 | Decision | Besluitvorming | Go/no-go decision by governance |
| 7 | Handover to Build | Overdracht bouwen | Transfer to development/implementation team |
| 8 | Handover to Operations | Overdracht beheer | Transfer to operational team for maintenance |
| 9 | Evaluation | Evaluatie | Post-handover review and lessons learned |
## Special Statuses
These are Project-only statuses (not lifecycle phases):
| Status | Purpose |
|---|---|
| Geparkeerd | Temporarily halted — can resume later |
| Gestopt | Permanently stopped — documented why |
| Afgerond | Successfully completed the full lifecycle |
## Phase Transition Logic
Phase transitions go through `ProjectService::transitionPhase()` in a database transaction:
1. Close current active phase (set status → `afgerond`, set `einddatum`)
2. Create new phase record (via `FaseType::tryFrom()` mapping)
3. Update project status
4. Write audit log entry with `from` and `to` states
Key constraint: `FaseType::tryFrom($newStatus->value)` — the first 9 ProjectStatus values map 1:1 to FaseType. Special statuses (geparkeerd, gestopt, afgerond) do not create new Fase records.
## Park and Stop
- **Park** (`ProjectService::park()`): closes active phase with reason in `opmerkingen`, sets status to `geparkeerd`
- **Stop** (`ProjectService::stop()`): same mechanics, sets status to `gestopt`
- Both log the transition reason in the audit trail
## Audit Trail
Every phase transition, park, or stop creates an `AuditLog` record:
```php
AuditLog::create([
'user_id' => Auth::id(),
'action' => "project.phase_transition",
'entity_type' => 'project',
'entity_id' => $project->id,
'payload' => ['from' => 'signaal', 'to' => 'verkenning'],
]);
```
## Metro Map Representation
- **Level 1**: Project status shown as station dot color (green=completed, cyan=active, yellow=parked, red=stopped)
- **Level 2**: Each lifecycle phase is a station on the project's "lifecycle" line, with active phase highlighted

34
wiki/index.md Normal file
View File

@@ -0,0 +1,34 @@
---
title: Wiki Index
created: 2026-04-08
updated: 2026-04-08
---
# Innovatieplatform Wiki Index
## Overview
- [Project Overview](overview.md) — current status, what works, what doesn't
- [Metrics Dashboard](metrics.md) — all numbers with provenance
- [Knowledge Graph](knowledge-graph.yaml) — structured data, machine-queryable
## Architecture
- [System Architecture](architecture/system-architecture.md) — layers, services, data flow
- [Domain Model](architecture/domain-model.md) — entities, relationships, lifecycle states
- [Metro Map UI](architecture/metro-map-ui.md) — D3 canvas, zoom dimensions, interaction model
## Core Concepts
- [Innovation Lifecycle](concepts/innovation-lifecycle.md) — phases from signaal to evaluatie
- [AI Integration](concepts/ai-integration.md) — RAG pipeline, LangGraph, agent skills
## Findings
<!-- Add findings pages — both proven AND disproven -->
## Sessions
- [2026-04-08 Wiki Setup](sessions/2026-04-08-wiki-setup.md) — initial wiki from template, codebase scan
## Not Yet Documented
- RBAC model and permission matrix
- Handover process (overdracht) details
- Budget/finance governance
- Student projects (Phase 2)
- Deployment pipeline / CI/CD

141
wiki/knowledge-graph.yaml Normal file
View File

@@ -0,0 +1,141 @@
# Innovatieplatform — Structured Knowledge Graph
# Single source of truth for numbers, metrics, and verifiable claims.
# Last updated: 2026-04-08
# ── TESTS ──
tests:
phpunit_feature:
count: 0
passing: 0
file: tests/Feature/
date: 2026-04-08
phpunit_unit:
count: 0
passing: 0
file: tests/Unit/
date: 2026-04-08
# ── METRICS ──
metrics:
backend_loc:
value: "~13,500"
source: "wc -l on PHP files excl. vendor"
date: 2026-04-08
frontend_loc:
value: "~3,900"
source: "wc -l on Vue/JS files excl. node_modules"
date: 2026-04-08
eloquent_models:
value: 21
source: "ls app/Models/"
date: 2026-04-08
enums:
value: 14
source: "ls app/Enums/"
date: 2026-04-08
migrations:
value: 27
source: "ls database/migrations/"
date: 2026-04-08
vue_components:
value: 15
source: "find resources/js -name '*.vue'"
date: 2026-04-08
docker_services:
value: 7
source: docker-compose.yml
date: 2026-04-08
git_commits:
value: 7
source: "git log --oneline"
date: 2026-04-08
# ── DISPROVEN CLAIMS ──
disproven: {}
# ── PERFORMANCE ──
performance: {}
# ── ARCHITECTURE ──
architecture:
backend: "Laravel 13.0 (PHP 8.3+)"
frontend: "Vue 3.5 + Inertia.js 3 + Vite 8"
database: "PostgreSQL 16 + pgvector"
ai_service: "Python FastAPI + LangGraph"
cache_queue: "Redis (cache, session, queue)"
styling: "Tailwind CSS 4.2"
visualization: "D3.js 7.9"
auth: "Laravel Fortify + Sanctum"
infrastructure: "Docker Compose (7 services)"
# ── DOMAIN MODEL ──
domain_model:
strategic_layer:
- Thema
- Speerpunt
- RoadmapItem
project_layer:
- Project
- Fase
- Risico
- Afhankelijkheid
commitment_layer:
- Commitment
- Actie
governance_layer:
- Besluit
- Budget
- Besteding
knowledge_layer:
- Document
- KennisArtikel
- LessonLearned
- Tag
handover_layer:
- Overdrachtsplan
- Criterium
- Acceptatie
auth_layer:
- User
- Role
- ProjectUser
system_layer:
- AuditLog
# ── LIFECYCLE PHASES ──
lifecycle_phases:
- signaal
- verkenning
- concept
- experiment
- pilot
- besluitvorming
- overdracht_bouwen
- overdracht_beheer
- evaluatie
# ── SPRINT STATUS ──
sprint_status:
sprint_0_scaffold: completed
sprint_1_foundation: completed
sprint_2_detail: not_started
# ── DATA SOURCES ──
data_sources: {}
# ── TIMELINE ──
timeline:
- date: 2026-04-01
desc: "Initial scaffold: Laravel 13, Vue 3, Docker, domain model"
- date: 2026-04-01
desc: "Style guide: metro map + retro-futurism aesthetic"
- date: 2026-04-02
desc: "Sprint 1: Auth, metro map canvas, services, retro UI"
- date: 2026-04-02
desc: "Fix Dutch model table names and enum values"
- date: 2026-04-02
desc: "Performance: OPcache, gzip, font subsetting, lazy loading"
- date: 2026-04-02
desc: "Logout button, metro label positioning fixes"
- date: 2026-04-08
desc: "Wiki initialized from wiki-template"

11
wiki/log.md Normal file
View File

@@ -0,0 +1,11 @@
---
title: Wiki Log
created: 2026-04-08
updated: 2026-04-08
status: evolving
tags: [log, history]
---
# Wiki Log
## [2026-04-08] init | Wiki initialized from wiki-template, populated with codebase scan data

55
wiki/metrics.md Normal file
View File

@@ -0,0 +1,55 @@
---
title: Metrics Dashboard
created: 2026-04-08
updated: 2026-04-08
status: evolving
tags: [metrics, tests, performance]
sources: [phpunit.xml, composer.json, package.json]
---
# Metrics Dashboard
All numbers with provenance. Source of truth: `knowledge-graph.yaml`.
## Test Results
| Suite | Score | File | Date |
|---|---|---|---|
| PHPUnit Feature | 0/0 | tests/Feature/ | 2026-04-08 |
| PHPUnit Unit | 0/0 | tests/Unit/ | 2026-04-08 |
> No tests written yet. PHPUnit 12.5 is configured and ready.
## Codebase Size
| Metric | Value | Source |
|---|---|---|
| Backend LOC | ~13,500 | PHP files excl. vendor |
| Frontend LOC | ~3,900 | Vue/JS files excl. node_modules |
| Models | 21 | app/Models/ |
| Enums | 14 | app/Enums/ |
| Migrations | 27 | database/migrations/ |
| Vue components | 15 | resources/js/ |
| Docker services | 7 | docker-compose.yml |
## Build Dependencies
| Layer | Package Manager | Production | Dev |
|---|---|---|---|
| Backend | Composer | 5 (Laravel, Fortify, Sanctum, Inertia, Tinker) | 6 (Faker, Pail, Pint, Mockery, Collision, PHPUnit) |
| Frontend | npm | 8 (Vue, Inertia, D3, Pinia, VueUse, fonts) | 6 (Tailwind, Vite, Axios, Concurrently, Laravel-Vite) |
| AI Service | pip | FastAPI, LangGraph, LangChain, Anthropic, pgvector | — |
## Sprint Progress
| Sprint | Status | Key Deliverables |
|---|---|---|
| Sprint 0 — Scaffold | completed | Domain model, Docker, migrations, seeders, agent definitions |
| Sprint 1 — Foundation | completed | Auth (Fortify), metro map canvas (D3), services, retro UI |
| Sprint 2 — Detail & Interaction | not started | Project detail view, zoom-to-dimension, commitment UI |
## Disproven Claims
| Claim | Evidence For | Evidence Against | Date |
|---|---|---|---|
| (none yet) | — | — | — |

69
wiki/overview.md Normal file
View File

@@ -0,0 +1,69 @@
---
title: Project Overview
created: 2026-04-08
updated: 2026-04-08
status: evolving
tags: [overview, status, roadmap]
sources: [CLAUDE.md, composer.json, package.json, docker-compose.yml]
---
# Innovatieplatform — R&D Lab Waterschap Brabantse Delta
Innovation governance platform supporting the full lifecycle of innovation trajectories — from signal to handover — with built-in AI support. The primary UI is a **zoomable metro map** with retro-futurism aesthetic (Commodore 64-inspired CLI bar, pixel accents, monospace typography).
## What Works
| Capability | Status | Evidence |
|---|---|---|
| Authentication (Fortify) | proven | Login, register, password reset, email verification all functional |
| Metro map canvas (D3.js) | proven | Zoomable SVG canvas with stations, lines, hover preview, breadcrumb |
| Project CRUD API | proven | REST endpoints for create, update, transition, park, stop, delete |
| Thema CRUD API | proven | REST endpoints for list, create, update |
| Domain model (21 models) | proven | All Eloquent models with relationships, 27 migrations |
| Service-oriented architecture | proven | ProjectService, MapDataService, ThemaService (411 LOC) |
| Docker Compose infrastructure | proven | 7 services: nginx, php-fpm, worker, scheduler, postgresql, redis, ai-service |
| Retro-futurism UI | proven | C64 CLI bar, VT323/Press Start 2P fonts, dark palette, glow effects |
| Demo data seeder | proven | 4 themes x 3 projects with lifecycle phases |
| Performance optimizations | proven | OPcache, gzip, font subsetting, lazy-loaded pages |
## What Doesn't Work (honestly)
| Capability | Status | Notes |
|---|---|---|
| Project detail pages | not started | Routes exist, page component missing |
| Commitment/action tracking UI | not started | Models + enums exist, no frontend |
| Document management | not started | Document model with embedding vector, no upload/display |
| AI chat integration | not started | Python FastAPI stub only, no LangGraph/RAG pipeline |
| Semantic search | not started | pgvector enabled, no embedding generation |
| RBAC UI | not started | Role/CheckRole middleware exist, no admin UI |
| Test suite | not started | PHPUnit 12.5 configured, 0 tests written |
| Roadmap visualization | not started | RoadmapItem model exists, no UI |
| Zoom-to-dimension transitions | not started | Canvas supports zoom, no cross-fade to child dimension |
| Right-click create/edit | not started | Planned interaction pattern, not implemented |
| Bilingual NL/EN | not started | All code in Dutch, no i18n framework |
## Current Scale
| Metric | Value |
|---|---|
| Backend LOC (excl. vendor) | ~13,500 |
| Frontend LOC (excl. node_modules) | ~3,900 |
| Eloquent models | 21 |
| Enums | 14 |
| Services | 3 |
| Controllers | 4 |
| Vue components/pages | 15 |
| Database migrations | 27 |
| Docker services | 7 |
| Git commits | 7 |
| Test coverage | 0% |
## Next Steps (Priority Order)
1. **Project detail view** — click station -> full project page with phases, commitments, documents
2. **Zoom-to-dimension** — scroll-zoom near station cross-fades into child metro map
3. **Commitment/action tracking** — UI for managing commitments with deadlines and owners
4. **Test suite** — PHPUnit feature tests for API + unit tests for services
5. **AI chat integration** — Wire Python FastAPI service with LangGraph orchestrator
6. **Document management** — Upload, link, embed, search documents
7. **RBAC admin** — Role assignment UI, permission management

View File

@@ -0,0 +1,41 @@
---
title: "Session: Wiki Setup"
created: 2026-04-08
updated: 2026-04-08
status: evolving
tags: [session, setup, wiki]
---
# 2026-04-08 — Wiki Setup
## Scope
Initialize wiki from wiki-template and populate with real codebase data.
## What Was Done
1. **Copied wiki-template** from `/home/znetsixe/wiki-template/` to `innovatieplatform/wiki/`
2. **Populated overview.md** with what works / what doesn't based on actual code scan
3. **Populated metrics.md** with codebase size, dependencies, sprint progress
4. **Populated knowledge-graph.yaml** with structured data: tests, metrics, architecture, domain model, lifecycle phases, timeline
5. **Created architecture pages**:
- `architecture/system-architecture.md` — stack, Docker topology, data flow, services
- `architecture/domain-model.md` — 21 models in 8 layers, relationships, enums, lifecycle
- `architecture/metro-map-ui.md` — D3 canvas, data contract, design system, planned enhancements
6. **Created concept pages**:
- `concepts/innovation-lifecycle.md` — 9 phases, transition logic, audit trail
- `concepts/ai-integration.md` — current stub state, planned RAG architecture
7. **Updated index.md** with all new pages
8. **Wired wiki into CLAUDE.md** for agent startup
## Key Numbers (from scan)
| Metric | Value |
|---|---|
| Backend LOC | ~13,500 |
| Frontend LOC | ~3,900 |
| Models | 21 |
| Enums | 14 |
| Migrations | 27 |
| Vue components | 15 |
| Tests | 0 |
| Git commits | 7 |

46
wiki/tools/lint.sh Normal file
View File

@@ -0,0 +1,46 @@
#!/bin/bash
# Wiki health check — find issues
# Usage: ./wiki/tools/lint.sh
WIKI_DIR="$(dirname "$(dirname "$(readlink -f "$0")")")"
echo "=== Wiki Health Check ==="
echo ""
echo "-- Page count --"
find "$WIKI_DIR" -name "*.md" -not -path "*/tools/*" | wc -l
echo " total pages"
echo ""
echo "-- Orphans (not linked from other pages) --"
for f in $(find "$WIKI_DIR" -name "*.md" -not -name "index.md" -not -name "log.md" -not -name "SCHEMA.md" -not -path "*/tools/*"); do
basename=$(basename "$f" .md)
refs=$(grep -rl --include="*.md" "$basename" "$WIKI_DIR" 2>/dev/null | grep -v "$f" | wc -l)
if [ "$refs" -eq 0 ]; then
echo " ORPHAN: $f"
fi
done
echo ""
echo "-- Status distribution --"
for status in proven disproven evolving speculative; do
count=$(grep -rl "status: $status" "$WIKI_DIR" --include="*.md" 2>/dev/null | wc -l)
echo " $status: $count"
done
echo ""
echo "-- Pages missing frontmatter --"
for f in $(find "$WIKI_DIR" -name "*.md" -not -name "SCHEMA.md" -not -path "*/tools/*"); do
if ! head -1 "$f" | grep -q "^---"; then
echo " NO FRONTMATTER: $f"
fi
done
echo ""
echo "-- Index completeness --"
indexed=$(grep -c '\[.*\](.*\.md)' "$WIKI_DIR/index.md" 2>/dev/null)
total=$(find "$WIKI_DIR" -name "*.md" -not -name "index.md" -not -name "log.md" -not -name "SCHEMA.md" -not -path "*/tools/*" | wc -l)
echo " Indexed: $indexed / Total: $total"
echo ""
echo "=== Done ==="

249
wiki/tools/query.py Normal file
View File

@@ -0,0 +1,249 @@
#!/usr/bin/env python3
"""Wiki Knowledge Graph query tool.
Queryable interface over knowledge-graph.yaml + wiki pages.
Usable by both humans (CLI) and LLM agents (imported).
Usage:
python wiki/tools/query.py health # project health
python wiki/tools/query.py entity "search term" # everything about an entity
python wiki/tools/query.py metric "search term" # find metrics
python wiki/tools/query.py status "proven" # all pages with status
python wiki/tools/query.py test "test name" # test results
python wiki/tools/query.py search "keyword" # full-text search
python wiki/tools/query.py related "page-name" # pages linking to/from
python wiki/tools/query.py timeline # commit timeline
"""
import yaml
import os
import sys
import re
from pathlib import Path
WIKI_DIR = Path(__file__).parent.parent
GRAPH_PATH = WIKI_DIR / 'knowledge-graph.yaml'
def load_graph():
if not GRAPH_PATH.exists():
return {}
with open(GRAPH_PATH) as f:
return yaml.safe_load(f) or {}
def load_all_pages():
pages = {}
for md_path in WIKI_DIR.rglob('*.md'):
if 'tools' in str(md_path):
continue
rel = md_path.relative_to(WIKI_DIR)
content = md_path.read_text()
meta = {}
if content.startswith('---'):
parts = content.split('---', 2)
if len(parts) >= 3:
try:
meta = yaml.safe_load(parts[1]) or {}
except yaml.YAMLError:
pass
content = parts[2]
links = re.findall(r'\[\[([^\]]+)\]\]', content)
pages[str(rel)] = {
'path': str(rel), 'meta': meta, 'content': content,
'links': links, 'title': meta.get('title', str(rel)),
'status': meta.get('status', 'unknown'),
'tags': meta.get('tags', []),
}
return pages
def flatten_graph(graph, prefix=''):
items = []
if isinstance(graph, dict):
for k, v in graph.items():
path = f"{prefix}.{k}" if prefix else k
if isinstance(v, (dict, list)):
items.extend(flatten_graph(v, path))
else:
items.append((path, str(v)))
elif isinstance(graph, list):
for i, v in enumerate(graph):
path = f"{prefix}[{i}]"
if isinstance(v, (dict, list)):
items.extend(flatten_graph(v, path))
else:
items.append((path, str(v)))
return items
def cmd_health():
graph = load_graph()
pages = load_all_pages()
statuses = {}
for p in pages.values():
s = p['status']
statuses[s] = statuses.get(s, 0) + 1
tests = graph.get('tests', {})
total_pass = sum(t.get('passing', 0) for t in tests.values() if isinstance(t, dict))
total_count = sum(t.get('count', t.get('total', 0)) for t in tests.values() if isinstance(t, dict))
disproven = len(graph.get('disproven', {}))
timeline = len(graph.get('timeline', []))
# Count broken links
all_titles = set()
for p in pages.values():
all_titles.add(p['title'].lower())
all_titles.add(p['path'].lower().replace('.md', '').split('/')[-1])
broken = sum(1 for p in pages.values() for link in p['links']
if not any(link.lower().replace('-', ' ') in t or t in link.lower().replace('-', ' ')
for t in all_titles))
print(f"Wiki Health:\n")
print(f" Pages: {len(pages)}")
print(f" Statuses: {statuses}")
if total_count:
print(f" Tests: {total_pass}/{total_count} passing")
print(f" Disproven: {disproven} claims tracked")
print(f" Timeline: {timeline} commits")
print(f" Broken links: {broken}")
def cmd_entity(query):
graph = load_graph()
pages = load_all_pages()
q = query.lower()
print(f"Entity: '{query}'\n")
flat = flatten_graph(graph)
hits = [(p, v) for p, v in flat if q in p.lower() or q in v.lower()]
if hits:
print(" -- Knowledge Graph --")
for path, value in hits[:20]:
print(f" {path}: {value}")
print("\n -- Wiki Pages --")
for rel, page in sorted(pages.items()):
if q in page['content'].lower() or q in page['title'].lower():
lines = [l.strip() for l in page['content'].split('\n')
if q in l.lower() and l.strip()]
print(f" {rel} ({page['status']})")
for line in lines[:3]:
print(f" {line[:100]}")
def cmd_metric(query):
flat = flatten_graph(load_graph())
q = query.lower()
print(f"Metrics matching '{query}':\n")
found = 0
for path, value in flat:
if q in path.lower() or q in value.lower():
print(f" {path}: {value}")
found += 1
if not found:
print(" (no matches)")
def cmd_status(status):
pages = load_all_pages()
graph = load_graph()
print(f"Status: '{status}'\n")
for rel, page in sorted(pages.items()):
if page['status'] == status:
print(f" {page['title']} ({rel})")
if page['tags']:
print(f" tags: {page['tags']}")
if status == 'disproven' and 'disproven' in graph:
print("\n -- Disproven Claims --")
for name, claim in graph['disproven'].items():
print(f" {name}:")
for k, v in claim.items():
print(f" {k}: {v}")
def cmd_test(query):
tests = load_graph().get('tests', {})
q = query.lower()
print(f"Test results for '{query}':\n")
for name, suite in tests.items():
if q in name.lower() or q in str(suite).lower():
print(f" -- {name} --")
if isinstance(suite, dict):
for k, v in suite.items():
if isinstance(v, dict):
print(f" {k}: {v.get('passing', '?')}/{v.get('total', '?')}")
elif k in ('count', 'passing', 'accuracy', 'file', 'date'):
print(f" {k}: {v}")
elif k == 'results' and isinstance(v, list):
for r in v:
mark = '' if r.get('result') == 'pass' else ''
print(f" {mark} {r.get('test', '?')}")
def cmd_search(query):
flat = flatten_graph(load_graph())
pages = load_all_pages()
q = query.lower()
print(f"Search: '{query}'\n")
graph_hits = [(p, v) for p, v in flat if q in v.lower()]
if graph_hits:
print(f" -- Knowledge Graph ({len(graph_hits)} hits) --")
for p, v in graph_hits[:10]:
print(f" {p}: {v[:80]}")
page_hits = sorted(
[(page['content'].lower().count(q), rel, page['title'])
for rel, page in pages.items() if q in page['content'].lower()],
reverse=True)
if page_hits:
print(f"\n -- Wiki Pages ({len(page_hits)} pages) --")
for count, rel, title in page_hits:
print(f" {count:3d}x {title} ({rel})")
def cmd_related(page_name):
pages = load_all_pages()
q = page_name.lower().replace('-', ' ').replace('_', ' ')
print(f"Related to: '{page_name}'\n")
print(" -- Links TO --")
for rel, page in sorted(pages.items()):
for link in page['links']:
if q in link.lower().replace('-', ' '):
print(f" <- {page['title']} ({rel})")
break
print("\n -- Links FROM --")
for rel, page in pages.items():
if q in page['title'].lower().replace('-', ' '):
for link in page['links']:
print(f" -> [[{link}]]")
break
def cmd_timeline():
for entry in load_graph().get('timeline', []):
print(f" [{entry.get('date')}] {entry.get('commit', '?')}: {entry.get('desc', '?')}")
COMMANDS = {
'health': cmd_health, 'entity': cmd_entity, 'metric': cmd_metric,
'status': cmd_status, 'test': cmd_test, 'search': cmd_search,
'related': cmd_related, 'timeline': cmd_timeline,
}
if __name__ == '__main__':
if len(sys.argv) < 2 or sys.argv[1] not in COMMANDS:
print(f"Usage: query.py <{'|'.join(COMMANDS)}> [args]")
sys.exit(1)
cmd = sys.argv[1]
args = sys.argv[2:]
if cmd in ('timeline', 'health'):
COMMANDS[cmd]()
elif args:
COMMANDS[cmd](' '.join(args))
else:
print(f"Usage: query.py {cmd} <query>")

18
wiki/tools/search.sh Normal file
View File

@@ -0,0 +1,18 @@
#!/bin/bash
# Search the wiki — usable by both humans and LLM agents
# Usage: ./wiki/tools/search.sh "query" [--files-only]
WIKI_DIR="$(dirname "$(dirname "$(readlink -f "$0")")")"
QUERY="$1"
MODE="${2:---content}"
if [ -z "$QUERY" ]; then
echo "Usage: $0 <query> [--files-only]"
exit 1
fi
if [ "$MODE" = "--files-only" ]; then
grep -rl --include="*.md" --include="*.yaml" "$QUERY" "$WIKI_DIR" 2>/dev/null | sort
else
grep -rn --include="*.md" --include="*.yaml" --color=auto -i "$QUERY" "$WIKI_DIR" 2>/dev/null
fi