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//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//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//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()})