コード例 #1
0
def opt_out_foreigners():
    from constant import foreign_teams
    f_team_query = session.query(Team.team_id).filter(
        Team.team_name.in_(foreign_teams)).all()
    f_team_ids = [t.team_id for t in f_team_query]
    # リストでフィルターをかけているが、deleteの引数synchronize_sessionのデフォルト値'evaluate'ではこれをサポートしていない(らしい)からFalseを指定する
    count = session.query(Record).filter(
        Record.team_id.in_(f_team_ids)).delete(synchronize_session=False)
    session.commit()
    notify_line(f'{len(f_team_ids)}件の外国籍チームを検出。{count}件の記録を削除')
コード例 #2
0
def add_records_wrapper(date_min, date_max):
    target_meets = session.query(Meet.meet_id).filter(
        Meet.start >= date_min,
        Meet.start <= date_max).order_by(Meet.start).all()
    target_meets_ids = [m.meet_id for m in target_meets]
    if not_up_to_date := imperfect_meets(target_meets_ids):
        count = session.query(Record).filter(
            Record.meet_id.in_(not_up_to_date)).delete(
                synchronize_session=False)
        session.commit()
        notify_line(f'大会ID:{not_up_to_date}、記録未納の可能性あり。{count}件の記録を削除')
コード例 #3
0
def raw_timestr_to_timeval(time_str):
    if time_str in ["", "--:--.--", "-", "ー"]: # リレーで第一泳者以外の失格の場合--:--.--になる。最後のはハイフンではなく半角カタカナ長音
        return 0 # 記録なし
    else:
        ob = re.match(time_format_ptn, time_str)
        if ob is None: # 普通じゃ想定していない
            # 大会0119722の平沼さんの女子2フリはこれが適用されている
            notify_line(f'<!!>無効なタイム文字列{time_str}を検出しました。とりま-1を返します')
            return -1
        else:
            min = ob.group(1) if ob.group(1) != "" else 0 # 32.34とか分がないときは分は0
            time_val = int(min)*6000 + int(ob.group(2))*100 + int(ob.group(3))
            return time_val
コード例 #4
0
    def parse_table(self): # 記録一覧のページの表を全部とってくる
        set_of_args = []
        for row, lap_table in zip(self.rows, self.lap_tables):
            data = row.find_all("td") # 一行の中に複数のtd(順位、氏名…)(リレーと個人で異なる)が格納されている
            laps_raw = lap_table.find_all("td", width = True) # タイムの書かれたtdのみがwidthの引数を持つ
            laps = [del_space(l.string) for l in laps_raw] # タグを取り除いて空白も削除
            laps_val = ",".join([str(raw_timestr_to_timeval(l)) for l in laps])

            if self.is_indivisual:
                raw_grade = del_space(data[3].string)
                try:
                    grade = japanese_grades.index(raw_grade)
                except ValueError:
                    notify_line(f'無効な学年を検出。{self.url} で{raw_grade}(L={len(raw_grade)})を検出。とりま-1を返しました。')
                    grade = -1
                time_raw = data[4].a
                name = del_space(data[1].string)
            else:
                grade = 0 # リレーに学年は存在しない
                time_raw = data[3].a
                # data[1].contentsはbrタグを含む配列 タグ以外をswimmersに格納
                names = [del_numspace(name) for name in data[1].contents if isinstance(name, element.NavigableString)]
                count = len(names)
                assert count == 1 or count == 4
                if count == 4:
                    name = ','.join(names)
                else: # リレーを棄権したため氏名の表記が無いとき、改行文字だけが検出され、要素数1となる
                    name = ''

            rank = del_space(data[0].text) # data[0].stringだとタグを含んだときにNoneが返されてしまう
            team = del_space(data[2].string)
            time = '' if time_raw is None else del_space(time_raw.string)
            time_val = raw_timestr_to_timeval(time)

            relay = 0 if self.is_indivisual else 5
            # レコードインスタンスを作るのに必要な引数をまとめたタプル
            arguments_for_single_record = (
                self.meet_id, self.event_id, relay, rank, name, team, grade, time_val, laps_val)
            set_of_args.append(arguments_for_single_record)
        return set_of_args
