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
@@ -0,0 +1,307 @@
"""
VIMS Instance Creator
Creates a new VIMS instance for any article/topic.
Usage:
python scripts/create_instance.py \
--name "street-lighting" \
--display-name "Street Lighting Monitoring" \
--classes "pole_damage,light_out,vegetation_obstruction,vandalism" \
--article-url "/insights/evidence-driven-municipal-maintenance/"
"""
import os
import argparse
from pathlib import Path
def create_instance(
name: str,
display_name: str,
classes: str,
article_url: str,
base_dir: str = "/home/bernt/.openclaw/workspace/vims-core/instances"
):
"""
Create new VIMS instance.
Args:
name: Instance name (directory name)
display_name: Human-readable name
classes: Comma-separated anomaly classes
article_url: Related Landvex article URL
base_dir: Base directory for instances
"""
instance_dir = Path(base_dir) / name
instance_dir.mkdir(parents=True, exist_ok=True)
# Create subdirectories
(instance_dir / "data" / "raw").mkdir(parents=True, exist_ok=True)
(instance_dir / "data" / "processed").mkdir(parents=True, exist_ok=True)
(instance_dir / "data" / "annotations").mkdir(parents=True, exist_ok=True)
(instance_dir / "models").mkdir(exist_ok=True)
(instance_dir / "src").mkdir(exist_ok=True)
class_list = [c.strip() for c in classes.split(",")]
# Create detector module
detector_code = f'''"""
{name} Anomaly Detector
Generated by VIMS Instance Creator
Related article: {article_url}
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent.parent / "core"))
from base_detector import VIMSBaseDetector, VIMSInstanceRegistry
class {name.title().replace("-", "")}Detector(VIMSBaseDetector):
"""
Anomaly detector for {display_name}.
Article: {article_url}
"""
TOPIC = "{name}"
CLASS_NAMES = {{
{', '.join([f'{i}: "{c}"' for i, c in enumerate(class_list)])}
}}
SEVERITY_MAP = {{
{', '.join([f'"{c}": 3' for c in class_list])}
}}
def preprocess(self, image):
"""{name}-specific preprocessing."""
# TODO: Implement specific preprocessing
return image
def postprocess(self, raw_output):
"""{name}-specific postprocessing."""
# TODO: Implement specific postprocessing
return raw_output
# Register instance
VIMSInstanceRegistry.register("{name}", {name.title().replace("-", "")}Detector)
'''
(instance_dir / "src" / "detector.py").write_text(detector_code)
# Create database setup
db_code = f'''"""
Database setup for {display_name}
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent.parent / "core"))
from database import VIMSDatabase
def setup():
"""Initialize database for {name}."""
db = VIMSDatabase("{name}")
db.create_schema(anomaly_classes={class_list})
print(f"Database initialized for {display_name}")
if __name__ == "__main__":
setup()
'''
(instance_dir / "src" / "database.py").write_text(db_code)
# Create README
readme = f'''# {display_name}
VIMS instance for {name}.
## Related Article
[{article_url}](https://landvex.com{article_url})
## Anomaly Classes
{chr(10).join([f"- {c}" for c in class_list])}
## Quick Start
1. Add training images to `data/raw/`
2. Annotate using LabelImg (YOLO format)
3. Run preprocessing: `python src/detector.py`
4. Train model: `python src/detector.py --train`
5. Run inference: `python src/detector.py --predict data/test/image.jpg`
## API
Once deployed, access via:
- REST: `POST /api/{name}/predict`
- WebSocket: `ws://host/ws/{name}/alerts`
'''
(instance_dir / "README.md").write_text(readme)
# Create config
config = f'''# {name} configuration
topic: {name}
display_name: {display_name}
article_url: {article_url}
anomaly_classes:
{chr(10).join([f" - {c}" for c in class_list])}
model:
base: yolov8n.pt
input_size: 640
training:
epochs: 100
batch_size: 16
'''
(instance_dir / "config.yaml").write_text(config)
print(f"✅ Created VIMS instance: {name}")
print(f" Location: {instance_dir}")
print(f" Classes: {', '.join(class_list)}")
print(f" Article: {article_url}")
print()
print("Next steps:")
print(f" 1. cd {instance_dir}")
print(" 2. Add training images to data/raw/")
print(" 3. python src/database.py")
print(" 4. python src/detector.py --train")
def main():
parser = argparse.ArgumentParser(description="Create VIMS Instance")
parser.add_argument("--name", required=True, help="Instance name (directory)")
parser.add_argument("--display-name", required=True, help="Human-readable name")
parser.add_argument("--classes", required=True, help="Comma-separated anomaly classes")
parser.add_argument("--article-url", required=True, help="Related article URL")
args = parser.parse_args()
create_instance(
name=args.name,
display_name=args.display_name,
classes=args.classes,
article_url=args.article_url
)
if __name__ == "__main__":
main()
'''
(instance_dir / "src" / "detector.py").write_text(detector_code)
# Create database setup
db_code = f'''"""
Database setup for {display_name}
"""
import sys
from pathlib import Path
sys.path.append(str(Path(__file__).parent.parent.parent / "core"))
from database import VIMSDatabase
def setup():
"""Initialize database for {name}."""
db = VIMSDatabase("{name}")
db.create_schema(anomaly_classes={class_list})
print(f"Database initialized for {display_name}")
if __name__ == "__main__":
setup()
'''
(instance_dir / "src" / "database.py").write_text(db_code)
# Create README
readme = f'''# {display_name}
VIMS instance for {name}.
## Related Article
[{article_url}](https://landvex.com{article_url})
## Anomaly Classes
{chr(10).join([f"- {c}" for c in class_list])}
## Quick Start
1. Add training images to `data/raw/`
2. Annotate using LabelImg (YOLO format)
3. Run preprocessing: `python src/detector.py`
4. Train model: `python src/detector.py --train`
5. Run inference: `python src/detector.py --predict data/test/image.jpg`
## API
Once deployed, access via:
- REST: `POST /api/{name}/predict`
- WebSocket: `ws://host/ws/{name}/alerts`
'''
(instance_dir / "README.md").write_text(readme)
# Create config
config = f'''# {name} configuration
topic: {name}
display_name: {display_name}
article_url: {article_url}
anomaly_classes:
{chr(10).join([f" - {c}" for c in class_list])}
model:
base: yolov8n.pt
input_size: 640
training:
epochs: 100
batch_size: 16
'''
(instance_dir / "config.yaml").write_text(config)
print(f"✅ Created VIMS instance: {name}")
print(f" Location: {instance_dir}")
print(f" Classes: {', '.join(class_list)}")
print(f" Article: {article_url}")
print()
print("Next steps:")
print(f" 1. cd {instance_dir}")
print(" 2. Add training images to data/raw/")
print(" 3. python src/database.py")
print(" 4. python src/detector.py --train")
def main():
parser = argparse.ArgumentParser(description="Create VIMS Instance")
parser.add_argument("--name", required=True, help="Instance name (directory)")
parser.add_argument("--display-name", required=True, help="Human-readable name")
parser.add_argument("--classes", required=True, help="Comma-separated anomaly classes")
parser.add_argument("--article-url", required=True, help="Related article URL")
args = parser.parse_args()
create_instance(
name=args.name,
display_name=args.display_name,
classes=args.classes,
article_url=args.article_url
)
if __name__ == "__main__":
main()
+137
View File
@@ -0,0 +1,137 @@
"""
Database Setup Script
Creates database schema for ATM anomaly detection.
Supports PostgreSQL and SQLite.
"""
import os
import sys
import argparse
from pathlib import Path
def setup_sqlite(db_path: str = 'data/atm_anomaly.db'):
"""Setup SQLite database."""
import sqlite3
os.makedirs(os.path.dirname(db_path), exist_ok=True)
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Read schema
schema_path = Path(__file__).parent.parent / 'config' / 'database.sql'
with open(schema_path, 'r') as f:
schema = f.read()
# Execute schema (SQLite compatible)
# Replace PostgreSQL-specific syntax
schema = schema.replace('SERIAL PRIMARY KEY', 'INTEGER PRIMARY KEY AUTOINCREMENT')
schema = schema.replace('JSONB', 'JSON')
schema = schema.replace('DECIMAL(10, 8)', 'REAL')
schema = schema.replace('DECIMAL(11, 8)', 'REAL')
schema = schema.replace('DECIMAL(10, 2)', 'REAL')
schema = schema.replace('DECIMAL(10, 6)', 'REAL')
schema = schema.replace('DECIMAL(5, 4)', 'REAL')
schema = schema.replace('TIMESTAMP', 'DATETIME')
schema = schema.replace('CHECK (severity_level BETWEEN 1 AND 5)', '')
schema = schema.replace('CHECK (priority BETWEEN 1 AND 5)', '')
# Split and execute statements
statements = schema.split(';')
for stmt in statements:
stmt = stmt.strip()
if stmt:
try:
cursor.execute(stmt)
except sqlite3.Error as e:
print(f"Warning: {e}")
print(f"Statement: {stmt[:100]}...")
conn.commit()
conn.close()
print(f"SQLite database created: {db_path}")
def setup_postgres(connection_string: str):
"""Setup PostgreSQL database."""
import psycopg2
conn = psycopg2.connect(connection_string)
cursor = conn.cursor()
schema_path = Path(__file__).parent.parent / 'config' / 'database.sql'
with open(schema_path, 'r') as f:
schema = f.read()
cursor.execute(schema)
conn.commit()
conn.close()
print("PostgreSQL database initialized")
def seed_demo_data(db_path: str = 'data/atm_anomaly.db'):
"""Insert demo data for testing."""
import sqlite3
conn = sqlite3.connect(db_path)
cursor = conn.cursor()
# Insert demo ATMs
atms = [
('ATM-001', 'Swedbank', 'Stockholm Central', 'Sergels Torg 1, Stockholm', 59.3326, 18.0649, 'Stockholm', 'Sweden'),
('ATM-002', 'SEB', 'Göteborg Central', 'Drottningtorget 2, Göteborg', 57.7089, 11.9746, 'Göteborg', 'Sweden'),
('ATM-003', 'Nordea', 'Malmö Central', 'Centralplan 1, Malmö', 55.6090, 13.0007, 'Malmö', 'Sweden'),
]
cursor.executemany('''
INSERT OR IGNORE INTO atm_locations
(atm_id, bank_name, branch_name, address, latitude, longitude, city, country)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', atms)
# Insert demo captures
captures = [
('ATM-001', '2026-07-11 06:00:00', 'front', 'data/raw/atm_001_20260711060000_front.jpg', 'day'),
('ATM-001', '2026-07-11 06:05:00', 'side', 'data/raw/atm_001_20260711060500_side.jpg', 'day'),
('ATM-002', '2026-07-11 06:00:00', 'front', 'data/raw/atm_002_20260711060000_front.jpg', 'day'),
]
cursor.executemany('''
INSERT INTO atm_captures
(atm_id, capture_timestamp, camera_angle, image_path, lighting_condition)
VALUES (?, ?, ?, ?, ?)
''', captures)
conn.commit()
conn.close()
print("Demo data inserted")
def main():
parser = argparse.ArgumentParser(description='Setup ATM Anomaly Database')
parser.add_argument('--db-type', choices=['sqlite', 'postgres'], default='sqlite')
parser.add_argument('--connection', help='PostgreSQL connection string')
parser.add_argument('--db-path', default='data/atm_anomaly.db', help='SQLite database path')
parser.add_argument('--seed', action='store_true', help='Insert demo data')
args = parser.parse_args()
if args.db_type == 'sqlite':
setup_sqlite(args.db_path)
if args.seed:
seed_demo_data(args.db_path)
elif args.db_type == 'postgres':
if not args.connection:
print("Error: --connection required for PostgreSQL")
sys.exit(1)
setup_postgres(args.connection)
print("Database setup complete!")
if __name__ == "__main__":
main()