653 lines
22 KiB
Python
653 lines
22 KiB
Python
import requests
|
|
import sqlite3
|
|
import sys
|
|
|
|
DB_PATH = "tarkov.db"
|
|
API_URL = "https://api.tarkov.dev/graphql"
|
|
|
|
# Slot nameId patterns that identify what type of mod goes in a slot
|
|
SLOT_TYPE_MAP = {
|
|
"mod_muzzle": "suppressor",
|
|
"mod_scope": "scope",
|
|
"mod_tactical": "flashlight",
|
|
"mod_tactical_001": "flashlight",
|
|
"mod_tactical_002": "flashlight",
|
|
"mod_tactical_003": "flashlight",
|
|
"mod_stock": "stock",
|
|
"mod_stock_000": "stock",
|
|
"mod_stock_001": "stock",
|
|
"mod_pistol_grip": "grip",
|
|
"mod_grip": "foregrip",
|
|
"mod_foregrip": "foregrip",
|
|
"mod_magazine": "magazine",
|
|
"mod_barrel": "barrel",
|
|
"mod_gas_block": "gas_block",
|
|
"mod_handguard": "handguard",
|
|
"mod_launcher": "launcher",
|
|
"mod_bipod": "bipod",
|
|
}
|
|
|
|
# Fetch weapons separately due to large slots/allowedItems payload
|
|
GRAPHQL_QUERY_WEAPONS = """
|
|
{
|
|
weapons: items(types: [gun]) {
|
|
id name shortName weight gridImageLink wikiLink
|
|
properties {
|
|
... on ItemPropertiesWeapon {
|
|
caliber
|
|
fireRate
|
|
ergonomics
|
|
recoilVertical
|
|
defaultWeight
|
|
slots {
|
|
id
|
|
name
|
|
nameId
|
|
required
|
|
filters {
|
|
allowedItems { id }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
"""
|
|
|
|
GRAPHQL_QUERY_GEAR = """
|
|
{
|
|
armor: items(types: [armor]) {
|
|
id name shortName weight gridImageLink wikiLink
|
|
properties {
|
|
... on ItemPropertiesArmor {
|
|
class
|
|
durability
|
|
material { name }
|
|
zones
|
|
armorSlots {
|
|
__typename
|
|
... on ItemArmorSlotOpen {
|
|
nameId
|
|
zones
|
|
allowedPlates { id }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
helmets: items(types: [helmet]) {
|
|
id name shortName weight gridImageLink wikiLink
|
|
properties {
|
|
... on ItemPropertiesHelmet {
|
|
class
|
|
durability
|
|
material { name }
|
|
headZones
|
|
deafening
|
|
}
|
|
}
|
|
}
|
|
|
|
wearables: items(types: [wearable]) {
|
|
id name shortName weight gridImageLink wikiLink
|
|
properties {
|
|
... on ItemPropertiesHelmet {
|
|
class
|
|
durability
|
|
material { name }
|
|
headZones
|
|
deafening
|
|
}
|
|
}
|
|
}
|
|
|
|
backpacks: items(types: [backpack]) {
|
|
id name shortName weight gridImageLink wikiLink
|
|
properties {
|
|
... on ItemPropertiesBackpack {
|
|
capacity
|
|
grids { width height }
|
|
}
|
|
}
|
|
}
|
|
|
|
rigs: items(types: [rig]) {
|
|
id name shortName weight gridImageLink wikiLink
|
|
properties {
|
|
... on ItemPropertiesChestRig {
|
|
capacity
|
|
class
|
|
durability
|
|
zones
|
|
armorSlots {
|
|
__typename
|
|
... on ItemArmorSlotOpen {
|
|
nameId
|
|
zones
|
|
allowedPlates { id }
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
plates: items(types: [armorPlate]) {
|
|
id name shortName weight gridImageLink wikiLink
|
|
properties {
|
|
... on ItemPropertiesArmorAttachment {
|
|
class
|
|
durability
|
|
material { name }
|
|
zones
|
|
}
|
|
}
|
|
}
|
|
|
|
suppressors: items(types: [suppressor]) {
|
|
id name shortName weight gridImageLink wikiLink
|
|
}
|
|
|
|
mods: items(types: [mods]) {
|
|
id name shortName weight gridImageLink wikiLink
|
|
}
|
|
}
|
|
"""
|
|
|
|
|
|
def gql(query, label="query"):
|
|
response = requests.post(
|
|
API_URL,
|
|
json={"query": query},
|
|
timeout=90
|
|
)
|
|
response.raise_for_status()
|
|
data = response.json()
|
|
if "errors" in data:
|
|
raise RuntimeError(f"{label} errors: {data['errors']}")
|
|
return data["data"]
|
|
|
|
|
|
def upsert_item(cursor, item_id, name, short_name, category, weight,
|
|
grid_image_url, wiki_url,
|
|
caliber=None, fire_rate=None, ergonomics=None, recoil_vertical=None,
|
|
default_weight=None, armor_class=None, durability=None, material=None,
|
|
zones=None, head_zones=None, deafening=None, capacity=None, mod_type=None):
|
|
cursor.execute("SELECT id FROM gear_items WHERE api_id = ?", (item_id,))
|
|
existing = cursor.fetchone()
|
|
if existing:
|
|
cursor.execute("""
|
|
UPDATE gear_items
|
|
SET name=?, short_name=?, category=?, weight_kg=?, grid_image_url=?, wiki_url=?,
|
|
caliber=?, fire_rate=?, ergonomics=?, recoil_vertical=?, default_weight=?,
|
|
armor_class=?, durability=?, material=?, zones=?, head_zones=?, deafening=?,
|
|
capacity=?, mod_type=?, updated_at=CURRENT_TIMESTAMP
|
|
WHERE api_id=?
|
|
""", (name, short_name, category, weight, grid_image_url, wiki_url,
|
|
caliber, fire_rate, ergonomics, recoil_vertical, default_weight,
|
|
armor_class, durability, material, zones, head_zones, deafening,
|
|
capacity, mod_type, item_id))
|
|
return "updated"
|
|
else:
|
|
cursor.execute("""
|
|
INSERT INTO gear_items
|
|
(id, api_id, name, short_name, category, weight_kg, grid_image_url, wiki_url,
|
|
caliber, fire_rate, ergonomics, recoil_vertical, default_weight,
|
|
armor_class, durability, material, zones, head_zones, deafening,
|
|
capacity, mod_type)
|
|
VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)
|
|
""", (item_id, item_id, name, short_name, category, weight, grid_image_url, wiki_url,
|
|
caliber, fire_rate, ergonomics, recoil_vertical, default_weight,
|
|
armor_class, durability, material, zones, head_zones, deafening,
|
|
capacity, mod_type))
|
|
return "inserted"
|
|
|
|
|
|
def import_weapons(conn, weapons):
|
|
cursor = conn.cursor()
|
|
counts = {"inserted": 0, "updated": 0, "skipped": 0}
|
|
# Collect slot data for phase 2
|
|
slot_data = {} # gun_id -> list of slot dicts
|
|
|
|
for w in weapons:
|
|
item_id = w.get("id")
|
|
name = w.get("name")
|
|
if not item_id or not name:
|
|
counts["skipped"] += 1
|
|
continue
|
|
|
|
props = w.get("properties") or {}
|
|
result = upsert_item(
|
|
cursor, item_id, name, w.get("shortName"), "gun", w.get("weight"),
|
|
w.get("gridImageLink"), w.get("wikiLink"),
|
|
caliber=props.get("caliber"),
|
|
fire_rate=props.get("fireRate"),
|
|
ergonomics=props.get("ergonomics"),
|
|
recoil_vertical=props.get("recoilVertical"),
|
|
default_weight=props.get("defaultWeight"),
|
|
)
|
|
counts[result] += 1
|
|
|
|
slots = props.get("slots") or []
|
|
if slots:
|
|
slot_data[item_id] = slots
|
|
|
|
conn.commit()
|
|
return counts, slot_data
|
|
|
|
|
|
def import_armor(conn, items):
|
|
cursor = conn.cursor()
|
|
counts = {"inserted": 0, "updated": 0, "skipped": 0}
|
|
slot_data = {} # carrier_id -> armorSlots list
|
|
for item in items:
|
|
item_id = item.get("id")
|
|
name = item.get("name")
|
|
if not item_id or not name:
|
|
counts["skipped"] += 1
|
|
continue
|
|
props = item.get("properties") or {}
|
|
material = (props.get("material") or {}).get("name")
|
|
zones = ",".join(props.get("zones") or []) or None
|
|
result = upsert_item(
|
|
cursor, item_id, name, item.get("shortName"), "armor", item.get("weight"),
|
|
item.get("gridImageLink"), item.get("wikiLink"),
|
|
armor_class=props.get("class"),
|
|
durability=props.get("durability"),
|
|
material=material,
|
|
zones=zones,
|
|
)
|
|
counts[result] += 1
|
|
armor_slots = props.get("armorSlots") or []
|
|
if armor_slots:
|
|
slot_data[item_id] = armor_slots
|
|
conn.commit()
|
|
return counts, slot_data
|
|
|
|
|
|
def import_helmets(conn, items):
|
|
cursor = conn.cursor()
|
|
counts = {"inserted": 0, "updated": 0, "skipped": 0}
|
|
for item in items:
|
|
item_id = item.get("id")
|
|
name = item.get("name")
|
|
if not item_id or not name:
|
|
counts["skipped"] += 1
|
|
continue
|
|
props = item.get("properties") or {}
|
|
material = (props.get("material") or {}).get("name")
|
|
head_zones_list = props.get("headZones") or []
|
|
head_zones = ",".join(head_zones_list) or None
|
|
# True helmets cover the top of the head; face masks etc. go to 'headwear'
|
|
category = "helmet" if any("Top of the head" in z for z in head_zones_list) else "headwear"
|
|
result = upsert_item(
|
|
cursor, item_id, name, item.get("shortName"), category, item.get("weight"),
|
|
item.get("gridImageLink"), item.get("wikiLink"),
|
|
armor_class=props.get("class"),
|
|
durability=props.get("durability"),
|
|
material=material,
|
|
head_zones=head_zones,
|
|
deafening=props.get("deafening"),
|
|
)
|
|
counts[result] += 1
|
|
conn.commit()
|
|
return counts
|
|
|
|
|
|
def import_backpacks(conn, items):
|
|
cursor = conn.cursor()
|
|
counts = {"inserted": 0, "updated": 0, "skipped": 0}
|
|
for item in items:
|
|
item_id = item.get("id")
|
|
name = item.get("name")
|
|
if not item_id or not name:
|
|
counts["skipped"] += 1
|
|
continue
|
|
props = item.get("properties") or {}
|
|
capacity = props.get("capacity")
|
|
if capacity is None:
|
|
capacity = sum(
|
|
g.get("width", 0) * g.get("height", 0)
|
|
for g in (props.get("grids") or [])
|
|
) or None
|
|
result = upsert_item(
|
|
cursor, item_id, name, item.get("shortName"), "backpack", item.get("weight"),
|
|
item.get("gridImageLink"), item.get("wikiLink"),
|
|
capacity=capacity,
|
|
)
|
|
counts[result] += 1
|
|
conn.commit()
|
|
return counts
|
|
|
|
|
|
def import_rigs(conn, items):
|
|
cursor = conn.cursor()
|
|
counts = {"inserted": 0, "updated": 0, "skipped": 0}
|
|
slot_data = {} # carrier_id -> armorSlots list
|
|
for item in items:
|
|
item_id = item.get("id")
|
|
name = item.get("name")
|
|
if not item_id or not name:
|
|
counts["skipped"] += 1
|
|
continue
|
|
props = item.get("properties") or {}
|
|
zones = ",".join(props.get("zones") or []) or None
|
|
result = upsert_item(
|
|
cursor, item_id, name, item.get("shortName"), "rig", item.get("weight"),
|
|
item.get("gridImageLink"), item.get("wikiLink"),
|
|
armor_class=props.get("class"),
|
|
durability=props.get("durability"),
|
|
capacity=props.get("capacity"),
|
|
zones=zones,
|
|
)
|
|
counts[result] += 1
|
|
armor_slots = props.get("armorSlots") or []
|
|
if armor_slots:
|
|
slot_data[item_id] = armor_slots
|
|
conn.commit()
|
|
return counts, slot_data
|
|
|
|
|
|
def import_plates(conn, items):
|
|
cursor = conn.cursor()
|
|
counts = {"inserted": 0, "updated": 0, "skipped": 0}
|
|
for item in items:
|
|
item_id = item.get("id")
|
|
name = item.get("name")
|
|
if not item_id or not name:
|
|
counts["skipped"] += 1
|
|
continue
|
|
props = item.get("properties") or {}
|
|
material = (props.get("material") or {}).get("name")
|
|
zones = ",".join(props.get("zones") or []) or None
|
|
result = upsert_item(
|
|
cursor, item_id, name, item.get("shortName"), "plate", item.get("weight"),
|
|
item.get("gridImageLink"), item.get("wikiLink"),
|
|
armor_class=props.get("class"),
|
|
durability=props.get("durability"),
|
|
material=material,
|
|
zones=zones,
|
|
)
|
|
counts[result] += 1
|
|
conn.commit()
|
|
return counts
|
|
|
|
|
|
def import_armor_open_slots(conn, carrier_id, armor_slots):
|
|
"""
|
|
Insert open plate slots and their compatible plates for a carrier item.
|
|
armor_slots: list of armorSlots from the API (only ItemArmorSlotOpen are relevant).
|
|
"""
|
|
cursor = conn.cursor()
|
|
slot_count = 0
|
|
plate_count = 0
|
|
# Build set of known item IDs for fast lookup
|
|
known_ids = {row[0] for row in cursor.execute("SELECT id FROM gear_items").fetchall()}
|
|
|
|
for slot in armor_slots:
|
|
if slot.get("__typename") != "ItemArmorSlotOpen":
|
|
continue
|
|
slot_nameid = slot.get("nameId")
|
|
if not slot_nameid:
|
|
continue
|
|
zones = ",".join(slot.get("zones") or []) or None
|
|
cursor.execute("""
|
|
INSERT OR REPLACE INTO armor_open_slots (carrier_id, slot_nameid, zones)
|
|
VALUES (?, ?, ?)
|
|
""", (carrier_id, slot_nameid, zones))
|
|
slot_count += 1
|
|
|
|
for plate in (slot.get("allowedPlates") or []):
|
|
plate_id = plate.get("id")
|
|
if not plate_id or plate_id not in known_ids:
|
|
continue
|
|
cursor.execute("""
|
|
INSERT OR IGNORE INTO armor_slot_plates (carrier_id, slot_nameid, plate_id)
|
|
VALUES (?, ?, ?)
|
|
""", (carrier_id, slot_nameid, plate_id))
|
|
plate_count += 1
|
|
|
|
return slot_count, plate_count
|
|
|
|
|
|
def import_suppressors(conn, items):
|
|
cursor = conn.cursor()
|
|
counts = {"inserted": 0, "updated": 0, "skipped": 0}
|
|
for item in items:
|
|
item_id = item.get("id")
|
|
name = item.get("name")
|
|
if not item_id or not name:
|
|
counts["skipped"] += 1
|
|
continue
|
|
result = upsert_item(
|
|
cursor, item_id, name, item.get("shortName"), "mod", item.get("weight"),
|
|
item.get("gridImageLink"), item.get("wikiLink"),
|
|
mod_type="suppressor",
|
|
)
|
|
counts[result] += 1
|
|
conn.commit()
|
|
return counts
|
|
|
|
|
|
def import_mods(conn, items):
|
|
"""Import generic weapon mods (scopes, grips, stocks, barrels, etc.)"""
|
|
cursor = conn.cursor()
|
|
counts = {"inserted": 0, "updated": 0, "skipped": 0}
|
|
for item in items:
|
|
item_id = item.get("id")
|
|
name = item.get("name")
|
|
if not item_id or not name:
|
|
counts["skipped"] += 1
|
|
continue
|
|
# Don't overwrite suppressors already imported with their mod_type
|
|
cursor.execute("SELECT id FROM gear_items WHERE api_id = ?", (item_id,))
|
|
existing = cursor.fetchone()
|
|
if existing:
|
|
# Only update weight/image if item already exists — don't overwrite mod_type
|
|
cursor.execute("""
|
|
UPDATE gear_items
|
|
SET name=?, short_name=?, weight_kg=?, grid_image_url=?, wiki_url=?,
|
|
updated_at=CURRENT_TIMESTAMP
|
|
WHERE api_id=?
|
|
""", (name, item.get("shortName"), item.get("weight"),
|
|
item.get("gridImageLink"), item.get("wikiLink"), item_id))
|
|
counts["updated"] += 1
|
|
else:
|
|
result = upsert_item(
|
|
cursor, item_id, name, item.get("shortName"), "mod", item.get("weight"),
|
|
item.get("gridImageLink"), item.get("wikiLink"),
|
|
)
|
|
counts[result] += 1
|
|
conn.commit()
|
|
return counts
|
|
|
|
|
|
def import_slots(conn, slot_data):
|
|
"""
|
|
Phase 2: insert gun_slots and gun_slot_items rows.
|
|
slot_data: { gun_id: [slot_dict, ...] }
|
|
Only inserts gun_slot_items rows where item_id exists in gear_items.
|
|
"""
|
|
cursor = conn.cursor()
|
|
slots_inserted = 0
|
|
slot_items_inserted = 0
|
|
slot_items_skipped = 0
|
|
|
|
# Build set of known item IDs for fast lookup
|
|
known_ids = {row[0] for row in cursor.execute("SELECT id FROM gear_items").fetchall()}
|
|
|
|
for gun_id, slots in slot_data.items():
|
|
for slot in slots:
|
|
slot_id = slot.get("id")
|
|
slot_name = slot.get("name") or ""
|
|
slot_nameid = slot.get("nameId") or ""
|
|
required = 1 if slot.get("required") else 0
|
|
|
|
if not slot_id:
|
|
continue
|
|
|
|
cursor.execute("""
|
|
INSERT OR REPLACE INTO gun_slots (gun_id, slot_id, slot_name, slot_nameid, required)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
""", (gun_id, slot_id, slot_name, slot_nameid, required))
|
|
slots_inserted += 1
|
|
|
|
filters = slot.get("filters") or {}
|
|
allowed_items = filters.get("allowedItems") or []
|
|
for allowed in allowed_items:
|
|
item_id = allowed.get("id")
|
|
if not item_id or item_id not in known_ids:
|
|
slot_items_skipped += 1
|
|
continue
|
|
cursor.execute("""
|
|
INSERT OR IGNORE INTO gun_slot_items (gun_id, slot_id, item_id)
|
|
VALUES (?, ?, ?)
|
|
""", (gun_id, slot_id, item_id))
|
|
slot_items_inserted += 1
|
|
|
|
conn.commit()
|
|
return slots_inserted, slot_items_inserted, slot_items_skipped
|
|
|
|
|
|
def main():
|
|
print("=== OnlyScavs Gear Import ===")
|
|
|
|
# Phase 1a: fetch weapons (separate query — large payload)
|
|
print("\nFetching weapons from tarkov.dev...")
|
|
try:
|
|
weapon_data = gql(GRAPHQL_QUERY_WEAPONS, "weapons")
|
|
except Exception as e:
|
|
print(f"ERROR fetching weapons: {e}")
|
|
sys.exit(1)
|
|
weapons = weapon_data.get("weapons", [])
|
|
print(f" Fetched {len(weapons)} weapons")
|
|
|
|
# Phase 1b: fetch other gear
|
|
print("Fetching armor, helmets, backpacks, rigs, suppressors...")
|
|
try:
|
|
gear_data = gql(GRAPHQL_QUERY_GEAR, "gear")
|
|
except Exception as e:
|
|
print(f"ERROR fetching gear: {e}")
|
|
sys.exit(1)
|
|
|
|
armor = gear_data.get("armor", [])
|
|
helmets = gear_data.get("helmets", [])
|
|
backpacks = gear_data.get("backpacks", [])
|
|
rigs = gear_data.get("rigs", [])
|
|
suppressors = gear_data.get("suppressors", [])
|
|
mods = gear_data.get("mods", [])
|
|
plates = gear_data.get("plates", [])
|
|
|
|
# Wearables: only keep those whose properties resolved to ItemPropertiesHelmet
|
|
# (i.e. they have 'class' or 'headZones' set — not decorative items)
|
|
wearables_raw = gear_data.get("wearables", [])
|
|
# Known helmet IDs from the main helmet query — skip duplicates
|
|
helmet_ids = {h["id"] for h in helmets}
|
|
wearable_helmets = [
|
|
w for w in wearables_raw
|
|
if w["id"] not in helmet_ids
|
|
and isinstance(w.get("properties"), dict)
|
|
and (w["properties"].get("class") or w["properties"].get("headZones"))
|
|
]
|
|
print(f" armor={len(armor)}, helmets={len(helmets)}, wearable_helmets={len(wearable_helmets)}, "
|
|
f"backpacks={len(backpacks)}, rigs={len(rigs)}, suppressors={len(suppressors)}, "
|
|
f"mods={len(mods)}, plates={len(plates)})")
|
|
|
|
# Phase 2: insert into DB
|
|
conn = sqlite3.connect(DB_PATH)
|
|
conn.execute("PRAGMA foreign_keys = ON")
|
|
|
|
try:
|
|
print("\nImporting weapons...")
|
|
wcounts, slot_data = import_weapons(conn, weapons)
|
|
print(f" guns: +{wcounts['inserted']} inserted, ~{wcounts['updated']} updated, -{wcounts['skipped']} skipped")
|
|
|
|
print("Importing plates (armorPlate items)...")
|
|
c = import_plates(conn, plates)
|
|
print(f" plates: +{c['inserted']} inserted, ~{c['updated']} updated, -{c['skipped']} skipped")
|
|
|
|
print("Importing armor...")
|
|
c, armor_slot_data = import_armor(conn, armor)
|
|
print(f" armor: +{c['inserted']} inserted, ~{c['updated']} updated, -{c['skipped']} skipped")
|
|
|
|
print("Importing helmets...")
|
|
c = import_helmets(conn, helmets)
|
|
print(f" helmets: +{c['inserted']} inserted, ~{c['updated']} updated, -{c['skipped']} skipped")
|
|
|
|
print("Importing wearable helmets (missing from helmet type)...")
|
|
c = import_helmets(conn, wearable_helmets)
|
|
print(f" wearable helmets: +{c['inserted']} inserted, ~{c['updated']} updated, -{c['skipped']} skipped")
|
|
|
|
print("Importing backpacks...")
|
|
c = import_backpacks(conn, backpacks)
|
|
print(f" backpacks: +{c['inserted']} inserted, ~{c['updated']} updated, -{c['skipped']} skipped")
|
|
|
|
print("Importing rigs...")
|
|
c, rig_slot_data = import_rigs(conn, rigs)
|
|
print(f" rigs: +{c['inserted']} inserted, ~{c['updated']} updated, -{c['skipped']} skipped")
|
|
|
|
print("Importing suppressors...")
|
|
c = import_suppressors(conn, suppressors)
|
|
print(f" suppressors: +{c['inserted']} inserted, ~{c['updated']} updated, -{c['skipped']} skipped")
|
|
|
|
print("Importing mods (scopes, grips, stocks, barrels, etc.)...")
|
|
c = import_mods(conn, mods)
|
|
print(f" mods: +{c['inserted']} inserted, ~{c['updated']} updated, -{c['skipped']} skipped")
|
|
|
|
# Phase 3: gun slots (needs all items in DB first)
|
|
print("Importing gun slots and compatibility data...")
|
|
slots_ins, slot_items_ins, slot_items_skip = import_slots(conn, slot_data)
|
|
print(f" gun_slots: {slots_ins} rows")
|
|
print(f" gun_slot_items: {slot_items_ins} rows inserted, {slot_items_skip} skipped (item not in DB)")
|
|
|
|
# Phase 3b: armor open slots (needs plates in DB first)
|
|
print("Importing armor open plate slots...")
|
|
total_open_slots = 0
|
|
total_plate_links = 0
|
|
all_carrier_slots = {**armor_slot_data, **rig_slot_data}
|
|
for carrier_id, armor_slots in all_carrier_slots.items():
|
|
sc, pc = import_armor_open_slots(conn, carrier_id, armor_slots)
|
|
total_open_slots += sc
|
|
total_plate_links += pc
|
|
conn.commit()
|
|
print(f" armor_open_slots: {total_open_slots} rows")
|
|
print(f" armor_slot_plates: {total_plate_links} rows")
|
|
|
|
# Phase 4: classify mod_type for mods based on which slots they appear in
|
|
print("Classifying mod types from slot data...")
|
|
cursor = conn.cursor()
|
|
updated_mod_types = 0
|
|
for slot_nameid, mod_type in SLOT_TYPE_MAP.items():
|
|
result = cursor.execute("""
|
|
UPDATE gear_items
|
|
SET mod_type = ?
|
|
WHERE id IN (
|
|
SELECT gsi.item_id
|
|
FROM gun_slot_items gsi
|
|
JOIN gun_slots gs ON gs.gun_id = gsi.gun_id AND gs.slot_id = gsi.slot_id
|
|
WHERE gs.slot_nameid = ?
|
|
)
|
|
AND category = 'mod'
|
|
AND (mod_type IS NULL OR mod_type != 'suppressor')
|
|
""", (mod_type, slot_nameid))
|
|
updated_mod_types += result.rowcount
|
|
conn.commit()
|
|
print(f" mod_type set on {updated_mod_types} mod items")
|
|
|
|
except Exception as e:
|
|
conn.rollback()
|
|
print(f"ERROR: Database operation failed — {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
sys.exit(1)
|
|
finally:
|
|
conn.close()
|
|
|
|
print("\n=== Import complete ===")
|
|
print("Run: SELECT category, COUNT(*) FROM gear_items GROUP BY category;")
|
|
print(" to verify row counts.")
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|