Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 12 additions & 72 deletions EveProfitManager/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -311,24 +311,6 @@ def latest_cargo_after(self, session_id):
).fetchone()
return row["cargo_after"] if row else None

def log_abyss_run(self, session_id, isk_before, isk_after, time_left):
"""
Record an abyssal run from wallet amounts and the timer, the way runs were logged
before cargo runs. Nothing logs new runs this way now; kept for old wallet-based data.

Args:
session_id (int): Session to add the run to.
isk_before (float): Wallet before the run.
isk_after (float): Wallet after the run.
time_left (float): Minutes left on the abyssal timer when leaving.

Returns:
Run: The recorded run.
"""
_require_finite(isk_before, isk_after)
_require_time_left(time_left)
return self._add_run(session_id, isk_after - isk_before, MAXTIME - time_left, isk_after)

def log_incursion_site(self, session_id, profit, minutes):
"""Record one incursion site."""
_require_finite(profit)
Expand Down Expand Up @@ -395,52 +377,30 @@ def runs(self, session_id):
return [_run(r) for r in rows]

def delete_run(self, run_id):
"""
Delete a run. For abyssal runs the next run's profit is re-based on the
wallet before the deleted run, so the session still matches the wallet change.
"""
run = self.run(run_id)
if run is None:
return
"""Delete a run. Every other run keeps its profit."""
with self.db:
if run.isk_after is not None:
after = self._next_abyss_run(run)
if after:
before = run.isk_after - run.profit
self.db.execute(
"UPDATE runs SET profit = ? WHERE id = ?", (after["isk_after"] - before, after["id"])
)
self.db.execute("DELETE FROM runs WHERE id = ?", (run_id,))

def edit_abyss_run(self, run_id, isk_after, time_left):
def edit_wallet_run(self, run_id, profit, minutes):
"""
Correct an abyssal run's wallet or timer. The wallet before the run stays
as it was, and the next run's profit is re-based on the corrected wallet.
Correct an old wallet run's profit or time. Its wallet stays as it was
and no other run changes.

Args:
run_id (int): Abyssal run to change.
isk_after (float): Corrected wallet after the run.
time_left (float): Corrected minutes left on the abyssal timer.
run_id (int): Old wallet run (wallet after set, no cargo values) to change.
profit (float): Corrected profit in millions.
minutes (float): Corrected run time.

Returns:
Run: The updated run.
"""
_require_finite(isk_after)
_require_time_left(time_left)
_require_finite(profit)
_require_minutes(minutes)
run = self.run(run_id)
if run is None or run.isk_after is None:
raise ValueError("Only abyssal runs have a wallet to correct")
before = run.isk_after - run.profit
if run is None or run.isk_after is None or run.cargo_after is not None:
raise ValueError("Only old wallet runs have a profit to correct this way")
with self.db:
self.db.execute(
"UPDATE runs SET profit = ?, minutes = ?, isk_after = ? WHERE id = ?",
(isk_after - before, MAXTIME - time_left, isk_after, run_id),
)
after = self._next_abyss_run(run)
if after:
self.db.execute(
"UPDATE runs SET profit = ? WHERE id = ?", (after["isk_after"] - isk_after, after["id"])
)
self.db.execute("UPDATE runs SET profit = ?, minutes = ? WHERE id = ?", (profit, minutes, run_id))
return self.run(run_id)

def edit_cargo_run(self, run_id, cargo_before, cargo_after, time_left):
Expand Down Expand Up @@ -480,26 +440,6 @@ def edit_incursion_site(self, run_id, profit, minutes):
self.db.execute("UPDATE runs SET profit = ?, minutes = ? WHERE id = ?", (profit, minutes, run_id))
return self.run(run_id)

def _next_abyss_run(self, run):
"""The abyssal run after this one in its session, whose profit chains off its wallet."""
return self.db.execute(
"SELECT id, isk_after FROM runs WHERE session_id = ? AND id > ? "
"AND isk_after IS NOT NULL ORDER BY id LIMIT 1",
(run.session_id, run.id),
).fetchone()

