fix: seed import update mode fills gaps only — MSFS is primary

Changes  behavior: ALL fields now use ONLY_IF_EMPTY
policy for existing assets. Previously, AUTO fields (name, make, model,
company, place, building_name, floor, etc.) always overwrote existing
data, clobbering MSFS-synced and manually-cleaned records.

- db_writer.py: update() treats every field as 'skip if existing
  value non-null' — same as old PRESERVE behavior but universal
- sync.py: UPDATE path now fetches current values and only sets
  fields that are currently NULL/empty
- New assets (not found by machine_id) still insert with full seed data
- sync.py docstring and stats output updated to reflect new policy

Closes #13 — seed import no longer overwrites MSFS data
This commit is contained in:
2026-06-02 22:50:53 -04:00
parent 9888601863
commit 186a9d03f3
2 changed files with 31 additions and 25 deletions
+14 -18
View File
@@ -350,10 +350,12 @@ class DatabaseWriter:
""" """
Update mode: Upsert by machine_id. Update mode: Upsert by machine_id.
PRESERVE fields (lat, lng, photo_path, install_date, deployed, pulled_date): MSFS is primary data source — seed import fills gaps only.
ALL fields (except machine_id PK) are treated as ONLY_IF_EMPTY:
Skip update if existing value is non-null. Skip update if existing value is non-null.
AUTO fields: Always overwrite.
machine_id: Never changes (used as match key). New assets (not found by machine_id) are inserted with full data.
Returns dict with counts. Returns dict with counts.
""" """
@@ -408,26 +410,20 @@ class DatabaseWriter:
stats["errors"] += 1 stats["errors"] += 1
print(f" ⚠️ Error inserting machine {mid}: {e}") print(f" ⚠️ Error inserting machine {mid}: {e}")
else: else:
# UPDATE existing record with preserve/auto rules # UPDATE existing record — MSFS is primary, seed fills gaps
existing_dict = dict(existing) existing_dict = dict(existing)
updates = {} updates = {}
preserved_count = 0 skipped_count = 0
for col in update_cols: for col in update_cols:
if col == "machine_id":
continue # PK — never update
new_val = row.get(col) new_val = row.get(col)
old_val = existing_dict.get(col) old_val = existing_dict.get(col)
# Skip if existing value is non-null/empty (MSFS or user data wins)
if col in PRESERVE_FIELDS:
# PRESERVE: skip if existing value is non-null
if old_val is not None and old_val != "": if old_val is not None and old_val != "":
preserved_count += 1 skipped_count += 1
continue # Don't update continue
# If old is NULL, we can set it
updates[col] = new_val
elif col == "machine_id":
continue # PK — never update
else:
# AUTO: always overwrite
updates[col] = new_val updates[col] = new_val
if updates: if updates:
@@ -448,7 +444,7 @@ class DatabaseWriter:
set_values, set_values,
) )
stats["updated"] += 1 stats["updated"] += 1
stats["preserved"] += preserved_count stats["preserved"] += skipped_count
except Exception as e: except Exception as e:
stats["errors"] += 1 stats["errors"] += 1
print(f" ⚠️ Error updating machine {mid}: {e}") print(f" ⚠️ Error updating machine {mid}: {e}")
@@ -497,7 +493,7 @@ def write_update(machines, db_path):
final_count = writer.get_machine_count() final_count = writer.get_machine_count()
print(f" ✅ Inserted: {stats['inserted']}") print(f" ✅ Inserted: {stats['inserted']}")
print(f" 🔄 Updated: {stats['updated']}") print(f" 🔄 Updated: {stats['updated']}")
print(f" 🛡️ Preserved fields: {stats['preserved']}") print(f" 🛡️ Existing data preserved: {stats['preserved']} fields")
print(f" ⏭️ Skipped (no changes): {stats['skipped']}") print(f" ⏭️ Skipped (no changes): {stats['skipped']}")
print(f" 📊 Total in DB: {final_count}") print(f" 📊 Total in DB: {final_count}")
if stats["errors"]: if stats["errors"]:
+15 -5
View File
@@ -138,7 +138,7 @@ def push(source_db: str, target_db: str, dry_run: bool = False) -> dict:
location_id = 0 location_id = 0
existing = tgt.execute( existing = tgt.execute(
"SELECT id FROM assets WHERE machine_id = ?", (mid,) "SELECT * FROM assets WHERE machine_id = ?", (mid,)
).fetchone() ).fetchone()
# Category mapping # Category mapping
@@ -214,12 +214,22 @@ def push(source_db: str, target_db: str, dry_run: bool = False) -> dict:
} }
if existing: if existing:
# MSFS is primary — seed fills gaps only
# Build filtered update dict: only set fields that are currently empty
existing_dict = dict(existing)
filtered_data = {}
for col, val in asset_data.items():
current = existing_dict.get(col)
if current is None or str(current).strip() == "":
filtered_data[col] = val
report["assets_updated"] += 1 report["assets_updated"] += 1
if len(report["first_10"]) < 10: if len(report["first_10"]) < 10:
report["first_10"].append(f"UPDATE {mid}: {name} ({cat})") skipped_fields = len(asset_data) - len(filtered_data)
if not dry_run: report["first_10"].append(f"UPDATE {mid}: {name} ({cat}) — {len(filtered_data)} fields, {skipped_fields} skipped (MSFS/cleanup data preserved)")
sets = ", ".join(f"{col} = ?" for col in asset_data) if not dry_run and filtered_data:
vals = list(asset_data.values()) + [mid] sets = ", ".join(f"{col} = ?" for col in filtered_data)
vals = list(filtered_data.values()) + [mid]
tgt.execute( tgt.execute(
f"UPDATE assets SET {sets} WHERE machine_id = ?", f"UPDATE assets SET {sets} WHERE machine_id = ?",
vals, vals,