Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ AI-powered research workflow: paper discovery → LLM analysis → scholar track
<summary><b>Web Dashboard</b></summary>
<br>

Current dashboard layout focused on the active research question, the workflow console, and decision-critical alerts.

![Dashboard](asset/ui/dashboard.png)

| Research Workspace | AgentSwarm Studio |
Expand Down
Binary file modified asset/ui/dashboard.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
38 changes: 34 additions & 4 deletions src/paperbot/api/routes/research.py
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,36 @@ def _run() -> None:
background_tasks.add_task(_run)


def _normalize_deadline_match_terms(values: List[Any]) -> Set[str]:
terms: Set[str] = set()
for value in values:
normalized = str(value or "").strip().lower()
if not normalized:
continue
terms.add(normalized)
for token in re.split(r"[^a-z0-9]+", normalized):
token = token.strip()
if len(token) >= 2:
terms.add(token)
return terms


def _collect_track_deadline_terms(track: Dict[str, Any]) -> Set[str]:
values: List[Any] = []
for key in ("keywords", "methods", "venues"):
values.extend(track.get(key) or [])
return _normalize_deadline_match_terms(values)


def _collect_conference_deadline_terms(item: Dict[str, Any]) -> Set[str]:
values: List[Any] = list(item.get("keywords") or [])
values.append(item.get("field") or "")
name = re.sub(r"\b20\d{2}\b", "", str(item.get("name") or ""), flags=re.IGNORECASE).strip()
if name:
values.append(name)
return _normalize_deadline_match_terms(values)


class TrackCreateRequest(BaseModel):
user_id: str = "default"
name: str = Field(..., min_length=1, max_length=128)
Expand Down Expand Up @@ -303,9 +333,7 @@ def get_deadline_radar(
track_id = int(track.get("id") or 0)
if track_id <= 0:
continue
tokens = {
str(term).strip().lower() for term in (track.get("keywords") or []) if str(term).strip()
}
tokens = _collect_track_deadline_terms(track)
track_tokens[track_id] = tokens

rows: List[Dict[str, Any]] = []
Expand All @@ -327,19 +355,21 @@ def get_deadline_radar(
conf_keywords = {
str(k).strip().lower() for k in (item.get("keywords") or []) if str(k).strip()
}
conf_terms = _collect_conference_deadline_terms(item)

matched_tracks: List[Dict[str, Any]] = []
for track in tracks:
track_id = int(track.get("id") or 0)
if track_id <= 0:
continue
overlap = sorted(conf_keywords & track_tokens.get(track_id, set()))
overlap = sorted(conf_terms & track_tokens.get(track_id, set()))
if overlap:
matched_tracks.append(
{
"track_id": track_id,
"track_name": str(track.get("name") or ""),
"matched_keywords": overlap,
"matched_terms": overlap,
}
)
Comment on lines 355 to 374

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There are a couple of issues here:

  1. The conf_keywords variable is calculated on lines 355-357 but is not used anywhere. It can be removed.
  2. The response dictionary on lines 368-373 includes both matched_keywords and matched_terms with the same value (overlap). This is redundant. Since the frontend is being updated to prefer matched_terms, consider removing the matched_keywords field to keep the API clean. This would be a breaking change, but since this is a feature PR, it might be acceptable.
        conf_terms = _collect_conference_deadline_terms(item)

        matched_tracks: List[Dict[str, Any]] = []
        for track in tracks:
            track_id = int(track.get("id") or 0)
            if track_id <= 0:
                continue
            overlap = sorted(conf_terms & track_tokens.get(track_id, set()))
            if overlap:
                matched_tracks.append(
                    {
                        "track_id": track_id,
                        "track_name": str(track.get("name") or ""),
                        "matched_terms": overlap,
                    }
                )


Expand Down
14 changes: 14 additions & 0 deletions tests/unit/test_research_paper_registry_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,14 @@ def test_deadline_radar_route_returns_workflow_query_and_track_match(tmp_path, m
keywords=["llm", "retrieval"],
activate=True,
)
research_store.create_track(
user_id="u-deadline",
name="acl-track",
keywords=[],
venues=["ACL"],
methods=["retrieval"],
activate=False,
)

monkeypatch.setattr(research_route, "_research_store", research_store)

Expand All @@ -205,3 +213,9 @@ def test_deadline_radar_route_returns_workflow_query_and_track_match(tmp_path, m

matched_any = any(item.get("matched_tracks") for item in payload["items"])
assert matched_any

acl_item = next(item for item in payload["items"] if item["name"] == "ACL 2026")
acl_match = next(
match for match in acl_item["matched_tracks"] if match["track_name"] == "acl-track"
)
assert "acl" in acl_match["matched_terms"]
Loading
Loading