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