Skip to content

Commit 6c60d1f

Browse files
authored
Merge pull request #139 from ddxv/main
Add App Ranks, Revenue & MAU to App Explorer
2 parents 44281c4 + 9aeb56b commit 6c60d1f

File tree

27 files changed

+601
-517
lines changed

27 files changed

+601
-517
lines changed

.pre-commit-config.yaml

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
repos:
22
# Ruff
33
- repo: https://github.com/astral-sh/ruff-pre-commit
4-
rev: v0.15.0
4+
rev: v0.15.4
55
hooks:
6-
- id: ruff
6+
- id: ruff-check
77
args: [--fix]
88
- id: ruff-format
99
# Mypy
@@ -33,7 +33,7 @@ repos:
3333
- prettier
3434
- prettier-plugin-svelte
3535
- repo: https://github.com/pre-commit/mirrors-eslint
36-
rev: v10.0.0
36+
rev: v10.0.2
3737
hooks:
3838
- id: eslint
3939
name: eslint
@@ -61,12 +61,9 @@ repos:
6161
require_serial: true
6262
## SQL ##
6363
- repo: https://github.com/sqlfluff/sqlfluff
64-
rev: 4.0.0
64+
rev: 4.0.4
6565
hooks:
6666
- id: sqlfluff-lint
6767
args: ["--config", "backend/pyproject.toml"]
6868
- id: sqlfluff-fix
6969
args: ["--config", "backend/pyproject.toml"]
70-
# Arbitrary arguments to show an example
71-
# args: [--rules, "LT02,CP02"]
72-
# additional_dependencies: ['<dbt-adapter>', 'sqlfluff-templater-dbt']

backend/api_app/controllers/apps.py

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ def api_call_dfs(state: State, store_id: str) -> pd.DataFrame:
7272
return df
7373

7474