def current_isk(self, session_id):
"""Wallet after the latest abyssal run, or the starting wallet if none yet."""
row = self.db.execute(
"SELECT isk_after FROM runs WHERE session_id = ? AND isk_after IS NOT NULL "
"ORDER BY id DESC LIMIT 1",
(session_id,),
).fetchone()
if row:
return row["isk_after"]
session = self.session(session_id)
return session.starting_isk if session else None

# --- stats ----------------------------------------------------------

def totals(self, kind=None):
Expand Down
8 changes: 3 additions & 5 deletions EveProfitManager/gui.py
Original file line number Diff line number Diff line change
Expand Up @@ -1041,15 +1041,13 @@ def after_log(self):
def edit_fields(self, run):
left = ("Time left (min)", f"{MAXTIME - run.minutes:g}")
if run.cargo_after is None: # an old wallet run
return [("Wallet after run (M)", f"{run.isk_after:g}"), left]
return [("Profit (M)", f"{run.profit:g}"), ("Minutes", f"{run.minutes:g}")]
return [("Cargo before (M)", f"{run.cargo_before:g}"), ("Cargo after (M)", f"{run.cargo_after:g}"), left]

def save_edit(self, run, *values):
if run.cargo_after is None:
isk_after, time_left = values
return self.tracker.edit_abyss_run(
run.id, parse_isk(isk_after, "wallet after"), parse_minutes(time_left, "time left")
)
profit, minutes = values
return self.tracker.edit_wallet_run(run.id, parse_isk(profit, "profit"), parse_minutes(minutes, "minutes"))
before, after, time_left = values
return self.tracker.edit_cargo_run(
run.id, parse_isk(before, "cargo before"), parse_isk(after, "cargo after"),
Expand Down
12 changes: 8 additions & 4 deletions EveProfitManager/test_cargo_page.py
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ def test_resuming_prefills_cargo_before_from_the_latest_cargo_run(self):

def test_resuming_a_wallet_only_session_leaves_cargo_before_empty(self):
sid = self.tracker.start_session(ABYSS, starting_isk=1000, weather="Gamma", tier=2)
self.tracker.log_abyss_run(sid, 1000, 1030, time_left=10)
self.tracker._add_run(sid, 30, 10, 1030)
page = self.open_app()
self.assertEqual(page.cargo_before.get(), "")
self.type_run("0", "40", "10")
Expand Down Expand Up @@ -134,12 +134,16 @@ def test_a_bad_cargo_edit_keeps_the_dialog_open_and_changes_nothing(self):
self.assertIn("between 0 and 20", dialog.message.cget("text"))
self.assertEqual(self.tracker.run(run.id), run)

def test_an_old_wallet_run_keeps_the_wallet_edit_dialog(self):
def test_an_old_wallet_run_edits_profit_and_minutes(self):
sid = self.tracker.start_session(ABYSS, starting_isk=1000, weather="Gamma", tier=2)
run = self.tracker.log_abyss_run(sid, 1000, 1030, time_left=10)
run = self.tracker._add_run(sid, 30, 10, 1030)
self.open_app()
dialog = self.open_edit(run)
self.assertEqual(self.texts(dialog)[:2], ["Wallet after run (M)", "Time left (min)"])
self.assertEqual(self.texts(dialog)[:2], ["Profit (M)", "Minutes"])
self.assertEqual([e.get() for e in dialog.entries], ["30", "10"])
self.gui.set_entry(dialog.entries[0], "35")
dialog.save()
self.assertEqual((self.tracker.run(run.id).profit, self.tracker.run(run.id).isk_after), (35, 1030))

def test_settings_copy_no_longer_asks_for_the_wallet_for_abyssal_runs(self):
self.open_app()
Expand Down
4 changes: 2 additions & 2 deletions EveProfitManager/test_cli_abyss.py
Original file line number Diff line number Diff line change
Expand Up @@ -80,8 +80,8 @@ def test_bad_input_is_asked_again(self):
self.assertIn("Please enter a number", out)

def test_the_cli_no_longer_logs_wallet_runs(self):
with patch.object(Tracker, "log_abyss_run", side_effect=AssertionError("wallet run")):
session, _, _ = self.drive(["1", "1", "100", "130", "5", "0"])
self.assertFalse(hasattr(Tracker, "log_abyss_run"))
session, _, _ = self.drive(["1", "1", "100", "130", "5", "0"])
self.assertEqual(session.runs, 1)


Expand Down
81 changes: 38 additions & 43 deletions EveProfitManager/test_core.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,16 +72,16 @@ def test_bad_cargo_runs_are_rejected_with_the_validator_errors(self):

def test_latest_cargo_after_skips_wallet_runs_and_other_sessions(self):
old = self.tracker.start_session(ABYSS, starting_isk=1000)
self.tracker.log_abyss_run(old, 1000, 1030, time_left=10)
self.tracker._add_run(old, 30, 10, 1030)
self.assertIsNone(self.tracker.latest_cargo_after(old)) # wallet runs only
self.tracker.log_cargo_run(old, 10, 70, time_left=10)
self.tracker.log_abyss_run(old, 1030, 1040, time_left=10) # a wallet run after it
self.tracker._add_run(old, 10, 10, 1040) # a wallet run after it
self.assertEqual(self.tracker.latest_cargo_after(old), 70)
self.assertIsNone(self.tracker.latest_cargo_after(self.tracker.start_session(ABYSS)))

def test_a_session_open_across_the_update_mixes_wallet_and_cargo_runs(self):
sid = self.tracker.start_session(ABYSS, starting_isk=1000, weather="Dark", tier=3)
self.tracker.log_abyss_run(sid, 1000, 1030, time_left=10) # logged before the update
self.tracker._add_run(sid, 30, 10, 1030) # logged before the update
self.assertIsNone(self.tracker.latest_cargo_after(sid)) # its first cargo before is empty
self.tracker.log_cargo_run(sid, 50, 90, time_left=12)
self.tracker.log_cargo_run(sid, 90, 80, time_left=5)
Expand All @@ -96,16 +96,11 @@ def test_a_session_open_across_the_update_mixes_wallet_and_cargo_runs(self):
def test_sessions_do_not_leak_profit(self):
for _ in range(2):
sid = self.tracker.start_session(ABYSS, starting_isk=100)
self.tracker.log_abyss_run(sid, 100, 130, time_left=0)
self.tracker._add_run(sid, 30, 20, 130)
self.tracker.end_session(sid)
self.assertEqual(self.tracker.session(sid).profit, 30)
self.assertEqual(self.tracker.totals().profit, 60)

def test_time_left_is_validated(self):
sid = self.tracker.start_session(ABYSS, starting_isk=100)
with self.assertRaises(ValueError):
self.tracker.log_abyss_run(sid, 100, 120, time_left=25)

def test_active_session_and_empty_end(self):
sid = self.tracker.start_session(INCURSION)
self.assertEqual(self.tracker.active_session(INCURSION).id, sid)
Expand All @@ -129,42 +124,42 @@ def test_delete_run(self):
self.tracker.delete_run(run.id)
self.assertEqual(self.tracker.session(sid).runs, 0)

def test_deleting_middle_abyss_run_keeps_wallet_gain(self):
def test_deleting_a_wallet_or_cargo_run_leaves_the_other_runs_alone(self):
sid = self.tracker.start_session(ABYSS, starting_isk=1000)
self.tracker.log_abyss_run(sid, 1000, 1030, time_left=10)
middle = self.tracker.log_abyss_run(sid, 1030, 1060, time_left=10)
self.tracker.log_abyss_run(sid, 1060, 1100, time_left=10)
first = self.tracker._add_run(sid, 30, 10, 1030)
middle = self.tracker._add_run(sid, 30, 10, 1060)
last = self.tracker._add_run(sid, 40, 10, 1100)
cargo = self.tracker.log_cargo_run(sid, 10, 25, time_left=5)
self.tracker.delete_run(middle.id)
session = self.tracker.session(sid)
self.assertEqual((session.runs, session.profit), (2, 100))
self.assertEqual(self.tracker.current_isk(sid), 1100)
self.tracker.delete_run(cargo.id)
self.assertEqual(self.tracker.runs(sid), [first, last])
self.assertEqual(self.tracker.session(sid).profit, 70)

def test_editing_abyss_run_wallet_rebases_next_run(self):
def test_editing_a_wallet_run_changes_only_its_profit_and_minutes(self):
sid = self.tracker.start_session(ABYSS, starting_isk=1000)
self.tracker.log_abyss_run(sid, 1000, 1030, time_left=10)
typo = self.tracker.log_abyss_run(sid, 1030, 1600, time_left=10) # meant 1060
last = self.tracker.log_abyss_run(sid, 1600, 1100, time_left=10)
edited = self.tracker.edit_abyss_run(typo.id, 1060, time_left=4)
self.assertEqual((edited.profit, edited.minutes, edited.isk_after), (30, 16, 1060))
self.assertEqual(self.tracker.run(last.id).profit, 40)
first = self.tracker._add_run(sid, 30, 10, 1030)
typo = self.tracker._add_run(sid, 570, 10, 1600) # meant 30
last = self.tracker._add_run(sid, -500, 10, 1100)
edited = self.tracker.edit_wallet_run(typo.id, 30, 16)
self.assertEqual((edited.profit, edited.minutes, edited.isk_after), (30, 16, 1600))
for run in (first, last):
self.assertEqual(self.tracker.run(run.id), run)
session = self.tracker.session(sid)
self.assertEqual((session.runs, session.profit, session.minutes), (3, 100, 36))
self.assertEqual(self.tracker.current_isk(sid), 1100)
self.assertEqual((session.runs, session.profit, session.minutes), (3, -440, 36))

def test_bad_abyss_edits_are_rejected(self):
def test_bad_wallet_edits_are_rejected(self):
sid = self.tracker.start_session(ABYSS, starting_isk=1000)
run = self.tracker.log_abyss_run(sid, 1000, 1030, time_left=10)
run = self.tracker._add_run(sid, 30, 10, 1030)
cargo = self.tracker.log_cargo_run(sid, 10, 25, time_left=5)
site = self.tracker.log_incursion_site(self.tracker.start_session(INCURSION), 31, 10)
for run_id, isk_after, time_left in (
(run.id, 1030, 25),
(run.id, 1030, -1),
(run.id, float("nan"), 5),
(site.id, 1030, 5),
for run_id, profit, minutes in (
(run.id, 30, -1), (run.id, float("nan"), 5), (run.id, 30, float("inf")),
(cargo.id, 30, 5), (site.id, 30, 5), (999, 30, 5),
):
with self.assertRaises(ValueError):
self.tracker.edit_abyss_run(run_id, isk_after, time_left)
self.assertEqual(self.tracker.run(run.id), run)
self.assertEqual(self.tracker.run(site.id), site)
self.tracker.edit_wallet_run(run_id, profit, minutes)
for unchanged in (run, cargo, site):
self.assertEqual(self.tracker.run(unchanged.id), unchanged)

def test_editing_a_cargo_run_recomputes_its_profit_and_minutes(self):
sid = self.tracker.start_session(ABYSS)
Expand All @@ -176,7 +171,7 @@ def test_editing_a_cargo_run_recomputes_its_profit_and_minutes(self):

def test_editing_a_cargo_run_leaves_the_runs_around_it_alone(self):
sid = self.tracker.start_session(ABYSS, starting_isk=1000)
wallet = self.tracker.log_abyss_run(sid, 1000, 1030, time_left=10)
wallet = self.tracker._add_run(sid, 30, 10, 1030)
first = self.tracker.log_cargo_run(sid, 50, 90, time_left=12)
middle = self.tracker.log_cargo_run(sid, 90, 900, time_left=6)
last = self.tracker.log_cargo_run(sid, 900, 120, time_left=5)
Expand All @@ -188,7 +183,7 @@ def test_editing_a_cargo_run_leaves_the_runs_around_it_alone(self):
def test_bad_cargo_edits_are_rejected_like_logging(self):
sid = self.tracker.start_session(ABYSS, starting_isk=1000)
run = self.tracker.log_cargo_run(sid, 100, 131, time_left=5)
wallet = self.tracker.log_abyss_run(sid, 1000, 1030, time_left=10)
wallet = self.tracker._add_run(sid, 30, 10, 1030)
site = self.tracker.log_incursion_site(self.tracker.start_session(INCURSION), 31, 10)
inf, nan = float("inf"), float("nan")
for run_id, before, after, left, error in (
Expand All @@ -214,7 +209,7 @@ def test_editing_incursion_site(self):
def test_bad_incursion_edits_are_rejected(self):
site = self.tracker.log_incursion_site(self.tracker.start_session(INCURSION), 31, 10)
sid = self.tracker.start_session(ABYSS, starting_isk=1000)
run = self.tracker.log_abyss_run(sid, 1000, 1030, time_left=10)
run = self.tracker._add_run(sid, 30, 10, 1030)
for run_id, profit, minutes in ((site.id, 31, -1), (site.id, float("inf"), 10), (run.id, 31, 10)):
with self.assertRaises(ValueError):
self.tracker.edit_incursion_site(run_id, profit, minutes)
Expand All @@ -226,8 +221,8 @@ def test_non_finite_values_are_rejected(self):
inc = self.tracker.start_session(INCURSION)
nan, inf = float("nan"), float("inf")
for call in (
lambda: self.tracker.log_abyss_run(abyss, 100, inf, time_left=5),
lambda: self.tracker.log_abyss_run(abyss, 100, 120, time_left=nan),
lambda: self.tracker.log_cargo_run(abyss, 100, inf, time_left=5),
lambda: self.tracker.log_cargo_run(abyss, 100, 120, time_left=nan),
lambda: self.tracker.log_incursion_site(inc, nan, 10),
lambda: self.tracker.log_incursion_site(inc, 31, inf),
):
Expand Down Expand Up @@ -264,7 +259,7 @@ def test_totals_by_weather(self):
(None, None, 10, 10), # before weather was recorded
):
sid = self.tracker.start_session(ABYSS, starting_isk=100, weather=weather, tier=tier)
self.tracker.log_abyss_run(sid, 100, 100 + profit, time_left)
self.tracker.log_cargo_run(sid, 100, 100 + profit, time_left)
self.tracker.start_session(ABYSS, starting_isk=100, weather="Dark", tier=1) # no runs yet
inc = self.tracker.start_session(INCURSION)
self.tracker.log_incursion_site(inc, 31, 10)
Expand All @@ -280,7 +275,7 @@ def test_winning_sessions_count_session_profit(self):
for wallets in ((100, 150, 120), (100, 80, 130), (100, 90)): # +20, +30 with a losing run, -10
sid = self.tracker.start_session(ABYSS, starting_isk=wallets[0])
for before, after in zip(wallets, wallets[1:]):
self.tracker.log_abyss_run(sid, before, after, time_left=5)
self.tracker.log_cargo_run(sid, before, after, time_left=5)
inc = self.tracker.start_session(INCURSION)
self.tracker.log_incursion_site(inc, 0, 10) # break-even isn't a win

Expand Down Expand Up @@ -366,7 +361,7 @@ def test_an_old_database_with_a_loot_table_still_opens_and_keeps_it(self):
reopened = Tracker(path)
self.assertIn("loot", self.tables(reopened.db))
self.assertEqual(reopened.session(sid).starting_isk, 1000)
reopened.log_abyss_run(sid, 1000, 1050, 10)
reopened.log_cargo_run(sid, 1000, 1050, 10)
self.assertEqual(len(reopened.runs(sid)), 1)
reopened.close()

Expand Down
6 changes: 3 additions & 3 deletions EveProfitManager/test_export.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ def rows(self, path):

def seed(self):
a = self.tracker.start_session(ABYSS, starting_isk=1000, weather="Dark", tier=3)
self.tracker.log_abyss_run(a, 1000, 1031, time_left=5)
self.tracker.log_abyss_run(a, 1031, 1050, time_left=8)
self.tracker._add_run(a, 31, 15, 1031)
self.tracker._add_run(a, 19, 12, 1050)
self.tracker.end_session(a)
i = self.tracker.start_session(INCURSION)
self.tracker.log_incursion_site(i, 93, 12)
Expand All @@ -49,7 +49,7 @@ def test_csv_has_one_row_per_run_with_its_session(self):

def test_csv_shows_the_cargo_values_of_cargo_runs_and_leaves_them_empty_for_wallet_runs(self):
sid = self.tracker.start_session(ABYSS, starting_isk=1000, weather="Dark", tier=3)
self.tracker.log_abyss_run(sid, 1000, 1030, time_left=10)
self.tracker._add_run(sid, 30, 10, 1030)
self.tracker.log_cargo_run(sid, 50, 92.5, time_left=12)
out = self.dir / "out.csv"
self.tracker.export_csv(out)
Expand Down
Loading