出力ログ無しバージョン

This commit is contained in:
野間 2023-07-03 13:47:19 +09:00
parent e38938a243
commit 0250080bc6
7 changed files with 120 additions and 227 deletions

View File

@ -7,7 +7,6 @@ DB_SCHEMA=src05
ARISJ_DATA_BUCKET=mbj-newdwh2021-staging-jskult-arisj ARISJ_DATA_BUCKET=mbj-newdwh2021-staging-jskult-arisj
JSKULT_BACKUP_BUCKET=mbj-newdwh2021-staging-backup-jskult JSKULT_BACKUP_BUCKET=mbj-newdwh2021-staging-backup-jskult
JSKULT_CONFIG_BUCKET=mbj-newdwh2021-staging-config JSKULT_CONFIG_BUCKET=mbj-newdwh2021-staging-config
ULTMARC_BACKUP_FOLDER=************
LOG_LEVEL=INFO LOG_LEVEL=INFO
ARISJ_DATA_FOLDER=DATA ARISJ_DATA_FOLDER=DATA

View File

@ -65,9 +65,34 @@ class ConfigBucket(S3Bucket):
return temporary_file_path return temporary_file_path
class ArisjBucket(S3Bucket):
_bucket_name = environment.ARISJ_DATA_BUCKET
_folder = environment.ARISJ_BACKUP_FOLDER
def list_dat_file(self):
return self._s3_client.list_objects(self._bucket_name, self._folder)
def s3_arisj_csv_upload(self, arisj_create_csv: str, csv_file_path: str):
# s3にCSVファイルをUPする
Bucket = environment.ARISJ_DATA_BUCKET
folder = environment.ARISJ_DATA_FOLDER
csv_file_name = f'{folder}/{arisj_create_csv}'
s3_client = S3Client()
s3_client.upload_file(csv_file_path, Bucket, csv_file_name)
return
def backup_dat_file(self, dat_file_key: str, datetime_key: str):
# バックアップバケットにコピー
arisj_backup_bucket = ArisjBackupBucket()
folder = environment.ARISJ_DATA_FOLDER
dat_file_key = f'{folder}/{dat_file_key}'
backup_key = f'{arisj_backup_bucket._folder}/{datetime_key}/{dat_file_key.replace(f"{self._folder}/", "")}'
self._s3_client.copy(self._bucket_name, dat_file_key, arisj_backup_bucket._bucket_name, backup_key)
class JskUltBackupBucket(S3Bucket): class JskUltBackupBucket(S3Bucket):
_bucket_name = environment.JSKULT_BACKUP_BUCKET _bucket_name = environment.JSKULT_BACKUP_BUCKET
class UltmarcBackupBucket(JskUltBackupBucket): class ArisjBackupBucket(JskUltBackupBucket):
_folder = environment.ULTMARC_BACKUP_FOLDER _folder = environment.ARISJ_BACKUP_FOLDER

View File

@ -1,5 +1,6 @@
class BatchContext: class BatchContext:
__instance = None __instance = None
__syor_date: str # 処理日(yyyy/mm/dd形式)
__is_arisj_output_day: bool # 月次バッチ起動日フラグ __is_arisj_output_day: bool # 月次バッチ起動日フラグ
def __init__(self) -> None: def __init__(self) -> None:
@ -11,6 +12,14 @@ class BatchContext:
cls.__instance = cls() cls.__instance = cls()
return cls.__instance return cls.__instance
@property
def syor_date(self):
return self.__syor_date
@syor_date.setter
def syor_date(self, syor_date_str: str):
self.__syor_date = syor_date_str
@property @property
def is_arisj_output_day(self): def is_arisj_output_day(self):
return self.__is_arisj_output_day return self.__is_arisj_output_day

View File

