はじめに
私は競馬予想プログラムソフトの制作過程を動画で投稿している者です。
ここでは、モデル分析用のWEBアプリの開発手順を話していきます。
現在作成している競馬予想プログラムソフトの概要は以下を参照ください。
本プログラムの前提
本プログラムでは以下の前提を置いています。
- Windows 10
 - Python3.10.5
 - Django ver5.0.4
 - 競馬予想プログラムで作成したモデルを分析する目的で使います。
 - Bookersで公開中のモデル分析管理クラスと連携してモデル分析を行います。
 
とりわけ、最後の2つに関しては有料記事でソースを公開しているため、まったく同じ環境で競馬予想プログラムの作成とモデルの分析を行いたい場合は以下のBookers記事一覧から記事を購入ください。
また、競馬予想プログラムの制作過程については大まかな概要は以下の再生リストから参照ください。
詳細な解説は以下の記事一覧を参照ください。

モデル管理画面:モデルリスト表示機能
今回扱う機能は「モデルリスト表示」です。インポートしたモデルをWEB画面上から確認できる機能になります。
実装する動作は以下です。
- モデルを表示する
- モデルの詳細画面を別画面で表示する
 
 - モデルを更新する
 - モデルを削除する
 
完成イメージ(gif)
上記の実装する動作の完成イメージを見せます。
モデルを表示する

モデルを更新する

モデルを削除する

機能説明
上記の動作ごとの完成イメージの動きがまんま説明になってます。実際に動作を見た方が分かりやすいと思うので、下手な説明は割愛します。
フォルダ構成
今回の実装でもフォルダ構成を少し変えています。前回記事のフォルダ構成をベースに変更箇所を赤字にしています。
<any-dir>
  ┣ <app_keiba>
  ┃      ┣ <app_keiba>
  ┃      ┃      ┣ __init__.py
  ┃      ┃      ┣ settings.py
  ┃      ┃      ┣ urls.py
  ┃      ┃      ┣ asgi.py
  ┃      ┃      ┗ wsgi.py
  ┃      ┣ <model_analyzer>
  ┃      ┃      ┣ <migrations>
  ┃      ┃      ┃      ┗ __init__.py
  ┃      ┃      ┣ <tools>
  ┃      ┃      ┃      ┣ <form_control>
  ┃      ┃      ┃      ┃     ┗ model_manage_forms.py
  ┃      ┃      ┃      ┗ <model_control>
  ┃      ┃      ┃             ┗ model_manage_models.py
  ┃      ┃      ┣ __init__.py
  ┃      ┃      ┣ admin.py
  ┃      ┃      ┣ apps.py
  ┃      ┃      ┣ models.py
  ┃      ┃      ┣ test.py
  ┃      ┃      ┣ urls.py
  ┃      ┃      ┗ views.py
  ┃      ┣ <static>
  ┃      ┃      ┣ <css>
  ┃      ┃      ┃    ┣ <model_manage>
  ┃      ┃      ┃    ┃      ┗ style.css
  ┃      ┃      ┃    ┗ base.css
  ┃      ┃      ┣ <images>
  ┃      ┃      ┃    ┗ favicon.png
  ┃      ┃      ┗ <  js  >
  ┃      ┃            ┣ <base_layout>
  ┃      ┃            ┃     ┗ fade_popup.js
  ┃      ┃            ┗ <model_manage>
  ┃      ┃                   ┗ update_form_initial.js
  ┃      ┣ <templates>
  ┃      ┃      ┣ <model_manage> ※ モデル管理画面のHTMLをフォルダにまとめる
  ┃      ┃      ┃    ┣ <modal> ※ モーダル機能のHTMLをフォルダにまとめる
  ┃      ┃      ┃    ┃     ┣ modelDeleteModal.html
  ┃      ┃      ┃    ┃     ┣ modelImportModal.html
  ┃      ┃      ┃    ┃     ┗ modelInfoModal.html
  ┃      ┃      ┃    ┗ model_manage.html ※ ファイルの位置が変わってるので注意
  ┃      ┃      ┣ base_layout.html
  ┃      ┃      ┣ top_page.html
  ┃      ┃      ┗ model_analyze.html
  ┃      ┗ manage.py
  ┗ <src>
