Skip to content

Commit 05178ea

Browse files
Merge pull request #56 from NirrWorks/fix/hashtag-case-variants
2 parents 73cdb5b + ecc9d3c commit 05178ea

3 files changed

Lines changed: 30 additions & 4 deletions

File tree

osmsg/catalog.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def recent_user_hashtags(attach, uids: list[int], *, prefixes, frontier, start=N
198198
uid_list = ", ".join(str(int(u)) for u in uids)
199199
ranges = " OR ".join(f"(lower(h) >= {_pg_str(lo)} AND lower(h) < {_pg_str(hi)})" for lo, hi in prefixes)
200200
inner = (
201-
f"SELECT DISTINCT s.uid AS uid, h AS hashtag "
201+
f"SELECT DISTINCT s.uid AS uid, lower(h) AS hashtag "
202202
f"FROM changeset_stats s JOIN changesets c USING (changeset_id), unnest(c.hashtags) AS h "
203203
f"WHERE s.uid IN ({uid_list}) AND {_pg_user_window(frontier, start, end)} AND ({ranges})"
204204
)
@@ -211,7 +211,7 @@ def recent_user_cooccur_hashtags(attach, uids: list[int], *, prefixes, frontier,
211211
uid_list = ", ".join(str(int(u)) for u in uids)
212212
ranges = " OR ".join(f"(lower(x) >= {_pg_str(lo)} AND lower(x) < {_pg_str(hi)})" for lo, hi in prefixes)
213213
inner = (
214-
f"SELECT DISTINCT s.uid AS uid, h AS hashtag "
214+
f"SELECT DISTINCT s.uid AS uid, lower(h) AS hashtag "
215215
f"FROM changeset_stats s JOIN changesets c USING (changeset_id), unnest(c.hashtags) AS h "
216216
f"WHERE s.uid IN ({uid_list}) AND {_pg_user_window(frontier, start, end)} "
217217
f"AND EXISTS (SELECT 1 FROM unnest(c.hashtags) AS x WHERE {ranges})"

osmsg/query.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -338,7 +338,8 @@ def _attach_user_hashtags(
338338
else:
339339
recent_pred = " OR ".join("(lower(x) >= ? AND lower(x) < ?)" for _ in prefixes)
340340
recent = (
341-
f"SELECT uid, h AS hashtag FROM (SELECT uid, UNNEST(hashtags) AS h FROM {s.recent_changesets_rel} "
341+
f"SELECT uid, lower(h) AS hashtag FROM (SELECT uid, UNNEST(hashtags) AS h "
342+
f"FROM {s.recent_changesets_rel} "
342343
f"WHERE created_at >= ?{window_sql} AND uid IN ({ph}) "
343344
f"AND EXISTS (SELECT 1 FROM UNNEST(hashtags) AS t(x) WHERE {recent_pred}))"
344345
)
@@ -592,7 +593,7 @@ def hashtags(
592593
else:
593594
recent_match = " OR ".join("(lower(x) >= ? AND lower(x) < ?)" for _ in prefixes)
594595
parts.append(
595-
f"SELECT h AS hashtag, uid, mc FROM (SELECT c.uid, UNNEST(c.hashtags) AS h, cs.mc AS mc "
596+
f"SELECT lower(h) AS hashtag, uid, mc FROM (SELECT c.uid, UNNEST(c.hashtags) AS h, cs.mc AS mc "
596597
f"FROM {s.recent_changesets_rel} c JOIN (SELECT changeset_id, {map_changes_sum(alias='mc')} "
597598
f"FROM {s.recent_stats_rel} GROUP BY changeset_id) cs USING (changeset_id) "
598599
f"WHERE c.created_at >= ?{window_sql} AND EXISTS "

tests/test_query.py

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,31 @@ def test_cooccurring_hashtags(con, sources):
127127
]
128128

129129

130+
def _tag_written_both_ways(con) -> None:
131+
"""The same hashtag as the two sides really store it: the rollup lowercases, while the base
132+
`changesets` table keeps the case the mapper typed."""
133+
con.execute("INSERT INTO history SELECT '#youthmappers', * EXCLUDE (hashtag) FROM history WHERE changeset_id = 1")
134+
con.execute("UPDATE csets SET hashtags = list_append(hashtags, '#YouthMappers') WHERE changeset_id = 3")
135+
136+
137+
def test_cooccurring_hashtags_merge_case_variants(con, sources):
138+
# Grouping is on the raw string, so a tag written both ways used to come back as two rows, each
139+
# holding only part of the contributors. Both sides lowercase before grouping.
140+
_tag_written_both_ways(con)
141+
tags = [r["hashtag"] for r in query.hashtags(con, "hotosm", sources)]
142+
assert "#YouthMappers" not in tags
143+
assert tags.count("#youthmappers") == 1
144+
145+
146+
def test_leaderboard_cooccurring_hashtags_merge_case_variants(con, sources):
147+
# Same split on the per-user chips: alice carries the tag from history and from the recent tail.
148+
_tag_written_both_ways(con)
149+
items = query.leaderboard(con, "hotosm", sources)["items"]
150+
alice = next(r for r in items if r["name"] == "alice")
151+
assert "#YouthMappers" not in alice["hashtags"]
152+
assert alice["hashtags"].count("#youthmappers") == 1
153+
154+
130155
def test_tags_breakdown(con, sources):
131156
tg = query.tags(con, "hotosm", sources)
132157
building = next(r for r in tg if r["tag_key"] == "building")

0 commit comments

Comments
 (0)