@ -3,87 +3,88 @@ from datetime import datetime
from src.db.database import Database from src.db.database import Database
from src.error.exceptions import BatchOperationException from src.error.exceptions import BatchOperationException
from src.aws.s3 import S3Client
from src.logging.get_logger import get_logger from src.logging.get_logger import get_logger
from src.aws.s3 import ArisjBucket
from src.batch.common.batch_context import BatchContext
import tempfile import tempfile
import os
import os.path as path import os.path as path
import logging
import csv import csv
logger = get_logger('実消化&アルトマーク月次バッチ') logger = get_logger('ARIS-J連携データ出力')
create_date_format = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
prg_id = 'PrgId:BI0402'
create_date = datetime.now().strftime('%Y%m%d%H%M%S') create_date = datetime.now().strftime('%Y%m%d%H%M%S')
aris_create_csv = f'D0004_ARIS_M_DCF_{create_date}.csv' arisj_create_csv = f'D0004_ARIS_M_DCF_{create_date}.csv'
res_log = f'D0004{create_date}.log' sql_err_msg = "SQL実行エラーです。"
sql_err_msg = "MsgID:999999000002 Message:SQL実行エラーです。"
move_err_msg = "MsgID:BI0000000041 Message:S3バケットARISへのCSVデータ、実行ログ移動できませんでした。"
def exec(): def exec():
""" 実消化&アルトマーク月次バッチ """ """ 実消化&アルトマーク月次バッチ """
try: try:
logger.info('バッチ処理を開始しました。')
start_msg = "MsgID:BI0000000001 Message:バッチ処理を開始しました。" try:
cnt_msg = "MsgID: Message: LogText:"
# 実行ログに書き込む
resLog, log_file_path = make_log_data()
resLog.info(f'{create_date_format}[DWH][3][INFO]{prg_id} {start_msg}')
logger.info(f'{create_date_format}[DWH][3][INFO]{prg_id} {start_msg}')
db = Database.get_instance() db = Database.get_instance()
# DB接続 # DB接続
db.connect() db.connect()
except Exception as e:
logger.info('DB接続エラーです')
raise e
# トランザクションの開始 # トランザクションの開始
db.begin() db.begin()
# 正常系データの反映 # 正常系データの反映
# 過去分は不要のため、デリート # 過去分は不要のため、デリート
physical_normal_delete(db) physical_wk_inst_aris_if_delete(db)
# 正常系データを取得しWKテーブルに保存する。 # 正常系データを取得しWKテーブルに保存する。
normal_insert_into(db) wk_inst_aris_if_insert_into(db)
# 正常系データの件数を取得 # 正常系データの件数を取得
suc_count = normal_count(db) suc_count = wk_inst_aris_if_count(db)
# 警告系データの反映 # 警告系データの反映
# 過去分は不要のため、DWH.WK_INST_ARIS_IF_WRNをデリートする。 # 過去分は不要のため、DWH.WK_INST_ARIS_IF_WRNをデリートする。
physical_abnormal_delete(db) physical_wk_inst_aris_if_wrn_delete(db)
# 異常系データを取得しWKテーブルに保存する。 # 異常系データを取得しWKテーブルに保存する。
abnormal_insert_into(db) wk_inst_aris_if_wrn_insert_into(db)
# 異常系データの件数を取得 # 異常系データの件数を取得
wrn_count = abnormal_count(db) wrn_count = wk_inst_aris_if_wrn_count(db)
# CSVファイルの作成用のSQL実行 # CSVファイルの作成用のSQL実行
record_csv = csv_data_select(db) record_csv = csv_data_select(db)
# CSVファイル作成 # CSVファイル作成
csv_file_path = make_csv_data(record_csv, resLog) csv_file_path = make_csv_data(record_csv)
# トランザクションの終了 # トランザクションの終了
db.commit() db.commit()
# 実行ログファイルの追記 # ログに処理件数を出力
# 実行ログに処理件数を書き込む。
sum_count = suc_count + wrn_count sum_count = suc_count + wrn_count
resLog.info(f'{create_date_format}[DWH][3][INFO]{prg_id} {cnt_msg}(対象件数:{sum_count}/正常件数:{suc_count}/警告件数:{wrn_count})') logger.info(f'(対象件数:{sum_count}/正常件数:{suc_count}/警告件数:{wrn_count})')
logger.info(f'{create_date_format}[DWH][3][INFO]{prg_id} {cnt_msg}(対象件数:{sum_count}/正常件数:{suc_count}/警告件数:{wrn_count})')
# CSVファイル移動処理 # CSVファイル移動処理
s3_csv_upload_data(csv_file_path, resLog) try:
ArisjBucket().s3_arisj_csv_upload(arisj_create_csv, csv_file_path)
# logファイル移動処理 except Exception as e:
s3_log_upload_data(log_file_path) logger.info('S3バケットArisjへのCSVデータ、移動できませんでした。')
raise e
logger.info('実消化&アルトマーク月次バッチ処理: 終了')
# 処理後ファイルをバックアップ
try:
arisj_bucket = ArisjBucket()
batch_context = BatchContext.get_instance()
arisj_bucket.backup_dat_file(arisj_create_csv, batch_context.syor_date)
except Exception as e:
logger.info('S3バケットArisjバックアップへCSVデータ、コピーできませんでした。')
raise e
logger.info('バッチ処理を終了しました。')
except Exception as e: except Exception as e:
logger.info(f'{create_date_format}[DWH][5][INFO]')
raise BatchOperationException(e) raise BatchOperationException(e)
finally: finally:
@ -92,7 +93,7 @@ def exec():
db.disconnect() db.disconnect()
def physical_normal_delete(db): def physical_wk_inst_aris_if_delete(db):
# 過去分は不要のため、デリート # 過去分は不要のため、デリート
try: try:
# WKテーブルの過去分削除SQL # WKテーブルの過去分削除SQL
@ -102,11 +103,11 @@ def physical_normal_delete(db):
db.execute(sql) db.execute(sql)
return return
except Exception as e: except Exception as e:
logger.debug(f'{create_date_format}:{prg_id} {sql_err_msg}') logger.debug(f'{sql_err_msg}')
raise e raise e
def normal_insert_into(db): def wk_inst_aris_if_insert_into(db):
# 正常系データを取得しWKテーブルに保存する。 # 正常系データを取得しWKテーブルに保存する。
try: try:
# 正常系データを取得しWKテーブルに保存SQL # 正常系データを取得しWKテーブルに保存SQL
@ -150,11 +151,11 @@ def normal_insert_into(db):
db.execute(sql) db.execute(sql)
return return
except Exception as e: except Exception as e:
logger.debug(f'{create_date_format}:{prg_id} {sql_err_msg}') logger.debug(f'{sql_err_msg}')
raise e raise e
def normal_count(db): def wk_inst_aris_if_count(db):
# 正常系データの件数を取得 # 正常系データの件数を取得
try: try:
# 正常系データの件数を取得SQL # 正常系データの件数を取得SQL
@ -164,11 +165,11 @@ def normal_count(db):
record_count = db.execute_select(sql) record_count = db.execute_select(sql)
return record_count[0]['countNum'] return record_count[0]['countNum']
except Exception as e: except Exception as e:
logger.debug(f'{create_date_format}:{prg_id} {sql_err_msg}') logger.debug(f'{sql_err_msg}')
raise e raise e
def physical_abnormal_delete(db): def physical_wk_inst_aris_if_wrn_delete(db):
# 過去分は不要のため、DWH.WK_INST_ARIS_IF_WRNをデリートする。 # 過去分は不要のため、DWH.WK_INST_ARIS_IF_WRNをデリートする。
try: try:
# 異常系WKテーブルの過去分削除SQL # 異常系WKテーブルの過去分削除SQL
@ -179,11 +180,11 @@ def physical_abnormal_delete(db):
db.execute(sql) db.execute(sql)
return return
except Exception as e: except Exception as e:
logger.debug(f'{create_date_format}:{prg_id} {sql_err_msg}') logger.debug(f'{sql_err_msg}')
raise e raise e
def abnormal_insert_into(db): def wk_inst_aris_if_wrn_insert_into(db):
# 異常系データを取得しWKテーブルに保存する。 # 異常系データを取得しWKテーブルに保存する。
try: try:
# 異常系データを取得しWKテーブルに保存SQL # 異常系データを取得しWKテーブルに保存SQL
@ -233,11 +234,11 @@ def abnormal_insert_into(db):
db.execute(sql) db.execute(sql)
return return
except Exception as e: except Exception as e:
logger.debug(f'{create_date_format}:{prg_id} {sql_err_msg}') logger.debug(f'{sql_err_msg}')
raise e raise e
def abnormal_count(db): def wk_inst_aris_if_wrn_count(db):
# 異常系データの件数を取得 # 異常系データの件数を取得
try: try:
# 異常系データの件数を取得SQL # 異常系データの件数を取得SQL
@ -249,7 +250,7 @@ def abnormal_count(db):
return record_count[0]['countNum'] return record_count[0]['countNum']
except Exception as e: except Exception as e:
logger.debug(f'{create_date_format}:{prg_id} {sql_err_msg}') logger.debug(f'{sql_err_msg}')
raise e raise e
@ -266,18 +267,15 @@ def csv_data_select(db):
return db.execute_select(sql) return db.execute_select(sql)
except Exception as e: except Exception as e:
logger.debug(f'{create_date_format}:{prg_id} {sql_err_msg}') logger.debug(f'{sql_err_msg}')
raise e raise e
def make_csv_data(record_csv: list, resLog): def make_csv_data(record_csv: list):
# 一時ファイルとして保存する(CSVファイル) # 一時ファイルとして保存する(CSVファイル)
try: try:
err_end_msg = "MsgID:BI0000009998 Message:バッチ処理を異常終了しました。"
csv_err_msg = "MsgID:BI0000000040 Message:ワークデータの作成に失敗しました。"
temporary_dir = tempfile.mkdtemp() temporary_dir = tempfile.mkdtemp()
csv_file_path = path.join(temporary_dir, aris_create_csv) csv_file_path = path.join(temporary_dir, arisj_create_csv)
head_str = ['TC_HOSPITAL', 'TJ_HOSPITAL', 'TJ_HOSPITALSHORT', 'TK_HOSPITAL', head_str = ['TC_HOSPITAL', 'TJ_HOSPITAL', 'TJ_HOSPITALSHORT', 'TK_HOSPITAL',
'TC_PREFECTURE', 'TJ_PREFECTURE', 'TJ_ZIPCODE', 'TJ_CITY', 'TJ_ADDRESS', 'TJ_DEPARTMENT', 'TC_PREFECTURE', 'TJ_PREFECTURE', 'TJ_ZIPCODE', 'TJ_CITY', 'TJ_ADDRESS', 'TJ_DEPARTMENT',
@ -298,64 +296,8 @@ def make_csv_data(record_csv: list, resLog):
writer.writerow(csv_data) writer.writerow(csv_data)
except Exception as e: except Exception as e:
resLog.info(f'{create_date_format}[DWH][5][INFO]{prg_id} {csv_err_msg}') logger.info('ワークデータの作成に失敗しました。')
resLog.info(f'{create_date_format}[DWH][5][INFO]{prg_id} {err_end_msg}') logger.info('バッチ処理を異常終了しました。')
logger.info(f'{create_date_format}[DWH][5][INFO]{prg_id} {csv_err_msg}')
logger.info(f'{create_date_format}[DWH][5][INFO]{prg_id} {err_end_msg}')
raise e raise e
return csv_file_path return csv_file_path
def make_log_data():
# 一時ファイルとして保存する(ログファイル)
temporary_dir = tempfile.mkdtemp()
log_file_path = path.join(temporary_dir, res_log)
# ロガーの生成
resLog = logging.getLogger('resLog')
# 出力レベルの設定
resLog.setLevel(logging.INFO)
# ハンドラの生成
resLog_handler = logging.FileHandler(log_file_path)
# ロガーにハンドラを登録
resLog.addHandler(resLog_handler)
# フォーマッタの生成
fmt = logging.Formatter('%(message)s')
# ハンドラにフォーマッタを登録
resLog_handler.setFormatter(fmt)
return resLog, log_file_path
def s3_csv_upload_data(csv_file_path, resLog):
# s3にCSVファイルをUPする
Bucket = os.environ['ARISJ_DATA_BUCKET']
folder = os.environ['ARISJ_DATA_FOLDER']
csv_file_name = f'{folder}/{aris_create_csv}'
s3_client = S3Client()
try:
s3_client.upload_file(csv_file_path, Bucket, csv_file_name)
except Exception as e:
resLog.info(f'{create_date_format}[DWH][5][INFO]{prg_id} {move_err_msg}')
logger.info(f'{create_date_format}[DWH][5][INFO]{prg_id} {move_err_msg}')
raise e
return
def s3_log_upload_data(log_file_path):
# s3にログファイルをUPする
Bucket = os.environ['ARISJ_DATA_BUCKET']
folder = os.environ['ARISJ_DATA_FOLDER']
log_file_name = f'{folder}/{res_log}'
s3_client = S3Client()
try:
s3_client.upload_file(log_file_path, Bucket, log_file_name)
except Exception as e:
logger.info(f'{create_date_format}[DWH][5][INFO]{prg_id} {move_err_msg}')
raise e
return

View File

@ -1,6 +1,7 @@
"""実消化&アルトマーク 月次バッチ処理""" """実消化&アルトマーク 月次バッチ処理"""
from src.aws.s3 import ConfigBucket from src.aws.s3 import ConfigBucket
from src.aws.s3 import ArisjBackupBucket
from src.batch.batch_functions import get_batch_statuses from src.batch.batch_functions import get_batch_statuses
from src.batch.common.batch_context import BatchContext from src.batch.common.batch_context import BatchContext
from src.batch.common.calendar_file import CalendarFile from src.batch.common.calendar_file import CalendarFile
@ -9,28 +10,34 @@ from src.logging.get_logger import get_logger
from src.system_var import constants from src.system_var import constants
from src.batch import output_arisj_file_process from src.batch import output_arisj_file_process
logger = get_logger('月次処理コントロール') logger = get_logger('月次処理コントロールARIS-J')
# バッチ共通設定を取得 # バッチ共通設定を取得
batch_context = BatchContext.get_instance() batch_context = BatchContext.get_instance()
arisj_bucket = ArisjBackupBucket()
def exec(): def exec():
try: try:
logger.info('月次バッチ:開始') logger.info('月次バッチ:開始')
try: try:
# 月次バッチ処置中フラグ、dump処理状態区分、処理日を取得 logger.info('処理日取得')
# 月次バッチ処置中フラグ、処理日を取得
batch_processing_flag, syor_date = get_batch_statuses() batch_processing_flag, syor_date = get_batch_statuses()
except BatchOperationException as e: except BatchOperationException as e:
logger.exception(f'付テーブル取得(異常終了){e}') logger.exception(f'次ジョブ取得エラー(異常終了){e}')
return constants.BATCH_EXIT_CODE_SUCCESS return constants.BATCH_EXIT_CODE_SUCCESS
# 日次バッチ処理中の場合、後続の処理は行わない # 日次バッチ処理中の場合、後続の処理は行わない
logger.info('日次ジョブ処理中判定')
if batch_processing_flag == constants.BATCH_ACTF_BATCH_IN_PROCESSING: if batch_processing_flag == constants.BATCH_ACTF_BATCH_IN_PROCESSING:
logger.error('日次バッチ処理中のため、月次バッチ処理を終了します。') logger.error('日次ジョブ処理中エラー(異常終了)')
return constants.BATCH_EXIT_CODE_SUCCESS return constants.BATCH_EXIT_CODE_SUCCESS
logger.info(f'処理日={syor_date}') # バッチ共通設定に処理日を追加
batch_context.syor_date = syor_date
logger.info(f'処理日取得={syor_date}')
# 稼働日かかどうかを、実消化&アルトマーク月次バッチ稼働日ファイルをダウンロードして判定 # 稼働日かかどうかを、実消化&アルトマーク月次バッチ稼働日ファイルをダウンロードして判定
try: try:
@ -38,22 +45,22 @@ def exec():
arisj_output_day_calendar = CalendarFile(arisj_output_day_list_file_path) arisj_output_day_calendar = CalendarFile(arisj_output_day_list_file_path)
batch_context.is_arisj_output_day = arisj_output_day_calendar.compare_date(syor_date) batch_context.is_arisj_output_day = arisj_output_day_calendar.compare_date(syor_date)
except Exception as e: except Exception as e:
logger.exception(f'実消化&アルトマーク月次バッチ稼働日ファイルの読み込みに失敗しました。{e}') logger.exception(f'処理日取得エラー(異常終了){e}')
return constants.BATCH_EXIT_CODE_SUCCESS return constants.BATCH_EXIT_CODE_SUCCESS
# 調査目的で実消化&アルトマーク月次バッチ稼働日かどうかをログ出力 # 調査目的で実消化&アルトマーク月次バッチ稼働日かどうかをログ出力
if batch_context.is_arisj_output_day: if not batch_context.is_arisj_output_day:
logger.info('本日は実消化&アルトマーク月次バッチ稼働日です。') logger.info('ARIS-J連携データ出力日でない為、処理終了')
else:
logger.info('月次バッチは行われませんでした。')
return constants.BATCH_EXIT_CODE_SUCCESS return constants.BATCH_EXIT_CODE_SUCCESS
logger.info('ARIS-J連携データ出力日です')
try: try:
logger.info('月次バッチ:起動') logger.info('ARIS-J連携データ出力:起動')
output_arisj_file_process.exec() output_arisj_file_process.exec()
logger.info('月次バッチ:終了') logger.info('ARIS-J連携データ出力:終了')
except BatchOperationException as e: except BatchOperationException as e:
logger.exception(f'月次バッチ処理エラー(異常終了){e}') logger.exception(f'ARIS-J連携データ出力(異常終了){e}')
return constants.BATCH_EXIT_CODE_SUCCESS return constants.BATCH_EXIT_CODE_SUCCESS
# 正常終了を保守ユーザーに通知 # 正常終了を保守ユーザーに通知

View File

@ -15,7 +15,6 @@ ARISJ_DATA_FOLDER = os.environ['ARISJ_DATA_FOLDER']
ARISJ_BACKUP_FOLDER = os.environ['ARISJ_BACKUP_FOLDER'] ARISJ_BACKUP_FOLDER = os.environ['ARISJ_BACKUP_FOLDER']
JSKULT_CONFIG_CALENDAR_FOLDER = os.environ['JSKULT_CONFIG_CALENDAR_FOLDER'] JSKULT_CONFIG_CALENDAR_FOLDER = os.environ['JSKULT_CONFIG_CALENDAR_FOLDER']
JSKULT_CONFIG_CALENDAR_ARISJ_OUTPUT_DAY_LIST_FILE_NAME = os.environ['JSKULT_CONFIG_CALENDAR_ARISJ_OUTPUT_DAY_LIST_FILE_NAME'] JSKULT_CONFIG_CALENDAR_ARISJ_OUTPUT_DAY_LIST_FILE_NAME = os.environ['JSKULT_CONFIG_CALENDAR_ARISJ_OUTPUT_DAY_LIST_FILE_NAME']
ULTMARC_BACKUP_FOLDER = os.environ['ULTMARC_BACKUP_FOLDER']
# 初期値がある環境変数 # 初期値がある環境変数
LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO') LOG_LEVEL = os.environ.get('LOG_LEVEL', 'INFO')

View File

@ -1,100 +1,12 @@
2023/06/23 2023/01/05
2023/06/24 2023/02/01
2023/06/25 2023/03/01
2023/06/26 2023/04/03
2023/06/27 2023/05/01
2023/06/28 2023/06/01
2023/06/29
2023/06/30
2023/07/01
2023/07/02
2023/07/03 2023/07/03
2023/07/04
2023/07/05
2023/07/06
2023/07/07
2023/07/08
2023/07/09
2023/07/10
2023/07/11
2023/07/12
2023/07/13
2023/07/14
2023/07/15
2023/07/16
2023/07/17
2023/07/18
2023/07/19
2023/07/20
2023/07/21
2023/07/22
2023/07/23
2023/07/24
2023/07/25
2023/07/26
2023/07/27
2023/07/28
2023/07/29
2023/07/30
2023/07/31
2023/08/01 2023/08/01
2023/08/02
2023/08/03
2023/08/04
2023/08/05
2023/08/06
2023/08/07
2023/08/08
2023/08/09
2023/08/10
2023/08/11
2023/08/12
2023/08/13
2023/08/14
2023/08/15
2023/08/16
2023/08/17
2023/08/18
2023/08/19
2023/08/20
2023/08/21
2023/08/22
2023/08/23
2023/08/24
2023/08/25
2023/08/26
2023/08/27
2023/08/28
2023/08/29
2023/08/30
2023/08/31
2023/09/01 2023/09/01
2023/09/02 2023/10/02
2023/09/03 2023/11/01
2023/09/04 2023/12/01
2023/09/05
2023/09/06
2023/09/07
2023/09/08
2023/09/09
2023/09/10
2023/09/11
2023/09/12
2023/09/13
2023/09/14
2023/09/15
2023/09/16
2023/09/17
2023/09/18
2023/09/19
2023/09/20
2023/09/21
2023/09/22
2023/09/23
2023/09/24
2023/09/25
2023/09/26
2023/09/27
2023/09/28
2023/09/29
2023/09/30