Skip to content

BACKEND ISSUE 19 — [FEATURE] Implement Graph Network & Proximity Connections API With Project-Based Filters #37

Description

@LeDuyCoder

🎯 Goal

Implement API GET /analytics/network/topology để trả về dữ liệu graph topology dạng nodesedges.

API này phục vụ các widget phụ phía dưới:

  • Conceptual Proximity
  • Global Collaboration Network

Mục tiêu là cung cấp cấu trúc đồ thị mạng lưới để FE dựng sơ đồ kết nối không gian, bao gồm:

  • Các node đại diện cho keyword / topic / author / institution
  • Các edge đại diện cho quan hệ liên kết, độ gần khái niệm hoặc hợp tác nghiên cứu
  • Trọng số weight để FE biểu diễn độ mạnh/yếu của kết nối

🖼️ UI Mapping

UI Blocks

Conceptual Proximity
Global Collaboration Network

Chart Type

Network graph / Force-directed graph / Spatial topology graph

Description

API trả về graph topology gồm:

nodes = danh sách thực thể
edges = danh sách liên kết giữa các thực thể

Ví dụ:

LLMs -------------- Ethics
AI Safety --------- Bias Detection
Author A ---------- Institution B

📡 Endpoint

GET /analytics/network/topology

📥 Query Parameters

Param Type Required Description
project_id string / number Yes ID của project cần lấy network topology
network_type string No Loại network cần lấy. Values: conceptual, collaboration, all. Default: all
subject_area string No Lọc hẹp thêm theo subject area cụ thể
keywords string[] / comma-separated No Lọc hẹp thêm theo danh sách keyword
from_year number No Năm bắt đầu lọc dữ liệu
to_year number No Năm kết thúc lọc dữ liệu
limit_nodes number No Số lượng node tối đa. Default: 50
min_weight number No Chỉ lấy edge có weight tối thiểu. Default: 0.1

✅ Example Requests

1. Fetch all topology by project only

GET /analytics/network/topology?project_id=PROJECT_ID

2. Fetch conceptual proximity only

GET /analytics/network/topology?project_id=PROJECT_ID&network_type=conceptual

3. Fetch collaboration network only

GET /analytics/network/topology?project_id=PROJECT_ID&network_type=collaboration

4. Fetch topology with keywords filter

GET /analytics/network/topology?project_id=PROJECT_ID&keywords=LLM,RAG,AI Safety

5. Fetch topology with limits

GET /analytics/network/topology?project_id=PROJECT_ID&limit_nodes=40&min_weight=0.3

🧾 Response Contract

{
  "code": 200,
  "message": "Fetch network topology successfully",
  "data": {
    "nodes": [
      {
        "id": "n1",
        "label": "LLMs",
        "size": 20
      },
      {
        "id": "n2",
        "label": "Ethics",
        "size": 15
      }
    ],
    "edges": [
      {
        "from": "n1",
        "to": "n2",
        "weight": 0.82
      }
    ]
  }
}

📌 Response Field Explanation

Node

Field Type Description
id string Unique ID của node
label string Tên hiển thị của node
size number Kích thước node trên graph

Edge

Field Type Description
from string ID node nguồn
to string ID node đích
weight number Độ mạnh liên kết giữa 2 node

✅ Optional Extended Response Fields

FE hiện tại chỉ cần id, label, size, from, to, weight.

Tuy nhiên BE có thể bổ sung thêm các field sau nếu cần mà không phá contract:

{
  "id": "kw_001",
  "label": "LLMs",
  "type": "KEYWORD",
  "size": 20,
  "color": "#FF6B00"
}
{
  "from": "kw_001",
  "to": "kw_002",
  "weight": 0.82,
  "type": "CONCEPTUAL_PROXIMITY",
  "label": "0.82 proximity"
}

🧠 Business Logic

1. Project-based filtering

API bắt buộc nhận project_id.

