fix: restore original dedup/prompt order in jobs.py, move time import to module level in reconcile.py

_build_job no longer calls filter_already_uploaded internally; callers pass
pre-filtered assets so there's no double DB hit and the interactive path
restores the original prompt order (retry_rejected asked before strategy,
so post-dedup count informs the choice). Skip-count rprint restored in
auto_configure. Late 'import time' inside reconcile_frigate_mappings moved
to module level.
This commit is contained in:
2026-06-14 20:14:55 +00:00
parent 343cb2ad0f
commit 71f1924f1a
2 changed files with 36 additions and 41 deletions
+35 -39
View File
@@ -122,19 +122,16 @@ def _perform_selection(
def _build_job( def _build_job(
person: dict, person: dict,
recent_assets: list, assets: list,
limit: int | str, limit: int | str,
selection_mode: str, selection_mode: str,
retry_rejected: bool = False,
quality_replacement: bool = False, quality_replacement: bool = False,
) -> dict | None: ) -> dict | None:
"""Build a job dict from pre-fetched assets and parameters. No I/O.""" """Select from pre-filtered assets and build a job dict. No terminal I/O."""
name = person["name"] if not assets:
new_asset_ids = set(filter_already_uploaded([a["id"] for a in recent_assets], retry_rejected=retry_rejected))
new_assets = [a for a in recent_assets if a["id"] in new_asset_ids]
if not new_assets:
return None return None
selected = _perform_selection(new_assets, limit, name, selection_mode, person_id=person["id"]) name = person["name"]
selected = _perform_selection(assets, limit, name, selection_mode, person_id=person["id"])
if not selected: if not selected:
return None return None
return { return {
@@ -150,7 +147,6 @@ def _configure_person(person: dict, people: list[dict]) -> dict | None:
name = person["name"] name = person["name"]
console.print(f"\nSelected: [bold green]{name}[/bold green]") console.print(f"\nSelected: [bold green]{name}[/bold green]")
# Fetch and filter assets
years = IntPrompt.ask("Filter images older than (years)", default=Config.YEARS_FILTER) years = IntPrompt.ask("Filter images older than (years)", default=Config.YEARS_FILTER)
console.print(f"Scanning for {name}...") console.print(f"Scanning for {name}...")
@@ -160,36 +156,32 @@ def _configure_person(person: dict, people: list[dict]) -> dict | None:
rprint(f" Found [bold]{len(all_assets)}[/bold] total, [bold]{len(recent_assets)}[/bold] in range ({years} years).") rprint(f" Found [bold]{len(all_assets)}[/bold] total, [bold]{len(recent_assets)}[/bold] in range ({years} years).")
# Strategy selection # Ask before strategy so the post-dedup count can inform the choice
retry_env = os.environ.get("RETRY_REJECTED", "false").lower() in ("true", "1", "yes")
retry_rejected = Confirm.ask("Include previously rejected images?", default=retry_env)
before_dedup = len(recent_assets)
new_asset_ids = set(filter_already_uploaded([a["id"] for a in recent_assets], retry_rejected=retry_rejected))
recent_assets = [a for a in recent_assets if a["id"] in new_asset_ids]
skipped = before_dedup - len(recent_assets)
if skipped:
rprint(f" [dim]Skipped {skipped} assets already uploaded to Frigate.[/dim]")
if not recent_assets:
rprint(" [dim]Skipping (0 new images after dedup).[/dim]")
return None
has_embedding = is_embedding_available() has_embedding = is_embedding_available()
rprint(f"\n[bold cyan]Select Training Strategy for {name}:[/bold cyan]") rprint(f"\n[bold cyan]Select Training Strategy for {name}:[/bold cyan]")
limit, selection_mode = _get_strategy_choice(has_embedding) limit, selection_mode = _get_strategy_choice(has_embedding)
if selection_mode == "skip": if selection_mode == "skip":
return None return None
# In interactive mode, ask about retry_rejected — use env var as default job = _build_job(person, recent_assets, limit, selection_mode, quality_replacement=Config.QUALITY_REPLACEMENT)
retry_env = os.environ.get("RETRY_REJECTED", "false").lower() in ("true", "1", "yes")
retry_rejected = Confirm.ask("Include previously rejected images?", default=retry_env)
job = _build_job(
person,
recent_assets,
limit,
selection_mode,
retry_rejected=retry_rejected,
quality_replacement=Config.QUALITY_REPLACEMENT,
)
if job is None: if job is None:
rprint(" [dim]Skipping (0 new images after dedup or selection).[/dim]") rprint(" [dim]Skipping (0 images selected).[/dim]")
return None return None
# Show how many were skipped (dedup info)
new_asset_ids = set(filter_already_uploaded([a["id"] for a in recent_assets], retry_rejected=retry_rejected))
skipped = len(recent_assets) - len(new_asset_ids)
if skipped:
rprint(f" [dim]Skipped {skipped} assets already uploaded to Frigate.[/dim]")
rprint(f" [green]Queued {job['limit']} images for {name}.[/green]") rprint(f" [green]Queued {job['limit']} images for {name}.[/green]")
return job return job
@@ -312,16 +304,20 @@ def auto_configure(people: list[dict]) -> list[dict]:
continue continue
retry_rejected = os.environ.get("RETRY_REJECTED", "false").lower() in ("true", "1", "yes") retry_rejected = os.environ.get("RETRY_REJECTED", "false").lower() in ("true", "1", "yes")
job = _build_job( before_dedup = len(recent_assets)
person, new_asset_ids = set(filter_already_uploaded([a["id"] for a in recent_assets], retry_rejected=retry_rejected))
recent_assets, recent_assets = [a for a in recent_assets if a["id"] in new_asset_ids]
limit, skipped = before_dedup - len(recent_assets)
selection_mode, if skipped:
retry_rejected=retry_rejected, rprint(f" [dim]Skipped {skipped} assets already uploaded to Frigate.[/dim]")
quality_replacement=quality_replacement,
) if not recent_assets:
rprint(f" [dim]Skipping {name} (0 new images after dedup).[/dim]")
continue
job = _build_job(person, recent_assets, limit, selection_mode, quality_replacement=quality_replacement)
if job is None: if job is None:
rprint(f" [dim]Skipping {name} (0 new images after dedup or selection).[/dim]") rprint(f" [dim]Skipping {name} (0 images selected).[/dim]")
continue continue
# Apply auto_cap post-selection if needed # Apply auto_cap post-selection if needed
+1 -2
View File
@@ -1,6 +1,7 @@
"""Frigate upload post-processing: reconciliation and asset enrichment.""" """Frigate upload post-processing: reconciliation and asset enrichment."""
import logging import logging
import time
from .frigate_api import get_frigate_person_files from .frigate_api import get_frigate_person_files
from .immich_api import fetch_face_data from .immich_api import fetch_face_data
@@ -30,8 +31,6 @@ def reconcile_frigate_mappings(
diffing. Until then, the external-upload guard keeps mappings correct at diffing. Until then, the external-upload guard keeps mappings correct at
the cost of occasionally missing them when another client is active. the cost of occasionally missing them when another client is active.
""" """
import time
target = len(uploaded) target = len(uploaded)
current_files: set[str] = set() current_files: set[str] = set()