From 85c3f75e9b1665bb4af12100a191abe3aaaf45b2 Mon Sep 17 00:00:00 2001 From: Maciej Pienczyn Date: Sat, 17 Jan 2026 09:14:30 +0100 Subject: [PATCH] feat(zopk): Graf relacji encji (Priorytet 5) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Dodano endpoint /admin/zopk/knowledge/graph z wizualizacją D3.js - Dodano API endpoint /api/zopk/knowledge/graph/data - Graf współwystępowania encji z kolorami według typu - Rozmiar węzłów proporcjonalny do liczby wzmianek - Filtry: typ encji, minimalna liczba współwystąpień - Tooltips z informacjami o encjach - Zoom i drag-and-drop interakcje Co-Authored-By: Claude Opus 4.5 --- app.py | 119 ++++ templates/admin/zopk_knowledge_dashboard.html | 7 + templates/admin/zopk_knowledge_graph.html | 509 ++++++++++++++++++ 3 files changed, 635 insertions(+) create mode 100644 templates/admin/zopk_knowledge_graph.html diff --git a/app.py b/app.py index 17e5031..511a817 100644 --- a/app.py +++ b/app.py @@ -11906,6 +11906,125 @@ def api_zopk_duplicates_merge(): db.close() +# ============================================================ +# ZOPK ENTITY RELATIONS GRAPH +# ============================================================ + +@app.route('/admin/zopk/knowledge/graph') +@login_required +def admin_zopk_knowledge_graph(): + """Admin page for entity relations graph visualization.""" + if not current_user.is_admin: + flash('Brak uprawnień do tej strony.', 'error') + return redirect(url_for('dashboard')) + + return render_template('admin/zopk_knowledge_graph.html') + + +@app.route('/api/zopk/knowledge/graph/data') +@login_required +def api_zopk_knowledge_graph_data(): + """Get graph data for entity co-occurrence visualization.""" + if not current_user.is_admin: + return jsonify({'success': False, 'error': 'Brak uprawnień'}), 403 + + from sqlalchemy import text, func + from database import ZOPKKnowledgeEntity, ZOPKKnowledgeEntityMention + + db = SessionLocal() + try: + # Filter parameters + entity_type = request.args.get('entity_type', '') + min_cooccurrence = int(request.args.get('min_cooccurrence', 3)) + limit = min(int(request.args.get('limit', 100)), 500) + + # Get top entities by mentions + entities_query = db.query(ZOPKKnowledgeEntity).filter( + ZOPKKnowledgeEntity.mentions_count >= 5 + ) + if entity_type: + entities_query = entities_query.filter( + ZOPKKnowledgeEntity.entity_type == entity_type + ) + entities_query = entities_query.order_by( + ZOPKKnowledgeEntity.mentions_count.desc() + ).limit(100) + + entities = entities_query.all() + entity_ids = [e.id for e in entities] + + if not entity_ids: + return jsonify({'success': True, 'nodes': [], 'links': []}) + + # Get co-occurrences (entities appearing in same chunk) + cooccur_query = text(""" + SELECT + m1.entity_id as source, + m2.entity_id as target, + COUNT(*) as value + FROM zopk_knowledge_entity_mentions m1 + JOIN zopk_knowledge_entity_mentions m2 + ON m1.chunk_id = m2.chunk_id + AND m1.entity_id < m2.entity_id + WHERE m1.entity_id = ANY(:entity_ids) + AND m2.entity_id = ANY(:entity_ids) + GROUP BY m1.entity_id, m2.entity_id + HAVING COUNT(*) >= :min_cooccurrence + ORDER BY COUNT(*) DESC + LIMIT :limit + """) + + result = db.execute(cooccur_query, { + 'entity_ids': entity_ids, + 'min_cooccurrence': min_cooccurrence, + 'limit': limit + }) + + # Build nodes and links + used_entity_ids = set() + links = [] + + for row in result: + links.append({ + 'source': row.source, + 'target': row.target, + 'value': row.value + }) + used_entity_ids.add(row.source) + used_entity_ids.add(row.target) + + # Build nodes only for entities that have links + entity_map = {e.id: e for e in entities} + nodes = [] + + for eid in used_entity_ids: + if eid in entity_map: + e = entity_map[eid] + nodes.append({ + 'id': e.id, + 'name': e.name, + 'type': e.entity_type, + 'mentions': e.mentions_count, + 'verified': e.is_verified + }) + + return jsonify({ + 'success': True, + 'nodes': nodes, + 'links': links, + 'stats': { + 'total_nodes': len(nodes), + 'total_links': len(links) + } + }) + + except Exception as e: + logger.error(f"Error getting graph data: {e}") + return jsonify({'success': False, 'error': str(e)}), 500 + finally: + db.close() + + # ============================================================ # KRS AUDIT (Krajowy Rejestr Sądowy) # ============================================================ diff --git a/templates/admin/zopk_knowledge_dashboard.html b/templates/admin/zopk_knowledge_dashboard.html index 7c730bc..bc22bdd 100644 --- a/templates/admin/zopk_knowledge_dashboard.html +++ b/templates/admin/zopk_knowledge_dashboard.html @@ -331,6 +331,13 @@ + + + +