Từ project_id, hệ thống cần lấy scope nghiên cứu của project:

  • Subject area của project
  • Subject categories thuộc subject area đó
  • Keywords project đang theo dõi

Sau đó dùng scope này để lọc tập bài báo trước khi build network topology.

Ví dụ project đang theo dõi:

{
  "project_id": 1,
  "subject_area": "Artificial Intelligence",
  "subject_categories": ["Computer Science Applications", "Artificial Intelligence"],
  "keywords": ["LLM", "RAG", "Machine Learning"]
}

API chỉ build graph trên dữ liệu thuộc scope này.


2. Filter priority

Nếu client chỉ truyền:

project_id

API tự động lấy toàn bộ subject area, subject categories và keywords của project để lọc.

Nếu client truyền thêm:

subject_area
keywords

API sẽ lọc hẹp hơn trong phạm vi project.

Ví dụ:

GET /analytics/network/topology?project_id=1&subject_area=AI&keywords=LLM,RAG

Kết quả chỉ build network trên dữ liệu:

  • Thuộc project scope
  • Thuộc subject area AI
  • Có keyword liên quan đến LLM hoặc RAG

3. Network type logic

API hỗ trợ 3 chế độ:

conceptual
collaboration
all

network_type = conceptual

Build network từ quan hệ gần nhau về mặt khái niệm giữa:

  • Keyword
  • Topic
  • Subject category

Ví dụ:

LLMs -> Ethics
AI Safety -> Bias Detection
Quantum Sim -> Physics

network_type = collaboration

Build network từ quan hệ hợp tác giữa:

  • Author
  • Institution
  • Country nếu có dữ liệu

Ví dụ:

Dr. Helena Vance -> Stanford Bio-Dynamics Lab
Author A -> Author B
Institution A -> Institution B

network_type = all

Kết hợp cả:

conceptual network + collaboration network

4. Conceptual proximity node logic

Với network_type=conceptual, node nên lấy từ:

Keyword
Topic
Subject_Category

Suggested phase 1:

Use Keyword and Topic as nodes

Node example:

{
  "id": "kw_001",
  "label": "LLMs",
  "size": 20
}

Node size calculation:

size = 10 + min(article_count * 2, 30)

Hoặc normalize theo volume:

size = normalized(article_count, min=10, max=40)

5. Conceptual proximity edge logic

Edge giữa 2 keyword/topic được tạo khi chúng cùng xuất hiện trong cùng bài báo hoặc có liên hệ qua topic/sub-topic.

Suggested phase 1:

weight = co_occurrence_count / max_co_occurrence_count

Trong đó:

co_occurrence_count = số bài báo mà 2 keyword/topic cùng xuất hiện

Yêu cầu normalize:

weight nằm trong khoảng 0 đến 1

Ví dụ:

{
  "from": "kw_001",
  "to": "kw_002",
  "weight": 0.82
}

6. Collaboration network node logic

Với network_type=collaboration, node nên lấy từ:

Author
Institution

Suggested phase 1 nếu chưa có institution data:

Use Author nodes only

Node size calculation:

size = 10 + min(article_count * 2, 30)

Hoặc:

size = normalized(article_count + citation_count, min=10, max=40)

7. Collaboration network edge logic

Edge được tạo từ quan hệ hợp tác nghiên cứu.

Supported phase 1:

Edge Meaning
Author -> Author Hai tác giả cùng viết một hoặc nhiều bài báo
Author -> Institution Tác giả thuộc / hợp tác với institution
Institution -> Institution Hai institution cùng xuất hiện trong bài báo

Suggested phase 1:

weight = co_author_article_count

Có thể normalize về 0-1 nếu FE muốn dùng cùng scale với conceptual.

Recommended for current contract:

weight = normalized collaboration strength from 0 to 1

8. Edge canonicalization

Để tránh duplicate:

n1 -> n2
n2 -> n1

phải merge thành một edge duy nhất.

