diff --git a/calefaction/format.py b/calefaction/format.py index 66596cc..5a6e42c 100644 --- a/calefaction/format.py +++ b/calefaction/format.py @@ -2,8 +2,16 @@ from datetime import datetime, timedelta import humanize -__all__ = ["format_isk", "format_isk_compact", "format_utctime", - "format_utctime_compact", "format_security", "get_security_class"] +__all__ = [ + "format_quantity", "format_isk", "format_isk_compact", "format_utctime", + "format_utctime_compact", "format_security", "get_security_class" +] + +def format_quantity(value): + """Nicely format an integer quantity.""" + if value < 10**6: + return "{:,}".format(value) + return humanize.intword(value, "%.2f") def format_isk(value): """Nicely format an ISK value.""" diff --git a/calefaction/modules/campaigns/database.py b/calefaction/modules/campaigns/database.py index 44cf9a3..2a66b75 100644 --- a/calefaction/modules/campaigns/database.py +++ b/calefaction/modules/campaigns/database.py @@ -187,7 +187,7 @@ class CampaignDB: The data should be a multi-layered dictionary. It maps operation names to a dict that maps character IDs to a dict that maps type IDs to - integer counts. + tuples of integer counts and float values. """ with self._conn as conn: cur = conn.execute("BEGIN TRANSACTION") @@ -195,29 +195,31 @@ class CampaignDB: cur.execute(query, (campaign,)) query = """INSERT INTO oper_item ( - oi_campaign, oi_operation, oi_character, oi_type, oi_count) - VALUES (?, ?, ?, ?, ?)""" + oi_campaign, oi_operation, oi_character, oi_type, oi_count, + oi_value) + VALUES (?, ?, ?, ?, ?, ?)""" cur.executemany(query, [ - (campaign, operation, int(char_id), int(type_id), int(count)) + (campaign, operation, int(char_id), int(type_id), int(count), + float(value)) for operation, chars in data.items() for char_id, types in chars.items() - for type_id, count in types.items()]) + for type_id, (count, value) in types.items()]) def get_associated_items(self, campaign, operation, limit=5, offset=0): """Return a list of items associated with a campaign/operation. - Items are returned as 2-tuples of (item_type, item_count), most recent - first, up to a limit. Use -1 for no limit. + Items are returned as 2-tuples of (item_type, item_count), most + valuable first, up to a limit. Use -1 for no limit. """ if not isinstance(limit, int): raise ValueError(limit) if not isinstance(offset, int): raise ValueError(offset) - query = """SELECT oi_type, SUM(oi_count) as total_count + query = """SELECT oi_type, SUM(oi_count), TOTAL(oi_value) as total_val FROM oper_item WHERE oi_campaign = ? AND oi_operation = ? - GROUP BY oi_type ORDER BY total_count DESC LIMIT {} OFFSET {}""" + GROUP BY oi_type ORDER BY total_val DESC LIMIT {} OFFSET {}""" qform = query.format(limit, offset) res = self._conn.execute(qform, (campaign, operation)).fetchall() - return [(type_id, count or 0) for type_id, count in res] + return [(type_id, count or 0, value) for type_id, count, value in res] diff --git a/calefaction/modules/campaigns/update.py b/calefaction/modules/campaigns/update.py index 27c3f6f..1c7862e 100644 --- a/calefaction/modules/campaigns/update.py +++ b/calefaction/modules/campaigns/update.py @@ -81,23 +81,22 @@ def _update_killboard_operations(cname, opnames, min_kill_id): secondary = None _save_operation(cname, opname, primary, secondary, key=max_kill_id) +def _get_prices(): + """Return a dict mapping type IDs to ISK prices.""" + pricelist = g.eve.esi().v1.markets.prices.get() + return {entry["type_id"]: entry["average_price"] + for entry in pricelist if "average_price" in entry} + def _save_collection_overview(cname, opnames, data): """Save collection overview data to the database.""" operations = config["campaigns"][cname]["operations"] - if any(operations[opname].get("isk", True) for opname in opnames): - pricelist = g.eve.esi().v1.markets.prices.get() - prices = {entry["type_id"]: entry["average_price"] - for entry in pricelist if "average_price" in entry} - else: - prices = {} - for opname in opnames: - primary = sum(sum(d.values()) for d in data[opname].values()) + primary = sum(count for d in data[opname].values() + for (count, _) in d.values()) show_isk = operations[opname].get("isk", True) if show_isk: - secondary = sum(prices.get(typeid, 0.0) * count - for d in data[opname].values() - for typeid, count in d.items()) + secondary = sum(value for d in data[opname].values() + for (_, value) in d.values()) else: secondary = None _save_operation(cname, opname, primary, secondary) @@ -110,6 +109,7 @@ def _update_collection_operations(cname, opnames): qualif = operations[opname]["qualifiers"] filters.append((_build_filter(qualif, "asset"), opname)) + prices = _get_prices() data = {opname: {} for opname in opnames} for char_id, token in g.auth.get_valid_characters(): @@ -131,11 +131,13 @@ def _update_collection_operations(cname, opnames): if filt(asset): typeid = asset["type_id"] count = 1 if asset["is_singleton"] else asset["quantity"] + value = prices.get(typeid, 0.0) char = data[opname][char_id] if typeid in char: - char[typeid] += count + char[typeid][0] += count + char[typeid][1] += count * value else: - char[typeid] = count + char[typeid] = [count, count * value] g.campaign_db.update_items(cname, data) _save_collection_overview(cname, opnames, data) diff --git a/config/modules/campaigns.yml.sample b/config/modules/campaigns.yml.sample index 01ea6c8..93b20e5 100644 --- a/config/modules/campaigns.yml.sample +++ b/config/modules/campaigns.yml.sample @@ -47,6 +47,7 @@ campaigns: isk: false # Report as "10 units" / "1 unit" of Tritanium unit: unit|units + # Python function to filter items: qualifiers: |- type = g.eve.universe.type(asset["type_id"]) return type.name == "Tritanium" diff --git a/data/schema_campaigns.sql b/data/schema_campaigns.sql index 56d8fc7..762aac2 100644 --- a/data/schema_campaigns.sql +++ b/data/schema_campaigns.sql @@ -60,6 +60,7 @@ CREATE TABLE oper_item ( oi_character INTEGER, oi_type INTEGER, oi_count INTEGER, + oi_value REAL, UNIQUE (oi_campaign, oi_operation, oi_character, oi_type) ); diff --git a/static/main.css b/static/main.css index 4a5c093..ddedb70 100644 --- a/static/main.css +++ b/static/main.css @@ -519,13 +519,16 @@ h2 .disabled-info { border-bottom: none; } -.operation .itemboard .num { +.operation .itemboard td:last-child { padding-left: 0.5em; text-align: right; +} + +.operation .itemboard .count { font-weight: bold; } -.operation .itemboard .num::before { +.operation .itemboard .count::before { content: "×"; font-weight: normal; color: #AAA; diff --git a/templates/campaigns/renderers.mako b/templates/campaigns/renderers.mako index 9d6374a..b4ad832 100644 --- a/templates/campaigns/renderers.mako +++ b/templates/campaigns/renderers.mako @@ -1,7 +1,7 @@ <%! from calefaction.format import ( - format_isk_compact, format_utctime_compact, format_security, - get_security_class) + format_quantity, format_isk_compact, format_utctime_compact, + format_security, get_security_class) %> <%def name="_killboard_kill(kill)"> <% @@ -56,13 +56,20 @@ <%def name="_itemboard_item(item)"> <% - type_id, count = item + type_id, count, value = item type = g.eve.universe.type(type_id) %> - - ${type.name | h} - ${count | h} + + + + + ${type.name | h} + + + ${format_quantity(count) | h}
+ ${format_isk_compact(value) | h} + <%def name="_killboard_recent(summary)">