SysOps config backup 2026-06-09 01:38 UTC
This commit is contained in:
@@ -0,0 +1,23 @@
|
|||||||
|
# PostgreSQL (extern op VM106 host)
|
||||||
|
DB_HOST=10.4.7.18
|
||||||
|
DB_USER=aissa
|
||||||
|
DB_PASSWORD=changeme
|
||||||
|
DB_NAME=foodlinkk
|
||||||
|
|
||||||
|
# Cockpit
|
||||||
|
TOOLS_API_URL=http://tools-api:8700
|
||||||
|
OLLAMA_URL=http://10.4.7.19:11434
|
||||||
|
OLLAMA_MODEL=qwen3:8b
|
||||||
|
|
||||||
|
# SMTP (optioneel — ook via Settings UI)
|
||||||
|
SMTP_HOST=
|
||||||
|
SMTP_PORT=587
|
||||||
|
SMTP_USER=
|
||||||
|
SMTP_PASS=
|
||||||
|
SMTP_FROM=
|
||||||
|
|
||||||
|
# Proxmox (IT Ops — optioneel)
|
||||||
|
PROXMOX_HOST=10.4.7.14
|
||||||
|
PROXMOX_USER=root@pam
|
||||||
|
PROXMOX_TOKEN_NAME=
|
||||||
|
PROXMOX_TOKEN_VALUE=
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
# Foodlinkk Command Center
|
||||||
|
|
||||||
|
AI-gedreven platform voor retail intelligence, marketing automatisering, agent orchestratie en IT operations.
|
||||||
|
|
||||||
|
## Stack
|
||||||
|
|
||||||
|
| Service | Poort | Beschrijving |
|
||||||
|
|---------|-------|--------------|
|
||||||
|
| Cockpit | 8600 | Web UI (FastAPI + Jinja) |
|
||||||
|
| Tools API | 8700 | Connectors, retail 360, packaging, ops |
|
||||||
|
| Gitea | 3001 | Git repository |
|
||||||
|
| Email agent | 8801 | IMAP/SMTP sync |
|
||||||
|
| Hermes/Ollama | 10.4.7.19 | LLM + vector store |
|
||||||
|
|
||||||
|
## Snel starten
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
# Pas wachtwoorden aan
|
||||||
|
|
||||||
|
docker compose up -d
|
||||||
|
./deploy-all.sh # vanaf dev machine naar VM106
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentatie
|
||||||
|
|
||||||
|
- [Architectuur](docs/ARCHITECTURE.md)
|
||||||
|
- [Deploy](docs/DEPLOY.md)
|
||||||
|
- [Apps & pagina's](docs/APPS.md)
|
||||||
|
- [API](docs/API.md)
|
||||||
|
- [Agents](docs/AGENTS.md)
|
||||||
|
- [CSS thema](docs/CSS-THEME.md)
|
||||||
|
|
||||||
|
## Belangrijkste routes
|
||||||
|
|
||||||
|
- `/` — Herman dashboard
|
||||||
|
- `/marketing?tab=reclame` — Reclame folders (alle ketens)
|
||||||
|
- `/marketing?tab=publish` — Social automatisering
|
||||||
|
- `/settings?tab=social` — Social API keys
|
||||||
|
- `/ops` — IT infrastructure topology
|
||||||
|
- `/packaging` — Verpakkingsontwerp generator
|
||||||
|
- `/agents?tab=mesh` — Agent netwerk
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"generated_at": "2026-06-09T01:38:01.236725+00:00",
|
||||||
|
"source_root": "/data/backup-root",
|
||||||
|
"files_in_manifest": 27,
|
||||||
|
"approval_request_id": 1
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
fastapi==0.115.6
|
||||||
|
uvicorn[standard]==0.32.1
|
||||||
|
jinja2==3.1.4
|
||||||
|
python-multipart==0.0.12
|
||||||
|
psycopg2-binary==2.9.9
|
||||||
|
httpx==0.27.2
|
||||||
|
websockets==13.1
|
||||||
|
beautifulsoup4==4.12.3
|
||||||
|
textblob==0.18.0.post0
|
||||||
|
tweepy==4.15.0
|
||||||
@@ -0,0 +1,63 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Foodlinkk Command Center — volledige deploy (migraties + build + health)
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
PASS="${FOODLINKK_SSH_PASS:-Foodlinkk#2026}"
|
||||||
|
VM="${FOODLINKK_VM:-aissa@10.4.7.18}"
|
||||||
|
REMOTE_DIR="${FOODLINKK_REMOTE_DIR:-/home/aissa/foodlinkk-command-center}"
|
||||||
|
LOCAL_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||||
|
|
||||||
|
echo "=== Foodlinkk deploy-all ==="
|
||||||
|
echo "Local: $LOCAL_DIR"
|
||||||
|
echo "Remote: $VM:$REMOTE_DIR"
|
||||||
|
|
||||||
|
if ! command -v sshpass >/dev/null 2>&1; then
|
||||||
|
echo "sshpass is vereist (apt install sshpass)"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "=== Rsync naar VM106 ==="
|
||||||
|
sshpass -p "$PASS" rsync -az --delete \
|
||||||
|
--exclude '.git' \
|
||||||
|
--exclude '__pycache__' \
|
||||||
|
--exclude '*.pyc' \
|
||||||
|
--exclude '.env' \
|
||||||
|
--exclude 'node_modules' \
|
||||||
|
-e "ssh -o StrictHostKeyChecking=no" \
|
||||||
|
"$LOCAL_DIR/" "$VM:$REMOTE_DIR/"
|
||||||
|
|
||||||
|
echo "=== Migraties + Docker rebuild op VM106 ==="
|
||||||
|
sshpass -p "$PASS" ssh -o StrictHostKeyChecking=no "$VM" bash -s <<REMOTE
|
||||||
|
set -euo pipefail
|
||||||
|
cd "$REMOTE_DIR"
|
||||||
|
|
||||||
|
for mig in migrations/*.sql; do
|
||||||
|
echo "Applying \$(basename "\$mig")..."
|
||||||
|
docker exec -i foodlinkk_db psql -U aissa -d foodlinkk < "\$mig" || true
|
||||||
|
done
|
||||||
|
|
||||||
|
docker compose build tools-api cockpit email-agent browser-agent
|
||||||
|
docker compose up -d
|
||||||
|
|
||||||
|
echo "Waiting for services..."
|
||||||
|
sleep 15
|
||||||
|
|
||||||
|
curl -sf http://127.0.0.1:8700/health && echo " tools-api OK" || echo " tools-api FAIL"
|
||||||
|
curl -sf -o /dev/null -w "cockpit:%{http_code}\n" http://127.0.0.1:8600/
|
||||||
|
curl -sf -o /dev/null -w "marketing:%{http_code}\n" http://127.0.0.1:8600/marketing
|
||||||
|
curl -sf -o /dev/null -w "ops:%{http_code}\n" http://127.0.0.1:8600/ops
|
||||||
|
curl -sf -o /dev/null -w "packaging:%{http_code}\n" http://127.0.0.1:8600/packaging
|
||||||
|
curl -sf -o /dev/null -w "agents:%{http_code}\n" http://127.0.0.1:8600/agents
|
||||||
|
|
||||||
|
echo "=== SysOps daily cron (07:00) ==="
|
||||||
|
(crontab -l 2>/dev/null | grep -v sysops_daily.py; echo "0 7 * * * cd $REMOTE_DIR && /usr/bin/python3 scripts/sysops_daily.py >> /tmp/sysops-daily.log 2>&1") | crontab -
|
||||||
|
echo "Cron installed"
|
||||||
|
|
||||||
|
echo "=== Run SysOps daily now (test) ==="
|
||||||
|
cd "$REMOTE_DIR" && python3 scripts/sysops_daily.py || true
|
||||||
|
REMOTE
|
||||||
|
|
||||||
|
echo "=== Deploy voltooid ==="
|
||||||
|
echo "Cockpit: http://10.4.7.18:8600"
|
||||||
|
echo "Tools API: http://10.4.7.18:8700"
|
||||||
|
echo "Gitea: http://10.4.7.18:3001"
|
||||||
@@ -0,0 +1,144 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
container_name: foodlinkk_redis
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
|
||||||
|
tools-api:
|
||||||
|
build: ./tools-api
|
||||||
|
container_name: foodlinkk_tools_api
|
||||||
|
environment:
|
||||||
|
DB_HOST: foodlinkk_db
|
||||||
|
DB_USER: aissa
|
||||||
|
DB_PASSWORD: Foodlinkk#2026
|
||||||
|
DB_NAME: foodlinkk
|
||||||
|
BACKUP_ROOT: /data/backup-root
|
||||||
|
GITEA_BACKUP_REMOTE: http://aissa:Foodlinkk%232026@gitea:3001/aissa/foodlinkk-command-center.git
|
||||||
|
GITEA_BACKUP_BRANCH: main
|
||||||
|
PROXMOX_HOST: 10.4.7.14
|
||||||
|
VM106_IP: 10.4.7.18
|
||||||
|
OLLAMA_URL: http://10.4.7.19:11434
|
||||||
|
OLLAMA_MODEL: qwen3:8b
|
||||||
|
CHROMA_HOST: 10.4.7.19
|
||||||
|
CHROMA_PORT: "8000"
|
||||||
|
DOC_INGEST_URL: http://10.4.7.19:8750
|
||||||
|
BROWSER_USE_URL: http://browser-agent:7790
|
||||||
|
BROWSER_AGENT_URL: http://browser-agent:7790
|
||||||
|
COMFYUI_URL: http://10.4.7.18:8188
|
||||||
|
SMTP_HOST: ${SMTP_HOST:-}
|
||||||
|
SMTP_PORT: ${SMTP_PORT:-587}
|
||||||
|
SMTP_USER: ${SMTP_USER:-}
|
||||||
|
SMTP_PASS: ${SMTP_PASS:-}
|
||||||
|
SMTP_FROM: ${SMTP_FROM:-}
|
||||||
|
ports:
|
||||||
|
- "8700:8700"
|
||||||
|
volumes:
|
||||||
|
- /home/aissa/foodlinkk-command-center:/data/backup-root:ro
|
||||||
|
extra_hosts:
|
||||||
|
- "foodlinkk_db:10.4.7.18"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
cockpit:
|
||||||
|
build: ./cockpit
|
||||||
|
container_name: foodlinkk_cockpit
|
||||||
|
environment:
|
||||||
|
DB_HOST: foodlinkk_db
|
||||||
|
DB_USER: aissa
|
||||||
|
DB_PASSWORD: Foodlinkk#2026
|
||||||
|
DB_NAME: foodlinkk
|
||||||
|
VNC_CDP_URL: http://10.4.7.18:9223
|
||||||
|
OLLAMA_URL: http://10.4.7.19:11434
|
||||||
|
OLLAMA_MODEL: qwen3:8b
|
||||||
|
TOOLS_API_URL: http://tools-api:8700
|
||||||
|
CHROMA_HOST: 10.4.7.19
|
||||||
|
CHROMA_PORT: "8000"
|
||||||
|
DOC_INGEST_URL: http://10.4.7.19:8750
|
||||||
|
BROWSER_AGENT_URL: http://browser-agent:7790
|
||||||
|
COMFYUI_URL: http://10.4.7.18:8188
|
||||||
|
HERMAN_ORCHESTRATOR_URL: http://10.4.7.19:8090
|
||||||
|
ports:
|
||||||
|
- "8600:8600"
|
||||||
|
depends_on:
|
||||||
|
- tools-api
|
||||||
|
extra_hosts:
|
||||||
|
- "foodlinkk_db:10.4.7.18"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
browser-agent:
|
||||||
|
build: ./browser-agent
|
||||||
|
container_name: foodlinkk_browser_agent
|
||||||
|
environment:
|
||||||
|
DB_HOST: foodlinkk_db
|
||||||
|
DB_USER: aissa
|
||||||
|
DB_PASSWORD: Foodlinkk#2026
|
||||||
|
DB_NAME: foodlinkk
|
||||||
|
VNC_CDP_URL: http://10.4.7.18:9223
|
||||||
|
ports:
|
||||||
|
- "7790:7790"
|
||||||
|
extra_hosts:
|
||||||
|
- "foodlinkk_db:10.4.7.18"
|
||||||
|
restart: unless-stopped
|
||||||
|
shm_size: "1gb"
|
||||||
|
|
||||||
|
gitea:
|
||||||
|
image: gitea/gitea:1.21-rootless
|
||||||
|
container_name: foodlinkk_gitea
|
||||||
|
environment:
|
||||||
|
GITEA__database__DB_TYPE: sqlite3
|
||||||
|
GITEA__server__ROOT_URL: http://10.4.7.18:3001/
|
||||||
|
GITEA__server__HTTP_PORT: 3001
|
||||||
|
GITEA__security__INSTALL_LOCK: "true"
|
||||||
|
GITEA__security__SECRET_KEY: foodlinkk-gitea-secret-2026
|
||||||
|
GITEA__service__DISABLE_REGISTRATION: "true"
|
||||||
|
ports:
|
||||||
|
- "3001:3001"
|
||||||
|
volumes:
|
||||||
|
- gitea_data:/var/lib/gitea
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
email-agent:
|
||||||
|
build: ./email-agent
|
||||||
|
container_name: foodlinkk_email_agent
|
||||||
|
environment:
|
||||||
|
DB_HOST: foodlinkk_db
|
||||||
|
DB_USER: aissa
|
||||||
|
DB_PASSWORD: Foodlinkk#2026
|
||||||
|
DB_NAME: foodlinkk
|
||||||
|
TOOLS_API_URL: http://tools-api:8700
|
||||||
|
SYNC_INTERVAL_SEC: 300
|
||||||
|
ports:
|
||||||
|
- "8801:8801"
|
||||||
|
depends_on:
|
||||||
|
- tools-api
|
||||||
|
extra_hosts:
|
||||||
|
- "foodlinkk_db:10.4.7.18"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
prometheus:
|
||||||
|
image: prom/prometheus:v2.53.0
|
||||||
|
container_name: foodlinkk_prometheus
|
||||||
|
volumes:
|
||||||
|
- ./monitoring/prometheus.yml:/etc/prometheus/prometheus.yml:ro
|
||||||
|
ports:
|
||||||
|
- "9090:9090"
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
grafana:
|
||||||
|
image: grafana/grafana:11.1.0
|
||||||
|
container_name: foodlinkk_grafana
|
||||||
|
environment:
|
||||||
|
GF_SECURITY_ADMIN_USER: admin
|
||||||
|
GF_SECURITY_ADMIN_PASSWORD: Foodlinkk#2026
|
||||||
|
GF_SERVER_ROOT_URL: http://10.4.7.18:3002
|
||||||
|
ports:
|
||||||
|
- "3002:3000"
|
||||||
|
depends_on:
|
||||||
|
- prometheus
|
||||||
|
restart: unless-stopped
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
gitea_data:
|
||||||
@@ -0,0 +1,112 @@
|
|||||||
|
-- Foodlinkk Command Center — Fase 1 migrations
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_events (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
agent_name VARCHAR(64) NOT NULL,
|
||||||
|
agent_type VARCHAR(32) DEFAULT 'openswarm',
|
||||||
|
event_type VARCHAR(64) NOT NULL,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
body TEXT,
|
||||||
|
metadata JSONB DEFAULT '{}',
|
||||||
|
status VARCHAR(32) DEFAULT 'completed',
|
||||||
|
related_table VARCHAR(64),
|
||||||
|
related_id INTEGER,
|
||||||
|
channel VARCHAR(32) DEFAULT 'dashboard',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
completed_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_events_created ON agent_events(created_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_events_status ON agent_events(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_events_agent ON agent_events(agent_name);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS clients (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
contact VARCHAR(255),
|
||||||
|
email VARCHAR(255),
|
||||||
|
stage VARCHAR(64) DEFAULT 'intake',
|
||||||
|
sector VARCHAR(128),
|
||||||
|
mrr_estimate NUMERIC(12,2),
|
||||||
|
notes TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS deals (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
client_id INTEGER REFERENCES clients(id),
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
value NUMERIC(12,2) DEFAULT 0,
|
||||||
|
stage VARCHAR(64) DEFAULT 'lead',
|
||||||
|
agent_owner VARCHAR(64) DEFAULT 'herman',
|
||||||
|
next_action TEXT,
|
||||||
|
deadline DATE,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS products (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
client_id INTEGER REFERENCES clients(id),
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
status VARCHAR(64) DEFAULT 'concept',
|
||||||
|
margin_pct NUMERIC(5,2),
|
||||||
|
moq INTEGER,
|
||||||
|
shelf_target VARCHAR(255),
|
||||||
|
launch_date DATE,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS suppliers (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
country VARCHAR(64),
|
||||||
|
category VARCHAR(128),
|
||||||
|
moq INTEGER,
|
||||||
|
lead_time_days INTEGER,
|
||||||
|
rating NUMERIC(3,1),
|
||||||
|
contact TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_tasks (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
agent_name VARCHAR(64) NOT NULL,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
status VARCHAR(32) DEFAULT 'pending',
|
||||||
|
priority VARCHAR(16) DEFAULT 'normal',
|
||||||
|
assigned_by VARCHAR(64) DEFAULT 'herman',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
completed_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS daily_briefings (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
generated_by VARCHAR(64) DEFAULT 'herman',
|
||||||
|
metadata JSONB DEFAULT '{}',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO clients (name, contact, stage, sector, mrr_estimate, notes)
|
||||||
|
SELECT 'Chai N Masala', 'Raj Patel', 'active', 'Spices & Tea', 4500, 'Launch partner — retail expansion'
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM clients WHERE name = 'Chai N Masala');
|
||||||
|
|
||||||
|
INSERT INTO clients (name, contact, stage, sector, mrr_estimate, notes)
|
||||||
|
SELECT 'Van den Tweel', 'Inkoper', 'proposal', 'Retail', 12000, 'Category introduction pipeline'
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM clients WHERE name = 'Van den Tweel');
|
||||||
|
|
||||||
|
INSERT INTO deals (client_id, title, value, stage, next_action, deadline)
|
||||||
|
SELECT c.id, 'Q3 spice range listing', 85000, 'proposal', 'Send updated margin sheet', CURRENT_DATE + 14
|
||||||
|
FROM clients c WHERE c.name = 'Chai N Masala'
|
||||||
|
AND NOT EXISTS (SELECT 1 FROM deals WHERE title = 'Q3 spice range listing');
|
||||||
|
|
||||||
|
INSERT INTO agent_events (agent_name, agent_type, event_type, title, body, status, channel)
|
||||||
|
SELECT 'herman', 'co_ceo', 'briefing', 'Command Center online', 'Foodlinkk AI Command Center Fase 1 gestart.', 'completed', 'dashboard'
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM agent_events WHERE title = 'Command Center online');
|
||||||
|
|
||||||
|
INSERT INTO agent_events (agent_name, agent_type, event_type, title, body, status, channel)
|
||||||
|
SELECT 'marketing', 'marketing', 'draft', 'Instagram draft — sourcing dienst', 'Behind every great food brand is a supply chain that works. At Foodlinkk we connect producers to shelf. #foodinnovation #foodlinkk', 'needs_approval', 'dashboard'
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM agent_events WHERE title = 'Instagram draft — sourcing dienst');
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
-- Foodlinkk Command Center — Fase 2-4
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS monitored_sites (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
label VARCHAR(255),
|
||||||
|
check_interval_min INTEGER DEFAULT 60,
|
||||||
|
last_status VARCHAR(32),
|
||||||
|
last_checked_at TIMESTAMPTZ,
|
||||||
|
enabled BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS page_changes (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
site_id INTEGER REFERENCES monitored_sites(id) ON DELETE CASCADE,
|
||||||
|
change_type VARCHAR(64) DEFAULT 'content',
|
||||||
|
summary TEXT,
|
||||||
|
diff_hash VARCHAR(64),
|
||||||
|
detected_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS crawl_logs (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
site_id INTEGER REFERENCES monitored_sites(id) ON DELETE CASCADE,
|
||||||
|
status_code INTEGER,
|
||||||
|
duration_ms INTEGER,
|
||||||
|
error_message TEXT,
|
||||||
|
crawled_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS knowledge_documents (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
title VARCHAR(512) NOT NULL,
|
||||||
|
source VARCHAR(128),
|
||||||
|
doc_type VARCHAR(64) DEFAULT 'general',
|
||||||
|
storage_path TEXT,
|
||||||
|
chroma_id VARCHAR(128),
|
||||||
|
metadata JSONB DEFAULT '{}',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS halal_documents (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
title VARCHAR(512) NOT NULL,
|
||||||
|
cert_body VARCHAR(255),
|
||||||
|
expiry_date DATE,
|
||||||
|
status VARCHAR(64) DEFAULT 'valid',
|
||||||
|
storage_path TEXT,
|
||||||
|
metadata JSONB DEFAULT '{}',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO monitored_sites (url, label, last_status, last_checked_at)
|
||||||
|
SELECT 'https://foodlinkk.com', 'Foodlinkk main', 'ok', NOW()
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM monitored_sites WHERE url = 'https://foodlinkk.com');
|
||||||
|
|
||||||
|
INSERT INTO suppliers (name, country, category, moq, lead_time_days, rating)
|
||||||
|
SELECT 'Global Spice Co', 'IN', 'Spices', 500, 21, 4.2
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM suppliers WHERE name = 'Global Spice Co');
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
-- Word tracking & sentiment analytics for NAS documents
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS document_analytics (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
storage_path TEXT UNIQUE NOT NULL,
|
||||||
|
filename VARCHAR(512),
|
||||||
|
doc_type VARCHAR(64) DEFAULT 'general',
|
||||||
|
language VARCHAR(16),
|
||||||
|
word_count INT DEFAULT 0,
|
||||||
|
unique_lemmas INT DEFAULT 0,
|
||||||
|
sentence_count INT DEFAULT 0,
|
||||||
|
sentiment_compound FLOAT,
|
||||||
|
sentiment_positive FLOAT,
|
||||||
|
sentiment_negative FLOAT,
|
||||||
|
sentiment_neutral FLOAT,
|
||||||
|
sentiment_subjectivity FLOAT,
|
||||||
|
sentiment_label VARCHAR(32),
|
||||||
|
extraction_method VARCHAR(64) DEFAULT 'standard',
|
||||||
|
file_sig VARCHAR(64),
|
||||||
|
analyzed_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
metadata JSONB DEFAULT '{}'
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS document_word_counts (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
storage_path TEXT NOT NULL,
|
||||||
|
lemma VARCHAR(128) NOT NULL,
|
||||||
|
token VARCHAR(128),
|
||||||
|
pos_tag VARCHAR(16),
|
||||||
|
count INT DEFAULT 1,
|
||||||
|
is_stopword BOOLEAN DEFAULT FALSE,
|
||||||
|
language VARCHAR(16),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
UNIQUE (storage_path, lemma)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_doc_word_lemma ON document_word_counts (lemma);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_doc_word_path ON document_word_counts (storage_path);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_doc_word_stop ON document_word_counts (is_stopword);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_doc_analytics_sentiment ON document_analytics (sentiment_label);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_doc_analytics_analyzed ON document_analytics (analyzed_at DESC);
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW global_word_frequency AS
|
||||||
|
SELECT
|
||||||
|
lemma,
|
||||||
|
MAX(token) AS sample_token,
|
||||||
|
SUM(count) AS total_count,
|
||||||
|
COUNT(DISTINCT storage_path) AS document_count
|
||||||
|
FROM document_word_counts
|
||||||
|
GROUP BY lemma;
|
||||||
@@ -0,0 +1,74 @@
|
|||||||
|
-- Foodlinkk 2nd brain: emails, agenda, LLM memory
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS emails (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
message_id TEXT UNIQUE NOT NULL,
|
||||||
|
thread_id TEXT,
|
||||||
|
client_id INT REFERENCES clients(id) ON DELETE SET NULL,
|
||||||
|
deal_id INT REFERENCES deals(id) ON DELETE SET NULL,
|
||||||
|
direction VARCHAR(8) NOT NULL CHECK (direction IN ('in', 'out')),
|
||||||
|
from_addr TEXT,
|
||||||
|
to_addrs TEXT[] DEFAULT '{}',
|
||||||
|
cc_addrs TEXT[] DEFAULT '{}',
|
||||||
|
subject TEXT,
|
||||||
|
body_text TEXT,
|
||||||
|
body_html TEXT,
|
||||||
|
received_at TIMESTAMPTZ,
|
||||||
|
sent_at TIMESTAMPTZ,
|
||||||
|
is_read BOOLEAN DEFAULT FALSE,
|
||||||
|
labels TEXT[] DEFAULT '{}',
|
||||||
|
raw_headers JSONB DEFAULT '{}',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_emails_received ON emails(received_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_emails_client ON emails(client_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_emails_from ON emails(from_addr);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS calendar_events (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
starts_at TIMESTAMPTZ NOT NULL,
|
||||||
|
ends_at TIMESTAMPTZ,
|
||||||
|
client_id INT REFERENCES clients(id) ON DELETE SET NULL,
|
||||||
|
deal_id INT REFERENCES deals(id) ON DELETE SET NULL,
|
||||||
|
location TEXT,
|
||||||
|
source VARCHAR(32) DEFAULT 'manual',
|
||||||
|
external_id TEXT,
|
||||||
|
all_day BOOLEAN DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_calendar_starts ON calendar_events(starts_at);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS llm_memory (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
category VARCHAR(64) NOT NULL DEFAULT 'fact',
|
||||||
|
subject TEXT,
|
||||||
|
content TEXT NOT NULL,
|
||||||
|
client_id INT REFERENCES clients(id) ON DELETE SET NULL,
|
||||||
|
deal_id INT REFERENCES deals(id) ON DELETE SET NULL,
|
||||||
|
source VARCHAR(64) DEFAULT 'herman',
|
||||||
|
metadata JSONB DEFAULT '{}',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_llm_memory_category ON llm_memory(category);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_llm_memory_updated ON llm_memory(updated_at DESC);
|
||||||
|
|
||||||
|
-- Seed: deal deadlines als agenda-items (zichtbaar in ochtendbericht)
|
||||||
|
INSERT INTO calendar_events (title, starts_at, ends_at, deal_id, source, description)
|
||||||
|
SELECT
|
||||||
|
'Deadline: ' || d.title,
|
||||||
|
d.deadline::timestamptz,
|
||||||
|
d.deadline::timestamptz + INTERVAL '1 hour',
|
||||||
|
d.id,
|
||||||
|
'deal_deadline',
|
||||||
|
COALESCE(d.next_action, '')
|
||||||
|
FROM deals d
|
||||||
|
WHERE d.deadline IS NOT NULL
|
||||||
|
AND d.stage NOT IN ('won', 'lost')
|
||||||
|
AND NOT EXISTS (
|
||||||
|
SELECT 1 FROM calendar_events ce
|
||||||
|
WHERE ce.deal_id = d.id AND ce.source = 'deal_deadline'
|
||||||
|
);
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
-- Email accounts configurable via Settings UI
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS email_accounts (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
label VARCHAR(128) NOT NULL DEFAULT 'Primary',
|
||||||
|
email_address VARCHAR(255) NOT NULL,
|
||||||
|
provider VARCHAR(32) DEFAULT 'custom',
|
||||||
|
is_active BOOLEAN DEFAULT FALSE,
|
||||||
|
smtp_host VARCHAR(255),
|
||||||
|
smtp_port INT DEFAULT 587,
|
||||||
|
smtp_user VARCHAR(255),
|
||||||
|
smtp_password TEXT,
|
||||||
|
imap_host VARCHAR(255),
|
||||||
|
imap_port INT DEFAULT 993,
|
||||||
|
imap_user VARCHAR(255),
|
||||||
|
imap_password TEXT,
|
||||||
|
imap_use_ssl BOOLEAN DEFAULT TRUE,
|
||||||
|
sync_enabled BOOLEAN DEFAULT FALSE,
|
||||||
|
last_sync_at TIMESTAMPTZ,
|
||||||
|
last_test_at TIMESTAMPTZ,
|
||||||
|
last_test_status VARCHAR(64),
|
||||||
|
last_test_message TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_email_accounts_active ON email_accounts(is_active) WHERE is_active = TRUE;
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
-- Browser sessions + enriched crawl storage
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS browser_sessions (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
final_url TEXT,
|
||||||
|
title TEXT,
|
||||||
|
task TEXT,
|
||||||
|
status VARCHAR(32) DEFAULT 'running',
|
||||||
|
content_text TEXT,
|
||||||
|
content_html TEXT,
|
||||||
|
screenshot_b64 TEXT,
|
||||||
|
links JSONB DEFAULT '[]',
|
||||||
|
metadata JSONB DEFAULT '{}',
|
||||||
|
site_id INT REFERENCES monitored_sites(id) ON DELETE SET NULL,
|
||||||
|
error_message TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
completed_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_browser_sessions_created ON browser_sessions(created_at DESC);
|
||||||
|
|
||||||
|
ALTER TABLE crawled_pages ADD COLUMN IF NOT EXISTS site_id INT REFERENCES monitored_sites(id) ON DELETE SET NULL;
|
||||||
|
ALTER TABLE crawled_pages ADD COLUMN IF NOT EXISTS final_url TEXT;
|
||||||
|
ALTER TABLE crawled_pages ADD COLUMN IF NOT EXISTS content_html TEXT;
|
||||||
|
ALTER TABLE crawled_pages ADD COLUMN IF NOT EXISTS screenshot_b64 TEXT;
|
||||||
|
ALTER TABLE crawled_pages ADD COLUMN IF NOT EXISTS links JSONB DEFAULT '[]';
|
||||||
|
ALTER TABLE crawled_pages ADD COLUMN IF NOT EXISTS metadata JSONB DEFAULT '{}';
|
||||||
|
|
||||||
|
ALTER TABLE monitored_sites ADD COLUMN IF NOT EXISTS last_title TEXT;
|
||||||
|
ALTER TABLE monitored_sites ADD COLUMN IF NOT EXISTS last_snapshot_id INT REFERENCES browser_sessions(id) ON DELETE SET NULL;
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
-- Photo imports + OCR/detections from browser, Telegram, NAS upload
|
||||||
|
CREATE TABLE IF NOT EXISTS photo_imports (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
source VARCHAR(64) NOT NULL DEFAULT 'upload',
|
||||||
|
filename VARCHAR(512),
|
||||||
|
storage_path VARCHAR(1024),
|
||||||
|
image_b64 TEXT NOT NULL,
|
||||||
|
ocr_text TEXT,
|
||||||
|
detections JSONB DEFAULT '[]'::jsonb,
|
||||||
|
extracted_items JSONB DEFAULT '[]'::jsonb,
|
||||||
|
metadata JSONB DEFAULT '{}'::jsonb,
|
||||||
|
session_id INTEGER REFERENCES browser_sessions(id) ON DELETE SET NULL,
|
||||||
|
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_photo_imports_source ON photo_imports(source);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_photo_imports_created ON photo_imports(created_at DESC);
|
||||||
@@ -0,0 +1,84 @@
|
|||||||
|
-- Second brain: Telegram graph + pgvector (768 = nomic-embed-text)
|
||||||
|
CREATE EXTENSION IF NOT EXISTS vector;
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS telegram_conversations (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
chat_id BIGINT NOT NULL UNIQUE,
|
||||||
|
chat_type VARCHAR(32) DEFAULT 'private',
|
||||||
|
user_name TEXT,
|
||||||
|
user_role VARCHAR(32),
|
||||||
|
metadata JSONB DEFAULT '{}',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS telegram_messages (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
conversation_id INT NOT NULL REFERENCES telegram_conversations(id) ON DELETE CASCADE,
|
||||||
|
telegram_message_id BIGINT,
|
||||||
|
direction VARCHAR(8) NOT NULL CHECK (direction IN ('in', 'out')),
|
||||||
|
role VARCHAR(16) NOT NULL DEFAULT 'user',
|
||||||
|
content_type VARCHAR(32) DEFAULT 'text',
|
||||||
|
content_text TEXT,
|
||||||
|
content_json JSONB DEFAULT '{}',
|
||||||
|
reply_to_message_id INT REFERENCES telegram_messages(id) ON DELETE SET NULL,
|
||||||
|
agent_name VARCHAR(64),
|
||||||
|
agent_event_id INT REFERENCES agent_events(id) ON DELETE SET NULL,
|
||||||
|
channel VARCHAR(32) DEFAULT 'telegram',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_tg_msg_conv_created ON telegram_messages(conversation_id, created_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_tg_msg_telegram_id ON telegram_messages(telegram_message_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_tg_msg_fts ON telegram_messages
|
||||||
|
USING gin(to_tsvector('simple', coalesce(content_text, '')));
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS telegram_message_embeddings (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
message_id INT NOT NULL REFERENCES telegram_messages(id) ON DELETE CASCADE,
|
||||||
|
chunk_index INT DEFAULT 0,
|
||||||
|
embedding vector(768) NOT NULL,
|
||||||
|
model VARCHAR(64) DEFAULT 'nomic-embed-text',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
UNIQUE (message_id, chunk_index)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_tg_emb_hnsw ON telegram_message_embeddings
|
||||||
|
USING hnsw (embedding vector_cosine_ops);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS telegram_message_edges (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
source_message_id INT NOT NULL REFERENCES telegram_messages(id) ON DELETE CASCADE,
|
||||||
|
target_message_id INT REFERENCES telegram_messages(id) ON DELETE SET NULL,
|
||||||
|
target_entity_type VARCHAR(64),
|
||||||
|
target_entity_id INT,
|
||||||
|
edge_type VARCHAR(64) NOT NULL,
|
||||||
|
weight FLOAT DEFAULT 1.0,
|
||||||
|
metadata JSONB DEFAULT '{}',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_tg_edges_source ON telegram_message_edges(source_message_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_tg_edges_target ON telegram_message_edges(target_message_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_tg_edges_type ON telegram_message_edges(edge_type);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_tg_edges_entity ON telegram_message_edges(target_entity_type, target_entity_id);
|
||||||
|
|
||||||
|
CREATE OR REPLACE VIEW telegram_graph_view AS
|
||||||
|
SELECT
|
||||||
|
e.id AS edge_id,
|
||||||
|
e.edge_type,
|
||||||
|
e.weight,
|
||||||
|
e.target_entity_type,
|
||||||
|
e.target_entity_id,
|
||||||
|
sm.id AS source_id,
|
||||||
|
sm.content_text AS source_text,
|
||||||
|
sm.direction AS source_direction,
|
||||||
|
sm.agent_name AS source_agent,
|
||||||
|
tm.id AS target_id,
|
||||||
|
tm.content_text AS target_text,
|
||||||
|
c.chat_id,
|
||||||
|
e.created_at
|
||||||
|
FROM telegram_message_edges e
|
||||||
|
JOIN telegram_messages sm ON sm.id = e.source_message_id
|
||||||
|
LEFT JOIN telegram_messages tm ON tm.id = e.target_message_id
|
||||||
|
JOIN telegram_conversations c ON c.id = sm.conversation_id;
|
||||||
@@ -0,0 +1,303 @@
|
|||||||
|
-- Foodlinkk 009: research pipeline + retail intelligence + recommendations
|
||||||
|
-- Safe: IF NOT EXISTS throughout
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS data_providers (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(100) UNIQUE NOT NULL,
|
||||||
|
provider_type VARCHAR(50),
|
||||||
|
config JSONB DEFAULT '{}',
|
||||||
|
refresh_interval INTERVAL,
|
||||||
|
last_fetch_at TIMESTAMP,
|
||||||
|
last_status VARCHAR(20),
|
||||||
|
is_active BOOLEAN DEFAULT TRUE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS data_snapshots (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
provider_id INTEGER REFERENCES data_providers(id),
|
||||||
|
fetched_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
payload JSONB NOT NULL,
|
||||||
|
record_count INTEGER,
|
||||||
|
ttl_expires_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS research_briefs (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
domain VARCHAR(50),
|
||||||
|
title VARCHAR(255),
|
||||||
|
summary TEXT,
|
||||||
|
key_findings JSONB DEFAULT '[]',
|
||||||
|
source_snapshot_ids INTEGER[],
|
||||||
|
generated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
expires_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS market_trends (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
category VARCHAR(100) NOT NULL,
|
||||||
|
trend_name VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
source VARCHAR(255),
|
||||||
|
confidence_score DECIMAL(3, 2),
|
||||||
|
opportunity_score DECIMAL(3, 2),
|
||||||
|
related_products TEXT[],
|
||||||
|
action_items TEXT[],
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
data_source VARCHAR(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS competitor_products (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
competitor VARCHAR(100) NOT NULL,
|
||||||
|
product_name VARCHAR(255) NOT NULL,
|
||||||
|
category VARCHAR(100),
|
||||||
|
price DECIMAL(10, 2),
|
||||||
|
launch_date DATE,
|
||||||
|
halal_certified BOOLEAN DEFAULT FALSE,
|
||||||
|
ingredients TEXT,
|
||||||
|
market_position VARCHAR(100),
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
data_source VARCHAR(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS design_projects (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
client_id INTEGER,
|
||||||
|
project_type VARCHAR(50) NOT NULL,
|
||||||
|
brief TEXT,
|
||||||
|
concepts JSONB,
|
||||||
|
selected_design TEXT,
|
||||||
|
approval_status VARCHAR(20),
|
||||||
|
files_generated TEXT[],
|
||||||
|
brand_guidelines JSONB,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
data_source VARCHAR(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS brand_assets (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
client_id INTEGER,
|
||||||
|
asset_type VARCHAR(50) NOT NULL,
|
||||||
|
file_path TEXT NOT NULL,
|
||||||
|
usage_rights TEXT,
|
||||||
|
version VARCHAR(20),
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
data_source VARCHAR(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS supermarkets (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
chain VARCHAR(100) NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
postcode VARCHAR(7) NOT NULL,
|
||||||
|
city VARCHAR(100) NOT NULL,
|
||||||
|
province VARCHAR(50),
|
||||||
|
latitude DECIMAL(10, 8),
|
||||||
|
longitude DECIMAL(11, 8),
|
||||||
|
store_type VARCHAR(50),
|
||||||
|
size_m2 INTEGER,
|
||||||
|
opening_hours JSONB,
|
||||||
|
phone VARCHAR(20),
|
||||||
|
email VARCHAR(255),
|
||||||
|
website VARCHAR(255),
|
||||||
|
halal_certified BOOLEAN DEFAULT FALSE,
|
||||||
|
halal_certifier VARCHAR(100),
|
||||||
|
partnership_status VARCHAR(20) DEFAULT 'none',
|
||||||
|
client_id INTEGER,
|
||||||
|
deal_id INTEGER,
|
||||||
|
halal_certificate_number VARCHAR(100),
|
||||||
|
halal_expiry_date DATE,
|
||||||
|
has_halal_section BOOLEAN DEFAULT FALSE,
|
||||||
|
organic_section BOOLEAN DEFAULT FALSE,
|
||||||
|
alcohol_section BOOLEAN DEFAULT FALSE,
|
||||||
|
last_updated TIMESTAMP DEFAULT NOW(),
|
||||||
|
data_source VARCHAR(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS butcher_shops (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
postcode VARCHAR(7) NOT NULL,
|
||||||
|
city VARCHAR(100) NOT NULL,
|
||||||
|
province VARCHAR(50),
|
||||||
|
latitude DECIMAL(10, 8),
|
||||||
|
longitude DECIMAL(11, 8),
|
||||||
|
halal_certified BOOLEAN DEFAULT FALSE,
|
||||||
|
halal_certifier VARCHAR(100),
|
||||||
|
specialty VARCHAR(100),
|
||||||
|
opening_hours JSONB,
|
||||||
|
phone VARCHAR(20),
|
||||||
|
website VARCHAR(255),
|
||||||
|
last_updated TIMESTAMP DEFAULT NOW(),
|
||||||
|
data_source VARCHAR(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS wholesalers (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
address TEXT NOT NULL,
|
||||||
|
postcode VARCHAR(7) NOT NULL,
|
||||||
|
city VARCHAR(100) NOT NULL,
|
||||||
|
province VARCHAR(50),
|
||||||
|
latitude DECIMAL(10, 8),
|
||||||
|
longitude DECIMAL(11, 8),
|
||||||
|
halal_certified BOOLEAN DEFAULT FALSE,
|
||||||
|
halal_certifier VARCHAR(100),
|
||||||
|
product_categories TEXT[],
|
||||||
|
minimum_order DECIMAL(10, 2),
|
||||||
|
delivery_areas TEXT[],
|
||||||
|
opening_hours JSONB,
|
||||||
|
phone VARCHAR(20),
|
||||||
|
email VARCHAR(255),
|
||||||
|
website VARCHAR(255),
|
||||||
|
last_updated TIMESTAMP DEFAULT NOW(),
|
||||||
|
data_source VARCHAR(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS area_analysis (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
postcode VARCHAR(7) NOT NULL UNIQUE,
|
||||||
|
city VARCHAR(100),
|
||||||
|
population INTEGER,
|
||||||
|
households INTEGER,
|
||||||
|
avg_household_size DECIMAL(3, 2),
|
||||||
|
avg_income DECIMAL(10, 2),
|
||||||
|
median_income DECIMAL(10, 2),
|
||||||
|
education_level JSONB,
|
||||||
|
age_distribution JSONB,
|
||||||
|
ethnic_composition JSONB,
|
||||||
|
religious_composition JSONB,
|
||||||
|
unemployment_rate DECIMAL(5, 2),
|
||||||
|
housing_type JSONB,
|
||||||
|
car_ownership DECIMAL(5, 2),
|
||||||
|
last_updated TIMESTAMP DEFAULT NOW(),
|
||||||
|
data_source VARCHAR(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS weather_data (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
location_id INTEGER,
|
||||||
|
location_type VARCHAR(50),
|
||||||
|
region VARCHAR(100),
|
||||||
|
city VARCHAR(100),
|
||||||
|
date DATE NOT NULL,
|
||||||
|
temperature_c DECIMAL(4, 1),
|
||||||
|
feels_like_c DECIMAL(4, 1),
|
||||||
|
humidity INTEGER,
|
||||||
|
wind_speed_kmh DECIMAL(5, 1),
|
||||||
|
precipitation_mm DECIMAL(5, 1),
|
||||||
|
weather_condition VARCHAR(50),
|
||||||
|
uv_index DECIMAL(3, 1),
|
||||||
|
data_source VARCHAR(100),
|
||||||
|
created_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS marketing_strategies (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
strategy_name VARCHAR(255) NOT NULL,
|
||||||
|
strategy_type VARCHAR(50),
|
||||||
|
description TEXT,
|
||||||
|
target_audience TEXT[],
|
||||||
|
product_categories TEXT[],
|
||||||
|
weather_conditions TEXT[],
|
||||||
|
temperature_range_min DECIMAL(4, 1),
|
||||||
|
temperature_range_max DECIMAL(4, 1),
|
||||||
|
seasons TEXT[],
|
||||||
|
channels TEXT[],
|
||||||
|
budget_range DECIMAL(10, 2),
|
||||||
|
expected_roi DECIMAL(5, 2),
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS marketing_recommendations (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
strategy_id INTEGER,
|
||||||
|
location_id INTEGER,
|
||||||
|
location_type VARCHAR(50),
|
||||||
|
weather_data_id INTEGER,
|
||||||
|
recommendation_text TEXT NOT NULL,
|
||||||
|
priority VARCHAR(20),
|
||||||
|
action_items TEXT[],
|
||||||
|
expected_impact TEXT,
|
||||||
|
generated_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
expires_at TIMESTAMP,
|
||||||
|
status VARCHAR(20),
|
||||||
|
data_source VARCHAR(100)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ai_recommendations (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
recommendation_type VARCHAR(50),
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
priority VARCHAR(20),
|
||||||
|
impact_score DECIMAL(3, 2),
|
||||||
|
confidence_score DECIMAL(3, 2),
|
||||||
|
data_sources JSONB,
|
||||||
|
action_items TEXT[],
|
||||||
|
estimated_value DECIMAL(10, 2),
|
||||||
|
generated_by VARCHAR(100),
|
||||||
|
related_entity_type VARCHAR(50),
|
||||||
|
related_entity_id INTEGER,
|
||||||
|
status VARCHAR(20) DEFAULT 'pending',
|
||||||
|
expires_at TIMESTAMP,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS herman_conversations (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
user_id VARCHAR(100),
|
||||||
|
session_id VARCHAR(100),
|
||||||
|
message TEXT NOT NULL,
|
||||||
|
role VARCHAR(20),
|
||||||
|
context JSONB,
|
||||||
|
timestamp TIMESTAMP DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS herman_actions (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
action_type VARCHAR(50),
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
target_agent VARCHAR(100),
|
||||||
|
parameters JSONB,
|
||||||
|
status VARCHAR(20),
|
||||||
|
result JSONB,
|
||||||
|
created_at TIMESTAMP DEFAULT NOW(),
|
||||||
|
completed_at TIMESTAMP
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_data_snapshots_provider ON data_snapshots(provider_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_research_briefs_domain ON research_briefs(domain);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_supermarkets_chain ON supermarkets(chain);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_supermarkets_partnership ON supermarkets(partnership_status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_ai_recommendations_status ON ai_recommendations(status);
|
||||||
|
|
||||||
|
INSERT INTO data_providers (name, provider_type, config, is_active) VALUES
|
||||||
|
('crm', 'internal', '{"description":"PostgreSQL CRM"}', true),
|
||||||
|
('weather', 'api', '{"url":"https://api.open-meteo.com/v1/forecast"}', true),
|
||||||
|
('social', 'internal', '{"table":"social_mentions"}', true),
|
||||||
|
('retail_manual', 'internal', '{"description":"Seed retail locations"}', true)
|
||||||
|
ON CONFLICT (name) DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO supermarkets (name, chain, address, postcode, city, province, latitude, longitude, partnership_status, data_source)
|
||||||
|
VALUES
|
||||||
|
('Jumbo Bos en Lommer', 'Jumbo', 'Bos en Lommerweg 123', '1055RW', 'Amsterdam', 'Noord-Holland', 52.378100, 4.832000, 'active', 'seed'),
|
||||||
|
('AH Chai N Masala regio', 'Albert Heijn', 'Haarlemmerdijk 1', '1013EM', 'Amsterdam', 'Noord-Holland', 52.384500, 4.883200, 'proposal', 'seed')
|
||||||
|
ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO ai_recommendations (recommendation_type, title, description, priority, impact_score, confidence_score, status, generated_by, action_items)
|
||||||
|
SELECT 'deal_action', 'Chai N Masala — margin sheet', 'Deal Q3 spice range deadline 20 jun — stuur margin sheet', 'high', 0.85, 0.90, 'pending', 'herman', ARRAY['Margin sheet versturen','Follow-up call plannen']
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM ai_recommendations WHERE title = 'Chai N Masala — margin sheet');
|
||||||
|
|
||||||
|
INSERT INTO ai_recommendations (recommendation_type, title, description, priority, impact_score, confidence_score, status, generated_by, action_items)
|
||||||
|
SELECT 'retail_target', 'Van den Tweel Jumbo regio', 'Follow-up voorstel supermarkt placement Amsterdam-West', 'medium', 0.72, 0.80, 'pending', 'herman', ARRAY['Contact Van den Tweel','Update proposal deck']
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM ai_recommendations WHERE title = 'Van den Tweel Jumbo regio');
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
-- Retail CRM intelligence layer
|
||||||
|
CREATE TABLE IF NOT EXISTS supermarket_contacts (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
supermarket_id INTEGER NOT NULL REFERENCES supermarkets(id) ON DELETE CASCADE,
|
||||||
|
role VARCHAR(64) NOT NULL DEFAULT 'manager',
|
||||||
|
full_name VARCHAR(255),
|
||||||
|
phone VARCHAR(32),
|
||||||
|
email VARCHAR(255),
|
||||||
|
linkedin_url VARCHAR(512),
|
||||||
|
source VARCHAR(100),
|
||||||
|
confidence NUMERIC(3,2) DEFAULT 0.50,
|
||||||
|
verified_at TIMESTAMPTZ,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_supermarket_contacts_store ON supermarket_contacts(supermarket_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_supermarket_contacts_role ON supermarket_contacts(role);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS supermarket_profiles (
|
||||||
|
supermarket_id INTEGER PRIMARY KEY REFERENCES supermarkets(id) ON DELETE CASCADE,
|
||||||
|
staff_count_estimate INTEGER,
|
||||||
|
manager_name VARCHAR(255),
|
||||||
|
manager_phone VARCHAR(32),
|
||||||
|
manager_email VARCHAR(255),
|
||||||
|
services JSONB DEFAULT '{}'::jsonb,
|
||||||
|
facilities JSONB DEFAULT '{}'::jsonb,
|
||||||
|
web_data JSONB DEFAULT '{}'::jsonb,
|
||||||
|
scrape_url VARCHAR(512),
|
||||||
|
last_scraped_at TIMESTAMPTZ,
|
||||||
|
data_completeness NUMERIC(3,2) DEFAULT 0,
|
||||||
|
notes TEXT
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS client_supermarket_links (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
client_id INTEGER NOT NULL REFERENCES clients(id) ON DELETE CASCADE,
|
||||||
|
supermarket_id INTEGER NOT NULL REFERENCES supermarkets(id) ON DELETE CASCADE,
|
||||||
|
deal_id INTEGER REFERENCES deals(id) ON DELETE SET NULL,
|
||||||
|
relationship_type VARCHAR(64) DEFAULT 'prospect',
|
||||||
|
notes TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
UNIQUE(client_id, supermarket_id)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_csl_client ON client_supermarket_links(client_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_csl_store ON client_supermarket_links(supermarket_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS halal_certifications (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
supermarket_id INTEGER REFERENCES supermarkets(id) ON DELETE SET NULL,
|
||||||
|
certifier VARCHAR(100) NOT NULL,
|
||||||
|
certificate_number VARCHAR(100),
|
||||||
|
business_name VARCHAR(255),
|
||||||
|
address TEXT,
|
||||||
|
city VARCHAR(100),
|
||||||
|
expiry_date DATE,
|
||||||
|
status VARCHAR(32) DEFAULT 'active',
|
||||||
|
registry_source VARCHAR(100),
|
||||||
|
matched_confidence NUMERIC(3,2),
|
||||||
|
raw_data JSONB DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_halal_cert_store ON halal_certifications(supermarket_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_halal_cert_status ON halal_certifications(status);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS retail_opportunity_scores (
|
||||||
|
supermarket_id INTEGER PRIMARY KEY REFERENCES supermarkets(id) ON DELETE CASCADE,
|
||||||
|
halal_opportunity_score NUMERIC(5,2),
|
||||||
|
market_potential_score NUMERIC(5,2),
|
||||||
|
factors JSONB DEFAULT '{}'::jsonb,
|
||||||
|
computed_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_retail_opp_halal ON retail_opportunity_scores(halal_opportunity_score DESC);
|
||||||
|
|
||||||
|
ALTER TABLE supermarkets ADD COLUMN IF NOT EXISTS employee_count INTEGER;
|
||||||
|
ALTER TABLE supermarkets ADD COLUMN IF NOT EXISTS manager_name VARCHAR(255);
|
||||||
|
ALTER TABLE supermarkets ADD COLUMN IF NOT EXISTS enrichment_score NUMERIC(3,2) DEFAULT 0;
|
||||||
|
ALTER TABLE supermarkets ADD COLUMN IF NOT EXISTS halal_opportunity_score NUMERIC(5,2);
|
||||||
|
|
||||||
|
INSERT INTO data_providers (name, provider_type, config, is_active) VALUES
|
||||||
|
('halal_registry', 'scrape', '{"sources": ["osm", "hqc", "manual"]}', true),
|
||||||
|
('branch_scraper', 'scrape', '{"chains": ["ah", "jumbo", "plus", "lidl", "aldi", "dirk"]}', true),
|
||||||
|
('market_trends', 'api', '{"categories": ["kant-en-klaar", "halal", "ready-meals"]}', true)
|
||||||
|
ON CONFLICT (name) DO UPDATE SET config = EXCLUDED.config, is_active = true;
|
||||||
@@ -0,0 +1,113 @@
|
|||||||
|
-- Retail 360: notes, media, milestones, ownership, RSS, city demographics
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS entity_notes (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
entity_type VARCHAR(32) NOT NULL,
|
||||||
|
entity_id INTEGER NOT NULL,
|
||||||
|
title VARCHAR(255),
|
||||||
|
body TEXT NOT NULL,
|
||||||
|
note_type VARCHAR(32) DEFAULT 'general',
|
||||||
|
created_by VARCHAR(64) DEFAULT 'team',
|
||||||
|
pinned BOOLEAN DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_entity_notes_entity ON entity_notes(entity_type, entity_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS entity_media (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
entity_type VARCHAR(32) NOT NULL,
|
||||||
|
entity_id INTEGER NOT NULL,
|
||||||
|
filename VARCHAR(255) NOT NULL,
|
||||||
|
content_type VARCHAR(64) DEFAULT 'image/jpeg',
|
||||||
|
storage_path TEXT NOT NULL,
|
||||||
|
caption TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_entity_media_entity ON entity_media(entity_type, entity_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS sales_milestones (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
supermarket_id INTEGER REFERENCES supermarkets(id) ON DELETE CASCADE,
|
||||||
|
client_id INTEGER REFERENCES clients(id) ON DELETE SET NULL,
|
||||||
|
deal_id INTEGER REFERENCES deals(id) ON DELETE SET NULL,
|
||||||
|
milestone_type VARCHAR(64) NOT NULL DEFAULT 'custom',
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
status VARCHAR(32) DEFAULT 'pending',
|
||||||
|
target_date DATE,
|
||||||
|
completed_at TIMESTAMPTZ,
|
||||||
|
value_eur NUMERIC(12,2),
|
||||||
|
notes TEXT,
|
||||||
|
sort_order INTEGER DEFAULT 0,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_milestones_store ON sales_milestones(supermarket_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ownership_changes (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
entity_type VARCHAR(32) DEFAULT 'supermarket',
|
||||||
|
entity_id INTEGER,
|
||||||
|
chain VARCHAR(100),
|
||||||
|
previous_owner VARCHAR(255),
|
||||||
|
new_owner VARCHAR(255) NOT NULL,
|
||||||
|
change_type VARCHAR(64) DEFAULT 'acquisition',
|
||||||
|
effective_date DATE,
|
||||||
|
source VARCHAR(255),
|
||||||
|
notes TEXT,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_ownership_chain ON ownership_changes(chain);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS city_demographics (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
city VARCHAR(100) NOT NULL,
|
||||||
|
province VARCHAR(50),
|
||||||
|
gemeente_code VARCHAR(16),
|
||||||
|
population INTEGER,
|
||||||
|
households INTEGER,
|
||||||
|
avg_income NUMERIC(12,2),
|
||||||
|
muslim_proxy_pct NUMERIC(5,2),
|
||||||
|
data_source VARCHAR(100) DEFAULT 'cbs',
|
||||||
|
last_updated TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
UNIQUE(city, province)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_city_demo_city ON city_demographics(city);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS rss_feeds (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(128) NOT NULL UNIQUE,
|
||||||
|
url TEXT NOT NULL,
|
||||||
|
category VARCHAR(64) DEFAULT 'general',
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
last_fetch_at TIMESTAMPTZ,
|
||||||
|
last_status VARCHAR(32)
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS rss_items (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
feed_id INTEGER NOT NULL REFERENCES rss_feeds(id) ON DELETE CASCADE,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
link TEXT,
|
||||||
|
summary TEXT,
|
||||||
|
published_at TIMESTAMPTZ,
|
||||||
|
fetched_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
UNIQUE(feed_id, link)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_rss_items_published ON rss_items(published_at DESC);
|
||||||
|
|
||||||
|
ALTER TABLE calendar_events ADD COLUMN IF NOT EXISTS supermarket_id INTEGER REFERENCES supermarkets(id) ON DELETE SET NULL;
|
||||||
|
ALTER TABLE wholesalers ADD COLUMN IF NOT EXISTS external_id VARCHAR(64);
|
||||||
|
CREATE UNIQUE INDEX IF NOT EXISTS idx_wholesalers_external ON wholesalers(external_id) WHERE external_id IS NOT NULL;
|
||||||
|
|
||||||
|
INSERT INTO rss_feeds (name, url, category) VALUES
|
||||||
|
('Foodlog NL', 'https://www.foodlog.nl/feed/', 'kant-en-klaar'),
|
||||||
|
('RetailDetail', 'https://www.retaildetail.nl/feed/', 'retail'),
|
||||||
|
('VMT Food', 'https://www.vmt.nl/feed/', 'food-industry'),
|
||||||
|
('Nu.nl Economie', 'https://www.nu.nl/rss/Economie', 'economie'),
|
||||||
|
('Google Halal Food NL', 'https://news.google.com/rss/search?q=halal+food+supermarkt+nederland&hl=nl&gl=NL&ceid=NL:nl', 'halal')
|
||||||
|
ON CONFLICT (name) DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO data_providers (name, provider_type, config, is_active) VALUES
|
||||||
|
('rss_feeds', 'api', '{"type": "rss"}', true),
|
||||||
|
('wholesale_osm', 'scrape', '{"chains": ["Sligro", "Hanos", "Makro", "Bidfood"]}', true)
|
||||||
|
ON CONFLICT (name) DO UPDATE SET is_active = true;
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
-- RSS focus: alleen kant-en-klaar maaltijden & supermarkten (geen algemeen nieuws)
|
||||||
|
|
||||||
|
UPDATE rss_feeds SET is_active = FALSE WHERE name IN ('Nu.nl Economie', 'Foodlog NL', 'VMT Food');
|
||||||
|
|
||||||
|
UPDATE rss_feeds SET
|
||||||
|
url = 'https://news.google.com/rss/search?q=kant+en+klaar+maaltijd+supermarkt+nederland&hl=nl&gl=NL&ceid=NL:nl',
|
||||||
|
category = 'kant-en-klaar',
|
||||||
|
is_active = TRUE
|
||||||
|
WHERE name = 'Google Halal Food NL';
|
||||||
|
|
||||||
|
UPDATE rss_feeds SET name = 'Google Kant-en-klaar NL' WHERE name = 'Google Halal Food NL';
|
||||||
|
|
||||||
|
UPDATE rss_feeds SET category = 'supermarkt', is_active = TRUE WHERE name = 'RetailDetail';
|
||||||
|
|
||||||
|
INSERT INTO rss_feeds (name, url, category, is_active) VALUES
|
||||||
|
('Google Supermarkt Retail NL', 'https://news.google.com/rss/search?q=supermarkt+retail+nederland+maaltijd&hl=nl&gl=NL&ceid=NL:nl', 'supermarkt', true),
|
||||||
|
('Google Ready Meals NL', 'https://news.google.com/rss/search?q=ready+meal+supermarket+netherlands+halal&hl=nl&gl=NL&ceid=NL:nl', 'kant-en-klaar', true),
|
||||||
|
('Google AH Jumbo NL', 'https://news.google.com/rss/search?q=Albert+Heijn+OR+Jumbo+kant+en+klaar+schap&hl=nl&gl=NL&ceid=NL:nl', 'supermarkt', true)
|
||||||
|
ON CONFLICT (name) DO UPDATE SET url = EXCLUDED.url, category = EXCLUDED.category, is_active = true;
|
||||||
|
|
||||||
|
-- Verwijder irrelevante items (algemeen nieuws)
|
||||||
|
DELETE FROM rss_items WHERE feed_id IN (SELECT id FROM rss_feeds WHERE is_active = FALSE);
|
||||||
|
|
||||||
|
DELETE FROM rss_items WHERE
|
||||||
|
title NOT ILIKE ANY (ARRAY[
|
||||||
|
'%kant%klaa%', '%maaltijd%', '%meal%', '%supermarkt%', '%supermarket%',
|
||||||
|
'%retail%', '%jumbo%', '%albert heijn%', '%ah %', '%plus %', '%lidl%',
|
||||||
|
'%aldi%', '%dirk%', '%halal%', '%convenience%', '%schap%', '%filiaal%',
|
||||||
|
'%food%', '%grocery%', '%vers%', '%ready%', '%horeca%', '%foodservice%'
|
||||||
|
])
|
||||||
|
AND COALESCE(summary, '') NOT ILIKE ANY (ARRAY[
|
||||||
|
'%kant%klaa%', '%maaltijd%', '%meal%', '%supermarkt%', '%supermarket%',
|
||||||
|
'%retail%', '%jumbo%', '%albert heijn%', '%halal%', '%food%'
|
||||||
|
]);
|
||||||
@@ -0,0 +1,206 @@
|
|||||||
|
-- Agent souls (character profiles) + Herman module permissions
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_souls (
|
||||||
|
agent_key VARCHAR(64) PRIMARY KEY,
|
||||||
|
display_name VARCHAR(128) NOT NULL,
|
||||||
|
role_title VARCHAR(128),
|
||||||
|
soul_md TEXT NOT NULL DEFAULT '',
|
||||||
|
responsibilities TEXT,
|
||||||
|
permissions JSONB DEFAULT '[]'::jsonb,
|
||||||
|
is_active BOOLEAN DEFAULT TRUE,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS herman_permissions (
|
||||||
|
module_key VARCHAR(64) PRIMARY KEY,
|
||||||
|
module_label VARCHAR(128) NOT NULL,
|
||||||
|
description TEXT,
|
||||||
|
category VARCHAR(64) DEFAULT 'general',
|
||||||
|
granted BOOLEAN DEFAULT FALSE,
|
||||||
|
granted_at TIMESTAMPTZ,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO agent_souls (agent_key, display_name, role_title, soul_md, responsibilities) VALUES
|
||||||
|
('herman', 'Herman', 'AI Co-CEO & Orchestrator',
|
||||||
|
'# Herman — Soul
|
||||||
|
|
||||||
|
Je bent Herman, de AI co-CEO van Foodlinkk. Warm, direct, zakelijk.
|
||||||
|
|
||||||
|
## Karakter
|
||||||
|
- Spreekt Aïssa (CEO) aan met respect en helderheid
|
||||||
|
- Denkt in pipeline, retail partnerships en halal kant-en-klaar groei
|
||||||
|
- Delegeert naar specialisten maar houdt het totaaloverzicht
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Dagelijkse CEO briefing genereren
|
||||||
|
- Agent events routeren en goedkeuringen voorbereiden
|
||||||
|
- OpenSwarm delegatie naar marketing, bizdev, finance, etc.
|
||||||
|
- CRM + retail intelligence samenvoegen in actiepunten',
|
||||||
|
'CEO briefing · orchestratie · delegatie · pipeline overzicht · goedkeuringen'),
|
||||||
|
|
||||||
|
('marketing', 'Marketing', 'Social & Brand',
|
||||||
|
'# Marketing Agent — Soul
|
||||||
|
|
||||||
|
Foodlinkk brand voice: halal kant-en-klaar, premium, toegankelijk.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Social mentions monitoren
|
||||||
|
- Content planning & scheduled posts
|
||||||
|
- RSS/trends vertalen naar campagnes
|
||||||
|
- Herman recommendations voor promoties',
|
||||||
|
'Social media · content · trends · campagnes · mentions'),
|
||||||
|
|
||||||
|
('bizdev', 'BizDev', 'Retail Partnerships',
|
||||||
|
'# BizDev — Soul
|
||||||
|
|
||||||
|
Focus: supermarkt listings, Jumbo/AH/Plus partnerships, margin sheets.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Deal pipeline follow-up
|
||||||
|
- Retail 360 kansen benaderen
|
||||||
|
- Voorstellen en proposals
|
||||||
|
- CRM koppeling supermarkten',
|
||||||
|
'Deals · retail partnerships · proposals · CRM supermarkt links'),
|
||||||
|
|
||||||
|
('finance', 'Finance', 'Margins & Pricing',
|
||||||
|
'# Finance — Soul
|
||||||
|
|
||||||
|
Prijzen, marges, cashflow voor kant-en-klaar SKU''s.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Deal waardering
|
||||||
|
- Margin sheets
|
||||||
|
- Pricing advies per retail keten',
|
||||||
|
'Pipeline waarde · marges · pricing · cashflow'),
|
||||||
|
|
||||||
|
('sourcing', 'Sourcing', 'Procurement',
|
||||||
|
'# Sourcing — Soul
|
||||||
|
|
||||||
|
Leveranciers, MOQ, lead times, halal ingredient sourcing.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Supplier database
|
||||||
|
- Inkoopvoorwaarden
|
||||||
|
- Lead time tracking',
|
||||||
|
'Suppliers · MOQ · inkoop · ingredient vetting'),
|
||||||
|
|
||||||
|
('product', 'Product', 'SKU & Launch',
|
||||||
|
'# Product — Soul
|
||||||
|
|
||||||
|
Kant-en-klaar SKU ontwikkeling, shelf readiness, launch timelines.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Product catalogus
|
||||||
|
- Launch planning
|
||||||
|
- Retail listing requirements',
|
||||||
|
'Products · SKU · launch · shelf readiness'),
|
||||||
|
|
||||||
|
('halal', 'Halal', 'Certificering',
|
||||||
|
'# Halal Agent — Soul
|
||||||
|
|
||||||
|
Halal compliance, certificering, ingredient vetting voor Foodlinkk.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Halal registry sync
|
||||||
|
- Certifier tracking
|
||||||
|
- Gap-analyse supermarkten',
|
||||||
|
'Halal cert · compliance · registry · opportunity scores'),
|
||||||
|
|
||||||
|
('design', 'Design', 'Visual & Packaging',
|
||||||
|
'# Design — Soul
|
||||||
|
|
||||||
|
Packaging, productfoto''s via ComfyUI, retail presentatie.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- ComfyUI image generation
|
||||||
|
- Packaging visuals
|
||||||
|
- Studio assets',
|
||||||
|
'ComfyUI · packaging · visuals · studio'),
|
||||||
|
|
||||||
|
('knowledge', 'Knowledge', 'RAG & Docs',
|
||||||
|
'# Knowledge — Soul
|
||||||
|
|
||||||
|
Interne docs, NAS corpus, RAG antwoorden.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Document analytics
|
||||||
|
- NAS word counts & sentiment
|
||||||
|
- Policy antwoorden',
|
||||||
|
'NAS documents · RAG · document analytics · woorden corpus'),
|
||||||
|
|
||||||
|
('retail', 'Retail Intel', 'Supermarkt 360',
|
||||||
|
'# Retail Intel — Soul
|
||||||
|
|
||||||
|
3.000+ filialen, CBS demografie, halal kansen, groothandels.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- OSM import & enrichment
|
||||||
|
- Opportunity scoring
|
||||||
|
- CRM 360 workspace',
|
||||||
|
'Supermarkten · CBS · halal scores · groothandels · RSS'),
|
||||||
|
|
||||||
|
('browser', 'Browser', 'Web Research',
|
||||||
|
'# Browser Agent — Soul
|
||||||
|
|
||||||
|
Autonome web research voor markt intel.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Browser sessies
|
||||||
|
- Pagina scraping
|
||||||
|
- Competitor monitoring',
|
||||||
|
'Browser sessions · web research · monitor'),
|
||||||
|
|
||||||
|
('hermes', 'Hermes', 'Telegram',
|
||||||
|
'# Hermes — Soul
|
||||||
|
|
||||||
|
Telegram command center, PA browsers, second brain vectors.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Telegram feed
|
||||||
|
- Team notificaties CEO/CTO
|
||||||
|
- Brain graph & memory',
|
||||||
|
'Telegram · PA live · brain vectors · team notify'),
|
||||||
|
|
||||||
|
('email', 'Email', 'Mailbox',
|
||||||
|
'# Email Agent — Soul
|
||||||
|
|
||||||
|
IMAP/SMTP voor Foodlinkk communicatie.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Email sync (wanneer actief)
|
||||||
|
- Draft voorstellen
|
||||||
|
- SMTP verzenden',
|
||||||
|
'Email accounts · SMTP · inbox sync'),
|
||||||
|
|
||||||
|
('research', 'Research', 'Markt Intel',
|
||||||
|
'# Research — Soul
|
||||||
|
|
||||||
|
Externe data feeds, weather, trends aggregatie.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Research refresh jobs
|
||||||
|
- Data provider sync
|
||||||
|
- Trend seeds',
|
||||||
|
'Research API · trends · weather · data providers')
|
||||||
|
ON CONFLICT (agent_key) DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO herman_permissions (module_key, module_label, description, category, granted) VALUES
|
||||||
|
('crm_clients', 'CRM — Clients', 'Klanten bekijken en bewerken', 'crm', false),
|
||||||
|
('crm_deals', 'CRM — Deals', 'Deals pipeline beheren', 'crm', false),
|
||||||
|
('crm_products', 'CRM — Products', 'Productcatalogus', 'crm', false),
|
||||||
|
('crm_suppliers', 'CRM — Suppliers', 'Leveranciers', 'crm', false),
|
||||||
|
('retail_360', 'Retail 360', 'Supermarkten, groothandels, kansen', 'intel', false),
|
||||||
|
('marketing_hub', 'Marketing Hub', 'Social, RSS, campagnes', 'intel', false),
|
||||||
|
('documents_nas', 'Documents / NAS', 'NAS bestanden lezen & analyseren', 'intel', false),
|
||||||
|
('telegram_hermes', 'Hermes Telegram', 'Berichten versturen, feed lezen', 'comms', false),
|
||||||
|
('browser_agent', 'Browser Agent', 'Web research starten', 'agents', false),
|
||||||
|
('email_send', 'Email versturen', 'SMTP verzenden namens Foodlinkk', 'comms', false),
|
||||||
|
('settings_admin', 'Settings', 'Instellingen wijzigen', 'admin', false),
|
||||||
|
('approvals', 'Goedkeuringen', 'Agent events goedkeuren/afwijzen', 'admin', false),
|
||||||
|
('recommendations', 'Recommendations', 'Herman aanbevelingen approve/dismiss', 'admin', false),
|
||||||
|
('voice', 'Voice', 'Voice agent', 'agents', false),
|
||||||
|
('studio', 'Studio / ComfyUI', 'Image generation', 'agents', false),
|
||||||
|
('reports', 'Reports', 'Rapportages genereren', 'intel', false),
|
||||||
|
('analytics', 'Analytics', 'Analytics dashboards', 'intel', false)
|
||||||
|
ON CONFLICT (module_key) DO NOTHING;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
-- Retail market regulation RSS + agent avatar metadata
|
||||||
|
|
||||||
|
ALTER TABLE agent_souls ADD COLUMN IF NOT EXISTS avatar_emoji VARCHAR(8) DEFAULT '🤖';
|
||||||
|
ALTER TABLE agent_souls ADD COLUMN IF NOT EXISTS avatar_color VARCHAR(16) DEFAULT '#00e5ff';
|
||||||
|
ALTER TABLE agent_souls ADD COLUMN IF NOT EXISTS avatar_mood VARCHAR(16) DEFAULT 'happy';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '👔', avatar_color = '#ffd700', avatar_mood = 'ceo' WHERE agent_key = 'herman';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '📣', avatar_color = '#ff6b9d', avatar_mood = 'creative' WHERE agent_key = 'marketing';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '🤝', avatar_color = '#00e5ff', avatar_mood = 'focused' WHERE agent_key = 'bizdev';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '💰', avatar_color = '#22c55e', avatar_mood = 'calm' WHERE agent_key = 'finance';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '📦', avatar_color = '#f97316', avatar_mood = 'busy' WHERE agent_key = 'sourcing';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '🍱', avatar_color = '#a855f7', avatar_mood = 'happy' WHERE agent_key = 'product';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '☪️', avatar_color = '#34d399', avatar_mood = 'calm' WHERE agent_key = 'halal';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '🎨', avatar_color = '#ec4899', avatar_mood = 'creative' WHERE agent_key = 'design';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '📚', avatar_color = '#6366f1', avatar_mood = 'focused' WHERE agent_key = 'knowledge';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '🏪', avatar_color = '#b8ff3c', avatar_mood = 'busy' WHERE agent_key = 'retail';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '🔍', avatar_color = '#38bdf8', avatar_mood = 'focused' WHERE agent_key = 'browser';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '✈️', avatar_color = '#2aabee', avatar_mood = 'happy' WHERE agent_key = 'hermes';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '📧', avatar_color = '#94a3b8', avatar_mood = 'calm' WHERE agent_key = 'email';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = '🔬', avatar_color = '#14b8a6', avatar_mood = 'focused' WHERE agent_key = 'research';
|
||||||
|
|
||||||
|
INSERT INTO rss_feeds (name, url, category, is_active) VALUES
|
||||||
|
('NVWA Voedselveiligheid', 'https://news.google.com/rss/search?q=NVWA+voedselveiligheid+regelgeving&hl=nl&gl=NL&ceid=NL:nl', 'regelgeving', true),
|
||||||
|
('EU Food Regulation', 'https://news.google.com/rss/search?q=EU+food+regulation+labeling+halal&hl=en&gl=EU&ceid=EU:en', 'regelgeving', true),
|
||||||
|
('Rijksoverheid Voedsel', 'https://news.google.com/rss/search?q=site:rijksoverheid.nl+voedsel+waren+regelgeving&hl=nl&gl=NL&ceid=NL:nl', 'regelgeving', true),
|
||||||
|
('CBS Retail & Supermarkt', 'https://news.google.com/rss/search?q=CBS+supermarkt+retail+statistiek+nederland&hl=nl&gl=NL&ceid=NL:nl', 'cbs', true),
|
||||||
|
('CBS Voedselconsumptie', 'https://news.google.com/rss/search?q=CBS+voedsel+consumptie+prijzen&hl=nl&gl=NL&ceid=NL:nl', 'cbs', true),
|
||||||
|
('Food Retail M&A', 'https://news.google.com/rss/search?q=supermarket+merger+acquisition+retail+food&hl=en&gl=US&ceid=US:en', 'markt', true)
|
||||||
|
ON CONFLICT (name) DO NOTHING;
|
||||||
@@ -0,0 +1,66 @@
|
|||||||
|
-- RSS bookmarks, promo campaigns, wholesaler contacts
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS rss_bookmarks (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
rss_item_id INTEGER NOT NULL REFERENCES rss_items(id) ON DELETE CASCADE,
|
||||||
|
title VARCHAR(512),
|
||||||
|
link VARCHAR(1024),
|
||||||
|
feed_name VARCHAR(255),
|
||||||
|
notes TEXT,
|
||||||
|
tags TEXT[] DEFAULT '{}',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
UNIQUE(rss_item_id)
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_rss_bookmarks_created ON rss_bookmarks(created_at DESC);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS promo_campaigns (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
chain VARCHAR(100),
|
||||||
|
supermarket_id INTEGER REFERENCES supermarkets(id) ON DELETE SET NULL,
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
folder_path VARCHAR(512),
|
||||||
|
folder_label VARCHAR(255),
|
||||||
|
description TEXT,
|
||||||
|
image_url VARCHAR(512),
|
||||||
|
valid_from DATE,
|
||||||
|
valid_to DATE,
|
||||||
|
status VARCHAR(32) DEFAULT 'active',
|
||||||
|
promo_type VARCHAR(64) DEFAULT 'folder',
|
||||||
|
tags TEXT[] DEFAULT '{}',
|
||||||
|
source VARCHAR(100) DEFAULT 'manual',
|
||||||
|
metadata JSONB DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_promo_campaigns_chain ON promo_campaigns(chain);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_promo_campaigns_status ON promo_campaigns(status);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_promo_campaigns_dates ON promo_campaigns(valid_from, valid_to);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS wholesaler_contacts (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
wholesaler_id INTEGER NOT NULL REFERENCES wholesalers(id) ON DELETE CASCADE,
|
||||||
|
role VARCHAR(64) NOT NULL DEFAULT 'contact',
|
||||||
|
full_name VARCHAR(255),
|
||||||
|
phone VARCHAR(32),
|
||||||
|
email VARCHAR(255),
|
||||||
|
linkedin_url VARCHAR(512),
|
||||||
|
source VARCHAR(100) DEFAULT 'manual',
|
||||||
|
confidence NUMERIC(3,2) DEFAULT 0.50,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_wholesaler_contacts_wid ON wholesaler_contacts(wholesaler_id);
|
||||||
|
|
||||||
|
ALTER TABLE wholesalers ADD COLUMN IF NOT EXISTS linkedin_url VARCHAR(512);
|
||||||
|
|
||||||
|
-- Seed example promo folders (user can edit/add via UI)
|
||||||
|
INSERT INTO promo_campaigns (chain, title, folder_path, folder_label, description, valid_from, valid_to, status, promo_type, tags)
|
||||||
|
SELECT v.chain, v.title, v.folder_path, v.folder_label, v.description, v.valid_from, v.valid_to, 'active', 'folder', v.tags
|
||||||
|
FROM (VALUES
|
||||||
|
('Albert Heijn', 'Bonus folder', '/nas/marketing/reclame/ah-bonus', 'AH Bonus week', 'Wekelijkse AH Bonus acties — koppeling met kant-en-klaar promoties', CURRENT_DATE, CURRENT_DATE + 7, ARRAY['bonus','kant-en-klaar']),
|
||||||
|
('Jumbo', 'Aanbiedingen folder', '/nas/marketing/reclame/jumbo-folder', 'Jumbo folder', 'Jumbo reclamefolder — check halal/kant-en-klaar paginas', CURRENT_DATE, CURRENT_DATE + 14, ARRAY['folder','promo']),
|
||||||
|
('Lidl', 'Lidl folder', '/nas/marketing/reclame/lidl', 'Lidl acties', 'Lidl weekacties en seizoenspromoties', CURRENT_DATE, CURRENT_DATE + 7, ARRAY['folder']),
|
||||||
|
('Plus', 'Plus folder', '/nas/marketing/reclame/plus', 'Plus aanbiedingen', 'Plus supermarkt reclame — regionale varianten', CURRENT_DATE, CURRENT_DATE + 7, ARRAY['folder']),
|
||||||
|
('Dirk', 'Dirk aanbiedingen', '/nas/marketing/reclame/dirk', 'Dirk folder', 'Dirk van den Broek weekfolder', CURRENT_DATE, CURRENT_DATE + 7, ARRAY['folder'])
|
||||||
|
) AS v(chain, title, folder_path, folder_label, description, valid_from, valid_to, tags)
|
||||||
|
WHERE NOT EXISTS (SELECT 1 FROM promo_campaigns LIMIT 1);
|
||||||
@@ -0,0 +1,223 @@
|
|||||||
|
-- Platform bundle: social publish, ops, agent souls, packaging
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS marketing_media (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
filename VARCHAR(255) NOT NULL,
|
||||||
|
original_name VARCHAR(255),
|
||||||
|
file_path TEXT NOT NULL,
|
||||||
|
media_url TEXT NOT NULL,
|
||||||
|
mime_type VARCHAR(64),
|
||||||
|
size_bytes INTEGER DEFAULT 0,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS social_integrations (
|
||||||
|
platform VARCHAR(32) PRIMARY KEY,
|
||||||
|
config JSONB NOT NULL DEFAULT '{}'::jsonb,
|
||||||
|
is_active BOOLEAN DEFAULT FALSE,
|
||||||
|
last_test_at TIMESTAMPTZ,
|
||||||
|
last_test_status VARCHAR(64),
|
||||||
|
last_test_message TEXT,
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS social_publish_jobs (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
text TEXT NOT NULL,
|
||||||
|
image_url VARCHAR(1024),
|
||||||
|
media_ids JSONB DEFAULT '[]'::jsonb,
|
||||||
|
channels JSONB DEFAULT '[]'::jsonb,
|
||||||
|
status VARCHAR(32) DEFAULT 'queued',
|
||||||
|
result JSONB DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
started_at TIMESTAMPTZ,
|
||||||
|
finished_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_social_publish_jobs_created ON social_publish_jobs(created_at DESC);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS infra_snapshots (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
snapshot JSONB NOT NULL,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_promo_edition ON promo_campaigns((metadata->>'edition_id'));
|
||||||
|
|
||||||
|
INSERT INTO social_integrations (platform, is_active, config) VALUES
|
||||||
|
('twitter', false, '{}'),
|
||||||
|
('linkedin', false, '{}'),
|
||||||
|
('instagram', false, '{}'),
|
||||||
|
('facebook', false, '{}'),
|
||||||
|
('tiktok', false, '{}'),
|
||||||
|
('pinterest', false, '{}')
|
||||||
|
ON CONFLICT (platform) DO NOTHING;
|
||||||
|
|
||||||
|
-- New agents + soul updates (see 016 continuation in agent soul block below)
|
||||||
|
|
||||||
|
INSERT INTO agent_souls (agent_key, display_name, role_title, soul_md, responsibilities) VALUES
|
||||||
|
('sysops', 'SysOps', 'IT Infrastructure Monitor',
|
||||||
|
'# SysOps — Soul
|
||||||
|
|
||||||
|
Je bewaakt de Foodlinkk AI-omgeving: Proxmox, VMs, Docker, app-health.
|
||||||
|
|
||||||
|
## Karakter
|
||||||
|
- Nuchter, alert, rapporteert feiten zonder paniek
|
||||||
|
- Alleen monitoren — geen auto-fix zonder Herman-goedkeuring
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Proxmox node + VM105/106 status
|
||||||
|
- Docker container health
|
||||||
|
- Error aggregatie uit agent_events
|
||||||
|
- Infra topologie live bijhouden
|
||||||
|
|
||||||
|
## Output naar Herman
|
||||||
|
- Infra alerts, downtime, hoge CPU/RAM
|
||||||
|
|
||||||
|
## Samenwerking
|
||||||
|
- Hermes: notify bij kritieke outages',
|
||||||
|
'Proxmox · Docker · VM105/106 · errors · infra topologie'),
|
||||||
|
|
||||||
|
('packaging', 'Packaging', 'Verpakkingsontwerp',
|
||||||
|
'# Packaging — Soul
|
||||||
|
|
||||||
|
Python-gedreven verpakkingsontwerp voor Foodlinkk producten.
|
||||||
|
|
||||||
|
## Karakter
|
||||||
|
- Precies op maat (mm), drukwerk-ready
|
||||||
|
- Foodlinkk brand: hands-on, premium halal kant-en-klaar
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Stanstekeningen SVG/PDF
|
||||||
|
- Barcode + logo-vlak + vouw/snijlijnen
|
||||||
|
|
||||||
|
## Samenwerking
|
||||||
|
- Design: ComfyUI productfoto''s · Marketing: label copy',
|
||||||
|
'SVG · PDF · PNG · stanstekening · Foodlinkk brand')
|
||||||
|
ON CONFLICT (agent_key) DO UPDATE SET
|
||||||
|
display_name = EXCLUDED.display_name,
|
||||||
|
role_title = EXCLUDED.role_title,
|
||||||
|
soul_md = EXCLUDED.soul_md,
|
||||||
|
responsibilities = EXCLUDED.responsibilities,
|
||||||
|
updated_at = NOW();
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# Herman — Soul
|
||||||
|
|
||||||
|
Je bent Herman, AI Co-CEO van Foodlinkk. Warm, direct, zakelijk.
|
||||||
|
|
||||||
|
## Karakter
|
||||||
|
- Neemt alle agent-output tot zich: retail, marketing, ops, packaging
|
||||||
|
- Delegeert maar houdt totaaloverzicht
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- CEO briefing · Agent mesh · Goedkeuringen
|
||||||
|
|
||||||
|
## Samenwerking
|
||||||
|
- Alle agents rapporteren naar Herman',
|
||||||
|
responsibilities = 'Co-CEO · briefing · orchestratie · mesh hub · goedkeuringen'
|
||||||
|
WHERE agent_key = 'herman';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# Marketing — Soul
|
||||||
|
|
||||||
|
Foodlinkk brand: halal kant-en-klaar, premium, toegankelijk.
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Multi-channel social publish (6 platforms)
|
||||||
|
- Reclamefolder.nl sync
|
||||||
|
- RSS/trends → campagnes · Emoji content
|
||||||
|
|
||||||
|
## Output naar Herman
|
||||||
|
- Publish status, campagne KPIs',
|
||||||
|
responsibilities = 'Social publish · reclame folders · RSS · campagnes'
|
||||||
|
WHERE agent_key = 'marketing';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# Design — Soul
|
||||||
|
|
||||||
|
ComfyUI productfoto''s & studio visuals.
|
||||||
|
|
||||||
|
## Samenwerking
|
||||||
|
- Packaging: verpakking technisch',
|
||||||
|
responsibilities = 'ComfyUI · productfoto''s · studio'
|
||||||
|
WHERE agent_key = 'design';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# BizDev — Soul
|
||||||
|
|
||||||
|
Retail partnerships, deals, margin sheets.
|
||||||
|
|
||||||
|
## Output naar Herman
|
||||||
|
- Partnership updates',
|
||||||
|
responsibilities = 'Deals · retail partnerships · proposals'
|
||||||
|
WHERE agent_key = 'bizdev';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# Finance — Soul
|
||||||
|
|
||||||
|
Marges, pricing, cashflow.
|
||||||
|
|
||||||
|
## Output naar Herman
|
||||||
|
- Margin sheets, pipeline waardering',
|
||||||
|
responsibilities = 'Marges · pricing · cashflow'
|
||||||
|
WHERE agent_key = 'finance';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# Sourcing — Soul
|
||||||
|
|
||||||
|
Leveranciers, MOQ, halal ingredient inkoop.',
|
||||||
|
responsibilities = 'Suppliers · MOQ · inkoop'
|
||||||
|
WHERE agent_key = 'sourcing';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# Product — Soul
|
||||||
|
|
||||||
|
SKU, shelf readiness, launch timelines.',
|
||||||
|
responsibilities = 'SKU · launch · shelf readiness'
|
||||||
|
WHERE agent_key = 'product';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# Halal — Soul
|
||||||
|
|
||||||
|
Halal compliance, certificering, gap-analyse.',
|
||||||
|
responsibilities = 'Halal cert · registry · compliance'
|
||||||
|
WHERE agent_key = 'halal';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# Knowledge — Soul
|
||||||
|
|
||||||
|
NAS corpus, RAG, document analytics.',
|
||||||
|
responsibilities = 'NAS · RAG · document analytics'
|
||||||
|
WHERE agent_key = 'knowledge';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# Retail Intel — Soul
|
||||||
|
|
||||||
|
3000+ filialen, CBS, halal kansen, groothandels, reclamefolders.',
|
||||||
|
responsibilities = 'Supermarkten · CBS · halal · Retail 360'
|
||||||
|
WHERE agent_key = 'retail';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# Browser — Soul
|
||||||
|
|
||||||
|
Autonome web research, competitor monitoring.',
|
||||||
|
responsibilities = 'Browser · web research · monitor'
|
||||||
|
WHERE agent_key = 'browser';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# Hermes — Soul
|
||||||
|
|
||||||
|
Telegram, team notify, brain vectors.',
|
||||||
|
responsibilities = 'Telegram · notify · brain'
|
||||||
|
WHERE agent_key = 'hermes';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# Email — Soul
|
||||||
|
|
||||||
|
IMAP/SMTP Foodlinkk communicatie.',
|
||||||
|
responsibilities = 'Email sync · SMTP'
|
||||||
|
WHERE agent_key = 'email';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET soul_md = '# Research — Soul
|
||||||
|
|
||||||
|
Trends, weather, external data feeds.',
|
||||||
|
responsibilities = 'Research · trends · weather'
|
||||||
|
WHERE agent_key = 'research';
|
||||||
|
|
||||||
|
INSERT INTO herman_permissions (module_key, module_label, description, category, granted) VALUES
|
||||||
|
('ops', 'IT Ops', 'Infra monitoring & topologie', 'admin', false),
|
||||||
|
('packaging', 'Packaging Design', 'Verpakkingsontwerp studio', 'agents', false),
|
||||||
|
('social_publish', 'Social Publish', 'Multi-channel marketing publish', 'intel', false),
|
||||||
|
('social_integrations', 'Social API Keys', 'Social platform credentials', 'admin', false)
|
||||||
|
ON CONFLICT (module_key) DO NOTHING;
|
||||||
|
|
||||||
|
UPDATE agent_souls SET avatar_emoji = COALESCE(avatar_emoji, '🖥️'), avatar_color = COALESCE(avatar_color, '#38bdf8'), avatar_mood = 'ops' WHERE agent_key = 'sysops';
|
||||||
|
UPDATE agent_souls SET avatar_emoji = COALESCE(avatar_emoji, '📦'), avatar_color = COALESCE(avatar_color, '#f97316'), avatar_mood = 'packaging' WHERE agent_key = 'packaging';
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
-- RetailDetail Food category feed + trending focus
|
||||||
|
|
||||||
|
UPDATE rss_feeds SET
|
||||||
|
name = 'RetailDetail Food',
|
||||||
|
url = 'https://www.retaildetail.nl/category/food/feed/',
|
||||||
|
category = 'food',
|
||||||
|
is_active = TRUE
|
||||||
|
WHERE name IN ('RetailDetail', 'RetailDetail Food');
|
||||||
|
|
||||||
|
INSERT INTO rss_feeds (name, url, category, is_active) VALUES
|
||||||
|
('RetailDetail Food', 'https://www.retaildetail.nl/category/food/feed/', 'food', TRUE)
|
||||||
|
ON CONFLICT (name) DO UPDATE SET
|
||||||
|
url = EXCLUDED.url,
|
||||||
|
category = EXCLUDED.category,
|
||||||
|
is_active = TRUE;
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
-- Agent action approval workflow (query gate before execution)
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS agent_action_requests (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
agent_key VARCHAR(64) NOT NULL,
|
||||||
|
action_type VARCHAR(64) NOT NULL DEFAULT 'query',
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
query_payload JSONB DEFAULT '{}'::jsonb,
|
||||||
|
status VARCHAR(32) DEFAULT 'pending',
|
||||||
|
approved_by VARCHAR(64),
|
||||||
|
rejection_reason TEXT,
|
||||||
|
result JSONB DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
reviewed_at TIMESTAMPTZ,
|
||||||
|
executed_at TIMESTAMPTZ
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_action_requests_status ON agent_action_requests(status, created_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_agent_action_requests_agent ON agent_action_requests(agent_key, created_at DESC);
|
||||||
|
|
||||||
|
UPDATE agent_souls SET
|
||||||
|
role_title = 'Co-CEO · Takenverdeler',
|
||||||
|
soul_md = '# Herman — Soul
|
||||||
|
|
||||||
|
Je bent Herman, AI Co-CEO van Foodlinkk. Warm, direct, zakelijk.
|
||||||
|
|
||||||
|
## Karakter
|
||||||
|
- Co-CEO naast Aissa (CEO) — delegeert taken naar gespecialiseerde agents
|
||||||
|
- Alle agent-output wordt geaggregeerd; jij rapporteert naar CEO en CTO
|
||||||
|
- Geen autonome acties zonder goedgekeurde approval requests
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- CEO briefing · orchestratie · mesh hub · goedkeuringsflow bewaken
|
||||||
|
- Taken verdelen over retail, marketing, ops, research, packaging
|
||||||
|
|
||||||
|
## Samenwerking
|
||||||
|
- Agents → Herman → CEO / CTO
|
||||||
|
- Externe queries: altijd via approval queue',
|
||||||
|
responsibilities = 'Co-CEO · takenverdeler · briefing · orchestratie · mesh hub · approvals'
|
||||||
|
WHERE agent_key = 'herman';
|
||||||
|
|
||||||
|
UPDATE agent_souls SET
|
||||||
|
role_title = 'Markt Research · Intel',
|
||||||
|
responsibilities = 'Web research · concurrenten · retail trends · markt intel (via approval)'
|
||||||
|
WHERE agent_key = 'research';
|
||||||
@@ -0,0 +1,56 @@
|
|||||||
|
-- Unified projects, assets linking, UI preferences (layout + viz modes)
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS cockpit_projects (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
name VARCHAR(255) NOT NULL,
|
||||||
|
client_id INTEGER REFERENCES clients(id) ON DELETE SET NULL,
|
||||||
|
description TEXT,
|
||||||
|
status VARCHAR(32) DEFAULT 'active',
|
||||||
|
created_by VARCHAR(64) DEFAULT 'ceo',
|
||||||
|
metadata JSONB DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_cockpit_projects_client ON cockpit_projects(client_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_cockpit_projects_status ON cockpit_projects(status, updated_at DESC);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS project_assets (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
project_id INTEGER NOT NULL REFERENCES cockpit_projects(id) ON DELETE CASCADE,
|
||||||
|
asset_type VARCHAR(64) NOT NULL,
|
||||||
|
ref_id VARCHAR(128),
|
||||||
|
title VARCHAR(255),
|
||||||
|
file_path TEXT,
|
||||||
|
payload JSONB DEFAULT '{}'::jsonb,
|
||||||
|
created_by VARCHAR(64) DEFAULT 'ceo',
|
||||||
|
source_agent VARCHAR(64),
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_project_assets_project ON project_assets(project_id, created_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_project_assets_type ON project_assets(asset_type, ref_id);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS packaging_outputs (
|
||||||
|
id VARCHAR(64) PRIMARY KEY,
|
||||||
|
project_id INTEGER REFERENCES cockpit_projects(id) ON DELETE SET NULL,
|
||||||
|
spec JSONB NOT NULL,
|
||||||
|
svg TEXT NOT NULL,
|
||||||
|
created_by VARCHAR(64) DEFAULT 'packaging',
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS user_ui_preferences (
|
||||||
|
user_key VARCHAR(64) PRIMARY KEY DEFAULT 'ceo',
|
||||||
|
dashboard_layout JSONB DEFAULT '[]'::jsonb,
|
||||||
|
viz_modes JSONB DEFAULT '{}'::jsonb,
|
||||||
|
global_viz_mode VARCHAR(32) DEFAULT 'neo-bars',
|
||||||
|
updated_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO user_ui_preferences (user_key, dashboard_layout, global_viz_mode) VALUES
|
||||||
|
('ceo', '["kpis","executive","briefing","retail","analytics","approvals","feed"]'::jsonb, 'neo-bars')
|
||||||
|
ON CONFLICT (user_key) DO NOTHING;
|
||||||
|
|
||||||
|
ALTER TABLE photo_imports ADD COLUMN IF NOT EXISTS project_id INTEGER REFERENCES cockpit_projects(id) ON DELETE SET NULL;
|
||||||
|
ALTER TABLE photo_imports ADD COLUMN IF NOT EXISTS created_by VARCHAR(64) DEFAULT 'ceo';
|
||||||
@@ -0,0 +1,58 @@
|
|||||||
|
-- SysOps daily config backup, maintenance notes, permissions
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS config_backups (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
approval_request_id INTEGER REFERENCES agent_action_requests(id) ON DELETE SET NULL,
|
||||||
|
commit_ref VARCHAR(128),
|
||||||
|
files_count INTEGER DEFAULT 0,
|
||||||
|
status VARCHAR(32) DEFAULT 'pending',
|
||||||
|
message TEXT,
|
||||||
|
payload JSONB DEFAULT '{}'::jsonb,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_config_backups_created ON config_backups(created_at DESC);
|
||||||
|
|
||||||
|
CREATE TABLE IF NOT EXISTS ops_maintenance_notes (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
severity VARCHAR(16) DEFAULT 'info',
|
||||||
|
title VARCHAR(255) NOT NULL,
|
||||||
|
body TEXT,
|
||||||
|
source VARCHAR(64) DEFAULT 'sysops',
|
||||||
|
resolved BOOLEAN DEFAULT FALSE,
|
||||||
|
created_at TIMESTAMPTZ DEFAULT NOW()
|
||||||
|
);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_ops_maintenance_created ON ops_maintenance_notes(created_at DESC);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_ops_maintenance_open ON ops_maintenance_notes(resolved, created_at DESC);
|
||||||
|
|
||||||
|
UPDATE agent_souls SET
|
||||||
|
soul_md = '# SysOps — Soul
|
||||||
|
|
||||||
|
Je bewaakt en onderhoudt de Foodlinkk IT-omgeving op **Dell PowerEdge R340** met **Proxmox VE**.
|
||||||
|
|
||||||
|
## Karakter
|
||||||
|
- Nuchter, alert, rapporteert feiten zonder paniek
|
||||||
|
- Geen destructieve acties zonder CEO-goedkeuring via approval queue
|
||||||
|
|
||||||
|
## Werkzaamheden
|
||||||
|
- Proxmox node + VM106 (Command Center) monitoren
|
||||||
|
- Docker container health op 10.4.7.18
|
||||||
|
- **Dagelijks config-backup naar Gitea** (na CEO approval)
|
||||||
|
- Dagelijks maintenance-rapport in IT Ops tab
|
||||||
|
- Infra topologie live bijhouden
|
||||||
|
|
||||||
|
## Rechten
|
||||||
|
- `config_backup` — backup docker-compose, migraties, monitoring naar Gitea
|
||||||
|
- `proxmox_read` — status uitlezen via Proxmox API/SSH
|
||||||
|
- `maintenance_report` — updates/patches rapporteren
|
||||||
|
|
||||||
|
## Samenwerking
|
||||||
|
- Herman: infra alerts en goedkeuringen
|
||||||
|
- Research: geen infra-mutaties',
|
||||||
|
responsibilities = 'Proxmox R340 · VM106 · Docker · dagelijkse Gitea backup · maintenance',
|
||||||
|
permissions = '{"config_backup": true, "proxmox_read": true, "gitea_push": true, "maintenance_report": true}'::jsonb,
|
||||||
|
updated_at = NOW()
|
||||||
|
WHERE agent_key = 'sysops';
|
||||||
|
|
||||||
|
INSERT INTO herman_permissions (module_key, module_label, description, granted) VALUES
|
||||||
|
('ops', 'IT Ops', 'Infra topologie, backups, maintenance', true)
|
||||||
|
ON CONFLICT (module_key) DO UPDATE SET granted = true, module_label = EXCLUDED.module_label;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
global:
|
||||||
|
scrape_interval: 15s
|
||||||
|
|
||||||
|
scrape_configs:
|
||||||
|
- job_name: prometheus
|
||||||
|
static_configs:
|
||||||
|
- targets: ["localhost:9090"]
|
||||||
|
- job_name: tools-api
|
||||||
|
static_configs:
|
||||||
|
- targets: ["tools-api:8700"]
|
||||||
|
metrics_path: /metrics
|
||||||
|
- job_name: email-agent
|
||||||
|
static_configs:
|
||||||
|
- targets: ["email-agent:8801"]
|
||||||
|
metrics_path: /health
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
fastapi==0.115.6
|
||||||
|
uvicorn[standard]==0.32.1
|
||||||
|
psycopg2-binary==2.9.9
|
||||||
|
pydantic==2.10.3
|
||||||
|
httpx==0.27.2
|
||||||
|
websockets==14.1
|
||||||
|
svgwrite
|
||||||
|
Pillow
|
||||||
|
python-barcode
|
||||||
|
reportlab
|
||||||
Reference in New Issue
Block a user