Suggested rule:

from = smaller node id
to = larger node id

Nếu duplicate edge:

weight = merged / normalized value

9. Graph limiting rule

Để FE không bị quá tải:

limit_nodes default = 50
limit_nodes max = 150

Suggested logic:

1. Tính importance của node
2. Chọn top N nodes
3. Chỉ giữ edge có cả from và to nằm trong selected nodes
4. Apply min_weight
5. Sort edges by weight DESC

Node importance có thể tính bằng:

article_count + citation_count + degree_count

10. Weight normalization

Vì API có thể trộn conceptual và collaboration, nên chuẩn hóa edge weight về:

0 <= weight <= 1

Suggested formula:

weight = rawWeight / maxRawWeight

Round:

round to 2 decimal places

Nếu maxRawWeight = 0:

weight = 0

11. Data validation rule

Response phải đảm bảo:

  • nodes là array
  • edges là array
  • Node id không null
  • Node label không null
  • Node size là number
  • Edge fromto trỏ tới node tồn tại
  • Edge from !== to
  • Edge weight là number
  • Không có NaN
  • Không có duplicate node
  • Không có duplicate edge
  • Không có reverse duplicate edge

📦 Data Source

API có thể lấy dữ liệu từ các bảng hiện tại:

Project
Project_Keyword
Subject_Area
Subject_Category
Topic
Sub_Topic
Keyword
Keyword_Article
Article
Author
Author_Article
Institution
Author_Institution

Relevant schema flow:

Project.subject_area -> Subject_Area.subject_area_id
Subject_Category.subject_area_id -> Subject_Area.subject_area_id
Article.primary_topic -> Topic.topic_id
Topic.subject_category_id -> Subject_Category.subject_category_id
Sub_Topic.article_id -> Article.article_id
Sub_Topic.topic_id -> Topic.topic_id
Keyword_Article.article_id -> Article.article_id
Keyword_Article.keyword_id -> Keyword.keyword_id
Project_Keyword.keyword_id -> Keyword.keyword_id
Author_Article.article_id -> Article.article_id
Author_Article.author_id -> Author.author_id
Author -> Author_Institution -> Institution

🧮 Suggested Query Logic

1. Resolve project scope

project_id
-> subject_area
-> subject_category_ids
-> keyword_ids

2. Filter related articles

Article match nếu thỏa mãn ít nhất một trong các điều kiện:

Article.primary_topic thuộc subject_category_ids
OR Article có Sub_Topic thuộc subject_category_ids
OR Article có Keyword_Article thuộc project keyword_ids

3. Build conceptual graph

For each filtered article:
  collect keywords/topics
  create nodes
  create pairwise co-occurrence edges

4. Build collaboration graph

For each filtered article:
  collect authors/institutions
  create nodes
  create pairwise collaboration edges

5. Merge graph

Nếu network_type=all:

nodes = conceptual nodes + collaboration nodes
edges = conceptual edges + collaboration edges

6. Normalize and limit

normalize node size
normalize edge weight
apply limit_nodes
apply min_weight

⚠️ Edge Cases

1. Project not found

Nếu project_id không tồn tại:

{
  "code": 404,
  "message": "Project not found",
  "data": null
}

2. Project has no scope

Nếu project chưa có subject area hoặc keywords:

{
  "code": 200,
  "message": "Fetch network topology successfully",
  "data": {
    "nodes": [],
    "edges": []
  }
}

3. Empty filtered dataset

Nếu filter xong không có bài báo phù hợp:

{
  "code": 200,
  "message": "Fetch network topology successfully",
  "data": {
    "nodes": [],
    "edges": []
  }
}

4. Invalid network_type

Nếu network_type không thuộc:

conceptual
collaboration
all

Response:

{
  "code": 400,
  "message": "Invalid network_type",
  "data": null
}

5. Article has only one node