モデルリスト表示の開発手順(Django側)
それでは、実際に変更されたソースを一つずつ見せていきます。基本はこの手順通りにソースを変更して頂ければ、完成するはずです。
(更新) model_manage_forms.pyの編集
モデルインポート機能で用いるフォームの動作方法を変更しています。この変更で、モデルを更新した場合に更新対象のモデルIDが存在しない場合、および新規モデルをインポートする際にすでにインポート済のモデルIDが存在している場合はエラーメッセージを表示する。
# この関数を丸っとすべて以下に変更
def validation_model_import_forms(request: WSGIRequest, nav_params: dict):
    model_id = request.POST.get("model_update")
    form = ModelImportForms(request.POST, request.FILES)
    if form.is_valid():
        model_info = json.loads(
            form.cleaned_data.pop("model_info_json").read())
        form.cleaned_data |= model_info
        model_form = ModelListForms(form.cleaned_data)
        if model_form.is_valid():
            if model_id:
                if model_form.cleaned_data["model_id"] != model_id:
                    messages.error(
                        request,
                        "更新対象の'model_id'が間違えています。正しい'model_info.json'を指定してください。" +
                        f"Error詳細: JsonファイルのモデルID: {model_form.cleaned_data['model_id']}, 更新対象のモデルID: {model_id}"
                    )
                    return True
            else:
                if ModelList.objects.filter(model_id=model_form.cleaned_data["model_id"]).exists():
                    messages.warning(
                        request,
                        "すでにモデルが登録されています。モデル一覧のテーブルにある該当するモデルIDから更新してください。" +
                        f"Error詳細: 登録エラー。JsonファイルのモデルID: {model_form.cleaned_data['model_id']}はすでに存在しています。"
                    )
                    return True
            if check_submit_token(request):
                model_form.save()
                messages.success(
                    request,
                    "モデルのインポートに成功しました。" +
                    f"モデル名: {model_form.cleaned_data['model_name']}, " +
                    f"モデル種別: {model_form.cleaned_data['model_type']}"
                )
            else:
                return True
        else:
            if check_submit_token(request):
                for key, error in model_form.errors.items():
                    messages.error(
                        request,
                        "model_info.jsonファイルが不正です。" +
                        f"Error詳細: {key} {error.as_text()}"
                    )
            else:
                return True
    else:
        if check_submit_token(request):
            for key, error in form.errors.items():
                messages.error(
                    request,
                    "入力内容に不正があります。" +
                    f"Error詳細: {key} {error.as_text()}"
                )
        else:
            return True
    return False
(新規) model_manage_models.pyの作成
DBからインポートしたモデル情報を取り出すための関数です。モデル管理画面用としてフォルダを分けましたが、モデル分析画面からも呼び出す予定です。
from model_analyzer.models import ModelList
from django.db.models import Max, Min, Count, Q
def get_model_list():
    # 各model_idについて最新のものを取得
    return ModelList.objects.values(
        "model_id"
    ).annotate(
        import_date=Min("regist_date"),
        latest_id=Max("id"),
        sort_id=Min("id"),
        version=Count("model_id")
    )
