fix: quest tree cleaned up a bit, Key's ratings not saving fixed

This commit is contained in:
serversdwn
2026-02-25 05:53:16 +00:00
parent 9d572f5d15
commit 394c7ebde7
5 changed files with 814 additions and 255 deletions

141
app.py
View File

@@ -8,9 +8,66 @@ DB_PATH = "tarkov.db"
def get_db():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA foreign_keys = ON")
return conn
def _migrate_key_ids_and_maps():
conn = sqlite3.connect(DB_PATH)
conn.row_factory = sqlite3.Row
conn.execute("PRAGMA foreign_keys = OFF")
# Backfill missing key IDs with their api_id so ratings can join correctly.
conn.execute("""
UPDATE keys
SET id = api_id
WHERE id IS NULL AND api_id IS NOT NULL
""")
# If key_maps was created with INTEGER key_id, migrate to TEXT to match keys.id.
cols = conn.execute("PRAGMA table_info(key_maps)").fetchall()
key_id_type = None
if cols:
for col in cols:
if col["name"] == "key_id":
key_id_type = (col["type"] or "").upper()
break
if key_id_type and key_id_type != "TEXT":
conn.execute("ALTER TABLE key_maps RENAME TO key_maps_old")
conn.execute("""
CREATE TABLE key_maps (
key_id TEXT NOT NULL,
map_id INTEGER NOT NULL,
PRIMARY KEY (key_id, map_id),
FOREIGN KEY (key_id) REFERENCES keys(id),
FOREIGN KEY (map_id) REFERENCES maps(id)
)
""")
conn.execute("""
INSERT OR IGNORE INTO key_maps (key_id, map_id)
SELECT CAST(key_id AS TEXT), map_id
FROM key_maps_old
WHERE key_id IS NOT NULL
AND EXISTS (SELECT 1 FROM keys WHERE id = CAST(key_id AS TEXT))
""")
conn.execute("DROP TABLE key_maps_old")
# Remove orphaned ratings created with "None" or missing keys.
conn.execute("""
DELETE FROM key_ratings
WHERE key_id IS NULL
OR key_id = 'None'
OR key_id NOT IN (SELECT id FROM keys)
""")
conn.commit()
conn.execute("PRAGMA foreign_keys = ON")
conn.close()
_migrate_key_ids_and_maps()
@app.route("/")
def index():
conn = get_db()
@@ -198,6 +255,7 @@ def rate_all():
def quests():
conn = get_db()
only_collector = request.args.get("collector") == "1"
view = request.args.get("view", "flow") # "flow" or "list"
# All quests + done state
all_quests = conn.execute("""
@@ -243,7 +301,7 @@ def quests():
# Filter to collector-only if requested
if only_collector:
visible = collector_prereqs | {collector_row["id"]}
visible = set(collector_prereqs)
else:
visible = set(quest_by_id.keys())
@@ -270,12 +328,14 @@ def quests():
visible=visible,
collector_prereqs=collector_prereqs,
only_collector=only_collector,
view=view,
)
@app.route("/collector")
def collector():
conn = get_db()
view = request.args.get("view", "flow")
collector = conn.execute(
"SELECT id FROM quests WHERE name = 'Collector'"
).fetchone()
@@ -284,33 +344,76 @@ def collector():
conn.close()
return "Run import_quests.py first to populate quest data.", 503
# Recursive CTE: all transitive prerequisites, then keep only leaves
# (quests that are not themselves a dependency of another prereq)
prereqs = conn.execute("""
# All quests + done state
all_quests = conn.execute("""
SELECT q.id, q.name, q.trader, q.wiki_link,
COALESCE(qp.done, 0) AS done
FROM quests q
LEFT JOIN quest_progress qp ON q.id = qp.quest_id
ORDER BY q.trader, q.name
""").fetchall()
# All dependency edges
all_deps = conn.execute("SELECT quest_id, depends_on FROM quest_deps").fetchall()
# Collector prereq set (transitive)
rows = conn.execute("""
WITH RECURSIVE deps(quest_id) AS (
SELECT depends_on FROM quest_deps WHERE quest_id = ?
UNION
SELECT qd.depends_on FROM quest_deps qd
JOIN deps d ON qd.quest_id = d.quest_id
)
SELECT q.id, q.name, q.trader, q.wiki_link,
COALESCE(qp.done, 0) AS done
FROM quests q
JOIN deps d ON q.id = d.quest_id
LEFT JOIN quest_progress qp ON q.id = qp.quest_id
WHERE q.id NOT IN (
SELECT qd2.depends_on
FROM quest_deps qd2
WHERE qd2.quest_id IN (SELECT quest_id FROM deps)
AND qd2.depends_on IN (SELECT quest_id FROM deps)
)
ORDER BY q.trader, q.name
SELECT quest_id FROM deps
""", (collector["id"],)).fetchall()
collector_prereqs = {r[0] for r in rows}
conn.close()
total = len(prereqs)
done = sum(1 for q in prereqs if q["done"])
return render_template("collector.html", quests=prereqs, total=total, done=done)
# Build lookup structures
quest_by_id = {q["id"]: q for q in all_quests}
# children[parent_id] = [child_id, ...] (child depends_on parent)
children = {}
parents = {}
for dep in all_deps:
child, parent = dep["quest_id"], dep["depends_on"]
children.setdefault(parent, []).append(child)
parents.setdefault(child, []).append(parent)
# Sort each child list by quest name
for parent_id in children:
children[parent_id].sort(key=lambda i: quest_by_id[i]["name"] if i in quest_by_id else "")
visible = set(collector_prereqs)
# Root quests: in visible set and have no parents also in visible set
roots = [
qid for qid in visible
if not any(p in visible for p in parents.get(qid, []))
]
# Group roots by trader, sorted
trader_roots = {}
for qid in sorted(roots, key=lambda i: (quest_by_id[i]["trader"], quest_by_id[i]["name"])):
t = quest_by_id[qid]["trader"]
trader_roots.setdefault(t, []).append(qid)
traders = sorted(trader_roots.keys())
total = len(collector_prereqs)
done = sum(1 for qid in collector_prereqs if qid in quest_by_id and quest_by_id[qid]["done"])
return render_template(
"collector.html",
quest_by_id=quest_by_id,
children=children,
trader_roots=trader_roots,
traders=traders,
visible=visible,
collector_prereqs=collector_prereqs,
collector_id=collector["id"],
total=total,
done=done,
view=view,
)
@app.route("/collector/toggle", methods=["POST"])