mirror of
https://github.com/allaunthefox/Research-Stack.git
synced 2026-07-31 03:05:21 +00:00
Add concept extraction/orchestration scripts under scripts/ and infra metadata in infra.json. Generated extraction outputs live under extraction/ and are regenerated from scripts; add extraction/ to .gitignore.
212 lines
6 KiB
Python
212 lines
6 KiB
Python
#!/usr/bin/env -S uv run
|
|
# /// script
|
|
# requires-python = ">=3.11"
|
|
# dependencies = ["gremlinpython", "python-dotenv"]
|
|
# ///
|
|
"""
|
|
analyze_concept_graph.py — Graph topology analysis for cornfield concepts.
|
|
|
|
Queries:
|
|
Q1: Degree distribution (out + in) for all cornfield concepts
|
|
Q2: Bridge concepts (relates_to degree > 2)
|
|
Q3: Missing depends_on edges (has lean module but no edge)
|
|
Q4: Active Lean modules most referenced by cornfield concepts
|
|
Q5: relates_to pairs among cornfield concepts (cluster analysis)
|
|
"""
|
|
import os
|
|
import sys
|
|
from pathlib import Path
|
|
from dotenv import load_dotenv
|
|
|
|
load_dotenv(Path(__file__).parent.parent / ".env.gremlin")
|
|
|
|
from gremlin_python.driver import client as gc, serializer
|
|
|
|
c = gc.Client(
|
|
os.environ["GREMLIN_ENDPOINT"], "g",
|
|
username=os.environ["GREMLIN_USERNAME"],
|
|
password=os.environ["GREMLIN_PASSWORD"],
|
|
message_serializer=serializer.GraphSONSerializersV2d0(),
|
|
)
|
|
|
|
def q(query, bindings=None):
|
|
try:
|
|
result = c.submitAsync(query, bindings or {}).result().all().result()
|
|
return result
|
|
except Exception as e:
|
|
print(f" [ERROR] {e}")
|
|
return []
|
|
|
|
SEP = "═" * 68
|
|
|
|
print(SEP)
|
|
print("Q1: CORNFIELD CONCEPT DEGREE DISTRIBUTION")
|
|
print("─" * 68)
|
|
|
|
r = q(
|
|
"g.V().has('cohort','cornfield')"
|
|
".project('vid','out_deg','in_deg')"
|
|
".by(id())"
|
|
".by(outE().count())"
|
|
".by(inE().count())"
|
|
)
|
|
|
|
if r:
|
|
# Sort by total degree desc
|
|
rows = sorted(r, key=lambda x: x['out_deg'] + x['in_deg'])
|
|
print(f" {'ID':<40} {'out':>4} {'in':>4} {'total':>6}")
|
|
print(" " + "-" * 60)
|
|
isolated = []
|
|
for row in rows:
|
|
vid = str(row['vid'])
|
|
out_d = row['out_deg']
|
|
in_d = row['in_deg']
|
|
total = out_d + in_d
|
|
flag = " *** ISOLATED" if total <= 1 else (" * LOW" if total <= 2 else "")
|
|
print(f" {vid:<40} {out_d:>4} {in_d:>4} {total:>6}{flag}")
|
|
if total <= 1:
|
|
isolated.append(vid)
|
|
print()
|
|
print(f" Total cornfield concepts queried: {len(rows)}")
|
|
print(f" Isolated (degree <= 1): {isolated}")
|
|
else:
|
|
print(" No results or error.")
|
|
|
|
print()
|
|
print(SEP)
|
|
print("Q2: BRIDGE CONCEPTS (relates_to out-degree > 2)")
|
|
print("─" * 68)
|
|
|
|
r2 = q(
|
|
"g.V().has('cohort','cornfield')"
|
|
".filter(outE('relates_to').count().is(gt(2)))"
|
|
".project('vid','rt_count')"
|
|
".by(id())"
|
|
".by(outE('relates_to').count())"
|
|
".order().by(select('rt_count'), decr)"
|
|
)
|
|
|
|
if r2:
|
|
for row in r2:
|
|
print(f" {str(row['vid']):<40} relates_to_out={row['rt_count']}")
|
|
else:
|
|
print(" None found (no concept has > 2 outgoing relates_to edges).")
|
|
|
|
print()
|
|
print(SEP)
|
|
print("Q3: MISSING depends_on EDGES (has lean module, no depends_on edge)")
|
|
print("─" * 68)
|
|
|
|
r3 = q(
|
|
"g.V().has('cohort','cornfield')"
|
|
".has('lean_status', within('LIBRARY','ARCHIVED','PARTIALLY_ACTIVE','PROVEN','DEFINED'))"
|
|
".not(outE('depends_on'))"
|
|
".project('vid','lean_status')"
|
|
".by(id())"
|
|
".by('lean_status')"
|
|
)
|
|
|
|
if r3:
|
|
for row in r3:
|
|
print(f" {str(row['vid']):<40} lean_status={row.get('lean_status','?')}")
|
|
print(f"\n Count: {len(r3)} concepts with Lean modules but no depends_on edge")
|
|
else:
|
|
print(" All concepts with Lean modules already have depends_on edges, or none found.")
|
|
|
|
print()
|
|
print(SEP)
|
|
print("Q4: TOP ACTIVE LEAN MODULES (most cornfield depends_on edges incoming)")
|
|
print("─" * 68)
|
|
|
|
r4 = q(
|
|
"g.V().has('cohort','cornfield').out('depends_on')"
|
|
".hasLabel('module')"
|
|
".groupCount().by(id())"
|
|
".order(local).by(values, decr).limit(local, 15)"
|
|
)
|
|
|
|
if r4 and len(r4) > 0:
|
|
result_map = r4[0] if isinstance(r4[0], dict) else {}
|
|
for mod_id, count in sorted(result_map.items(), key=lambda x: -x[1]):
|
|
print(f" {str(mod_id):<55} count={count}")
|
|
else:
|
|
print(" No depends_on edges to modules found, or error.")
|
|
|
|
print()
|
|
print(SEP)
|
|
print("Q5: CORNFIELD CONCEPT CLUSTER ANALYSIS (relates_to pairs)")
|
|
print("─" * 68)
|
|
|
|
r5 = q(
|
|
"g.V().has('cohort','cornfield').as('a')"
|
|
".out('relates_to').has('cohort','cornfield').as('b')"
|
|
".select('a','b').by(id())"
|
|
)
|
|
|
|
if r5:
|
|
print(f" Total relates_to edges between cornfield concepts: {len(r5)}")
|
|
print()
|
|
# Build adjacency for cluster analysis
|
|
adj = {}
|
|
for row in r5:
|
|
a = str(row['a'])
|
|
b = str(row['b'])
|
|
adj.setdefault(a, set()).add(b)
|
|
adj.setdefault(b, set()).add(a)
|
|
|
|
print(" Pairs found:")
|
|
for row in r5:
|
|
a = str(row['a'])
|
|
b = str(row['b'])
|
|
print(f" {a:<40} → {b}")
|
|
|
|
print()
|
|
# Find concepts with multiple relates_to neighbors
|
|
print(" Cluster hubs (2+ cornfield neighbours):")
|
|
for node, neighbors in sorted(adj.items(), key=lambda x: -len(x[1])):
|
|
if len(neighbors) >= 2:
|
|
print(f" {node:<40} ({len(neighbors)} neighbors): {sorted(neighbors)[:5]}")
|
|
else:
|
|
print(" No relates_to edges between cornfield concepts found in graph.")
|
|
|
|
print()
|
|
print(SEP)
|
|
print("Q6: ALL EDGE LABELS ON CORNFIELD VERTICES (to see what edges exist)")
|
|
print("─" * 68)
|
|
|
|
r6 = q(
|
|
"g.V().has('cohort','cornfield').bothE()"
|
|
".groupCount().by(label())"
|
|
)
|
|
|
|
if r6 and r6[0]:
|
|
for label, count in sorted(r6[0].items()):
|
|
print(f" {label:<30} count={count}")
|
|
else:
|
|
print(" No edges found on cornfield concepts.")
|
|
|
|
print()
|
|
print(SEP)
|
|
print("Q7: FULL DEGREE TABLE SORTED BY TOTAL (ascending — find lowest)")
|
|
print("─" * 68)
|
|
|
|
# Show all vertex IDs with their property values to match to concept names
|
|
r7 = q(
|
|
"g.V().has('cohort','cornfield')"
|
|
".project('vid','name','lean_status','tags')"
|
|
".by(id())"
|
|
".by(coalesce(values('name'), constant('?')))"
|
|
".by(coalesce(values('lean_status'), constant('?')))"
|
|
".by(coalesce(values('tags'), constant('?')))"
|
|
)
|
|
|
|
if r7:
|
|
print(f" Total concepts: {len(r7)}")
|
|
for row in r7:
|
|
print(f" id={str(row['vid']):<45} name={str(row.get('name','?')):<40} status={str(row.get('lean_status','?')):<25} tags={str(row.get('tags','?'))[:60]}")
|
|
else:
|
|
print(" No property results.")
|
|
|
|
c.close()
|
|
print()
|
|
print("Done.")
|