Skip to content

BACKEND ISSUE 18 — [FEATURE] Implement Country Collaboration Chord Topology API With Project-Based Filters #36

Description

@LeDuyCoder

🎯 Goal

Implement API GET /analytics/network/chord để trả về dữ liệu liên kết hợp tác nghiên cứu xuyên quốc gia giữa các quốc gia / khu vực nghiên cứu lớn.

API này phục vụ biểu đồ Country Collaboration Chord dạng Chord Diagram, hiển thị các dây nối giữa các quốc gia dựa trên mức độ hợp tác học thuật.

Mục tiêu là cung cấp danh sách connection pair gồm:

  • source: quốc gia nguồn
  • target: quốc gia đích
  • coAuthorshipValue: cường độ hợp tác, thường tính bằng số bài báo đồng tác giả xuyên quốc gia

🖼️ UI Mapping

UI Block

Country Collaboration Chord

Chart Type

Chord Diagram / Circular Network Diagram

Description

Biểu đồ vòng tròn hiển thị quan hệ trao đổi học thuật xuyên biên giới giữa các cường quốc nghiên cứu.

Ví dụ:

UNITED STATES <-> CHINA
CHINA <-> JAPAN
EU <-> UNITED STATES

Mỗi dây nối thể hiện mức độ hợp tác đồng tác giả giữa 2 quốc gia.


📡 Endpoint

GET /analytics/network/chord

📥 Query Parameters

Param Type Required Description
project_id string / number Yes ID của project cần lấy country collaboration chord
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_countries number No Số lượng quốc gia tối đa. Default: 10
min_value number No Chỉ lấy connection có coAuthorshipValue tối thiểu. Default: 1

✅ Example Requests

1. Fetch chord data by project only

GET /analytics/network/chord?project_id=PROJECT_ID

2. Fetch chord data with subject area filter

GET /analytics/network/chord?project_id=PROJECT_ID&subject_area=Computer Science

3. Fetch chord data with keywords filter

GET /analytics/network/chord?project_id=PROJECT_ID&keywords=AI,Machine Learning,RAG

4. Fetch chord data with year range

GET /analytics/network/chord?project_id=PROJECT_ID&from_year=2021&to_year=2026

5. Fetch chord data with limits

GET /analytics/network/chord?project_id=PROJECT_ID&limit_countries=8&min_value=5

🧾 Response Contract

{
  "code": 200,
  "message": "Fetch collaboration chord successfully",
  "data": [
    {
      "source": "UNITED STATES",
      "target": "CHINA",
      "coAuthorshipValue": 450
    },
    {
      "source": "CHINA",
      "target": "JAPAN",
      "coAuthorshipValue": 210
    }
  ]
}

📌 Response Field Explanation

Field Type Description
source string Tên quốc gia / khu vực nguồn
target string Tên quốc gia / khu vực đích
coAuthorshipValue number Số lượng bài báo đồng tác giả hoặc cường độ hợp tác giữa 2 quốc gia

🧠 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 country collaboration 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ỉ tính collaboration chord trên các bài báo 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/chord?project_id=1&subject_area=AI&keywords=LLM,RAG

Kết quả chỉ tính collaboration chord trên các bài báo:

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

3. Country collaboration definition

Một connection giữa 2 quốc gia được tạo khi cùng một bài báo có tác giả / tổ chức đến từ ít nhất 2 quốc gia khác nhau.

Ví dụ:

Article A:
- Author 1 affiliation: UNITED STATES
- Author 2 affiliation: CHINA
- Author 3 affiliation: JAPAN

Tạo các pair:

UNITED STATES - CHINA
UNITED STATES - JAPAN
CHINA - JAPAN

Mỗi pair tăng:

coAuthorshipValue += 1

4. Country source

Country nên lấy từ dữ liệu affiliation / institution của tác giả.

Suggested sources:

Institution.country
Author_Affiliation.country
Article_Affiliation.country

Important: Country collaboration không nên lấy từ Publisher hoặc Journal, vì publisher/journal không đại diện cho quốc gia hợp tác nghiên cứu của tác giả.

Nếu chưa có institution / affiliation country data:

  • Phase 1 có thể trả mock data
  • Hoặc trả data: []
  • Tạo follow-up task bổ sung Institution/Affiliation/Country schema

5. Country normalization

API cần normalize tên quốc gia để tránh duplicate.

Ví dụ mapping:

Raw Value Normalized Value
USA UNITED STATES
US UNITED STATES
United States of America UNITED STATES
PR China CHINA
People's Republic of China CHINA
Japan JAPAN
European Union EU

