From 7d3426ecdb0abd06c9e3557f55b0e029f0826ca5 Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Wed, 22 Nov 2023 19:00:21 +0900 Subject: [PATCH 01/35] =?UTF-8?q?[feat]=20=EC=97=B0=EB=A0=B9=20=ED=9E=88?= =?UTF-8?q?=EC=8A=A4=ED=86=A0=EA=B7=B8=EB=9E=A8=20endpoint=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 3 +- model/AgeHist.py | 30 ++++++++++++++ model/BasicResponse.py | 1 + model/MongoDB.py | 2 + routers/ageHist.py | 94 ++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 129 insertions(+), 1 deletion(-) create mode 100644 model/AgeHist.py create mode 100644 routers/ageHist.py diff --git a/main.py b/main.py index 579505e..45c814c 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,6 @@ from fastapi import FastAPI, Request from dotenv import load_dotenv -from routers import scrapResult, commonInfo +from routers import scrapResult, commonInfo, ageHist from contextlib import asynccontextmanager from typing import Dict from model import MongoDB @@ -21,3 +21,4 @@ async def initMongo(app: FastAPI): app.include_router(scrapResult.router) app.include_router(commonInfo.router) +app.include_router(ageHist.router) diff --git a/model/AgeHist.py b/model/AgeHist.py new file mode 100644 index 0000000..a92d140 --- /dev/null +++ b/model/AgeHist.py @@ -0,0 +1,30 @@ +from pydantic import BaseModel +from enum import StrEnum + + +class AgeHistDataTypes(StrEnum): + elected = "elected" + candidate = "candidate" + + +class AgeHistMethodTypes(StrEnum): + equal = "equal" + kmeans = "kmeans" + + +class AgeHistDataPoint(BaseModel): + minAge: int + maxAge: int + count: int + ageGroup: int + + +class MetroAgeHistData(BaseModel): + metroId: int + data: list[AgeHistDataPoint] + + +class LocalAgeHistData(BaseModel): + metroId: int + localId: int + data: list[AgeHistDataPoint] diff --git a/model/BasicResponse.py b/model/BasicResponse.py index f6523bb..e991d0c 100644 --- a/model/BasicResponse.py +++ b/model/BasicResponse.py @@ -3,6 +3,7 @@ SUCCESS = 200 REGION_CODE_ERR = 400 +COLLECTION_NOT_EXIST_ERR = 600 class MessageResponse(BaseModel): diff --git a/model/MongoDB.py b/model/MongoDB.py index 7228259..7f79cc7 100644 --- a/model/MongoDB.py +++ b/model/MongoDB.py @@ -10,11 +10,13 @@ def __init__(self): self.client = None self.council_db = None self.district_db = None + self.age_hist_db = None def connect(self): self.client = AsyncIOMotorClient(os.getenv("MONGO_CONNECTION_URI")) self.council_db = AsyncIOMotorDatabase(self.client, "council") self.district_db = AsyncIOMotorDatabase(self.client, "district") + self.age_hist_db = AsyncIOMotorDatabase(self.client, "age_hist") def close(self): self.client.close() diff --git a/routers/ageHist.py b/routers/ageHist.py new file mode 100644 index 0000000..de38e7d --- /dev/null +++ b/routers/ageHist.py @@ -0,0 +1,94 @@ +from fastapi import APIRouter +from model import BasicResponse, MongoDB +from model.AgeHist import AgeHistDataTypes, AgeHistMethodTypes, MetroAgeHistData + + +router = APIRouter(prefix="/localCouncil", tags=["localCouncil"]) + + +@router.get("/age-hist/{metroId}") +async def getMetroAgeHistData( + metroId: int, ageHistType: AgeHistDataTypes, year: int, method: AgeHistMethodTypes +) -> BasicResponse.ErrorResponse | MetroAgeHistData: + if ( + await MongoDB.client.district_db["metro_district"].find_one( + {"metroId": metroId} + ) + is None + ): + return BasicResponse.ErrorResponse.model_validate( + { + "error": "RegionCodeError", + "code": BasicResponse.REGION_CODE_ERR, + "message": f"No metro district with metroId {metroId}.", + } + ) + + match ageHistType: + case AgeHistDataTypes.elected: + collection_name = f"지선-당선_{year}_1level_{method}" + case AgeHistDataTypes.candidate: + collection_name = f"지선-후보_{year}_1level_{method}" + + if collection_name not in await MongoDB.client.age_hist_db.list_collection_names(): + return BasicResponse.ErrorResponse.model_validate( + { + "error": "CollectionNotExistError", + "code": BasicResponse.COLLECTION_NOT_EXIST_ERR, + "message": f"No collection with name f{collection_name}. Perhaps the year is wrong?", + } + ) + + histogram = await MongoDB.client.age_hist_db[collection_name].find_one( + {"metroId": metroId} + ) + + return MetroAgeHistData.model_validate( + {"metroId": metroId, "data": histogram["data"]} + ) + + +@router.get("/age-hist/{metroId}/{localId}") +async def getLocalAgeHistData( + metroId: int, + localId: int, + ageHistType: AgeHistDataTypes, + year: int, + method: AgeHistMethodTypes, +) -> BasicResponse.ErrorResponse | MetroAgeHistData: + if ( + await MongoDB.client.district_db["local_district"].find_one( + {"metroId": metroId, "localId": localId} + ) + is None + ): + return BasicResponse.ErrorResponse.model_validate( + { + "error": "RegionCodeError", + "code": BasicResponse.REGION_CODE_ERR, + "message": f"No local district with metroId {metroId} and localId {localId}.", + } + ) + + match ageHistType: + case AgeHistDataTypes.elected: + collection_name = f"지선-당선_{year}_2level_{method}" + case AgeHistDataTypes.candidate: + collection_name = f"지선-후보_{year}_2level_{method}" + + if collection_name not in await MongoDB.client.age_hist_db.list_collection_names(): + return BasicResponse.ErrorResponse.model_validate( + { + "error": "CollectionNotExistError", + "code": BasicResponse.COLLECTION_NOT_EXIST_ERR, + "message": f"No collection with name f{collection_name}. Perhaps the year is wrong?", + } + ) + + histogram = await MongoDB.client.age_hist_db[collection_name].find_one( + {"metroId": metroId, "localId": localId} + ) + + return MetroAgeHistData.model_validate( + {"metroId": metroId, "localId": localId, "data": histogram["data"]} + ) From 1b58d76c90f8750de5e2244467aef1d1009a79c2 Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Wed, 22 Nov 2023 19:11:51 +0900 Subject: [PATCH 02/35] =?UTF-8?q?[fix]=20=EC=97=B0=EB=A0=B9=20=EA=B5=AC?= =?UTF-8?q?=EA=B0=84=20convention=20=ED=86=B5=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/ScrapResult.py | 2 +- routers/scrapResult.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/model/ScrapResult.py b/model/ScrapResult.py index d53b501..ddd3141 100644 --- a/model/ScrapResult.py +++ b/model/ScrapResult.py @@ -39,7 +39,7 @@ class GenderChartDataPoint(BaseModel): class AgeChartDataPoint(BaseModel): minAge: int # 닫힌 구간 - maxAge: int # 닫힌 구간 + maxAge: int # 열린 구간 count: int diff --git a/routers/scrapResult.py b/routers/scrapResult.py index d6d32cc..d9b7189 100644 --- a/routers/scrapResult.py +++ b/routers/scrapResult.py @@ -103,7 +103,7 @@ async def getLocalChartData( "data": [ { "minAge": age, - "maxAge": age + AGE_STAIR - 1, + "maxAge": age + AGE_STAIR, "count": age_count[age], } for age in age_count From df60a62820e384740f7a10a3e02ec4fd29d10dbe Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Wed, 22 Nov 2023 21:05:29 +0900 Subject: [PATCH 03/35] =?UTF-8?q?[feat]=20=EC=97=B0=EB=A0=B9=20=ED=85=9C?= =?UTF-8?q?=ED=94=8C=EB=A6=BF=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/ScrapResult.py | 42 ++++++++++++++++++++++++++++++++- routers/scrapResult.py | 53 +++++++++++++++++++++++++++++++++++++++++- 2 files changed, 93 insertions(+), 2 deletions(-) diff --git a/model/ScrapResult.py b/model/ScrapResult.py index ddd3141..aac77bc 100644 --- a/model/ScrapResult.py +++ b/model/ScrapResult.py @@ -22,7 +22,47 @@ class GenderTemplateData(BaseModel): class AgeTemplateData(BaseModel): - ageDiversityIndex: float + class AgeRankingParagraphData(BaseModel): + class AgeRankingAllIndices(BaseModel): + metroId: int + rank: int + ageDiversityIndex: float + + ageDiversityIndex: float + allIndices: list[AgeRankingAllIndices] + + class AgeIndexHistoryParagraphData(BaseModel): + class AgeIndexHistoryIndexData(BaseModel): + year: int + unit: int + candidateCount: int + candidateDiversityIndex: float + candidateDiversityRank: int + electedDiversityIndex: float + electedDiversityRank: int + + mostRecentYear: int + history: list[AgeIndexHistoryIndexData] + + class AgeHistogramParagraphData(BaseModel): + class AgeHistogramAreaData(BaseModel): + localId: int + firstQuintile: int + lastQuintile: int + + year: int + candidateCount: int + electedCount: int + firstQuintile: int + lastQuintile: int + divArea: AgeHistogramAreaData + uniArea: AgeHistogramAreaData + + metroId: int + localId: int + rankingParagraph: AgeRankingParagraphData + indexHistoryParagraph: AgeIndexHistoryParagraphData + ageHistogramParagraph: AgeHistogramParagraphData class PartyTemplateData(BaseModel): diff --git a/routers/scrapResult.py b/routers/scrapResult.py index d9b7189..bc3e3b1 100644 --- a/routers/scrapResult.py +++ b/routers/scrapResult.py @@ -41,7 +41,58 @@ async def getLocalTemplateData( age_list = [councilor["age"] async for councilor in councilors] age_diversity_index = diversity.gini_simpson(age_list, stair=AGE_STAIR) return ScrapResult.AgeTemplateData.model_validate( - {"ageDiversityIndex": age_diversity_index} + { + "metroId": metroId, + "localId": localId, + "rankingParagraph": { + "ageDiversityIndex": age_diversity_index, + "allIndices": [ + {"metroId": 3, "rank": 2, "ageDiversityIndex": 0.9}, + {"metroId": 14, "rank": 7, "ageDiversityIndex": 0.4}, + {"metroId": 15, "rank": 18, "ageDiversityIndex": 0.2}, + ], + }, + "indexHistoryParagraph": { + "mostRecentYear": 2022, + "history": [ + { + "year": 2022, + "unit": 8, + "candidateCount": 80, + "candidateDiversityIndex": 0.11, + "candidateDiversityRank": 33, + "electedDiversityIndex": 0.42, + "electedDiversityRank": 12, + }, + { + "year": 2018, + "unit": 7, + "candidateCount": 70, + "candidateDiversityIndex": 0.73, + "candidateDiversityRank": 3, + "electedDiversityIndex": 0.85, + "electedDiversityRank": 2, + }, + ], + }, + "ageHistogramParagraph": { + "year": 2022, + "candidateCount": 80, + "electedCount": 16, + "firstQuintile": 66, + "lastQuintile": 29, + "divArea": { + "localId": 172, + "firstQuintile": 43, + "lastQuintile": 21, + }, + "uniArea": { + "localId": 63, + "firstQuintile": 84, + "lastQuintile": 56, + }, + }, + } ) case ScrapResult.FactorType.party: From a6a4123db3b038d9d1365d991664405d8dc0f76c Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Thu, 23 Nov 2023 23:55:23 +0900 Subject: [PATCH 04/35] =?UTF-8?q?[feat]=20=EC=97=B0=EB=A0=B9=20=ED=85=9C?= =?UTF-8?q?=ED=94=8C=EB=A6=BF=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=8B=A4?= =?UTF-8?q?=EC=A0=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/MongoDB.py | 4 +- model/ScrapResult.py | 2 +- routers/ageHist.py | 55 +++++------- routers/scrapResult.py | 191 +++++++++++++++++++++++++++++------------ utils/diversity.py | 2 +- 5 files changed, 162 insertions(+), 92 deletions(-) diff --git a/model/MongoDB.py b/model/MongoDB.py index 7f79cc7..afc663e 100644 --- a/model/MongoDB.py +++ b/model/MongoDB.py @@ -10,13 +10,13 @@ def __init__(self): self.client = None self.council_db = None self.district_db = None - self.age_hist_db = None + self.stats_db = None def connect(self): self.client = AsyncIOMotorClient(os.getenv("MONGO_CONNECTION_URI")) self.council_db = AsyncIOMotorDatabase(self.client, "council") self.district_db = AsyncIOMotorDatabase(self.client, "district") - self.age_hist_db = AsyncIOMotorDatabase(self.client, "age_hist") + self.stats_db = AsyncIOMotorDatabase(self.client, "stats") def close(self): self.client.close() diff --git a/model/ScrapResult.py b/model/ScrapResult.py index aac77bc..d7d4dd4 100644 --- a/model/ScrapResult.py +++ b/model/ScrapResult.py @@ -24,7 +24,7 @@ class GenderTemplateData(BaseModel): class AgeTemplateData(BaseModel): class AgeRankingParagraphData(BaseModel): class AgeRankingAllIndices(BaseModel): - metroId: int + localId: int rank: int ageDiversityIndex: float diff --git a/routers/ageHist.py b/routers/ageHist.py index de38e7d..d10dfba 100644 --- a/routers/ageHist.py +++ b/routers/ageHist.py @@ -24,23 +24,16 @@ async def getMetroAgeHistData( } ) - match ageHistType: - case AgeHistDataTypes.elected: - collection_name = f"지선-당선_{year}_1level_{method}" - case AgeHistDataTypes.candidate: - collection_name = f"지선-후보_{year}_1level_{method}" - - if collection_name not in await MongoDB.client.age_hist_db.list_collection_names(): - return BasicResponse.ErrorResponse.model_validate( - { - "error": "CollectionNotExistError", - "code": BasicResponse.COLLECTION_NOT_EXIST_ERR, - "message": f"No collection with name f{collection_name}. Perhaps the year is wrong?", - } - ) - - histogram = await MongoDB.client.age_hist_db[collection_name].find_one( - {"metroId": metroId} + histogram = await MongoDB.client.stats_db["age_hist"].find_one( + { + "level": 1, + "councilorType": ( + "elected" if ageHistType == AgeHistDataTypes.elected else "candidate" + ), + "year": year, + "method": method, + "metroId": metroId, + } ) return MetroAgeHistData.model_validate( @@ -70,23 +63,17 @@ async def getLocalAgeHistData( } ) - match ageHistType: - case AgeHistDataTypes.elected: - collection_name = f"지선-당선_{year}_2level_{method}" - case AgeHistDataTypes.candidate: - collection_name = f"지선-후보_{year}_2level_{method}" - - if collection_name not in await MongoDB.client.age_hist_db.list_collection_names(): - return BasicResponse.ErrorResponse.model_validate( - { - "error": "CollectionNotExistError", - "code": BasicResponse.COLLECTION_NOT_EXIST_ERR, - "message": f"No collection with name f{collection_name}. Perhaps the year is wrong?", - } - ) - - histogram = await MongoDB.client.age_hist_db[collection_name].find_one( - {"metroId": metroId, "localId": localId} + histogram = await MongoDB.client.stats_db["age_hist"].find_one( + { + "level": 2, + "councilorType": ( + "elected" if ageHistType == AgeHistDataTypes.elected else "candidate" + ), + "year": year, + "method": method, + "metroId": metroId, + "localId": localId, + } ) return MetroAgeHistData.model_validate( diff --git a/routers/scrapResult.py b/routers/scrapResult.py index bc3e3b1..2ac16bb 100644 --- a/routers/scrapResult.py +++ b/routers/scrapResult.py @@ -1,7 +1,18 @@ +from typing import TypeVar from fastapi import APIRouter -from model import BasicResponse, MongoDB, ScrapResult +from model.BasicResponse import ErrorResponse, REGION_CODE_ERR +from model.MongoDB import client +from model.ScrapResult import ( + GenderTemplateData, + GenderChartDataPoint, + AgeTemplateData, + AgeChartDataPoint, + PartyTemplateData, + PartyChartDataPoint, + FactorType, + ChartData, +) from utils import diversity -from typing import TypeVar router = APIRouter(prefix="/localCouncil", tags=["localCouncil"]) @@ -11,45 +22,122 @@ @router.get("/template-data/{metroId}/{localId}") async def getLocalTemplateData( - metroId: int, localId: int, factor: ScrapResult.FactorType -) -> BasicResponse.ErrorResponse | ScrapResult.GenderTemplateData | ScrapResult.AgeTemplateData | ScrapResult.PartyTemplateData: + metroId: int, localId: int, factor: FactorType +) -> ErrorResponse | GenderTemplateData | AgeTemplateData | PartyTemplateData: if ( - await MongoDB.client.district_db["local_district"].find_one( + await client.district_db["local_district"].find_one( {"localId": localId, "metroId": metroId} ) is None ): - return BasicResponse.ErrorResponse.model_validate( + return ErrorResponse.model_validate( { "error": "RegionCodeError", - "code": BasicResponse.REGION_CODE_ERR, + "code": REGION_CODE_ERR, "message": f"No local district with metroId {metroId} and localId {localId}.", } ) - councilors = MongoDB.client.council_db["local_councilor"].find({"localId": localId}) + local_stat = await client.stats_db["diversity_index"].find_one({"localId": localId}) match factor: - case ScrapResult.FactorType.gender: - gender_list = [councilor["gender"] async for councilor in councilors] - gender_diversity_index = diversity.gini_simpson(gender_list) - return ScrapResult.GenderTemplateData.model_validate( - {"genderDiversityIndex": gender_diversity_index} + case FactorType.gender: + return GenderTemplateData.model_validate( + {"genderDiversityIndex": local_stat["genderDiversityIndex"]} ) - case ScrapResult.FactorType.age: - age_list = [councilor["age"] async for councilor in councilors] - age_diversity_index = diversity.gini_simpson(age_list, stair=AGE_STAIR) - return ScrapResult.AgeTemplateData.model_validate( + case FactorType.age: + # ============================ + # rankingParagraph + # ============================ + age_diversity_index = local_stat["ageDiversityIndex"] + + localIds_of_same_metroId = [ + doc["localId"] + async for doc in client.district_db["local_district"].find( + {"metroId": metroId} + ) + ] + all_indices = ( + await client.stats_db["diversity_index"] + .find({"localId": {"$in": localIds_of_same_metroId}}) + .to_list(500) + ) + all_indices.sort(key=lambda x: x["ageDiversityRank"]) + + # ============================ + # ageHistogramParagraph + # ============================ + age_stat_elected = ( + await client.stats_db["age_stat"] + .aggregate( + [ + { + "$match": { + "level": 2, + "councilorType": "elected", + "metroId": metroId, + "localId": localId, + } + }, + {"$sort": {"year": -1}}, + {"$limit": 1}, + ] + ) + .to_list(500) + )[0] + most_recent_year = age_stat_elected["year"] + age_stat_candidate = await client.stats_db["age_stat"].find_one( + { + "level": 2, + "councilorType": "candidate", + "metroId": metroId, + "localId": localId, + "year": most_recent_year, + } + ) + + divArea_id = ( + await client.stats_db["diversity_index"].find_one( + {"ageDiversityRank": 1} + ) + )["localId"] + divArea = await client.stats_db["age_stat"].find_one( + { + "level": 2, + "councilorType": "elected", + "localId": divArea_id, + "year": most_recent_year, + } + ) + + uniArea_id = ( + await client.stats_db["diversity_index"].find_one( + {"ageDiversityRank": 226} + ) + )["localId"] + uniArea = await client.stats_db["age_stat"].find_one( + { + "level": 2, + "councilorType": "elected", + "localId": uniArea_id, + "year": most_recent_year, + } + ) + + return AgeTemplateData.model_validate( { "metroId": metroId, "localId": localId, "rankingParagraph": { "ageDiversityIndex": age_diversity_index, "allIndices": [ - {"metroId": 3, "rank": 2, "ageDiversityIndex": 0.9}, - {"metroId": 14, "rank": 7, "ageDiversityIndex": 0.4}, - {"metroId": 15, "rank": 18, "ageDiversityIndex": 0.2}, + { + "localId": doc["localId"], + "rank": idx + 1, + "ageDiversityIndex": doc["ageDiversityIndex"], + } + for idx, doc in enumerate(all_indices) ], }, "indexHistoryParagraph": { @@ -76,68 +164,65 @@ async def getLocalTemplateData( ], }, "ageHistogramParagraph": { - "year": 2022, - "candidateCount": 80, - "electedCount": 16, - "firstQuintile": 66, - "lastQuintile": 29, + "year": most_recent_year, + "candidateCount": age_stat_candidate["data"][0]["population"], + "electedCount": age_stat_elected["data"][0]["population"], + "firstQuintile": age_stat_elected["data"][0]["firstquintile"], + "lastQuintile": age_stat_elected["data"][0]["lastquintile"], "divArea": { - "localId": 172, - "firstQuintile": 43, - "lastQuintile": 21, + "localId": divArea_id, + "firstQuintile": divArea["data"][0]["firstquintile"], + "lastQuintile": divArea["data"][0]["lastquintile"], }, "uniArea": { - "localId": 63, - "firstQuintile": 84, - "lastQuintile": 56, + "localId": uniArea_id, + "firstQuintile": uniArea["data"][0]["firstquintile"], + "lastQuintile": uniArea["data"][0]["lastquintile"], }, }, } ) - case ScrapResult.FactorType.party: - party_list = [councilor["jdName"] async for councilor in councilors] - party_diversity_index = diversity.gini_simpson(party_list) - return ScrapResult.PartyTemplateData.model_validate( + case FactorType.party: + party_diversity_index = local_stat["partyDiversityIndex"] + return PartyTemplateData.model_validate( {"partyDiversityIndex": party_diversity_index} ) T = TypeVar( "T", - ScrapResult.GenderChartDataPoint, - ScrapResult.AgeChartDataPoint, - ScrapResult.PartyChartDataPoint, + GenderChartDataPoint, + AgeChartDataPoint, + PartyChartDataPoint, ) @router.get("/chart-data/{metroId}/{localId}") async def getLocalChartData( - metroId: int, localId: int, factor: ScrapResult.FactorType -) -> BasicResponse.ErrorResponse | ScrapResult.ChartData[T]: + metroId: int, localId: int, factor: FactorType +) -> ErrorResponse | ChartData[T]: if ( - await MongoDB.client.district_db["local_district"].find_one( + await client.district_db["local_district"].find_one( {"localId": localId, "metroId": metroId} ) is None ): - return BasicResponse.ErrorResponse.model_validate( + return ErrorResponse.model_validate( { "error": "RegionCodeError", - "code": BasicResponse.REGION_CODE_ERR, + "code": REGION_CODE_ERR, "message": f"No local district with metroId {metroId} and localId {localId}.", } ) - councilors = MongoDB.client.council_db["local_councilor"].find({"localId": localId}) + councilors = client.council_db["local_councilor"].find({"localId": localId}) match factor: - case ScrapResult.FactorType.gender: + case FactorType.gender: gender_list = [councilor["gender"] async for councilor in councilors] gender_count = diversity.count(gender_list) - return ScrapResult.ChartData[ - ScrapResult.GenderChartDataPoint - ].model_validate( + return ChartData[GenderChartDataPoint].model_validate( { "data": [ {"gender": gender, "count": gender_count[gender]} @@ -146,10 +231,10 @@ async def getLocalChartData( } ) - case ScrapResult.FactorType.age: + case FactorType.age: age_list = [councilor["age"] async for councilor in councilors] age_count = diversity.count(age_list, stair=AGE_STAIR) - return ScrapResult.ChartData[ScrapResult.AgeChartDataPoint].model_validate( + return ChartData[AgeChartDataPoint].model_validate( { "data": [ { @@ -162,12 +247,10 @@ async def getLocalChartData( } ) - case ScrapResult.FactorType.party: + case FactorType.party: party_list = [councilor["jdName"] async for councilor in councilors] party_count = diversity.count(party_list) - return ScrapResult.ChartData[ - ScrapResult.PartyChartDataPoint - ].model_validate( + return ChartData[PartyChartDataPoint].model_validate( { "data": [ {"party": party, "count": party_count[party]} diff --git a/utils/diversity.py b/utils/diversity.py index 95536e0..12b23d1 100644 --- a/utils/diversity.py +++ b/utils/diversity.py @@ -19,7 +19,7 @@ def gini_simpson(data, stair=0, opts=True): """ counts = count(data, stair) total = sum(counts.values()) - gs_idx = 1 - sum((n / total) ** 2 for n in counts.values()) + gs_idx = 1 - sum((n / total) * ((n - 1) / (total - 1)) for n in counts.values()) if opts: num_cats = len([c for c in counts.values() if c > 0]) From e8588e151393e64c7d5727a78218922d282f3d71 Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Fri, 24 Nov 2023 03:32:23 +0900 Subject: [PATCH 05/35] feat(cors) cors --- main.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index 2543508..2feeb90 100644 --- a/main.py +++ b/main.py @@ -20,7 +20,8 @@ async def initMongo(app: FastAPI): origin = [ "http://localhost:5173", - "https://diversity.tech4impact.kr/" + "https://diversity.tech4impact.kr/", + "https://*..netlify.app/", ] app.add_middleware( From 4b5802e26b34d6768ac18a0ce7c2a86d6150f432 Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Fri, 24 Nov 2023 03:35:23 +0900 Subject: [PATCH 06/35] feat(remove) comma --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 2feeb90..f1139d6 100644 --- a/main.py +++ b/main.py @@ -21,7 +21,7 @@ async def initMongo(app: FastAPI): origin = [ "http://localhost:5173", "https://diversity.tech4impact.kr/", - "https://*..netlify.app/", + "https://*.netlify.app/", ] app.add_middleware( From d1e2616d591b6332fd8c4e793a68c2c174e8f635 Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Fri, 24 Nov 2023 03:37:31 +0900 Subject: [PATCH 07/35] refactor(fix) --- main.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index f1139d6..0c482d6 100644 --- a/main.py +++ b/main.py @@ -20,8 +20,8 @@ async def initMongo(app: FastAPI): origin = [ "http://localhost:5173", - "https://diversity.tech4impact.kr/", - "https://*.netlify.app/", + "https://diversity.tech4impact.kr", + "https://*.netlify.app", ] app.add_middleware( From 53863a7afd32e0101b661de93479d6eef67fe5cc Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Fri, 24 Nov 2023 03:53:31 +0900 Subject: [PATCH 08/35] refactor(fix) --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 0c482d6..9169500 100644 --- a/main.py +++ b/main.py @@ -21,12 +21,12 @@ async def initMongo(app: FastAPI): origin = [ "http://localhost:5173", "https://diversity.tech4impact.kr", - "https://*.netlify.app", ] app.add_middleware( CORSMiddleware, allow_origins=origin, + allow_origin_regex=["https://.*\.netlify\.app"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"], From 3bc5e9a0431373802438a0df1877870b4669bb8a Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Fri, 24 Nov 2023 03:56:39 +0900 Subject: [PATCH 09/35] Fix --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 9169500..6a1e32b 100644 --- a/main.py +++ b/main.py @@ -26,7 +26,7 @@ async def initMongo(app: FastAPI): app.add_middleware( CORSMiddleware, allow_origins=origin, - allow_origin_regex=["https://.*\.netlify\.app"], + allow_origin_regex="https://.*\.netlify\.app", allow_credentials=True, allow_methods=["*"], allow_headers=["*"], From 93e0f84f03e79d2a6ae0af98648761ea888fc9d3 Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Fri, 24 Nov 2023 13:21:40 +0900 Subject: [PATCH 10/35] =?UTF-8?q?[feat]=20indexHistoryParagraph=20?= =?UTF-8?q?=EC=8B=A4=EC=A0=9C=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EB=B0=98?= =?UTF-8?q?=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/scrapResult.py | 73 +++++++++++++++++++++++++++++++----------- utils/diversity.py | 2 ++ 2 files changed, 57 insertions(+), 18 deletions(-) diff --git a/routers/scrapResult.py b/routers/scrapResult.py index 2ac16bb..864883d 100644 --- a/routers/scrapResult.py +++ b/routers/scrapResult.py @@ -65,6 +65,40 @@ async def getLocalTemplateData( ) all_indices.sort(key=lambda x: x["ageDiversityRank"]) + # ============================ + # indexHistoryParagraph + # ============================ + years = list( + {doc["year"] async for doc in client.stats_db["age_hist"].find()} + ) + years.sort() + history_candidate = [ + await client.stats_db["age_hist"].find_one( + { + "year": year, + "level": 2, + "councilorType": "candidate", + "method": "equal", + "metroId": metroId, + "localId": localId, + } + ) + for year in years + ] + history_elected = [ + await client.stats_db["age_hist"].find_one( + { + "year": year, + "level": 2, + "councilorType": "elected", + "method": "equal", + "metroId": metroId, + "localId": localId, + } + ) + for year in years + ] + # ============================ # ageHistogramParagraph # ============================ @@ -141,26 +175,29 @@ async def getLocalTemplateData( ], }, "indexHistoryParagraph": { - "mostRecentYear": 2022, + "mostRecentYear": years[-1], "history": [ { - "year": 2022, - "unit": 8, - "candidateCount": 80, - "candidateDiversityIndex": 0.11, - "candidateDiversityRank": 33, - "electedDiversityIndex": 0.42, - "electedDiversityRank": 12, - }, - { - "year": 2018, - "unit": 7, - "candidateCount": 70, - "candidateDiversityIndex": 0.73, - "candidateDiversityRank": 3, - "electedDiversityIndex": 0.85, - "electedDiversityRank": 2, - }, + "year": year, + "unit": (year - 1998) / 4 + 2, + "candidateCount": sum( + group["count"] + for group in history_candidate[idx]["data"] + ), + "candidateDiversityIndex": history_candidate[idx][ + "diversityIndex" + ], + "candidateDiversityRank": history_candidate[idx][ + "diversityRank" + ], + "electedDiversityIndex": history_elected[idx][ + "diversityIndex" + ], + "electedDiversityRank": history_elected[idx][ + "diversityRank" + ], + } + for idx, year in enumerate(years) ], }, "ageHistogramParagraph": { diff --git a/utils/diversity.py b/utils/diversity.py index 12b23d1..be91f56 100644 --- a/utils/diversity.py +++ b/utils/diversity.py @@ -23,6 +23,8 @@ def gini_simpson(data, stair=0, opts=True): if opts: num_cats = len([c for c in counts.values() if c > 0]) + if num_cats <= 1: + return 0.0 max_gs_idx = (num_cats - 1) / num_cats * total / (total - 1) gs_idx /= max_gs_idx From b351f43963723974f9c74f8aae69ee24dbfc8610 Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Fri, 24 Nov 2023 16:36:30 +0900 Subject: [PATCH 11/35] =?UTF-8?q?[feat]=20=EA=B4=91=EC=97=AD=EC=9D=98?= =?UTF-8?q?=ED=9A=8C=20=EC=A0=95=EB=B3=B4=20=EB=B0=98=ED=99=98=20endpoint?= =?UTF-8?q?=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 6 +- model/ScrapResultCommon.py | 62 ++++ model/{ScrapResult.py => ScrapResultLocal.py} | 67 +--- model/ScrapResultMetro.py | 56 ++++ routers/ageHist.py | 2 +- .../{scrapResult.py => scrapResultLocal.py} | 22 +- routers/scrapResultMetro.py | 288 ++++++++++++++++++ 7 files changed, 426 insertions(+), 77 deletions(-) create mode 100644 model/ScrapResultCommon.py rename model/{ScrapResult.py => ScrapResultLocal.py} (51%) create mode 100644 model/ScrapResultMetro.py rename routers/{scrapResult.py => scrapResultLocal.py} (94%) create mode 100644 routers/scrapResultMetro.py diff --git a/main.py b/main.py index 6a1e32b..8c4f66c 100644 --- a/main.py +++ b/main.py @@ -1,12 +1,13 @@ from fastapi import FastAPI, Request from dotenv import load_dotenv -from routers import scrapResult, commonInfo, ageHist +from routers import commonInfo, ageHist, scrapResultLocal, scrapResultMetro from contextlib import asynccontextmanager from typing import Dict from model import MongoDB from model.ResponseType import ChartResponse, GenderInfo, PartyInfo, AgeInfo from fastapi.middleware.cors import CORSMiddleware + @asynccontextmanager async def initMongo(app: FastAPI): MongoDB.client.connect() @@ -32,6 +33,7 @@ async def initMongo(app: FastAPI): allow_headers=["*"], ) -app.include_router(scrapResult.router) +app.include_router(scrapResultLocal.router) +app.include_router(scrapResultMetro.router) app.include_router(commonInfo.router) app.include_router(ageHist.router) diff --git a/model/ScrapResultCommon.py b/model/ScrapResultCommon.py new file mode 100644 index 0000000..1438b26 --- /dev/null +++ b/model/ScrapResultCommon.py @@ -0,0 +1,62 @@ +from pydantic import BaseModel +from enum import StrEnum +from typing import TypeVar, Generic + + +class GenderType(StrEnum): + male = "남" + female = "여" + + +class FactorType(StrEnum): + gender = "gender" + age = "age" + party = "party" + + +# ============================================== +# = Chart Data Types = +# ============================================== +class GenderChartDataPoint(BaseModel): + gender: GenderType + count: int + + +class AgeChartDataPoint(BaseModel): + minAge: int # 닫힌 구간 + maxAge: int # 열린 구간 + count: int + + +class PartyChartDataPoint(BaseModel): + party: str + count: int + + +T = TypeVar("T", GenderChartDataPoint, AgeChartDataPoint, PartyChartDataPoint) + + +class ChartData(BaseModel, Generic[T]): + data: list[T] + + +# ============================================== +# = Scrap Result Data Types = +# ============================================== +class CouncilType(StrEnum): + local_council = "local_council" + national_council = "national_council" + metropolitan_council = "metropolitan_council" + local_leader = "local_leader" + metro_leader = "metro_leader" + + +class CouncilInfo(BaseModel): + name: str + party: str + + +class ScrapResult(BaseModel): + council_id: str + council_type: CouncilType + councilers: list[CouncilInfo] diff --git a/model/ScrapResult.py b/model/ScrapResultLocal.py similarity index 51% rename from model/ScrapResult.py rename to model/ScrapResultLocal.py index d7d4dd4..5a6d16c 100644 --- a/model/ScrapResult.py +++ b/model/ScrapResultLocal.py @@ -1,27 +1,14 @@ from pydantic import BaseModel -from enum import StrEnum -from typing import TypeVar, Generic - - -class GenderType(StrEnum): - male = "남" - female = "여" - - -class FactorType(StrEnum): - gender = "gender" - age = "age" - party = "party" # ============================================== # = Template Data Types = # ============================================== -class GenderTemplateData(BaseModel): +class GenderTemplateDataLocal(BaseModel): genderDiversityIndex: float -class AgeTemplateData(BaseModel): +class AgeTemplateDataLocal(BaseModel): class AgeRankingParagraphData(BaseModel): class AgeRankingAllIndices(BaseModel): localId: int @@ -65,53 +52,5 @@ class AgeHistogramAreaData(BaseModel): ageHistogramParagraph: AgeHistogramParagraphData -class PartyTemplateData(BaseModel): +class PartyTemplateDataLocal(BaseModel): partyDiversityIndex: float - - -# ============================================== -# = Chart Data Types = -# ============================================== -class GenderChartDataPoint(BaseModel): - gender: GenderType - count: int - - -class AgeChartDataPoint(BaseModel): - minAge: int # 닫힌 구간 - maxAge: int # 열린 구간 - count: int - - -class PartyChartDataPoint(BaseModel): - party: str - count: int - - -T = TypeVar("T", GenderChartDataPoint, AgeChartDataPoint, PartyChartDataPoint) - - -class ChartData(BaseModel, Generic[T]): - data: list[T] - - -# ============================================== -# = Scrap Result Data Types = -# ============================================== -class CouncilType(StrEnum): - local_council = "local_council" - national_council = "national_council" - metropolitan_council = "metropolitan_council" - local_leader = "local_leader" - metro_leader = "metro_leader" - - -class CouncilInfo(BaseModel): - name: str - party: str - - -class ScrapResult(BaseModel): - council_id: str - council_type: CouncilType - councilers: list[CouncilInfo] diff --git a/model/ScrapResultMetro.py b/model/ScrapResultMetro.py new file mode 100644 index 0000000..b49f60b --- /dev/null +++ b/model/ScrapResultMetro.py @@ -0,0 +1,56 @@ +from pydantic import BaseModel + + +# ============================================== +# = Template Data Types = +# ============================================== +class GenderTemplateDataMetro(BaseModel): + genderDiversityIndex: float + + +class AgeTemplateDataMetro(BaseModel): + class AgeRankingParagraphData(BaseModel): + class AgeRankingAllIndices(BaseModel): + localId: int + rank: int + ageDiversityIndex: float + + ageDiversityIndex: float + allIndices: list[AgeRankingAllIndices] + + class AgeIndexHistoryParagraphData(BaseModel): + class AgeIndexHistoryIndexData(BaseModel): + year: int + unit: int + candidateCount: int + candidateDiversityIndex: float + candidateDiversityRank: int + electedDiversityIndex: float + electedDiversityRank: int + + mostRecentYear: int + history: list[AgeIndexHistoryIndexData] + + class AgeHistogramParagraphData(BaseModel): + class AgeHistogramAreaData(BaseModel): + localId: int + firstQuintile: int + lastQuintile: int + + year: int + candidateCount: int + electedCount: int + firstQuintile: int + lastQuintile: int + divArea: AgeHistogramAreaData + uniArea: AgeHistogramAreaData + + metroId: int + localId: int + rankingParagraph: AgeRankingParagraphData + indexHistoryParagraph: AgeIndexHistoryParagraphData + ageHistogramParagraph: AgeHistogramParagraphData + + +class PartyTemplateDataMetro(BaseModel): + partyDiversityIndex: float diff --git a/routers/ageHist.py b/routers/ageHist.py index d10dfba..bdf7708 100644 --- a/routers/ageHist.py +++ b/routers/ageHist.py @@ -3,7 +3,7 @@ from model.AgeHist import AgeHistDataTypes, AgeHistMethodTypes, MetroAgeHistData -router = APIRouter(prefix="/localCouncil", tags=["localCouncil"]) +router = APIRouter() @router.get("/age-hist/{metroId}") diff --git a/routers/scrapResult.py b/routers/scrapResultLocal.py similarity index 94% rename from routers/scrapResult.py rename to routers/scrapResultLocal.py index 864883d..3ba09ee 100644 --- a/routers/scrapResult.py +++ b/routers/scrapResultLocal.py @@ -2,16 +2,18 @@ from fastapi import APIRouter from model.BasicResponse import ErrorResponse, REGION_CODE_ERR from model.MongoDB import client -from model.ScrapResult import ( - GenderTemplateData, +from model.ScrapResultCommon import ( GenderChartDataPoint, - AgeTemplateData, AgeChartDataPoint, - PartyTemplateData, PartyChartDataPoint, FactorType, ChartData, ) +from model.ScrapResultLocal import ( + GenderTemplateDataLocal, + AgeTemplateDataLocal, + PartyTemplateDataLocal, +) from utils import diversity @@ -23,7 +25,7 @@ @router.get("/template-data/{metroId}/{localId}") async def getLocalTemplateData( metroId: int, localId: int, factor: FactorType -) -> ErrorResponse | GenderTemplateData | AgeTemplateData | PartyTemplateData: +) -> ErrorResponse | GenderTemplateDataLocal | AgeTemplateDataLocal | PartyTemplateDataLocal: if ( await client.district_db["local_district"].find_one( {"localId": localId, "metroId": metroId} @@ -42,7 +44,7 @@ async def getLocalTemplateData( match factor: case FactorType.gender: - return GenderTemplateData.model_validate( + return GenderTemplateDataLocal.model_validate( {"genderDiversityIndex": local_stat["genderDiversityIndex"]} ) @@ -133,7 +135,7 @@ async def getLocalTemplateData( divArea_id = ( await client.stats_db["diversity_index"].find_one( - {"ageDiversityRank": 1} + {"localId": {"$exists": True}, "ageDiversityRank": 1} ) )["localId"] divArea = await client.stats_db["age_stat"].find_one( @@ -147,7 +149,7 @@ async def getLocalTemplateData( uniArea_id = ( await client.stats_db["diversity_index"].find_one( - {"ageDiversityRank": 226} + {"localId": {"$exists": True}, "ageDiversityRank": 226} ) )["localId"] uniArea = await client.stats_db["age_stat"].find_one( @@ -159,7 +161,7 @@ async def getLocalTemplateData( } ) - return AgeTemplateData.model_validate( + return AgeTemplateDataLocal.model_validate( { "metroId": metroId, "localId": localId, @@ -222,7 +224,7 @@ async def getLocalTemplateData( case FactorType.party: party_diversity_index = local_stat["partyDiversityIndex"] - return PartyTemplateData.model_validate( + return PartyTemplateDataLocal.model_validate( {"partyDiversityIndex": party_diversity_index} ) diff --git a/routers/scrapResultMetro.py b/routers/scrapResultMetro.py new file mode 100644 index 0000000..67f6749 --- /dev/null +++ b/routers/scrapResultMetro.py @@ -0,0 +1,288 @@ +from typing import TypeVar +from fastapi import APIRouter +from model.BasicResponse import ErrorResponse, REGION_CODE_ERR +from model.MongoDB import client +from model.ScrapResultCommon import ( + GenderChartDataPoint, + AgeChartDataPoint, + PartyChartDataPoint, + FactorType, + ChartData, +) +from model.ScrapResultMetro import ( + GenderTemplateDataMetro, + AgeTemplateDataMetro, + PartyTemplateDataMetro, +) +from utils import diversity + + +router = APIRouter(prefix="/metroCouncil", tags=["metroCouncil"]) + +AGE_STAIR = 10 + + +@router.get("/template-data/{metroId}") +async def getMetroTemplateData( + metroId: int, factor: FactorType +) -> ErrorResponse | GenderTemplateDataMetro | AgeTemplateDataMetro | PartyTemplateDataMetro: + if ( + await client.district_db["metro_district"].find_one({"metroId": metroId}) + is None + ): + return ErrorResponse.model_validate( + { + "error": "RegionCodeError", + "code": REGION_CODE_ERR, + "message": f"No metro district with metroId {metroId}.", + } + ) + + metro_stat = await client.stats_db["diversity_index"].find_one({"metroId": metroId}) + + match factor: + case FactorType.gender: + return GenderTemplateDataMetro.model_validate( + {"genderDiversityIndex": metro_stat["genderDiversityIndex"]} + ) + + case FactorType.age: + # ============================ + # rankingParagraph + # ============================ + age_diversity_index = metro_stat["ageDiversityIndex"] + + all_metroIds = [ + doc["metroId"] + async for doc in client.district_db["metro_district"].find() + ] + all_indices = ( + await client.stats_db["diversity_index"] + .find({"metroId": {"$in": all_metroIds}}) + .to_list(500) + ) + all_indices.sort(key=lambda x: x["ageDiversityRank"]) + + # ============================ + # indexHistoryParagraph + # ============================ + years = list( + {doc["year"] async for doc in client.stats_db["age_hist"].find()} + ) + years.sort() + history_candidate = [ + await client.stats_db["age_hist"].find_one( + { + "year": year, + "level": 1, + "councilorType": "candidate", + "method": "equal", + "metroId": metroId, + } + ) + for year in years + ] + history_elected = [ + await client.stats_db["age_hist"].find_one( + { + "year": year, + "level": 1, + "councilorType": "elected", + "method": "equal", + "metroId": metroId, + } + ) + for year in years + ] + + # ============================ + # ageHistogramParagraph + # ============================ + age_stat_elected = ( + await client.stats_db["age_stat"] + .aggregate( + [ + { + "$match": { + "level": 1, + "councilorType": "elected", + "metroId": metroId, + } + }, + {"$sort": {"year": -1}}, + {"$limit": 1}, + ] + ) + .to_list(500) + )[0] + most_recent_year = age_stat_elected["year"] + age_stat_candidate = await client.stats_db["age_stat"].find_one( + { + "level": 1, + "councilorType": "candidate", + "metroId": metroId, + "year": most_recent_year, + } + ) + + divArea_id = ( + await client.stats_db["diversity_index"].find_one( + {"metroId": {"$exists": True}, "ageDiversityRank": 1} + ) + )["localId"] + divArea = await client.stats_db["age_stat"].find_one( + { + "level": 1, + "councilorType": "elected", + "metroId": divArea_id, + "year": most_recent_year, + } + ) + + uniArea_id = ( + await client.stats_db["diversity_index"].find_one( + {"metroId": {"$exists": True}, "ageDiversityRank": 17} + ) + )["metroId"] + uniArea = await client.stats_db["age_stat"].find_one( + { + "level": 1, + "councilorType": "elected", + "metroId": uniArea_id, + "year": most_recent_year, + } + ) + + return AgeTemplateDataMetro.model_validate( + { + "metroId": metroId, + "rankingParagraph": { + "ageDiversityIndex": age_diversity_index, + "allIndices": [ + { + "metroId": doc["metroId"], + "rank": doc["ageDiversityRank"], + "ageDiversityIndex": doc["ageDiversityIndex"], + } + for doc in all_indices + ], + }, + "indexHistoryParagraph": { + "mostRecentYear": years[-1], + "history": [ + { + "year": year, + "unit": (year - 1998) / 4 + 2, + "candidateCount": sum( + group["count"] + for group in history_candidate[idx]["data"] + ), + "candidateDiversityIndex": history_candidate[idx][ + "diversityIndex" + ], + "candidateDiversityRank": history_candidate[idx][ + "diversityRank" + ], + "electedDiversityIndex": history_elected[idx][ + "diversityIndex" + ], + "electedDiversityRank": history_elected[idx][ + "diversityRank" + ], + } + for idx, year in enumerate(years) + ], + }, + "ageHistogramParagraph": { + "year": most_recent_year, + "candidateCount": age_stat_candidate["data"][0]["population"], + "electedCount": age_stat_elected["data"][0]["population"], + "firstQuintile": age_stat_elected["data"][0]["firstquintile"], + "lastQuintile": age_stat_elected["data"][0]["lastquintile"], + "divArea": { + "metroId": divArea_id, + "firstQuintile": divArea["data"][0]["firstquintile"], + "lastQuintile": divArea["data"][0]["lastquintile"], + }, + "uniArea": { + "metroId": uniArea_id, + "firstQuintile": uniArea["data"][0]["firstquintile"], + "lastQuintile": uniArea["data"][0]["lastquintile"], + }, + }, + } + ) + + case FactorType.party: + party_diversity_index = metro_stat["partyDiversityIndex"] + return PartyTemplateDataMetro.model_validate( + {"partyDiversityIndex": party_diversity_index} + ) + + +T = TypeVar( + "T", + GenderChartDataPoint, + AgeChartDataPoint, + PartyChartDataPoint, +) + + +@router.get("/chart-data/{metroId}") +async def getLocalChartData( + metroId: int, factor: FactorType +) -> ErrorResponse | ChartData[T]: + if ( + await client.district_db["metro_district"].find_one({"metroId": metroId}) + is None + ): + return ErrorResponse.model_validate( + { + "error": "RegionCodeError", + "code": REGION_CODE_ERR, + "message": f"No metro district with metroId {metroId}.", + } + ) + + councilors = client.council_db["metro_councilor"].find({"metroId": metroId}) + + match factor: + case FactorType.gender: + gender_list = [councilor["gender"] async for councilor in councilors] + gender_count = diversity.count(gender_list) + return ChartData[GenderChartDataPoint].model_validate( + { + "data": [ + {"gender": gender, "count": gender_count[gender]} + for gender in gender_count + ] + } + ) + + case FactorType.age: + age_list = [councilor["age"] async for councilor in councilors] + age_count = diversity.count(age_list, stair=AGE_STAIR) + return ChartData[AgeChartDataPoint].model_validate( + { + "data": [ + { + "minAge": age, + "maxAge": age + AGE_STAIR, + "count": age_count[age], + } + for age in age_count + ] + } + ) + + case FactorType.party: + party_list = [councilor["jdName"] async for councilor in councilors] + party_count = diversity.count(party_list) + return ChartData[PartyChartDataPoint].model_validate( + { + "data": [ + {"party": party, "count": party_count[party]} + for party in party_count + ] + } + ) From af7ba17d4dd766433d0939c800eec896f11bf39c Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Sat, 25 Nov 2023 17:05:38 +0900 Subject: [PATCH 12/35] =?UTF-8?q?[fix]=20=EA=B4=91=EC=97=AD=EC=9D=98?= =?UTF-8?q?=ED=9A=8C=20=EC=A0=95=EB=B3=B4=20=EB=B0=98=ED=99=98=20endpoint?= =?UTF-8?q?=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/ScrapResultMetro.py | 5 ++--- routers/scrapResultMetro.py | 5 +++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/model/ScrapResultMetro.py b/model/ScrapResultMetro.py index b49f60b..4dea5c9 100644 --- a/model/ScrapResultMetro.py +++ b/model/ScrapResultMetro.py @@ -11,7 +11,7 @@ class GenderTemplateDataMetro(BaseModel): class AgeTemplateDataMetro(BaseModel): class AgeRankingParagraphData(BaseModel): class AgeRankingAllIndices(BaseModel): - localId: int + metroId: int rank: int ageDiversityIndex: float @@ -33,7 +33,7 @@ class AgeIndexHistoryIndexData(BaseModel): class AgeHistogramParagraphData(BaseModel): class AgeHistogramAreaData(BaseModel): - localId: int + metroId: int firstQuintile: int lastQuintile: int @@ -46,7 +46,6 @@ class AgeHistogramAreaData(BaseModel): uniArea: AgeHistogramAreaData metroId: int - localId: int rankingParagraph: AgeRankingParagraphData indexHistoryParagraph: AgeIndexHistoryParagraphData ageHistogramParagraph: AgeHistogramParagraphData diff --git a/routers/scrapResultMetro.py b/routers/scrapResultMetro.py index 67f6749..c007220 100644 --- a/routers/scrapResultMetro.py +++ b/routers/scrapResultMetro.py @@ -129,7 +129,7 @@ async def getMetroTemplateData( await client.stats_db["diversity_index"].find_one( {"metroId": {"$exists": True}, "ageDiversityRank": 1} ) - )["localId"] + )["metroId"] divArea = await client.stats_db["age_stat"].find_one( { "level": 1, @@ -141,7 +141,8 @@ async def getMetroTemplateData( uniArea_id = ( await client.stats_db["diversity_index"].find_one( - {"metroId": {"$exists": True}, "ageDiversityRank": 17} + # {"metroId": {"$exists": True}, "ageDiversityRank": 17} + {"metroId": {"$exists": True}, "ageDiversityRank": 15} ) )["metroId"] uniArea = await client.stats_db["age_stat"].find_one( From 34e25764137153327597b654f591d284414a87b7 Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Mon, 27 Nov 2023 16:57:48 +0900 Subject: [PATCH 13/35] =?UTF-8?q?[fix]=20=EC=97=B0=EB=A0=B9=20=ED=9E=88?= =?UTF-8?q?=EC=8A=A4=ED=86=A0=EA=B7=B8=EB=9E=A8,=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20endpoint=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/ageHist.py | 10 ++++------ routers/scrapResultLocal.py | 21 +++++++++++++++------ routers/scrapResultMetro.py | 18 ++++++++++++------ 3 files changed, 31 insertions(+), 18 deletions(-) diff --git a/routers/ageHist.py b/routers/ageHist.py index bdf7708..4a378ec 100644 --- a/routers/ageHist.py +++ b/routers/ageHist.py @@ -27,9 +27,8 @@ async def getMetroAgeHistData( histogram = await MongoDB.client.stats_db["age_hist"].find_one( { "level": 1, - "councilorType": ( - "elected" if ageHistType == AgeHistDataTypes.elected else "candidate" - ), + "councilorType": "metro_councilor", + "is_elected": ageHistType == AgeHistDataTypes.elected, "year": year, "method": method, "metroId": metroId, @@ -66,9 +65,8 @@ async def getLocalAgeHistData( histogram = await MongoDB.client.stats_db["age_hist"].find_one( { "level": 2, - "councilorType": ( - "elected" if ageHistType == AgeHistDataTypes.elected else "candidate" - ), + "councilorType": "local_councilor", + "is_elected": ageHistType == AgeHistDataTypes.elected, "year": year, "method": method, "metroId": metroId, diff --git a/routers/scrapResultLocal.py b/routers/scrapResultLocal.py index 3ba09ee..b18e5dc 100644 --- a/routers/scrapResultLocal.py +++ b/routers/scrapResultLocal.py @@ -79,7 +79,8 @@ async def getLocalTemplateData( { "year": year, "level": 2, - "councilorType": "candidate", + "councilorType": "local_councilor", + "is_elected": False, "method": "equal", "metroId": metroId, "localId": localId, @@ -92,7 +93,8 @@ async def getLocalTemplateData( { "year": year, "level": 2, - "councilorType": "elected", + "councilorType": "local_councilor", + "is_elected": True, "method": "equal", "metroId": metroId, "localId": localId, @@ -111,7 +113,8 @@ async def getLocalTemplateData( { "$match": { "level": 2, - "councilorType": "elected", + "councilorType": "local_councilor", + "is_elected": True, "metroId": metroId, "localId": localId, } @@ -126,7 +129,8 @@ async def getLocalTemplateData( age_stat_candidate = await client.stats_db["age_stat"].find_one( { "level": 2, - "councilorType": "candidate", + "councilorType": "local_councilor", + "is_elected": False, "metroId": metroId, "localId": localId, "year": most_recent_year, @@ -141,7 +145,8 @@ async def getLocalTemplateData( divArea = await client.stats_db["age_stat"].find_one( { "level": 2, - "councilorType": "elected", + "councilorType": "local_councilor", + "is_elected": True, "localId": divArea_id, "year": most_recent_year, } @@ -155,7 +160,8 @@ async def getLocalTemplateData( uniArea = await client.stats_db["age_stat"].find_one( { "level": 2, - "councilorType": "elected", + "councilorType": "local_councilor", + "is_elected": True, "localId": uniArea_id, "year": most_recent_year, } @@ -186,12 +192,15 @@ async def getLocalTemplateData( group["count"] for group in history_candidate[idx]["data"] ), + # "candidateCount": 0, "candidateDiversityIndex": history_candidate[idx][ "diversityIndex" ], "candidateDiversityRank": history_candidate[idx][ "diversityRank" ], + # "candidateDiversityIndex": 0.0, + # "candidateDiversityRank": 0, "electedDiversityIndex": history_elected[idx][ "diversityIndex" ], diff --git a/routers/scrapResultMetro.py b/routers/scrapResultMetro.py index c007220..cef0e72 100644 --- a/routers/scrapResultMetro.py +++ b/routers/scrapResultMetro.py @@ -75,7 +75,8 @@ async def getMetroTemplateData( { "year": year, "level": 1, - "councilorType": "candidate", + "councilorType": "metro_councilor", + "is_elected": False, "method": "equal", "metroId": metroId, } @@ -87,7 +88,8 @@ async def getMetroTemplateData( { "year": year, "level": 1, - "councilorType": "elected", + "councilorType": "metro_councilor", + "is_elected": True, "method": "equal", "metroId": metroId, } @@ -105,7 +107,8 @@ async def getMetroTemplateData( { "$match": { "level": 1, - "councilorType": "elected", + "councilorType": "metro_councilor", + "is_elected": True, "metroId": metroId, } }, @@ -119,7 +122,8 @@ async def getMetroTemplateData( age_stat_candidate = await client.stats_db["age_stat"].find_one( { "level": 1, - "councilorType": "candidate", + "councilorType": "metro_councilor", + "is_elected": False, "metroId": metroId, "year": most_recent_year, } @@ -133,7 +137,8 @@ async def getMetroTemplateData( divArea = await client.stats_db["age_stat"].find_one( { "level": 1, - "councilorType": "elected", + "councilorType": "metro_councilor", + "is_elected": True, "metroId": divArea_id, "year": most_recent_year, } @@ -148,7 +153,8 @@ async def getMetroTemplateData( uniArea = await client.stats_db["age_stat"].find_one( { "level": 1, - "councilorType": "elected", + "councilorType": "metro_councilor", + "is_elected": True, "metroId": uniArea_id, "year": most_recent_year, } From bebc6e685d39689d9f8c32442fe7e46abd8d91cc Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Mon, 27 Nov 2023 17:45:46 +0900 Subject: [PATCH 14/35] =?UTF-8?q?[feat]=20=EA=B5=AD=ED=9A=8C=EC=9D=98?= =?UTF-8?q?=EC=9B=90=20=EB=8B=A4=EC=96=91=EC=84=B1=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EB=B0=98=ED=99=98=20endpoint=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/ScrapResultNational.py | 41 +++++++ routers/scrapResultLocal.py | 7 +- routers/scrapResultMetro.py | 7 +- routers/scrapResultNational.py | 211 +++++++++++++++++++++++++++++++++ 4 files changed, 264 insertions(+), 2 deletions(-) create mode 100644 model/ScrapResultNational.py create mode 100644 routers/scrapResultNational.py diff --git a/model/ScrapResultNational.py b/model/ScrapResultNational.py new file mode 100644 index 0000000..b95d784 --- /dev/null +++ b/model/ScrapResultNational.py @@ -0,0 +1,41 @@ +from pydantic import BaseModel + + +# ============================================== +# = Template Data Types = +# ============================================== +class GenderTemplateDataNational(BaseModel): + genderDiversityIndex: float + + +class AgeTemplateDataNational(BaseModel): + class AgeRankingParagraphData(BaseModel): + ageDiversityIndex: float + + class AgeIndexHistoryParagraphData(BaseModel): + class AgeIndexHistoryIndexData(BaseModel): + year: int + unit: int + candidateCount: int + candidateDiversityIndex: float + candidateDiversityRank: int + electedDiversityIndex: float + electedDiversityRank: int + + mostRecentYear: int + history: list[AgeIndexHistoryIndexData] + + class AgeHistogramParagraphData(BaseModel): + year: int + candidateCount: int + electedCount: int + firstQuintile: int + lastQuintile: int + + rankingParagraph: AgeRankingParagraphData + indexHistoryParagraph: AgeIndexHistoryParagraphData + ageHistogramParagraph: AgeHistogramParagraphData + + +class PartyTemplateDataNational(BaseModel): + partyDiversityIndex: float diff --git a/routers/scrapResultLocal.py b/routers/scrapResultLocal.py index b18e5dc..eb98dc3 100644 --- a/routers/scrapResultLocal.py +++ b/routers/scrapResultLocal.py @@ -71,7 +71,12 @@ async def getLocalTemplateData( # indexHistoryParagraph # ============================ years = list( - {doc["year"] async for doc in client.stats_db["age_hist"].find()} + { + doc["year"] + async for doc in client.stats_db["age_hist"].find( + {"councilorType": "local_councilor"} + ) + } ) years.sort() history_candidate = [ diff --git a/routers/scrapResultMetro.py b/routers/scrapResultMetro.py index cef0e72..fd1276d 100644 --- a/routers/scrapResultMetro.py +++ b/routers/scrapResultMetro.py @@ -67,7 +67,12 @@ async def getMetroTemplateData( # indexHistoryParagraph # ============================ years = list( - {doc["year"] async for doc in client.stats_db["age_hist"].find()} + { + doc["year"] + async for doc in client.stats_db["age_hist"].find( + {"councilorType": "metro_councilor"} + ) + } ) years.sort() history_candidate = [ diff --git a/routers/scrapResultNational.py b/routers/scrapResultNational.py new file mode 100644 index 0000000..9d59fdc --- /dev/null +++ b/routers/scrapResultNational.py @@ -0,0 +1,211 @@ +from typing import TypeVar +from fastapi import APIRouter +from model.BasicResponse import ErrorResponse, REGION_CODE_ERR +from model.MongoDB import client +from model.ScrapResultCommon import ( + GenderChartDataPoint, + AgeChartDataPoint, + PartyChartDataPoint, + FactorType, + ChartData, +) +from model.ScrapResultNational import ( + GenderTemplateDataNational, + AgeTemplateDataNational, + PartyTemplateDataNational, +) +from utils import diversity + + +router = APIRouter(prefix="/nationalCouncil", tags=["nationalCouncil"]) + +AGE_STAIR = 10 + + +@router.get("/template-data") +async def getNationalTemplateData( + factor: FactorType, +) -> ErrorResponse | GenderTemplateDataNational | AgeTemplateDataNational | PartyTemplateDataNational: + national_stat = await client.stats_db["diversity_index"].find_one( + {"national": True} + ) + + match factor: + case FactorType.gender: + return GenderTemplateDataNational.model_validate( + {"genderDiversityIndex": national_stat["genderDiversityIndex"]} + ) + + case FactorType.age: + # ============================ + # rankingParagraph + # ============================ + age_diversity_index = national_stat["ageDiversityIndex"] + + # ============================ + # indexHistoryParagraph + # ============================ + years = list( + { + doc["year"] + async for doc in client.stats_db["age_hist"].find( + {"councilorType": "national_councilor"} + ) + } + ) + years.sort() + history_candidate = [ + await client.stats_db["age_hist"].find_one( + { + "year": year, + "councilorType": "national_councilor", + "is_elected": False, + "method": "equal", + } + ) + for year in years + ] + history_elected = [ + await client.stats_db["age_hist"].find_one( + { + "year": year, + "councilorType": "national_councilor", + "is_elected": True, + "method": "equal", + } + ) + for year in years + ] + + # ============================ + # ageHistogramParagraph + # ============================ + age_stat_elected = ( + await client.stats_db["age_stat"] + .aggregate( + [ + { + "$match": { + "level": 2, + "councilorType": "national_councilor", + "is_elected": True, + } + }, + {"$sort": {"year": -1}}, + {"$limit": 1}, + ] + ) + .to_list(500) + )[0] + most_recent_year = age_stat_elected["year"] + age_stat_candidate = await client.stats_db["age_stat"].find_one( + { + "councilorType": "national_councilor", + "is_elected": False, + "year": most_recent_year, + } + ) + + return AgeTemplateDataNational.model_validate( + { + "rankingParagraph": { + "ageDiversityIndex": age_diversity_index, + }, + "indexHistoryParagraph": { + "mostRecentYear": years[-1], + "history": [ + { + "year": year, + "unit": (year - 2000) / 4 + 2, + "candidateCount": sum( + group["count"] + for group in history_candidate[idx]["data"] + ), + # "candidateCount": 0, + "candidateDiversityIndex": history_candidate[idx][ + "diversityIndex" + ], + "candidateDiversityRank": history_candidate[idx][ + "diversityRank" + ], + # "candidateDiversityIndex": 0.0, + # "candidateDiversityRank": 0, + "electedDiversityIndex": history_elected[idx][ + "diversityIndex" + ], + "electedDiversityRank": history_elected[idx][ + "diversityRank" + ], + } + for idx, year in enumerate(years) + ], + }, + "ageHistogramParagraph": { + "year": most_recent_year, + "candidateCount": age_stat_candidate["data"][0]["population"], + "electedCount": age_stat_elected["data"][0]["population"], + "firstQuintile": age_stat_elected["data"][0]["firstquintile"], + "lastQuintile": age_stat_elected["data"][0]["lastquintile"], + }, + } + ) + + case FactorType.party: + party_diversity_index = national_stat["partyDiversityIndex"] + return PartyTemplateDataNational.model_validate( + {"partyDiversityIndex": party_diversity_index} + ) + + +T = TypeVar( + "T", + GenderChartDataPoint, + AgeChartDataPoint, + PartyChartDataPoint, +) + + +@router.get("/chart-data") +async def getNationalChartData(factor: FactorType) -> ErrorResponse | ChartData[T]: + councilors = client.council_db["national_councilor"].find() + + match factor: + case FactorType.gender: + gender_list = [councilor["gender"] async for councilor in councilors] + gender_count = diversity.count(gender_list) + return ChartData[GenderChartDataPoint].model_validate( + { + "data": [ + {"gender": gender, "count": gender_count[gender]} + for gender in gender_count + ] + } + ) + + case FactorType.age: + age_list = [councilor["age"] async for councilor in councilors] + age_count = diversity.count(age_list, stair=AGE_STAIR) + return ChartData[AgeChartDataPoint].model_validate( + { + "data": [ + { + "minAge": age, + "maxAge": age + AGE_STAIR, + "count": age_count[age], + } + for age in age_count + ] + } + ) + + case FactorType.party: + party_list = [councilor["jdName"] async for councilor in councilors] + party_count = diversity.count(party_list) + return ChartData[PartyChartDataPoint].model_validate( + { + "data": [ + {"party": party, "count": party_count[party]} + for party in party_count + ] + } + ) From 68641bc9e3d0572ec48297d34b42f2476b79ca53 Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Mon, 27 Nov 2023 17:46:08 +0900 Subject: [PATCH 15/35] =?UTF-8?q?[feat]=20=EA=B5=AD=ED=9A=8C=EC=9D=98?= =?UTF-8?q?=EC=9B=90=20=EC=97=B0=EB=A0=B9=20=ED=9E=88=EC=8A=A4=ED=86=A0?= =?UTF-8?q?=EA=B7=B8=EB=9E=A8=20=EB=B0=98=ED=99=98=20endpoint=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/AgeHist.py | 4 ++++ routers/ageHist.py | 22 +++++++++++++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/model/AgeHist.py b/model/AgeHist.py index a92d140..f02659c 100644 --- a/model/AgeHist.py +++ b/model/AgeHist.py @@ -19,6 +19,10 @@ class AgeHistDataPoint(BaseModel): ageGroup: int +class NationalAgeHistData(BaseModel): + data: list[AgeHistDataPoint] + + class MetroAgeHistData(BaseModel): metroId: int data: list[AgeHistDataPoint] diff --git a/routers/ageHist.py b/routers/ageHist.py index 4a378ec..1497627 100644 --- a/routers/ageHist.py +++ b/routers/ageHist.py @@ -3,10 +3,26 @@ from model.AgeHist import AgeHistDataTypes, AgeHistMethodTypes, MetroAgeHistData -router = APIRouter() +router = APIRouter(prefix="/age-hist", tags=["age-hist"]) -@router.get("/age-hist/{metroId}") +@router.get("/") +async def getNationalAgeHistData( + ageHistType: AgeHistDataTypes, year: int, method: AgeHistMethodTypes +) -> BasicResponse.ErrorResponse | MetroAgeHistData: + histogram = await MongoDB.client.stats_db["age_hist"].find_one( + { + "councilorType": "national_councilor", + "is_elected": ageHistType == AgeHistDataTypes.elected, + "year": year, + "method": method, + } + ) + + return MetroAgeHistData.model_validate({"data": histogram["data"]}) + + +@router.get("/{metroId}") async def getMetroAgeHistData( metroId: int, ageHistType: AgeHistDataTypes, year: int, method: AgeHistMethodTypes ) -> BasicResponse.ErrorResponse | MetroAgeHistData: @@ -40,7 +56,7 @@ async def getMetroAgeHistData( ) -@router.get("/age-hist/{metroId}/{localId}") +@router.get("/{metroId}/{localId}") async def getLocalAgeHistData( metroId: int, localId: int, From 7cf18d13d7664da35407cd170fe5eb24d89201d2 Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Mon, 27 Nov 2023 21:34:44 +0900 Subject: [PATCH 16/35] =?UTF-8?q?[feat]=20=EB=8B=A4=EC=96=91=EC=84=B1=20?= =?UTF-8?q?=EC=A0=95=EB=B3=B4=20=EB=B0=98=ED=99=98=20endpoint=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 9 +- model/BasicResponse.py | 10 ++ model/ScrapResultLocal.py | 20 +++ model/ScrapResultMetro.py | 18 +++ model/ScrapResultNational.py | 14 +++ routers/ageHist.py | 181 ++++++++++++++++++++++----- routers/scrapResultLocal.py | 79 ++++++++++-- routers/scrapResultMetro.py | 222 ++++++++++++++++++++++----------- routers/scrapResultNational.py | 121 +++++++++++++----- 9 files changed, 524 insertions(+), 150 deletions(-) diff --git a/main.py b/main.py index 8c4f66c..21b0d14 100644 --- a/main.py +++ b/main.py @@ -1,6 +1,12 @@ from fastapi import FastAPI, Request from dotenv import load_dotenv -from routers import commonInfo, ageHist, scrapResultLocal, scrapResultMetro +from routers import ( + commonInfo, + ageHist, + scrapResultLocal, + scrapResultMetro, + scrapResultNational, +) from contextlib import asynccontextmanager from typing import Dict from model import MongoDB @@ -35,5 +41,6 @@ async def initMongo(app: FastAPI): app.include_router(scrapResultLocal.router) app.include_router(scrapResultMetro.router) +app.include_router(scrapResultNational.router) app.include_router(commonInfo.router) app.include_router(ageHist.router) diff --git a/model/BasicResponse.py b/model/BasicResponse.py index e991d0c..06d1217 100644 --- a/model/BasicResponse.py +++ b/model/BasicResponse.py @@ -4,6 +4,7 @@ SUCCESS = 200 REGION_CODE_ERR = 400 COLLECTION_NOT_EXIST_ERR = 600 +NO_DATA_ERROR = 800 class MessageResponse(BaseModel): @@ -15,3 +16,12 @@ class ErrorResponse(BaseModel): error: str code: int message: str + + +NO_DATA_ERROR_RESPONSE: ErrorResponse = ErrorResponse.model_validate( + { + "error": "NoDataError", + "code": NO_DATA_ERROR, + "message": "No data was retrieved with the provided input.", + } +) diff --git a/model/ScrapResultLocal.py b/model/ScrapResultLocal.py index 5a6d16c..9e4ee09 100644 --- a/model/ScrapResultLocal.py +++ b/model/ScrapResultLocal.py @@ -5,7 +5,18 @@ # = Template Data Types = # ============================================== class GenderTemplateDataLocal(BaseModel): + class GenderTemplateDataPoint(BaseModel): + year: int + malePop: int + femalePop: int + + metroId: int + localId: int genderDiversityIndex: float + current: GenderTemplateDataPoint + prev: GenderTemplateDataPoint + meanMalePop: float + meanFemalePop: float class AgeTemplateDataLocal(BaseModel): @@ -53,4 +64,13 @@ class AgeHistogramAreaData(BaseModel): class PartyTemplateDataLocal(BaseModel): + class PartyCountDataPoint(BaseModel): + party: str + count: int + + metroId: int + localId: int partyDiversityIndex: float + prevElected: list[PartyCountDataPoint] + currentElected: list[PartyCountDataPoint] + currentCandidate: list[PartyCountDataPoint] diff --git a/model/ScrapResultMetro.py b/model/ScrapResultMetro.py index 4dea5c9..8d60537 100644 --- a/model/ScrapResultMetro.py +++ b/model/ScrapResultMetro.py @@ -5,7 +5,17 @@ # = Template Data Types = # ============================================== class GenderTemplateDataMetro(BaseModel): + class GenderTemplateDataPoint(BaseModel): + year: int + malePop: int + femalePop: int + + metroId: int genderDiversityIndex: float + current: GenderTemplateDataPoint + prev: GenderTemplateDataPoint + meanMalePop: float + meanFemalePop: float class AgeTemplateDataMetro(BaseModel): @@ -52,4 +62,12 @@ class AgeHistogramAreaData(BaseModel): class PartyTemplateDataMetro(BaseModel): + class PartyCountDataPoint(BaseModel): + party: str + count: int + + metroId: int partyDiversityIndex: float + prevElected: list[PartyCountDataPoint] + currentElected: list[PartyCountDataPoint] + currentCandidate: list[PartyCountDataPoint] diff --git a/model/ScrapResultNational.py b/model/ScrapResultNational.py index b95d784..66c847c 100644 --- a/model/ScrapResultNational.py +++ b/model/ScrapResultNational.py @@ -5,7 +5,14 @@ # = Template Data Types = # ============================================== class GenderTemplateDataNational(BaseModel): + class GenderTemplateDataPoint(BaseModel): + year: int + malePop: int + femalePop: int + genderDiversityIndex: float + current: GenderTemplateDataPoint + prev: GenderTemplateDataPoint class AgeTemplateDataNational(BaseModel): @@ -38,4 +45,11 @@ class AgeHistogramParagraphData(BaseModel): class PartyTemplateDataNational(BaseModel): + class PartyCountDataPoint(BaseModel): + party: str + count: int + partyDiversityIndex: float + prevElected: list[PartyCountDataPoint] + currentElected: list[PartyCountDataPoint] + currentCandidate: list[PartyCountDataPoint] diff --git a/routers/ageHist.py b/routers/ageHist.py index 1497627..14ebec8 100644 --- a/routers/ageHist.py +++ b/routers/ageHist.py @@ -1,6 +1,11 @@ from fastapi import APIRouter from model import BasicResponse, MongoDB -from model.AgeHist import AgeHistDataTypes, AgeHistMethodTypes, MetroAgeHistData +from model.AgeHist import ( + AgeHistDataTypes, + AgeHistMethodTypes, + MetroAgeHistData, + NationalAgeHistData, +) router = APIRouter(prefix="/age-hist", tags=["age-hist"]) @@ -9,52 +14,155 @@ @router.get("/") async def getNationalAgeHistData( ageHistType: AgeHistDataTypes, year: int, method: AgeHistMethodTypes -) -> BasicResponse.ErrorResponse | MetroAgeHistData: - histogram = await MongoDB.client.stats_db["age_hist"].find_one( +) -> BasicResponse.ErrorResponse | NationalAgeHistData: + # histogram = await MongoDB.client.stats_db["age_hist"].find_one( + # { + # "councilorType": "national_councilor", + # "is_elected": ageHistType == AgeHistDataTypes.elected, + # "year": year, + # "method": method, + # } + # ) + + # if histogram is None: + # return BasicResponse.ErrorResponse.model_validate( + # { + # "error": "NoDataError", + # "code": BasicResponse.NO_DATA_ERROR, + # "message": "No data retrieved with the provided input.", + # } + # ) + + # return NationalAgeHistData.model_validate({"data": histogram["data"]}) + return NationalAgeHistData.model_validate( { - "councilorType": "national_councilor", - "is_elected": ageHistType == AgeHistDataTypes.elected, - "year": year, - "method": method, + "data": [ + { + "minAge": 21, + "maxAge": 22, + "count": 75, + "ageGroup": 0, + }, + { + "minAge": 22, + "maxAge": 23, + "count": 87, + "ageGroup": 1, + }, + { + "minAge": 29, + "maxAge": 30, + "count": 104, + "ageGroup": 2, + }, + { + "minAge": 45, + "maxAge": 46, + "count": 354, + "ageGroup": 2, + }, + { + "minAge": 46, + "maxAge": 47, + "count": 463, + "ageGroup": 3, + }, + { + "minAge": 63, + "maxAge": 64, + "count": 240, + "ageGroup": 4, + }, + ] } ) - return MetroAgeHistData.model_validate({"data": histogram["data"]}) - @router.get("/{metroId}") async def getMetroAgeHistData( metroId: int, ageHistType: AgeHistDataTypes, year: int, method: AgeHistMethodTypes ) -> BasicResponse.ErrorResponse | MetroAgeHistData: - if ( - await MongoDB.client.district_db["metro_district"].find_one( - {"metroId": metroId} - ) - is None - ): - return BasicResponse.ErrorResponse.model_validate( - { - "error": "RegionCodeError", - "code": BasicResponse.REGION_CODE_ERR, - "message": f"No metro district with metroId {metroId}.", - } - ) + # if ( + # await MongoDB.client.district_db["metro_district"].find_one( + # {"metroId": metroId} + # ) + # is None + # ): + # return BasicResponse.ErrorResponse.model_validate( + # { + # "error": "RegionCodeError", + # "code": BasicResponse.REGION_CODE_ERR, + # "message": f"No metro district with metroId {metroId}.", + # } + # ) - histogram = await MongoDB.client.stats_db["age_hist"].find_one( + # histogram = await MongoDB.client.stats_db["age_hist"].find_one( + # { + # "level": 1, + # "councilorType": "metro_councilor", + # "is_elected": ageHistType == AgeHistDataTypes.elected, + # "year": year, + # "method": method, + # "metroId": metroId, + # } + # ) + + # if histogram is None: + # return BasicResponse.ErrorResponse.model_validate( + # { + # "error": "NoDataError", + # "code": BasicResponse.NO_DATA_ERROR, + # "message": "No data retrieved with the provided input.", + # } + # ) + + # return MetroAgeHistData.model_validate( + # {"metroId": metroId, "data": histogram["data"]} + # ) + return MetroAgeHistData.model_validate( { - "level": 1, - "councilorType": "metro_councilor", - "is_elected": ageHistType == AgeHistDataTypes.elected, - "year": year, - "method": method, "metroId": metroId, + "data": [ + { + "minAge": 21, + "maxAge": 22, + "count": 75, + "ageGroup": 0, + }, + { + "minAge": 22, + "maxAge": 23, + "count": 87, + "ageGroup": 1, + }, + { + "minAge": 29, + "maxAge": 30, + "count": 104, + "ageGroup": 2, + }, + { + "minAge": 45, + "maxAge": 46, + "count": 354, + "ageGroup": 2, + }, + { + "minAge": 46, + "maxAge": 47, + "count": 463, + "ageGroup": 3, + }, + { + "minAge": 63, + "maxAge": 64, + "count": 240, + "ageGroup": 4, + }, + ], } ) - return MetroAgeHistData.model_validate( - {"metroId": metroId, "data": histogram["data"]} - ) - @router.get("/{metroId}/{localId}") async def getLocalAgeHistData( @@ -90,6 +198,15 @@ async def getLocalAgeHistData( } ) + if histogram is None: + return BasicResponse.ErrorResponse.model_validate( + { + "error": "NoDataError", + "code": BasicResponse.NO_DATA_ERROR, + "message": "No data retrieved with the provided input.", + } + ) + return MetroAgeHistData.model_validate( {"metroId": metroId, "localId": localId, "data": histogram["data"]} ) diff --git a/routers/scrapResultLocal.py b/routers/scrapResultLocal.py index eb98dc3..81429e6 100644 --- a/routers/scrapResultLocal.py +++ b/routers/scrapResultLocal.py @@ -1,6 +1,6 @@ from typing import TypeVar from fastapi import APIRouter -from model.BasicResponse import ErrorResponse, REGION_CODE_ERR +from model.BasicResponse import ErrorResponse, REGION_CODE_ERR, NO_DATA_ERROR_RESPONSE from model.MongoDB import client from model.ScrapResultCommon import ( GenderChartDataPoint, @@ -42,10 +42,36 @@ async def getLocalTemplateData( local_stat = await client.stats_db["diversity_index"].find_one({"localId": localId}) + if local_stat is None: + return NO_DATA_ERROR_RESPONSE + match factor: case FactorType.gender: + councilors = ( + await client.council_db["local_councilor"] + .find({"metroId": metroId, "localId": localId}) + .to_list(500) + ) + gender_list = [councilor["gender"] for councilor in councilors] + gender_count = diversity.count(gender_list) return GenderTemplateDataLocal.model_validate( - {"genderDiversityIndex": local_stat["genderDiversityIndex"]} + { + "metroId": metroId, + "localId": localId, + "genderDiversityIndex": local_stat["genderDiversityIndex"], + "current": { + "year": 2022, + "malePop": gender_count["남"], + "femalePop": gender_count["여"], + }, + "prev": { + "year": 0, + "malePop": 0, + "femalePop": 0, + }, + "meanMalePop": 0.0, + "meanFemalePop": 0.0, + } ) case FactorType.age: @@ -238,8 +264,40 @@ async def getLocalTemplateData( case FactorType.party: party_diversity_index = local_stat["partyDiversityIndex"] + councilors = ( + await client.council_db["local_councilor"] + .find({"metroId": metroId, "localId": localId}) + .to_list(500) + ) + party_list = [councilor["jdName"] for councilor in councilors] + party_count = diversity.count(party_list) + + candidates = ( + await client.council_db["local_councilor_candidate"] + .find({"metroId": metroId, "localId": localId}) + .to_list(500) + ) + candidate_party_list = [candidate["jdName"] for candidate in candidates] + candidate_party_count = diversity.count(candidate_party_list) return PartyTemplateDataLocal.model_validate( - {"partyDiversityIndex": party_diversity_index} + { + "metroId": metroId, + "localId": localId, + "partyDiversityIndex": party_diversity_index, + "prevElected": [ + {"party": "포도당", "count": 6}, + {"party": "유당", "count": 6}, + {"party": "과당", "count": 5}, + ], + "currentElected": [ + {"party": party, "count": party_count[party]} + for party in party_count + ], + "currentCandidate": [ + {"party": party, "count": candidate_party_count[party]} + for party in candidate_party_count + ], + } ) @@ -269,11 +327,18 @@ async def getLocalChartData( } ) - councilors = client.council_db["local_councilor"].find({"localId": localId}) + councilors = ( + await client.council_db["local_councilor"] + .find({"localId": localId}) + .to_list(5000) + ) + + if councilors is None or len(councilors) == 0: + return NO_DATA_ERROR_RESPONSE match factor: case FactorType.gender: - gender_list = [councilor["gender"] async for councilor in councilors] + gender_list = [councilor["gender"] for councilor in councilors] gender_count = diversity.count(gender_list) return ChartData[GenderChartDataPoint].model_validate( { @@ -285,7 +350,7 @@ async def getLocalChartData( ) case FactorType.age: - age_list = [councilor["age"] async for councilor in councilors] + age_list = [councilor["age"] for councilor in councilors] age_count = diversity.count(age_list, stair=AGE_STAIR) return ChartData[AgeChartDataPoint].model_validate( { @@ -301,7 +366,7 @@ async def getLocalChartData( ) case FactorType.party: - party_list = [councilor["jdName"] async for councilor in councilors] + party_list = [councilor["jdName"] for councilor in councilors] party_count = diversity.count(party_list) return ChartData[PartyChartDataPoint].model_validate( { diff --git a/routers/scrapResultMetro.py b/routers/scrapResultMetro.py index fd1276d..b655d91 100644 --- a/routers/scrapResultMetro.py +++ b/routers/scrapResultMetro.py @@ -42,8 +42,30 @@ async def getMetroTemplateData( match factor: case FactorType.gender: + councilors = ( + await client.council_db["metro_councilor"] + .find({"metroId": metroId}) + .to_list(500) + ) + gender_list = [councilor["gender"] for councilor in councilors] + gender_count = diversity.count(gender_list) return GenderTemplateDataMetro.model_validate( - {"genderDiversityIndex": metro_stat["genderDiversityIndex"]} + { + "metroId": metroId, + "genderDiversityIndex": metro_stat["genderDiversityIndex"], + "current": { + "year": 2022, + "malePop": gender_count["남"], + "femalePop": gender_count["여"], + }, + "prev": { + "year": 0, + "malePop": 0, + "femalePop": 0, + }, + "meanMalePop": 0.0, + "meanFemalePop": 0.0, + } ) case FactorType.age: @@ -105,65 +127,65 @@ async def getMetroTemplateData( # ============================ # ageHistogramParagraph # ============================ - age_stat_elected = ( - await client.stats_db["age_stat"] - .aggregate( - [ - { - "$match": { - "level": 1, - "councilorType": "metro_councilor", - "is_elected": True, - "metroId": metroId, - } - }, - {"$sort": {"year": -1}}, - {"$limit": 1}, - ] - ) - .to_list(500) - )[0] - most_recent_year = age_stat_elected["year"] - age_stat_candidate = await client.stats_db["age_stat"].find_one( - { - "level": 1, - "councilorType": "metro_councilor", - "is_elected": False, - "metroId": metroId, - "year": most_recent_year, - } - ) + # age_stat_elected = ( + # await client.stats_db["age_hist"] + # .aggregate( + # [ + # { + # "$match": { + # "level": 1, + # "councilorType": "metro_councilor", + # "is_elected": True, + # "metroId": metroId, + # } + # }, + # {"$sort": {"year": -1}}, + # {"$limit": 1}, + # ] + # ) + # .to_list(500) + # )[0] + # most_recent_year = age_stat_elected["year"] + # age_stat_candidate = await client.stats_db["age_hist"].find_one( + # { + # "level": 1, + # "councilorType": "metro_councilor", + # "is_elected": False, + # "metroId": metroId, + # "year": most_recent_year, + # } + # ) - divArea_id = ( - await client.stats_db["diversity_index"].find_one( - {"metroId": {"$exists": True}, "ageDiversityRank": 1} - ) - )["metroId"] - divArea = await client.stats_db["age_stat"].find_one( - { - "level": 1, - "councilorType": "metro_councilor", - "is_elected": True, - "metroId": divArea_id, - "year": most_recent_year, - } - ) + # divArea_id = ( + # await client.stats_db["diversity_index"].find_one( + # {"metroId": {"$exists": True}, "ageDiversityRank": 1} + # ) + # )["metroId"] + # divArea = await client.stats_db["age_hist"].find_one( + # { + # "level": 1, + # "councilorType": "metro_councilor", + # "is_elected": True, + # "metroId": divArea_id, + # "year": most_recent_year, + # } + # ) - uniArea_id = ( - await client.stats_db["diversity_index"].find_one( - # {"metroId": {"$exists": True}, "ageDiversityRank": 17} - {"metroId": {"$exists": True}, "ageDiversityRank": 15} - ) - )["metroId"] - uniArea = await client.stats_db["age_stat"].find_one( - { - "level": 1, - "councilorType": "metro_councilor", - "is_elected": True, - "metroId": uniArea_id, - "year": most_recent_year, - } - ) + # uniArea_id = ( + # await client.stats_db["diversity_index"].find_one( + # # {"metroId": {"$exists": True}, "ageDiversityRank": 17} + # {"metroId": {"$exists": True}, "ageDiversityRank": 15} + # ) + # )["metroId"] + # uniArea = await client.stats_db["age_hist"].find_one( + # { + # "level": 1, + # "councilorType": "metro_councilor", + # "is_elected": True, + # "metroId": uniArea_id, + # "year": most_recent_year, + # } + # ) return AgeTemplateDataMetro.model_validate( { @@ -195,31 +217,48 @@ async def getMetroTemplateData( "candidateDiversityRank": history_candidate[idx][ "diversityRank" ], - "electedDiversityIndex": history_elected[idx][ - "diversityIndex" - ], - "electedDiversityRank": history_elected[idx][ - "diversityRank" - ], + # "electedDiversityIndex": history_elected[idx][ + # "diversityIndex" + # ], + # "electedDiversityRank": history_elected[idx][ + # "diversityRank" + # ], + "electedDiversityIndex": 0.003141592, + "electedDiversityRank": 99999, } for idx, year in enumerate(years) ], }, "ageHistogramParagraph": { - "year": most_recent_year, - "candidateCount": age_stat_candidate["data"][0]["population"], - "electedCount": age_stat_elected["data"][0]["population"], - "firstQuintile": age_stat_elected["data"][0]["firstquintile"], - "lastQuintile": age_stat_elected["data"][0]["lastquintile"], + # "year": most_recent_year, + # "candidateCount": age_stat_candidate["data"][0]["population"], + # "electedCount": age_stat_elected["data"][0]["population"], + # "firstQuintile": age_stat_elected["data"][0]["firstquintile"], + # "lastQuintile": age_stat_elected["data"][0]["lastquintile"], + # "divArea": { + # "metroId": divArea_id, + # "firstQuintile": divArea["data"][0]["firstquintile"], + # "lastQuintile": divArea["data"][0]["lastquintile"], + # }, + # "uniArea": { + # "metroId": uniArea_id, + # "firstQuintile": uniArea["data"][0]["firstquintile"], + # "lastQuintile": uniArea["data"][0]["lastquintile"], + # }, + "year": 2022, + "candidateCount": 99999, + "electedCount": 88888, + "firstQuintile": 74, + "lastQuintile": 21, "divArea": { - "metroId": divArea_id, - "firstQuintile": divArea["data"][0]["firstquintile"], - "lastQuintile": divArea["data"][0]["lastquintile"], + "metroId": 1, + "firstQuintile": 45, + "lastQuintile": 20, }, "uniArea": { - "metroId": uniArea_id, - "firstQuintile": uniArea["data"][0]["firstquintile"], - "lastQuintile": uniArea["data"][0]["lastquintile"], + "metroId": 8, + "firstQuintile": 86, + "lastQuintile": 43, }, }, } @@ -227,8 +266,39 @@ async def getMetroTemplateData( case FactorType.party: party_diversity_index = metro_stat["partyDiversityIndex"] + councilors = ( + await client.council_db["metro_councilor"] + .find({"metroId": metroId}) + .to_list(500) + ) + party_list = [councilor["jdName"] for councilor in councilors] + party_count = diversity.count(party_list) + + candidates = ( + await client.council_db["metro_councilor_candidate"] + .find({"metroId": metroId}) + .to_list(500) + ) + candidate_party_list = [candidate["jdName"] for candidate in candidates] + candidate_party_count = diversity.count(candidate_party_list) return PartyTemplateDataMetro.model_validate( - {"partyDiversityIndex": party_diversity_index} + { + "metroId": metroId, + "partyDiversityIndex": party_diversity_index, + "prevElected": [ + {"party": "포도당", "count": 6}, + {"party": "유당", "count": 6}, + {"party": "과당", "count": 5}, + ], + "currentElected": [ + {"party": party, "count": party_count[party]} + for party in party_count + ], + "currentCandidate": [ + {"party": party, "count": candidate_party_count[party]} + for party in candidate_party_count + ], + } ) diff --git a/routers/scrapResultNational.py b/routers/scrapResultNational.py index 9d59fdc..a09f412 100644 --- a/routers/scrapResultNational.py +++ b/routers/scrapResultNational.py @@ -1,6 +1,6 @@ from typing import TypeVar from fastapi import APIRouter -from model.BasicResponse import ErrorResponse, REGION_CODE_ERR +from model.BasicResponse import ErrorResponse, NO_DATA_ERROR_RESPONSE from model.MongoDB import client from model.ScrapResultCommon import ( GenderChartDataPoint, @@ -29,11 +29,32 @@ async def getNationalTemplateData( national_stat = await client.stats_db["diversity_index"].find_one( {"national": True} ) + if national_stat is None: + return NO_DATA_ERROR_RESPONSE match factor: case FactorType.gender: + councilors = ( + await client.council_db["national_councilor"].find().to_list(500) + ) + ( + await client.council_db["national_councilor_global"].find().to_list(500) + ) + gender_list = [councilor["gender"] for councilor in councilors] + gender_count = diversity.count(gender_list) return GenderTemplateDataNational.model_validate( - {"genderDiversityIndex": national_stat["genderDiversityIndex"]} + { + "genderDiversityIndex": national_stat["genderDiversityIndex"], + "current": { + "year": 2022, + "malePop": gender_count["남"], + "femalePop": gender_count["여"], + }, + "prev": { + "year": 0, + "malePop": 0, + "femalePop": 0, + }, + } ) case FactorType.age: @@ -80,31 +101,31 @@ async def getNationalTemplateData( # ============================ # ageHistogramParagraph # ============================ - age_stat_elected = ( - await client.stats_db["age_stat"] - .aggregate( - [ - { - "$match": { - "level": 2, - "councilorType": "national_councilor", - "is_elected": True, - } - }, - {"$sort": {"year": -1}}, - {"$limit": 1}, - ] - ) - .to_list(500) - )[0] - most_recent_year = age_stat_elected["year"] - age_stat_candidate = await client.stats_db["age_stat"].find_one( - { - "councilorType": "national_councilor", - "is_elected": False, - "year": most_recent_year, - } - ) + # age_stat_elected = ( + # await client.stats_db["age_stat"] + # .aggregate( + # [ + # { + # "$match": { + # "level": 2, + # "councilorType": "national_councilor", + # "is_elected": True, + # } + # }, + # {"$sort": {"year": -1}}, + # {"$limit": 1}, + # ] + # ) + # .to_list(500) + # )[0] + # most_recent_year = age_stat_elected["year"] + # age_stat_candidate = await client.stats_db["age_stat"].find_one( + # { + # "councilorType": "national_councilor", + # "is_elected": False, + # "year": most_recent_year, + # } + # ) return AgeTemplateDataNational.model_validate( { @@ -112,7 +133,8 @@ async def getNationalTemplateData( "ageDiversityIndex": age_diversity_index, }, "indexHistoryParagraph": { - "mostRecentYear": years[-1], + # "mostRecentYear": years[-1], + "mostRecentYear": 2022, "history": [ { "year": year, @@ -140,20 +162,51 @@ async def getNationalTemplateData( for idx, year in enumerate(years) ], }, + # "ageHistogramParagraph": { + # "year": most_recent_year, + # "candidateCount": age_stat_candidate["data"][0]["population"], + # "electedCount": age_stat_elected["data"][0]["population"], + # "firstQuintile": age_stat_elected["data"][0]["firstquintile"], + # "lastQuintile": age_stat_elected["data"][0]["lastquintile"], + # }, "ageHistogramParagraph": { - "year": most_recent_year, - "candidateCount": age_stat_candidate["data"][0]["population"], - "electedCount": age_stat_elected["data"][0]["population"], - "firstQuintile": age_stat_elected["data"][0]["firstquintile"], - "lastQuintile": age_stat_elected["data"][0]["lastquintile"], + "year": 2022, + "candidateCount": 99999, + "electedCount": 88888, + "firstQuintile": 98, + "lastQuintile": 18, }, } ) case FactorType.party: party_diversity_index = national_stat["partyDiversityIndex"] + councilors = ( + await client.council_db["national_councilor"].find().to_list(500) + ) + ( + await client.council_db["national_councilor_global"].find().to_list(500) + ) + party_list = [councilor["jdName"] for councilor in councilors] + party_count = diversity.count(party_list) + return PartyTemplateDataNational.model_validate( - {"partyDiversityIndex": party_diversity_index} + { + "partyDiversityIndex": party_diversity_index, + "prevElected": [ + {"party": "포도당", "count": 6}, + {"party": "유당", "count": 6}, + {"party": "과당", "count": 5}, + ], + "currentElected": [ + {"party": party, "count": party_count[party]} + for party in party_count + ], + "currentCandidate": [ + {"party": "포도당", "count": 6}, + {"party": "유당", "count": 6}, + {"party": "과당", "count": 5}, + ], + } ) From 4ee89bd6871f4942fbf67d267ed606085b68de69 Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Tue, 28 Nov 2023 23:21:37 +0900 Subject: [PATCH 17/35] =?UTF-8?q?[feat]=20=EC=84=B1=EB=B3=84,=20=EC=A0=95?= =?UTF-8?q?=EB=8B=B9=20=ED=86=B5=EA=B3=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EB=B0=98=ED=99=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/scrapResultLocal.py | 283 ++++++++++++++++++++++++++------- routers/scrapResultMetro.py | 278 ++++++++++++++++++++++++++------ routers/scrapResultNational.py | 239 ++++++++++++++++++++++------ 3 files changed, 640 insertions(+), 160 deletions(-) diff --git a/routers/scrapResultLocal.py b/routers/scrapResultLocal.py index 81429e6..8cd1383 100644 --- a/routers/scrapResultLocal.py +++ b/routers/scrapResultLocal.py @@ -47,30 +47,91 @@ async def getLocalTemplateData( match factor: case FactorType.gender: - councilors = ( - await client.council_db["local_councilor"] - .find({"metroId": metroId, "localId": localId}) + years = list( + { + doc["year"] + async for doc in client.stats_db["gender_hist"].find( + { + "councilorType": "local_councilor", + "level": 2, + "is_elected": True, + "localId": localId, + "metroId": metroId, + } + ) + } + ) + years.sort() + assert len(years) >= 2 + + current = await client.stats_db["gender_hist"].find_one( + { + "councilorType": "local_councilor", + "level": 2, + "is_elected": True, + "localId": localId, + "metroId": metroId, + "year": years[-1], + } + ) + + previous = await client.stats_db["gender_hist"].find_one( + { + "councilorType": "local_councilor", + "level": 2, + "is_elected": True, + "localId": localId, + "metroId": metroId, + "year": years[-2], + } + ) + + current_all = ( + await client.stats_db["gender_hist"] + .aggregate( + [ + { + "$match": { + "councilorType": "local_councilor", + "level": 2, + "is_elected": True, + "year": years[-1], + } + }, + { + "$group": { + "_id": None, + "male_tot": {"$sum": "$남"}, + "female_tot": {"$sum": "$여"}, + "district_cnt": {"$sum": 1}, + } + }, + ] + ) .to_list(500) ) - gender_list = [councilor["gender"] for councilor in councilors] - gender_count = diversity.count(gender_list) + assert len(current_all) == 1 + current_all = current_all[0] + return GenderTemplateDataLocal.model_validate( { "metroId": metroId, "localId": localId, "genderDiversityIndex": local_stat["genderDiversityIndex"], "current": { - "year": 2022, - "malePop": gender_count["남"], - "femalePop": gender_count["여"], + "year": years[-1], + "malePop": current["남"], + "femalePop": current["여"], }, "prev": { - "year": 0, - "malePop": 0, - "femalePop": 0, + "year": years[-2], + "malePop": previous["남"], + "femalePop": previous["여"], }, - "meanMalePop": 0.0, - "meanFemalePop": 0.0, + "meanMalePop": current_all["male_tot"] + / current_all["district_cnt"], + "meanFemalePop": current_all["female_tot"] + / current_all["district_cnt"], } ) @@ -264,55 +325,111 @@ async def getLocalTemplateData( case FactorType.party: party_diversity_index = local_stat["partyDiversityIndex"] - councilors = ( - await client.council_db["local_councilor"] - .find({"metroId": metroId, "localId": localId}) - .to_list(500) + years = list( + { + doc["year"] + async for doc in client.stats_db["party_hist"].find( + { + "councilorType": "local_councilor", + "level": 2, + "is_elected": True, + "localId": localId, + "metroId": metroId, + } + ) + } ) - party_list = [councilor["jdName"] for councilor in councilors] - party_count = diversity.count(party_list) + years.sort() + assert len(years) >= 2 - candidates = ( - await client.council_db["local_councilor_candidate"] - .find({"metroId": metroId, "localId": localId}) - .to_list(500) + current_elected = client.stats_db["party_hist"].find( + { + "councilorType": "local_councilor", + "level": 2, + "is_elected": True, + "localId": localId, + "metroId": metroId, + "year": years[-1], + }, + { + "_id": 0, + "councilorType": 0, + "level": 0, + "is_elected": 0, + "localId": 0, + "metroId": 0, + "year": 0, + }, + ) + current_candidate = client.stats_db["party_hist"].find( + { + "councilorType": "local_councilor", + "level": 2, + "is_elected": False, + "localId": localId, + "metroId": metroId, + "year": years[-1], + }, + { + "_id": 0, + "councilorType": 0, + "level": 0, + "is_elected": 0, + "localId": 0, + "metroId": 0, + "year": 0, + }, + ) + previous = client.stats_db["party_hist"].find( + { + "councilorType": "local_councilor", + "level": 2, + "is_elected": True, + "localId": localId, + "metroId": metroId, + "year": years[-2], + }, + { + "_id": 0, + "councilorType": 0, + "level": 0, + "is_elected": 0, + "localId": 0, + "metroId": 0, + "year": 0, + }, ) - candidate_party_list = [candidate["jdName"] for candidate in candidates] - candidate_party_count = diversity.count(candidate_party_list) + return PartyTemplateDataLocal.model_validate( { "metroId": metroId, "localId": localId, "partyDiversityIndex": party_diversity_index, "prevElected": [ - {"party": "포도당", "count": 6}, - {"party": "유당", "count": 6}, - {"party": "과당", "count": 5}, + {"party": party, "count": doc[party]} + async for doc in previous + for party in doc ], "currentElected": [ - {"party": party, "count": party_count[party]} - for party in party_count + {"party": party, "count": doc[party]} + async for doc in current_elected + for party in doc ], "currentCandidate": [ - {"party": party, "count": candidate_party_count[party]} - for party in candidate_party_count + {"party": party, "count": doc[party]} + async for doc in current_candidate + for party in doc ], } ) -T = TypeVar( - "T", - GenderChartDataPoint, - AgeChartDataPoint, - PartyChartDataPoint, -) - - @router.get("/chart-data/{metroId}/{localId}") async def getLocalChartData( metroId: int, localId: int, factor: FactorType -) -> ErrorResponse | ChartData[T]: +) -> ErrorResponse | ChartData[GenderChartDataPoint] | ChartData[ + AgeChartDataPoint +] | ChartData[PartyChartDataPoint]: if ( await client.district_db["local_district"].find_one( {"localId": localId, "metroId": metroId} @@ -327,52 +444,98 @@ async def getLocalChartData( } ) - councilors = ( - await client.council_db["local_councilor"] - .find({"localId": localId}) - .to_list(5000) - ) - - if councilors is None or len(councilors) == 0: - return NO_DATA_ERROR_RESPONSE - match factor: case FactorType.gender: - gender_list = [councilor["gender"] for councilor in councilors] - gender_count = diversity.count(gender_list) + gender_cnt = ( + await client.stats_db["gender_hist"] + .find( + { + "councilorType": "local_councilor", + "level": 2, + "is_elected": True, + "localId": localId, + "metroId": metroId, + } + ) + .sort({"year": -1}) + .limit(1) + .to_list(5) + )[0] + return ChartData[GenderChartDataPoint].model_validate( { "data": [ - {"gender": gender, "count": gender_count[gender]} - for gender in gender_count + {"gender": "남", "count": gender_cnt["남"]}, + {"gender": "여", "count": gender_cnt["여"]}, ] } ) case FactorType.age: - age_list = [councilor["age"] for councilor in councilors] - age_count = diversity.count(age_list, stair=AGE_STAIR) + age_cnt = ( + await client.stats_db["age_hist"] + .find( + { + "councilorType": "local_councilor", + "level": 2, + "is_elected": True, + "method": "equal", + "localId": localId, + "metroId": metroId, + } + ) + .sort({"year": -1}) + .limit(1) + .to_list(5) + )[0] + age_list = [ + age["minAge"] for age in age_cnt["data"] for _ in range(age["count"]) + ] + age_stair = diversity.count(age_list, stair=AGE_STAIR) return ChartData[AgeChartDataPoint].model_validate( { "data": [ { "minAge": age, "maxAge": age + AGE_STAIR, - "count": age_count[age], + "count": age_stair[age], } - for age in age_count + for age in age_stair ] } ) case FactorType.party: - party_list = [councilor["jdName"] for councilor in councilors] - party_count = diversity.count(party_list) + party_count = ( + await client.stats_db["party_hist"] + .find( + { + "councilorType": "local_councilor", + "level": 2, + "is_elected": True, + "localId": localId, + "metroId": metroId, + } + ) + .sort({"year": -1}) + .limit(1) + .to_list(5) + )[0] return ChartData[PartyChartDataPoint].model_validate( { "data": [ {"party": party, "count": party_count[party]} for party in party_count + if party + not in [ + "_id", + "councilorType", + "level", + "is_elected", + "localId", + "metroId", + "year", + ] ] } ) diff --git a/routers/scrapResultMetro.py b/routers/scrapResultMetro.py index b655d91..28cd395 100644 --- a/routers/scrapResultMetro.py +++ b/routers/scrapResultMetro.py @@ -42,29 +42,87 @@ async def getMetroTemplateData( match factor: case FactorType.gender: - councilors = ( - await client.council_db["metro_councilor"] - .find({"metroId": metroId}) + years = list( + { + doc["year"] + async for doc in client.stats_db["gender_hist"].find( + { + "councilorType": "metro_councilor", + "level": 1, + "is_elected": True, + "metroId": metroId, + } + ) + } + ) + years.sort() + assert len(years) >= 2 + + current = await client.stats_db["gender_hist"].find_one( + { + "councilorType": "metro_councilor", + "level": 1, + "is_elected": True, + "metroId": metroId, + "year": years[-1], + } + ) + + previous = await client.stats_db["gender_hist"].find_one( + { + "councilorType": "metro_councilor", + "level": 1, + "is_elected": True, + "metroId": metroId, + "year": years[-2], + } + ) + + current_all = ( + await client.stats_db["gender_hist"] + .aggregate( + [ + { + "$match": { + "councilorType": "metro_councilor", + "level": 1, + "is_elected": True, + "year": years[-1], + } + }, + { + "$group": { + "_id": None, + "male_tot": {"$sum": "$남"}, + "female_tot": {"$sum": "$여"}, + "district_cnt": {"$sum": 1}, + } + }, + ] + ) .to_list(500) ) - gender_list = [councilor["gender"] for councilor in councilors] - gender_count = diversity.count(gender_list) + assert len(current_all) == 1 + current_all = current_all[0] + return GenderTemplateDataMetro.model_validate( { "metroId": metroId, "genderDiversityIndex": metro_stat["genderDiversityIndex"], "current": { - "year": 2022, - "malePop": gender_count["남"], - "femalePop": gender_count["여"], + "year": years[-1], + "malePop": current["남"], + "femalePop": current["여"], }, "prev": { - "year": 0, - "malePop": 0, - "femalePop": 0, + "year": years[-2], + "malePop": previous["남"], + "femalePop": previous["여"], }, - "meanMalePop": 0.0, - "meanFemalePop": 0.0, + "meanMalePop": current_all["male_tot"] + / current_all["district_cnt"], + "meanFemalePop": current_all["female_tot"] + / current_all["district_cnt"], } ) @@ -266,37 +324,92 @@ async def getMetroTemplateData( case FactorType.party: party_diversity_index = metro_stat["partyDiversityIndex"] - councilors = ( - await client.council_db["metro_councilor"] - .find({"metroId": metroId}) - .to_list(500) + years = list( + { + doc["year"] + async for doc in client.stats_db["party_hist"].find( + { + "councilorType": "metro_councilor", + "level": 1, + "is_elected": True, + "metroId": metroId, + } + ) + } ) - party_list = [councilor["jdName"] for councilor in councilors] - party_count = diversity.count(party_list) + years.sort() + assert len(years) >= 2 - candidates = ( - await client.council_db["metro_councilor_candidate"] - .find({"metroId": metroId}) - .to_list(500) + current_elected = client.stats_db["party_hist"].find( + { + "councilorType": "metro_councilor", + "level": 1, + "is_elected": True, + "metroId": metroId, + "year": years[-1], + }, + { + "_id": 0, + "councilorType": 0, + "level": 0, + "is_elected": 0, + "metroId": 0, + "year": 0, + }, + ) + current_candidate = client.stats_db["party_hist"].find( + { + "councilorType": "metro_councilor", + "level": 1, + "is_elected": False, + "metroId": metroId, + "year": years[-1], + }, + { + "_id": 0, + "councilorType": 0, + "level": 0, + "is_elected": 0, + "metroId": 0, + "year": 0, + }, ) - candidate_party_list = [candidate["jdName"] for candidate in candidates] - candidate_party_count = diversity.count(candidate_party_list) + previous = client.stats_db["party_hist"].find( + { + "councilorType": "metro_councilor", + "level": 1, + "is_elected": True, + "metroId": metroId, + "year": years[-2], + }, + { + "_id": 0, + "councilorType": 0, + "level": 0, + "is_elected": 0, + "metroId": 0, + "year": 0, + }, + ) + return PartyTemplateDataMetro.model_validate( { "metroId": metroId, "partyDiversityIndex": party_diversity_index, "prevElected": [ - {"party": "포도당", "count": 6}, - {"party": "유당", "count": 6}, - {"party": "과당", "count": 5}, + {"party": party, "count": doc[party]} + async for doc in previous + for party in doc ], "currentElected": [ - {"party": party, "count": party_count[party]} - for party in party_count + {"party": party, "count": doc[party]} + async for doc in current_elected + for party in doc ], "currentCandidate": [ - {"party": party, "count": candidate_party_count[party]} - for party in candidate_party_count + {"party": party, "count": doc[party]} + async for doc in current_candidate + for party in doc ], } ) @@ -311,9 +424,11 @@ async def getMetroTemplateData( @router.get("/chart-data/{metroId}") -async def getLocalChartData( +async def getMetroChartData( metroId: int, factor: FactorType -) -> ErrorResponse | ChartData[T]: +) -> ErrorResponse | ChartData[GenderChartDataPoint] | ChartData[ + AgeChartDataPoint +] | ChartData[PartyChartDataPoint]: if ( await client.district_db["metro_district"].find_one({"metroId": metroId}) is None @@ -326,45 +441,110 @@ async def getLocalChartData( } ) - councilors = client.council_db["metro_councilor"].find({"metroId": metroId}) - match factor: case FactorType.gender: - gender_list = [councilor["gender"] async for councilor in councilors] - gender_count = diversity.count(gender_list) + gender_cnt = ( + await client.stats_db["gender_hist"] + .find( + { + "councilorType": "metro_councilor", + "level": 1, + "is_elected": True, + "metroId": metroId, + } + ) + .sort({"year": -1}) + .limit(1) + .to_list(5) + )[0] + return ChartData[GenderChartDataPoint].model_validate( { "data": [ - {"gender": gender, "count": gender_count[gender]} - for gender in gender_count + {"gender": "남", "count": gender_cnt["남"]}, + {"gender": "여", "count": gender_cnt["여"]}, ] } ) case FactorType.age: - age_list = [councilor["age"] async for councilor in councilors] - age_count = diversity.count(age_list, stair=AGE_STAIR) + # age_cnt = ( + # await client.stats_db["age_hist"] + # .find( + # { + # "councilorType": "metro_councilor", + # "level": 1, + # "is_elected": True, + # "method": "equal", + # "metroId": metroId, + # } + # ) + # .sort({"year": -1}) + # .limit(1) + # .to_list(5) + # )[0] + # age_list = [ + # age["minAge"] for age in age_cnt["data"] for _ in range(age["count"]) + # ] + # age_stair = diversity.count(age_list, stair=AGE_STAIR) + # return ChartData[AgeChartDataPoint].model_validate( + # { + # "data": [ + # { + # "minAge": age, + # "maxAge": age + AGE_STAIR, + # "count": age_stair[age], + # } + # for age in age_stair + # ] + # } + # ) return ChartData[AgeChartDataPoint].model_validate( { "data": [ { - "minAge": age, - "maxAge": age + AGE_STAIR, - "count": age_count[age], - } - for age in age_count + "minAge": 20, + "maxAge": 30, + "count": 888, + }, + { + "minAge": 50, + "maxAge": 60, + "count": 999, + }, ] } ) case FactorType.party: - party_list = [councilor["jdName"] async for councilor in councilors] - party_count = diversity.count(party_list) + party_count = ( + await client.stats_db["party_hist"] + .find( + { + "councilorType": "metro_councilor", + "level": 1, + "is_elected": True, + "metroId": metroId, + } + ) + .sort({"year": -1}) + .limit(1) + .to_list(5) + )[0] return ChartData[PartyChartDataPoint].model_validate( { "data": [ {"party": party, "count": party_count[party]} for party in party_count + if party + not in [ + "_id", + "councilorType", + "level", + "is_elected", + "metroId", + "year", + ] ] } ) diff --git a/routers/scrapResultNational.py b/routers/scrapResultNational.py index a09f412..3cee913 100644 --- a/routers/scrapResultNational.py +++ b/routers/scrapResultNational.py @@ -34,25 +34,51 @@ async def getNationalTemplateData( match factor: case FactorType.gender: - councilors = ( - await client.council_db["national_councilor"].find().to_list(500) - ) + ( - await client.council_db["national_councilor_global"].find().to_list(500) + years = list( + { + doc["year"] + async for doc in client.stats_db["gender_hist"].find( + { + "councilorType": "national_councilor", + "level": 0, + "is_elected": True, + } + ) + } ) - gender_list = [councilor["gender"] for councilor in councilors] - gender_count = diversity.count(gender_list) + years.sort() + assert len(years) >= 2 + + current = await client.stats_db["gender_hist"].find_one( + { + "councilorType": "national_councilor", + "level": 0, + "is_elected": True, + "year": years[-1], + } + ) + + previous = await client.stats_db["gender_hist"].find_one( + { + "councilorType": "national_councilor", + "level": 0, + "is_elected": True, + "year": years[-1], + } + ) + return GenderTemplateDataNational.model_validate( { "genderDiversityIndex": national_stat["genderDiversityIndex"], "current": { - "year": 2022, - "malePop": gender_count["남"], - "femalePop": gender_count["여"], + "year": years[-1], + "malePop": current["남"], + "femalePop": current["여"], }, "prev": { - "year": 0, - "malePop": 0, - "femalePop": 0, + "year": years[-2], + "malePop": previous["남"], + "femalePop": previous["여"], }, } ) @@ -107,7 +133,7 @@ async def getNationalTemplateData( # [ # { # "$match": { - # "level": 2, + # "level": 0, # "councilorType": "national_councilor", # "is_elected": True, # } @@ -181,84 +207,195 @@ async def getNationalTemplateData( case FactorType.party: party_diversity_index = national_stat["partyDiversityIndex"] - councilors = ( - await client.council_db["national_councilor"].find().to_list(500) - ) + ( - await client.council_db["national_councilor_global"].find().to_list(500) + years = list( + { + doc["year"] + async for doc in client.stats_db["party_hist"].find( + { + "councilorType": "national_councilor", + "level": 0, + "is_elected": True, + } + ) + } + ) + years.sort() + assert len(years) >= 2 + + current_elected = client.stats_db["party_hist"].find( + { + "councilorType": "national_councilor", + "level": 0, + "is_elected": True, + "year": years[-1], + }, + { + "_id": 0, + "councilorType": 0, + "level": 0, + "is_elected": 0, + "year": 0, + }, + ) + current_candidate = client.stats_db["party_hist"].find( + { + "councilorType": "national_councilor", + "level": 0, + "is_elected": False, + "year": years[-1], + }, + { + "_id": 0, + "councilorType": 0, + "level": 0, + "is_elected": 0, + "year": 0, + }, + ) + previous = client.stats_db["party_hist"].find( + { + "councilorType": "national_councilor", + "level": 0, + "is_elected": True, + "year": years[-2], + }, + { + "_id": 0, + "councilorType": 0, + "level": 0, + "is_elected": 0, + "year": 0, + }, ) - party_list = [councilor["jdName"] for councilor in councilors] - party_count = diversity.count(party_list) return PartyTemplateDataNational.model_validate( { "partyDiversityIndex": party_diversity_index, "prevElected": [ - {"party": "포도당", "count": 6}, - {"party": "유당", "count": 6}, - {"party": "과당", "count": 5}, + {"party": party, "count": doc[party]} + async for doc in previous + for party in doc ], "currentElected": [ - {"party": party, "count": party_count[party]} - for party in party_count + {"party": party, "count": doc[party]} + async for doc in current_elected + for party in doc ], "currentCandidate": [ - {"party": "포도당", "count": 6}, - {"party": "유당", "count": 6}, - {"party": "과당", "count": 5}, + {"party": party, "count": doc[party]} + async for doc in current_candidate + for party in doc ], } ) -T = TypeVar( - "T", - GenderChartDataPoint, - AgeChartDataPoint, - PartyChartDataPoint, -) - - @router.get("/chart-data") -async def getNationalChartData(factor: FactorType) -> ErrorResponse | ChartData[T]: - councilors = client.council_db["national_councilor"].find() - +async def getNationalChartData( + factor: FactorType, +) -> ErrorResponse | ChartData[GenderChartDataPoint] | ChartData[ + AgeChartDataPoint +] | ChartData[PartyChartDataPoint]: match factor: case FactorType.gender: - gender_list = [councilor["gender"] async for councilor in councilors] - gender_count = diversity.count(gender_list) + gender_cnt = ( + await client.stats_db["gender_hist"] + .find( + { + "councilorType": "national_councilor", + "level": 0, + "is_elected": True, + } + ) + .sort({"year": -1}) + .limit(1) + .to_list(5) + )[0] + return ChartData[GenderChartDataPoint].model_validate( { "data": [ - {"gender": gender, "count": gender_count[gender]} - for gender in gender_count + {"gender": "남", "count": gender_cnt["남"]}, + {"gender": "여", "count": gender_cnt["여"]}, ] } ) case FactorType.age: - age_list = [councilor["age"] async for councilor in councilors] - age_count = diversity.count(age_list, stair=AGE_STAIR) + # age_cnt = ( + # await client.stats_db["age_hist"] + # .find( + # { + # "councilorType": "national_councilor", + # "level": 0, + # "is_elected": True, + # "method": "equal", + # } + # ) + # .sort({"year": -1}) + # .limit(1) + # .to_list(5) + # )[0] + # age_list = [ + # age["minAge"] for age in age_cnt["data"] for _ in range(age["count"]) + # ] + # age_stair = diversity.count(age_list, stair=AGE_STAIR) + # return ChartData[AgeChartDataPoint].model_validate( + # { + # "data": [ + # { + # "minAge": age, + # "maxAge": age + AGE_STAIR, + # "count": age_stair[age], + # } + # for age in age_stair + # ] + # } + # ) return ChartData[AgeChartDataPoint].model_validate( { "data": [ { - "minAge": age, - "maxAge": age + AGE_STAIR, - "count": age_count[age], - } - for age in age_count + "minAge": 20, + "maxAge": 30, + "count": 888, + }, + { + "minAge": 50, + "maxAge": 60, + "count": 999, + }, ] } ) case FactorType.party: - party_list = [councilor["jdName"] async for councilor in councilors] - party_count = diversity.count(party_list) + party_count = ( + await client.stats_db["party_hist"] + .find( + { + "councilorType": "national_councilor", + "level": 0, + "is_elected": True, + } + ) + .sort({"year": -1}) + .limit(1) + .to_list(5) + )[0] return ChartData[PartyChartDataPoint].model_validate( { "data": [ {"party": party, "count": party_count[party]} for party in party_count + if party + not in [ + "_id", + "councilorType", + "level", + "is_elected", + "year", + ] ] } ) From 203d9dd0e5639af3676e7d58a8666a849a95df43 Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Wed, 29 Nov 2023 20:56:09 +0900 Subject: [PATCH 18/35] =?UTF-8?q?[fix]=20=EA=B4=91=EC=97=AD=EC=9D=98?= =?UTF-8?q?=ED=9A=8C=20=EC=97=B0=EB=A0=B9=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20?= =?UTF-8?q?=EC=A0=95=EC=83=81=20=EC=B6=9C=EB=A0=A5=20=ED=99=95=EC=9D=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/scrapResultMetro.py | 228 +++++++++++++++--------------------- 1 file changed, 97 insertions(+), 131 deletions(-) diff --git a/routers/scrapResultMetro.py b/routers/scrapResultMetro.py index 28cd395..d3351a5 100644 --- a/routers/scrapResultMetro.py +++ b/routers/scrapResultMetro.py @@ -185,65 +185,64 @@ async def getMetroTemplateData( # ============================ # ageHistogramParagraph # ============================ - # age_stat_elected = ( - # await client.stats_db["age_hist"] - # .aggregate( - # [ - # { - # "$match": { - # "level": 1, - # "councilorType": "metro_councilor", - # "is_elected": True, - # "metroId": metroId, - # } - # }, - # {"$sort": {"year": -1}}, - # {"$limit": 1}, - # ] - # ) - # .to_list(500) - # )[0] - # most_recent_year = age_stat_elected["year"] - # age_stat_candidate = await client.stats_db["age_hist"].find_one( - # { - # "level": 1, - # "councilorType": "metro_councilor", - # "is_elected": False, - # "metroId": metroId, - # "year": most_recent_year, - # } - # ) + age_stat_elected = ( + await client.stats_db["age_stat"] + .aggregate( + [ + { + "$match": { + "level": 1, + "councilorType": "metro_councilor", + "is_elected": True, + "metroId": metroId, + } + }, + {"$sort": {"year": -1}}, + {"$limit": 1}, + ] + ) + .to_list(500) + )[0] + most_recent_year = age_stat_elected["year"] + age_stat_candidate = await client.stats_db["age_stat"].find_one( + { + "level": 1, + "councilorType": "metro_councilor", + "is_elected": False, + "metroId": metroId, + "year": most_recent_year, + } + ) - # divArea_id = ( - # await client.stats_db["diversity_index"].find_one( - # {"metroId": {"$exists": True}, "ageDiversityRank": 1} - # ) - # )["metroId"] - # divArea = await client.stats_db["age_hist"].find_one( - # { - # "level": 1, - # "councilorType": "metro_councilor", - # "is_elected": True, - # "metroId": divArea_id, - # "year": most_recent_year, - # } - # ) + divArea_id = ( + await client.stats_db["diversity_index"].find_one( + {"metroId": {"$exists": True}, "ageDiversityRank": 1} + ) + )["metroId"] + divArea = await client.stats_db["age_stat"].find_one( + { + "level": 1, + "councilorType": "metro_councilor", + "is_elected": True, + "metroId": divArea_id, + "year": most_recent_year, + } + ) - # uniArea_id = ( - # await client.stats_db["diversity_index"].find_one( - # # {"metroId": {"$exists": True}, "ageDiversityRank": 17} - # {"metroId": {"$exists": True}, "ageDiversityRank": 15} - # ) - # )["metroId"] - # uniArea = await client.stats_db["age_hist"].find_one( - # { - # "level": 1, - # "councilorType": "metro_councilor", - # "is_elected": True, - # "metroId": uniArea_id, - # "year": most_recent_year, - # } - # ) + uniArea_id = ( + await client.stats_db["diversity_index"].find_one( + {"metroId": {"$exists": True}, "ageDiversityRank": 16} + ) + )["metroId"] + uniArea = await client.stats_db["age_stat"].find_one( + { + "level": 1, + "councilorType": "metro_councilor", + "is_elected": True, + "metroId": uniArea_id, + "year": most_recent_year, + } + ) return AgeTemplateDataMetro.model_validate( { @@ -275,48 +274,31 @@ async def getMetroTemplateData( "candidateDiversityRank": history_candidate[idx][ "diversityRank" ], - # "electedDiversityIndex": history_elected[idx][ - # "diversityIndex" - # ], - # "electedDiversityRank": history_elected[idx][ - # "diversityRank" - # ], - "electedDiversityIndex": 0.003141592, - "electedDiversityRank": 99999, + "electedDiversityIndex": history_elected[idx][ + "diversityIndex" + ], + "electedDiversityRank": history_elected[idx][ + "diversityRank" + ], } for idx, year in enumerate(years) ], }, "ageHistogramParagraph": { - # "year": most_recent_year, - # "candidateCount": age_stat_candidate["data"][0]["population"], - # "electedCount": age_stat_elected["data"][0]["population"], - # "firstQuintile": age_stat_elected["data"][0]["firstquintile"], - # "lastQuintile": age_stat_elected["data"][0]["lastquintile"], - # "divArea": { - # "metroId": divArea_id, - # "firstQuintile": divArea["data"][0]["firstquintile"], - # "lastQuintile": divArea["data"][0]["lastquintile"], - # }, - # "uniArea": { - # "metroId": uniArea_id, - # "firstQuintile": uniArea["data"][0]["firstquintile"], - # "lastQuintile": uniArea["data"][0]["lastquintile"], - # }, - "year": 2022, - "candidateCount": 99999, - "electedCount": 88888, - "firstQuintile": 74, - "lastQuintile": 21, + "year": most_recent_year, + "candidateCount": age_stat_candidate["data"][0]["population"], + "electedCount": age_stat_elected["data"][0]["population"], + "firstQuintile": age_stat_elected["data"][0]["firstquintile"], + "lastQuintile": age_stat_elected["data"][0]["lastquintile"], "divArea": { - "metroId": 1, - "firstQuintile": 45, - "lastQuintile": 20, + "metroId": divArea_id, + "firstQuintile": divArea["data"][0]["firstquintile"], + "lastQuintile": divArea["data"][0]["lastquintile"], }, "uniArea": { - "metroId": 8, - "firstQuintile": 86, - "lastQuintile": 43, + "metroId": uniArea_id, + "firstQuintile": uniArea["data"][0]["firstquintile"], + "lastQuintile": uniArea["data"][0]["lastquintile"], }, }, } @@ -468,50 +450,34 @@ async def getMetroChartData( ) case FactorType.age: - # age_cnt = ( - # await client.stats_db["age_hist"] - # .find( - # { - # "councilorType": "metro_councilor", - # "level": 1, - # "is_elected": True, - # "method": "equal", - # "metroId": metroId, - # } - # ) - # .sort({"year": -1}) - # .limit(1) - # .to_list(5) - # )[0] - # age_list = [ - # age["minAge"] for age in age_cnt["data"] for _ in range(age["count"]) - # ] - # age_stair = diversity.count(age_list, stair=AGE_STAIR) - # return ChartData[AgeChartDataPoint].model_validate( - # { - # "data": [ - # { - # "minAge": age, - # "maxAge": age + AGE_STAIR, - # "count": age_stair[age], - # } - # for age in age_stair - # ] - # } - # ) + age_cnt = ( + await client.stats_db["age_hist"] + .find( + { + "councilorType": "metro_councilor", + "level": 1, + "is_elected": True, + "method": "equal", + "metroId": metroId, + } + ) + .sort({"year": -1}) + .limit(1) + .to_list(5) + )[0] + age_list = [ + age["minAge"] for age in age_cnt["data"] for _ in range(age["count"]) + ] + age_stair = diversity.count(age_list, stair=AGE_STAIR) return ChartData[AgeChartDataPoint].model_validate( { "data": [ { - "minAge": 20, - "maxAge": 30, - "count": 888, - }, - { - "minAge": 50, - "maxAge": 60, - "count": 999, - }, + "minAge": age, + "maxAge": age + AGE_STAIR, + "count": age_stair[age], + } + for age in age_stair ] } ) From f868b332812a34b4dd32b9aef139ed46574470a2 Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Wed, 29 Nov 2023 22:43:57 +0900 Subject: [PATCH 19/35] =?UTF-8?q?[fix]=20=EB=AA=A8=EB=93=A0=20mock=20data?= =?UTF-8?q?=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/ageHist.py | 188 +++++++++------------------------ routers/scrapResultLocal.py | 3 - routers/scrapResultNational.py | 135 ++++++++++------------- 3 files changed, 106 insertions(+), 220 deletions(-) diff --git a/routers/ageHist.py b/routers/ageHist.py index 14ebec8..a1e58db 100644 --- a/routers/ageHist.py +++ b/routers/ageHist.py @@ -3,6 +3,7 @@ from model.AgeHist import ( AgeHistDataTypes, AgeHistMethodTypes, + LocalAgeHistData, MetroAgeHistData, NationalAgeHistData, ) @@ -15,154 +16,69 @@ async def getNationalAgeHistData( ageHistType: AgeHistDataTypes, year: int, method: AgeHistMethodTypes ) -> BasicResponse.ErrorResponse | NationalAgeHistData: - # histogram = await MongoDB.client.stats_db["age_hist"].find_one( - # { - # "councilorType": "national_councilor", - # "is_elected": ageHistType == AgeHistDataTypes.elected, - # "year": year, - # "method": method, - # } - # ) - - # if histogram is None: - # return BasicResponse.ErrorResponse.model_validate( - # { - # "error": "NoDataError", - # "code": BasicResponse.NO_DATA_ERROR, - # "message": "No data retrieved with the provided input.", - # } - # ) - - # return NationalAgeHistData.model_validate({"data": histogram["data"]}) - return NationalAgeHistData.model_validate( + histogram = await MongoDB.client.stats_db["age_hist"].find_one( { - "data": [ - { - "minAge": 21, - "maxAge": 22, - "count": 75, - "ageGroup": 0, - }, - { - "minAge": 22, - "maxAge": 23, - "count": 87, - "ageGroup": 1, - }, - { - "minAge": 29, - "maxAge": 30, - "count": 104, - "ageGroup": 2, - }, - { - "minAge": 45, - "maxAge": 46, - "count": 354, - "ageGroup": 2, - }, - { - "minAge": 46, - "maxAge": 47, - "count": 463, - "ageGroup": 3, - }, - { - "minAge": 63, - "maxAge": 64, - "count": 240, - "ageGroup": 4, - }, - ] + "councilorType": "national_councilor", + "is_elected": ageHistType == AgeHistDataTypes.elected, + "year": year, + "method": method, } ) + if histogram is None: + return BasicResponse.ErrorResponse.model_validate( + { + "error": "NoDataError", + "code": BasicResponse.NO_DATA_ERROR, + "message": "No data retrieved with the provided input.", + } + ) + + return NationalAgeHistData.model_validate({"data": histogram["data"]}) + @router.get("/{metroId}") async def getMetroAgeHistData( metroId: int, ageHistType: AgeHistDataTypes, year: int, method: AgeHistMethodTypes ) -> BasicResponse.ErrorResponse | MetroAgeHistData: - # if ( - # await MongoDB.client.district_db["metro_district"].find_one( - # {"metroId": metroId} - # ) - # is None - # ): - # return BasicResponse.ErrorResponse.model_validate( - # { - # "error": "RegionCodeError", - # "code": BasicResponse.REGION_CODE_ERR, - # "message": f"No metro district with metroId {metroId}.", - # } - # ) - - # histogram = await MongoDB.client.stats_db["age_hist"].find_one( - # { - # "level": 1, - # "councilorType": "metro_councilor", - # "is_elected": ageHistType == AgeHistDataTypes.elected, - # "year": year, - # "method": method, - # "metroId": metroId, - # } - # ) - - # if histogram is None: - # return BasicResponse.ErrorResponse.model_validate( - # { - # "error": "NoDataError", - # "code": BasicResponse.NO_DATA_ERROR, - # "message": "No data retrieved with the provided input.", - # } - # ) - - # return MetroAgeHistData.model_validate( - # {"metroId": metroId, "data": histogram["data"]} - # ) - return MetroAgeHistData.model_validate( + if ( + await MongoDB.client.district_db["metro_district"].find_one( + {"metroId": metroId} + ) + is None + ): + return BasicResponse.ErrorResponse.model_validate( + { + "error": "RegionCodeError", + "code": BasicResponse.REGION_CODE_ERR, + "message": f"No metro district with metroId {metroId}.", + } + ) + + histogram = await MongoDB.client.stats_db["age_hist"].find_one( { + "level": 1, + "councilorType": "metro_councilor", + "is_elected": ageHistType == AgeHistDataTypes.elected, + "year": year, + "method": method, "metroId": metroId, - "data": [ - { - "minAge": 21, - "maxAge": 22, - "count": 75, - "ageGroup": 0, - }, - { - "minAge": 22, - "maxAge": 23, - "count": 87, - "ageGroup": 1, - }, - { - "minAge": 29, - "maxAge": 30, - "count": 104, - "ageGroup": 2, - }, - { - "minAge": 45, - "maxAge": 46, - "count": 354, - "ageGroup": 2, - }, - { - "minAge": 46, - "maxAge": 47, - "count": 463, - "ageGroup": 3, - }, - { - "minAge": 63, - "maxAge": 64, - "count": 240, - "ageGroup": 4, - }, - ], } ) + if histogram is None: + return BasicResponse.ErrorResponse.model_validate( + { + "error": "NoDataError", + "code": BasicResponse.NO_DATA_ERROR, + "message": "No data retrieved with the provided input.", + } + ) + + return MetroAgeHistData.model_validate( + {"metroId": metroId, "data": histogram["data"]} + ) + @router.get("/{metroId}/{localId}") async def getLocalAgeHistData( @@ -171,7 +87,7 @@ async def getLocalAgeHistData( ageHistType: AgeHistDataTypes, year: int, method: AgeHistMethodTypes, -) -> BasicResponse.ErrorResponse | MetroAgeHistData: +) -> BasicResponse.ErrorResponse | LocalAgeHistData: if ( await MongoDB.client.district_db["local_district"].find_one( {"metroId": metroId, "localId": localId} @@ -207,6 +123,6 @@ async def getLocalAgeHistData( } ) - return MetroAgeHistData.model_validate( + return LocalAgeHistData.model_validate( {"metroId": metroId, "localId": localId, "data": histogram["data"]} ) diff --git a/routers/scrapResultLocal.py b/routers/scrapResultLocal.py index 8cd1383..85eca06 100644 --- a/routers/scrapResultLocal.py +++ b/routers/scrapResultLocal.py @@ -284,15 +284,12 @@ async def getLocalTemplateData( group["count"] for group in history_candidate[idx]["data"] ), - # "candidateCount": 0, "candidateDiversityIndex": history_candidate[idx][ "diversityIndex" ], "candidateDiversityRank": history_candidate[idx][ "diversityRank" ], - # "candidateDiversityIndex": 0.0, - # "candidateDiversityRank": 0, "electedDiversityIndex": history_elected[idx][ "diversityIndex" ], diff --git a/routers/scrapResultNational.py b/routers/scrapResultNational.py index 3cee913..fca7d5b 100644 --- a/routers/scrapResultNational.py +++ b/routers/scrapResultNational.py @@ -127,31 +127,31 @@ async def getNationalTemplateData( # ============================ # ageHistogramParagraph # ============================ - # age_stat_elected = ( - # await client.stats_db["age_stat"] - # .aggregate( - # [ - # { - # "$match": { - # "level": 0, - # "councilorType": "national_councilor", - # "is_elected": True, - # } - # }, - # {"$sort": {"year": -1}}, - # {"$limit": 1}, - # ] - # ) - # .to_list(500) - # )[0] - # most_recent_year = age_stat_elected["year"] - # age_stat_candidate = await client.stats_db["age_stat"].find_one( - # { - # "councilorType": "national_councilor", - # "is_elected": False, - # "year": most_recent_year, - # } - # ) + age_stat_elected = ( + await client.stats_db["age_stat"] + .aggregate( + [ + { + "$match": { + "level": 0, + "councilorType": "national_councilor", + "is_elected": True, + } + }, + {"$sort": {"year": -1}}, + {"$limit": 1}, + ] + ) + .to_list(500) + )[0] + most_recent_year = age_stat_elected["year"] + age_stat_candidate = await client.stats_db["age_stat"].find_one( + { + "councilorType": "national_councilor", + "is_elected": False, + "year": most_recent_year, + } + ) return AgeTemplateDataNational.model_validate( { @@ -159,8 +159,7 @@ async def getNationalTemplateData( "ageDiversityIndex": age_diversity_index, }, "indexHistoryParagraph": { - # "mostRecentYear": years[-1], - "mostRecentYear": 2022, + "mostRecentYear": years[-1], "history": [ { "year": year, @@ -169,15 +168,12 @@ async def getNationalTemplateData( group["count"] for group in history_candidate[idx]["data"] ), - # "candidateCount": 0, "candidateDiversityIndex": history_candidate[idx][ "diversityIndex" ], "candidateDiversityRank": history_candidate[idx][ "diversityRank" ], - # "candidateDiversityIndex": 0.0, - # "candidateDiversityRank": 0, "electedDiversityIndex": history_elected[idx][ "diversityIndex" ], @@ -188,19 +184,12 @@ async def getNationalTemplateData( for idx, year in enumerate(years) ], }, - # "ageHistogramParagraph": { - # "year": most_recent_year, - # "candidateCount": age_stat_candidate["data"][0]["population"], - # "electedCount": age_stat_elected["data"][0]["population"], - # "firstQuintile": age_stat_elected["data"][0]["firstquintile"], - # "lastQuintile": age_stat_elected["data"][0]["lastquintile"], - # }, "ageHistogramParagraph": { - "year": 2022, - "candidateCount": 99999, - "electedCount": 88888, - "firstQuintile": 98, - "lastQuintile": 18, + "year": most_recent_year, + "candidateCount": age_stat_candidate["data"][0]["population"], + "electedCount": age_stat_elected["data"][0]["population"], + "firstQuintile": age_stat_elected["data"][0]["firstquintile"], + "lastQuintile": age_stat_elected["data"][0]["lastquintile"], }, } ) @@ -322,49 +311,33 @@ async def getNationalChartData( ) case FactorType.age: - # age_cnt = ( - # await client.stats_db["age_hist"] - # .find( - # { - # "councilorType": "national_councilor", - # "level": 0, - # "is_elected": True, - # "method": "equal", - # } - # ) - # .sort({"year": -1}) - # .limit(1) - # .to_list(5) - # )[0] - # age_list = [ - # age["minAge"] for age in age_cnt["data"] for _ in range(age["count"]) - # ] - # age_stair = diversity.count(age_list, stair=AGE_STAIR) - # return ChartData[AgeChartDataPoint].model_validate( - # { - # "data": [ - # { - # "minAge": age, - # "maxAge": age + AGE_STAIR, - # "count": age_stair[age], - # } - # for age in age_stair - # ] - # } - # ) + age_cnt = ( + await client.stats_db["age_hist"] + .find( + { + "councilorType": "national_councilor", + "level": 0, + "is_elected": True, + "method": "equal", + } + ) + .sort({"year": -1}) + .limit(1) + .to_list(5) + )[0] + age_list = [ + age["minAge"] for age in age_cnt["data"] for _ in range(age["count"]) + ] + age_stair = diversity.count(age_list, stair=AGE_STAIR) return ChartData[AgeChartDataPoint].model_validate( { "data": [ { - "minAge": 20, - "maxAge": 30, - "count": 888, - }, - { - "minAge": 50, - "maxAge": 60, - "count": 999, - }, + "minAge": age, + "maxAge": age + AGE_STAIR, + "count": age_stair[age], + } + for age in age_stair ] } ) From 0e49e7a4ff4c3a60fa46f62a4b68f89de1ec71e0 Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Thu, 30 Nov 2023 02:20:04 +0900 Subject: [PATCH 20/35] add: year select --- routers/scrapResultLocal.py | 33 +++++++++++++++++++++++---------- 1 file changed, 23 insertions(+), 10 deletions(-) diff --git a/routers/scrapResultLocal.py b/routers/scrapResultLocal.py index 85eca06..b04b1e5 100644 --- a/routers/scrapResultLocal.py +++ b/routers/scrapResultLocal.py @@ -24,7 +24,7 @@ @router.get("/template-data/{metroId}/{localId}") async def getLocalTemplateData( - metroId: int, localId: int, factor: FactorType + metroId: int, localId: int, factor: FactorType, year:int = 2022 ) -> ErrorResponse | GenderTemplateDataLocal | AgeTemplateDataLocal | PartyTemplateDataLocal: if ( await client.district_db["local_district"].find_one( @@ -64,6 +64,10 @@ async def getLocalTemplateData( years.sort() assert len(years) >= 2 + year_index = years.index(year) + if year_index == 0: + return NO_DATA_ERROR_RESPONSE + current = await client.stats_db["gender_hist"].find_one( { "councilorType": "local_councilor", @@ -71,7 +75,7 @@ async def getLocalTemplateData( "is_elected": True, "localId": localId, "metroId": metroId, - "year": years[-1], + "year": years[year_index], } ) @@ -82,7 +86,7 @@ async def getLocalTemplateData( "is_elected": True, "localId": localId, "metroId": metroId, - "year": years[-2], + "year": years[year_index - 1], } ) @@ -95,7 +99,7 @@ async def getLocalTemplateData( "councilorType": "local_councilor", "level": 2, "is_elected": True, - "year": years[-1], + "year": years[year_index], } }, { @@ -119,12 +123,12 @@ async def getLocalTemplateData( "localId": localId, "genderDiversityIndex": local_stat["genderDiversityIndex"], "current": { - "year": years[-1], + "year": years[year_index], "malePop": current["남"], "femalePop": current["여"], }, "prev": { - "year": years[-2], + "year": years[year_index - 1], "malePop": previous["남"], "femalePop": previous["여"], }, @@ -166,6 +170,11 @@ async def getLocalTemplateData( } ) years.sort() + + year_index = years.index(year) + if year_index == 0: + return NO_DATA_ERROR_RESPONSE + history_candidate = [ await client.stats_db["age_hist"].find_one( { @@ -217,7 +226,7 @@ async def getLocalTemplateData( ) .to_list(500) )[0] - most_recent_year = age_stat_elected["year"] + most_recent_year = year age_stat_candidate = await client.stats_db["age_stat"].find_one( { "level": 2, @@ -339,6 +348,10 @@ async def getLocalTemplateData( years.sort() assert len(years) >= 2 + year_index = years.index(year) + if year_index == 0: + return NO_DATA_ERROR_RESPONSE + current_elected = client.stats_db["party_hist"].find( { "councilorType": "local_councilor", @@ -346,7 +359,7 @@ async def getLocalTemplateData( "is_elected": True, "localId": localId, "metroId": metroId, - "year": years[-1], + "year": years[year_index], }, { "_id": 0, @@ -365,7 +378,7 @@ async def getLocalTemplateData( "is_elected": False, "localId": localId, "metroId": metroId, - "year": years[-1], + "year": years[year_index], }, { "_id": 0, @@ -384,7 +397,7 @@ async def getLocalTemplateData( "is_elected": True, "localId": localId, "metroId": metroId, - "year": years[-2], + "year": years[year_index - 1], }, { "_id": 0, From 122ce959918f7661c18a000fb2b1a7dbcee55217 Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Thu, 30 Nov 2023 02:36:17 +0900 Subject: [PATCH 21/35] add: year on local --- routers/scrapResultLocal.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/routers/scrapResultLocal.py b/routers/scrapResultLocal.py index b04b1e5..7867a90 100644 --- a/routers/scrapResultLocal.py +++ b/routers/scrapResultLocal.py @@ -436,7 +436,7 @@ async def getLocalTemplateData( @router.get("/chart-data/{metroId}/{localId}") async def getLocalChartData( - metroId: int, localId: int, factor: FactorType + metroId: int, localId: int, factor: FactorType, year:int = 2022 ) -> ErrorResponse | ChartData[GenderChartDataPoint] | ChartData[ AgeChartDataPoint ] | ChartData[PartyChartDataPoint]: @@ -465,9 +465,9 @@ async def getLocalChartData( "is_elected": True, "localId": localId, "metroId": metroId, + "year": year } ) - .sort({"year": -1}) .limit(1) .to_list(5) )[0] @@ -492,9 +492,9 @@ async def getLocalChartData( "method": "equal", "localId": localId, "metroId": metroId, + "year": year } ) - .sort({"year": -1}) .limit(1) .to_list(5) )[0] @@ -525,9 +525,9 @@ async def getLocalChartData( "is_elected": True, "localId": localId, "metroId": metroId, + "year": year } ) - .sort({"year": -1}) .limit(1) .to_list(5) )[0] From 212cb6d291d4830edf4078d8fb0f802e015ea18a Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Thu, 30 Nov 2023 02:36:24 +0900 Subject: [PATCH 22/35] add: year on metro --- routers/scrapResultMetro.py | 49 +++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/routers/scrapResultMetro.py b/routers/scrapResultMetro.py index d3351a5..0543300 100644 --- a/routers/scrapResultMetro.py +++ b/routers/scrapResultMetro.py @@ -1,6 +1,6 @@ from typing import TypeVar from fastapi import APIRouter -from model.BasicResponse import ErrorResponse, REGION_CODE_ERR +from model.BasicResponse import ErrorResponse, REGION_CODE_ERR, NO_DATA_ERROR_RESPONSE from model.MongoDB import client from model.ScrapResultCommon import ( GenderChartDataPoint, @@ -24,7 +24,7 @@ @router.get("/template-data/{metroId}") async def getMetroTemplateData( - metroId: int, factor: FactorType + metroId: int, factor: FactorType, year: int=2022 ) -> ErrorResponse | GenderTemplateDataMetro | AgeTemplateDataMetro | PartyTemplateDataMetro: if ( await client.district_db["metro_district"].find_one({"metroId": metroId}) @@ -57,6 +57,9 @@ async def getMetroTemplateData( ) years.sort() assert len(years) >= 2 + year_index = years.index(year) + if year_index == 0: + return NO_DATA_ERROR_RESPONSE current = await client.stats_db["gender_hist"].find_one( { @@ -64,7 +67,7 @@ async def getMetroTemplateData( "level": 1, "is_elected": True, "metroId": metroId, - "year": years[-1], + "year": years[year_index], } ) @@ -74,7 +77,7 @@ async def getMetroTemplateData( "level": 1, "is_elected": True, "metroId": metroId, - "year": years[-2], + "year": years[year_index-1], } ) @@ -87,7 +90,7 @@ async def getMetroTemplateData( "councilorType": "metro_councilor", "level": 1, "is_elected": True, - "year": years[-1], + "year": years[year_index], } }, { @@ -110,12 +113,12 @@ async def getMetroTemplateData( "metroId": metroId, "genderDiversityIndex": metro_stat["genderDiversityIndex"], "current": { - "year": years[-1], + "year": years[year_index], "malePop": current["남"], "femalePop": current["여"], }, "prev": { - "year": years[-2], + "year": years[year_index-1], "malePop": previous["남"], "femalePop": previous["여"], }, @@ -155,6 +158,10 @@ async def getMetroTemplateData( } ) years.sort() + year_index = years.index(year) + if year_index == 0: + return NO_DATA_ERROR_RESPONSE + history_candidate = [ await client.stats_db["age_hist"].find_one( { @@ -195,15 +202,14 @@ async def getMetroTemplateData( "councilorType": "metro_councilor", "is_elected": True, "metroId": metroId, + "year": years[year_index], } }, - {"$sort": {"year": -1}}, - {"$limit": 1}, ] ) .to_list(500) )[0] - most_recent_year = age_stat_elected["year"] + most_recent_year = year age_stat_candidate = await client.stats_db["age_stat"].find_one( { "level": 1, @@ -259,7 +265,7 @@ async def getMetroTemplateData( ], }, "indexHistoryParagraph": { - "mostRecentYear": years[-1], + "mostRecentYear": years[year_index], "history": [ { "year": year, @@ -322,13 +328,17 @@ async def getMetroTemplateData( years.sort() assert len(years) >= 2 + year_index = years.index(year) + if year_index == 0: + return NO_DATA_ERROR_RESPONSE + current_elected = client.stats_db["party_hist"].find( { "councilorType": "metro_councilor", "level": 1, "is_elected": True, "metroId": metroId, - "year": years[-1], + "year": years[year_index], }, { "_id": 0, @@ -345,7 +355,7 @@ async def getMetroTemplateData( "level": 1, "is_elected": False, "metroId": metroId, - "year": years[-1], + "year": years[year_index], }, { "_id": 0, @@ -362,7 +372,7 @@ async def getMetroTemplateData( "level": 1, "is_elected": True, "metroId": metroId, - "year": years[-2], + "year": years[year_index - 1], }, { "_id": 0, @@ -407,7 +417,7 @@ async def getMetroTemplateData( @router.get("/chart-data/{metroId}") async def getMetroChartData( - metroId: int, factor: FactorType + metroId: int, factor: FactorType, year:int = 2022 ) -> ErrorResponse | ChartData[GenderChartDataPoint] | ChartData[ AgeChartDataPoint ] | ChartData[PartyChartDataPoint]: @@ -433,10 +443,9 @@ async def getMetroChartData( "level": 1, "is_elected": True, "metroId": metroId, + "year": year, } ) - .sort({"year": -1}) - .limit(1) .to_list(5) )[0] @@ -459,10 +468,9 @@ async def getMetroChartData( "is_elected": True, "method": "equal", "metroId": metroId, + "year": year, } ) - .sort({"year": -1}) - .limit(1) .to_list(5) )[0] age_list = [ @@ -491,10 +499,9 @@ async def getMetroChartData( "level": 1, "is_elected": True, "metroId": metroId, + "year": year } ) - .sort({"year": -1}) - .limit(1) .to_list(5) )[0] return ChartData[PartyChartDataPoint].model_validate( From 31e8f8bb0045aaf3671f6c97b7835684ba000e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jeong=20Sang=20=28=EC=A0=95=EC=83=81=29?= Date: Thu, 30 Nov 2023 03:11:22 +0900 Subject: [PATCH 23/35] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4d47f7e..c7181f4 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ -# backend-python \ No newline at end of file +# backend-python +![CD Status](https://github.com/NewWays-TechForImpactKAIST/backend-python/actions/workflows/build-dev-image.yaml/badge.svg) From 0b998da6fef2831a8e95bf56c6061e6655982dc6 Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Thu, 30 Nov 2023 21:49:21 +0900 Subject: [PATCH 24/35] Update README.md --- README.md | 48 +++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 47 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index c7181f4..b43b3a2 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,48 @@ -# backend-python +# 다양성 평가 리포트 웹사이트 백엔드 + ![CD Status](https://github.com/NewWays-TechForImpactKAIST/backend-python/actions/workflows/build-dev-image.yaml/badge.svg) + +FastAPI로 개발되는 다양성 평가 리포트 웹사이트의 백엔드 레포지토리입니다. + +## Setup + +이 프로젝트를 실행하기 위해서는 Python(v3.9 이상)이 설치되어 있어야 합니다. + +## 설치 및 실행 과정 + +1. 파이썬 가상환경 생성 + - 아래 명령을 실행하여 파이썬 가상환경을 생성합니다. + ```bash + cd ~ && virtualenv newways --python=3.10 + ``` +2. 가상환경 활성화 + - 아래 명령을 실행하여 가상환경을 활성화합니다. + ```bash + source ~/newways/bin/activate + ``` +3. 레포지토리 클론 + - 아래 명령을 실행하여 레포지토리를 클론합니다. + ```bash + git clone https://github.com/NewWays-TechForImpactKAIST/backend-python + ``` +4. 필요한 패키지 설치 + - requirements.txt에 명시된 패키지를 설치합니다. + ```bash + pip install -r requirements.txt + ``` +5. 환경 변수 설정 + - `.env.example` 파일을 복사하여 `.env` 파일을 생성합니다. + ```bash + cp .env.example .env + ``` + - `.env` 파일을 열어 환경 변수의 값을 필요에 따라 바꾸어줍니다. +6. uvicorn 실행 + + - uvicorn을 사용해 fastapi를 실행합니다. + ```bash + uvicorn main:app --host HOST --port PORT + ``` + + ``` + + ``` From 974dd9b299a1f79ef7c349d7fd54f6e3b9e543c4 Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Thu, 30 Nov 2023 21:50:11 +0900 Subject: [PATCH 25/35] =?UTF-8?q?Docs:=20=EC=98=A4=ED=83=88=EC=9E=90=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/README.md b/README.md index b43b3a2..a792fb6 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,3 @@ FastAPI로 개발되는 다양성 평가 리포트 웹사이트의 백엔드 레 ```bash uvicorn main:app --host HOST --port PORT ``` - - ``` - - ``` From 5031e8f853c085186c01adf7850b4efa696e0728 Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Thu, 30 Nov 2023 21:52:34 +0900 Subject: [PATCH 26/35] =?UTF-8?q?[feat]=20=ED=9B=84=EB=B3=B4=EC=9E=90=20?= =?UTF-8?q?=EC=84=B1=EB=B3=84=20=EB=8D=B0=EC=9D=B4=ED=84=B0=20=EC=A0=9C?= =?UTF-8?q?=EA=B3=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- model/ScrapResultLocal.py | 2 ++ model/ScrapResultMetro.py | 2 ++ model/ScrapResultNational.py | 2 ++ routers/scrapResultLocal.py | 44 +++++++++++++++++++++++++++++----- routers/scrapResultMetro.py | 42 +++++++++++++++++++++++++++----- routers/scrapResultNational.py | 42 ++++++++++++++++++++++++++++---- 6 files changed, 117 insertions(+), 17 deletions(-) diff --git a/model/ScrapResultLocal.py b/model/ScrapResultLocal.py index 9e4ee09..bab4e3f 100644 --- a/model/ScrapResultLocal.py +++ b/model/ScrapResultLocal.py @@ -14,7 +14,9 @@ class GenderTemplateDataPoint(BaseModel): localId: int genderDiversityIndex: float current: GenderTemplateDataPoint + currentCandidate: GenderTemplateDataPoint prev: GenderTemplateDataPoint + prevCandidate: GenderTemplateDataPoint meanMalePop: float meanFemalePop: float diff --git a/model/ScrapResultMetro.py b/model/ScrapResultMetro.py index 8d60537..90dd843 100644 --- a/model/ScrapResultMetro.py +++ b/model/ScrapResultMetro.py @@ -13,7 +13,9 @@ class GenderTemplateDataPoint(BaseModel): metroId: int genderDiversityIndex: float current: GenderTemplateDataPoint + currentCandidate: GenderTemplateDataPoint prev: GenderTemplateDataPoint + prevCandidate: GenderTemplateDataPoint meanMalePop: float meanFemalePop: float diff --git a/model/ScrapResultNational.py b/model/ScrapResultNational.py index 66c847c..0c0fd74 100644 --- a/model/ScrapResultNational.py +++ b/model/ScrapResultNational.py @@ -12,7 +12,9 @@ class GenderTemplateDataPoint(BaseModel): genderDiversityIndex: float current: GenderTemplateDataPoint + currentCandidate: GenderTemplateDataPoint prev: GenderTemplateDataPoint + prevCandidate: GenderTemplateDataPoint class AgeTemplateDataNational(BaseModel): diff --git a/routers/scrapResultLocal.py b/routers/scrapResultLocal.py index 7867a90..1252365 100644 --- a/routers/scrapResultLocal.py +++ b/routers/scrapResultLocal.py @@ -24,7 +24,7 @@ @router.get("/template-data/{metroId}/{localId}") async def getLocalTemplateData( - metroId: int, localId: int, factor: FactorType, year:int = 2022 + metroId: int, localId: int, factor: FactorType, year: int = 2022 ) -> ErrorResponse | GenderTemplateDataLocal | AgeTemplateDataLocal | PartyTemplateDataLocal: if ( await client.district_db["local_district"].find_one( @@ -79,6 +79,17 @@ async def getLocalTemplateData( } ) + current_candidate = await client.stats_db["gender_hist"].find_one( + { + "councilorType": "local_councilor", + "level": 2, + "is_elected": False, + "localId": localId, + "metroId": metroId, + "year": years[year_index], + } + ) + previous = await client.stats_db["gender_hist"].find_one( { "councilorType": "local_councilor", @@ -90,6 +101,17 @@ async def getLocalTemplateData( } ) + previous_candidate = await client.stats_db["gender_hist"].find_one( + { + "councilorType": "local_councilor", + "level": 2, + "is_elected": False, + "localId": localId, + "metroId": metroId, + "year": years[year_index], + } + ) + current_all = ( await client.stats_db["gender_hist"] .aggregate( @@ -127,11 +149,21 @@ async def getLocalTemplateData( "malePop": current["남"], "femalePop": current["여"], }, + "currentCandidate": { + "year": years[year_index], + "malePop": current_candidate["남"], + "femalePop": current_candidate["여"], + }, "prev": { "year": years[year_index - 1], "malePop": previous["남"], "femalePop": previous["여"], }, + "prevCandidate": { + "year": years[year_index], + "malePop": previous_candidate["남"], + "femalePop": previous_candidate["여"], + }, "meanMalePop": current_all["male_tot"] / current_all["district_cnt"], "meanFemalePop": current_all["female_tot"] @@ -351,7 +383,7 @@ async def getLocalTemplateData( year_index = years.index(year) if year_index == 0: return NO_DATA_ERROR_RESPONSE - + current_elected = client.stats_db["party_hist"].find( { "councilorType": "local_councilor", @@ -436,7 +468,7 @@ async def getLocalTemplateData( @router.get("/chart-data/{metroId}/{localId}") async def getLocalChartData( - metroId: int, localId: int, factor: FactorType, year:int = 2022 + metroId: int, localId: int, factor: FactorType, year: int = 2022 ) -> ErrorResponse | ChartData[GenderChartDataPoint] | ChartData[ AgeChartDataPoint ] | ChartData[PartyChartDataPoint]: @@ -465,7 +497,7 @@ async def getLocalChartData( "is_elected": True, "localId": localId, "metroId": metroId, - "year": year + "year": year, } ) .limit(1) @@ -492,7 +524,7 @@ async def getLocalChartData( "method": "equal", "localId": localId, "metroId": metroId, - "year": year + "year": year, } ) .limit(1) @@ -525,7 +557,7 @@ async def getLocalChartData( "is_elected": True, "localId": localId, "metroId": metroId, - "year": year + "year": year, } ) .limit(1) diff --git a/routers/scrapResultMetro.py b/routers/scrapResultMetro.py index 0543300..92c4bc7 100644 --- a/routers/scrapResultMetro.py +++ b/routers/scrapResultMetro.py @@ -24,7 +24,7 @@ @router.get("/template-data/{metroId}") async def getMetroTemplateData( - metroId: int, factor: FactorType, year: int=2022 + metroId: int, factor: FactorType, year: int = 2022 ) -> ErrorResponse | GenderTemplateDataMetro | AgeTemplateDataMetro | PartyTemplateDataMetro: if ( await client.district_db["metro_district"].find_one({"metroId": metroId}) @@ -71,13 +71,33 @@ async def getMetroTemplateData( } ) + current_candidate = await client.stats_db["gender_hist"].find_one( + { + "councilorType": "metro_councilor", + "level": 1, + "is_elected": False, + "metroId": metroId, + "year": years[year_index], + } + ) + previous = await client.stats_db["gender_hist"].find_one( { "councilorType": "metro_councilor", "level": 1, "is_elected": True, "metroId": metroId, - "year": years[year_index-1], + "year": years[year_index - 1], + } + ) + + previous_candidate = await client.stats_db["gender_hist"].find_one( + { + "councilorType": "metro_councilor", + "level": 1, + "is_elected": False, + "metroId": metroId, + "year": years[year_index], } ) @@ -117,11 +137,21 @@ async def getMetroTemplateData( "malePop": current["남"], "femalePop": current["여"], }, + "currentCandidate": { + "year": years[year_index], + "malePop": current_candidate["남"], + "femalePop": current_candidate["여"], + }, "prev": { - "year": years[year_index-1], + "year": years[year_index - 1], "malePop": previous["남"], "femalePop": previous["여"], }, + "prevCandidate": { + "year": years[year_index], + "malePop": previous_candidate["남"], + "femalePop": previous_candidate["여"], + }, "meanMalePop": current_all["male_tot"] / current_all["district_cnt"], "meanFemalePop": current_all["female_tot"] @@ -161,7 +191,7 @@ async def getMetroTemplateData( year_index = years.index(year) if year_index == 0: return NO_DATA_ERROR_RESPONSE - + history_candidate = [ await client.stats_db["age_hist"].find_one( { @@ -417,7 +447,7 @@ async def getMetroTemplateData( @router.get("/chart-data/{metroId}") async def getMetroChartData( - metroId: int, factor: FactorType, year:int = 2022 + metroId: int, factor: FactorType, year: int = 2022 ) -> ErrorResponse | ChartData[GenderChartDataPoint] | ChartData[ AgeChartDataPoint ] | ChartData[PartyChartDataPoint]: @@ -499,7 +529,7 @@ async def getMetroChartData( "level": 1, "is_elected": True, "metroId": metroId, - "year": year + "year": year, } ) .to_list(5) diff --git a/routers/scrapResultNational.py b/routers/scrapResultNational.py index fca7d5b..045307c 100644 --- a/routers/scrapResultNational.py +++ b/routers/scrapResultNational.py @@ -24,7 +24,7 @@ @router.get("/template-data") async def getNationalTemplateData( - factor: FactorType, + factor: FactorType, year: int = 2020 ) -> ErrorResponse | GenderTemplateDataNational | AgeTemplateDataNational | PartyTemplateDataNational: national_stat = await client.stats_db["diversity_index"].find_one( {"national": True} @@ -49,12 +49,25 @@ async def getNationalTemplateData( years.sort() assert len(years) >= 2 + year_index = years.index(year) + if year_index == 0: + return NO_DATA_ERROR_RESPONSE + current = await client.stats_db["gender_hist"].find_one( { "councilorType": "national_councilor", "level": 0, "is_elected": True, - "year": years[-1], + "year": years[year_index], + } + ) + + current_candidate = await client.stats_db["gender_hist"].find_one( + { + "councilorType": "national_councilor", + "level": 0, + "is_elected": False, + "year": years[year_index - 1], } ) @@ -63,7 +76,16 @@ async def getNationalTemplateData( "councilorType": "national_councilor", "level": 0, "is_elected": True, - "year": years[-1], + "year": years[year_index], + } + ) + + previous_candidate = await client.stats_db["gender_hist"].find_one( + { + "councilorType": "national_councilor", + "level": 0, + "is_elected": False, + "year": years[year_index - 1], } ) @@ -71,15 +93,25 @@ async def getNationalTemplateData( { "genderDiversityIndex": national_stat["genderDiversityIndex"], "current": { - "year": years[-1], + "year": years[year_index], "malePop": current["남"], "femalePop": current["여"], }, + "currentCandidate": { + "year": years[year_index - 1], + "malePop": current_candidate["남"], + "femalePop": current_candidate["여"], + }, "prev": { - "year": years[-2], + "year": years[year_index], "malePop": previous["남"], "femalePop": previous["여"], }, + "prevCandidate": { + "year": years[year_index - 1], + "malePop": previous_candidate["남"], + "femalePop": previous_candidate["여"], + }, } ) From 2179aa862ed494bf2a124290df5f10fe28add7b1 Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Thu, 30 Nov 2023 21:52:54 +0900 Subject: [PATCH 27/35] Docs: Docker --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index a792fb6..5a6dc24 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,12 @@ FastAPI로 개발되는 다양성 평가 리포트 웹사이트의 백엔드 레 ``` - `.env` 파일을 열어 환경 변수의 값을 필요에 따라 바꾸어줍니다. 6. uvicorn 실행 - - uvicorn을 사용해 fastapi를 실행합니다. ```bash uvicorn main:app --host HOST --port PORT ``` +7. Docker build + - docker image를 빌드합니다. + ```bash + docker-compose up -d -build + ``` From 6cf996ce73d50aed2056fdc5386d4e88ccb70b46 Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Thu, 30 Nov 2023 21:55:31 +0900 Subject: [PATCH 28/35] Docs: Swagger --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 5a6dc24..74b0294 100644 --- a/README.md +++ b/README.md @@ -4,6 +4,11 @@ FastAPI로 개발되는 다양성 평가 리포트 웹사이트의 백엔드 레포지토리입니다. +## Docs + +본 프로젝트는 Swagger를 사용하여 API 문서를 작성하고 있습니다. +![Swagger](https://diversity-api.tech4impact.kr/docs) 에서 API Endpoints 들을 확인하고 테스트 할 수 있습니다. + ## Setup 이 프로젝트를 실행하기 위해서는 Python(v3.9 이상)이 설치되어 있어야 합니다. From 2a8cee0df85d3d523daf87f3bf3562b4e3514c56 Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Thu, 30 Nov 2023 21:56:17 +0900 Subject: [PATCH 29/35] Image fix --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 74b0294..f5c747d 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ FastAPI로 개발되는 다양성 평가 리포트 웹사이트의 백엔드 레 ## Docs 본 프로젝트는 Swagger를 사용하여 API 문서를 작성하고 있습니다. -![Swagger](https://diversity-api.tech4impact.kr/docs) 에서 API Endpoints 들을 확인하고 테스트 할 수 있습니다. +[Swagger](https://diversity-api.tech4impact.kr/docs) 에서 API Endpoints 들을 확인하고 테스트 할 수 있습니다. ## Setup From b7575fa08522752d1717ada87ece44d82292aedd Mon Sep 17 00:00:00 2001 From: withSang Date: Thu, 30 Nov 2023 13:20:56 +0000 Subject: [PATCH 30/35] docs(readme): add docker compose --- README.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index f5c747d..de9f5d8 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ FastAPI로 개발되는 다양성 평가 리포트 웹사이트의 백엔드 레 이 프로젝트를 실행하기 위해서는 Python(v3.9 이상)이 설치되어 있어야 합니다. -## 설치 및 실행 과정 +### 개발환경 설정 과정 1. 파이썬 가상환경 생성 - 아래 명령을 실행하여 파이썬 가상환경을 생성합니다. @@ -40,14 +40,29 @@ FastAPI로 개발되는 다양성 평가 리포트 웹사이트의 백엔드 레 ```bash cp .env.example .env ``` - - `.env` 파일을 열어 환경 변수의 값을 필요에 따라 바꾸어줍니다. + - `.env` 파일을 열어 환경변수를 필요에 따라 변경합니다. 6. uvicorn 실행 - uvicorn을 사용해 fastapi를 실행합니다. ```bash uvicorn main:app --host HOST --port PORT ``` -7. Docker build - - docker image를 빌드합니다. + +### 배포 과정 + +이 레포의 main 브랜치에 새 커밋이 생성될 때마다, GitHub Actions를 통해 배포용 Docker 이미지가 빌드됩니다. +이 Docker 이미지를 사용하여 서비스를 배포할 수 있습니다. + +1. 환경변수 설정 + - `.env.example` 파일을 복사하여 `.env` 파일을 생성합니다. ```bash - docker-compose up -d -build + cp .env.example .env ``` + - `.env` 파일을 열어 환경변수를 필요에 따라 변경합니다. + +2. 백엔드 컨테이너 배포 + - 컨테이너를 아래 명령으로 생성합니다. + ```bash + docker-compose -f docker-compose.dev.yml up -d + ``` + - `newways-watchtower`는 1분에 한 번씩 새 백엔드 이미지가 있는지 확인하여, 백엔드 컨테이너를 주기적으로 업데이트하는 역할을 수행합니다. + From 2bb5fb0daf9de1b4221c744e26efab5123c500cd Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Fri, 1 Dec 2023 00:24:31 +0900 Subject: [PATCH 31/35] Fix: Year --- routers/scrapResultNational.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/routers/scrapResultNational.py b/routers/scrapResultNational.py index 045307c..9eaec53 100644 --- a/routers/scrapResultNational.py +++ b/routers/scrapResultNational.py @@ -242,13 +242,16 @@ async def getNationalTemplateData( ) years.sort() assert len(years) >= 2 - + year_index = years.index(year) + if year_index == 0: + return NO_DATA_ERROR_RESPONSE + current_elected = client.stats_db["party_hist"].find( { "councilorType": "national_councilor", "level": 0, "is_elected": True, - "year": years[-1], + "year": years[year_index], }, { "_id": 0, @@ -263,7 +266,7 @@ async def getNationalTemplateData( "councilorType": "national_councilor", "level": 0, "is_elected": False, - "year": years[-1], + "year": years[year_index], }, { "_id": 0, @@ -278,7 +281,7 @@ async def getNationalTemplateData( "councilorType": "national_councilor", "level": 0, "is_elected": True, - "year": years[-2], + "year": years[year_index - 1], }, { "_id": 0, From cda1fcc4380c73103ebde72f78feb65a9c3c93a8 Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Fri, 1 Dec 2023 00:34:08 +0900 Subject: [PATCH 32/35] Add: Chart Data year --- routers/scrapResultNational.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/routers/scrapResultNational.py b/routers/scrapResultNational.py index 9eaec53..0b71c96 100644 --- a/routers/scrapResultNational.py +++ b/routers/scrapResultNational.py @@ -316,7 +316,7 @@ async def getNationalTemplateData( @router.get("/chart-data") async def getNationalChartData( - factor: FactorType, + factor: FactorType, year: int = 2020 ) -> ErrorResponse | ChartData[GenderChartDataPoint] | ChartData[ AgeChartDataPoint ] | ChartData[PartyChartDataPoint]: @@ -328,11 +328,10 @@ async def getNationalChartData( { "councilorType": "national_councilor", "level": 0, + "year": year, "is_elected": True, } ) - .sort({"year": -1}) - .limit(1) .to_list(5) )[0] @@ -354,10 +353,9 @@ async def getNationalChartData( "level": 0, "is_elected": True, "method": "equal", + "year": year, } ) - .sort({"year": -1}) - .limit(1) .to_list(5) )[0] age_list = [ @@ -385,10 +383,9 @@ async def getNationalChartData( "councilorType": "national_councilor", "level": 0, "is_elected": True, + "year": year, } ) - .sort({"year": -1}) - .limit(1) .to_list(5) )[0] return ChartData[PartyChartDataPoint].model_validate( From c94f18628fd556bc23e63d806ed6e18294a5bf0c Mon Sep 17 00:00:00 2001 From: happycastle <41810556+happycastle114@users.noreply.github.com> Date: Fri, 1 Dec 2023 01:24:38 +0900 Subject: [PATCH 33/35] Add: Chart Data year --- routers/scrapResultNational.py | 1 + 1 file changed, 1 insertion(+) diff --git a/routers/scrapResultNational.py b/routers/scrapResultNational.py index 0b71c96..c62af76 100644 --- a/routers/scrapResultNational.py +++ b/routers/scrapResultNational.py @@ -168,6 +168,7 @@ async def getNationalTemplateData( "level": 0, "councilorType": "national_councilor", "is_elected": True, + "year": year, } }, {"$sort": {"year": -1}}, From 366a6de93ca5f3cd2abbe60af7d6fd2e9349dfb8 Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Fri, 1 Dec 2023 12:42:59 +0900 Subject: [PATCH 34/35] =?UTF-8?q?[fix]=20=EA=B8=B0=EC=B4=88=EC=9D=98?= =?UTF-8?q?=ED=9A=8C=20divArea=20=EB=A1=9C=EC=A7=81=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- routers/scrapResultLocal.py | 34 ++++++++++++++++++++-------------- 1 file changed, 20 insertions(+), 14 deletions(-) diff --git a/routers/scrapResultLocal.py b/routers/scrapResultLocal.py index 1252365..2b0d1c6 100644 --- a/routers/scrapResultLocal.py +++ b/routers/scrapResultLocal.py @@ -270,20 +270,26 @@ async def getLocalTemplateData( } ) - divArea_id = ( - await client.stats_db["diversity_index"].find_one( - {"localId": {"$exists": True}, "ageDiversityRank": 1} - ) - )["localId"] - divArea = await client.stats_db["age_stat"].find_one( - { - "level": 2, - "councilorType": "local_councilor", - "is_elected": True, - "localId": divArea_id, - "year": most_recent_year, - } + areas_sorted = ( + client.stats_db["diversity_index"] + .find({"localId": {"$exists": True}}) + .sort("ageDiversityRank") ) + async for area in areas_sorted: + divArea = await client.stats_db["age_stat"].find_one( + { + "level": 2, + "councilorType": "local_councilor", + "is_elected": True, + "localId": area["localId"], + "year": most_recent_year, + } + ) + if divArea is not None: + break + + if divArea is None: + return NO_DATA_ERROR_RESPONSE uniArea_id = ( await client.stats_db["diversity_index"].find_one( @@ -348,7 +354,7 @@ async def getLocalTemplateData( "firstQuintile": age_stat_elected["data"][0]["firstquintile"], "lastQuintile": age_stat_elected["data"][0]["lastquintile"], "divArea": { - "localId": divArea_id, + "localId": divArea["localId"], "firstQuintile": divArea["data"][0]["firstquintile"], "lastQuintile": divArea["data"][0]["lastquintile"], }, From a84e460f5e91898527008a43ede732829ed39bcd Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 16 Oct 2024 07:58:30 +0000 Subject: [PATCH 35/35] Bump starlette from 0.27.0 to 0.40.0 Bumps [starlette](https://github.com/encode/starlette) from 0.27.0 to 0.40.0. - [Release notes](https://github.com/encode/starlette/releases) - [Changelog](https://github.com/encode/starlette/blob/master/docs/release-notes.md) - [Commits](https://github.com/encode/starlette/compare/0.27.0...0.40.0) --- updated-dependencies: - dependency-name: starlette dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index c2d4271..a24bbbd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,6 +12,6 @@ pydantic_core==2.10.1 pymongo==4.6.0 python-dotenv==1.0.0 sniffio==1.3.0 -starlette==0.27.0 +starlette==0.40.0 typing_extensions==4.8.0 uvicorn==0.24.0.post1