75-
def get_search_results(state: State, search_term: str) -> dict:
75+
def get_search_results(state: State, search_term: str) -> AppGroupByStore:
7676
"""Parse search term and return resulting AppGroup."""
7777
decoded_input = urllib.parse.unquote(search_term)
7878
decoded_input = decoded_input.strip()
@@ -81,10 +81,12 @@ def get_search_results(state: State, search_term: str) -> dict:
8181
df = search_apps(state, search_input=decoded_input, limit=60)
8282
df = extend_app_icon_url(df)
8383
logger.info(f"{decoded_input=} returned rows: {df.shape[0]}")
84-
apple_apps_dict = df[df["store"].str.startswith("Apple")].to_dict(orient="records")
85-
google_apps_dict = df[df["store"].str.startswith("Google")].to_dict(
84+
apple_apps_dict: list[AppDetail] = df[df["store"].str.startswith("Apple")].to_dict(
8685
orient="records"
8786
)
87+
google_apps_dict: list[AppDetail] = df[
88+
df["store"].str.startswith("Google")
89+
].to_dict(orient="records")
8890
app_group = AppGroupByStore(
8991
key=f"Apps matching '{search_term}'",
9092
apple=AppGroup(title="Apple", apps=apple_apps_dict),
@@ -337,7 +339,7 @@ async def get_new_apps(
337339
@get(path="/growth/{store:int}", cache=86400)
338340
async def get_growth_apps(
339341
self: Self, state: State, store: int, app_category: str | None = None
340-
) -> list[dict]:
342+
) -> dict:
341343
"""Handle GET request for a list of fastest growing apps.
342344
343345
Args:
@@ -555,7 +557,7 @@ async def get_sdk_details(self: Self, state: State, store_id: str) -> SDKsDetail
555557

556558
cats = df.loc[df["category_slug"].notna(), "category_slug"].unique().tolist()
557559
company_sdk_dict = {}
558-
found_sdk_tlds = []
560+
found_sdk_tlds: list = []
559561
# example: {"ad-networks":
560562
# {"bytedance.com":
561563
# {"com.bytedance.sdk":
@@ -833,7 +835,7 @@ async def request_sdk_scan(
833835
insert_sdk_scan_request(state, store_id, user_id)
834836
return {"status": "success"}
835837

836-
@get(path="/{store_id:str}/keywords", cache=86400)
838+
@get(path="/{store_id:str}/keywords", cache=3600)
837839
async def get_app_keywords(self: Self, state: State, store_id: str) -> dict:
838840
"""Handle GET request for a list of apps.
839841
@@ -851,7 +853,7 @@ async def get_app_keywords(self: Self, state: State, store_id: str) -> dict:
851853
logger.info(f"{self.path}/{store_id}/keywords took {duration}ms")
852854
return keywords_dict
853855

854-
@get(path="/{store_id:str}/apis", cache=86400)
856+
@get(path="/{store_id:str}/apis", cache=3600)
855857
async def get_app_apis(self: Self, state: State, store_id: str) -> dict:
856858
"""Handle GET request for a list of apps.
857859
@@ -885,6 +887,7 @@ async def get_crossfilter_apps(self: Self, state: State, data: dict) -> dict:
885887
require_sdk_api = bool(data.get("require_sdk_api", False))
886888
require_iap = bool(data.get("require_iap", False))
887889
require_ads = bool(data.get("require_ads", False))
890+
ranking_country = data.get("ranking_country")
888891
mydate = data.get("mydate", "2024-01-01")
889892
category = data.get("category")
890893
store = data.get("store")
@@ -894,8 +897,6 @@ async def get_crossfilter_apps(self: Self, state: State, data: dict) -> dict:
894897
max_rating_count = data.get("max_rating_count")
895898
min_installs_d30 = data.get("min_installs_d30")
896899
max_installs_d30 = data.get("max_installs_d30")
897-
sort_col = data.get("sort_col", "installs")
898-
sort_order = data.get("sort_order", "desc")
899900

900901
# Ensure domains are lists of strings
901902
if isinstance(include_domains, str):
@@ -906,12 +907,14 @@ async def get_crossfilter_apps(self: Self, state: State, data: dict) -> dict:
906907
# Filter out empty strings
907908
include_domains = [d for d in include_domains if d and isinstance(d, str)]
908909
exclude_domains = [d for d in exclude_domains if d and isinstance(d, str)]
910+
if ranking_country is not None:
911+
ranking_country = str(ranking_country).strip() or None
909912

910913
logger.info(
911914
f"Crossfilter query: include={len(include_domains)} domains, "
912915
f"exclude={len(exclude_domains)} domains, sdk_api={require_sdk_api}, "
913-
f"iap={require_iap}, ads={require_ads}, date={mydate}, "
914-
f"category={category}, store={store}, sort={sort_col} {sort_order}"
916+
f"iap={require_iap}, ads={require_ads}, ranking_country={ranking_country}, date={mydate}, "
917+
f"category={category}, store={store}"
915918
)
916919

917920
try:
@@ -922,6 +925,7 @@ async def get_crossfilter_apps(self: Self, state: State, data: dict) -> dict:
922925
require_sdk_api=require_sdk_api,
923926
require_iap=require_iap,
924927
require_ads=require_ads,
928+
ranking_country=ranking_country,
925929
mydate=mydate,
926930
category=category,
927931
store=store,
@@ -931,8 +935,6 @@ async def get_crossfilter_apps(self: Self, state: State, data: dict) -> dict:
931935
max_rating_count=max_rating_count,
932936
min_installs_d30=min_installs_d30,
933937
max_installs_d30=max_installs_d30,
934-
sort_col=sort_col,
935-
sort_order=sort_order,
936938
)
937939
apps_df = extend_app_icon_url(apps_df)
938940
apps_list = apps_df.to_dict(orient="records")

backend/api_app/controllers/categories.py

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ def category_overview(state: State) -> CategoriesOverview:
2929
cats["name"] = cats["category"]
3030
cats["name"] = (
3131
cats["name"]
32-
.str.replace("game_", "")
32+
.str.replace("game_", "Games: ")
3333
.str.replace("_and_", " & ")
3434
.str.replace("_", " ")
3535
.str.title()
@@ -47,7 +47,20 @@ def category_overview(state: State) -> CategoriesOverview:
4747

4848
cats = cats.sort_values("total_apps", ascending=False)
4949

50-
cats["type"] = np.where(cats.id.str.contains("_game|games"), "game", "app")
50+
android_games_total = cats[cats.id.str.startswith("game")]["android"].sum()
51+
52+
cats.loc[cats["id"] == "games", "android"] = android_games_total
53+
54+
cats["type"] = np.where(
55+
cats.id.str.contains("_game|games", regex=True), "game", "app"
56+
)
57+
58+
cats["sort_priority"] = 2
59+
cats.loc[cats["id"] == "overall", "sort_priority"] = 0
60+
cats.loc[(cats["id"] == "games") | (cats["name"] == "Games"), "sort_priority"] = 1
61+
62+
cats = cats.sort_values(["sort_priority", "type", "name"])
63+
cats = cats.drop(columns=["sort_priority"])
5164

5265
category_dicts = cats.to_dict(orient="records")
5366

backend/api_app/controllers/companies.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -344,8 +344,6 @@ def get_overviews(
344344
category_overview_stats = make_companies_stats(
345345
df=companies_df.copy(),
346346
tag_source_category_app_counts=tag_source_category_app_counts,
347-
category=category,
348-
type_slug=type_slug,
349347
)
350348

351349
companies_df = prep_companies_overview_df(state, companies_df)
@@ -396,8 +394,6 @@ def append_overall_categories(df: pd.DataFrame) -> pd.DataFrame:
396394
def make_companies_stats(
397395
df: pd.DataFrame,
398396
tag_source_category_app_counts: pd.DataFrame,
399-
category: str | None = None,
400-
type_slug: str | None = None,
401397
) -> CompaniesCategoryOverview:
402398
"""Make category sums for overview."""
403399
overview = CompaniesCategoryOverview()

backend/api_app/controllers/creatives.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ async def company_creatives(
136136
logger.info(f"{self.path}/companies/{company_domain} took {duration}ms")
137137
return df.to_dict(orient="records")
138138

139-
@get(path="/apps/{store_id: str}/monetized", cache=86400)
139+
@get(path="/apps/{store_id: str}/monetized", cache=3600)
140140
async def monetized_creatives(self: Self, state: State, store_id: str) -> dict:
141141
"""Handle GET request for a list of creatives used for monetization by an app.
142142
@@ -197,7 +197,7 @@ async def monetized_creatives(self: Self, state: State, store_id: str) -> dict:
197197
"by_creative": cdf.to_dict(orient="records"),
198198
}
199199

200-
@get(path="/apps/{store_id: str}/ads", cache=86400)
200+
@get(path="/apps/{store_id: str}/ads", cache=3600)
201201
async def advertiser_creatives(self: Self, state: State, store_id: str) -> dict:
202202
"""Handle GET request for a list of creatives for an app.
203203
@@ -296,7 +296,7 @@ async def advertiser_creatives(self: Self, state: State, store_id: str) -> dict:
296296
"by_creative": cdf.to_dict(orient="records"),
297297
}
298298

299-
@get(path="/apps/{store_id: str}/ads/{vhash: str}", cache=86400)
299+
@get(path="/apps/{store_id: str}/ads/{vhash: str}", cache=3600)
300300
async def get_advertiser_creative_records(
301301
self: Self, state: State, store_id: str, vhash: str
302302
) -> dict:

backend/api_app/models.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,8 @@ class AppHistory:
2929
class AdsTxtEntries:
3030
"""App ads txt entries."""
3131

32-
direct_entries: dict
33-
reseller_entries: dict
32+
direct_entries: list[dict]
33+
reseller_entries: list[dict]
3434

3535

3636
@dataclass
@@ -82,7 +82,7 @@ class PlatformDeveloper:
8282
"""Developer details for a specific platform."""
8383

8484
developer_id: str
85-
developer_name: str
85+
developer_name: str | None
8686
developer_url: str
8787
pub_domain_url: str | None
8888
apps: AppGroup
@@ -193,7 +193,7 @@ class ParentCompanyTree:
193193
parent_company_domain: str | None
194194
queried_company_domain: str
195195
queried_company_name: str
196-
domains: list[str]
196+
domains: list[dict]
197197
children_companies: list[ChildrenCompanyTree]
198198
queried_company_logo_url: str | None = None
199199
parent_company_logo_url: str | None = None
@@ -442,7 +442,7 @@ class AppRankOverview:
442442
"""
443443

444444
countries: list[str]
445-
best_ranks: dict
445+
best_ranks: list[dict]
446446

447447

448448
@dataclass

backend/dbcon/queries.py

Lines changed: 5 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -637,7 +637,7 @@ def get_category_tag_type_stats(
637637

638638
@cache_by_params
639639
def get_tag_source_category_totals(
640-
state: State, app_category: str | None = None, type_slug: str | None = None
640+
state: State, app_category: str | None = None
641641
) -> pd.DataFrame:
642642
"""Get category totals."""
643643
if app_category:
@@ -918,6 +918,7 @@ def query_apps_crossfilter(
918918
require_sdk_api: bool = False,
919919
require_iap: bool = False,
920920
require_ads: bool = False,
921+
ranking_country: str | None = None,
921922
mydate: str = "2024-01-01",
922923
category: str | None = None,
923924
store: int | None = None,
@@ -927,38 +928,21 @@ def query_apps_crossfilter(
927928
max_rating_count: int | None = None,
928929
min_installs_d30: int | None = None,
929930
max_installs_d30: int | None = None,
930-
sort_col: str = "installs",
931-
sort_order: str = "desc",
932931
) -> pd.DataFrame:
933932
"""Query apps for analytics dashboard."""
934933
# Ensure domains are lists, not None
935934
include_domains = include_domains or []
936935
exclude_domains = exclude_domains or []
937936

938-
# Return empty dataframe if no include domains specified
939-
if not include_domains:
940-
return pd.DataFrame()
937+
if category == "games":
938+
category = "game%"
941939

942940
# Parse date with fallback
943941
try:
944942
parsed_date = datetime.datetime.strptime(mydate, "%Y-%m-%d").date()
945943
except (ValueError, TypeError):
946944
parsed_date = datetime.date(2024, 1, 1)
947945

948-
# Validate sort_col to prevent injection (though utilizing param binding in SQL for logic selection)
949-
allowed_sort_cols = [
950-
"installs",
951-
"rating_count",
952-
"installs_d30",
953-
"review_count",
954-
"rating",
955-
]
956-
if sort_col not in allowed_sort_cols:
957-
sort_col = "installs"
958-
959-
if sort_order not in ["asc", "desc"]:
960-
sort_order = "desc"
961-
962946
df = pd.read_sql(
963947
sql.apps_crossfilter,
964948
state.dbcon.engine,
@@ -968,6 +952,7 @@ def query_apps_crossfilter(
968952
"require_sdk_api": bool(require_sdk_api),
969953
"require_iap": bool(require_iap),
970954
"require_ads": bool(require_ads),
955+
"ranking_country": ranking_country,
971956
"mydate": parsed_date,
972957
"category": category,
973958
"store": store,
@@ -977,8 +962,6 @@ def query_apps_crossfilter(
977962
"max_rating_count": max_rating_count,
978963
"min_installs_d30": min_installs_d30,
979964
"max_installs_d30": max_installs_d30,
980-
# "sort_col": sort_col,
981-
# "sort_order": sort_order,
982965
},
983966
)
984967
return df
Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1-
SELECT id, name, url_slug
1+
SELECT
2+
id,
3+
name,
4+
url_slug
25
FROM
36
adtech.categories;

0 commit comments

Comments
 (0)