Suggested phase 1:

Return uppercase country names

6. EU handling

Nếu data có quốc gia cụ thể thuộc EU như Germany, France, Netherlands, Italy, Spain, có 2 option:

Option A: Keep country-level data

GERMANY
FRANCE
NETHERLANDS

Option B: Group EU countries into EU

GERMANY -> EU
FRANCE -> EU
NETHERLANDS -> EU

Suggested phase 1:

Keep country-level data unless UI explicitly requires EU grouping.

Nếu UI mock có EU, có thể thêm query param sau này:

group_eu=true

7. Pair canonicalization

Để tránh duplicate edge 2 chiều:

UNITED STATES -> CHINA
CHINA -> UNITED STATES

phải được merge thành một pair duy nhất.

Suggested rule:

source = alphabetically smaller country name
target = alphabetically larger country name

Hoặc giữ theo predefined order nếu UI cần:

UNITED STATES, CHINA, JAPAN, EU

Yêu cầu:

No duplicate reverse pairs.

8. coAuthorshipValue calculation

Suggested phase 1:

coAuthorshipValue = count(distinct article_id) for each country pair

Nếu một article có nhiều authors từ cùng 2 quốc gia, vẫn chỉ tính 1 cho pair đó trong article.

Ví dụ:

Article A:
- 3 authors from UNITED STATES
- 2 authors from CHINA

Pair:

UNITED STATES - CHINA += 1

Không tính thành 6.


9. Country limiting rule

Để FE render chord diagram không quá rối:

  • Default limit_countries = 10
  • Suggested max limit_countries = 30

Suggested logic:

1. Tính tổng collaboration value của từng country
2. Chọn top N countries
3. Chỉ giữ pair có cả source và target trong top countries
4. Apply min_value
5. Sort by coAuthorshipValue DESC

10. Data validation rule

Mỗi item trả về phải đảm bảo:

  • source không null
  • target không null
  • source !== target
  • coAuthorshipValue là number
  • coAuthorshipValue > 0
  • Không có duplicate pair
  • Không có reverse duplicate pair
  • Không có NaN

📦 Data Source

API cần dữ liệu bài báo, tác giả và quốc gia affiliation.

Có thể dùng các bảng hiện tại nếu có:

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

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
Institution.country -> Country name

🧮 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. Get countries per article

For each filtered article:

article_id -> authors -> institutions -> countries

Deduplicate countries per article:

countries = unique countries for that article

4. Build country pairs

Nếu một article có countries:

UNITED STATES, CHINA, JAPAN

Tạo pairs:

UNITED STATES - CHINA
UNITED STATES - JAPAN
CHINA - JAPAN

5. Aggregate co-authorship value

coAuthorshipValue = count distinct article_id per country pair

6. Apply country limit and min value

limit_countries
min_value

7. Build response

{
  "source": "UNITED STATES",
  "target": "CHINA",
  "coAuthorshipValue": 450
}

⚠️ 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 collaboration chord successfully",
  "data": []
}

3. Empty filtered dataset

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

{
  "code": 200,
  "message": "Fetch collaboration chord successfully",
  "data": []
}

4. Article has authors from one country only

Nếu bài báo chỉ có tác giả từ một quốc gia:

Không tạo chord pair cho bài đó

5. Article has missing country

Nếu author/institution thiếu country:

Ignore missing country

Nếu article sau khi ignore chỉ còn 1 country:

Không tạo chord pair

6. No country / affiliation data available

Nếu hệ thống chưa có country hoặc affiliation data:

{
  "code": 200,
  "message": "Fetch collaboration chord successfully",
  "data": []
}

Tạo follow-up task bổ sung schema:

Institution / Affiliation / Country

7. Invalid year range

Nếu from_year > to_year:

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

8. Invalid limit_countries

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

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

Suggested max:

30

9. Invalid min_value

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