コード例 #5
0
def add_records(target_meets_ids):  # 大会IDのリストから1大会ごとにRecordの行を生成しDBに追加
    notify_line(
        f"目標大会をセット。{target_meets_ids[0]}から{target_meets_ids[-1]}。{len(target_meets_ids)}大会の全記録調査開始"
    )
    record_length = 0  # 追加した行数
    erased = 0  # 削除した行数
    skipped = 0  # 飛ばした種目数
    events_count = 0  # 対象の種目数

    for meet_id in Takenoko(target_meets_ids):
        events_list = scraper.all_events(meet_id)  # Eventインスタンスのリスト
        events_count += (sub_total := len(events_list))

        # 既にDBにある同一大会IDの記録を抽出し、それぞれの種目IDが何行あるかを調べる
        # しかしこれでは記録数変わらずに記録の中身(タイムが空白からアップデートされたとき)に対応できない
        existing_records_in_meet = session.query(
            Record.event).filter_by(meet_id=meet_id).all()
        existing_event_id_list = [e.event for e in existing_records_in_meet]

        for event in events_list:
            event.crawl()
            # print(f'{event.event_id} / {sub_total} in {event.meet_id}')
            if existing_event_id_list.count(event.event_id) != len(
                    event.rows):  # 記録数が一致していなかったら削除して登録し直し
                erased += session.query(Record).filter_by(
                    meet_id=event.meet_id, event=event.event_id).delete()
                records = [Record(*args) for args in event.parse_table()]
                for rc in records:
                    rc.set_team()
                    rc.set_swimmer()
                session.add_all(records)
                record_length += len(records)
                session.commit()
            else:
                skipped += 1

    notify_line(
        f'{erased}件を削除 {record_length}件を新規に保存 現在:{format(count_records(), ",")}件\n{events_count}種目中{skipped}をスキップ'
    )
コード例 #6
0
def analyze_all(year):
    # statisticsテーブルの行を一行ずつ見ていき、それぞれアップデート
    notify_line('全記録分析を開始')
    stats_table = session.query(Stats).all()

    for stat in Takenoko(stats_table, 20):
        conditions = set_conditions(stat.pool, stat.event, year, stat.grade)
        stmt = session.query(Record.time).distinct(Record.swimmer_id).filter(
            *conditions).order_by(Record.swimmer_id, Record.time).subquery()

        subq = aliased(Record, stmt)
        times = session.query(stmt).order_by(subq.time).all()
        count_ranking = len(times)
        stat.count_ranking = count_ranking
        stat.count_agg = session.query(func.count(
            Record.record_id)).filter(*conditions).scalar()

        if count_ranking >= 2:
            stat.border = times[499].time if count_ranking >= 500 else None
            vals = pd.Series([t.time for t in times])

            # 外れ値除くための範囲を決める
            q1 = vals.quantile(.25)
            q3 = vals.quantile(.75)
            iqr = q3 - q1
            lower_limit = q1 - iqr * 1.5
            upper_limit = q3 + iqr * 1.5

            # 外れ値除外したやつの記述統計量を取得
            desc = vals[(vals > lower_limit) & (vals < upper_limit)].describe()
            stat.mean = round(desc['mean'], 2)  # 小数点第2位までで四捨五入
            stat.std = round(desc['std'], 2)
            stat.q1 = int(desc['25%'])
            stat.q2 = int(desc['50%'])
            stat.q3 = int(desc['75%'])

        session.commit()
    notify_line('全記録の分析を完了')
コード例 #7
0
def add_meets(year, force=False):
    notify_line(f"各地域の大会情報の収集を開始。対象:20{year}年")
    meet_ids = []
    # for area_int in Takenoko(range(14,15)): # ローカル用
    for area_int in Takenoko(list(range(1, 54)) +
                             [70, 80]):  # 本番用 1から53までと全国70国際80がarea番号になる
        meet_ids.extend(scraper.find_meet(year, area_int))

    saved_meets = session.query(func.sum(
        Meet.meet_id)).filter_by(year=year).scalar()
    if force or sum(meet_ids) != saved_meets:  # 大会IDの合計値が一致しないか、強制実行の場合
        notify_line(f'全{len(meet_ids)}の大会を検出')

        meets = []
        for id in Takenoko(meet_ids, 20):
            area = id // 100000
            year = (id % 100000) // 1000
            start, end, name, place, pool = scraper.meet_info(id)
            meets.append(
                Meet(meet_id=id,
                     meet_name=name,
                     place=place,
                     pool=pool,
                     start=start,
                     end=end,
                     area=area,
                     year=year))

        erased = session.query(Meet).filter_by(
            year=year).delete()  # 同じ年度を二重に登録しないように削除する
        session.add_all(meets)
        session.commit()
        notify_line(f'{erased}件を削除 全{len(meets)}の大会情報を保存')

    else:
        notify_line(f'大会情報に更新はありませんでした')
コード例 #8
0
def add_row_for_relay(relay, meet_id, swimmer_id):
    event = convert_relay_event(relay.event)
    laps_list = relay.laps.split(',')
    if (lap_len := len(laps_list)) < 4:
        notify_line(f'R1_INVALID: 無効なタイム({laps_list}) on {relay.record_id}')
        return 0