def get_table_list(nav_params: dict):
    model_list = get_model_list()
    id_list = [q['latest_id'] for q in model_list]
    table_list = ModelList.objects.filter(Q(id__in=id_list, delete_flag=0))
    model_list = sorted(model_list, key=lambda x: x["sort_id"])
    mapping = []
    for item in model_list:
        template = {
            "version": item["version"],
            "import_date": item["import_date"],
        }
        for data in table_list:
            if item["latest_id"] == data.id:
                template |= data.__dict__
                template["motivate"] = data.get_motivate_markdown()
                template["raw_motivate"] = data.motivate
                template["ticket"] = data.target_horse_ticket()
                mapping += [template.copy()]
    nav_params |= {"model_table": mapping}
    nav_params |= {
        "model_columns": [
            "モデル名",
            "モデルID",
            "モデル種別",
            "モデル情報",
            "登録日",
            "更新日",
            "バージョン",
            "",
            ""
        ]
    }
(更新) forms.pyの編集
フォーム画面に初期値を入れるのとMarkdownに対応するための変更をします。
ModelImportFormsクラスのmativateフィールドを変更
# 16行目付近
    motivate = forms.CharField(
        label="モデルの説明 (markdown対応)",
        required=True,
        widget=forms.Textarea(
            attrs={
                "rows": 8
            }
        ),
        initial="""以下テンプレ、好きに変えてください。
#### モデル作成の動機
#### モデルの目的
#### 確認したい仮説
#### 特徴量
#### 目的変数
"""
    )
(更新) models.pyの編集
Markdownに対応するために、Modelクラスを変更します。
# 新規に以下のモジュールのImport文を追加
from markdownx.models import MarkdownxField
from django.utils.safestring import mark_safe
from markdownx.utils import markdownify
# ModelListクラスのmotivateフィールドを変更
    motivate = MarkdownxField(
        verbose_name="モデル説明", max_length=2048, blank=False)
また、models.pyを修正したため、今回もmigrationの実行をしましょう。
python manage.py makemigrations
python manage.py migrate
※上記の2コマンドを実行した際にエラーが出てしまう人は、以下のように対処してください。
 このやり方は環境を完全に作り直すやり方なので、開発初期のころには有効ですが普通に濫用はやめた方がいいです。
・db.sqlite3ファイルを削除
・「model_analyzer/__pycache__」フォルダを削除
・「model_analyzer/migrations/__pycache__」フォルダを削除
上記の3つを削除後、再度migrationコマンドを実行してください。
(更新) urls.pyの編集
モデル削除用のURLルートを新規に追加します。GETリクエスト用のURLになってるのでそのURLへアクセスするだけでDBからモデル情報を削除できるため若干問題ありですが、気になる人はPOST用に改造しても良いです。
    path('model-manage', views.ModelManageView.as_view(), name='model-manage'),
    # 以下を追加
    path('model-manage/delete/<int:number>',
         views.modelDeleteView, name='model-manage-delete'),
(更新) views.pyの編集
Viewクラスについて、モデル削除用の処理画面およびDBから取得したモデル情報をViewクラスへ渡す処理を追加します。
# 以下の新規モジュールのImport文を追加
from model_analyzer.models import ModelList
from model_analyzer.tools.model_control.model_manage_models import get_table_list
# ModelManageViewクラスを変更
class ModelManageView(View):
    def start(self, request: WSGIRequest):
        (省略)
        # 以下を追加
        get_table_list(self.nav_params)
    def get(self, request: WSGIRequest):
        (省略)
        # 以下を修正
        return render(request, 'model_manage/model_manage.html', self.nav_params)
    def post(self, request: WSGIRequest):
        flag = validation_model_import_forms(request, self.nav_params)
        # 以下を追加
        self.start(request)
        (省略)
        # 以下を修正
        return render(request, 'model_manage/model_manage.html', self.nav_params)
# 以下を追加;モデル削除用の処理
def modelDeleteView(request: WSGIRequest, number: int):
    model = ModelList.objects.get(id=number)
    target_model_list = ModelList.objects.filter(model_id=model.model_id)
    for imodel in target_model_list:
        imodel.delete()
    return redirect("/model-manage")
