Skip to content

Commit 06c86e3

Browse files
committed
Add hashtags to stats API response
1 parent 5305593 commit 06c86e3

3 files changed

Lines changed: 26 additions & 3 deletions

File tree

api/queries.py

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,18 @@
3737
GROUP BY uid
3838
)"""
3939

40+
_HASHTAG_CTE = """,
41+
user_hashtags AS (
42+
SELECT
43+
st.uid,
44+
ARRAY_AGG(DISTINCT ht.hashtag ORDER BY ht.hashtag) AS hashtags
45+
FROM stats_scope st
46+
JOIN changesets cs ON cs.changeset_id = st.changeset_id
47+
CROSS JOIN LATERAL UNNEST(cs.hashtags) AS ht(hashtag)
48+
WHERE cs.hashtags IS NOT NULL
49+
GROUP BY st.uid
50+
)"""
51+
4052

4153
def _user_stats_sql(*, filter_dates: bool, filter_hashtags: bool, include_tags: bool) -> str:
4254
n = 1
@@ -76,7 +88,7 @@ def _user_stats_sql(*, filter_dates: bool, filter_hashtags: bool, include_tags:
7688
tag_group = ", tpu.tag_stats" if include_tags else ""
7789

7890
return f"""
79-
{scope_cte}{tag_ctes}
91+
{scope_cte}{_HASHTAG_CTE}{tag_ctes}
8092
SELECT
8193
u.uid,
8294
u.username AS name,
@@ -112,11 +124,13 @@ def _user_stats_sql(*, filter_dates: bool, filter_hashtags: bool, include_tags:
112124
) DESC,
113125
u.uid ASC
114126
) AS rank,
127+
COALESCE(uh.hashtags, ARRAY[]::TEXT[]) AS hashtags,
115128
{tag_select}
116129
FROM users u
117130
JOIN stats_scope st ON u.uid = st.uid
131+
LEFT JOIN user_hashtags uh ON uh.uid = u.uid
118132
{tag_join}
119-
GROUP BY u.uid, u.username{tag_group}
133+
GROUP BY u.uid, u.username, uh.hashtags{tag_group}
120134
ORDER BY map_changes DESC, u.uid ASC
121135
LIMIT {limit_param} OFFSET {offset_param}
122136
"""

api/schemas.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
from datetime import datetime
22

3-
from pydantic import BaseModel
3+
from pydantic import BaseModel, Field
44

55

66
class TagValueStats(BaseModel):
@@ -26,6 +26,7 @@ class UserStat(BaseModel):
2626
poi_modify: int
2727
map_changes: int
2828
rank: int
29+
hashtags: list[str] = Field(default_factory=list)
2930
tag_stats: dict[str, dict[str, TagValueStats]] | None = None
3031

3132

tests/test_api.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ async def fake_fetch_user_stats(*, start, end, hashtag, tags, limit, offset):
8787
"poi_modify": 1,
8888
"map_changes": 58,
8989
"rank": 1,
90+
"hashtags": ["#mapathon", "#roads"],
9091
"tag_stats": {"building": {"yes": {"c": 3, "m": 0}}},
9192
}
9293
]
@@ -130,6 +131,7 @@ async def fake_fetch_user_stats(*, start, end, hashtag, tags, limit, offset):
130131
"poi_modify": 1,
131132
"map_changes": 58,
132133
"rank": 1,
134+
"hashtags": ["#mapathon", "#roads"],
133135
"tag_stats": {"building": {"yes": {"c": 3, "m": 0, "len": None}}},
134136
}
135137
],
@@ -171,6 +173,7 @@ async def fake_fetch_user_stats(*, tags, **_kwargs):
171173
"poi_modify": 0,
172174
"map_changes": 0,
173175
"rank": 1,
176+
"hashtags": [],
174177
"tag_stats": None,
175178
}
176179
]
@@ -192,6 +195,7 @@ def test_user_stats_sql_omits_tag_ctes_when_tags_false():
192195
assert "tag_per_user" in sql_with
193196
assert "tag_per_user" not in sql_without
194197
assert "NULL::jsonb AS tag_stats" in sql_without
198+
assert "user_hashtags" in sql_without
195199

196200

197201
def _seed_pg_via_to_psql(fresh_db, populated_db_factory, dsn):
@@ -268,6 +272,8 @@ def test_live_api_stats_default_returns_dicts_not_strings(live_api_client):
268272
assert body["count"] == 2
269273
by_name = {u["name"]: u for u in body["users"]}
270274
assert isinstance(by_name["alice"]["tag_stats"], dict)
275+
assert by_name["alice"]["hashtags"] == ["#mapathon"]
276+
assert by_name["bob"]["hashtags"] == []
271277
assert by_name["alice"]["tag_stats"]["building"]["yes"]["c"] == 5
272278
assert by_name["alice"]["tag_stats"]["building"]["yes"]["m"] == 1
273279
assert by_name["alice"]["tag_stats"]["highway"]["residential"]["len"] == 245.7
@@ -318,6 +324,7 @@ def test_live_api_stats_hashtag_filters_to_matching_changesets(live_api_client):
318324
assert body["hashtag"] == ["#mapathon"]
319325
names = {u["name"] for u in body["users"]}
320326
assert names == {"alice"}
327+
assert body["users"][0]["hashtags"] == ["#mapathon"]
321328

322329

323330
@pytest.mark.network
@@ -448,6 +455,7 @@ def test_user_stats_sql_no_filter_skips_changesets_join():
448455
assert "filtered_changesets" not in sql
449456
assert "JOIN filtered_changesets" not in sql
450457
assert "stats_scope AS (SELECT * FROM changeset_stats)" in sql
458+
assert "LEFT JOIN user_hashtags" in sql
451459

452460

453461
def test_user_stats_sql_filtered_uses_changesets_join():

0 commit comments

Comments
 (0)