🎯 Goal
Implement API GET /analytics/matrix/productivity để trả về dữ liệu điểm tọa độ cho biểu đồ Author Productivity vs Impact Matrix.
API này dùng để biểu thị tương quan giữa:
- Productivity: năng suất xuất bản của tác giả
- Impact: chỉ số tác động của tác giả, đại diện bằng
hIndex
Biểu đồ sẽ giúp FE render scatter/bubble chart với:
X-axis: Yearly Output
Y-axis: H-Index
API cần nhận project_id, sau đó dựa trên scope mà project đang theo dõi như:
subject_area
subject_category
keywords
để lọc dữ liệu bài báo trước khi tính productivity của từng tác giả.
🖼️ UI Mapping
Khối UI:
Author Productivity vs Impact Matrix
Chart type:
Scatter chart / Matrix chart
Trục tọa độ:
X-axis: Yearly Output
Y-axis: H-Index
Mỗi điểm trên chart đại diện cho một tác giả.
📡 Endpoint
GET /analytics/matrix/productivity
📥 Query Parameters
| Param |
Type |
Required |
Description |
project_id |
string / number |
Yes |
ID của project cần lấy dữ liệu matrix |
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 |
number |
No |
Số lượng author points muốn lấy. Default: 50 |
✅ Example Requests
1. Fetch matrix points by project only
GET /analytics/matrix/productivity?project_id=PROJECT_ID
2. Fetch matrix points with subject area filter
GET /analytics/matrix/productivity?project_id=PROJECT_ID&subject_area=Computer Science
3. Fetch matrix points with keywords filter
GET /analytics/matrix/productivity?project_id=PROJECT_ID&keywords=AI,Machine Learning,RAG
4. Fetch matrix points with year range
GET /analytics/matrix/productivity?project_id=PROJECT_ID&from_year=2021&to_year=2026
5. Fetch limited matrix points
GET /analytics/matrix/productivity?project_id=PROJECT_ID&limit=30
🧾 Response Contract
{
"code": 200,
"message": "Fetch matrix points successfully",
"data": [
{
"authorId": "auth_01",
"yearlyOutput": 12,
"hIndex": 35
}
]
}
🧠 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 matrix points.
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 điểm matrix trên các tác giả 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/productivity?project_id=1&subject_area=AI&keywords=LLM,RAG
Kết quả chỉ tính các tác giả 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. Author productivity calculation
yearlyOutput thể hiện năng suất xuất bản của tác giả.
Default rule
Nếu không truyền from_year và to_year:
yearlyOutput = số bài báo của author trong năm mới nhất có dữ liệu
Year range rule
Nếu có from_year và to_year:
yearlyOutput = round(totalArticlesInRange / numberOfYears)
Trong đó:
numberOfYears = to_year - from_year + 1
Ví dụ:
from_year = 2021
to_year = 2023
totalArticlesInRange = 36
numberOfYears = 3
yearlyOutput = 36 / 3 = 12
4. H-Index calculation
hIndex lấy từ bảng Author.h_index nếu đã có sẵn.
Nếu Author.h_index null:
Phase 1 ưu tiên dùng field Author.h_index có sẵn. Không cần tự tính lại H-index từ citation distribution.
5. Ranking / sorting rule
Để dữ liệu matrix dễ đọc, API nên sort theo tác giả có ảnh hưởng cao hơn trước.
Suggested sort:
ORDER BY hIndex DESC, yearlyOutput DESC
Sau đó apply limit.
Default:
Max suggested:
6. Data validation rule
Mỗi item trả về phải đảm bảo:
authorId không null
yearlyOutput là number
hIndex là number
- Không có
NaN
- Không có giá trị âm
- Không trả author không có bài báo trong filtered dataset
📦 Data Source
API có thể lấy dữ liệu từ các bảng hiện tại:
Author
Author_Article
Article
Keyword_Article
Topic
Sub_Topic
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 SQL 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. Aggregate author metrics
Group theo author:
authorId = Author.author_id
yearlyOutput = count(distinct Article.article_id) theo rule năm
hIndex = Author.h_index
⚠️ 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 matrix points successfully",
"data": []
}
3. Empty filtered dataset
Nếu filter xong không có bài báo phù hợp:
{
"code": 200,
"message": "Fetch matrix points successfully",
"data": []
}
4. Author has no h_index
Nếu author không có h_index:
{
"authorId": "auth_01",
"yearlyOutput": 12,
"hIndex": 0
}
5. Invalid year range
Nếu from_year > to_year:
{
"code": 400,
"message": "Invalid year range",
"data": null
}
6. Invalid limit
Nếu limit <= 0 hoặc không phải number:
{
"code": 400,
"message": "Invalid limit",
"data": null
}
Nếu limit > 200, API nên cap về 200 hoặc trả lỗi tùy convention BE.
Suggested phase 1:
🧪 Acceptance Criteria
🧪 Test Cases
TC-01: Fetch matrix points by project_id only
Request
GET /analytics/matrix/productivity?project_id=1
Expected
- API lấy project scope
- Filter bài báo theo project scope
- Group theo author
- Trả về
authorId, yearlyOutput, hIndex
- Data không có null hoặc NaN
TC-02: Fetch matrix points with subject_area filter
Request
GET /analytics/matrix/productivity?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 points vẫn đúng contract
TC-03: Fetch matrix points with keywords filter
Request
GET /analytics/matrix/productivity?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ó yearlyOutput/hIndex null hoặc NaN
TC-04: Fetch matrix points with year range
Request
GET /analytics/matrix/productivity?project_id=1&from_year=2021&to_year=2023
Expected
Nếu author có 36 bài trong giai đoạn 2021-2023:
{
"authorId": "auth_01",
"yearlyOutput": 12,
"hIndex": 35
}
TC-05: Author has no h_index
Input
Author có bài báo phù hợp nhưng h_index = null.
Expected
{
"authorId": "auth_01",
"yearlyOutput": 12,
"hIndex": 0
}
TC-06: Project not found
Request
GET /analytics/matrix/productivity?project_id=999
Expected
{
"code": 404,
"message": "Project not found",
"data": null
}
TC-07: Empty filtered dataset
Request
GET /analytics/matrix/productivity?project_id=1&keywords=unknown-keyword
Expected
{
"code": 200,
"message": "Fetch matrix points successfully",
"data": []
}
TC-08: Invalid year range
Request
GET /analytics/matrix/productivity?project_id=1&from_year=2026&to_year=2021
Expected
{
"code": 400,
"message": "Invalid year range",
"data": null
}
TC-09: Invalid limit
Request
GET /analytics/matrix/productivity?project_id=1&limit=-1
Expected
{
"code": 400,
"message": "Invalid limit",
"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
-> aggregate author productivity
-> attach author h_index
-> normalize response
-> build response contract
Gợi ý function:
getProjectTrackingScope(projectId)
buildArticleScopeFilter(scope, queryParams)
getAuthorProductivityMetrics(filters)
calculateYearlyOutput(totalArticles, fromYear, toYear)
normalizeAuthorMatrixPoints(items)
buildProductivityMatrixResponse(points)
📦 Suggested Mock Data
const mockAuthorProductivityMatrix = [
{
authorId: "auth_01",
yearlyOutput: 12,
hIndex: 35
},
{
authorId: "auth_02",
yearlyOutput: 8,
hIndex: 28
},
{
authorId: "auth_03",
yearlyOutput: 15,
hIndex: 42
}
];
🚀 Future Improvements
- Add author display name for tooltip
- Add author avatar URL
- Add total citation count
- Add bubble size based on citation count
- Add quadrants classification:
- High Productivity / High Impact
- High Productivity / Low Impact
- Low Productivity / High Impact
- Low Productivity / Low Impact
- Add
metric_type=h_index|citation_count|impact_score
- Add Redis caching:
analytics:matrix:productivity:{project_id}:{subject_area}:{keywords}:{from_year}:{to_year}:{limit}
- Optimize query with indexes on:
project_id
publication_year
author_id
article_id
keyword_id
subject_category_id
🎯 Goal
Implement API
GET /analytics/matrix/productivityđể trả về dữ liệu điểm tọa độ cho biểu đồ Author Productivity vs Impact Matrix.API này dùng để biểu thị tương quan giữa:
hIndexBiểu đồ sẽ giúp FE render scatter/bubble chart với:
API cần nhận
project_id, sau đó dựa trên scope mà project đang theo dõi như:subject_areasubject_categorykeywordsđể lọc dữ liệu bài báo trước khi tính productivity của từng tác giả.
🖼️ UI Mapping
Khối UI:
Chart type:
Trục tọa độ:
Mỗi điểm trên chart đại diện cho một tác giả.
📡 Endpoint
📥 Query Parameters
project_idsubject_areakeywordsfrom_yearto_yearlimit50✅ Example Requests
1. Fetch matrix points by project only
2. Fetch matrix points with subject area filter
3. Fetch matrix points with keywords filter
4. Fetch matrix points with year range
5. Fetch limited matrix points
🧾 Response Contract
{ "code": 200, "message": "Fetch matrix points successfully", "data": [ { "authorId": "auth_01", "yearlyOutput": 12, "hIndex": 35 } ] }🧠 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 matrix points.
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 điểm matrix trên các tác giả 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 các tác giả có bài báo:
AILLMhoặcRAG3. Author productivity calculation
yearlyOutputthể hiện năng suất xuất bản của tác giả.Default rule
Nếu không truyền
from_yearvàto_year:Year range rule
Nếu có
from_yearvàto_year:Trong đó:
Ví dụ:
4. H-Index calculation
hIndexlấy từ bảngAuthor.h_indexnếu đã có sẵn.Nếu
Author.h_indexnull:5. Ranking / sorting rule
Để dữ liệu matrix dễ đọc, API nên sort theo tác giả có ảnh hưởng cao hơn trước.
Suggested sort:
Sau đó apply
limit.Default:
Max suggested:
6. Data validation rule
Mỗi item trả về phải đảm bảo:
authorIdkhông nullyearlyOutputlà numberhIndexlà numberNaN📦 Data Source
API có thể lấy dữ liệu từ các bảng hiện tại:
Relevant schema flow:
🧮 Suggested SQL 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. Aggregate author metrics
Group theo author:
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 matrix points successfully", "data": [] }3. Empty filtered dataset
Nếu filter xong không có bài báo phù hợp:
{ "code": 200, "message": "Fetch matrix points successfully", "data": [] }4. Author has no h_index
Nếu author không có
h_index:{ "authorId": "auth_01", "yearlyOutput": 12, "hIndex": 0 }5. Invalid year range
Nếu
from_year > to_year:{ "code": 400, "message": "Invalid year range", "data": null }6. Invalid limit
Nếu
limit <= 0hoặc không phải number:{ "code": 400, "message": "Invalid limit", "data": null }Nếu
limit > 200, API nên cap về200hoặc trả lỗi tùy convention BE.Suggested phase 1:
🧪 Acceptance Criteria
project_idsubject_areakeywordsfrom_yearvàto_yearlimitauthorId,yearlyOutput,hIndexauthorIdkhông nullyearlyOutputluôn là numberhIndexluôn là numberNaNtrong response100ms🧪 Test Cases
TC-01: Fetch matrix points by project_id only
Request
Expected
authorId,yearlyOutput,hIndexTC-02: Fetch matrix points with subject_area filter
Request
Expected
TC-03: Fetch matrix points with keywords filter
Request
Expected
LLMhoặcRAGTC-04: Fetch matrix points with year range
Request
Expected
Nếu author có 36 bài trong giai đoạn 2021-2023:
{ "authorId": "auth_01", "yearlyOutput": 12, "hIndex": 35 }TC-05: Author has no h_index
Input
Author có bài báo phù hợp nhưng
h_index = null.Expected
{ "authorId": "auth_01", "yearlyOutput": 12, "hIndex": 0 }TC-06: Project not found
Request
Expected
{ "code": 404, "message": "Project not found", "data": null }TC-07: Empty filtered dataset
Request
Expected
{ "code": 200, "message": "Fetch matrix points successfully", "data": [] }TC-08: Invalid year range
Request
Expected
{ "code": 400, "message": "Invalid year range", "data": null }TC-09: Invalid limit
Request
Expected
{ "code": 400, "message": "Invalid limit", "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
metric_type=h_index|citation_count|impact_scoreanalytics:matrix:productivity:{project_id}:{subject_area}:{keywords}:{from_year}:{to_year}:{limit}project_idpublication_yearauthor_idarticle_idkeyword_idsubject_category_id