Files
eve_structure/webapp/routes/cookbook.py
2025-08-27 18:55:45 +02:00

294 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from __future__ import annotations
from typing import Any, Dict, List, Tuple
from flask import Blueprint, request, jsonify
import time
# Services
from webapp.services.ccookbook import (
call_cookbook_build_cost,
call_eve_ref_materials,
)
# Storage
from webapp.storage.orders import (
load_orders, update_order,
cache_costs, cache_materials,
)
bp = Blueprint("cookbook", __name__, url_prefix="/api")
def _now() -> str:
return time.strftime("%Y-%m-%dT%H:%M:%S%z", time.gmtime())
# ----------------------------- Helpers ----------------------------------------
def _arg(name: str, default: str = "") -> str:
v = request.args.get(name)
return v if v is not None else default
def _to_int(s: str, default: int = 0) -> int:
try:
return int(float(s))
except Exception:
return default
def _order_by_id(order_id: str) -> Dict[str, Any] | None:
for o in load_orders():
if o.get("id") == order_id:
return o
return None
# ----------------------------- Endpoints Raw --------------------------------
@bp.get("/cookbook/build_cost")
def api_build_cost():
"""Kosten via evecookbook (Preise, Gesamt etc.)."""
bps = request.args.getlist("blueprintTypeId")
if not bps and request.args.get("blueprintTypeId"):
bps = [request.args.get("blueprintTypeId")]
if not bps:
return jsonify({"error": "missing blueprintTypeId"}), 400
try:
data = call_cookbook_build_cost(
blueprint_type_ids=bps,
quantity=_to_int(_arg("quantity", "1")),
price_mode=_arg("priceMode", "buy"),
additional_costs=_to_int(_arg("additionalCosts", "0")),
base_me=_to_int(_arg("baseMe", "0")),
components_me=_to_int(_arg("componentsMe", "0")),
system=_arg("system", "Jita"),
facility_tax=_to_int(_arg("facilityTax", "0")),
industry_structure_type=_arg("industryStructureType", "Station"),
industry_rig=_arg("industryRig", "None"),
reaction_structure_type=_arg("reactionStructureType", "Athanor"),
reaction_rig=_arg("reactionRig", "None"),
reaction_flag=_arg("reactionFlag", ""),
blueprint_version=_arg("blueprintVersion", "tq"),
)
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)}), 502
@bp.get("/cookbook/materials")
def api_materials():
"""
Materialliste via EVE Ref.
Erwartet: blueprintTypeId, optional quantity/baseMe/industryStructureType.
"""
if not request.args.get("blueprintTypeId"):
return jsonify({"error": "missing blueprintTypeId"}), 400
try:
data = call_eve_ref_materials(dict(request.args))
# Sicherstellen, dass immer eine Array-Liste unter "materials" steht
mats = data.get("materials") or []
if isinstance(mats, dict):
mats = list(mats.values())
data["materials"] = mats
return jsonify(data)
except Exception as e:
return jsonify({"error": str(e)}), 502
# ----------------------------- Endpoints Cache/Order ------------------------
@bp.post("/order/<order_id>/bp")
def api_set_bp(order_id: str):
payload = request.get_json(silent=True) or {}
bp_id = payload.get("blueprint_type_id")
if not bp_id:
return jsonify({"error": "missing blueprint_type_id"}), 400
ok = update_order(order_id, {"blueprint_type_id": int(bp_id), "last_updated": _now()})
return jsonify({"ok": bool(ok)})
@bp.post("/order/<order_id>/cache")
def api_cache(order_id: str):
payload = request.get_json(silent=True) or {}
ok = False
if "costs" in payload:
ok = cache_costs(order_id, payload["costs"])
if "materials" in payload:
ok = cache_materials(order_id, payload["materials"])
return jsonify({"ok": bool(ok)})
@bp.post("/order/<order_id>/refresh")
def api_refresh_one(order_id: str):
o = _order_by_id(order_id)
if not o:
return jsonify({"error": "order not found"}), 404
bp_id = o.get("blueprint_type_id")
if not bp_id:
return jsonify({"error": "blueprint_type_id missing"}), 400
# Basis-Parameter aus Order
p = {
"blueprintTypeId": str(bp_id),
"quantity": str(o.get("quantity", 1)),
"priceMode": "buy",
"additionalCosts": "0",
"baseMe": str(o.get("me", 0)),
"componentsMe": str(o.get("me", 0)),
"system": o.get("system", "Jita"),
"facilityTax": str(o.get("facility_tax", 0)),
"industryStructureType": o.get("industry_structure", "Station"),
"industryRig": o.get("industry_rig", "None"),
"reactionStructureType": o.get("reaction_structure", "Athanor"),
"reactionRig": o.get("reaction_rig", "None"),
"reactionFlag": "",
"blueprintVersion": "tq",
}
# Kosten
try:
costs = call_cookbook_build_cost([bp_id],
quantity=int(p["quantity"]),
price_mode=p["priceMode"],
additional_costs=int(p["additionalCosts"]),
base_me=int(p["baseMe"]),
components_me=int(p["componentsMe"]),
system=p["system"],
facility_tax=int(float(p["facilityTax"])),
industry_structure_type=p["industryStructureType"],
industry_rig=p["industryRig"],
reaction_structure_type=p["reactionStructureType"],
reaction_rig=p["reactionRig"],
reaction_flag=p["reactionFlag"],
blueprint_version=p["blueprintVersion"],
)
cache_costs(order_id, costs)
except Exception as e:
costs = {"error": str(e)}
# Materialien
try:
mats = call_eve_ref_materials(p)
if isinstance(mats.get("materials"), dict):
mats["materials"] = list(mats["materials"].values())
cache_materials(order_id, mats)
except Exception as e:
mats = {"error": str(e)}
return jsonify({"ok": True, "costs": costs, "materials": mats})
@bp.post("/orders/refresh_all")
def api_refresh_all():
orders = [o for o in load_orders() if o.get("status") == "open" and o.get("blueprint_type_id")]
done = 0
for o in orders:
try:
request.environ["werkzeug.server.shutdown"] # no-op
except Exception:
pass
try:
_ = api_refresh_one(o["id"])
done += 1
except Exception:
continue
return jsonify({"ok": True, "refreshed": done})
# ----------------------------- Endpoints Aggregation ------------------------
@bp.get("/cookbook/open_costs")
def api_open_costs():
"""
Aggregiert Kosten je Order (nutzt Cache; fehlt er, wird berechnet).
"""
items: List[Dict[str, Any]] = []
total = 0.0
for o in load_orders():
if o.get("status") != "open" or not o.get("blueprint_type_id"):
continue
entry: Dict[str, Any] = {
"structure": o.get("structure"),
"quantity": o.get("quantity"),
"me": o.get("me"),
"system": o.get("system"),
"industry_structure": o.get("industry_structure"),
"industry_rig": o.get("industry_rig"),
"reaction_structure": o.get("reaction_structure"),
"reaction_rig": o.get("reaction_rig"),
}
costs = o.get("cookbook")
if not costs:
try:
costs = call_cookbook_build_cost([o["blueprint_type_id"]],
quantity=int(o.get("quantity", 1)),
price_mode="buy",
additional_costs=0,
base_me=int(o.get("me", 0)),
components_me=int(o.get("me", 0)),
system=o.get("system", "Jita"),
facility_tax=int(float(o.get("facility_tax", 0))),
industry_structure_type=o.get("industry_structure", "Station"),
industry_rig=o.get("industry_rig", "None"),
reaction_structure_type=o.get("reaction_structure", "Athanor"),
reaction_rig=o.get("reaction_rig", "None"),
reaction_flag="",
blueprint_version="tq",
)
cache_costs(o["id"], costs)
except Exception as e:
entry["error"] = str(e)
items.append(entry)
continue
msg = costs.get("message") or costs
unit = msg.get("buildCostPerUnit") or msg.get("unitCost")
tot = msg.get("totalCost") or msg.get("total") or 0
entry["unit_cost"] = unit
entry["total_cost"] = tot
total += float(tot or 0)
items.append(entry)
return jsonify({"items": items, "total_cost": total, "refreshed_at": _now()})
@bp.get("/cookbook/open_materials")
def api_open_materials():
"""
Aggregiert Materialien über alle offenen Orders.
"""
agg: Dict[int, Dict[str, Any]] = {}
total_cost = 0.0
def _add(tid: int, name: str, qty: float, cost: float | None):
row = agg.setdefault(tid, {"type_id": tid, "name": name, "quantity": 0, "cost": 0})
row["quantity"] += float(qty or 0)
if cost is not None:
row["cost"] += float(cost or 0)
for o in load_orders():
if o.get("status") != "open" or not o.get("blueprint_type_id"):
continue
mats = o.get("materials")
if not mats:
try:
mats = call_eve_ref_materials({
"blueprintTypeId": str(o["blueprint_type_id"]),
"quantity": str(o.get("quantity", 1)),
"baseMe": str(o.get("me", 0)),
"industryStructureType": o.get("industry_structure", "Station"),
"facilityTax": str(o.get("facility_tax", 0)),
})
if isinstance(mats.get("materials"), dict):
mats["materials"] = list(mats["materials"].values())
cache_materials(o["id"], mats)
except Exception:
mats = {"materials": []}
for m in mats.get("materials") or []:
tid = int(m.get("type_id") or m.get("typeId") or 0)
if not tid:
continue
name = m.get("name") or f"Type {tid}"
qty = m.get("quantity") or m.get("qty") or m.get("amount") or 0
cost = m.get("cost") or m.get("cost_per_unit") or m.get("unit_cost")
_add(tid, name, qty, cost)
if cost:
total_cost += float(cost or 0)
items = sorted(agg.values(), key=lambda r: (r.get("cost") or 0), reverse=True)
return jsonify({"items": items, "total_cost": total_cost, "refreshed_at": _now()})