モデルリスト表示の開発手順(WEB画面側)
以下のCSSファイル、JavaScriptファイル、HTMLファイルを新規作成・編集をしてください。
CSSファイル
.cell-pointer {
    cursor: pointer;
}
.cell-pointer:hover {
    text-decoration: underline;
}
body {
    margin-top: 56px;
}
JavaScriptファイル
表示時間を変更しています。
$(document).ready(function () {
    $.each(
        $("[id=success-alert]"),
        function (i, elem) {
            $(elem).fadeTo(
                10000 + 2000 * i,
                2000
            ).slideUp(
                2000, function () {
                    $(elem).slideUp(500);
                }
            );
        }
    );
});
function initForm(data, flag) {
    let title_elem = document.getElementById("import-title");
    let model_name_elem = document.getElementById("id_model_name");
    let motivate_elem = document.getElementById("id_motivate");
    let memo_elem = document.getElementById("id_memo");
    let model_id_elem = document.getElementById("id_model_id");
    if (flag) {
        title_elem.textContent = "更新フォーム";
        model_name_elem.value = data.model_name;
        motivate_elem.innerHTML = data.motivate;
        memo_elem.value = data.memo;
        model_id_elem.value = data.model_id;
    }
    else {
        title_elem.textContent = "登録フォーム";
        motivate_elem.innerHTML = data.motivate;
        model_id_elem.value = null;
    }
}
HTMLファイル
<span class="cell-pointer text-danger" data-bs-toggle="modal" data-bs-target="#modelDeleteForm{{data.id}}">削除</span>
<div id="modelDeleteForm{{data.id}}" class="modal text-reset" tabindex="-1" aria-labelledby="modelDeleteLabel"
  aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <div class="modal-header">
        <h1 class="modal-title fs-5">モデル削除最終確認</h1>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <div class="row ml-5">
          以下のモデルを削除しますか?
        </div>
        <div class="row ml-1 mpr-1">
          <div class="table-responsive">
            <table class="table table-hover table-sm">
              <thead class="table-danger">
                <tr>
                  <th scope="col" class="border-start border-end text-center">モデルID</th>
                  <th scope="col" class="border-start border-end text-center">モデル名</th>
                  <th scope="col" class="border-start border-end text-center">モデル種別</th>
                </tr>
              </thead>
              <tbody class="table-group-divider">
                <tr>
                  <th scope="row" class="border-start border-end text-center">{{data.model_id}}</th>
                  <td class="border-start border-end text-center">{{data.model_name}}</td>
                  <td class="border-start border-end text-center">{{data.model_type}}</td>
                </tr>
              </tbody>
            </table>
          </div>
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary btn-sm" data-bs-dismiss="modal">キャンセル</button>
          <a type="button" class="btn btn-danger btn-sm" href="/model-manage/delete/{{ data.id }}">削除</a>
        </div>
      </div>
    </div>
  </div>
{% load crispy_forms_tags %}
<div class="col-sm-12 col-xs-12 col-md-6">
  <button type="button" class="btn btn-primary" data-bs-toggle="modal" data-bs-target="#modelImportForm"
    onclick="initForm({motivate: `{{form.fields.motivate.initial}}`}, false)">
    モデル登録
  </button>
</div>
<div id="modelImportForm" class="modal fade" tabindex="-1" aria-labelledby="modelImportLabel" aria-hidden="true">
  <div class="modal-dialog">
    <div class="modal-content">
      <form class="" action="" method="post" enctype="multipart/form-data">
        <div class="modal-header">
          <h1 class="modal-title fs-5" id="import-title">登録フォーム</h1>
          <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
        </div>
        <div class="modal-body">
          {% csrf_token %}
          {{ form|crispy }}
          <input type="hidden" name="model_update" id="id_model_id" value="">
        </div>
        <div class="modal-footer">
          <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
          <button type="submit" class="btn btn-primary">送信</button>
        </div>
      </form>
    </div>
  </div>
</div>
<div id="modelInfo{{data.id}}" class="modal" tabindex="-1" aria-labelledby="modelInfoLabel" aria-hidden="true">
  <div class="modal-dialog modal-dialog-scrollable modal-lg">
    <div class="modal-content">
      <div class="modal-header">
        <h1 class="modal-title fs-5">【詳細】モデル名: {{data.model_name}}</h1>
        <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <div class="table table-borderless shadow bg-body rounded">
          <table class="w-100">
            <tbody class="p-2">
              <tr>
                <th scope="row" class="border-bottom text-start p-2 bg-secondary text-white">モデルID</th>
                <td class="border-start border-bottom text-start p-3">{{data.model_id}}</td>
              </tr>
              <tr>
                <th scope="row" class="border-bottom text-start p-2 bg-secondary text-white">モデル種別</th>
                <td class="border-start border-bottom text-start p-3">{{data.model_type}}</td>
              </tr>
              <tr>
                <th scope="row" class="border-bottom text-start p-2 bg-secondary text-white">対象馬券</th>
                <td class="border-bottom border-start text-start p-3">{{data.ticket}}</td>
              </tr>
              <tr>
                <th scope="row" class="border-bottom text-start p-2 bg-secondary text-white">モデル説明</th>
                <td class="border-start border-bottom text-start p-3">{{data.motivate | safe}}</td>
              </tr>
              <tr>
                <th scope="row" class="border-bottom text-start p-2 bg-secondary text-white">モデルパス</th>
                <td class="border-start border-bottom text-start p-3">{{data.model_dir}}</td>
              </tr>
              <tr>
                <th scope="row" class="text-start p-2 bg-secondary text-white">備考</th>
                <td class="border-start text-start p-3">{{data.memo}}</td>
              </tr>
            </tbody>
          </table>
        </div>
      </div>
    </div>
  </div>
大幅に変更されているので、差し換えてください
{% extends "base_layout.html" %}
{% load crispy_forms_tags %}
{% block cssblock %}
<link rel="stylesheet" href="/static/css/model_manage/style.css">
{% endblock %}
{% block content %}
<div class="px-md-5">
  <div class="row my-2">
    {% include 'model_manage/modal/modelImportModal.html' %}
  </div>
  <div class="row mt-5 md-5"></div>
  <div class="row">
    <h4>モデル一覧</h4>
  </div>
  <div class="row">
    <div class="table-responsive">
      <table class="table table-hover table-striped shadow-sm p-3 mb-5 bg-body rounded">
        <thead class="table-dark">
          <tr class="text-nowrap">
            {% for col in model_columns %}
            <th scope="col" class="border-start border-end text-center">{{col}}</th>
            {% endfor %}
          </tr>
        </thead>
        <tbody class="table-group-divider">
          {% for data in model_table %}
          <tr class="text-nowrap">
            <th scope="row" class="border-start border-end">{{data.model_name}}</th>
            <td class="border-start border-end">{{data.model_id}}</td>
            <td class="border-start border-end">{{data.model_type}}</td>
            <td class="border-start border-end text-center text-primary">
              <span class="cell-pointer" data-bs-toggle="modal" data-bs-target="#modelInfo{{data.id}}">Details</span>
              {% include 'model_manage/modal/modelInfoModal.html' %}
            </td>
            <td class="border-start border-end">{{data.import_date|date:"Y/m/d"}}</td>
            {% if data.version > 1 %}
            <td class="border-start border-end">
              {{data.regist_date|date:"Y/n/j H:i"}}
            </td>
            {% else %}
            <td class="border-start border-end text-center">
              ---
            </td>
            {% endif %}
            </td>
            <td class="border-start border-end text-end">{{data.version}}</td>
            <td class="border-start border-end text-primary" data-bs-toggle="modal" data-bs-target="#modelImportForm"
              onclick="initForm({model_name: '{{ data.model_name }}', motivate: `{{data.raw_motivate}}`, memo: '{{data.memo}}', model_id: '{{data.model_id}}'}, true)">
              <span class="cell-pointer">更新</span>
            </td>
            <td class="border-start border-end">
              {% include 'model_manage/modal/modelDeleteModal.html' %}
            </td>
          </tr>
          {% endfor %}
        </tbody>
      </table>
    </div>
  </div>