Nếu một article chỉ có một keyword/topic/author hợp lệ:

  • Vẫn có thể tạo node
  • Không tạo edge từ article đó

6. Missing node label

Nếu label bị thiếu:

Use "Unknown"

hoặc bỏ qua node tùy convention.

Suggested phase 1:

Use "Unknown"

7. Edge references missing node

Nếu edge có from hoặc to không nằm trong selected nodes:

Remove edge

8. Invalid year range

Nếu from_year > to_year:

{
  "code": 400,
  "message": "Invalid year range",
  "data": null
}

9. Invalid limit_nodes

Nếu limit_nodes <= 0 hoặc không phải number:

{
  "code": 400,
  "message": "Invalid limit_nodes",
  "data": null
}

Suggested max:

150

10. Invalid min_weight

Nếu min_weight < 0 hoặc không phải number:

{
  "code": 400,
  "message": "Invalid min_weight",
  "data": null
}

🧪 Acceptance Criteria

  • API nhận được project_id
  • API tự lấy được subject area / subject categories / keywords của project
  • API hỗ trợ network_type=conceptual
  • API hỗ trợ network_type=collaboration
  • API hỗ trợ network_type=all
  • API hỗ trợ filter thêm bằng subject_area
  • API hỗ trợ filter thêm bằng keywords
  • API hỗ trợ filter theo from_yearto_year
  • API hỗ trợ limit_nodes
  • API hỗ trợ min_weight
  • Response có object data.nodes
  • Response có object data.edges
  • Mỗi node có đủ id, label, size
  • Mỗi edge có đủ from, to, weight
  • Node id không null
  • Node label không null
  • Node size luôn là number
  • Edge fromto đều tồn tại trong nodes
  • Edge from !== to
  • Edge weight luôn là number
  • Edge weight >= 0
  • Không có NaN trong response
  • Không có duplicate node
  • Không có duplicate edge
  • Không có reverse duplicate edge
  • Empty dataset trả về { nodes: [], edges: [] }
  • FE có thể render network graph trực tiếp không cần transform nhiều
  • Response time với dữ liệu nhỏ dưới 100ms

🧪 Test Cases

TC-01: Fetch all topology by project_id only

Request

GET /analytics/network/topology?project_id=1

Expected

  • API lấy project scope
  • Filter bài báo theo project scope
  • Build conceptual graph
  • Build collaboration graph
  • Merge nodes and edges
  • Response đúng contract

TC-02: Fetch conceptual network only

Request

GET /analytics/network/topology?project_id=1&network_type=conceptual

Expected

  • Nodes là keyword/topic/concept
  • Edges là conceptual proximity/co-occurrence
  • Không trả author/institution nodes nếu không cần

TC-03: Fetch collaboration network only

Request

GET /analytics/network/topology?project_id=1&network_type=collaboration

Expected

  • Nodes là author/institution
  • Edges là quan hệ collaboration
  • Không trả keyword/topic nodes nếu không cần

TC-04: Fetch topology with keywords filter

Request

GET /analytics/network/topology?project_id=1&keywords=LLM,RAG

Expected

  • Chỉ lấy bài báo có keyword liên quan đến LLM hoặc RAG
  • Nodes và edges vẫn hợp lệ
  • Không có edge trỏ tới node không tồn tại

TC-05: Article with multiple keywords

Input

Article có keywords:

LLMs
Ethics
Bias

Expected

Tạo nodes:

LLMs
Ethics
Bias

Tạo edges:

LLMs - Ethics
LLMs - Bias
Ethics - Bias

TC-06: Duplicate conceptual edge

Input

LLMsEthics cùng xuất hiện trong 5 bài.

Expected

Chỉ trả 1 edge:

{
  "from": "kw_ethics",
  "to": "kw_llms",
  "weight": 1
}

Sau khi normalize, weight nằm trong khoảng 0 đến 1.


TC-07: Edge references selected nodes only

Input

