Skip to content

BACKEND ISSUE 11 — [FEATURE] Implement Author Productivity vs Impact Matrix API With Project-Based Filters #27

Description

@LeDuyCoder

🎯 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:

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/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_yearto_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_yearto_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:

hIndex = 0

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:

limit = 50

Max suggested:

limit = 200

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:

max limit = 200

🧪 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
  • Response trả về array data
  • Mỗi item có đủ authorId, yearlyOutput, hIndex
  • authorId không null
  • yearlyOutput luôn là number
  • hIndex luôn là number
  • Không có NaN trong response
  • Không có giá trị âm trong response
  • Empty dataset trả về array rỗng, không làm API lỗi
  • FE có thể render scatter chart trực tiếp không cần transform thêm
  • Response time với dữ liệu nhỏ dưới 100ms

🧪 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

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Projects

Status
Todo

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions