Skip to content

Commit daf1bc6

Browse files
authored
Merge pull request #37 from data-exp-lab/fe-edge
Feature Edge Adding
2 parents d4eb5cc + 70af3b7 commit daf1bc6

File tree

9 files changed

+1967
-278
lines changed

9 files changed

+1967
-278
lines changed

backend/app/main.py

Lines changed: 197 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,12 @@
22
from flask_cors import CORS
33
from services.topic_service import TopicService
44
from services.ai_service import AITopicProcessor
5-
from services.gexy_node_service import GexfNodeGenerator
5+
from services.gexf_node_service import GexfNodeGenerator
6+
from services.edge_generation_service import EdgeGenerationService
67
import os
78
import asyncio
89
import re
10+
import json
911

1012
app = Flask(__name__, static_folder='gexf', static_url_path='/gexf')
1113
CORS(
@@ -21,7 +23,8 @@
2123

2224
topic_service = TopicService()
2325
ai_processor = AITopicProcessor()
24-
gexy_node_service = GexfNodeGenerator()
26+
gexf_node_service = GexfNodeGenerator()
27+
edge_generation_service = EdgeGenerationService()
2528

2629

2730
@app.route("/api/process-topics", methods=["GET", "POST"])
@@ -241,7 +244,7 @@ def suggest_topics():
241244
def finalized_node_gexf():
242245
data = request.get_json()
243246
topics = data.get("topics", [])
244-
gexf_path = gexy_node_service.generate_gexf_nodes_for_topics(topics)
247+
gexf_path = gexf_node_service.generate_gexf_nodes_for_topics(topics)
245248
# print(topics)
246249
# Read the GEXF file content
247250
with open(gexf_path, "r", encoding="utf-8") as f:
@@ -253,6 +256,197 @@ def finalized_node_gexf():
253256
})
254257

255258