Sau khi apply limit_nodes, node n3 bị loại.

Expected

Mọi edge có from=n3 hoặc to=n3 phải bị loại.


TC-08: Invalid network_type

Request

GET /analytics/network/topology?project_id=1&network_type=invalid

Expected

{
  "code": 400,
  "message": "Invalid network_type",
  "data": null
}

TC-09: Project not found

Request

GET /analytics/network/topology?project_id=999

Expected

{
  "code": 404,
  "message": "Project not found",
  "data": null
}

TC-10: Empty filtered dataset

Request

GET /analytics/network/topology?project_id=1&keywords=unknown-keyword

Expected

{
  "code": 200,
  "message": "Fetch network topology successfully",
  "data": {
    "nodes": [],
    "edges": []
  }
}

TC-11: Invalid year range

Request

GET /analytics/network/topology?project_id=1&from_year=2026&to_year=2021

Expected

{
  "code": 400,
  "message": "Invalid year range",
  "data": null
}

TC-12: Invalid limit_nodes

Request

GET /analytics/network/topology?project_id=1&limit_nodes=-1

Expected

{
  "code": 400,
  "message": "Invalid limit_nodes",
  "data": null
}

TC-13: Invalid min_weight

Request

GET /analytics/network/topology?project_id=1&min_weight=-1

Expected

{
  "code": 400,
  "message": "Invalid min_weight",
  "data": null
}

📌 Implementation Notes

Nên tách logic thành các phần riêng:

Controller
  -> validate query params
Service
  -> get project tracking scope
  -> filter related articles
  -> build conceptual nodes and edges
  -> build collaboration nodes and edges
  -> merge graph by network_type
  -> normalize node sizes
  -> normalize edge weights
  -> apply limits
  -> build response contract

Gợi ý function:

getProjectTrackingScope(projectId)
buildArticleScopeFilter(scope, queryParams)
getFilteredArticles(scope, filters)
buildConceptualNodes(filteredArticles)
buildConceptualEdges(filteredArticles)
buildCollaborationNodes(filteredArticles)
buildCollaborationEdges(filteredArticles)
mergeTopologyGraphs(graphs)
normalizeNodeSizes(nodes)
normalizeEdgeWeights(edges)
applyTopologyLimits(nodes, edges, limitNodes, minWeight)
buildNetworkTopologyResponse(nodes, edges)

📦 Suggested Mock Data

const mockNetworkTopology = {
  nodes: [
    {
      id: "kw_llms",
      label: "LLMs",
      size: 20
    },
    {
      id: "kw_ethics",
      label: "Ethics",
      size: 15
    },
    {
      id: "kw_bias",
      label: "Bias",
      size: 18
    }
  ],
  edges: [
    {
      from: "kw_llms",
      to: "kw_ethics",
      weight: 0.82
    },
    {
      from: "kw_llms",
      to: "kw_bias",
      weight: 0.64
    }
  ]
};

🚀 Future Improvements

  • Add node type:
KEYWORD
TOPIC
AUTHOR
INSTITUTION
COUNTRY
  • Add edge type:
CONCEPTUAL_PROXIMITY
CO_OCCURRENCE
CO_AUTHORSHIP
AFFILIATION
  • Add node color by type
  • Add edge label
  • Add fixed x/y coordinates for deterministic layout
  • Add community / cluster detection
  • Add graph density score
  • Add centrality metrics:
    • degree centrality
    • betweenness centrality
    • closeness centrality
  • Add metric_type=co_occurrence|citation_weighted|semantic_similarity
  • Add Redis caching:
analytics:network:topology:{project_id}:{network_type}:{subject_area}:{keywords}:{from_year}:{to_year}:{limit_nodes}:{min_weight}
  • Optimize query with indexes on:
    • project_id
    • publication_year
    • article_id
    • author_id
    • keyword_id
    • topic_id
    • subject_category_id

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    Status
    Todo

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions