SGソフトウェアの開発日記

日々の開発に関するあれこれを書き散らかしています。

ブラックジョー問題(小変更すると今まで動いていた別の処理が動かなくなる件)

ご存知の通り(?)、JRA出走データ解析プロジェクト進行中です。
このプロジェクトでは、当日の予想のために、出走馬の過去走成績を加味した出馬表のExcelシートを生成しています。出馬表のExcelシートには、出走したレースの最高のクラスと、1着になったことがある最高のクラスを一覧として表示するようにしています。
この出馬表のデータ等を使ってレースを予想し、レース終了後の復習の際の参考にもしているのですが、10/15の東京12Rで1着になったブラックジョーのデータを見直していたところ、データに誤りがあることに気がつきました。
10/15の時点で、ブラックジョーが1着になった最高クラスは、正しくは500万下なのですが、表中では未勝利が最高クラスとして表示されてしまっています。

ブラックジョーの1着戦績が間違っている出馬表

f:id:yokotan:20161018115831j:plain


1着戦績が正しい出馬表

f:id:yokotan:20161018115825j:plain

このような不正確なデータを使っていては、正確な予想ができるはずがありません。急いで、原因の追究にとりかかりました。
このデータは、競走馬ごとの過去走を記録したExcelデータが元になっています。そこで、問題発生時のブラックジョーのデータを確認してみました。すると、500万下で1着になった前走(9/18)のレースのデータの行だけが欠落しており、2走前(8/20)のレース以前のデータは揃っていました。これが、データに誤りを生じた直接の原因です。


9/18のデータが欠落したブラックジョーの過去走成績

f:id:yokotan:20161018115821j:plain

9/18のデータを含め、欠損のないブラックジョーの過去走成績

f:id:yokotan:20161018115810j:plain

ではなぜ、このレースのデータが欠落していたのか。本来であれば、出馬表のExcelシートを作成する際に、あらかじめ、競走馬のExcelデータを更新しておく必要があったのですが、その更新が行われていなかったため、直近のデータが反映されていなかったということです。
でも、今回の問題発覚まで気がつかないぐらい、表面上はうまく動いているように見えました。どうも、最近おこなった、なんらかの修正が問題発生のきっかけになったとしか思えません。
落ち着いて考えてみると、思い当たる節がありました。ロジックをトレースしてみたところ、予想した原因が当たっているようです。
そもそも、出馬表のシートの生成処理では、競走馬のExcelシートを更新する処理はおこなっていませんでした。従って、出馬表のシートの生成処理を単体で実行している限り、まともに動くわけがないのです。にもかかわらず、たまたま、別の要因で正常に動いていたので問題視していませんでした。
たまたま動いていたロジックはこうです。
JRA出走データ解析では、当日のレースの出馬表生成処理以外に、終了したレースのレース結果生成処理をおこなうようにしています。このとき、新たなレースの着順が確定しているので、レースに出走した競走馬のExcelシートは、このレースの結果を反映するために更新されます。そのため、対象の競走馬が次のレースに出走する時点では、過去走すべてのデータが反映された競走馬のExcelシートの準備が整っており、次のレースの出馬表のシートの生成処理では、改めて、競走馬のExcelシートを更新する必要はなかったのです。
この一連の処理をそのままおこなっていれば、今回の問題は発生しなかったのですが、数週間前に、レース結果の生成処理を効率化しようと小変更を加えた結果、競走馬のExcelシートの更新処理まで外れてしまいました。その結果、一連の処理中に、どこにも競走馬のExcelシートの更新処理がないという事態に陥ったのです。
レース結果の生成処理の効率化自体は必要な修正だったのでそちらを元に戻すことはせず、出馬表の生成処理に競走馬のExcelシートの更新処理を含めるように変更して解決しました。他にも別のケアレスミスが発覚したりして修正に少し時間がかかりましたが、プログラム自体は処理済みです。
残っている問題は、今までに作成した出馬表データの解析結果が、現時点では信用できないということで、今まで作成した出馬表データをレース結果のデータと共に、再生成する必要が出てきました。1日分のデータの解析に3~5時間かかるので、次の開催に間に合わないことは確実ですが、今までのデータが全く使えないわけではないので、地道にデータの再生成を積み重ねて、徐々にデータを入れ替えていきます。
いずれにせよ、自分用のシステムでやらかしただけなので、今回は、(自分以外には)迷惑をかけていないことが不幸中の幸いです。
一般的なシステム開発では、こういうミスは、あってはならないことですが、開発の終盤(しかも予定納期ぎりぎりか、とっくに過ぎてる)では、よくある話です。「いや、うちのプロジェクトではありえない!」という方もいらっしゃると思いますが、うらやましい限りです。
なにが厄介かといって、今回のようなミスは、システムのリリース時点では露見せず、一見関係のない別の変更を行ったときに時限爆弾のように発生するタイプの問題なんですね。責任を問われるのはミスを仕込んだ時のリーダーではなく、本来は功労者であるはずの、問題が発生して解決した時のリーダーなのが、理不尽な話です。せめて、自分では、こういうミスを仕込まないように注意して開発を進めたいと思います。もっとも、仕込んだからこそ、今回こうなったんですが...。

 

 

 

