File size: 5,735 Bytes
75e0882
 
 
 
 
 
 
 
 
 
 
 
 
 
14e7ce6
 
75e0882
 
913537b
75e0882
 
 
 
 
913537b
75e0882
 
 
913537b
 
 
75e0882
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14e7ce6
226ca6c
14e7ce6
 
 
226ca6c
 
75e0882
 
 
 
 
226ca6c
75e0882
226ca6c
14e7ce6
 
 
226ca6c
 
14e7ce6
75e0882
226ca6c
 
75e0882
226ca6c
 
75e0882
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6ca17ee
 
 
75e0882
 
 
6ca17ee
 
 
75e0882
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14e7ce6
 
 
 
 
75e0882
14e7ce6
 
 
 
 
 
75e0882
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
"""Catalog module: JSON-based wardrobe storage with search.

The catalog is a simple JSON file that stores garment entries. Each
garment has a unique ID and structured attributes extracted by the
vision pipeline.

No vector DB, no embeddings. In-memory filtering over a list of dicts.
A wardrobe will never exceed a few hundred items.
"""

import json
import logging
from pathlib import Path

from .storage import save_image, get_image_path, delete_image

logger = logging.getLogger(__name__)

CATALOG_PATH = Path(__file__).resolve().parent.parent / "data" / "catalog.json"


def load_catalog() -> list[dict]:
    """Load the catalog from disk. Returns empty list if file doesn't exist."""
    if not CATALOG_PATH.exists():
        logger.debug("Catalog file not found: %s", CATALOG_PATH)
        return []
    try:
        with open(CATALOG_PATH, "r", encoding="utf-8") as f:
            catalog = json.load(f)
        logger.debug("Loaded catalog: %d garments from %s", len(catalog), CATALOG_PATH)
        return catalog
    except (json.JSONDecodeError, IOError) as e:
        logger.error("Failed to load catalog: %s", e)
        return []


def save_catalog(garments: list[dict]) -> None:
    """Write the full catalog to disk."""
    CATALOG_PATH.parent.mkdir(parents=True, exist_ok=True)
    with open(CATALOG_PATH, "w", encoding="utf-8") as f:
        json.dump(garments, f, indent=2, ensure_ascii=False)
    logger.info("Catalog saved: %d garments", len(garments))


def _next_id(catalog: list[dict]) -> int:
    """Get the next available garment ID number."""
    if not catalog:
        return 1
    existing_ids = []
    for g in catalog:
        gid = g.get("id", "")
        if isinstance(gid, str) and gid.startswith("garment_"):
            try:
                existing_ids.append(int(gid.split("_")[1]))
            except (ValueError, IndexError):
                pass
    return max(existing_ids, default=0) + 1


def add_garments(
    garments_with_images: list[tuple[dict, bytes | None]],
) -> list[dict]:
    """Add new garments to the catalog. Assigns IDs and persists images.

    Accepts a list of (garment_dict, image_bytes) tuples. Each garment
    gets its own individual image saved.

    Returns the garments with their assigned IDs.
    """
    catalog = load_catalog()
    next_num = _next_id(catalog)
    added = []

    for i, (garment, img_bytes) in enumerate(garments_with_images):
        garment_id = f"garment_{next_num + i:03d}"
        garment["id"] = garment_id

        if img_bytes:
            filename = save_image(garment_id, img_bytes)
            garment["image_ref"] = filename

        catalog.append(garment)
        added.append(garment)

    save_catalog(catalog)
    return added


def search(
    garment_type: str | None = None,
    color: str | None = None,
    season: str | None = None,
    formality: str | None = None,
) -> list[dict]:
    """Filter catalog by attributes. All filters are AND-combined."""
    catalog = load_catalog()
    results = catalog

    if garment_type:
        garment_type = garment_type.lower().strip()
        results = [g for g in results if garment_type in g.get("type", "")]

    if color:
        color = color.lower().strip()
        results = [g for g in results if color in g.get("color", "")]

    if season:
        season = season.lower().strip()
        results = [g for g in results if g.get("season", "") in (season, "all")]

    if formality:
        formality = formality.lower().strip()
        results = [g for g in results if formality in g.get("formality", "")]

    return results


def get_catalog_summary() -> str:
    """Format the catalog as a text block for LLM context injection."""
    catalog = load_catalog()
    if not catalog:
        return "The wardrobe is empty. No garments have been cataloged yet."

    lines = [f"Wardrobe catalog ({len(catalog)} garments):\n"]
    for g in catalog:
        gid = g.get("id", "?")
        gtype = g.get("type", "?")
        color = g.get("color", "?")
        material = g.get("material", "?")
        pattern = g.get("pattern", "?")
        season = g.get("season", "?")
        formality = g.get("formality", "?")
        description = g.get("description", "")

        line = (
            f"- [{gid}] {color} {gtype} ({material}, {pattern}) "
            f"| season: {season} | style: {formality}"
        )
        if description:
            line += f"\n  → {description}"
        lines.append(line)

    return "\n".join(lines)


def get_catalog_stats() -> dict:
    """Return aggregate statistics about the catalog."""
    catalog = load_catalog()
    if not catalog:
        return {"total": 0}

    types: dict[str, int] = {}
    colors: dict[str, int] = {}
    seasons: dict[str, int] = {}

    for g in catalog:
        t = g.get("type", "unknown")
        types[t] = types.get(t, 0) + 1
        c = g.get("color", "unknown")
        colors[c] = colors.get(c, 0) + 1
        s = g.get("season", "unknown")
        seasons[s] = seasons.get(s, 0) + 1

    return {
        "total": len(catalog),
        "by_type": dict(sorted(types.items(), key=lambda x: -x[1])),
        "by_color": dict(sorted(colors.items(), key=lambda x: -x[1])),
        "by_season": dict(sorted(seasons.items(), key=lambda x: -x[1])),
    }


def get_garment_image_path(garment_id: str) -> str | None:
    """Resolve the local filesystem path for a garment's image."""
    return get_image_path(garment_id)


def clear_catalog() -> None:
    """Remove all garments from the catalog and their images."""
    catalog = load_catalog()
    for g in catalog:
        gid = g.get("id", "")
        if gid:
            delete_image(gid)
    save_catalog([])
    logger.info("Catalog cleared")