From bebc6e685d39689d9f8c32442fe7e46abd8d91cc Mon Sep 17 00:00:00 2001 From: pingpingy1 Date: Mon, 27 Nov 2023 17:45:46 +0900 Subject: [PATCH 1/4] =?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 2/4] =?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 3/4] =?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 4/4] =?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", + ] ] } )