259+
@app.route("/api/generate-graph-with-edges", methods=["POST"])
260+
def generate_graph_with_edges():
261+
"""
262+
Generate a graph with edges based on multiple criteria working in combination.
263+
264+
Expected request body:
265+
{
266+
"topics": ["topic1", "topic2"],
267+
"criteria_config": {
268+
"topic_based_linking": true,
269+
"contributor_overlap_enabled": true,
270+
"contributor_overlap_threshold": 2,
271+
"shared_organization_enabled": false,
272+
"common_stargazers_enabled": true,
273+
"stargazer_overlap_threshold": 3
274+
}
275+
}
276+
"""
277+
try:
278+
data = request.get_json()
279+
topics = data.get("topics", [])
280+
criteria_config = data.get("criteria_config", {})
281+
282+
if not topics:
283+
return jsonify({
284+
"success": False,
285+
"error": "No topics provided"
286+
}), 400
287+
288+
# Generate graph with edges based on criteria
289+
G, edge_stats = edge_generation_service.generate_edges_with_criteria(topics, criteria_config)
290+
291+
if not G.nodes():
292+
return jsonify({
293+
"success": False,
294+
"error": "No repositories found for the given topics"
295+
}), 404
296+
297+
# Generate unique filename for this graph
298+
import hashlib
299+
from datetime import datetime
300+
301+
# Create hash from topics and criteria
302+
criteria_str = json.dumps(criteria_config, sort_keys=True)
303+
topics_str = "|".join(sorted(topics))
304+
combined_str = f"{topics_str}_{criteria_str}"
305+
hash_object = hashlib.md5(combined_str.encode())
306+
hash_hex = hash_object.hexdigest()[:12]
307+
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
308+
filename = f"graph_with_edges_{hash_hex}_{timestamp}.gexf"
309+
310+
# Save to gexf directory
311+
gexf_dir = os.path.join(os.path.dirname(__file__), "gexf")
312+
os.makedirs(gexf_dir, exist_ok=True)
313+
gexf_path = os.path.join(gexf_dir, filename)
314+
315+
# Save graph with edges
316+
edge_generation_service.save_graph_with_edges(G, gexf_path)
317+
318+
# Read the GEXF file content
319+
with open(gexf_path, "r", encoding="utf-8") as f:
320+
gexf_content = f.read()
321+
322+
# Get comprehensive statistics
323+
graph_stats = edge_generation_service.get_edge_statistics(G)
324+
325+
return jsonify({
326+
"success": True,
327+
"gexfContent": gexf_content,
328+
"filename": filename,
329+
"edge_statistics": edge_stats,
330+
"graph_statistics": graph_stats
331+
})
332+
333+
except Exception as e:
334+
print(f"Error generating graph with edges: {str(e)}")
335+
return jsonify({
336+
"success": False,
337+
"error": str(e),
338+
"message": "An error occurred while generating the graph with edges"
339+
}), 500
340+
341+
342+
@app.route("/api/edge-generation-criteria", methods=["GET"])
343+
def get_edge_generation_criteria():
344+
"""
345+
Get information about available edge generation criteria and their descriptions.
346+
"""
347+
criteria_info = {
348+
"topic_based_linking": {
349+
"description": "Create edges between repositories that share common topics",
350+
"type": "boolean",
351+
"default": True,
352+
"category": "Content-based"
353+
},
354+
"contributor_overlap_enabled": {
355+
"description": "Create edges between repositories that have overlapping contributors",
356+
"type": "boolean",
357+
"default": False,
358+
"category": "Collaboration-based"
359+
},
360+
"contributor_overlap_threshold": {
361+
"description": "Minimum number of shared contributors required to create an edge",
362+
"type": "integer",
363+
"default": 2,
364+
"min": 1,
365+
"max": 100,
366+
"category": "Collaboration-based"
367+
},
368+
"shared_organization_enabled": {
369+
"description": "Create edges between repositories owned by the same organization",
370+
"type": "boolean",
371+
"default": False,
372+
"category": "Organizational"
373+
},
374+
"common_stargazers_enabled": {
375+
"description": "Create edges between repositories that have overlapping stargazers",
376+
"type": "boolean",
377+
"default": False,
378+
"category": "Interest-based"
379+
},
380+
"stargazer_overlap_threshold": {
381+
"description": "Minimum number of shared stargazers required to create an edge",
382+
"type": "integer",
383+
"default": 2,
384+
"min": 1,
385+
"max": 1000,
386+
"category": "Interest-based"
387+
},
388+
"use_and_logic": {
389+
"description": "Use AND logic to require multiple criteria to be satisfied for an edge",
390+
"type": "boolean",
391+
"default": False,
392+
"category": "Logic Control"
393+
}
394+
}
395+
396+
return jsonify({
397+
"success": True,
398+
"criteria": criteria_info,
399+
"usage_examples": [
400+
{
401+
"name": "Topic + Contributor Overlap",
402+
"description": "Connect repositories by both shared topics and contributor overlap",
403+
"config": {
404+
"topic_based_linking": True,
405+
"contributor_overlap_enabled": True,
406+
"contributor_overlap_threshold": 2,
407+
"shared_organization_enabled": False,
408+
"common_stargazers_enabled": False
409+
}
410+
},
411+
{
412+
"name": "Organization + Stargazer Overlap",
413+
"description": "Connect repositories by organization ownership and stargazer overlap",
414+
"config": {
415+
"topic_based_linking": False,
416+
"contributor_overlap_enabled": False,
417+
"shared_organization_enabled": True,
418+
"common_stargazers_enabled": True,
419+
"stargazer_overlap_threshold": 3
420+
}
421+
},
422+
{
423+
"name": "All Criteria Combined",
424+
"description": "Use all available criteria to create comprehensive connections",
425+
"config": {
426+
"topic_based_linking": True,
427+
"contributor_overlap_enabled": True,
428+
"contributor_overlap_threshold": 2,
429+
"shared_organization_enabled": True,
430+
"common_stargazers_enabled": True,
431+
"stargazer_overlap_threshold": 2
432+
}
433+
},
434+
{
435+
"name": "Contributor + Organization (AND Logic)",
436+
"description": "Only create edges when BOTH contributor overlap AND shared organization criteria are satisfied",
437+
"config": {
438+
"topic_based_linking": False,
439+
"contributor_overlap_enabled": True,
440+
"contributor_overlap_threshold": 2,
441+
"shared_organization_enabled": True,
442+
"common_stargazers_enabled": False,
443+
"use_and_logic": True
444+
}
445+
}
446+
]
447+
})
448+
449+
256450
@app.route("/api/get-unique-repos", methods=["POST"])
257451
def get_unique_repos():
258452
try:

0 commit comments

Comments
 (0)