Spaces:
Runtime error
Runtime error
neoai-kterasawa
commited on
Commit
·
9e16e32
1
Parent(s):
4e83f99
update
Browse files- .gitignore +2 -0
- app.py +41 -30
- archive/app.py +42 -0
- app_gradio.py → archive/app_gradio.py +3 -1
- app_streamlit.py → archive/app_streamlit.py +2 -1
- utils.py → archive/utils.py +0 -0
- kculculate.code-workspace +63 -0
- makefile +7 -0
- poetry.lock +0 -0
- pyproject.toml +22 -0
- requirements.txt +2 -3
- src/func.py +19 -0
- src/model/odds.py +161 -0
.gitignore
ADDED
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
1 |
+
.token
|
2 |
+
__pycache__
|
app.py
CHANGED
@@ -1,42 +1,53 @@
|
|
1 |
-
from typing import
|
2 |
|
3 |
-
import gradio as gr
|
4 |
|
5 |
-
|
6 |
|
7 |
-
DEFAULT_AMOUNT =
|
8 |
|
9 |
|
10 |
-
def
|
11 |
-
|
12 |
|
13 |
-
df = get_calculated_df(amount, text2oddslist(str(_text)))
|
14 |
-
info = get_info(df)
|
15 |
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
|
|
20 |
|
21 |
-
# markdownの整形
|
22 |
-
md_string = (
|
23 |
-
f"**購入額:** ¥ **{info['sum']:,}**<br>"
|
24 |
-
f"**点数:** **{info['num_kind']}** 点<br>"
|
25 |
-
f"**払戻:** ¥ **{info['refound_mean']:,}** (**{info['profit_mean']:+,}** (**{info['rate_min']:+.0%}**))<br>"
|
26 |
-
"---"
|
27 |
-
)
|
28 |
|
29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
30 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
31 |
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
|
36 |
-
|
37 |
-
|
38 |
-
|
39 |
-
|
40 |
-
|
|
|
|
|
41 |
|
42 |
-
|
|
|
|
1 |
+
from typing import Final
|
2 |
|
3 |
+
import gradio as gr # type: ignore
|
4 |
|
5 |
+
import src.func as func
|
6 |
|
7 |
+
DEFAULT_AMOUNT: Final[int] = 10_000
|
8 |
|
9 |
|
10 |
+
def clear(text: str) -> str:
|
11 |
+
return func.clear(text)
|
12 |
|
|
|
|
|
13 |
|
14 |
+
def calculate(budget: int, text: str) -> tuple[str, list[list]]:
|
15 |
+
try:
|
16 |
+
return func.calculate(budget, text)
|
17 |
+
except Exception as e:
|
18 |
+
raise gr.Error(e)
|
19 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
20 |
|
21 |
+
with gr.Blocks() as demo:
|
22 |
+
with gr.Row():
|
23 |
+
with gr.Column():
|
24 |
+
budget_input = gr.Number(value=DEFAULT_AMOUNT, label="予算")
|
25 |
+
text_input = gr.Textbox(label="netkeiba 買い目コピペ")
|
26 |
+
with gr.Row():
|
27 |
+
calculate_button = gr.Button(value="Calculate", variant="primary")
|
28 |
+
clear_button = gr.Button(value="Clear")
|
29 |
|
30 |
+
with gr.Column():
|
31 |
+
markdown_output = gr.Markdown()
|
32 |
+
df_output = gr.Dataframe(
|
33 |
+
value=[],
|
34 |
+
label=None,
|
35 |
+
headers=["", "", "オッズ", "賭け額", "払戻"],
|
36 |
+
datatype=["str", "str", "number", "number", "number"],
|
37 |
+
col_count=(5, "fixed"),
|
38 |
+
)
|
39 |
|
40 |
+
# click
|
41 |
+
calculate_button.click(
|
42 |
+
calculate,
|
43 |
+
inputs=[budget_input, text_input],
|
44 |
+
outputs=[markdown_output, df_output],
|
45 |
+
)
|
46 |
+
clear_button.click(
|
47 |
+
clear,
|
48 |
+
inputs=[text_input],
|
49 |
+
outputs=[text_input],
|
50 |
+
)
|
51 |
|
52 |
+
if "__main__" == __name__:
|
53 |
+
demo.launch(debug=True)
|
archive/app.py
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Any
|
2 |
+
|
3 |
+
import gradio as gr
|
4 |
+
|
5 |
+
from archive.utils import get_calculated_df, get_info, text2oddslist
|
6 |
+
|
7 |
+
DEFAULT_AMOUNT = 5000
|
8 |
+
|
9 |
+
|
10 |
+
def calculate(_amount: Any, _text: Any) -> tuple[str, dict]:
|
11 |
+
amount = int(_amount) or DEFAULT_AMOUNT
|
12 |
+
|
13 |
+
df = get_calculated_df(amount, text2oddslist(str(_text)))
|
14 |
+
info = get_info(df)
|
15 |
+
|
16 |
+
# dfの整形
|
17 |
+
display_cols = ["odds", "buy", "refound"]
|
18 |
+
if df["name"].map(lambda name: name != "").any():
|
19 |
+
display_cols = ["name"] + display_cols
|
20 |
+
|
21 |
+
# markdownの整形
|
22 |
+
md_string = (
|
23 |
+
f"**購入額:** ¥ **{info['sum']:,}**<br>"
|
24 |
+
f"**点数:** **{info['num_kind']}** 点<br>"
|
25 |
+
f"**払戻:** ¥ **{info['refound_mean']:,}** (**{info['profit_mean']:+,}** (**{info['rate_min']:+.0%}**))<br>"
|
26 |
+
"---"
|
27 |
+
)
|
28 |
+
|
29 |
+
return md_string, df[["index"] + display_cols]
|
30 |
+
|
31 |
+
|
32 |
+
amount_input = gr.Number(value=DEFAULT_AMOUNT, label="amount")
|
33 |
+
text_input = gr.Textbox(label="text")
|
34 |
+
markdown_output = gr.Markdown()
|
35 |
+
df_output = gr.Dataframe(type="pandas")
|
36 |
+
iface = gr.Interface(
|
37 |
+
fn=calculate,
|
38 |
+
inputs=[amount_input, text_input],
|
39 |
+
outputs=[markdown_output, df_output],
|
40 |
+
)
|
41 |
+
|
42 |
+
iface.launch()
|
app_gradio.py → archive/app_gradio.py
RENAMED
@@ -1,7 +1,9 @@
|
|
1 |
import gradio as gr
|
2 |
-
from utils import get_calculated_df, get_info, text2oddslist
|
3 |
|
4 |
DEFAULT_AMOUNT = 5000
|
|
|
|
|
5 |
def calculate(amount, text):
|
6 |
amount = amount or DEFAULT_AMOUNT
|
7 |
odds_list = text2oddslist(text)
|
|
|
1 |
import gradio as gr
|
2 |
+
from archive.utils import get_calculated_df, get_info, text2oddslist
|
3 |
|
4 |
DEFAULT_AMOUNT = 5000
|
5 |
+
|
6 |
+
|
7 |
def calculate(amount, text):
|
8 |
amount = amount or DEFAULT_AMOUNT
|
9 |
odds_list = text2oddslist(text)
|
app_streamlit.py → archive/app_streamlit.py
RENAMED
@@ -1,5 +1,6 @@
|
|
1 |
import streamlit as st
|
2 |
-
|
|
|
3 |
|
4 |
amount: int = int(st.number_input("amount", min_value=100, value=3000, step=100))
|
5 |
text: str = st.text_area("text", "")
|
|
|
1 |
import streamlit as st
|
2 |
+
|
3 |
+
from archive.utils import get_calculated_df, get_info, text2oddslist
|
4 |
|
5 |
amount: int = int(st.number_input("amount", min_value=100, value=3000, step=100))
|
6 |
text: str = st.text_area("text", "")
|
utils.py → archive/utils.py
RENAMED
File without changes
|
kculculate.code-workspace
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
{
|
2 |
+
"folders": [
|
3 |
+
{
|
4 |
+
"name": "kculculate",
|
5 |
+
"path": "."
|
6 |
+
}
|
7 |
+
],
|
8 |
+
"settings": {
|
9 |
+
"editor.codeActionsOnSave": {
|
10 |
+
"source.fixAll.eslint": "explicit",
|
11 |
+
"source.fixAll.stylelint": "explicit"
|
12 |
+
},
|
13 |
+
"editor.formatOnSave": true,
|
14 |
+
"editor.formatOnPaste": true,
|
15 |
+
"editor.formatOnType": true,
|
16 |
+
"json.format.keepLines": true,
|
17 |
+
"[javascript]": {
|
18 |
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
19 |
+
},
|
20 |
+
"[typescript]": {
|
21 |
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
22 |
+
},
|
23 |
+
"[typescriptreact]": {
|
24 |
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
25 |
+
},
|
26 |
+
"[css]": {
|
27 |
+
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
28 |
+
},
|
29 |
+
"[json]": {
|
30 |
+
"editor.defaultFormatter": "vscode.json-language-features"
|
31 |
+
},
|
32 |
+
"search.exclude": {
|
33 |
+
"**/node_modules": true,
|
34 |
+
"static": true
|
35 |
+
},
|
36 |
+
"[python]": {
|
37 |
+
"editor.defaultFormatter": "ms-python.black-formatter",
|
38 |
+
"editor.codeActionsOnSave": {
|
39 |
+
"source.organizeImports": "explicit"
|
40 |
+
}
|
41 |
+
},
|
42 |
+
"flake8.args": [
|
43 |
+
"--max-line-length=119",
|
44 |
+
"--max-complexity=15",
|
45 |
+
"--ignore=E203,E501,E704,W503",
|
46 |
+
"--exclude=.venv,.git,__pycache__,.mypy_cache,.hg"
|
47 |
+
],
|
48 |
+
"isort.args": ["--settings-path=pyproject.toml"],
|
49 |
+
"black-formatter.args": ["--config=pyproject.toml"],
|
50 |
+
"mypy-type-checker.args": ["--config-file=pyproject.toml"],
|
51 |
+
"python.analysis.extraPaths": ["./backend"]
|
52 |
+
},
|
53 |
+
"extensions": {
|
54 |
+
"recommendations": [
|
55 |
+
"esbenp.prettier-vscode",
|
56 |
+
"dbaeumer.vscode-eslint",
|
57 |
+
"ms-python.flake8",
|
58 |
+
"ms-python.isort",
|
59 |
+
"ms-python.black-formatter",
|
60 |
+
"ms-python.mypy-type-checker"
|
61 |
+
]
|
62 |
+
}
|
63 |
+
}
|
makefile
ADDED
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
.PHONY: lint
|
2 |
+
lint: ## run tests with poetry (isort, black, pflake8, mypy)
|
3 |
+
poetry run black src
|
4 |
+
poetry run isort src
|
5 |
+
poetry run pflake8 src
|
6 |
+
poetry run mypy src --explicit-package-bases
|
7 |
+
|
poetry.lock
ADDED
The diff for this file is too large to render.
See raw diff
|
|
pyproject.toml
ADDED
@@ -0,0 +1,22 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
[tool.poetry]
|
2 |
+
name = "kculculate"
|
3 |
+
version = "0.1.0"
|
4 |
+
description = ""
|
5 |
+
authors = ["neoai-kterasawa <[email protected]>"]
|
6 |
+
readme = "README.md"
|
7 |
+
|
8 |
+
[tool.poetry.dependencies]
|
9 |
+
python = "^3.10"
|
10 |
+
gradio = "^4.39.0"
|
11 |
+
pydantic = "^2.8.2"
|
12 |
+
|
13 |
+
[tool.poetry.group.dev.dependencies]
|
14 |
+
isort = "^5.13.2"
|
15 |
+
black = "^24.4.2"
|
16 |
+
mypy = "^1.11.0"
|
17 |
+
pyproject-flake8 = "^7.0.0"
|
18 |
+
|
19 |
+
|
20 |
+
[build-system]
|
21 |
+
requires = ["poetry-core"]
|
22 |
+
build-backend = "poetry.core.masonry.api"
|
requirements.txt
CHANGED
@@ -1,3 +1,2 @@
|
|
1 |
-
|
2 |
-
|
3 |
-
gradio==3.44.3
|
|
|
1 |
+
gradio == "4.39.0"
|
2 |
+
pydantic == "2.8.2"
|
|
src/func.py
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from typing import Final
|
2 |
+
|
3 |
+
from src.model.odds import Choices
|
4 |
+
|
5 |
+
DEFAULT_AMOUNT: Final[int] = 10_000
|
6 |
+
|
7 |
+
|
8 |
+
def calculate(budget: int, raw_text: str) -> tuple[str, list[list]]:
|
9 |
+
choices = Choices.from_text(raw_text)
|
10 |
+
choices_with_bet = choices.to_choices_with_bet(budget)
|
11 |
+
|
12 |
+
summary_markdown = choices_with_bet.to_summary_markdwown()
|
13 |
+
table = choices_with_bet.to_table()
|
14 |
+
|
15 |
+
return summary_markdown, table
|
16 |
+
|
17 |
+
|
18 |
+
def clear(text: str) -> str:
|
19 |
+
return ""
|
src/model/odds.py
ADDED
@@ -0,0 +1,161 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
|
3 |
+
from pydantic import (
|
4 |
+
BaseModel,
|
5 |
+
RootModel,
|
6 |
+
StrictFloat,
|
7 |
+
StrictInt,
|
8 |
+
StrictStr,
|
9 |
+
computed_field,
|
10 |
+
field_validator,
|
11 |
+
)
|
12 |
+
|
13 |
+
|
14 |
+
class Choice(BaseModel):
|
15 |
+
number: StrictStr
|
16 |
+
name: StrictStr
|
17 |
+
odds: StrictFloat
|
18 |
+
|
19 |
+
|
20 |
+
class ChoiceWithBet(Choice):
|
21 |
+
bet_amount: StrictInt
|
22 |
+
|
23 |
+
@computed_field # type: ignore
|
24 |
+
@property
|
25 |
+
def pay_back(self) -> int:
|
26 |
+
return int(self.bet_amount * self.odds)
|
27 |
+
|
28 |
+
@computed_field # type: ignore
|
29 |
+
@property
|
30 |
+
def profit(self) -> int:
|
31 |
+
return self.pay_back - self.bet_amount
|
32 |
+
|
33 |
+
|
34 |
+
class Choices(RootModel[list[Choice]]):
|
35 |
+
root: list[Choice]
|
36 |
+
|
37 |
+
@field_validator("root")
|
38 |
+
@classmethod
|
39 |
+
def ensure_at_least_one_choice(cls, choices: list[Choice]) -> list[Choice]:
|
40 |
+
if len(choices) == 0:
|
41 |
+
raise ValueError("買い目が1つもありません")
|
42 |
+
return choices
|
43 |
+
|
44 |
+
def to_choices_with_bet(self, budget: int) -> "ChoicesWithBet":
|
45 |
+
inverse_ratios_sum = sum(1 / choice.odds for choice in self.root)
|
46 |
+
|
47 |
+
# 重み計算
|
48 |
+
choice_with_bet_list: list[ChoiceWithBet] = []
|
49 |
+
for chice in self.root:
|
50 |
+
weight = (1 / chice.odds) / inverse_ratios_sum
|
51 |
+
# 逆比
|
52 |
+
choice_with_bet_list.append(
|
53 |
+
ChoiceWithBet(
|
54 |
+
number=chice.number,
|
55 |
+
name=chice.name,
|
56 |
+
odds=chice.odds,
|
57 |
+
bet_amount=max(int(weight * budget / 100) * 100, 100),
|
58 |
+
)
|
59 |
+
)
|
60 |
+
|
61 |
+
return ChoicesWithBet(root=choice_with_bet_list)
|
62 |
+
|
63 |
+
@classmethod
|
64 |
+
def from_text(cls, text: str) -> "Choices":
|
65 |
+
lines = text.split("\n")
|
66 |
+
for strat_key in ("選択", "オッズ", "組み合わせ", "オッズを見て個別選択"):
|
67 |
+
if strat_key in lines:
|
68 |
+
lines = lines[lines.index(strat_key) + 1 :]
|
69 |
+
text = "\n".join(lines)
|
70 |
+
# pcの時
|
71 |
+
is_pc = re.match(r"[\d\n]+", text)
|
72 |
+
if is_pc:
|
73 |
+
return cls._from_text_from_pc(text)
|
74 |
+
# moduleの時
|
75 |
+
return cls._from_text_from_mobile(text)
|
76 |
+
|
77 |
+
@classmethod
|
78 |
+
def _from_text_from_pc(cls, text: str) -> "Choices":
|
79 |
+
choices_list: list[Choice] = []
|
80 |
+
tmp_list: list[str] = []
|
81 |
+
lines: list[str] = text.split("\n")
|
82 |
+
for line in lines:
|
83 |
+
is_odds = "." in line
|
84 |
+
if is_odds:
|
85 |
+
choices_list.append(
|
86 |
+
Choice(
|
87 |
+
number="-".join(tmp_list),
|
88 |
+
name="",
|
89 |
+
odds=float(line),
|
90 |
+
)
|
91 |
+
)
|
92 |
+
tmp_list = []
|
93 |
+
else:
|
94 |
+
if line.strip():
|
95 |
+
tmp_list.append(line.strip())
|
96 |
+
return cls(root=choices_list)
|
97 |
+
|
98 |
+
@classmethod
|
99 |
+
def _from_text_from_mobile(cls, text: str) -> "Choices":
|
100 |
+
choice_list: list[Choice] = []
|
101 |
+
lines: list[str] = text.split("\n")
|
102 |
+
for strat_key in ("選択", "オッズ", "組み合わせ", "オッズを見て個別選択"):
|
103 |
+
if strat_key in lines:
|
104 |
+
lines = lines[lines.index(strat_key) + 1 :]
|
105 |
+
if "すべてを選択" in lines:
|
106 |
+
lines = lines[: lines.index("すべてを選択")]
|
107 |
+
for i in range(len(lines) // 3):
|
108 |
+
# oddsが1つ目にある
|
109 |
+
if "." in lines[3 * i + 1]:
|
110 |
+
idx_odds = 1
|
111 |
+
idx_name = 2
|
112 |
+
else:
|
113 |
+
idx_odds = 2
|
114 |
+
idx_name = 1
|
115 |
+
|
116 |
+
choice_list.append(
|
117 |
+
Choice(
|
118 |
+
number="-".join(lines[3 * i].split()),
|
119 |
+
name="-".join(lines[3 * i + idx_name].split()),
|
120 |
+
odds=float(lines[3 * i + idx_odds]),
|
121 |
+
)
|
122 |
+
)
|
123 |
+
return cls(root=choice_list)
|
124 |
+
|
125 |
+
|
126 |
+
class ChoicesWithBet(RootModel[list[ChoiceWithBet]]):
|
127 |
+
root: list[ChoiceWithBet]
|
128 |
+
|
129 |
+
def to_summary_markdwown(self) -> str:
|
130 |
+
return (
|
131 |
+
f"**点数**: {len(self.root)} 点<br>"
|
132 |
+
f"**購入額**: ¥ {self.total_bet_amount:,}<br>"
|
133 |
+
f"**回収率**: {self.average_pay_back / self.total_bet_amount:.0%}"
|
134 |
+
)
|
135 |
+
|
136 |
+
def to_table(self) -> list[list]:
|
137 |
+
return [
|
138 |
+
[
|
139 |
+
choice.number,
|
140 |
+
choice.name,
|
141 |
+
choice.odds,
|
142 |
+
choice.bet_amount,
|
143 |
+
choice.pay_back,
|
144 |
+
]
|
145 |
+
for choice in self.root
|
146 |
+
]
|
147 |
+
|
148 |
+
@computed_field # type: ignore
|
149 |
+
@property
|
150 |
+
def total_bet_amount(self) -> int:
|
151 |
+
return sum(choice.bet_amount for choice in self.root)
|
152 |
+
|
153 |
+
@computed_field # type: ignore
|
154 |
+
@property
|
155 |
+
def average_pay_back(self) -> float:
|
156 |
+
return sum(choice.pay_back for choice in self.root) / len(self.root)
|
157 |
+
|
158 |
+
@computed_field # type: ignore # type: ignore
|
159 |
+
@property
|
160 |
+
def average_profit(self) -> float:
|
161 |
+
return sum(choice.profit for choice in self.root) / len(self.root)
|