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:
+14
-18
@@ -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"]:
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user