先頭行のタイトル位置を検索する処理が突然機能しなくなった件

JRA出走データ解析プロジェクト進行中です。

過去走の解析パラメータとして、馬場状態(良、稍重、重、不良)を追加したところ、別の箇所で誤動作が発生するようになりました。

該当の箇所は、過去走データの表の先頭行にあるタイトルの列位置を取得する処理です。誤動作が発生するのは、騎手重量の列位置を取得するところで、タイトルが「重」となっており、このタイトルを検出するはずが、馬場状態の「重」を検出してしまって、そのセルが先頭行ではないため、エラーと判定してしまっていました。

 

f:id:yokotan:20160920151802j:plain


検索する処理を実行する前に先頭行をセレクトしていたのですが、2行目に「重」と書かれたセルがあると、そのセルを検出してしまうのが原因です。不思議なことに、この現象が発生するのは、先頭行の直下のレースの馬場状態が「重」だったときだけで、二つ目以降のレースの馬場状態が「重」であっても問題は発生しません。

詳細な発生条件を切り分けるのが面倒だったので本当の原因はわかっていませんが、おそらく、レース毎にオートフィルターをかけたときに、先頭行の直下のレースの行は、タイトルがある先頭行と行が連続しているので、検索範囲として認識されてしまっているのだと思います。

最初は、なぜそうなるのか全然わからなかったですが、いろいろ調べて試してみるうちに、どうやらCurrentRegion指定していることが問題だろうという結論に至りました。

問題のコード

Dim FoundCell As Range
Rows("1:1").Select
Set FoundCell = _
    Range("A1").CurrentRegion.Find(What:=Caption, _
    LookAt:=xlWhole)

改修後のコード

Dim FoundCell As Range
Dim MaxCol As Long
MaxCol = Range("A1").End(xlToRight).Column
Set FoundCell = Range(Cells(1, 1), _
     Cells(1, MaxCol)).Find(What:=Caption, _
     LookAt:=xlWhole)

元々は、Find関数の検索範囲の起点をA1セルで指定してCurrentRegionで検索範囲を全体に広げるようになっていたのですが、CurrentRegionの意味を理解せずに、マクロの記録で自動生成したコードをそのまま使っていたのが敗因のようです。CurrentRegionを外して、Find関数の検索範囲をRangeで指定することで、先頭行のみを検索範囲とすることができました。

意味を理解しないままコピペしていたツケが回ってきたわけですね。

 

 

 

1500mのレースに未対応だったので修正をかけたら、さらにバグを発見した件

JRAの過去のレース結果を解析して予想に役立てようという試みを性懲りもなく続けています。

過去走の解析では、対象レースと同じ距離のレースだけでなく、その前後の長い距離、短い距離のレース結果も解析対象としています。1400mの競争ならば、1150m、1200mなどの短い距離や、1600mといった長い距離のレースが対象となります。先週までは、あまりかけ離れた距離よりも近い距離のほうが参考になるかと思って対象距離の上下250mで区切っていました。

今週から札幌開催が加わったのですが、ここで問題が発生しました。1500mのレースだけ、過去走の解析結果が、すっぽり抜け落ちていたのです。過去走のデータを確認すると、ほとんどの馬が、1800mのレースの経験馬で、250mの範囲に引っかからなかったので集計されなかったことがわかりました。

他の距離だとだいたい250mの範囲をカバーしておけばいいので、1500mのレースだけ範囲を300mに広げることにして、VBAのコードを修正しようと見直したのですが、そのコードに、致命的なバグが隠れていたというのが今回の話です。

これが、致命的なバグを含んだコードです。