</div>
{% endblock %}
{% block jsblock %}
<script src="/static/js/model_manage/update_form_initial.js"></script>
{% endblock %}
<!DOCTYPE html>
<html lang="ja">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <link rel="icon" type="image/x-icon" href="/static/images/favicon.png">
  <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"
    integrity="sha384-QWTKZyjpPEjISv5WaRU9OFeRpok6YctnYmDr5pNlyT2bRjXh0JMhjY6hW+ALEwIH" crossorigin="anonymous">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script>
  {% block cssblock %}
  {% endblock %}
  <title>競馬予想ソフト</title>
</head>
<body>
  <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js"
    integrity="sha384-YvpcrYf0tY3lHB60NNkmXc5s9fDVZLESaAA55NDzOxhy9GkcIdslK1eN7N6jIeHz"
    crossorigin="anonymous"></script>
  <header>
    <nav class="navbar navbar-expand bg-body-secondary" data-bs-theme="dark">
      <div class="container-fluid">
        <a class="navbar-brand" href="./index.html">um-AI</a>
        <button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbar"
          aria-controls="navbar" aria-expanded="false" aria-label="Toggle navigation">
          <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbar">
          <ul class="navbar-nav me-auto">
            <li class="nav-item">
              <a class="nav-link active" aria-current="page" href="./index.html">Home</a>
            </li>
            <li class="nav-item">
              {% if model_analyze %}
              <a class="nav-link active" href="./model-analyze">モデル分析</a>
              {% else %}
              <a class="nav-link disabled" href="./model-analyze" tabindex="-1" aria-disabled="true">モデル分析</a>
              {% endif %}
            </li>
            <li class="nav-item">
              {% if model_manage %}
              <a class="nav-link active" href="./model-manage">モデル管理</a>
              {% else %}
              <a class="nav-link disabled" href="./model-manage" tabindex="-1" aria-disabled="true">モデル管理</a>
              {% endif %}
            </li>
          </ul>
        </div>
      </div>
    </nav>
  </header>
  <div class="mt-1"></div>
  {% if messages %}
  <div class="container-fluid">
    <div class="notification is-info">
      {% for message in messages %}
      {% if message.tags != 'error' %}
      <div {% if message.tags %}class="alert alert-{{ message.tags }}" {% endif %} id="success-alert"
        style="font-size: small;">
        {% else %}
        <div {% if message.tags %}class="alert alert-danger" {% endif %} id="success-alert" style="font-size: small;">
          {% endif %}
          {{ message }}
        </div>
        {% endfor %}
      </div>
    </div>
    {% endif %}
    <main>
      <div class="d-flex container-fluid">
        {% block content %}
        <!-- ここに個別のhtmlが入る -->
        {% endblock %}
      </div>
    </main>
    {% block jsblock %}
    {% endblock %}
    <script src="/static/js/base_layout/fade_popup.js"></script>
</body>
</html>
サーバの起動と画面の確認
それでは、モデルリスト表示機能ができたので、サーバを起動しましょう。
manage.pyファイルがあるフォルダがカレントディレクトリになっていることを確認して、コマンドプロンプトで以下のコマンドを実行
python manage.py runserver
http://localhost:8000/model-manage へアクセスしましょう。
以下の画面が表示されればOKです!