コード例 #9
0
def add_first_swimmer_in_relay(target_meets_ids):
    # 対象大会内のレコードに一つも1泳者のレコード(relay=1)がなかったらまだ未追加
    notify_line(f"リレー第一泳者の記録の追加を開始")
    record_length = 0  # 追加した行数
    skipped = 0  # 飛ばした種目数

    for meet_id in Takenoko(target_meets_ids, 50):
        first_swimmers = session.query(Record.record_id).filter_by(
            meet_id=meet_id, relay=1).all()
        if first_swimmers:
            skipped += 1  # その大会においては既に1泳者追加していた
        else:
            relay_results = session.query(
                Record.record_id, Record.event, Swimmer.name,
                Record.rank, Record.team_id, Record.laps).filter(
                    Record.meet_id == meet_id,
                    Record.swimmer_id == Swimmer.swimmer_id, Record.relay == 5,
                    ~Record.rank.in_(['失格', '失格1泳者', '棄権', '途中棄権'])
                    # 2~4泳者の失格はよい あと失格、は誰が失格なのかわからないから一応除外
                ).all()
            only_relay_but_add = []
            sub_count = 0
            for relay in relay_results:
                swimmers = relay.name.split(',')
                if len(swimmers) == 1:
                    notify_line(
                        f'R1_INVALID: 無効なリレーオーダー({swimmers}) on {relay.record_id}'
                    )
                    continue
                first = swimmers[0]
                # とりあえず同じ名前の人探す
                candidates = session.query(
                    Swimmer.swimmer_id).filter_by(name=first).all()

                if candidates:
                    # 同一大会内の個人種目でその人が出場しているか
                    candidates_in_same_meet = session.query(
                        Record.swimmer_id).filter(
                            Record.meet_id == meet_id,
                            Record.swimmer_id.in_([
                                c.swimmer_id for c in candidates
                            ])).distinct(
                                Record.swimmer_id  # 同姓同名の選手が同じ大会に出場していたらオワオワリ
                            ).all()
                    suggest_s_ids = [
                        s.swimmer_id for s in candidates_in_same_meet
                    ]

                    if (length := len(suggest_s_ids)) == 1:
                        # これは特定余裕 同じ大会内で同じ名前の選手が一人だけいた
                        sub_count += add_row_for_relay(relay, meet_id,
                                                       suggest_s_ids[0])

                    elif length == 0:
                        # 同一大会で出場なし
                        if len(candidates) == 1:
                            sub_count += add_row_for_relay(
                                relay, meet_id, candidates[0])
                            only_relay_but_add.append(
                                f'{first}, {relay.record_id}')
                        else:
                            notify_line(
                                f'R1_INVALID: リレーのみ出場同姓同名({first}) on {relay.record_id}'
                            )
                    else:
                        # 同姓同名が同一大会で出場したため、リレー一泳が誰か特定不可
                        notify_line(
                            f'R1_INVALID: 同一大会内同姓同名({first}) on {relay.record_id}'
                        )

                else:  # 同じ名前の人がSwimmerテーブルに存在しない
                    notify_line(
                        f'R1_INVALID: テーブルに存在しない名前({first}) on {relay.record_id}'
                    )

            if only_relay_but_add:
                msg = ' '.join(only_relay_but_add)
                notify_line(
                    f'{meet_id}において、リレーのみ出場の選手かつ同姓同名なしで問題なしとしたのが以下。{msg}')
            session.commit()
            print(f'{meet_id}にて{sub_count}件追加')
            record_length += sub_count
コード例 #10
0
                        )

                else:  # 同じ名前の人がSwimmerテーブルに存在しない
                    notify_line(
                        f'R1_INVALID: テーブルに存在しない名前({first}) on {relay.record_id}'
                    )

            if only_relay_but_add:
                msg = ' '.join(only_relay_but_add)
                notify_line(
                    f'{meet_id}において、リレーのみ出場の選手かつ同姓同名なしで問題なしとしたのが以下。{msg}')
            session.commit()
            print(f'{meet_id}にて{sub_count}件追加')
            record_length += sub_count

    notify_line(f'{record_length}件の第一泳者の記録を新規に保存。{skipped}大会をスキップ')


def add_row_for_relay(relay, meet_id, swimmer_id):
    event = convert_relay_event(relay.event)
    laps_list = relay.laps.split(',')
    if (lap_len := len(laps_list)) < 4:
        notify_line(f'R1_INVALID: 無効なタイム({laps_list}) on {relay.record_id}')
        return 0
    else:
        assert lap_len % 4 == 0
        first_range = lap_len // 4
        first_laps = laps_list[:first_range]
        time = int(first_laps[-1])  # 最後の一つが1泳の正式タイム
        laps = ','.join(first_laps)
        first_result = Record(meet_id=meet_id,