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
Role field stores uppercase 'ADMIN' (from SystemRole enum). Use is_admin boolean property which is synced by set_role() for reliable checks. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
222 lines
7.7 KiB
Python
222 lines
7.7 KiB
Python
"""
|
|
ZOPK Public Routes - Public blueprint
|
|
|
|
Migrated from app.py as part of the blueprint refactoring.
|
|
Contains public-facing routes for ZOPK (Zielony Okręg Przemysłowy Kaszubia).
|
|
"""
|
|
|
|
from datetime import datetime, timedelta
|
|
|
|
from flask import abort, render_template, request
|
|
from flask_login import current_user
|
|
from sqlalchemy import func
|
|
|
|
from database import (
|
|
SessionLocal,
|
|
ZOPKProject,
|
|
ZOPKStakeholder,
|
|
ZOPKNews,
|
|
ZOPKResource,
|
|
ZOPKMilestone,
|
|
ZOPKCompanyLink,
|
|
ZOPKKnowledgeFact,
|
|
ZOPKKnowledgeEntity,
|
|
ZOPKKnowledgeChunk
|
|
)
|
|
from . import bp
|
|
|
|
# Import limiter from app - will be initialized when app starts
|
|
from flask import current_app
|
|
|
|
|
|
def get_limiter():
|
|
"""Get rate limiter from current app."""
|
|
return current_app.extensions.get('limiter')
|
|
|
|
|
|
@bp.route('/zopk')
|
|
def zopk_index():
|
|
"""
|
|
Public knowledge base page for ZOPK.
|
|
Shows projects, stakeholders, approved news, resources, and timeline.
|
|
"""
|
|
db = SessionLocal()
|
|
try:
|
|
# Get active projects
|
|
projects = db.query(ZOPKProject).filter(
|
|
ZOPKProject.is_active == True
|
|
).order_by(ZOPKProject.sort_order, ZOPKProject.name).all()
|
|
|
|
# Get milestones for timeline (sorted by target_date)
|
|
milestones = db.query(ZOPKMilestone).order_by(
|
|
ZOPKMilestone.target_date.asc()
|
|
).all()
|
|
|
|
# Get active stakeholders
|
|
stakeholders = db.query(ZOPKStakeholder).filter(
|
|
ZOPKStakeholder.is_active == True
|
|
).order_by(ZOPKStakeholder.importance.desc(), ZOPKStakeholder.name).limit(10).all()
|
|
|
|
# Get approved news (both manually approved and AI auto-approved)
|
|
news_items = db.query(ZOPKNews).filter(
|
|
ZOPKNews.status.in_(['approved', 'auto_approved'])
|
|
).order_by(ZOPKNews.published_at.desc()).limit(25).all()
|
|
|
|
# Get featured resources
|
|
resources = db.query(ZOPKResource).filter(
|
|
ZOPKResource.status == 'approved'
|
|
).order_by(ZOPKResource.sort_order, ZOPKResource.created_at.desc()).limit(12).all()
|
|
|
|
# News time-based statistics
|
|
now = datetime.now()
|
|
day_ago = now - timedelta(days=1)
|
|
week_ago = now - timedelta(days=7)
|
|
month_ago = now - timedelta(days=30)
|
|
|
|
approved_news_filter = ZOPKNews.status.in_(['approved', 'auto_approved'])
|
|
total_news = db.query(ZOPKNews).filter(approved_news_filter).count()
|
|
|
|
news_stats = {
|
|
'total': total_news,
|
|
'last_day': db.query(ZOPKNews).filter(
|
|
approved_news_filter,
|
|
ZOPKNews.published_at >= day_ago
|
|
).count(),
|
|
'last_week': db.query(ZOPKNews).filter(
|
|
approved_news_filter,
|
|
ZOPKNews.published_at >= week_ago
|
|
).count(),
|
|
'last_month': db.query(ZOPKNews).filter(
|
|
approved_news_filter,
|
|
ZOPKNews.published_at >= month_ago
|
|
).count()
|
|
}
|
|
|
|
# General stats
|
|
stats = {
|
|
'total_projects': len(projects),
|
|
'total_news': total_news,
|
|
'total_resources': db.query(ZOPKResource).filter(ZOPKResource.status == 'approved').count(),
|
|
'total_stakeholders': db.query(ZOPKStakeholder).filter(ZOPKStakeholder.is_active == True).count()
|
|
}
|
|
|
|
# Knowledge data — admin only
|
|
knowledge_data = None
|
|
if current_user.is_authenticated and current_user.is_admin:
|
|
knowledge_data = {
|
|
'total_facts': db.query(func.count(ZOPKKnowledgeFact.id)).scalar(),
|
|
'total_entities': db.query(func.count(ZOPKKnowledgeEntity.id)).filter(
|
|
ZOPKKnowledgeEntity.merged_into_id.is_(None)
|
|
).scalar(),
|
|
'total_chunks': db.query(func.count(ZOPKKnowledgeChunk.id)).filter(
|
|
ZOPKKnowledgeChunk.embedding.isnot(None)
|
|
).scalar(),
|
|
'fact_types': db.query(
|
|
ZOPKKnowledgeFact.fact_type, func.count()
|
|
).group_by(ZOPKKnowledgeFact.fact_type).all(),
|
|
'top_entities': db.query(ZOPKKnowledgeEntity).filter(
|
|
ZOPKKnowledgeEntity.merged_into_id.is_(None),
|
|
ZOPKKnowledgeEntity.mentions_count >= 3
|
|
).order_by(ZOPKKnowledgeEntity.mentions_count.desc()).limit(15).all(),
|
|
'key_investments': db.query(ZOPKKnowledgeFact).filter(
|
|
ZOPKKnowledgeFact.numeric_value.isnot(None),
|
|
ZOPKKnowledgeFact.confidence_score >= 0.5
|
|
).order_by(ZOPKKnowledgeFact.numeric_value.desc()).limit(5).all(),
|
|
'dated_facts': db.query(ZOPKKnowledgeFact).join(ZOPKNews).filter(
|
|
ZOPKKnowledgeFact.date_value.isnot(None),
|
|
ZOPKKnowledgeFact.confidence_score >= 0.4
|
|
).order_by(ZOPKKnowledgeFact.date_value.desc()).limit(20).all(),
|
|
}
|
|
|
|
return render_template('zopk/index.html',
|
|
projects=projects,
|
|
stakeholders=stakeholders,
|
|
news_items=news_items,
|
|
resources=resources,
|
|
stats=stats,
|
|
news_stats=news_stats,
|
|
milestones=milestones,
|
|
knowledge_data=knowledge_data
|
|
)
|
|
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/zopk/projekty/<slug>')
|
|
def zopk_project_detail(slug):
|
|
"""Project detail page"""
|
|
db = SessionLocal()
|
|
try:
|
|
project = db.query(ZOPKProject).filter(ZOPKProject.slug == slug).first()
|
|
if not project:
|
|
abort(404)
|
|
|
|
# Get news for this project (both manually approved and AI auto-approved)
|
|
news_items = db.query(ZOPKNews).filter(
|
|
ZOPKNews.project_id == project.id,
|
|
ZOPKNews.status.in_(['approved', 'auto_approved'])
|
|
).order_by(ZOPKNews.published_at.desc()).limit(10).all()
|
|
|
|
# Get resources for this project
|
|
resources = db.query(ZOPKResource).filter(
|
|
ZOPKResource.project_id == project.id,
|
|
ZOPKResource.status == 'approved'
|
|
).order_by(ZOPKResource.sort_order).all()
|
|
|
|
# Get Norda companies linked to this project
|
|
company_links = db.query(ZOPKCompanyLink).filter(
|
|
ZOPKCompanyLink.project_id == project.id
|
|
).order_by(ZOPKCompanyLink.relevance_score.desc()).all()
|
|
|
|
return render_template('zopk/project_detail.html',
|
|
project=project,
|
|
news_items=news_items,
|
|
resources=resources,
|
|
company_links=company_links
|
|
)
|
|
|
|
finally:
|
|
db.close()
|
|
|
|
|
|
@bp.route('/zopk/aktualnosci')
|
|
def zopk_news_list():
|
|
"""All ZOPK news - paginated"""
|
|
db = SessionLocal()
|
|
try:
|
|
page = request.args.get('page', 1, type=int)
|
|
per_page = 20
|
|
project_slug = request.args.get('projekt')
|
|
|
|
query = db.query(ZOPKNews).filter(ZOPKNews.status.in_(['approved', 'auto_approved']))
|
|
|
|
if project_slug:
|
|
project = db.query(ZOPKProject).filter(ZOPKProject.slug == project_slug).first()
|
|
if project:
|
|
query = query.filter(ZOPKNews.project_id == project.id)
|
|
|
|
total = query.count()
|
|
news_items = query.order_by(ZOPKNews.published_at.desc()).offset(
|
|
(page - 1) * per_page
|
|
).limit(per_page).all()
|
|
|
|
total_pages = (total + per_page - 1) // per_page
|
|
|
|
# Get projects for filter
|
|
projects = db.query(ZOPKProject).filter(
|
|
ZOPKProject.is_active == True
|
|
).order_by(ZOPKProject.sort_order).all()
|
|
|
|
return render_template('zopk/news_list.html',
|
|
news_items=news_items,
|
|
projects=projects,
|
|
current_project=project_slug,
|
|
page=page,
|
|
total_pages=total_pages,
|
|
total=total
|
|
)
|
|
|
|
finally:
|
|
db.close()
|