nordabiz/blueprints/admin/routes_zopk_timeline.py
Maciej Pienczyn 99dd628d4a
Some checks are pending
NordaBiz Tests / Unit & Integration Tests (push) Waiting to run
NordaBiz Tests / E2E Tests (Playwright) (push) Blocked by required conditions
NordaBiz Tests / Smoke Tests (Production) (push) Blocked by required conditions
NordaBiz Tests / Send Failure Notification (push) Blocked by required conditions
feat(zopk): Add AI-powered roadmap analysis with status updates and gap detection
New analyze_roadmap_with_ai() function sends existing milestones and recent
knowledge facts to Gemini for comprehensive analysis. Returns new milestone
suggestions, status update recommendations, and identified roadmap gaps.
Adds PATCH endpoint for milestone status updates and tabbed UI modal.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-09 17:10:28 +01:00

256 lines
8.1 KiB
Python

"""
ZOPK Timeline Routes - Admin blueprint
Migrated from app.py as part of the blueprint refactoring.
Contains routes for ZOPK timeline and milestones management.
"""
import logging
from datetime import datetime
from flask import flash, jsonify, redirect, render_template, request, url_for
from flask_login import current_user, login_required
from database import (
SessionLocal,
SystemRole,
ZOPKMilestone
)
from utils.decorators import role_required
from . import bp
logger = logging.getLogger(__name__)
@bp.route('/zopk/timeline')
@login_required
@role_required(SystemRole.ADMIN)
def admin_zopk_timeline():
"""Panel Timeline ZOPK."""
return render_template('admin/zopk_timeline.html')
@bp.route('/zopk-api/milestones')
@login_required
@role_required(SystemRole.OFFICE_MANAGER)
def api_zopk_milestones():
"""API - lista kamieni milowych ZOPK."""
db = SessionLocal()
try:
milestones = db.query(ZOPKMilestone).order_by(ZOPKMilestone.target_date).all()
return jsonify({
'success': True,
'milestones': [{
'id': m.id,
'title': m.title,
'description': m.description,
'category': m.category,
'target_date': m.target_date.isoformat() if m.target_date else None,
'actual_date': m.actual_date.isoformat() if m.actual_date else None,
'status': m.status,
'source_url': m.source_url
} for m in milestones]
})
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/milestones', methods=['POST'])
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_milestone_create():
"""API - utworzenie kamienia milowego."""
db = SessionLocal()
try:
data = request.get_json()
milestone = ZOPKMilestone(
title=data['title'],
description=data.get('description'),
category=data.get('category', 'other'),
target_date=datetime.strptime(data['target_date'], '%Y-%m-%d').date() if data.get('target_date') else None,
actual_date=datetime.strptime(data['actual_date'], '%Y-%m-%d').date() if data.get('actual_date') else None,
status=data.get('status', 'planned'),
source_url=data.get('source_url'),
source_news_id=data.get('source_news_id')
)
db.add(milestone)
db.commit()
return jsonify({'success': True, 'id': milestone.id})
except Exception as e:
db.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/milestones/<int:milestone_id>', methods=['PUT'])
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_milestone_update(milestone_id):
"""API - aktualizacja kamienia milowego."""
db = SessionLocal()
try:
milestone = db.query(ZOPKMilestone).get(milestone_id)
if not milestone:
return jsonify({'error': 'Not found'}), 404
data = request.get_json()
if 'title' in data:
milestone.title = data['title']
if 'description' in data:
milestone.description = data['description']
if 'category' in data:
milestone.category = data['category']
if 'target_date' in data:
milestone.target_date = datetime.strptime(data['target_date'], '%Y-%m-%d').date() if data['target_date'] else None
if 'actual_date' in data:
milestone.actual_date = datetime.strptime(data['actual_date'], '%Y-%m-%d').date() if data['actual_date'] else None
if 'status' in data:
milestone.status = data['status']
if 'source_url' in data:
milestone.source_url = data['source_url']
db.commit()
return jsonify({'success': True})
except Exception as e:
db.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/milestones/<int:milestone_id>', methods=['DELETE'])
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_milestone_delete(milestone_id):
"""API - usunięcie kamienia milowego."""
db = SessionLocal()
try:
milestone = db.query(ZOPKMilestone).get(milestone_id)
if not milestone:
return jsonify({'error': 'Not found'}), 404
db.delete(milestone)
db.commit()
return jsonify({'success': True})
except Exception as e:
db.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/timeline/ai-analyze')
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_timeline_ai_analyze():
"""API - AI analysis of roadmap: new milestones, status updates, gaps."""
from zopk_knowledge_service import analyze_roadmap_with_ai
db = SessionLocal()
try:
result = analyze_roadmap_with_ai(db)
return jsonify(result)
except Exception as e:
logger.error(f"AI analyze error: {e}")
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/milestones/<int:milestone_id>/status', methods=['PATCH'])
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_milestone_update_status(milestone_id):
"""API - update milestone status."""
db = SessionLocal()
try:
milestone = db.query(ZOPKMilestone).get(milestone_id)
if not milestone:
return jsonify({'error': 'Not found'}), 404
data = request.get_json()
new_status = data.get('status')
if new_status not in ('planned', 'in_progress', 'completed', 'delayed', 'cancelled'):
return jsonify({'error': 'Invalid status'}), 400
milestone.status = new_status
db.commit()
return jsonify({'success': True, 'milestone_id': milestone_id, 'status': new_status})
except Exception as e:
db.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/timeline/suggestions')
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_timeline_suggestions():
"""API - sugestie kamieni milowych z bazy wiedzy."""
from zopk_knowledge_service import get_timeline_suggestions
limit = request.args.get('limit', 30, type=int)
only_verified = request.args.get('only_verified', 'false').lower() == 'true'
use_ai = request.args.get('use_ai', 'false').lower() == 'true'
db = SessionLocal()
try:
result = get_timeline_suggestions(db, limit=limit, only_verified=only_verified)
if result['success'] and use_ai and result.get('suggestions'):
from zopk_knowledge_service import categorize_milestones_with_ai
result['suggestions'] = categorize_milestones_with_ai(db, result['suggestions'])
return jsonify(result)
except Exception as e:
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()
@bp.route('/zopk-api/timeline/suggestions/approve', methods=['POST'])
@login_required
@role_required(SystemRole.ADMIN)
def api_zopk_timeline_suggestion_approve():
"""API - zatwierdzenie sugestii i utworzenie kamienia milowego."""
from zopk_knowledge_service import create_milestone_from_suggestion
data = request.get_json()
if not data:
return jsonify({'error': 'No data provided'}), 400
fact_id = data.get('fact_id')
if not fact_id:
return jsonify({'error': 'fact_id is required'}), 400
db = SessionLocal()
try:
result = create_milestone_from_suggestion(
db_session=db,
fact_id=fact_id,
title=data.get('title', 'Kamień milowy'),
description=data.get('description'),
category=data.get('category', 'other'),
target_date=data.get('target_date'),
status=data.get('status', 'planned'),
source_url=data.get('source_url')
)
return jsonify(result)
except Exception as e:
db.rollback()
return jsonify({'success': False, 'error': str(e)}), 500
finally:
db.close()