🎯 Goal
Implement API GET /analytics/matrix/intensity để trả về dữ liệu cho bảng heatmap Topic Intensity Matrix.
API này dùng để biểu thị mức độ tập trung nghiên cứu của từng tác giả hoặc tổ chức vào từng chủ đề nghiên cứu.
Biểu đồ sẽ giúp FE render dạng:
Rows: Authors / Institutions
Columns: Topics
Cells: Intensity score
Trong đó intensity là giá trị từ 0 đến 1, dùng để quyết định độ đậm nhạt của ô màu cam trên heatmap.
🖼️ UI Mapping
Khối UI:
Chart type:
Heatmap / Matrix table / Color grid
Visual style:
- Mỗi row đại diện cho một tác giả hoặc tổ chức
- Mỗi column đại diện cho một topic
- Mỗi cell đại diện cho mức độ tập trung nghiên cứu
- Ô có
intensity càng cao thì màu cam càng đậm
- Ô có
intensity thấp thì màu cam nhạt hoặc gần trắng
Example:
AI/ML BIO-MED PHYSICS
H. Vance 0.9 0.2 0.1
M. Lee 0.4 0.8 0.3
📡 Endpoint
GET /analytics/matrix/intensity
📥 Query Parameters
| Param |
Type |
Required |
Description |
project_id |
string / number |
Yes |
ID của project cần lấy topic intensity matrix |
row_type |
string |
No |
Loại row muốn hiển thị. Values: author, institution. Default: author |
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_rows |
number |
No |
Số lượng row tối đa. Default: 10 |
limit_topics |
number |
No |
Số lượng topic tối đa. Default: 8 |
✅ Example Requests
1. Fetch author-topic intensity matrix by project only
GET /analytics/matrix/intensity?project_id=PROJECT_ID
2. Fetch institution-topic intensity matrix
GET /analytics/matrix/intensity?project_id=PROJECT_ID&row_type=institution
3. Fetch matrix with subject area filter
GET /analytics/matrix/intensity?project_id=PROJECT_ID&subject_area=Computer Science
4. Fetch matrix with keywords filter
GET /analytics/matrix/intensity?project_id=PROJECT_ID&keywords=AI,Machine Learning,RAG
5. Fetch matrix with year range
GET /analytics/matrix/intensity?project_id=PROJECT_ID&from_year=2021&to_year=2026
6. Fetch matrix with row and topic limits
GET /analytics/matrix/intensity?project_id=PROJECT_ID&limit_rows=12&limit_topics=6
🧾 Response Contract
{
"code": 200,
"message": "Fetch topic intensity matrix successfully",
"data": [
{
"rowName": "H. Vance",
"topic": "AI/ML",
"intensity": 0.9
},
{
"rowName": "H. Vance",
"topic": "BIO-MED",
"intensity": 0.2
}
]
}
📌 Response Field Explanation
| Field |
Type |
Description |
rowName |
string |
Tên tác giả hoặc tổ chức |
topic |
string |
Tên topic / subject category / research area |
intensity |
number |
Mức độ tập trung nghiên cứu, từ 0 đến 1 |
🧠 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 tính topic intensity.
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 matrix trên các bài báo thuộc scope này.
2. Filter priority
Nếu client chỉ truyền:
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:
API sẽ lọc hẹp hơn trong phạm vi project.
Ví dụ:
GET /analytics/matrix/intensity?project_id=1&subject_area=AI&keywords=LLM,RAG
Kết quả chỉ tính topic intensity 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. Row type logic
API hỗ trợ 2 loại row:
row_type = author
Mỗi row là một tác giả.
{
"rowName": "H. Vance",
"topic": "AI/ML",
"intensity": 0.9
}
Dữ liệu lấy từ:
Author -> Author_Article -> Article -> Topic
row_type = institution
Mỗi row là một tổ chức / viện nghiên cứu / trường đại học.
{
"rowName": "Stanford Bio-Dynamics Lab",
"topic": "AI/ML",
"intensity": 0.9
}
Dữ liệu lấy từ affiliation / institution của author hoặc article.
Important: Institution không phải Publisher hoặc Journal. Institution nên đến từ dữ liệu affiliation / organization.
Nếu chưa có Institution/Affiliation data, phase 1 có thể:
- Chỉ hỗ trợ
row_type=author
- Với
row_type=institution, trả data: []
- Tạo follow-up task để bổ sung Institution/Affiliation schema
4. Topic source
Topic có thể lấy từ:
hoặc nếu UI muốn nhóm lớn hơn:
Subject_Category.display_name
Suggested phase 1:
topic = Topic.display_name
Nếu topic quá nhiều, API lấy top topics theo article count hoặc citation count trong project scope.
5. Intensity calculation
intensity thể hiện mức độ tập trung nghiên cứu của một row vào một topic.
Suggested formula:
intensity = topicArticleCountOfRow / maxTopicArticleCountOfThatRow
Trong đó:
| Metric |
Meaning |
topicArticleCountOfRow |
Số bài báo của row trong topic đó |
maxTopicArticleCountOfThatRow |
Số bài báo cao nhất của row trong một topic bất kỳ |
Ví dụ:
Author H. Vance:
AI/ML = 9 articles
BIO-MED = 2 articles
PHYSICS = 1 article
max = 9
AI/ML intensity = 9 / 9 = 1.0
BIO-MED intensity = 2 / 9 = 0.22
PHYSICS intensity = 1 / 9 = 0.11
Kết quả có thể round:
round to 2 decimal places
6. Alternative global normalization
Nếu FE muốn so sánh cường độ giữa toàn bộ authors/institutions thay vì trong từng row, có thể dùng global normalization:
intensity = topicArticleCountOfRow / maxTopicArticleCountAcrossAllRows
Suggested phase 1:
Use row-based normalization
Vì row-based normalization giúp FE dễ thấy mỗi tác giả/tổ chức tập trung mạnh vào topic nào.
7. Missing cells
Để FE render heatmap ổn định, API nên trả đủ combination giữa selected rows và selected topics.
Nếu một row không có bài trong topic đó:
{
"rowName": "H. Vance",
"topic": "PHYSICS",
"intensity": 0
}
Rule:
Every selected row should have one item for every selected topic.
Ví dụ có 3 rows và 4 topics:
8. Sorting rule
Suggested sorting:
Rows
Sort rows theo tổng số bài báo trong project scope giảm dần:
ORDER BY totalArticles DESC
Topics
Sort topics theo tổng số bài báo trong project scope giảm dần:
ORDER BY totalArticles DESC
Sau đó apply:
📦 Data Source
API có thể lấy dữ liệu từ các bảng hiện tại:
Author
Author_Article
Article
Topic
Sub_Topic
Keyword
Keyword_Article
Subject_Category
Subject_Area
Project
Project_Keyword
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
Author_Article.author_id -> Author.author_id
Author_Article.article_id -> Article.article_id
Keyword_Article.article_id -> Article.article_id
Keyword_Article.keyword_id -> Keyword.keyword_id
Project_Keyword.keyword_id -> Keyword.keyword_id
🧮 Suggested Matrix Construction 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 keyword_ids
3. Select top rows
Nếu row_type=author:
Group by Author
Sort by total article count DESC
Limit by limit_rows
Nếu row_type=institution:
Group by Institution
Sort by total article count DESC
Limit by limit_rows
4. Select top topics
Group by Topic
Sort by total article count DESC
Limit by limit_topics
5. Build full matrix
Tạo full combination:
selectedRows x selectedTopics
Mỗi cell:
count articles by row and topic
normalize to intensity 0-1
Nếu không có article:
⚠️ 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 topic intensity matrix successfully",
"data": []
}
3. Empty filtered dataset
Nếu filter xong không có bài báo phù hợp:
{
"code": 200,
"message": "Fetch topic intensity matrix successfully",
"data": []
}
4. Author has no display name
Nếu author thiếu tên:
rowName = "Unknown Author"
5. Institution data unavailable
Nếu row_type=institution nhưng chưa có Institution/Affiliation data:
{
"code": 200,
"message": "Fetch topic intensity matrix successfully",
"data": []
}
6. Invalid row_type
Nếu row_type không thuộc:
Response:
{
"code": 400,
"message": "Invalid row_type",
"data": null
}
7. Invalid year range
Nếu from_year > to_year:
{
"code": 400,
"message": "Invalid year range",
"data": null
}
8. Invalid limit_rows
Nếu limit_rows <= 0 hoặc không phải number:
{
"code": 400,
"message": "Invalid limit_rows",
"data": null
}
Suggested max:
9. Invalid limit_topics
Nếu limit_topics <= 0 hoặc không phải number:
{
"code": 400,
"message": "Invalid limit_topics",
"data": null
}
Suggested max:
🧪 Acceptance Criteria
🧪 Test Cases
TC-01: Fetch author-topic intensity matrix by project_id only
Request
GET /analytics/matrix/intensity?project_id=1
Expected
- API lấy project scope
- Filter bài báo theo project scope
- Chọn top authors
- Chọn top topics
- Build full heatmap matrix
- Response đúng contract
TC-02: Fetch institution-topic intensity matrix
Request
GET /analytics/matrix/intensity?project_id=1&row_type=institution
Expected
Nếu có institution data:
- Mỗi row là institution
- Mỗi column là topic
intensity nằm trong khoảng 0 đến 1
Nếu chưa có institution data:
{
"code": 200,
"message": "Fetch topic intensity matrix successfully",
"data": []
}
TC-03: Fetch matrix with subject_area filter
Request
GET /analytics/matrix/intensity?project_id=1&subject_area=Artificial Intelligence
Expected
- Chỉ lấy dữ liệu thuộc subject area được truyền vào
- Không trả dữ liệu ngoài project scope
- Matrix vẫn đúng contract
TC-04: Fetch matrix with keywords filter
Request
GET /analytics/matrix/intensity?project_id=1&keywords=LLM,RAG
Expected
- Chỉ lấy dữ liệu có keyword match với
LLM hoặc RAG
- Không có item null
- Không có intensity null hoặc NaN
TC-05: Fetch matrix with year range
Request
GET /analytics/matrix/intensity?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
- Không trả data ngoài khoảng năm
- Response đúng contract
TC-06: Full combination matrix
Input
Selected rows:
Selected topics:
Expected
Response phải có:
2 rows * 3 topics = 6 items
Nếu H. Vance không có bài trong PHYSICS:
{
"rowName": "H. Vance",
"topic": "PHYSICS",
"intensity": 0
}
TC-07: Intensity normalization
Input
Author H. Vance có:
AI/ML = 9 articles
BIO-MED = 2 articles
PHYSICS = 1 article
Expected
[
{
"rowName": "H. Vance",
"topic": "AI/ML",
"intensity": 1
},
{
"rowName": "H. Vance",
"topic": "BIO-MED",
"intensity": 0.22
},
{
"rowName": "H. Vance",
"topic": "PHYSICS",
"intensity": 0.11
}
]
TC-08: Project not found
Request
GET /analytics/matrix/intensity?project_id=999
Expected
{
"code": 404,
"message": "Project not found",
"data": null
}
TC-09: Empty filtered dataset
Request
GET /analytics/matrix/intensity?project_id=1&keywords=unknown-keyword
Expected
{
"code": 200,
"message": "Fetch topic intensity matrix successfully",
"data": []
}
TC-10: Invalid row_type
Request
GET /analytics/matrix/intensity?project_id=1&row_type=publisher
Expected
{
"code": 400,
"message": "Invalid row_type",
"data": null
}
TC-11: Invalid year range
Request
GET /analytics/matrix/intensity?project_id=1&from_year=2026&to_year=2021
Expected
{
"code": 400,
"message": "Invalid year range",
"data": null
}
TC-12: Invalid limit_rows
Request
GET /analytics/matrix/intensity?project_id=1&limit_rows=-1
Expected
{
"code": 400,
"message": "Invalid limit_rows",
"data": null
}
TC-13: Invalid limit_topics
Request
GET /analytics/matrix/intensity?project_id=1&limit_topics=-1
Expected
{
"code": 400,
"message": "Invalid limit_topics",
"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
-> select top rows
-> select top topics
-> build matrix cells
-> normalize intensity
-> build response contract
Gợi ý function:
getProjectTrackingScope(projectId)
buildArticleScopeFilter(scope, queryParams)
getTopMatrixRows(rowType, filters, limitRows)
getTopMatrixTopics(filters, limitTopics)
getTopicCountsByRow(rowType, filters)
buildFullMatrix(rows, topics, counts)
normalizeIntensity(matrixItems)
buildTopicIntensityMatrixResponse(matrixItems)
📦 Suggested Mock Data
const mockTopicIntensityMatrix = [
{
rowName: "H. Vance",
topic: "AI/ML",
intensity: 0.9
},
{
rowName: "H. Vance",
topic: "BIO-MED",
intensity: 0.2
},
{
rowName: "M. Lee",
topic: "AI/ML",
intensity: 0.4
},
{
rowName: "M. Lee",
topic: "BIO-MED",
intensity: 0.8
}
];
🚀 Future Improvements
- Add
rowId to response
- Add
rowType to response
- Add
topicId to response
- Add
rawCount to response
- Add
normalization=row|global
- Add
metric_type=article_count|citation_count|impact_score
- Add support for institution rows after Institution/Affiliation schema is available
- Add tooltip metadata for FE
- Add Redis caching:
analytics:matrix:intensity:{project_id}:{row_type}:{subject_area}:{keywords}:{from_year}:{to_year}:{limit_rows}:{limit_topics}
- Optimize query with indexes on:
project_id
publication_year
author_id
article_id
keyword_id
topic_id
subject_category_id
🎯 Goal
Implement API
GET /analytics/matrix/intensityđể trả về dữ liệu cho bảng heatmap Topic Intensity Matrix.API này dùng để biểu thị mức độ tập trung nghiên cứu của từng tác giả hoặc tổ chức vào từng chủ đề nghiên cứu.
Biểu đồ sẽ giúp FE render dạng:
Trong đó
intensitylà giá trị từ0đến1, dùng để quyết định độ đậm nhạt của ô màu cam trên heatmap.🖼️ UI Mapping
Khối UI:
Chart type:
Visual style:
intensitycàng cao thì màu cam càng đậmintensitythấp thì màu cam nhạt hoặc gần trắngExample:
📡 Endpoint
📥 Query Parameters
project_idrow_typeauthor,institution. Default:authorsubject_areakeywordsfrom_yearto_yearlimit_rows10limit_topics8✅ Example Requests
1. Fetch author-topic intensity matrix by project only
2. Fetch institution-topic intensity matrix
3. Fetch matrix with subject area filter
4. Fetch matrix with keywords filter
5. Fetch matrix with year range
6. Fetch matrix with row and topic limits
🧾 Response Contract
{ "code": 200, "message": "Fetch topic intensity matrix successfully", "data": [ { "rowName": "H. Vance", "topic": "AI/ML", "intensity": 0.9 }, { "rowName": "H. Vance", "topic": "BIO-MED", "intensity": 0.2 } ] }📌 Response Field Explanation
rowNametopicintensity0đến1🧠 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:Sau đó dùng scope này để lọc tập bài báo trước khi tính topic intensity.
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 matrix trên các bài báo thuộc scope này.
2. Filter priority
Nếu client chỉ truyền:
project_idAPI 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:
API sẽ lọc hẹp hơn trong phạm vi project.
Ví dụ:
Kết quả chỉ tính topic intensity trên các bài báo:
AILLMhoặcRAG3. Row type logic
API hỗ trợ 2 loại row:
row_type = author
Mỗi row là một tác giả.
{ "rowName": "H. Vance", "topic": "AI/ML", "intensity": 0.9 }Dữ liệu lấy từ:
row_type = institution
Mỗi row là một tổ chức / viện nghiên cứu / trường đại học.
{ "rowName": "Stanford Bio-Dynamics Lab", "topic": "AI/ML", "intensity": 0.9 }Dữ liệu lấy từ affiliation / institution của author hoặc article.
Nếu chưa có Institution/Affiliation data, phase 1 có thể:
row_type=authorrow_type=institution, trảdata: []4. Topic source
Topic có thể lấy từ:
hoặc nếu UI muốn nhóm lớn hơn:
Suggested phase 1:
Nếu topic quá nhiều, API lấy top topics theo article count hoặc citation count trong project scope.
5. Intensity calculation
intensitythể hiện mức độ tập trung nghiên cứu của một row vào một topic.Suggested formula:
Trong đó:
topicArticleCountOfRowmaxTopicArticleCountOfThatRowVí dụ:
Kết quả có thể round:
6. Alternative global normalization
Nếu FE muốn so sánh cường độ giữa toàn bộ authors/institutions thay vì trong từng row, có thể dùng global normalization:
Suggested phase 1:
Vì row-based normalization giúp FE dễ thấy mỗi tác giả/tổ chức tập trung mạnh vào topic nào.
7. Missing cells
Để FE render heatmap ổn định, API nên trả đủ combination giữa selected rows và selected topics.
Nếu một row không có bài trong topic đó:
{ "rowName": "H. Vance", "topic": "PHYSICS", "intensity": 0 }Rule:
Ví dụ có 3 rows và 4 topics:
data.length = 3 * 4 = 128. Sorting rule
Suggested sorting:
Rows
Sort rows theo tổng số bài báo trong project scope giảm dần:
Topics
Sort topics theo tổng số bài báo trong project scope giảm dần:
Sau đó apply:
📦 Data Source
API có thể lấy dữ liệu từ các bảng hiện tại:
Relevant schema flow:
🧮 Suggested Matrix Construction Logic
1. Resolve project scope
2. Filter related articles
Article match nếu thỏa mãn ít nhất một trong các điều kiện:
3. Select top rows
Nếu
row_type=author:Nếu
row_type=institution:4. Select top topics
5. Build full matrix
Tạo full combination:
Mỗi cell:
Nếu không có article:
1. Project not found
Nếu
project_idkhô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 topic intensity matrix successfully", "data": [] }3. Empty filtered dataset
Nếu filter xong không có bài báo phù hợp:
{ "code": 200, "message": "Fetch topic intensity matrix successfully", "data": [] }4. Author has no display name
Nếu author thiếu tên:
5. Institution data unavailable
Nếu
row_type=institutionnhưng chưa có Institution/Affiliation data:{ "code": 200, "message": "Fetch topic intensity matrix successfully", "data": [] }6. Invalid row_type
Nếu
row_typekhông thuộc:Response:
{ "code": 400, "message": "Invalid row_type", "data": null }7. Invalid year range
Nếu
from_year > to_year:{ "code": 400, "message": "Invalid year range", "data": null }8. Invalid limit_rows
Nếu
limit_rows <= 0hoặc không phải number:{ "code": 400, "message": "Invalid limit_rows", "data": null }Suggested max:
9. Invalid limit_topics
Nếu
limit_topics <= 0hoặc không phải number:{ "code": 400, "message": "Invalid limit_topics", "data": null }Suggested max:
🧪 Acceptance Criteria
project_idrow_type=authorrow_type=institutionnếu có institution datasubject_areakeywordsfrom_yearvàto_yearlimit_rowslimit_topicsrowName,topic,intensityrowNamekhông nulltopickhông nullintensityluôn là numberintensitynằm trong khoảng0đến1NaNtrong response100ms🧪 Test Cases
TC-01: Fetch author-topic intensity matrix by project_id only
Request
Expected
TC-02: Fetch institution-topic intensity matrix
Request
Expected
Nếu có institution data:
intensitynằm trong khoảng0đến1Nếu chưa có institution data:
{ "code": 200, "message": "Fetch topic intensity matrix successfully", "data": [] }TC-03: Fetch matrix with subject_area filter
Request
Expected
TC-04: Fetch matrix with keywords filter
Request
Expected
LLMhoặcRAGTC-05: Fetch matrix with year range
Request
Expected
TC-06: Full combination matrix
Input
Selected rows:
Selected topics:
Expected
Response phải có:
2 rows * 3 topics = 6 itemsNếu H. Vance không có bài trong PHYSICS:
{ "rowName": "H. Vance", "topic": "PHYSICS", "intensity": 0 }TC-07: Intensity normalization
Input
Author H. Vance có:
Expected
[ { "rowName": "H. Vance", "topic": "AI/ML", "intensity": 1 }, { "rowName": "H. Vance", "topic": "BIO-MED", "intensity": 0.22 }, { "rowName": "H. Vance", "topic": "PHYSICS", "intensity": 0.11 } ]TC-08: Project not found
Request
Expected
{ "code": 404, "message": "Project not found", "data": null }TC-09: Empty filtered dataset
Request
Expected
{ "code": 200, "message": "Fetch topic intensity matrix successfully", "data": [] }TC-10: Invalid row_type
Request
Expected
{ "code": 400, "message": "Invalid row_type", "data": null }TC-11: Invalid year range
Request
Expected
{ "code": 400, "message": "Invalid year range", "data": null }TC-12: Invalid limit_rows
Request
Expected
{ "code": 400, "message": "Invalid limit_rows", "data": null }TC-13: Invalid limit_topics
Request
Expected
{ "code": 400, "message": "Invalid limit_topics", "data": null }📌 Implementation Notes
Nên tách logic thành các phần riêng:
Gợi ý function:
📦 Suggested Mock Data
🚀 Future Improvements
rowIdto responserowTypeto responsetopicIdto responserawCountto responsenormalization=row|globalmetric_type=article_count|citation_count|impact_scoreanalytics:matrix:intensity:{project_id}:{row_type}:{subject_area}:{keywords}:{from_year}:{to_year}:{limit_rows}:{limit_topics}project_idpublication_yearauthor_idarticle_idkeyword_idtopic_idsubject_category_id