Nucha commited on
Commit
dfba298
·
verified ·
1 Parent(s): a29e2b2

Upload 2 files

Browse files
Files changed (1) hide show
  1. app.py +9 -31
app.py CHANGED
@@ -1,19 +1,15 @@
1
  \
2
  import os
3
  import json
 
4
  from typing import Dict, Any, List, Tuple
5
  import gradio as gr
6
 
7
- # Network rendering
8
  from pyvis.network import Network
9
 
10
- DEFAULT_JSON = "job_skill_network.json" # Place this file in the Space repo root
11
 
12
  def _load_graph(file_obj) -> Dict[str, Any]:
13
- """
14
- Load JSON from uploaded file or from DEFAULT_JSON if present.
15
- Expect keys: 'nodes': [{'id','label','type',...}], 'edges': [{'source','target','type','weight',...}]
16
- """
17
  if file_obj is not None:
18
  with open(file_obj.name if hasattr(file_obj, "name") else file_obj, "r", encoding="utf-8") as f:
19
  return json.load(f)
@@ -37,12 +33,11 @@ def _filter_graph(graph: Dict[str, Any],
37
  top_n_jobs: int,
38
  keep_outside_similar: bool,
39
  include_job_nodes: bool,
40
- include_skill_nodes: bool) -> Tuple[List[Dict[str, Any]], List[Dict[str, Any]]]:
41
  nodes = graph.get("nodes", [])
42
  edges = graph.get("edges", [])
43
  jobs, skills = _split_nodes(nodes)
44
 
45
- # Sort jobs by postings desc (fallback to degree if missing)
46
  def _postings(n):
47
  try:
48
  return int(n.get("postings", 0))