{
  "code": 400,
  "message": "Invalid min_value",
  "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ợ 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_countries
  • API hỗ trợ min_value
  • Response trả về array data
  • Mỗi item có đủ source, target, coAuthorshipValue
  • source không null
  • target không null
  • source !== target
  • coAuthorshipValue luôn là number
  • coAuthorshipValue > 0
  • Không có NaN trong response
  • Không có duplicate pair
  • Không có reverse duplicate pair
  • Country names được normalize consistent
  • Empty dataset trả về array rỗng, không làm API lỗi
  • FE có thể render Chord Diagram 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 country collaboration chord by project_id only

Request

GET /analytics/network/chord?project_id=1

Expected

  • API lấy project scope
  • Filter bài báo theo project scope
  • Lấy country từ author affiliation / institution
  • Build country pairs
  • Response đúng contract

TC-02: Fetch chord with subject_area filter

Request

GET /analytics/network/chord?project_id=1&subject_area=Artificial Intelligence

Expected

  • Chỉ lấy bài báo thuộc subject area được truyền vào
  • Không trả dữ liệu ngoài project scope
  • Pairs vẫn đúng contract

TC-03: Fetch chord with keywords filter

Request

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

Expected

  • Chỉ lấy bài báo có keyword match với LLM hoặc RAG
  • Không có item null
  • Không có coAuthorshipValue null hoặc NaN

TC-04: Fetch chord with year range

Request

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

Expected

  • Chỉ lấy bài báo trong khoảng năm 2021 đến 2026
  • Response đúng contract

TC-05: Article with three countries

Input

Article có countries:

UNITED STATES
CHINA
JAPAN

Expected

Tạo 3 pairs:

[
  {
    "source": "CHINA",
    "target": "JAPAN",
    "coAuthorshipValue": 1
  },
  {
    "source": "CHINA",
    "target": "UNITED STATES",
    "coAuthorshipValue": 1
  },
  {
    "source": "JAPAN",
    "target": "UNITED STATES",
    "coAuthorshipValue": 1
  }
]

TC-06: Duplicate country pair across multiple articles

Input

UNITED STATESCHINA cùng xuất hiện trong 3 bài báo.

Expected

Chỉ trả 1 pair:

{
  "source": "CHINA",
  "target": "UNITED STATES",
  "coAuthorshipValue": 3
}

TC-07: Article has one country only

Input

Article chỉ có authors từ:

UNITED STATES

Expected

  • Không tạo pair
  • API không crash

TC-08: Missing country data

Input

Article có một số author thiếu country.

Expected

  • Ignore missing country
  • Nếu còn ít nhất 2 country hợp lệ thì tạo pair
  • Nếu còn dưới 2 country thì không tạo pair

TC-09: Project not found

Request

GET /analytics/network/chord?project_id=999

Expected

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

TC-10: Empty filtered dataset

Request

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

Expected

{
  "code": 200,
  "message": "Fetch collaboration chord successfully",
  "data": []
}

TC-11: Invalid year range

Request

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

Expected

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

TC-12: Invalid limit_countries

Request

GET /analytics/network/chord?project_id=1&limit_countries=-1

Expected

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

TC-13: Invalid min_value

Request

GET /analytics/network/chord?project_id=1&min_value=-1

Expected

{
  "code": 400,
  "message": "Invalid min_value",
  "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
  -> collect countries per article
  -> build country pairs
  -> aggregate co-authorship values
  -> normalize country names
  -> apply country limit and min value
  -> build response contract

Gợi ý function:

getProjectTrackingScope(projectId)
buildArticleScopeFilter(scope, queryParams)
getFilteredArticles(scope, filters)
getCountriesByArticle(articleIds)
normalizeCountryName(rawCountry)
buildCountryPairs(countriesByArticle)
aggregateCountryPairs(pairs)
applyChordLimits(items, limitCountries, minValue)
buildCountryChordResponse(items)

📦 Suggested Mock Data

const mockCountryCollaborationChord = [
  {
    source: "UNITED STATES",
    target: "CHINA",
    coAuthorshipValue: 450
  },
  {
    source: "CHINA",
    target: "JAPAN",
    coAuthorshipValue: 210
  },
  {
    source: "UNITED STATES",
    target: "EU",
    coAuthorshipValue: 180
  },
  {
    source: "JAPAN",
    target: "EU",
    coAuthorshipValue: 120
  }
];

🚀 Future Improvements

  • Add sourceCode and targetCode using ISO Alpha-2 country codes
  • Add sourceRegion and targetRegion
  • Add group_eu=true
  • Add normalizedValue for chord thickness
  • Add percentage share per connection
  • Add period object
  • Add country-level total collaboration count
  • Add Redis caching:
analytics:network:chord:{project_id}:{subject_area}:{keywords}:{from_year}:{to_year}:{limit_countries}:{min_value}
  • Optimize query with indexes on:
    • project_id
    • publication_year
    • article_id
    • author_id
    • institution_id
    • country
    • keyword_id
    • subject_category_id

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

Status
Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions