Replace ~170 manual `if not current_user.is_admin` checks with: - @role_required(SystemRole.ADMIN) for user management, security, ZOPK - @role_required(SystemRole.OFFICE_MANAGER) for content management - current_user.can_access_admin_panel() for admin UI access - current_user.can_moderate_forum() for forum moderation - current_user.can_edit_company(id) for company permissions Add @office_manager_required decorator shortcut. Add SQL migration to sync existing users' role field. Role hierarchy: UNAFFILIATED(10) < MEMBER(20) < EMPLOYEE(30) < MANAGER(40) < OFFICE_MANAGER(50) < ADMIN(100) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
232 lines
7.2 KiB
Python
232 lines
7.2 KiB
Python
"""
|
|
Admin Insights Routes
|
|
======================
|
|
|
|
Development insights from forum and chat for roadmap planning.
|
|
"""
|
|
|
|
import logging
|
|
|
|
from flask import render_template, request, redirect, url_for, flash, jsonify
|
|
from flask_login import login_required, current_user
|
|
|
|
from database import SystemRole
|
|
from utils.decorators import role_required
|
|
from . import bp
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
# ============================================================
|
|
# INSIGHTS DASHBOARD
|
|
# ============================================================
|
|
|
|
@bp.route('/insights')
|
|
@login_required
|
|
@role_required(SystemRole.OFFICE_MANAGER)
|
|
def admin_insights():
|
|
"""Admin dashboard for development insights from forum and chat"""
|
|
return render_template('admin/insights.html')
|
|
|
|
|
|
@bp.route('/insights-api', methods=['GET'])
|
|
@login_required
|
|
@role_required(SystemRole.OFFICE_MANAGER)
|
|
def api_get_insights():
|
|
"""Get development insights for roadmap"""
|
|
try:
|
|
from norda_knowledge_service import get_knowledge_service
|
|
service = get_knowledge_service()
|
|
|
|
status = request.args.get('status')
|
|
insights = service.get_development_insights(status=status)
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'insights': insights,
|
|
'count': len(insights)
|
|
})
|
|
except ImportError:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Knowledge service not available'
|
|
}), 500
|
|
except Exception as e:
|
|
logger.error(f"Error getting insights: {e}")
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
@bp.route('/insights-api/<int:insight_id>/status', methods=['PUT'])
|
|
@login_required
|
|
@role_required(SystemRole.OFFICE_MANAGER)
|
|
def api_update_insight_status(insight_id):
|
|
"""Update insight status (for roadmap planning)"""
|
|
try:
|
|
from norda_knowledge_service import get_knowledge_service
|
|
service = get_knowledge_service()
|
|
|
|
data = request.get_json()
|
|
status = data.get('status')
|
|
note = data.get('note')
|
|
|
|
if not status:
|
|
return jsonify({'success': False, 'error': 'Status is required'}), 400
|
|
|
|
success = service.update_insight_status(insight_id, status, note)
|
|
|
|
return jsonify({'success': success})
|
|
except Exception as e:
|
|
logger.error(f"Error updating insight status: {e}")
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
@bp.route('/insights-api/sync', methods=['POST'])
|
|
@login_required
|
|
@role_required(SystemRole.OFFICE_MANAGER)
|
|
def api_sync_insights():
|
|
"""Manually trigger knowledge sync from forum and chat"""
|
|
try:
|
|
from norda_knowledge_service import get_knowledge_service
|
|
service = get_knowledge_service()
|
|
|
|
data = request.get_json() or {}
|
|
days_back = data.get('days_back', 30)
|
|
|
|
results = {
|
|
'forum': service.sync_forum_knowledge(days_back),
|
|
'chat': service.sync_chat_knowledge(days_back),
|
|
'questions': service.analyze_user_questions(days_back)
|
|
}
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'results': results
|
|
})
|
|
except ImportError:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Knowledge service not available'
|
|
}), 500
|
|
except Exception as e:
|
|
logger.error(f"Error syncing insights: {e}")
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
@bp.route('/insights-api/stats', methods=['GET'])
|
|
@login_required
|
|
@role_required(SystemRole.OFFICE_MANAGER)
|
|
def api_insights_stats():
|
|
"""Get knowledge base statistics"""
|
|
try:
|
|
from norda_knowledge_service import get_knowledge_service
|
|
service = get_knowledge_service()
|
|
|
|
stats = service.get_knowledge_stats()
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'stats': stats
|
|
})
|
|
except ImportError:
|
|
return jsonify({
|
|
'success': False,
|
|
'error': 'Knowledge service not available'
|
|
}), 500
|
|
except Exception as e:
|
|
logger.error(f"Error getting stats: {e}")
|
|
return jsonify({'success': False, 'error': str(e)}), 500
|
|
|
|
|
|
# ============================================================
|
|
# AI LEARNING STATUS API
|
|
# ============================================================
|
|
|
|
@bp.route('/ai-learning-status')
|
|
@login_required
|
|
@role_required(SystemRole.OFFICE_MANAGER)
|
|
def api_ai_learning_status():
|
|
"""API: Get AI feedback learning status and examples"""
|
|
try:
|
|
from feedback_learning_service import get_feedback_learning_service
|
|
service = get_feedback_learning_service()
|
|
context = service.get_learning_context()
|
|
|
|
# Format examples for JSON response
|
|
positive_examples = []
|
|
for ex in context.get('positive_examples', []):
|
|
positive_examples.append({
|
|
'query': ex.query,
|
|
'response': ex.response[:300] + '...' if len(ex.response) > 300 else ex.response,
|
|
'companies': ex.companies_mentioned or []
|
|
})
|
|
|
|
negative_examples = []
|
|
for ex in context.get('negative_examples', []):
|
|
negative_examples.append({
|
|
'query': ex.query,
|
|
'response': ex.response,
|
|
'comment': ex.feedback_comment
|
|
})
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'learning_active': True,
|
|
'stats': context.get('stats', {}),
|
|
'using_seed_examples': context.get('stats', {}).get('using_seed_examples', False),
|
|
'positive_examples_count': len(positive_examples),
|
|
'negative_examples_count': len(negative_examples),
|
|
'positive_examples': positive_examples,
|
|
'negative_examples': negative_examples,
|
|
'negative_patterns': context.get('negative_patterns', []),
|
|
'generated_at': context.get('generated_at')
|
|
})
|
|
except ImportError:
|
|
return jsonify({
|
|
'success': True,
|
|
'learning_active': False,
|
|
'message': 'Feedback learning service not available'
|
|
})
|
|
except Exception as e:
|
|
logger.error(f"Error getting AI learning status: {e}")
|
|
return jsonify({
|
|
'success': False,
|
|
'error': str(e)
|
|
}), 500
|
|
|
|
|
|
# ============================================================
|
|
# CHAT STATS API
|
|
# ============================================================
|
|
|
|
@bp.route('/chat-stats')
|
|
@login_required
|
|
@role_required(SystemRole.OFFICE_MANAGER)
|
|
def api_chat_stats():
|
|
"""API: Get chat statistics for dashboard"""
|
|
from datetime import datetime, timedelta
|
|
from database import SessionLocal, AIChatMessage
|
|
|
|
db = SessionLocal()
|
|
try:
|
|
from sqlalchemy import func
|
|
|
|
# Stats for last 7 days
|
|
week_ago = datetime.now() - timedelta(days=7)
|
|
|
|
daily_stats = db.query(
|
|
func.date(AIChatMessage.created_at).label('date'),
|
|
func.count(AIChatMessage.id).label('count')
|
|
).filter(
|
|
AIChatMessage.created_at >= week_ago,
|
|
AIChatMessage.role == 'user'
|
|
).group_by(
|
|
func.date(AIChatMessage.created_at)
|
|
).order_by('date').all()
|
|
|
|
return jsonify({
|
|
'success': True,
|
|
'daily_queries': [{'date': str(d.date), 'count': d.count} for d in daily_stats]
|
|
})
|
|
finally:
|
|
db.close()
|