@@ -52,29 +47,24 @@ def _filter_graph(graph: Dict[str, Any],
52
  jobs_sorted = sorted(jobs, key=_postings, reverse=True)
53
  selected_job_ids = set([n["id"] for n in jobs_sorted[:max(1, int(top_n_jobs))]]) if include_job_nodes else set()
54
 
55
- # Start with required edge types
56
  selected_edges = []
57
  for e in edges:
58
  et = str(e.get("type","")).lower()
59
  if et == "requires" and include_requires and int(e.get("weight", 1)) >= int(min_weight):
60
- # only keep if we include job nodes and skill nodes
61
  if include_job_nodes or include_skill_nodes:
62
  selected_edges.append(e)
63
  elif et == "similar" and include_similar and int(e.get("weight", 1)) >= int(min_weight):
64
  selected_edges.append(e)
65
 
66
- # Build node id set from edges, but restricted to selected jobs for "requires" if we have a top_n constraint
67
  node_ids = set()
68
  for e in selected_edges:
69
  s, t, et = e.get("source"), e.get("target"), str(e.get("type","")).lower()
70
  if et == "requires" and selected_job_ids:
71
- # retain requires edges only if job is in top N (either as source or target, depending direction)
72
  if (s in selected_job_ids) or (t in selected_job_ids):
73
  node_ids.update([s, t])
74
  else:
75
  node_ids.update([s, t])
76
 
77
- # If include_similar is True and keep_outside_similar is True, add jobs similar to selected jobs even if outside top N
78
  if include_similar and keep_outside_similar and selected_job_ids:
79
  for e in selected_edges:
80
  if str(e.get("type","")).lower() != "similar":
@@ -83,7 +73,6 @@ def _filter_graph(graph: Dict[str, Any],
83
  if (s in selected_job_ids) or (t in selected_job_ids):
84
  node_ids.update([s, t])
85
 
86
- # Build final node list according to include flags
87
  node_map = _index_nodes(nodes)
88
  final_nodes = []
89
  for nid in list(node_ids):
@@ -94,18 +83,15 @@ def _filter_graph(graph: Dict[str, Any],
94
  if (ntype == "job" and include_job_nodes) or (ntype == "skill" and include_skill_nodes):
95
  final_nodes.append(n)
96
 
97
- # Filter edges to keep only those whose endpoints remain
98
  final_ids = set(n["id"] for n in final_nodes)
99
  final_edges = [e for e in selected_edges if e.get("source") in final_ids and e.get("target") in final_ids]
100
 
101
  return final_nodes, final_edges
102
 
103
- def _build_pyvis_html(nodes: List[Dict[str, Any]], edges: List[Dict[str, Any]], physics: bool, hierarchical: bool):
104
- # Create network
105
  net = Network(height="720px", width="100%", directed=False, notebook=False)
106
- net.barnes_hut() # default physics model
107
 
108
- # Add nodes
109
  for n in nodes:
110
  nid = n["id"]
111
  label = str(n.get("label", nid))
@@ -114,14 +100,12 @@ def _build_pyvis_html(nodes: List[Dict[str, Any]], edges: List[Dict[str, Any]],
114
  size = 12
115
  shape = "dot"
116
  if ntype == "job":
117
- size = 18 + int(n.get("postings", 0)) * 0.1 # scale by postings
118
  shape = "ellipse"
119
  elif ntype == "skill":
120
  size = 8
121
-
122
  net.add_node(nid, label=label, title=title, group=ntype, shape=shape, value=size)
123
 
124
- # Add edges
125
  for e in edges:
126
  s, t = e.get("source"), e.get("target")
127
  et = str(e.get("type",""))
@@ -129,7 +113,6 @@ def _build_pyvis_html(nodes: List[Dict[str, Any]], edges: List[Dict[str, Any]],
129
  title = f"{et} (w={weight})"
130
  net.add_edge(s, t, title=title, value=weight)
131
 
132
- # Options
133
  options = {
134
  "physics": {"enabled": bool(physics)},
135
  "interaction": {"hover": True, "multiselect": True, "dragNodes": True},
@@ -137,7 +120,6 @@ def _build_pyvis_html(nodes: List[Dict[str, Any]], edges: List[Dict[str, Any]],
137
  "edges": {"smooth": {"type": "dynamic"}}
138
  }
139
  if hierarchical:
140
- # Simple hierarchical layout (works better when dominated by job->skill edges)
141
  options["layout"] = {
142
  "hierarchical": {
143
  "enabled": True,
@@ -148,12 +130,10 @@ def _build_pyvis_html(nodes: List[Dict[str, Any]], edges: List[Dict[str, Any]],
148
  "sortMethod": "hubsize"
149
  }
150
  }
151
- # When hierarchical, physics should usually be off to avoid jitter
152
  options["physics"]["enabled"] = False
153
 
154
- net.set_options(json.dumps(options))
155
-
156
- # Return html string (include vis.js assets inline)
157
  return net.generate_html()
158
 
159
  def build_network(
@@ -177,7 +157,6 @@ def build_network(
177
 
178
  html = _build_pyvis_html(nodes, edges, physics, hierarchical)
179
 
180
- # Save to a temp file so users can download
181
  out_name = f"network_{uuid.uuid4().hex[:8]}.html"
182
  with open(out_name, "w", encoding="utf-8") as f:
183
  f.write(html)
@@ -185,8 +164,7 @@ def build_network(
185
  return gr.update(value=html), out_name
186
 
187
  with gr.Blocks(title="Job ↔ Hard Skill Network") as demo:
188
- gr.Markdown("# Job ↔ Hard Skill Network Diagram\n"
189
- "Upload `job_skill_network.json` or place it at repo root.")
190
 
191
  with gr.Row():
192
  with gr.Column(scale=1):
 
1
  \
2
  import os
3
  import json
4
+ import uuid # <-- FIX: added
5
  from typing import Dict, Any, List, Tuple
6
  import gradio as gr
7
 
 
8
  from pyvis.network import Network
9
 
10
+ DEFAULT_JSON = "job_skill_network.json"
11
 
12
  def _load_graph(file_obj) -> Dict[str, Any]:
 
 
 
 
13
  if file_obj is not None:
14
  with open(file_obj.name if hasattr(file_obj, "name") else file_obj, "r", encoding="utf-8") as f:
15
  return json.load(f)
 
33
  top_n_jobs: int,
34
  keep_outside_similar: bool,
35
  include_job_nodes: bool,
36
+ include_skill_nodes: bool):
37
  nodes = graph.get("nodes", [])
38
  edges = graph.get("edges", [])
39
  jobs, skills = _split_nodes(nodes)
40
 
 
41
  def _postings(n):
42
  try:
43
  return int(n.get("postings", 0))
 
47
  jobs_sorted = sorted(jobs, key=_postings, reverse=True)
48
  selected_job_ids = set([n["id"] for n in jobs_sorted[:max(1, int(top_n_jobs))]]) if include_job_nodes else set()
49
 
 
50
  selected_edges = []
51
  for e in edges:
52
  et = str(e.get("type","")).lower()
53
  if et == "requires" and include_requires and int(e.get("weight", 1)) >= int(min_weight):
 
54
  if include_job_nodes or include_skill_nodes:
55
  selected_edges.append(e)
56
  elif et == "similar" and include_similar and int(e.get("weight", 1)) >= int(min_weight):
57
  selected_edges.append(e)
58
 
 
59
  node_ids = set()
60
  for e in selected_edges:
61
  s, t, et = e.get("source"), e.get("target"), str(e.get("type","")).lower()
62
  if et == "requires" and selected_job_ids:
 
63
  if (s in selected_job_ids) or (t in selected_job_ids):
64
  node_ids.update([s, t])
65
  else:
66
  node_ids.update([s, t])
67
 
 
68
  if include_similar and keep_outside_similar and selected_job_ids:
69
  for e in selected_edges:
70
  if str(e.get("type","")).lower() != "similar":
 
73
  if (s in selected_job_ids) or (t in selected_job_ids):
74
  node_ids.update([s, t])
75
 
 
76
  node_map = _index_nodes(nodes)
77
  final_nodes = []
78
  for nid in list(node_ids):
 
83
  if (ntype == "job" and include_job_nodes) or (ntype == "skill" and include_skill_nodes):
84
  final_nodes.append(n)
85
 
 
86
  final_ids = set(n["id"] for n in final_nodes)
87
  final_edges = [e for e in selected_edges if e.get("source") in final_ids and e.get("target") in final_ids]
88
 
89
  return final_nodes, final_edges
90
 
91
+ def _build_pyvis_html(nodes, edges, physics: bool, hierarchical: bool):
 
92
  net = Network(height="720px", width="100%", directed=False, notebook=False)
93
+ net.barnes_hut()
94
 
 
95
  for n in nodes:
96
  nid = n["id"]
97
  label = str(n.get("label", nid))
 
100
  size = 12
101
  shape = "dot"
102
  if ntype == "job":
103
+ size = 18 + int(n.get("postings", 0)) * 0.1
104
  shape = "ellipse"
105
  elif ntype == "skill":
106
  size = 8
 
107
  net.add_node(nid, label=label, title=title, group=ntype, shape=shape, value=size)
108
 
 
109
  for e in edges:
110
  s, t = e.get("source"), e.get("target")
111
  et = str(e.get("type",""))
 
113
  title = f"{et} (w={weight})"
114
  net.add_edge(s, t, title=title, value=weight)
115
 
 
116
  options = {
117
  "physics": {"enabled": bool(physics)},
118
  "interaction": {"hover": True, "multiselect": True, "dragNodes": True},
 
120
  "edges": {"smooth": {"type": "dynamic"}}
121
  }
122
  if hierarchical:
 
123
  options["layout"] = {
124
  "hierarchical": {
125
  "enabled": True,
 
130
  "sortMethod": "hubsize"
131
  }
132
  }
 
133
  options["physics"]["enabled"] = False
134
 
135
+ import json as _json
136
+ net.set_options(_json.dumps(options))
 
137
  return net.generate_html()
138
 
139
  def build_network(
 
157
 
158
  html = _build_pyvis_html(nodes, edges, physics, hierarchical)
159
 
 
160
  out_name = f"network_{uuid.uuid4().hex[:8]}.html"
161
  with open(out_name, "w", encoding="utf-8") as f:
162
  f.write(html)
 
164
  return gr.update(value=html), out_name
165
 
166
  with gr.Blocks(title="Job ↔ Hard Skill Network") as demo:
167
+ gr.Markdown("# Job ↔ Hard Skill Network Diagram\nUpload `job_skill_network.json` or place it at repo root.")
 
168
 
169
  with gr.Row():
170
  with gr.Column(scale=1):