Create app.py
Browse files
app.py
ADDED
|
@@ -0,0 +1,188 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from flask import Flask, request, jsonify
|
| 2 |
+
from decimal import Decimal, ROUND_HALF_UP
|
| 3 |
+
import re
|
| 4 |
+
from datetime import datetime
|
| 5 |
+
|
| 6 |
+
app = Flask(__name__)
|
| 7 |
+
|
| 8 |
+
# 入れ子構造を持つ単位変換用の辞書
|
| 9 |
+
CONVERSION_TABLE = {
|
| 10 |
+
'length': {
|
| 11 |
+
'm': 1,
|
| 12 |
+
'cm': 0.01,
|
| 13 |
+
'mm': 0.001,
|
| 14 |
+
'km': 1000,
|
| 15 |
+
},
|
| 16 |
+
'time': {
|
| 17 |
+
's': 1,
|
| 18 |
+
'min': 60,
|
| 19 |
+
'h': 3600,
|
| 20 |
+
},
|
| 21 |
+
'mass': {
|
| 22 |
+
'g': 1,
|
| 23 |
+
'kg': 1000,
|
| 24 |
+
'mg': 0.001,
|
| 25 |
+
'µg': 1e-6,
|
| 26 |
+
},
|
| 27 |
+
'volume': {
|
| 28 |
+
'L': 1,
|
| 29 |
+
'mL': 0.001,
|
| 30 |
+
}
|
| 31 |
+
}
|
| 32 |
+
|
| 33 |
+
def log(message):
|
| 34 |
+
"""タイムスタンプ付きでメッセージを出力"""
|
| 35 |
+
timestamp = datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')[:-3]
|
| 36 |
+
print(f"[{timestamp}] {message}")
|
| 37 |
+
|
| 38 |
+
def parse_value_unit(value_unit_str):
|
| 39 |
+
"""数値と単位をパースして、共通単位に変換"""
|
| 40 |
+
log(f"Parsing: {value_unit_str}")
|
| 41 |
+
match = re.match(r"([\d.]+)\s*\[([a-zA-Zµ^2]+)\]", value_unit_str)
|
| 42 |
+
if match:
|
| 43 |
+
value, unit = match.groups()
|
| 44 |
+
log(f"Parsed value: {value}, unit: {unit}")
|
| 45 |
+
return Decimal(value), unit
|
| 46 |
+
else:
|
| 47 |
+
raise ValueError("Invalid format. The input should be in the form 'value[unit]'.")
|
| 48 |
+
|
| 49 |
+
def find_unit_family(unit):
|
| 50 |
+
"""指定された単位のファミリー(カテゴリ)を見つける"""
|
| 51 |
+
for family, units in CONVERSION_TABLE.items():
|
| 52 |
+
if unit in units:
|
| 53 |
+
return family
|
| 54 |
+
raise ValueError(f"Unit {unit} not found in any known unit families.")
|
| 55 |
+
|
| 56 |
+
def convert_to_common_unit(value, from_unit, to_unit):
|
| 57 |
+
"""異なる単位を同じ単位に変換する"""
|
| 58 |
+
from_family = find_unit_family(from_unit)
|
| 59 |
+
to_family = find_unit_family(to_unit)
|
| 60 |
+
|
| 61 |
+
if from_family != to_family:
|
| 62 |
+
raise ValueError(f"Cannot convert between different unit families: {from_unit} to {to_unit}")
|
| 63 |
+
|
| 64 |
+
conversion_factor = CONVERSION_TABLE[from_family][from_unit] / CONVERSION_TABLE[to_family][to_unit]
|
| 65 |
+
converted_value = value * Decimal(conversion_factor)
|
| 66 |
+
log(f"Converting {value} {from_unit} to {converted_value} {to_unit}")
|
| 67 |
+
return converted_value
|
| 68 |
+
|
| 69 |
+
def calculate_expression(expression):
|
| 70 |
+
"""複数の数値を処理する計算(加算、減算、乗算、除算に対応)"""
|
| 71 |
+
log(f"Calculating expression: {expression}")
|
| 72 |
+
pattern = r"([\d.]+\s*\[[^\]]+\])"
|
| 73 |
+
terms = re.findall(pattern, expression)
|
| 74 |
+
log(f"Found terms: {terms}")
|
| 75 |
+
|
| 76 |
+
if not terms:
|
| 77 |
+
raise ValueError("Invalid expression format")
|
| 78 |
+
|
| 79 |
+
# 最初の項目を取得
|
| 80 |
+
value1, unit1 = parse_value_unit(terms[0])
|
| 81 |
+
total_value = value1
|
| 82 |
+
decimal_places_list = [len(str(value1).replace('.', '').replace('-', ''))] # 有効数字のリストを保存
|
| 83 |
+
|
| 84 |
+
for i in range(1, len(terms)):
|
| 85 |
+
value2, unit2 = parse_value_unit(terms[i])
|
| 86 |
+
|
| 87 |
+
# 単位変換
|
| 88 |
+
if unit1 != unit2:
|
| 89 |
+
value2 = convert_to_common_unit(value2, unit2, unit1)
|
| 90 |
+
|
| 91 |
+
total_value += value2
|
| 92 |
+
decimal_places_list.append(len(str(value2).replace('.', '').replace('-', '')))
|
| 93 |
+
log(f"Current total: {total_value}, current significant figures list: {decimal_places_list}")
|
| 94 |
+
|
| 95 |
+
# 最も少ない有効数字に基づいて丸める
|
| 96 |
+
min_sig_figs = min(decimal_places_list)
|
| 97 |
+
rounded_result = total_value.quantize(Decimal('1e{0}'.format(-(min_sig_figs - 1))), rounding=ROUND_HALF_UP)
|
| 98 |
+
log(f"Final result: {rounded_result} [{unit1}]")
|
| 99 |
+
|
| 100 |
+
return f"{rounded_result} [{unit1}]"
|
| 101 |
+
|
| 102 |
+
# HTMLテンプレート(バイト列でエンコード)
|
| 103 |
+
template = b"""
|
| 104 |
+
<!DOCTYPE html>
|
| 105 |
+
<html lang="en">
|
| 106 |
+
<head>
|
| 107 |
+
<meta charset="UTF-8">
|
| 108 |
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
| 109 |
+
<title>Unit Calculator</title>
|
| 110 |
+
<style>
|
| 111 |
+
body {
|
| 112 |
+
font-family: Arial, sans-serif;
|
| 113 |
+
background-color: #f4f4f4;
|
| 114 |
+
margin: 0;
|
| 115 |
+
padding: 20px;
|
| 116 |
+
}
|
| 117 |
+
h1 {
|
| 118 |
+
color: #333;
|
| 119 |
+
}
|
| 120 |
+
form {
|
| 121 |
+
margin-bottom: 20px;
|
| 122 |
+
}
|
| 123 |
+
input[type="text"] {
|
| 124 |
+
padding: 8px;
|
| 125 |
+
width: 300px;
|
| 126 |
+
font-size: 16px;
|
| 127 |
+
}
|
| 128 |
+
button {
|
| 129 |
+
padding: 8px 12px;
|
| 130 |
+
font-size: 16px;
|
| 131 |
+
background-color: #007BFF;
|
| 132 |
+
color: #fff;
|
| 133 |
+
border: none;
|
| 134 |
+
cursor: pointer;
|
| 135 |
+
}
|
| 136 |
+
button:hover {
|
| 137 |
+
background-color: #0056b3;
|
| 138 |
+
}
|
| 139 |
+
.result {
|
| 140 |
+
font-size: 20px;
|
| 141 |
+
font-weight: bold;
|
| 142 |
+
color: #007BFF;
|
| 143 |
+
}
|
| 144 |
+
</style>
|
| 145 |
+
</head>
|
| 146 |
+
<body>
|
| 147 |
+
<h1>Unit Calculator</h1>
|
| 148 |
+
<form id="calcForm">
|
| 149 |
+
<label for="expression">Enter expression:</label>
|
| 150 |
+
<input type="text" id="expression" name="expression" required>
|
| 151 |
+
<button type="submit">Calculate</button>
|
| 152 |
+
</form>
|
| 153 |
+
<div id="result" class="result"></div>
|
| 154 |
+
|
| 155 |
+
<script>
|
| 156 |
+
document.getElementById('calcForm').addEventListener('submit', function(event) {
|
| 157 |
+
event.preventDefault();
|
| 158 |
+
var expression = document.getElementById('expression').value;
|
| 159 |
+
var xhr = new XMLHttpRequest();
|
| 160 |
+
xhr.open('POST', '/calculate', true);
|
| 161 |
+
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
|
| 162 |
+
xhr.onreadystatechange = function () {
|
| 163 |
+
if (xhr.readyState === 4 && xhr.status === 200) {
|
| 164 |
+
document.getElementById('result').innerText = 'Result: ' + xhr.responseText;
|
| 165 |
+
}
|
| 166 |
+
};
|
| 167 |
+
xhr.send('expression=' + encodeURIComponent(expression));
|
| 168 |
+
});
|
| 169 |
+
</script>
|
| 170 |
+
</body>
|
| 171 |
+
</html>
|
| 172 |
+
"""
|
| 173 |
+
|
| 174 |
+
@app.route('/', methods=['GET'])
|
| 175 |
+
def index():
|
| 176 |
+
return render_template_string(template.decode('utf-8'))
|
| 177 |
+
|
| 178 |
+
@app.route('/calculate', methods=['POST'])
|
| 179 |
+
def calculate():
|
| 180 |
+
expression = request.form['expression']
|
| 181 |
+
try:
|
| 182 |
+
result = calculate_expression(expression)
|
| 183 |
+
return result
|
| 184 |
+
except Exception as e:
|
| 185 |
+
return f"Error: {str(e)}", 400
|
| 186 |
+
|
| 187 |
+
if __name__ == '__main__':
|
| 188 |
+
app.run(debug=True)
|