'該当距離が存在しない場合に機能しない
Function fcClosedLongerDistance(Distance As Long, TrackType As String, DistanceList() As B2DistanceInfo)
    fcClosedLongerDistance = Distance
    Dim Distance2 As Long
    ListSize = UBound(DistanceList)
    Dim Index As Long
    
    Index = fcB2DistanceInfoGetIndex(Distance, DistanceList)
    If Index < 0 Then
        Exit Function
    End If
    
    Dim N As Long
    N = -1
    Dim Cnt As Long
    For Cnt = Index + 1 To ListSize
        Distance2 = DistanceList(Cnt).Distance
        If DistanceList(Cnt).N > N Then
            fcClosedLongerDistanceOLD = Distance2
            N = DistanceList(Cnt).N
        End If
        If Abs(Distance2 - Distance) > 250 Then
            Exit Function
        End If
    Next Cnt
End Function

Function fcB2DistanceInfoGetIndex(Distance As Long, DistanceList() As B2DistanceInfo)
    fcB2DistanceInfoGetIndex = -1
    Dim Distance2 As Long
    ListSize = UBound(DistanceList)
    Dim Cnt As Long
    For Cnt = 0 To ListSize
        If DistanceList(Cnt).Distance = Distance Then
            fcB2DistanceInfoGetIndex = Cnt
            Exit Function
        End If
    Next Cnt

End Function

ダメなのは、最初の関数の前半で二つ目の関数をコールしてIndexを取得している部分です。DistanceList配列が、過去走の中に出現する距離を昇順に並べたリストなのですが、 二つ目の関数で、対象レースの距離の要素番号を検索しています。長距離の解析の場合は、この要素番号以降の項目を、ひとつめの関数の後半でスキャンしているのですが、 大前提として、過去走に、対象レースの距離が含まれていないと、この処理は機能しません。

札幌の1500mのレースでは、そもそも1500mを走ったことのある馬がいなかったため、DistanceList配列に1500mの要素は含まれていません。従って、エラー(-1)を返していたという状況でした。 修正後のコードがこちら。

'長距離
Function fcClosedLongerDistance(Distance As Long, TrackType As String, DistanceList() As B2DistanceInfo)
    fcClosedLongerDistance = Distance
    Dim Distance2 As Long
    ListSize = UBound(DistanceList)
    
    Dim Diff As Long
    Diff = 250
    If Distance = 1500 Then
        Diff = 300
    End If
    
    Dim N As Long
    N = -1
    Dim Cnt As Long
    For Cnt = 0 To ListSize
        Distance2 = DistanceList(Cnt).Distance
        If Distance2 <= Distance Then
            GoTo Continue
        End If
        If DistanceList(Cnt).N > N Then
            fcClosedLongerDistance = Distance2
            N = DistanceList(Cnt).N
        End If
        If Abs(Distance2 - Distance) > Diff Then
            Exit Function
        End If
Continue:
    Next Cnt
End Function

Indexを取得する関数は不要になったので削除。1500mの距離のみ、探索範囲を300mとして1800mまでをカバーしています。他の距離については探索範囲は250mのままです。 DistanceList配列の要素Nは、各馬の過去4走における距離別のレースをカウントしたものです。同じレースであっても馬が異なれば別カウントです。1500mのレースの過去走で、 1700mのカウントが5、1800mのカウントが15であれば、1800mのレース成績を解析対象としています。

この変更で予想の精度が上がることを期待して解析処理を走らせてみたところ、一か所変更漏れが発覚し、先ほどから再度実行中。処理完了に6時間ぐらいかかるので、 今日のVBAプログラミングはこれにて終了。

Excel VBAの検索文で初心者並みの失敗をやらかした件

ときどき紹介していますが、JRAのサイトに掲載されているデータをExcelのデータに取り込むVBAを自分用に作成して使用しています。
以前から馬番が正しく反映されない不具合が発生していたので調べてみました。

不具合は下のように、本来14番となるべき「タイセイラナキラ」の馬番が8番になっているという現象です。

f:id:yokotan:20160313230546p:plain


最初は、JRAのサイトから取得したページを解析する際に、予期しないレイアウトを誤検出しているのかと思ったのですが、トレースしてみると、中京1Rのデータを取得した段階では正しく14番と認識できていました。

どうやら、それ以降の別のレースのデータで上書きしているようなので、該当行にアクセスしようとした時点で実行を止めるようにコードを修正してみたところ、中京12Rの8番「セイラ」のデータで上書きしてしまっていることがわかりました。

そのせいで、中京12Rのデータからは8番「セイラ」の行が消えてしまっています。

f:id:yokotan:20160313230602p:plain


理由はすぐにわかりました。対象馬の記述行の行番号を検索する関数が「タイ(セイラ)ナキラ」の文字の一部を「セイラ」だと誤認識して、誤った行番号を返してしまっていました。HTMLのデータ解析を行なうための部分一致検索の関数を、そのまま使っていたのが原因です。

対策として、完全一致検索の関数を新設し、その関数を検索に使うように修正しました。

