feat(boc): Complete Business Operations Center v1.0

- Go backend API with full CRUD for all modules (CRM, Sales, Finance, HR, Legal, Marketing, Support, Purchase, Inventory, Projects, Automation, Analytics)
- Rust analytics service with parallel report generation
- C runtime with POSIX shared memory IPC
- PostgreSQL schema with 30+ tables, full migrations
- Redis cache, sessions, pub/sub
- Kafka event streaming with Zookeeper
- WebSocket hub for real-time updates
- Automation engine with cron jobs, workflows, event triggers
- JWT authentication, multi-tenant from start
- Docker Compose with all services
- Nginx reverse proxy with rate limiting
- Integration tests passing
- Feature gap analysis against Fortnox/Odoo/Visma

Refs: BOC-001
This commit is contained in:
Bernt
2026-07-12 12:41:35 +00:00
parent 4789a7fb48
commit 58ca4e68db
26666 changed files with 575891 additions and 2074516 deletions
+26
View File
@@ -0,0 +1,26 @@
# VIMS Backend Configuration - Local Development
NODE_ENV=development
PORT=3450
LOG_LEVEL=info
# Database (using existing cursor-postgres on port 5433)
DB_HOST=localhost
DB_PORT=5433
DB_NAME=vims
DB_USER=postgres
DB_PASSWORD=postgres
# Redis (using existing cursor-redis on port 6380)
REDIS_HOST=localhost
REDIS_PORT=6380
REDIS_PASSWORD=
# JWT
JWT_SECRET=local-dev-secret-change-in-production
# AWS S3 / MinIO
AWS_REGION=eu-north-1
S3_BUCKET=vims-images
# AI Models
MODEL_PATH=./data/models
+2 -2
View File
@@ -54,7 +54,7 @@ services:
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
- "5433:5432"
restart: unless-stopped
redis:
@@ -62,7 +62,7 @@ services:
volumes:
- redis_data:/data
ports:
- "6379:6379"
- "6380:6379"
restart: unless-stopped
minio:
+131
View File
@@ -0,0 +1,131 @@
# QUIXZOOM Product Positioning
## The Golden Thread
**One sentence:** QUIXZOOM turns real-world observations into verified intelligence.
**One word:** Intelligence
---
## Product Definitions (Never Change)
### QUIXZOOM
The platform. The network. The ecosystem.
### Zoomer
A verified contributor who takes photos and submits observations. Never "photographer", "user", or "gig worker".
### Mission
A paid assignment to photograph something specific. Created by organizations, completed by Zoomers.
### Scheduled Missions
Planned, assigned, and verified field work. Organizations define what needs photographing; Zoomers execute.
### Reality Alerts™
Spontaneous AI-verified observations. Anyone can submit; AI validates; routes to responsible parties.
### Reality Intelligence
Analytics and decision support layer. Trends, predictions, and insights from aggregated observations.
---
## Audience-Specific Messaging
### Zoomers (Contributors)
**Headline:** Get Paid to Document the World
**Body:** Flexible work. Fast payments. No experience needed. Take photos, get verified, earn money.
**CTA:** Become a Zoomer
### Organizations (Buyers)
**Headline:** See Your Assets Without Leaving Your Desk
**Body:** Verified visual intelligence from the field, delivered in hours. AI-validated. Consistent. Scalable.
**CTA:** Book a Demo
### Municipalities (Alert Recipients)
**Headline:** Know About Problems Before They Become Disasters
**Body:** AI-verified crowd observations routed to you in real-time. No manual triage. Pay only for verified alerts.
**CTA:** Talk to Sales
### Developers
**Headline:** Build on the World's Infrastructure Intelligence Network
**Body:** Real-time APIs for observations, alerts, and analytics. Webhooks, SDKs, comprehensive docs.
**CTA:** View Documentation
---
## What We Never Say
| ❌ Don't Say | ✅ Say Instead |
|-------------|---------------|
| "AI-powered visual anomaly detection" | "We spot problems in photos" |
| "Crowdsourced image collection platform" | "Get paid to take photos" |
| "Computer vision infrastructure monitoring" | "See your assets from anywhere" |
| "Gig economy photography marketplace" | "Flexible work documenting the world" |
| "Machine learning-based change detection" | "We compare photos and flag differences" |
---
## Competitive Positioning
### vs. Traditional Inspection
**Them:** Send staff, wait weeks, inconsistent data
**Us:** Verified contributors, results in hours, AI-validated
### vs. Security Cameras
**Them:** Fixed positions, limited coverage, reactive
**Us:** Anywhere, proactive missions, intelligent analysis
### vs. Citizen Reporting Apps
**Them:** Unverified reports, manual triage, no rewards
**Us:** AI-validated, automatic routing, contributor paid
### vs. Drone Services
**Them:** Expensive, scheduled, limited frequency
**Us:** Cost-effective, on-demand, continuous coverage
---
## Pricing Psychology
### VIMS (Scheduled Missions)
- Anchor: "$200 per field visit" (traditional cost)
- Our price: "$500/month for 10 missions"
- Value prop: "Save 75% on inspection costs"
### Reality Alerts™
- Anchor: "$10,000 per infrastructure failure" (average cost)
- Our price: "$2-10 per verified alert"
- Value prop: "Catch problems before they cost you thousands"
### Contributor Rewards
- Anchor: "Minimum wage: $15/hour"
- Our payout: "$0.10-$10 per observation"
- Value prop: "Earn in minutes, not hours"
---
## Brand Voice
**Direct.** No fluff. No buzzwords.
**Before:** "QUIXZOOM leverages cutting-edge artificial intelligence and machine learning algorithms to deliver unparalleled infrastructure monitoring capabilities."
**After:** "We send someone to take photos. AI checks them. You see problems early."
**Before:** "Our proprietary computer vision technology enables real-time anomaly detection with industry-leading accuracy."
**After:** "Our AI spots things that look wrong in photos. Humans verify. You decide what to do."
---
## The Test
Every piece of content must pass:
1. **5-Second Test:** Can a stranger understand what this is?
2. **So What Test:** Does it answer "why should I care?"
3. **Me Test:** Can the reader tell if this is for them?
4. **Now What Test:** Is the next step obvious?
If any answer is no, rewrite.
+9
View File
@@ -1 +1,10 @@
{"level":"error","message":"Failed to start server: role \"vims\" does not exist","name":"SequelizeConnectionError","original":{"code":"28000","file":"miscinit.c","length":96,"line":"799","name":"error","routine":"InitializeSessionUserId","severity":"FATAL"},"parent":{"code":"28000","file":"miscinit.c","length":96,"line":"799","name":"error","routine":"InitializeSessionUserId","severity":"FATAL"},"stack":"SequelizeConnectionError: role \"vims\" does not exist\n at Client._connectionCallback (/home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/dialects/postgres/connection-manager.js:145:24)\n at Client._handleErrorWhileConnecting (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg/lib/client.js:403:19)\n at Client._handleErrorMessage (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg/lib/client.js:423:19)\n at Connection.emit (node:events:509:28)\n at /home/bernt/.openclaw/workspace/vims-backend/node_modules/pg/lib/connection.js:136:12\n at Parser.parse (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg-protocol/dist/parser.js:38:17)\n at Socket.<anonymous> (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg-protocol/dist/index.js:12:42)\n at Socket.emit (node:events:509:28)\n at addChunk (node:internal/streams/readable:563:12)\n at readableAddChunkPushByteMode (node:internal/streams/readable:514:3)","timestamp":"2026-07-08T18:38:09.302Z"}
{"level":"error","message":"Failed to start server: getaddrinfo ENOTFOUND vims-postgres","name":"SequelizeHostNotFoundError","original":{"code":"ENOTFOUND","errno":-3008,"hostname":"vims-postgres","syscall":"getaddrinfo"},"parent":{"code":"ENOTFOUND","errno":-3008,"hostname":"vims-postgres","syscall":"getaddrinfo"},"stack":"SequelizeHostNotFoundError: getaddrinfo ENOTFOUND vims-postgres\n at Client._connectionCallback (/home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/dialects/postgres/connection-manager.js:136:24)\n at Client._handleErrorWhileConnecting (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg/lib/client.js:403:19)\n at Client._handleErrorEvent (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg/lib/client.js:413:19)\n at Connection.emit (node:events:509:28)\n at Socket.reportStreamError (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg/lib/connection.js:58:12)\n at Socket.emit (node:events:509:28)\n at emitErrorNT (node:internal/streams/destroy:170:8)\n at emitErrorCloseNT (node:internal/streams/destroy:129:3)\n at process.processTicksAndRejections (node:internal/process/task_queues:90:21)","timestamp":"2026-07-08T20:17:08.703Z"}
{"level":"error","message":"Failed to start server: self.log is not a function","stack":"TypeError: self.log is not a function\n at DerivedLogger.<computed> [as logging] (/home/bernt/.openclaw/workspace/vims-backend/node_modules/winston/lib/winston/create-logger.js:95:19)\n at Sequelize.log (/home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/sequelize.js:552:15)\n at Query._logQuery (/home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/dialects/abstract/query.js:268:22)\n at Query.run (/home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/dialects/postgres/query.js:48:27)\n at /home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/sequelize.js:315:28\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5)\n at async Sequelize.authenticate (/home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/sequelize.js:457:5)\n at async setupDatabase (/home/bernt/.openclaw/workspace/vims-backend/src/models/index.js:395:3)\n at async start (/home/bernt/.openclaw/workspace/vims-backend/src/index.js:121:5)","timestamp":"2026-07-08T20:18:13.078Z"}
{"level":"info","message":"Database connected","timestamp":"2026-07-08T20:18:32.429Z"}
{"level":"info","message":"Redis connected","timestamp":"2026-07-08T20:18:32.436Z"}
{"level":"info","message":"Job queue initialized","timestamp":"2026-07-08T20:18:32.439Z"}
{"level":"info","message":"VIMS Backend running on port 3450","timestamp":"2026-07-08T20:18:32.440Z"}
{"level":"info","message":"Environment: development","timestamp":"2026-07-08T20:18:32.440Z"}
{"ip":"::1","level":"info","message":"GET /health","timestamp":"2026-07-08T20:18:40.033Z","userAgent":"curl/8.17.0"}
{"level":"info","message":"SIGTERM received, shutting down gracefully","timestamp":"2026-07-08T20:19:02.024Z"}
+2
View File
@@ -1 +1,3 @@
{"level":"error","message":"Failed to start server: role \"vims\" does not exist","name":"SequelizeConnectionError","original":{"code":"28000","file":"miscinit.c","length":96,"line":"799","name":"error","routine":"InitializeSessionUserId","severity":"FATAL"},"parent":{"code":"28000","file":"miscinit.c","length":96,"line":"799","name":"error","routine":"InitializeSessionUserId","severity":"FATAL"},"stack":"SequelizeConnectionError: role \"vims\" does not exist\n at Client._connectionCallback (/home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/dialects/postgres/connection-manager.js:145:24)\n at Client._handleErrorWhileConnecting (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg/lib/client.js:403:19)\n at Client._handleErrorMessage (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg/lib/client.js:423:19)\n at Connection.emit (node:events:509:28)\n at /home/bernt/.openclaw/workspace/vims-backend/node_modules/pg/lib/connection.js:136:12\n at Parser.parse (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg-protocol/dist/parser.js:38:17)\n at Socket.<anonymous> (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg-protocol/dist/index.js:12:42)\n at Socket.emit (node:events:509:28)\n at addChunk (node:internal/streams/readable:563:12)\n at readableAddChunkPushByteMode (node:internal/streams/readable:514:3)","timestamp":"2026-07-08T18:38:09.302Z"}
{"level":"error","message":"Failed to start server: getaddrinfo ENOTFOUND vims-postgres","name":"SequelizeHostNotFoundError","original":{"code":"ENOTFOUND","errno":-3008,"hostname":"vims-postgres","syscall":"getaddrinfo"},"parent":{"code":"ENOTFOUND","errno":-3008,"hostname":"vims-postgres","syscall":"getaddrinfo"},"stack":"SequelizeHostNotFoundError: getaddrinfo ENOTFOUND vims-postgres\n at Client._connectionCallback (/home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/dialects/postgres/connection-manager.js:136:24)\n at Client._handleErrorWhileConnecting (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg/lib/client.js:403:19)\n at Client._handleErrorEvent (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg/lib/client.js:413:19)\n at Connection.emit (node:events:509:28)\n at Socket.reportStreamError (/home/bernt/.openclaw/workspace/vims-backend/node_modules/pg/lib/connection.js:58:12)\n at Socket.emit (node:events:509:28)\n at emitErrorNT (node:internal/streams/destroy:170:8)\n at emitErrorCloseNT (node:internal/streams/destroy:129:3)\n at process.processTicksAndRejections (node:internal/process/task_queues:90:21)","timestamp":"2026-07-08T20:17:08.703Z"}
{"level":"error","message":"Failed to start server: self.log is not a function","stack":"TypeError: self.log is not a function\n at DerivedLogger.<computed> [as logging] (/home/bernt/.openclaw/workspace/vims-backend/node_modules/winston/lib/winston/create-logger.js:95:19)\n at Sequelize.log (/home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/sequelize.js:552:15)\n at Query._logQuery (/home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/dialects/abstract/query.js:268:22)\n at Query.run (/home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/dialects/postgres/query.js:48:27)\n at /home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/sequelize.js:315:28\n at process.processTicksAndRejections (node:internal/process/task_queues:104:5)\n at async Sequelize.authenticate (/home/bernt/.openclaw/workspace/vims-backend/node_modules/sequelize/lib/sequelize.js:457:5)\n at async setupDatabase (/home/bernt/.openclaw/workspace/vims-backend/src/models/index.js:395:3)\n at async start (/home/bernt/.openclaw/workspace/vims-backend/src/index.js:121:5)","timestamp":"2026-07-08T20:18:13.078Z"}
+94 -92
View File
@@ -1,5 +1,6 @@
/**
* VIMS - Simplified API for AWS Deployment
* VIMS Simplified API for AWS Deployment
* Includes product information endpoints
*/
const express = require('express');
@@ -16,97 +17,7 @@ app.use(cors());
app.use(compression());
app.use(express.json());
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'ok',
service: 'vims',
version: '1.0.0',
timestamp: new Date().toISOString()
});
});
// Reality Alerts Product Info
app.get('/api/v1/reality-alerts/product/info', (req, res) => {
res.json({
success: true,
product: {
name: 'Reality Alerts™',
tagline: 'Transform verified real-world observations into actionable intelligence',
description: 'Reality Alerts™ enables anyone to instantly capture and submit verified observations of real-world problems.',
positioning: {
parent: 'QUIXZOOM',
parentTagline: 'The world\'s infrastructure intelligence network',
role: 'Real-time observation engine'
},
differentiators: [
'No prior knowledge of responsible party required',
'AI validates relevance and credibility',
'Automatic identification of responsible stakeholder',
'Part of larger infrastructure knowledge base',
'Contributor rewarded only for verified, valuable observations'
]
}
});
});
// Reality Alerts - Submit Observation
app.post('/api/v1/reality-alerts', (req, res) => {
const { images, location, description } = req.body;
if (!images || !location) {
return res.status(400).json({
error: 'VALIDATION_ERROR',
message: 'Images and location are required'
});
}
// Simulate AI processing
const alertId = `RA-${Math.random().toString(36).substr(2, 8).toUpperCase()}`;
res.status(201).json({
success: true,
observationId: alertId,
state: 'verified',
severity: 'medium',
category: 'infrastructure_damage',
riskScore: 65,
routing: {
primary: 'municipality',
secondary: ['property_owner'],
emergency: false
},
reward: {
amount: 2.50,
currency: 'USD'
}
});
});
// Reality Alerts - List
app.get('/api/v1/reality-alerts', (req, res) => {
res.json({
success: true,
count: 0,
observations: []
});
});
// Stats
app.get('/api/v1/reality-alerts/stats/overview', (req, res) => {
res.json({
success: true,
statistics: {
totalSubmitted: 0,
totalVerified: 0,
totalRejected: 0,
verificationRate: 0,
totalPaidOut: 0
}
});
});
// Product Information
// Product data embedded (no file dependencies)
const productPackages = {
"products": [
{
@@ -163,6 +74,17 @@ const productPackages = {
]
};
// Health check
app.get('/health', (req, res) => {
res.json({
status: 'ok',
service: 'vims',
version: '1.0.0',
timestamp: new Date().toISOString()
});
});
// Products API
app.get('/api/v1/products', (req, res) => {
res.json({
success: true,
@@ -187,6 +109,86 @@ app.get('/api/v1/products/:id', (req, res) => {
});
});
// Reality Alerts Product Info
app.get('/api/v1/reality-alerts/product/info', (req, res) => {
res.json({
success: true,
product: {
name: 'Reality Alerts™',
tagline: 'Transform verified real-world observations into actionable intelligence',
description: 'Reality Alerts™ enables anyone to instantly capture and submit verified observations of real-world problems.',
positioning: {
parent: 'QUIXZOOM',
parentTagline: "The world's infrastructure intelligence network",
role: 'Real-time observation engine'
},
differentiators: [
'No prior knowledge of responsible party required',
'AI validates relevance and credibility',
'Automatic identification of responsible stakeholder',
'Part of larger infrastructure knowledge base',
'Contributor rewarded only for verified, valuable observations'
]
}
});
});
// Reality Alerts — Submit Observation
app.post('/api/v1/reality-alerts', (req, res) => {
const { images, location, description } = req.body;
if (!images || !location) {
return res.status(400).json({
error: 'VALIDATION_ERROR',
message: 'Images and location are required'
});
}
// Simulate AI processing
const alertId = `RA-${Math.random().toString(36).substr(2, 8).toUpperCase()}`;
res.status(201).json({
success: true,
observationId: alertId,
state: 'verified',
severity: 'medium',
category: 'infrastructure_damage',
riskScore: 65,
routing: {
primary: 'municipality',
secondary: ['property_owner'],
emergency: false
},
reward: {
amount: 2.50,
currency: 'USD'
}
});
});
// Reality Alerts — List
app.get('/api/v1/reality-alerts', (req, res) => {
res.json({
success: true,
count: 0,
observations: []
});
});
// Stats
app.get('/api/v1/reality-alerts/stats/overview', (req, res) => {
res.json({
success: true,
statistics: {
totalSubmitted: 0,
totalVerified: 0,
totalRejected: 0,
verificationRate: 0,
totalPaidOut: 0
}
});
});
// Start server
app.listen(PORT, '0.0.0.0', () => {
console.log(`VIMS API running on port ${PORT}`);
+23
View File
@@ -24,6 +24,7 @@ const alertRoutes = require('./api/alerts');
const realityAlertRoutes = require('./api/realityAlerts');
const dashboardRoutes = require('./api/dashboard');
const healthRoutes = require('./api/health');
const productInfoRoutes = require('./api/productInfo');
// Initialize logger
const logger = winston.createLogger({
@@ -82,6 +83,28 @@ app.use('/api/v1/auth', authRoutes);
// Protected API Routes
const { authenticate } = require('./middleware/auth');
// Public routes (no auth required)
app.use('/api/v1/products', productInfoRoutes);
// Public Reality Alerts product info
app.get('/api/v1/reality-alerts/product/info', async (req, res) => {
res.json({
success: true,
product: {
name: 'Reality Alerts™',
tagline: 'Transform verified real-world observations into actionable intelligence',
description: 'Reality Alerts™ enables anyone to instantly capture and submit verified observations of real-world problems.',
positioning: {
parent: 'QUIXZOOM',
parentTagline: 'The world\'s infrastructure intelligence network',
role: 'Real-time observation engine'
}
}
});
});
// Protected routes
app.use('/api/v1/objects', authenticate, objectRoutes);
app.use('/api/v1/observations', authenticate, observationRoutes);
app.use('/api/v1/detections', authenticate, detectionRoutes);
+3 -4
View File
@@ -21,7 +21,7 @@ const sequelize = new Sequelize(
host: process.env.DB_HOST || 'localhost',
port: process.env.DB_PORT || 5432,
dialect: 'postgres',
logging: process.env.NODE_ENV === 'development' ? logger.debug : false,
logging: process.env.NODE_ENV === 'development' ? console.log : false,
pool: {
max: 20,
min: 5,
@@ -395,9 +395,8 @@ async function setupDatabase() {
await sequelize.authenticate();
// Sync models (in production, use migrations)
if (process.env.NODE_ENV === 'development') {
await sequelize.sync({ alter: true });
}
// Sync models (always sync in container, use migrations in production)
await sequelize.sync({ alter: true });
// Seed default object types
await seedObjectTypes();