Spaces:
Running on Zero
Running on Zero
Filter QR analytics to post-creation scans
Browse files- hf_dashboard.py +60 -7
- tests-unit/hf_dashboard_test.py +20 -6
hf_dashboard.py
CHANGED
|
@@ -118,6 +118,35 @@ def _table_select(table: str, *, query: str) -> list[dict[str, Any]]:
|
|
| 118 |
return []
|
| 119 |
|
| 120 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
def _insert_row(table: str, row: Mapping[str, Any], *, upsert: bool = False) -> list[dict[str, Any]]:
|
| 122 |
prefer = "return=representation"
|
| 123 |
if upsert:
|
|
@@ -699,12 +728,36 @@ def get_generation_detail(request: Any | None, records: list[dict[str, Any]], in
|
|
| 699 |
),
|
| 700 |
)
|
| 701 |
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 705 |
|
| 706 |
-
countries = Counter(str(row.get("country_code") or "Unknown") for row in
|
| 707 |
-
scans_by_day = Counter(
|
|
|
|
|
|
|
| 708 |
|
| 709 |
top_countries_rows = [[country, count] for country, count in countries.most_common(10)]
|
| 710 |
scans_by_day_rows = [[day, scans_by_day[day]] for day in sorted(scans_by_day.keys())]
|
|
@@ -724,8 +777,8 @@ def get_generation_detail(request: Any | None, records: list[dict[str, Any]], in
|
|
| 724 |
f"- Destination: {record.get('destination_url_original') or '—'}",
|
| 725 |
f"- Short URL: {record.get('short_url') or '—'}",
|
| 726 |
f"- Effective QR text: {record.get('effective_qr_text') or '—'}",
|
| 727 |
-
f"- Total scans: {total_scans}",
|
| 728 |
-
f"- Approx. unique scans: {unique_scans}",
|
| 729 |
f"- Last scanned at: {last_scanned_at}",
|
| 730 |
]
|
| 731 |
)
|
|
|
|
| 118 |
return []
|
| 119 |
|
| 120 |
|
| 121 |
+
def _parse_timestamp(value: Any) -> datetime | None:
|
| 122 |
+
text = str(value or "").strip()
|
| 123 |
+
if not text:
|
| 124 |
+
return None
|
| 125 |
+
if text.endswith("Z"):
|
| 126 |
+
text = text[:-1] + "+00:00"
|
| 127 |
+
if "." in text:
|
| 128 |
+
main, suffix = text.split(".", 1)
|
| 129 |
+
tz_index_plus = suffix.find("+")
|
| 130 |
+
tz_index_minus = suffix.find("-")
|
| 131 |
+
tz_index_candidates = [idx for idx in (tz_index_plus, tz_index_minus) if idx != -1]
|
| 132 |
+
tz_index = min(tz_index_candidates) if tz_index_candidates else -1
|
| 133 |
+
if tz_index != -1:
|
| 134 |
+
fraction = suffix[:tz_index]
|
| 135 |
+
timezone_part = suffix[tz_index:]
|
| 136 |
+
else:
|
| 137 |
+
fraction = suffix
|
| 138 |
+
timezone_part = ""
|
| 139 |
+
if fraction:
|
| 140 |
+
fraction = (fraction + "000000")[:6]
|
| 141 |
+
text = f"{main}.{fraction}{timezone_part}"
|
| 142 |
+
if len(text) >= 3 and text[-3] in {"+", "-"} and text[-2:].isdigit():
|
| 143 |
+
text = text + ":00"
|
| 144 |
+
try:
|
| 145 |
+
return datetime.fromisoformat(text)
|
| 146 |
+
except ValueError:
|
| 147 |
+
return None
|
| 148 |
+
|
| 149 |
+
|
| 150 |
def _insert_row(table: str, row: Mapping[str, Any], *, upsert: bool = False) -> list[dict[str, Any]]:
|
| 151 |
prefer = "return=representation"
|
| 152 |
if upsert:
|
|
|
|
| 728 |
),
|
| 729 |
)
|
| 730 |
|
| 731 |
+
created_at_dt = _parse_timestamp(record.get("created_at"))
|
| 732 |
+
filtered_scan_rows = [
|
| 733 |
+
row
|
| 734 |
+
for row in scan_rows
|
| 735 |
+
if not bool(row.get("is_bot"))
|
| 736 |
+
and not bool(row.get("is_prefetch"))
|
| 737 |
+
and (
|
| 738 |
+
created_at_dt is None
|
| 739 |
+
or (_parse_timestamp(row.get("scanned_at")) or created_at_dt) >= created_at_dt
|
| 740 |
+
)
|
| 741 |
+
]
|
| 742 |
+
|
| 743 |
+
total_scans = len(filtered_scan_rows)
|
| 744 |
+
last_scanned_at = (
|
| 745 |
+
str(filtered_scan_rows[0].get("scanned_at") or "").replace("T", " ")[:19]
|
| 746 |
+
if filtered_scan_rows
|
| 747 |
+
else "Never"
|
| 748 |
+
)
|
| 749 |
+
unique_scans = len(
|
| 750 |
+
{
|
| 751 |
+
str(row.get("visitor_hash") or "")
|
| 752 |
+
for row in filtered_scan_rows
|
| 753 |
+
if str(row.get("visitor_hash") or "").strip()
|
| 754 |
+
}
|
| 755 |
+
)
|
| 756 |
|
| 757 |
+
countries = Counter(str(row.get("country_code") or "Unknown") for row in filtered_scan_rows)
|
| 758 |
+
scans_by_day = Counter(
|
| 759 |
+
str(row.get("scanned_at") or "")[:10] for row in filtered_scan_rows if str(row.get("scanned_at") or "")
|
| 760 |
+
)
|
| 761 |
|
| 762 |
top_countries_rows = [[country, count] for country, count in countries.most_common(10)]
|
| 763 |
scans_by_day_rows = [[day, scans_by_day[day]] for day in sorted(scans_by_day.keys())]
|
|
|
|
| 777 |
f"- Destination: {record.get('destination_url_original') or '—'}",
|
| 778 |
f"- Short URL: {record.get('short_url') or '—'}",
|
| 779 |
f"- Effective QR text: {record.get('effective_qr_text') or '—'}",
|
| 780 |
+
f"- Total scans since this QR was created: {total_scans}",
|
| 781 |
+
f"- Approx. unique scans since creation: {unique_scans}",
|
| 782 |
f"- Last scanned at: {last_scanned_at}",
|
| 783 |
]
|
| 784 |
)
|
tests-unit/hf_dashboard_test.py
CHANGED
|
@@ -116,26 +116,40 @@ def test_get_generation_detail_aggregates_scan_analytics(monkeypatch) -> None:
|
|
| 116 |
if table == "short_link_scan_events":
|
| 117 |
return [
|
| 118 |
{
|
| 119 |
-
"scanned_at": "2026-05-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 120 |
"country_code": "US",
|
| 121 |
"visitor_hash": "v1",
|
| 122 |
"is_bot": False,
|
| 123 |
"is_prefetch": False,
|
| 124 |
},
|
| 125 |
{
|
| 126 |
-
"scanned_at": "2026-05-
|
| 127 |
"country_code": "NL",
|
| 128 |
"visitor_hash": "v2",
|
| 129 |
"is_bot": False,
|
| 130 |
"is_prefetch": False,
|
| 131 |
},
|
| 132 |
{
|
| 133 |
-
"scanned_at": "2026-05-
|
| 134 |
"country_code": "US",
|
| 135 |
"visitor_hash": "v1",
|
| 136 |
"is_bot": False,
|
| 137 |
"is_prefetch": False,
|
| 138 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
]
|
| 140 |
raise AssertionError(f"Unexpected table: {table}")
|
| 141 |
|
|
@@ -144,7 +158,7 @@ def test_get_generation_detail_aggregates_scan_analytics(monkeypatch) -> None:
|
|
| 144 |
{
|
| 145 |
"id": "gen-1",
|
| 146 |
"app_user_id": "user-1",
|
| 147 |
-
"created_at": "2026-05-
|
| 148 |
"status": "completed",
|
| 149 |
"input_type": "URL",
|
| 150 |
"qr_mode": "standard",
|
|
@@ -159,8 +173,8 @@ def test_get_generation_detail_aggregates_scan_analytics(monkeypatch) -> None:
|
|
| 159 |
|
| 160 |
detail = hf_dashboard.get_generation_detail(FakeRequest(oauth_info=HF_OAUTH_INFO), records, 0)
|
| 161 |
|
| 162 |
-
assert "Total scans: 3" in detail["detail_markdown"]
|
| 163 |
-
assert "Approx. unique scans: 2" in detail["detail_markdown"]
|
| 164 |
assert detail["image"] == "https://cdn.example/full.png"
|
| 165 |
assert detail["top_countries_rows"] == [["US", 2], ["NL", 1]]
|
| 166 |
assert detail["scans_by_day_rows"] == [["2026-05-31", 3]]
|
|
|
|
| 116 |
if table == "short_link_scan_events":
|
| 117 |
return [
|
| 118 |
{
|
| 119 |
+
"scanned_at": "2026-05-31T14:00:00+00:00",
|
| 120 |
+
"country_code": "US",
|
| 121 |
+
"visitor_hash": "old-v",
|
| 122 |
+
"is_bot": False,
|
| 123 |
+
"is_prefetch": False,
|
| 124 |
+
},
|
| 125 |
+
{
|
| 126 |
+
"scanned_at": "2026-05-31T15:00:00+00:00",
|
| 127 |
"country_code": "US",
|
| 128 |
"visitor_hash": "v1",
|
| 129 |
"is_bot": False,
|
| 130 |
"is_prefetch": False,
|
| 131 |
},
|
| 132 |
{
|
| 133 |
+
"scanned_at": "2026-05-31T16:00:00+00:00",
|
| 134 |
"country_code": "NL",
|
| 135 |
"visitor_hash": "v2",
|
| 136 |
"is_bot": False,
|
| 137 |
"is_prefetch": False,
|
| 138 |
},
|
| 139 |
{
|
| 140 |
+
"scanned_at": "2026-05-31T17:00:00+00:00",
|
| 141 |
"country_code": "US",
|
| 142 |
"visitor_hash": "v1",
|
| 143 |
"is_bot": False,
|
| 144 |
"is_prefetch": False,
|
| 145 |
},
|
| 146 |
+
{
|
| 147 |
+
"scanned_at": "2026-05-31T18:00:00+00:00",
|
| 148 |
+
"country_code": "US",
|
| 149 |
+
"visitor_hash": "bot-v",
|
| 150 |
+
"is_bot": True,
|
| 151 |
+
"is_prefetch": False,
|
| 152 |
+
},
|
| 153 |
]
|
| 154 |
raise AssertionError(f"Unexpected table: {table}")
|
| 155 |
|
|
|
|
| 158 |
{
|
| 159 |
"id": "gen-1",
|
| 160 |
"app_user_id": "user-1",
|
| 161 |
+
"created_at": "2026-05-31T14:47:47+00:00",
|
| 162 |
"status": "completed",
|
| 163 |
"input_type": "URL",
|
| 164 |
"qr_mode": "standard",
|
|
|
|
| 173 |
|
| 174 |
detail = hf_dashboard.get_generation_detail(FakeRequest(oauth_info=HF_OAUTH_INFO), records, 0)
|
| 175 |
|
| 176 |
+
assert "Total scans since this QR was created: 3" in detail["detail_markdown"]
|
| 177 |
+
assert "Approx. unique scans since creation: 2" in detail["detail_markdown"]
|
| 178 |
assert detail["image"] == "https://cdn.example/full.png"
|
| 179 |
assert detail["top_countries_rows"] == [["US", 2], ["NL", 1]]
|
| 180 |
assert detail["scans_by_day_rows"] == [["2026-05-31", 3]]
|