Skip to content

Commit

Permalink
Merge pull request #1 from reedjones/add-graph-relationships
Browse files Browse the repository at this point in the history
feat(threads): threads.models, threads.graphs
  • Loading branch information
reedjones authored Nov 22, 2024
2 parents 4b0861b + e0434f5 commit 3766782
Show file tree
Hide file tree
Showing 14 changed files with 510 additions and 46 deletions.
File renamed without changes.
87 changes: 45 additions & 42 deletions poetry.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

62 changes: 62 additions & 0 deletions project/core/management/commands/load_graph_dummy_data.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# data_loader.py
from django.core.management.base import BaseCommand
from threads.models import Civi, CiviLink, Thread # Adjust import to your app's name

from django.contrib.auth import get_user_model

User = get_user_model()


class Command(BaseCommand):
help = "Load dummy data for Civis and CiviLinks"

def handle(self, *args, **kwargs):
# Create a thread
thread, _ = Thread.objects.get_or_create(
title="Net Neutrality",
)
user = User.objects.first()
# Create dummy Civis
civi1 = Civi.objects.create(
title="Civi 1: Importance of Net Neutrality",
body="Net neutrality ensures that all users have equal access to information and services online.",
author=user,
votes_pos=10,
votes_neg=2,
thread=thread,
)

civi2 = Civi.objects.create(
title="Civi 2: Risks of Removing Net Neutrality",
body="Without net neutrality, ISPs could prioritize their own content or the content of those who pay for faster access.",
author=user,
votes_pos=15,
votes_neg=1,
thread=thread,
)

civi3 = Civi.objects.create(
title="Civi 3: Public Opinion on Net Neutrality",
body="A significant portion of the public supports net neutrality regulations to protect free internet access.",
author=user,
votes_pos=20,
votes_neg=3,
thread=thread,
)

# Create dummy CiviLinks
CiviLink.objects.create(
from_civi=civi1, to_civi=civi2, relation_type="response"
)

CiviLink.objects.create(
from_civi=civi2, to_civi=civi1, relation_type="rebuttal"
)

CiviLink.objects.create(from_civi=civi1, to_civi=civi3, relation_type="support")

CiviLink.objects.create(
from_civi=civi3, to_civi=civi2, relation_type="challenge"
)

self.stdout.write(self.style.SUCCESS("Successfully loaded dummy data"))
2 changes: 2 additions & 0 deletions project/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""

from core.router import CiviWikiRouter
from django.conf import settings
from django.conf.urls.static import static
Expand All @@ -25,6 +26,7 @@
path("admin/", admin.site.urls),
path("api/v1/", include(CiviWikiRouter.urls)),
path("api/", include("threads.urls.api")),
path("api/", include("threads.urls.graph_api")),
path("", include("accounts.urls")),
path("", include("threads.urls.urls")),
path(
Expand Down
87 changes: 87 additions & 0 deletions project/threads/graph_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .graphs import (
load_graph_from_db,
most_caused_problems,
most_effective_solution,
shortest_path_problem_to_solution,
)


@api_view(["GET"])
def get_most_caused_problem(request):
with_score = request.GET.get("with_score", False)
G = load_graph_from_db(with_score)
problem = most_caused_problems(G)
if problem:
if with_score:
return Response(
{
"problem": G.nodes[problem]["label"],
"score": G.nodes[problem]["score"],
}
)
else:
return Response({"problem": G.nodes[problem]["label"]})
return Response({"error": "No problem found"}, status=404)


@api_view(["GET"])
def get_most_effective_solution(request):
with_score = request.GET.get("with_score", False)
G = load_graph_from_db(with_score)
solution = most_effective_solution(G)
if solution:
if with_score:
return Response(
{
"solution": G.nodes[solution]["label"],
"score": G.nodes[solution]["score"],
}
)
else:
return Response({"solution": G.nodes[solution]["label"]})
return Response({"error": "No solution found"}, status=404)


@api_view(["GET"])
def get_shortest_path(request, problem_id, solution_id):
with_score = request.GET.get("with_score", False)
G = load_graph_from_db(with_score)
path = shortest_path_problem_to_solution(G, int(problem_id), int(solution_id))
if path:
path_labels = [G.nodes[node]["label"] for node in path]
return Response({"path_labels": path_labels, "path":path})
return Response({"error": "No path found"}, status=404)




@api_view(['GET'])
def get_graph_data(request):
G = load_graph_from_db() # Load the graph from your DB or other data source
nodes = []
edges = []

# Format nodes and edges to match Cytoscape format
for node, attr in G.nodes(data=True):
nodes.append({
'data': {
'id': node,
'label': attr.get('label', node),
'type': attr.get('type'),
'score': attr.get('score', 0),
}
})

for source, target, attr in G.edges(data=True):
edges.append({
'data': {
'id': f'{source}-{target}',
'source': source,
'target': target,
'label': attr.get('label', 'related')
}
})

return Response({'nodes': nodes, 'edges': edges})
42 changes: 42 additions & 0 deletions project/threads/graphs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import networkx as nx
from .models import Civi, CiviLink


def load_graph_from_db(with_score:bool=False):
# Create a directed graph
G = nx.DiGraph()

# Add all Civis as nodes
for civi in Civi.objects.all():
node_args, node_kwargs = civi.__node__(with_score=with_score)
G.add_node(*node_args, **node_kwargs)

# Add CiviLinks as edges
for link in CiviLink.objects.all():
edge_args, edge_kwargs = link.__edge__()
G.add_edge(*edge_args, **edge_kwargs)

return G


def most_caused_problems(G, with_score:bool=False):
problem_nodes = [n for n, attr in G.nodes(data=True) if attr["type"] == "Problem"]
if with_score:
return max(problem_nodes, key=lambda n: (G.in_degree(n), G.nodes[n]['score']), default=None)
return max(problem_nodes, key=lambda n: G.in_degree(n), default=None)


def most_effective_solution(G, with_score:bool=False):
solution_nodes = [n for n, attr in G.nodes(data=True) if attr["type"] == "Solution"]
if with_score:
return max(solution_nodes, key=lambda n: (G.out_degree(n), G.nodes[n]['score']), default=None)
return max(solution_nodes, key=lambda n: G.out_degree(n), default=None)


def shortest_path_problem_to_solution(G, problem_id, solution_id, with_score:bool=False):
try:
if with_score:
return nx.shortest_path(G, source=problem_id, target=solution_id, weight='weight')
return nx.shortest_path(G, source=problem_id, target=solution_id)
except nx.NetworkXNoPath:
return None
Loading

0 comments on commit 3766782

Please sign in to comment.