当初、馬番がおかしくなる不具合を対策していたつもりだったのが、出走馬の記述行自体が消えてしまう不具合も同時に解決することができたので、恥ずかしいレベルのバグでしたが、結果オーライ。

 

パナソニック CF-RZ4CDDJR レッツノート RZシリーズ
 

 

3D版Android鉄道アプリ「超特急1.0.2」公開

 超特急1.0.2を公開しました。


超特急は、ショーティタイプの車両で編成した超特急列車が行き交う、ただそれだけのソフトです。暇つぶしにどうぞ。

1.0からの変更点は0系タイプの追加です。1.0では、列車編成はN700系タイプのみでしたが、初代の新幹線を追加しました。

バッテリーの消耗が激しいという難点がありますので、表示したまま放置することがないようご注意ください。

現時点では、描画処理の最適化が不十分ですので、高性能の端末でないとまともに表示されないかもしれませんので悪しからず。

右上のスピーカーアイコンで音のオンオフができます。他に操作できることはありません。ただただ眺めてください。

超特急は、著作権フリーBGM配布サイトHURT RECORDの効果音データを使用しています。

www.hurtrecord.com

 

 

超特急 - Google Play の Android アプリ

 

 

プラレール アドバンス AS-01 0系新幹線

プラレール アドバンス AS-01 0系新幹線

 

 

ぷららのホームページサービス上でのメール問い合わせ

先月、ブログのコメント欄を通じてアプリの問い合わせがあり、SGソフトウェアのサイトの問い合わせページが機能していないことを知りました。他に問い合わせする方法はないかと、ブログのコメント欄を利用して連絡してくださったとのこと。
確認してみると、プロバイダであるぷららが提供していたメール送信用のCGIのサービスが1年半も前に終了していました。つまり、1年半もの間、問い合わせページが使えなかったわけです。
この問い合わせにはメールで対応させていただいたのですが、問い合わせページが使えないのは困るので、ぷららのサービスを調べなおしたところ、ユーザ側でCGIを用意すれば、プロバイダのサーバーのsendmailを直接叩くことはできるので、問い合わせメールを受信するようにサイトを再構築することは可能だということはわかりました。メールを送ることができる相手がぷららのメール利用者に限られているのは、メール送信用CGIサービスが使えていた頃と同じです。sendmail側で制限をかけているのでしょうね。
しかし、このCGIを一から作り上げるのはなんとも大変そうなので、ネットを探してみたところ、ありがたいことにフリーのツールを提供しているところを見つけました。
シンクグラフィカさん提供のmailform.js。「メールCGI」で検索すると上位に出現しますし、使っている人の評判も良いようです。

 

メールフォームCGI | ホームページ制作素材ダウンロード

 

よくある質問Q&Aのコーナーも最高です。

 

ホームページ制作素材の利用規約

 

サイトの問い合わせページに合わせこむには、いくつか手を入れる必要がありましたが、なんとか問い合わせページからメールを送ることができるようになりました。
存在しないリンクが残存したりしてますが、問い合わせページの復活が最優先事項でしたので、ひとまずこれで、問い合わせページの修正完了とします。

お問い合わせ

f:id:yokotan:20160216232225p:plain

 

sendmail 第3版〈VOLUME1〉運用編

sendmail 第3版〈VOLUME1〉運用編

 

 

超特急1.0の動画

超特急の画面の動画をキャプチャしました。Android端末をお持ちでない方と、アプリをインストールしていない方への紹介動画です。


普通のアプリやゲームの紹介動画は、ほんのさわりだけを紹介して、後は実際にアプリを使ってみてくださいというものだと思いますが、超特急の場合、紹介動画の動きが全てです。違いといえば、キャプチャの方法がわからないので、音声無しの動画になってしまっていることだけです。

踏切など、いままでのアプリでは、パソコンの画面で表示することを想定して、わざわざ横長の画面に変換していましたが、よくよく考えると、メインのターゲットはスマートフォンタブレットのユーザーですから、アプリと同じ縦長のままでなんの問題もない、むしろ横長にするほうが間違いじゃない?と気がついたので、オリジナルの縦横比でアップロードしてみました。が、youtubeにアップロードすると画面サイズとしては横長にされるようで、本来の画面の左右に黒ベタが広がってしまいました。今後の課題です。

(初稿では横長になっていましたが横幅を調節するだけでトリミングできましたので、現在は修正済みです。)

 

Nゲージ 10-547 N700系新幹線 のぞみ 基本 (4両)

Nゲージ 10-547 N700系新幹線 のぞみ 基本 (4両)