簡単な動作確認
モデル一覧の表示、モデルの更新、モデルの削除の正常な動きは完成イメージで示した通りです。
そのため、動作確認では「モデルの新規登録失敗 (すでにインポート済とエラーになる)」の場合と「モデルの更新失敗 (モデル情報ファイルのモデルIDが更新対象のモデルIDと違う)」の場合を確認します。
まずは一旦モデルをインポートします。
以下のmodel_info.jsonファイルを指定して「モデル登録」ボタンから新規インポートしてください。(モデル名は適当で大丈夫です。)
{
  "model_id": "first_model",
  "model_type": "lightGBM",
  "model_dir": "E:\\keiba_dev\\keiba_ai\\models\\first_model",
  "model_analyze_dir": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze",
  "model_predict_dir": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\00_predict",
  "bet_columns_map": {
    "tan": "bet_tan"
  },
  "pl_column_map": {
    "tan": "pl_tan"
  },
  "return_hit_rate_file": {
    "tan": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\tan\\hit_and_return_rate.csv"
  },
  "fav_bet_num_dir": {
    "tan": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\tan\\fav_bet_num"
  },
  "profit_loss_dir": {
    "tan": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\tan\\profit_loss"
  },
  "odds_graph_file": {
    "tan": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\tan\\odds_graph"
  },
  "confidence_column": "pred_prob",
  "confidence_rank_column": "pred_rank"
}
以下のようになればOKです。

モデルの更新に失敗する場合の確認
次に「モデルの更新」の失敗時の動作確認のために、以下のmodel_info_wrong_modelID.jsonファイルを用意してください。
{
  "model_id": "first_model_wrongID",
  "model_type": "lightGBM",
  "model_dir": "E:\\keiba_dev\\keiba_ai\\models\\first_model",
  "model_analyze_dir": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze",
  "model_predict_dir": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\00_predict",
  "bet_columns_map": {
    "tan": "bet_tan"
  },
  "pl_column_map": {
    "tan": "pl_tan"
  },
  "return_hit_rate_file": {
    "tan": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\tan\\hit_and_return_rate.csv"
  },
  "fav_bet_num_dir": {
    "tan": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\tan\\fav_bet_num"
  },
  "profit_loss_dir": {
    "tan": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\tan\\profit_loss"
  },
  "odds_graph_file": {
    "tan": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\tan\\odds_graph"
  },
  "confidence_column": "pred_prob",
  "confidence_rank_column": "pred_rank"
}
以下のイメージgifの通りにモデルを更新できないことを確認してください。

上記のようにエラーメッセージのポップアップが出てきたら成功です。
モデルの新規登録に失敗する場合の確認
現状、以下のmodel_info.jsonの情報を持つモデルがインポート済だと思います。そこで、再度同じmodel_info.jsonファイルを指定して「モデル登録」をしてみましょう。
{
  "model_id": "first_model",
  "model_type": "lightGBM",
  "model_dir": "E:\\keiba_dev\\keiba_ai\\models\\first_model",
  "model_analyze_dir": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze",
  "model_predict_dir": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\00_predict",
  "bet_columns_map": {
    "tan": "bet_tan"
  },
  "pl_column_map": {
    "tan": "pl_tan"
  },
  "return_hit_rate_file": {
    "tan": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\tan\\hit_and_return_rate.csv"
  },
  "fav_bet_num_dir": {
    "tan": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\tan\\fav_bet_num"
  },
  "profit_loss_dir": {
    "tan": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\tan\\profit_loss"
  },
  "odds_graph_file": {
    "tan": "E:\\keiba_dev\\keiba_ai\\models\\first_model\\analyze\\tan\\odds_graph"
  },
  "confidence_column": "pred_prob",
  "confidence_rank_column": "pred_rank"
}
以下のイメージgifのように動かしてみて、既存のモデルを新規で登録できないことを確認しましょう。

上記のような警告メッセージのポップアップが出れば成功です。
以上で、モデルリスト表示機能の開発手順完了です。お疲れさまでした。
ソース公開しました!
Bookersでロードマップ4で解説したソースを公開しました!
以下のリンクへ飛んでいただき、BookersとYouTube連携して私のチャンネルを登録すると無料でWEBアプリのソースが手に入ります。

良ければ、実際に触って遊んでみてください!
  
  
  
  





コメント