野球リーグスコア管理システムとキャップ野球情報局に関する進捗です。
旧システムについてはこちらの記事をご覧ください。
前回、puppeteer-clusterで同時起動の制御をしたはずだったのだが、
書き方が悪いのか同時アクセスすると暴走してVPSの応答がなくなってしまうので、vercelに分離した。
herokuと同じようにサーバサイドのプログラムをデプロイできるサービス。
netlifyはフロントエンド特化。
vercelはnow.json
でルーティングを記述している。
正規表現で各メソッドにreq.query.id
を渡す場合の記述の仕方はこんな感じ。
"routes": [
{ "src": "/cap-baseball/(?<id>[^/]*)", "dest": "/screenshotCap.js?id=$1" },
{ "src": "/(?<id>[^/]*)", "dest": "/screenshot.js?id=$1" }
]
このあたりは前回の記事で説明しているので不要かなと思う。
cluster使わない書き方に戻してます。
const browser = await puppeteer.launch({
args: chrome.args,
executablePath: await chrome.executablePath,
headless: chrome.headless,
});
const page = await browser.newPage();
await page.goto("https://pawapro-gen.netlify.app/view/" + id);
await page.setCacheEnabled(false);
await page.waitForSelector("#main");
const fullPage = await page.$("#main");
const fullPageSize = await fullPage.boundingBox();
const VIEWPORT = { width: 600, height: 450, deviceScaleFactor: 1 };
await page.setViewport(
Object.assign({}, VIEWPORT, { height: fullPageSize.height })
);
await page.waitFor(5000);
const elements = await page.$$("#main");
let img;
for (const [index, element] of elements.entries()) {
img = await element.screenshot();
}
await page.close();
await console.log("screenshot done.");
return img;
「キャップ野球情報局」というサイトを作っています。
ちまちまとマイナーアップデートを重ねていましたが、
5/17からNextJSへ移植するのと同時に、次のメジャーアップデート(v2.0)を7月リリースを目標に作っています。
twitterアカウントを持っていれば誰でもログインできます。
別サービス「みんなのSCORE」のデータに対応する形で選手ページを持っています。
選手間で「この選手はどういう選手です」という他己紹介をする機能です。
別サービス「みんなのSCORE」のデータに対応する形でチームページを持っています。
チームの紹介のほか、チームのテーマカラーが設定できます。
キャップ野球には、主なイベントとして
がありますが、それらの情報を登録・編集することができます。
運営者ギルドでは画像ストレージとしてimgurを薦められていたのですが、imgurとcloudinary両方を実装して、使いやすさの観点からcloudinaryを採用することにしました。
imgurはOAuth認証すればログイン状態でアップロードできるのですが、ブラウザでPINが必要など使い勝手と実装に難がある印象です。
比較的実装が簡単ですが、imgurでは匿名アップロードした画像をGUIでは管理できません。
工数的にはcloudinaryのログイン状態アップロード≒imgurの匿名アップロードという印象だったので、GUIで全体管理ができるcloudinaryを選びました。用途としては無料枠で足りると思います。
以下のパッケージを使う。
- passport-twitter
- twitterログインを行うライブラリ
- express-session
- ログイン情報をセッション(メモリ)に格納するライブラリ。
- express-mysql-session
- 揮発するメモリのセッション情報をmysqlに格納するライブラリ。
reactJSからAPI(NodeJS/Express)へはcredentialsを要求するようにする。
ユーザが書き換えるテーブルに履歴テーブルを用意した。
これでこのプロジェクトの合計テーブル数は21となった。
これまで権限レベルが単一だったが、今回権限を制限したユーザを想定しているので権限周りの実装を行った。
スコア入力で大人数の中から選手を選ぶのに自動入力コンポーネントを使用している。
react-autocompleteはどうもモバイルフレンドリーではなかったようで、モバイルでサジェストが見えなくなってしまう欠点があったので使用するコンポーネントを移行した。
他プロジェクトではすでに導入しているSCSSをこのプロジェクトでも導入。最も古参プロジェクトだったのでCRA(react-scripts)のバージョンを上げるなど。
オートサジェスト修正した pic.twitter.com/ZMB7IBKccB
— ckoshien/みんなのSCORE ver4α (@ckoshien_tech) April 18, 2020
アカウントの申請をいただくと、「チーム管理者」として招待します。チーム管理者は・新規試合登録・自チーム試合結果編集・自チームメンバー登録ができます。 pic.twitter.com/3TCUtyyRCz
— ckoshien/みんなのSCORE ver4α (@ckoshien_tech) April 18, 2020
長らくボードを放置していましたが、
みんなのSCOREのversion4に向けて開発を開始しました。
の計算を三塁打を加味したものに修正。
メンテナンスの都合上、セイバーメトリクスの計算をSQLからTypescript側で行うように修正。
CASE
WHEN ((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) is null THEN 0
WHEN ((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) is not null THEN ((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats))
END as slg,
CASE
WHEN ((sum(hit)+sum(four_ball))/sum(tpa))+((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) is null THEN 0
WHEN ((sum(hit)+sum(four_ball))/sum(tpa))+((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats)) is not null THEN ((sum(hit)+sum(four_ball))/sum(tpa))+((sum(hit)+sum(twobase)*1+sum(homerun)*2)/sum(at_bats))
END as ops,
(sum(hit)+sum(four_ball))/sum(tpa) as obp,
((((sum(hit)+sum(twobase)*1+sum(homerun)*2)+0.26*sum(four_ball)-0.03*sum(strike_out)+3*sum(tpa))*(sum(hit)+sum(four_ball)+2.4*sum(tpa)))/(9*sum(tpa))-0.9*sum(tpa))*27/(sum(at_bats)-sum(hit)) as RC27,
CASE
WHEN sum(at_bats)/sum(strike_out) is null THEN 0
WHEN sum(at_bats)/sum(strike_out) is not null THEN sum(at_bats)/sum(strike_out)
END as not_strike_out,
CASE
WHEN sum(at_bats)/sum(homerun) is null THEN 0
WHEN sum(at_bats)/sum(homerun) is not null THEN sum(at_bats)/sum(homerun)
END as avg_homerun,
CASE
WHEN sum(rbi)/sum(at_bats) is null THEN 0
WHEN sum(rbi)/sum(at_bats) is not null THEN sum(rbi)/sum(at_bats)
END as avgRbi,
public calcSABR = (players) => {
for(let i = 0; i < players.length; i++){
players[i].average = players[i].hit/players[i].at_bats;
players[i].slg = (players[i].hit + players[i].twobase*1 + players[i].three_base*2 + players[i].homerun*3 )/players[i].at_bats;
players[i].obp = (players[i].hit + players[i].four_ball)/players[i].tpa;
players[i].ops = players[i].slg + players[i].obp;
const rc27b = players[i].slg * players[i].at_bats + 0.26 * players[i].four_ball - 0.03 * players[i].strike_out;
const rc27a = players[i].slg * players[i].at_bats + players[i].four_ball;
const rc27c = players[i].at_bats + players[i].four_ball;
players[i].rc27 = ((rc27a + 2.4*rc27c)*(rc27b + 3*rc27c)/(9*rc27c))-(0.9 + rc27c);
players[i].not_strike_out = players[i].at_bats/players[i].strike_out;
players[i].avg_homerun = players[i].at_bats/players[i].homerun;
players[i].avgRbi = players[i].rbi/players[i].at_bats;
}
return players;
}
チーム管理者は
みんなのSCORE、キャップ野球情報局はユーザの皆さんのご意見を募集しています。
おかげでtoFixed()
とかコケます。parseFloat
(文字列を小数にパースするメソッド)とか間に挟みました。
undefの判定に! value
って書いてたらvalue
が0
で躓きましたw
今回からdockerコンテナを使い始めたのですが、小文字のテーブルを大文字でSQL書くと認識しません。やっぱりlinuxですね。
複数リーグ対応のためにUIからAPIに渡すパラメータなどを増やす必要があるので調整。
関東キャップリーグさんの試合データが公開されているので入力していきます。
1カード分探しても見つからなかったのですが。
現在右上メニューバー内から遷移できます。
チームの一覧と所属選手一覧を実装しました。materializeのchipsを使用しています。それから、チーム名・選手名検索ができるフォームの実装をしました。今回は所属チームごとの表示の振り分けをクライアント側で行うようにしました。チームの一覧を最近試合を行った順に並び替えできていないのが課題です。
フォームに何も入力されていないときは選手一覧を返すようにして、
入力された場合はAPIに検索結果を取りに行く方式にしています。
フォームがクリアされるとまた選手一覧を取得します。
現在入力している全てのシーズンのデータから通算成績を取得する処理を実装しました。
react側は旧システムAPIであらかじめ作っていたのもあって、
これにはそんなに時間がかかりませんでした。
本日、2019年度前期シーズン開幕戦でした。
現行のReact/NodeJS/Netlifyのシステムでは初めての本格運用が始まります。
スコアの速報を入力していたのですがサーバサイド側で打率が0のときに打撃十傑から除く処理が抜けていたことに気づいてスコア入力を中断してシステムの修正を行うなど。
横軸を打席数(四球含む)、縦軸を打率として、
打席数―打率の相関グラフを描画するよう実装しました。
赤枠は規定打席以上ですが、chart.jsでは描画していません。
これまでバックエンドをJavaで書いていましたが、JavaからNodeJSへ移行中です。