alessiodevoto commited on
Commit
54fa6eb
1 Parent(s): f6f4b58

Upload folder using huggingface_hub

Browse files
.DS_Store ADDED
Binary file (6.15 kB). View file
 
README.md CHANGED
@@ -1,12 +1,16 @@
1
  ---
2
- title: Smartrec
3
- emoji: 🏢
4
- colorFrom: indigo
5
- colorTo: yellow
6
  sdk: gradio
7
  sdk_version: 5.7.0
8
- app_file: app.py
9
- pinned: false
10
  ---
 
 
 
 
 
 
 
 
 
11
 
12
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
1
  ---
2
+ title: smartrec
3
+ app_file: app.py
 
 
4
  sdk: gradio
5
  sdk_version: 5.7.0
 
 
6
  ---
7
+ # smartrec
8
+
9
+ AI-powered personalized newsletters.
10
+
11
+ ##### Quickstart
12
+ Tunable settings are defined in the `config.yaml` file.
13
+
14
+ - Update `config.yaml` with paths to local folders and OpenAI/Database keys.
15
+ - Run the app with `python app.py`
16
 
 
app.py ADDED
@@ -0,0 +1,241 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ from src.utils import LLMHandler, initialize_newsletter, integrate_personalized_text, build_context, build_prompt
3
+ from src.utils_api import get_recommendations
4
+ import yaml
5
+ import logging
6
+ import argparse
7
+ import os
8
+ import tempfile
9
+
10
+
11
+ # logging.basicConfig(filename='logs/app.log', encoding='utf-8', level=logging.info)
12
+ logging.basicConfig(level=logging.INFO)
13
+
14
+
15
+ def main():
16
+
17
+ # get arguments with argparse
18
+ parser = argparse.ArgumentParser(description='Newsletter Generator')
19
+ parser.add_argument('--config-file', type=str, default='./config/config.yaml', help='Path to the configuration file.')
20
+ args = parser.parse_args()
21
+
22
+ logging.info("Starting the Newsletter Generator app...")
23
+
24
+ # Load configuration from YAML file
25
+ logging.info("Loading configuration from config.yaml...")
26
+ with open(args.config_file, "r") as file:
27
+ config = yaml.safe_load(file)
28
+
29
+ # setup
30
+ os.environ["RECOMMENDER_URL"] = config['recommender_api']['base_url']
31
+ os.environ["RECOMMENDER_KEY"] = config['recommender_api']['key']
32
+ os.environ["OPENAI_KEY"] = config['llm']['api_key']
33
+
34
+ llm_settings = config['llm']
35
+ newsletter_meta_info = config['newsletter']
36
+
37
+ logging.debug(f"Configuration loaded: {config}")
38
+
39
+ # Initialize the LLM handler
40
+ llm_handler = LLMHandler(**llm_settings)
41
+ logging.info(f"LLM handler initialized with the following settings: {config['llm']}")
42
+
43
+
44
+ # Define the function to generate the newsletter using the OpenAI API
45
+ def generate_newsletter(
46
+ customer_id,
47
+ model_name,
48
+ temperature,
49
+ max_tokens,
50
+ system_message,
51
+ textual_preferences,
52
+ progress=gr.Progress()
53
+ ):
54
+
55
+
56
+ # get recommendations
57
+ progress(0.1, "Fetching Client History...")
58
+ logging.info("Getting recommendations...")
59
+ customer_info, recommendations, transactions = get_recommendations(
60
+ customer_id,
61
+ max_recs=newsletter_meta_info['max_recommendations'],
62
+ max_transactions=newsletter_meta_info['max_recents_items'])
63
+ logging.debug(f"Recommendations: {recommendations}")
64
+ logging.debug(f"Transactions: {transactions}")
65
+ print("cusomter info", customer_info)
66
+
67
+
68
+ # Load the html template and replace the placeholders for images with the actual content
69
+ logging.info("Initializing newsletter template...")
70
+ progress(0.5, "Initializing personalized content...")
71
+ newsletter_text = initialize_newsletter(newsletter_meta_info, transactions, recommendations)
72
+
73
+ # Build context from the user preferences, the recommendations and the transactions
74
+ context = build_context(
75
+ recommendations,
76
+ transactions,
77
+ textual_preferences,
78
+ customer_info)
79
+ logging.info(f"Context: {context}")
80
+
81
+ # Build the prompt for the LLM
82
+ progress(0.7, "Generating personalized content...")
83
+ prompt = build_prompt(context)
84
+ logging.info(f"Prompt: {prompt}")
85
+
86
+
87
+ # Generate the newsletter
88
+ sections = llm_handler.generate(
89
+ prompt,
90
+ model_name,
91
+ temperature,
92
+ max_tokens,
93
+ system_message)
94
+ logging.info(f"Sections: {sections}")
95
+
96
+
97
+ # Intergrate personalized text
98
+ logging.info("Integrating personalized text...")
99
+ newsletter_text = integrate_personalized_text(newsletter_text, customer_info, sections)
100
+
101
+ # Save HTML to a temporary file for download
102
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".html") as temp_file:
103
+ temp_file.write(newsletter_text.encode("utf-8"))
104
+ temp_file_path = temp_file.name
105
+ progress(1.0)
106
+ return newsletter_text, temp_file_path
107
+
108
+ logging.info("Creating interface...")
109
+
110
+ with gr.Blocks() as demo:
111
+ # Header Section
112
+ gr.Markdown("## AI-Powered Newsletter for Fashion Brands", elem_id="header")
113
+
114
+ # Input Section
115
+ with gr.Row():
116
+ customer_id = gr.Dropdown(
117
+ label="Customer ID",
118
+ #value="04a183a27a6877e560e1025216d0a3b40d88668c68366da17edfb18ed89c574c",
119
+ interactive=True,
120
+ choices=[
121
+ ("customer 1", "04a183a27a6877e560e1025216d0a3b40d88668c68366da17edfb18ed89c574c"),
122
+ ("customer 2", "1abaca5cd299000720538c70ba2ed246db6731bce924b5b4ca81770a47842656"),
123
+ ("customer 3", "1741b0d1b2c29994084b7312001c1b11ab8b112b3fd05ac765f4d232afdc4eaf")
124
+ ]
125
+ )
126
+
127
+ with gr.Row():
128
+ textual_preferences = gr.Textbox(
129
+ label="Newsletter Preferences",
130
+ placeholder="Enter rich newsletter preferences."
131
+ )
132
+
133
+ # Advanced Settings
134
+ with gr.Accordion("⚙️ Advanced Settings", open=False):
135
+ with gr.Row():
136
+ model_name = gr.Dropdown(
137
+ label="LLM Model",
138
+ choices=["gpt-3.5-turbo", "gpt-4o"],
139
+ value=llm_handler.model_name
140
+ )
141
+ temperature = gr.Slider(
142
+ label="Temperature",
143
+ minimum=0.0,
144
+ maximum=1.0,
145
+ step=0.05,
146
+ value=llm_handler.default_temperature
147
+ )
148
+ with gr.Row():
149
+ max_tokens = gr.Number(
150
+ label="Max Tokens",
151
+ value=llm_handler.default_max_tokens,
152
+ precision=0
153
+ )
154
+ system_message = gr.Textbox(
155
+ label="System Message",
156
+ placeholder="Enter a custom system message (optional).",
157
+ value=llm_handler.default_system_message,
158
+ visible=False
159
+ )
160
+
161
+ # User Context (Hidden by Default)
162
+ with gr.Accordion("🧑‍💻 User Context", open=False, visible=False):
163
+ pass # Placeholder for future user context integration.
164
+
165
+ # Output Section
166
+ with gr.Row():
167
+ generate_button = gr.Button("Generate Personalized Newsletter", variant="primary")
168
+ download = gr.DownloadButton("Download")
169
+
170
+ newsletter_output = gr.HTML(
171
+ label="Generated Newsletter",
172
+ value="<br><br><br><br><br>",
173
+ min_height=500
174
+ )
175
+
176
+ # Event Binding
177
+ generate_button.click(
178
+ fn=generate_newsletter,
179
+ inputs=[
180
+ customer_id,
181
+ model_name,
182
+ temperature,
183
+ max_tokens,
184
+ system_message,
185
+ textual_preferences
186
+ ],
187
+ outputs=[newsletter_output, download]
188
+ )
189
+
190
+ # Launch App
191
+ demo.queue().launch(
192
+ share=config['app']['share'],
193
+ server_port=config['app']['server_port']
194
+ )
195
+
196
+
197
+ # Gradio interface for the app
198
+ """ logging.info("Creating interface...")
199
+ with gr.Blocks() as demo:
200
+ gr.Markdown("### Newsletter Generator")
201
+
202
+ customer_id = gr.Textbox(label="Client ID", value="04a183a27a6877e560e1025216d0a3b40d88668c68366da17edfb18ed89c574c")
203
+ textual_preferences = gr.Textbox(label="Newsletter preferences", placeholder="The newsletter should be catchy.")
204
+
205
+ # llm_preferences = gr.Textbox(label="LLM Preferences", placeholder="Enter LLM preferences.", visible=False)
206
+ # create an openable block for the llm preferences
207
+ with gr.Accordion("LLM Preferences", open=False):
208
+ model_name = gr.Dropdown(label="Model Name", choices=["gpt-3.5-turbo", "gpt-4o"], value=llm_handler.model_name)
209
+ temperature = gr.Slider(label="Temperature", minimum=0.0, maximum=1.0, step=0.05, value=llm_handler.default_temperature)
210
+ max_tokens = gr.Number(label="Max Tokens", value=llm_handler.default_max_tokens)
211
+ system_message = gr.Textbox(label="System Message", placeholder="Enter the system message or Leave Blank.", value=llm_handler.default_system_message)
212
+
213
+ with gr.Accordion("User Context", open=False, visible=False):
214
+ # get profiled user context
215
+ pass
216
+
217
+ generate_button = gr.Button("Generate Newsletter")
218
+
219
+ # create a button to open the newsletter in a new tab
220
+ download = gr.DownloadButton(label="Download Newsletter")
221
+
222
+ newsletter_output = gr.HTML(label="Generated Newsletter", min_height="500", value="<br><br><br><br><br>")
223
+
224
+ generate_button.click(
225
+ fn=generate_newsletter,
226
+ inputs=[
227
+ customer_id,
228
+ # llm preferences
229
+ model_name,
230
+ temperature,
231
+ max_tokens,
232
+ system_message,
233
+ # newsletter preferences
234
+ textual_preferences],
235
+ outputs=[newsletter_output, download]
236
+ )
237
+
238
+ demo.queue().launch(share=config['app']['share'], server_port=config['app']['server_port'])"""
239
+
240
+ if __name__ == "__main__":
241
+ main()
config/config.yaml ADDED
@@ -0,0 +1,43 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # llm configs
2
+ llm:
3
+ api_key: "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"
4
+ model_name: "gpt-4o"
5
+ default_temperature: 0.8
6
+ default_max_tokens: 3000
7
+ default_system_message: |
8
+ You are a marketing assistant generating personalized newsletter content.
9
+ You will generate multiple sections of text that will be integrated into an HTML newsletter template.
10
+ You will write in first person.
11
+
12
+ For each section, ensure the content is coherent with the provided context and personalization parameters.
13
+ Format your response as a JSON-like structure with clear section demarcations.
14
+
15
+ Structure your response exactly as follows:
16
+ {
17
+ "greeting": "your greeting text here",
18
+ "intro": "your intro text here",
19
+ "recommendations": "your recommendations text here",
20
+ "closing": "your closing text here"
21
+ }
22
+
23
+
24
+ recommender_api:
25
+ base_url: "https://urfjxlnyxlehjqlucvuu.functions.supabase.co"
26
+ key: "CFSekdHEW2EPasoqjWTuPpvVHYQukD3ootXhd3JVUXdpuEea5d"
27
+
28
+ # app frontend
29
+ app:
30
+ server_port: 7865
31
+ share: true
32
+
33
+ # templates for newsletters and prompts
34
+ newsletter:
35
+ newsletter_example_path: "./newsletter_examples/1.html"
36
+ brand_logo: "https://seeklogo.com/images/L/luisa-spagnoli-logo-EF482BEE89-seeklogo.com.png"
37
+ brand_name: "AI x Fashion"
38
+ max_recommendations: 2
39
+ max_recents_items: 2
40
+
41
+
42
+ # logo luisa spagnoli https://seeklogo.com/images/L/luisa-spagnoli-logo-EF482BEE89-seeklogo.com.png
43
+ # logo h&m https://upload.wikimedia.org/wikipedia/commons/thumb/5/53/H%26M-Logo.svg/709px-H%26M-Logo.svg.png
logs/app.log ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ INFO:root:Starting the Newsletter Generator app...
2
+ INFO:root:Loading configuration from config.yaml...
3
+ INFO:root:Initializing the LLM handler...
newsletter_examples/0.html ADDED
@@ -0,0 +1,218 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Your Personalized Style Curation</title>
7
+ <style>
8
+ /* Elegant, sophisticated color palette with light brown background */
9
+ body {
10
+ margin: 0;
11
+ padding: 0;
12
+ width: 100%;
13
+ font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, serif;
14
+ line-height: 1.6;
15
+ color: #4A4A4A;
16
+ background-color: #F0E6D2; /* Light warm brown background */
17
+ }
18
+
19
+ .backgroundcontainer {
20
+ max-width: 600px;
21
+ margin: 0 auto;
22
+ padding: 30px;
23
+ background-color: #f5f0e6;
24
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
25
+ border: 1px solid #E0D3BA; /* Slight border to complement background */
26
+ }
27
+
28
+ .header {
29
+ text-align: center;
30
+ padding: 20px 0;
31
+ border-bottom: 1px solid #D4AD7A;
32
+ margin-bottom: 30px;
33
+ }
34
+
35
+ .header img {
36
+ max-width: 200px;
37
+ height: auto;
38
+ }
39
+
40
+ .section {
41
+ margin-bottom: 40px;
42
+ }
43
+
44
+ h1, h2, h3 {
45
+ font-family: 'Didot', serif;
46
+ color: #5D4037; /* Deep brown for headings */
47
+ margin-bottom: 15px;
48
+ }
49
+
50
+ .bought-item, .product {
51
+ display: flex;
52
+ flex-direction: column;
53
+ align-items: center;
54
+ width: 45%;
55
+ margin: 10px;
56
+ text-align: center;
57
+ padding: 20px;
58
+ background-color: #FFF8E7;
59
+ border-radius: 8px;
60
+ border: 1px solid #E0D3BA;
61
+ transition: transform 0.3s ease;
62
+ }
63
+
64
+ /* Add media query for responsive design */
65
+ @media screen and (max-width: 600px) {
66
+ .bought-item, .product {
67
+ width: 100%;
68
+ margin: 10px 0;
69
+ }
70
+ }
71
+
72
+ .bought-item:hover, .product:hover {
73
+ transform: scale(1.02);
74
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
75
+ }
76
+
77
+ .deal-box {
78
+ background-color: #F5E6D3; /* Soft light brown */
79
+ padding: 20px;
80
+ margin: 15px 0;
81
+ border-radius: 8px;
82
+ text-align: center;
83
+ border: 1px solid #D4AD7A;
84
+ }
85
+
86
+ .button {
87
+ display: inline-block;
88
+ padding: 12px 25px;
89
+ background-color: #8B6914; /* Rich brown */
90
+ color: #FFFFFF !important; /* Explicitly enforce white */
91
+ text-decoration: none;
92
+ border-radius: 5px;
93
+ font-weight: 600;
94
+ text-transform: uppercase;
95
+ letter-spacing: 1px;
96
+ transition: background-color 0.3s ease;
97
+ }
98
+
99
+ .button:link,
100
+ .button:visited,
101
+ .button:hover,
102
+ .button:active {
103
+ color: #FFFFFF; /* Ensure white for all link states */
104
+ text-decoration: none;
105
+ }
106
+
107
+ .button:hover {
108
+ background-color: #5D4037; /* Darker brown */
109
+ }
110
+
111
+ .footer {
112
+ text-align: center;
113
+ padding: 20px;
114
+ background-color: #F5E6D3; /* Soft light brown */
115
+ border-top: 1px solid #D4AD7A;
116
+ font-size: 0.9em;
117
+ }
118
+
119
+ .price {
120
+ font-size: 1.2em;
121
+ color: #8B6914; /* Rich brown */
122
+ font-weight: bold;
123
+ }
124
+
125
+ a {
126
+ color: #8B6914; /* Rich brown */
127
+ text-decoration: none;
128
+ }
129
+
130
+ a:hover {
131
+ text-decoration: underline;
132
+ }
133
+ </style>
134
+ </head>
135
+ <body>
136
+ <div class="backgroundcontainer">
137
+ <!-- Header Section -->
138
+
139
+ <div class="header" style="display: flex; justify-content: center; align-items: center;">
140
+ <img src="${brand_logo}" alt="Brand Logo">
141
+ </div>
142
+
143
+
144
+ <!-- Personal Greeting -->
145
+ <div class="section">
146
+ <!-- <h1>Hello ${customer_name},</h1> -->
147
+ <p style="font-size: 18px; font-family: 'Georgia', 'Times New Roman', serif; font-style: italic; color: #333; line-height: 1.5; letter-spacing: 0.5px; text-align: left;">
148
+ ${greeting} </p>
149
+ </div>
150
+
151
+ <!-- Previously Bought Items Section -->
152
+ <div class="section">
153
+ <p style="font-size: 18px; font-family: 'Georgia', 'Times New Roman', serif; font-style: italic; color: #333; line-height: 1.5; letter-spacing: 0.5px; text-align: left;"> ${intro} </p>
154
+ <div class="bought-item">
155
+ <img src= ${transaction_url} alt="Previous Product 1">
156
+ <h3> ${transaction_name} </h3>
157
+ </div>
158
+ <div class="bought-item">
159
+ <img src= ${transaction_url} alt="Previous Product 2">
160
+ <h3>${transaction_name}</h3>
161
+ </div>
162
+ </div>
163
+
164
+ <!-- Recommended Items -->
165
+ <p style="font-size: 18px; font-family: 'Georgia', 'Times New Roman', serif; font-style: italic; color: #333; line-height: 1.5; letter-spacing: 0.5px; text-align: left;">
166
+ ${recommendations} </p>
167
+ <div class="section">
168
+ <div class="product">
169
+ <img src=${recommendation_url} alt="Recommended Product 1">
170
+ <h3>${recommendation_name}</h3>
171
+ <a href="#" class="button">Explore</a>
172
+ </div>
173
+ <div class="product">
174
+ <img src=${recommendation_url} alt="Recommended Product 2">
175
+ <h3>${recommendation_name}</h3>
176
+ <a href="#" class="button">Explore</a>
177
+ </div>
178
+ </div>
179
+
180
+ <!-- Deals Section -->
181
+ <div class="section">
182
+ <h2 style="color: #8B6914;">Not Your Style?</h2>
183
+ <p style="font-size: 18px; font-family: 'Georgia', 'Times New Roman', serif; font-style: italic; color: #333; line-height: 1.5; letter-spacing: 0.5px; text-align: left;">
184
+ Well, I always try my best and recommend what I think would suit you well, but hey, we all make mistakes, right? </p>
185
+ <p style="font-size: 18px; font-family: 'Georgia', 'Times New Roman', serif; font-style: italic; color: #333; line-height: 1.5; letter-spacing: 0.5px; text-align: left;">
186
+ No worries though, I still have a lot of stuff for you! Take a look at these weekly deals! </p>
187
+ <div class="deal-box">
188
+ <h3> 30% OFF ALL DRESSES</h3>
189
+ <p>Exclusive Code: LSF30</p>
190
+ </div>
191
+ <div class="deal-box">
192
+ <h3> BUY 2 GET 1 FREE</h3>
193
+ <p>On our curated accessories collection</p>
194
+ </div>
195
+ <p style="font-size: 18px; font-family: 'Georgia', 'Times New Roman', serif; font-style: italic; color: #333; line-height: 1.5; letter-spacing: 0.5px; text-align: left;">
196
+ ${closing} </p>
197
+ </div>
198
+
199
+ <!-- Call to Action -->
200
+ <div class="section" style="text-align: center;">
201
+ <h2>Ready to Refine Your Wardrobe?</h2>
202
+ <a href="#" class="button">View New Arrivals</a>
203
+ <a href="#" class="button" style="background-color: #5D4037;">Book Styling Consultation</a>
204
+ </div>
205
+
206
+ <!-- Footer -->
207
+ <div class="footer">
208
+ <p style="font-weight: bold;">The ${brand_name} Team</p>
209
+ <p>
210
+ <small>
211
+ You're receiving this curated selection because you're part of our stylish community.
212
+ <a href="#">Unsubscribe</a> | <a href="#">View in Browser</a>
213
+ </small>
214
+ </p>
215
+ </div>
216
+ </div>
217
+ </body>
218
+ </html>
newsletter_examples/1.html ADDED
@@ -0,0 +1,231 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Your Personalized Style Curation</title>
7
+ <style>
8
+ /* Elegant, sophisticated color palette with light brown background */
9
+ body {
10
+ margin: 0;
11
+ padding: 0;
12
+ width: 100%;
13
+ font-family: 'Palatino Linotype', 'Book Antiqua', Palatino, serif;
14
+ line-height: 1.6;
15
+ color: #4A4A4A;
16
+ background-color: #F0E6D2; /* Light warm brown background */
17
+ }
18
+
19
+ .backgroundcontainer {
20
+ max-width: 600px;
21
+ margin: 0 auto;
22
+ padding: 30px;
23
+ background-color: #f5f0e6;
24
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
25
+ border: 1px solid #E0D3BA; /* Slight border to complement background */
26
+ }
27
+
28
+ .header {
29
+ text-align: center;
30
+ padding: 20px 0;
31
+ border-bottom: 1px solid #D4AD7A;
32
+ margin-bottom: 30px;
33
+ }
34
+
35
+ .header img {
36
+ max-width: 200px;
37
+ height: auto;
38
+ }
39
+
40
+ .section {
41
+ margin-bottom: 40px;
42
+ }
43
+
44
+ h1, h2, h3 {
45
+ font-family: 'Didot', serif;
46
+ color: #5D4037; /* Deep brown for headings */
47
+ margin-bottom: 15px;
48
+ }
49
+
50
+ /* New responsive section styles */
51
+ .section-row {
52
+ display: flex;
53
+ flex-wrap: wrap;
54
+ justify-content: space-between;
55
+ gap: 20px;
56
+ }
57
+
58
+ .bought-item, .product {
59
+ display: flex;
60
+ flex-direction: column;
61
+ align-items: center;
62
+ width: calc(50% - 10px); /* Adjust for gap */
63
+ margin: 10px 0;
64
+ text-align: center;
65
+ padding: 20px;
66
+ background-color: #FFF8E7;
67
+ border-radius: 8px;
68
+ border: 1px solid #E0D3BA;
69
+ transition: transform 0.3s ease;
70
+ }
71
+
72
+ /* Media query for smaller screens */
73
+ @media screen and (max-width: 600px) {
74
+ .section-row {
75
+ flex-direction: column;
76
+ }
77
+
78
+ .bought-item, .product {
79
+ width: 100%;
80
+ margin: 10px 0;
81
+ }
82
+ }
83
+
84
+ .bought-item:hover, .product:hover {
85
+ transform: scale(1.02);
86
+ box-shadow: 0 4px 6px rgba(0,0,0,0.1);
87
+ }
88
+
89
+ .deal-box {
90
+ background-color: #F5E6D3; /* Soft light brown */
91
+ padding: 20px;
92
+ margin: 15px 0;
93
+ border-radius: 8px;
94
+ text-align: center;
95
+ border: 1px solid #D4AD7A;
96
+ }
97
+
98
+ .button {
99
+ display: inline-block;
100
+ padding: 12px 25px;
101
+ background-color: #8B6914; /* Rich brown */
102
+ color: #FFFFFF !important; /* Explicitly enforce white */
103
+ text-decoration: none;
104
+ border-radius: 5px;
105
+ font-weight: 600;
106
+ text-transform: uppercase;
107
+ letter-spacing: 1px;
108
+ transition: background-color 0.3s ease;
109
+ }
110
+
111
+ .button:link,
112
+ .button:visited,
113
+ .button:hover,
114
+ .button:active {
115
+ color: #FFFFFF; /* Ensure white for all link states */
116
+ text-decoration: none;
117
+ }
118
+
119
+ .button:hover {
120
+ background-color: #5D4037; /* Darker brown */
121
+ }
122
+
123
+ .footer {
124
+ text-align: center;
125
+ padding: 20px;
126
+ background-color: #F5E6D3; /* Soft light brown */
127
+ border-top: 1px solid #D4AD7A;
128
+ font-size: 0.9em;
129
+ }
130
+
131
+ .price {
132
+ font-size: 1.2em;
133
+ color: #8B6914; /* Rich brown */
134
+ font-weight: bold;
135
+ }
136
+
137
+ a {
138
+ color: #8B6914; /* Rich brown */
139
+ text-decoration: none;
140
+ }
141
+
142
+ a:hover {
143
+ text-decoration: underline;
144
+ }
145
+ </style>
146
+ </head>
147
+ <body>
148
+ <div class="backgroundcontainer">
149
+ <!-- Header Section -->
150
+ <div class="header" style="display: flex; justify-content: center; align-items: center;">
151
+ <img src="${brand_logo}" alt="Brand Logo">
152
+ </div>
153
+
154
+ <!-- Personal Greeting -->
155
+ <div class="section">
156
+ <p style="font-size: 18px; font-family: 'Georgia', 'Times New Roman', serif; font-style: italic; color: #333; line-height: 1.5; letter-spacing: 0.5px; text-align: left;">
157
+ ${greeting} </p>
158
+ </div>
159
+
160
+ <!-- Previously Bought Items Section -->
161
+ <div class="section">
162
+ <p style="font-size: 18px; font-family: 'Georgia', 'Times New Roman', serif; font-style: italic; color: #333; line-height: 1.5; letter-spacing: 0.5px; text-align: left;"> ${intro} </p>
163
+ <div class="section-row">
164
+ <div class="bought-item">
165
+ <img src= ${transaction_url} alt="Previous Product 1">
166
+ <h3> ${transaction_name} </h3>
167
+ </div>
168
+ <div class="bought-item">
169
+ <img src= ${transaction_url} alt="Previous Product 2">
170
+ <h3>${transaction_name}</h3>
171
+ </div>
172
+ </div>
173
+ </div>
174
+
175
+ <!-- Recommended Items -->
176
+ <p style="font-size: 18px; font-family: 'Georgia', 'Times New Roman', serif; font-style: italic; color: #333; line-height: 1.5; letter-spacing: 0.5px; text-align: left;">
177
+ ${recommendations} </p>
178
+ <div class="section">
179
+ <div class="section-row">
180
+ <div class="product">
181
+ <img src=${recommendation_url} alt="Recommended Product 1">
182
+ <h3>${recommendation_name}</h3>
183
+ <a href="#" class="button">Explore</a>
184
+ </div>
185
+ <div class="product">
186
+ <img src=${recommendation_url} alt="Recommended Product 2">
187
+ <h3>${recommendation_name}</h3>
188
+ <a href="#" class="button">Explore</a>
189
+ </div>
190
+ </div>
191
+ </div>
192
+
193
+ <!-- Deals Section -->
194
+ <div class="section">
195
+ <h2 style="color: #8B6914;">Not Your Style?</h2>
196
+ <p style="font-size: 18px; font-family: 'Georgia', 'Times New Roman', serif; font-style: italic; color: #333; line-height: 1.5; letter-spacing: 0.5px; text-align: left;">
197
+ Well, I always try my best and recommend what I think would suit you well, but hey, we all make mistakes, right? </p>
198
+ <p style="font-size: 18px; font-family: 'Georgia', 'Times New Roman', serif; font-style: italic; color: #333; line-height: 1.5; letter-spacing: 0.5px; text-align: left;">
199
+ No worries though, I still have a lot of stuff for you! Take a look at these weekly deals! </p>
200
+ <div class="deal-box">
201
+ <h3> 30% OFF ALL DRESSES</h3>
202
+ <p>Exclusive Code: LSF30</p>
203
+ </div>
204
+ <div class="deal-box">
205
+ <h3> BUY 2 GET 1 FREE</h3>
206
+ <p>On our curated accessories collection</p>
207
+ </div>
208
+ <p style="font-size: 18px; font-family: 'Georgia', 'Times New Roman', serif; font-style: italic; color: #333; line-height: 1.5; letter-spacing: 0.5px; text-align: left;">
209
+ ${closing} </p>
210
+ </div>
211
+
212
+ <!-- Call to Action -->
213
+ <div class="section" style="text-align: center;">
214
+ <h2>Ready to Refine Your Wardrobe?</h2>
215
+ <a href="#" class="button">View New Arrivals</a>
216
+ <a href="#" class="button" style="background-color: #5D4037;">Book Styling Consultation</a>
217
+ </div>
218
+
219
+ <!-- Footer -->
220
+ <div class="footer">
221
+ <p style="font-weight: bold;">The ${brand_name} Team</p>
222
+ <p>
223
+ <small>
224
+ You're receiving this curated selection because you're part of our stylish community.
225
+ <a href="#">Unsubscribe</a> | <a href="#">View in Browser</a>
226
+ </small>
227
+ </p>
228
+ </div>
229
+ </div>
230
+ </body>
231
+ </html>
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio==5.4.0
2
+ gradio_client==1.4.2
3
+ openai==1.52.2
src/__pycache__/utils.cpython-311.pyc ADDED
Binary file (8.51 kB). View file
 
src/__pycache__/utils_api.cpython-311.pyc ADDED
Binary file (7.56 kB). View file
 
src/utils.py ADDED
@@ -0,0 +1,169 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from typing import Any, Dict
2
+ from openai import OpenAI
3
+ from string import Template
4
+ import logging
5
+ import json
6
+
7
+
8
+ class LLMHandler:
9
+ def __init__(self, api_key, model_name, default_temperature, default_max_tokens, default_system_message=None):
10
+
11
+ self.client = OpenAI(api_key=api_key)
12
+ self.model_name = model_name
13
+ self.default_temperature = default_temperature
14
+ self.default_max_tokens = default_max_tokens
15
+ self.default_system_message = default_system_message
16
+
17
+ def generate(
18
+ self,
19
+ prompt,
20
+ model_name,
21
+ temperature=None,
22
+ max_tokens=None,
23
+ system_message=None):
24
+
25
+ # optional parameters
26
+ model_name = model_name or self.model_name
27
+ temperature = temperature or self.default_temperature
28
+ max_tokens = max_tokens or self.default_max_tokens
29
+ system_message = system_message or self.default_system_message
30
+
31
+ # prepare messages
32
+ messages = [{"role": "user", "content": prompt}]
33
+ if system_message:
34
+ messages.insert(0, {"role": "system", "content": system_message})
35
+
36
+ # get response
37
+ logging.info(f"Generating text with model {model_name}, temperature {temperature}, and max tokens {max_tokens}...")
38
+ response = self.client.chat.completions.create(
39
+ model=model_name,
40
+ messages=messages,
41
+ temperature=temperature,
42
+ max_tokens=max_tokens,
43
+ seed=1234,
44
+ response_format={ "type": "json_object" }
45
+ )
46
+ return response.choices[0].message.content
47
+
48
+
49
+
50
+ def format_personalization(personalization):
51
+ return '\n'.join([f"- {key}: {value}" for key, value in personalization.items()])
52
+
53
+
54
+ def build_prompt(
55
+ context: Dict[str, Any]) -> str:
56
+ """
57
+ Create a detailed prompt incorporating all personalization factors
58
+
59
+ :param section_type: Type of section to generate
60
+ :param context: Context data including items and customer info
61
+ :param additional_textual_context: additional context for personalization
62
+ :return: Formatted prompt string
63
+ """
64
+
65
+ # Create a detailed user prompt with all context and personalization
66
+ user_prompt = f"""
67
+ Generate personalized newsletter content based on the following information:
68
+
69
+ Customer Purchase Context:
70
+ - Previously bought items: {context.pop('bought_items', [])}
71
+ - Recommended items: {context.pop('recommended_items', [])}
72
+
73
+ Personalization Parameters:
74
+ {format_personalization(context)}
75
+
76
+ Required Sections:
77
+ 1. greeting: A warm personalized welcome that considers all personalization parameters. Start with "Hello [customer name],"
78
+ 2. intro: Acknowledge previous purchases and build connection warmly. Mention the previous purchases. Only acknoweldge previous purchases that are actually present in the context.
79
+ 3. recommendations: Present recommended items naturally, explaining why they match the customer's preferences and previous purchases.
80
+ 4. closing: Wrap up maintaining the established tone.
81
+
82
+ General reequirements:
83
+ - Write in first person.
84
+ - Don't be too formal, maintain a friendly, warm and engaging tone, avoid stuff like "I hope you are doing well".
85
+ - The reader should feel like they have a special dedicated fashion assistant, who is also a friend.
86
+ - When mentioning items, use the item name and avoid mentioning colors that are not present in the image, or using adjectives like "vibrant".
87
+ - The goal should be to make the reader feel special and excited about the items they are being recommended.
88
+
89
+
90
+ Please generate all sections in a single, coherent response that maintains consistency in tone and style throughout. Begin each section with the section name in all caps.
91
+ """
92
+ return user_prompt
93
+
94
+
95
+ def initialize_newsletter(newsletter_meta_info, transactions, recommendations):
96
+
97
+ # get the paths to the template files
98
+ newsletter_example_path = newsletter_meta_info['newsletter_example_path']
99
+
100
+ # load the template from file
101
+ with open(newsletter_example_path, "r") as f:
102
+ newsletter_text = Template(f.read())
103
+ newsletter_text = newsletter_text.safe_substitute(
104
+ brand_logo=newsletter_meta_info.get("brand_logo", ""),
105
+ brand_name=newsletter_meta_info.get("brand_name", ""),
106
+ )
107
+
108
+ # iterate over the transactions and replace the placeholders with the actual content
109
+ for i, transaction in enumerate(transactions):
110
+ newsletter_text = newsletter_text.replace("${transaction_url}", transaction['product_image_url'], 1)
111
+ newsletter_text = newsletter_text.replace("${transaction_name}", transaction['product_name'], 1)
112
+
113
+ # iterate over the recommendations and replace the placeholders with the actual content
114
+ for i, recommendation in enumerate(recommendations):
115
+ newsletter_text = newsletter_text.replace("${recommendation_url}", recommendation['product_image_url'], 1)
116
+ newsletter_text = newsletter_text.replace("${recommendation_name}", recommendation['product_name'], 1)
117
+ return newsletter_text
118
+
119
+
120
+ def integrate_personalized_text(newsletter_text, customer_info, textual_sections):
121
+ # get dctionary from the textual sections json
122
+ textual_sections = json.loads(textual_sections)
123
+
124
+ logging.info(textual_sections)
125
+
126
+ newsletter_text = Template(newsletter_text)
127
+ newsletter_text = newsletter_text.safe_substitute(
128
+ **textual_sections
129
+ )
130
+
131
+ # make sure the greeting line is h1, so replace Hello ${customer_name} with <h1>Hello ${customer_name}</h1>
132
+ customer_name = customer_info.get("customer name")
133
+ newsletter_text = newsletter_text.replace(f"Hello {customer_name},", f"<h1>Hello {customer_name},</h1>")
134
+
135
+ return newsletter_text
136
+
137
+
138
+ def build_context(recommendations, transactions, additional_context, customer_info):
139
+
140
+ # clean recommendations and transactions
141
+ for rec in recommendations:
142
+ rec.pop('product_image_url', None)
143
+ rec.pop('article_id', None)
144
+ rec.pop('sales_channel_id', None)
145
+ rec.pop('transaction_date', None)
146
+ for tr in transactions:
147
+ tr.pop('product_image_url', None)
148
+ tr.pop('article_id', None)
149
+ tr.pop('sales_channel_id', None)
150
+ tr.pop('transaction_date', None)
151
+ tr.pop('price', None)
152
+
153
+ context = {
154
+ 'recommended_items': recommendations,
155
+ 'bought_items': transactions,
156
+ 'customer_name': customer_info.get('customer name', 'Unknown'),
157
+ 'customer_age': customer_info.get('customer age', 'Unknown'),
158
+ 'last_visit': customer_info.get('last_visit', 'Unknown'),
159
+ # 'preferred_categories': customer_info.get('preferred_categories', 'Unknown'),
160
+ # 'shopping_frequency': customer_info.get('shopping_frequency', 'Unknown'),
161
+ # 'style_preferences': customer_info.get('style_preferences', 'Unknown'),
162
+ # 'size_preferences': customer_info.get('size_preferences', 'Unknown'),
163
+ 'preferences': additional_context if len(additional_context) > 1 else 'No additional preferences',
164
+ }
165
+
166
+ return context
167
+
168
+
169
+
src/utils_api.py ADDED
@@ -0,0 +1,160 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import requests
2
+ import json
3
+ import os
4
+
5
+
6
+ def make_api_request(endpoint, ids):
7
+ """
8
+ Generic function to make API requests with error handling
9
+
10
+ :param endpoint: The specific API endpoint (e.g. 'get-images-from-id')
11
+ :param ids: List of IDs to query
12
+ :return: JSON response or None if error occurs
13
+ """
14
+ api_key = os.environ.get("RECOMMENDER_KEY")
15
+ if not api_key:
16
+ raise ValueError("API key is not set")
17
+ headers = {
18
+ "x-api-key": api_key,
19
+ "Content-Type": "application/json"
20
+ }
21
+
22
+ payload = {"ids": ids}
23
+
24
+ try:
25
+ recommender_url = os.environ.get("RECOMMENDER_URL")
26
+ if not recommender_url:
27
+ raise ValueError("Recommender URL is not set")
28
+ response = requests.post(f"{recommender_url}/{endpoint}",
29
+ headers=headers,
30
+ data=json.dumps(payload))
31
+
32
+ response.raise_for_status() # Raise an exception for bad status codes
33
+ return response.json()
34
+
35
+ except requests.exceptions.RequestException as e:
36
+ print(f"Error occurred while calling {endpoint}: {e}")
37
+ return None
38
+
39
+ def get_images_from_id(ids):
40
+ """Get images for specific product IDs"""
41
+ return make_api_request("get-images-from-id", ids)
42
+
43
+
44
+ def parse_article_data(article_data):
45
+ """Parse the article data and return a dictionary"""
46
+
47
+ # get the article information
48
+ article_info = article_data['article']
49
+ aricle_id = article_info.get('article_id', 'Unknown')
50
+ article_name = article_info.get('prod_name', 'Unknown')
51
+ article_type = article_info.get('department_name', 'Unknown')
52
+ article_color = article_info.get('perceived_colour_master_name', 'Unknown')
53
+
54
+ # get the article images
55
+ article_images = get_images_from_id([aricle_id])
56
+ img_url = 'no image'
57
+ if len(article_images) > 0:
58
+ img_url = article_images[0].get('url', 'no image')
59
+
60
+ return {
61
+ "product_name": article_name,
62
+ "product_type": article_type,
63
+ "product_color": article_color,
64
+ "product_image_url": img_url
65
+ }
66
+
67
+
68
+ def parse_transaction_data(transaction_data):
69
+ """Parse the transaction data and return a dictionary with article information."""
70
+
71
+ # Retrieve general transaction details
72
+ transaction_date = transaction_data.get('t_dat', 'Unknown')
73
+ sales_channel_id = transaction_data.get('sales_channel_id', 'Unknown')
74
+ price = transaction_data.get('price', 'Unknown')
75
+
76
+ # Get the article information
77
+ article_info = transaction_data.get('article', {})
78
+ article_id = article_info.get('article_id', 'Unknown')
79
+ article_name = article_info.get('prod_name', 'Unknown')
80
+ article_type = article_info.get('department_name', 'Unknown')
81
+ article_color = article_info.get('perceived_colour_master_name', 'Unknown')
82
+ article_detail = article_info.get('detail_desc', 'Unknown')
83
+
84
+ # Get the article images
85
+ article_images = get_images_from_id([article_id])
86
+ img_url = 'no image'
87
+ if article_images:
88
+ img_url = article_images[0].get('url', 'no image')
89
+
90
+ return {
91
+ "transaction_date": transaction_date,
92
+ "sales_channel_id": sales_channel_id,
93
+ "price": price,
94
+ "product_name": article_name,
95
+ "product_type": article_type,
96
+ "product_color": article_color,
97
+ "product_image_url": img_url,
98
+ "article_id": article_id
99
+ }
100
+
101
+
102
+ def parse_recommendations(recommendations, max_recs=2, max_transactions=2, only_with_images=True):
103
+ """Parse the recommendations and return a list of product IDs"""
104
+
105
+ # get the client information
106
+ customer_info = recommendations[0]['customer'][0]
107
+ customer_id = customer_info.get('customer_id', 'Unknown')
108
+ customer_name = customer_info.get('name', customer_id[:8]) # TODO change to real customer name
109
+ customer_age = customer_info.get('age', 'Unknown')
110
+
111
+ customer_info = {
112
+ "customer name": customer_name,
113
+ "customer age": customer_age
114
+ }
115
+
116
+ # get the recommendations
117
+ formatted_recommendations = [parse_article_data(article) for article in recommendations[0]['prediction']]
118
+
119
+ formatted_transactions = [parse_transaction_data(transaction) for transaction in recommendations[0]['transactions']]
120
+
121
+ print("formatted_recommendations", len(formatted_recommendations))
122
+ print("formatted_transactions", len(formatted_transactions))
123
+
124
+ if only_with_images:
125
+ formatted_recommendations = [rec for rec in formatted_recommendations if rec['product_image_url'] != 'no image']
126
+ formatted_transactions = [t for t in formatted_transactions if t['product_image_url'] != 'no image']
127
+
128
+ if len(formatted_recommendations) > max_recs:
129
+ formatted_recommendations = formatted_recommendations[:max_recs]
130
+
131
+ if len(formatted_transactions) > max_transactions:
132
+ formatted_transactions = formatted_transactions[:max_transactions]
133
+
134
+ print("formatted_recommendations", len(formatted_recommendations))
135
+ print("formatted_transactions", len(formatted_transactions))
136
+ return customer_info, formatted_recommendations, formatted_transactions
137
+
138
+
139
+ def get_recommendations(customer_ids, max_recs=4, max_transactions=2):
140
+ """Get product recommendations for specific customer IDs"""
141
+ if isinstance(customer_ids, str):
142
+ customer_ids = [customer_ids]
143
+ recommendations = make_api_request("get-recommendations", customer_ids)
144
+ if recommendations:
145
+ return parse_recommendations(recommendations, max_recs, max_transactions)
146
+ return None
147
+
148
+
149
+ if __name__ == "__main__":
150
+ # Example product IDs
151
+ product_ids = ["0110065002", "0111609001"]
152
+
153
+ # Example customer ID
154
+ customer_id = ["04a183a27a6877e560e1025216d0a3b40d88668c68366da17edfb18ed89c574c"]
155
+
156
+ # Demonstrate different API calls
157
+ print("Images:", get_images_from_id(product_ids))
158
+ print("Recommendations:", get_recommendations(customer_id))
159
+
160
+
test/api_calls.ipynb ADDED
@@ -0,0 +1,718 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "cells": [
3
+ {
4
+ "cell_type": "code",
5
+ "execution_count": 1,
6
+ "metadata": {},
7
+ "outputs": [
8
+ {
9
+ "name": "stdout",
10
+ "output_type": "stream",
11
+ "text": [
12
+ "Images: [{'name': '0110065002.jpg', 'url': 'https://urfjxlnyxlehjqlucvuu.supabase.co/storage/v1/object/public/images/011/0110065002.jpg'}, {'name': '0111609001.jpg', 'url': 'https://urfjxlnyxlehjqlucvuu.supabase.co/storage/v1/object/public/images/011/0111609001.jpg'}]\n",
13
+ "Article Data: [{'article_id': '0110065002', 'product_code': '0110065', 'prod_name': 'OP T-shirt (Idro)', 'product_type_no': '306', 'product_type_name': 'Bra', 'product_group_name': 'Underwear', 'graphical_appearance_no': '1010016', 'graphical_appearance_name': 'Solid', 'colour_group_code': '10', 'colour_group_name': 'White', 'perceived_colour_value_id': '3', 'perceived_colour_value_name': 'Light', 'perceived_colour_master_id': '9', 'perceived_colour_master_name': 'White', 'department_no': '1339', 'department_name': 'Clean Lingerie', 'index_code': 'B', 'index_name': 'Lingeries/Tights', 'index_group_no': '1', 'index_group_name': 'Ladieswear', 'section_no': '61', 'section_name': 'Womens Lingerie', 'garment_group_no': '1017', 'garment_group_name': 'Under-, Nightwear', 'detail_desc': 'Microfibre T-shirt bra with underwired, moulded, lightly padded cups that shape the bust and provide good support. Narrow adjustable shoulder straps and a narrow hook-and-eye fastening at the back. Without visible seams for greater comfort.'}, {'article_id': '0111609001', 'product_code': '0111609', 'prod_name': '200 den 1p Tights', 'product_type_no': '304', 'product_type_name': 'Underwear Tights', 'product_group_name': 'Socks & Tights', 'graphical_appearance_no': '1010016', 'graphical_appearance_name': 'Solid', 'colour_group_code': '09', 'colour_group_name': 'Black', 'perceived_colour_value_id': '4', 'perceived_colour_value_name': 'Dark', 'perceived_colour_master_id': '5', 'perceived_colour_master_name': 'Black', 'department_no': '3608', 'department_name': 'Tights basic', 'index_code': 'B', 'index_name': 'Lingeries/Tights', 'index_group_no': '1', 'index_group_name': 'Ladieswear', 'section_no': '62', 'section_name': 'Womens Nightwear, Socks & Tigh', 'garment_group_no': '1021', 'garment_group_name': 'Socks and Tights', 'detail_desc': 'Opaque matt tights. 200 denier.'}]\n",
14
+ "Customer Data: [{'customer_id': '00000dbacae5abe5e23885899a1fa44253a17956c6d1c3d25f88aa139fdfc657', 'FN': '', 'Active': '', 'club_member_status': 'ACTIVE', 'fashion_news_frequency': 'NONE', 'age': '49', 'postal_code': '52043ee2162cf5aa7ee79974281641c6f11a68d276429a91f8ca0d4b6efa8100'}]\n",
15
+ "Recommendations: [{'customer': [{'customer_id': '00000dbacae5abe5e23885899a1fa44253a17956c6d1c3d25f88aa139fdfc657', 'FN': '', 'Active': '', 'club_member_status': 'ACTIVE', 'fashion_news_frequency': 'NONE', 'age': '49', 'postal_code': '52043ee2162cf5aa7ee79974281641c6f11a68d276429a91f8ca0d4b6efa8100'}], 'prediction': [{'article': {'prod_name': 'Jade HW Skinny Denim TRS', 'article_id': '0706016001', 'index_code': 'D', 'index_name': 'Divided', 'section_no': '53', 'detail_desc': 'High-waisted jeans in washed superstretch denim with a zip fly and button, fake front pockets, real back pockets and super-skinny legs.', 'product_code': '0706016', 'section_name': 'Divided Collection', 'department_no': '1747', 'index_group_no': '2', 'department_name': 'Trousers', 'product_type_no': '272', 'garment_group_no': '1009', 'index_group_name': 'Divided', 'colour_group_code': '09', 'colour_group_name': 'Black', 'product_type_name': 'Trousers', 'garment_group_name': 'Trousers', 'product_group_name': 'Garment Lower body', 'graphical_appearance_no': '1010016', 'graphical_appearance_name': 'Solid', 'perceived_colour_value_id': '4', 'perceived_colour_master_id': '5', 'perceived_colour_value_name': 'Dark', 'perceived_colour_master_name': 'Black'}}, {'article': {'prod_name': 'Jade HW Skinny Denim TRS', 'article_id': '0706016002', 'index_code': 'D', 'index_name': 'Divided', 'section_no': '53', 'detail_desc': 'High-waisted jeans in washed superstretch denim with a zip fly and button, fake front pockets, real back pockets and super-skinny legs.', 'product_code': '0706016', 'section_name': 'Divided Collection', 'department_no': '1747', 'index_group_no': '2', 'department_name': 'Trousers', 'product_type_no': '272', 'garment_group_no': '1009', 'index_group_name': 'Divided', 'colour_group_code': '71', 'colour_group_name': 'Light Blue', 'product_type_name': 'Trousers', 'garment_group_name': 'Trousers', 'product_group_name': 'Garment Lower body', 'graphical_appearance_no': '1010016', 'graphical_appearance_name': 'Solid', 'perceived_colour_value_id': '3', 'perceived_colour_master_id': '2', 'perceived_colour_value_name': 'Light', 'perceived_colour_master_name': 'Blue'}}, {'article': {'prod_name': '7p Basic Shaftless', 'article_id': '0372860001', 'index_code': 'B', 'index_name': 'Lingeries/Tights', 'section_no': '62', 'detail_desc': 'Fine-knit trainer socks in a soft cotton blend.', 'product_code': '0372860', 'section_name': 'Womens Nightwear, Socks & Tigh', 'department_no': '3611', 'index_group_no': '1', 'department_name': 'Shopbasket Socks', 'product_type_no': '302', 'garment_group_no': '1021', 'index_group_name': 'Ladieswear', 'colour_group_code': '09', 'colour_group_name': 'Black', 'product_type_name': 'Socks', 'garment_group_name': 'Socks and Tights', 'product_group_name': 'Socks & Tights', 'graphical_appearance_no': '1010016', 'graphical_appearance_name': 'Solid', 'perceived_colour_value_id': '4', 'perceived_colour_master_id': '5', 'perceived_colour_value_name': 'Dark', 'perceived_colour_master_name': 'Black'}}, {'article': {'prod_name': 'Tilly (1)', 'article_id': '0610776002', 'index_code': 'A', 'index_name': 'Ladieswear', 'section_no': '16', 'detail_desc': 'T-shirt in lightweight jersey with a rounded hem. Slightly longer at the back.', 'product_code': '0610776', 'section_name': 'Womens Everyday Basics', 'department_no': '1676', 'index_group_no': '1', 'department_name': 'Jersey Basic', 'product_type_no': '255', 'garment_group_no': '1002', 'index_group_name': 'Ladieswear', 'colour_group_code': '09', 'colour_group_name': 'Black', 'product_type_name': 'T-shirt', 'garment_group_name': 'Jersey Basic', 'product_group_name': 'Garment Upper body', 'graphical_appearance_no': '1010016', 'graphical_appearance_name': 'Solid', 'perceived_colour_value_id': '4', 'perceived_colour_master_id': '5', 'perceived_colour_value_name': 'Dark', 'perceived_colour_master_name': 'Black'}}, {'article': {'prod_name': 'Tilda tank', 'article_id': '0759871002', 'index_code': 'D', 'index_name': 'Divided', 'section_no': '80', 'detail_desc': 'Cropped, fitted top in cotton jersey with narrow shoulder straps.', 'product_code': '0759871', 'section_name': 'Divided Complements Other', 'department_no': '3936', 'index_group_no': '2', 'department_name': 'EQ Divided Basics', 'product_type_no': '253', 'garment_group_no': '1002', 'index_group_name': 'Divided', 'colour_group_code': '09', 'colour_group_name': 'Black', 'product_type_name': 'Vest top', 'garment_group_name': 'Jersey Basic', 'product_group_name': 'Garment Upper body', 'graphical_appearance_no': '1010016', 'graphical_appearance_name': 'Solid', 'perceived_colour_value_id': '4', 'perceived_colour_master_id': '5', 'perceived_colour_value_name': 'Dark', 'perceived_colour_master_name': 'Black'}}, {'article': {'prod_name': 'Greta Thong Mynta Low 3p', 'article_id': '0464297007', 'index_code': 'B', 'index_name': 'Lingeries/Tights', 'section_no': '61', 'detail_desc': 'Thong briefs in cotton jersey with a wide lace trim at the top. Low waist, lined gusset, narrow sides and a string back.', 'product_code': '0464297', 'section_name': 'Womens Lingerie', 'department_no': '1334', 'index_group_no': '1', 'department_name': 'Casual Lingerie', 'product_type_no': '286', 'garment_group_no': '1017', 'index_group_name': 'Ladieswear', 'colour_group_code': '09', 'colour_group_name': 'Black', 'product_type_name': 'Underwear bottom', 'garment_group_name': 'Under-, Nightwear', 'product_group_name': 'Underwear', 'graphical_appearance_no': '1010014', 'graphical_appearance_name': 'Placement print', 'perceived_colour_value_id': '4', 'perceived_colour_master_id': '5', 'perceived_colour_value_name': 'Dark', 'perceived_colour_master_name': 'Black'}}, {'article': {'prod_name': '7p Basic Shaftless', 'article_id': '0372860002', 'index_code': 'B', 'index_name': 'Lingeries/Tights', 'section_no': '62', 'detail_desc': 'Fine-knit trainer socks in a soft cotton blend.', 'product_code': '0372860', 'section_name': 'Womens Nightwear, Socks & Tigh', 'department_no': '3611', 'index_group_no': '1', 'department_name': 'Shopbasket Socks', 'product_type_no': '302', 'garment_group_no': '1021', 'index_group_name': 'Ladieswear', 'colour_group_code': '10', 'colour_group_name': 'White', 'product_type_name': 'Socks', 'garment_group_name': 'Socks and Tights', 'product_group_name': 'Socks & Tights', 'graphical_appearance_no': '1010016', 'graphical_appearance_name': 'Solid', 'perceived_colour_value_id': '3', 'perceived_colour_master_id': '9', 'perceived_colour_value_name': 'Light', 'perceived_colour_master_name': 'White'}}, {'article': {'prod_name': 'Tilly (1)', 'article_id': '0610776001', 'index_code': 'A', 'index_name': 'Ladieswear', 'section_no': '16', 'detail_desc': 'T-shirt in lightweight jersey with a rounded hem. Slightly longer at the back.', 'product_code': '0610776', 'section_name': 'Womens Everyday Basics', 'department_no': '1676', 'index_group_no': '1', 'department_name': 'Jersey Basic', 'product_type_no': '255', 'garment_group_no': '1002', 'index_group_name': 'Ladieswear', 'colour_group_code': '10', 'colour_group_name': 'White', 'product_type_name': 'T-shirt', 'garment_group_name': 'Jersey Basic', 'product_group_name': 'Garment Upper body', 'graphical_appearance_no': '1010016', 'graphical_appearance_name': 'Solid', 'perceived_colour_value_id': '3', 'perceived_colour_master_id': '9', 'perceived_colour_value_name': 'Light', 'perceived_colour_master_name': 'White'}}, {'article': {'prod_name': 'Curvy Jeggings HW Ankle', 'article_id': '0399223001', 'index_code': 'D', 'index_name': 'Divided', 'section_no': '57', 'detail_desc': 'Jeggings in washed, superstretch denim with a high waist, fake front pockets, real back pockets and skinny legs.', 'product_code': '0399223', 'section_name': 'Ladies Denim', 'department_no': '1772', 'index_group_no': '2', 'department_name': 'Denim Trousers', 'product_type_no': '272', 'garment_group_no': '1016', 'index_group_name': 'Divided', 'colour_group_code': '09', 'colour_group_name': 'Black', 'product_type_name': 'Trousers', 'garment_group_name': 'Trousers Denim', 'product_group_name': 'Garment Lower body', 'graphical_appearance_no': '1010016', 'graphical_appearance_name': 'Solid', 'perceived_colour_value_id': '4', 'perceived_colour_master_id': '5', 'perceived_colour_value_name': 'Dark', 'perceived_colour_master_name': 'Black'}}, {'article': {'prod_name': 'Jade HW Skinny Denim TRS', 'article_id': '0706016003', 'index_code': 'D', 'index_name': 'Divided', 'section_no': '53', 'detail_desc': 'High-waisted jeans in washed superstretch denim with a zip fly and button, fake front pockets, real back pockets and super-skinny legs.', 'product_code': '0706016', 'section_name': 'Divided Collection', 'department_no': '1747', 'index_group_no': '2', 'department_name': 'Trousers', 'product_type_no': '272', 'garment_group_no': '1009', 'index_group_name': 'Divided', 'colour_group_code': '73', 'colour_group_name': 'Dark Blue', 'product_type_name': 'Trousers', 'garment_group_name': 'Trousers', 'product_group_name': 'Garment Lower body', 'graphical_appearance_no': '1010016', 'graphical_appearance_name': 'Solid', 'perceived_colour_value_id': '2', 'perceived_colour_master_id': '2', 'perceived_colour_value_name': 'Medium Dusty', 'perceived_colour_master_name': 'Blue'}}, {'article': {'prod_name': 'SUPREME RW tights', 'article_id': '0720125001', 'index_code': 'S', 'index_name': 'Sport', 'section_no': '5', 'detail_desc': 'Sports tights in fast-drying functional fabric with a wide waistband to hold in and shape the waist. Regular waist with a concealed key pocket in the waistband.', 'product_code': '0720125', 'section_name': 'Ladies H&M Sport', 'department_no': '8310', 'index_group_no': '26', 'department_name': 'Ladies Sport Bottoms', 'product_type_no': '273', 'garment_group_no': '1005', 'index_group_name': 'Sport', 'colour_group_code': '09', 'colour_group_name': 'Black', 'product_type_name': 'Leggings/Tights', 'garment_group_name': 'Jersey Fancy', 'product_group_name': 'Garment Lower body', 'graphical_appearance_no': '1010016', 'graphical_appearance_name': 'Solid', 'perceived_colour_value_id': '4', 'perceived_colour_master_id': '5', 'perceived_colour_value_name': 'Dark', 'perceived_colour_master_name': 'Black'}}, {'article': {'prod_name': 'Box 4p Tights', 'article_id': '0156231001', 'index_code': 'B', 'index_name': 'Lingeries/Tights', 'section_no': '62', 'detail_desc': 'Matt tights with an elasticated waist. 20 denier.', 'product_code': '0156231', 'section_name': 'Womens Nightwear, Socks & Tigh', 'department_no': '3608', 'index_group_no': '1', 'department_name': 'Tights basic', 'product_type_no': '304', 'garment_group_no': '1021', 'index_group_name': 'Ladieswear', 'colour_group_code': '09', 'colour_group_name': 'Black', 'product_type_name': 'Underwear Tights', 'garment_group_name': 'Socks and Tights', 'product_group_name': 'Socks & Tights', 'graphical_appearance_no': '1010016', 'graphical_appearance_name': 'Solid', 'perceived_colour_value_id': '4', 'perceived_colour_master_id': '5', 'perceived_colour_value_name': 'Dark', 'perceived_colour_master_name': 'Black'}}]}]\n"
16
+ ]
17
+ }
18
+ ],
19
+ "source": [
20
+ "import requests\n",
21
+ "import json\n",
22
+ "import os \n",
23
+ "\n",
24
+ "# API Configuration\n",
25
+ "BASE_URL = \"https://urfjxlnyxlehjqlucvuu.functions.supabase.co\"\n",
26
+ "API_KEY = \"CFSekdHEW2EPasoqjWTuPpvVHYQukD3ootXhd3JVUXdpuEea5d\"\n",
27
+ "\n",
28
+ "def make_api_request(endpoint, ids):\n",
29
+ " \"\"\"\n",
30
+ " Generic function to make API requests with error handling\n",
31
+ " \n",
32
+ " :param endpoint: The specific API endpoint (e.g. 'get-images-from-id')\n",
33
+ " :param ids: List of IDs to query\n",
34
+ " :return: JSON response or None if error occurs\n",
35
+ " \"\"\"\n",
36
+ " api_key = os.environ.get(\"RECOMMENDER_KEY\")\n",
37
+ " headers = {\n",
38
+ " \"x-api-key\": api_key,\n",
39
+ " \"Content-Type\": \"application/json\"\n",
40
+ " }\n",
41
+ " \n",
42
+ " payload = {\"ids\": ids}\n",
43
+ " \n",
44
+ " try:\n",
45
+ " recommender_url = os.environ.get(\"RECOMMENDER_URL\")\n",
46
+ " response = requests.post(f\"{recommender_url}/{endpoint}\", \n",
47
+ " headers=headers, \n",
48
+ " data=json.dumps(payload))\n",
49
+ " \n",
50
+ " response.raise_for_status() # Raise an exception for bad status codes\n",
51
+ " return response.json()\n",
52
+ " \n",
53
+ " except requests.exceptions.RequestException as e:\n",
54
+ " print(f\"Error occurred while calling {endpoint}: {e}\")\n",
55
+ " return None\n",
56
+ "\n",
57
+ "def get_images_from_id(ids):\n",
58
+ " \"\"\"Get images for specific product IDs\"\"\"\n",
59
+ " return make_api_request(\"get-images-from-id\", ids)\n",
60
+ "\n",
61
+ "def get_article_data(ids):\n",
62
+ " \"\"\"Get article data for specific product IDs\"\"\"\n",
63
+ " return make_api_request(\"get-articles-data\", ids)\n",
64
+ "\n",
65
+ "def get_customer_data(customer_ids):\n",
66
+ " \"\"\"Get customer data for specific customer IDs\"\"\"\n",
67
+ " return make_api_request(\"get-customer-data\", customer_ids)\n",
68
+ "\n",
69
+ "def get_recommendations(customer_ids):\n",
70
+ " \"\"\"Get product recommendations for specific customer IDs\"\"\"\n",
71
+ " return make_api_request(\"get-recommendations\", customer_ids)\n",
72
+ "\n",
73
+ "\n",
74
+ "# Example product IDs\n",
75
+ "product_ids = [\"0110065002\", \"0111609001\"]\n",
76
+ "\n",
77
+ "# Example customer ID\n",
78
+ "customer_id = [\"00000dbacae5abe5e23885899a1fa44253a17956c6d1c3d25f88aa139fdfc657\"]\n",
79
+ "\n",
80
+ "# Demonstrate different API calls\n",
81
+ "print(\"Images:\", get_images_from_id(product_ids))\n",
82
+ "print(\"Article Data:\", get_article_data(product_ids))\n",
83
+ "print(\"Customer Data:\", get_customer_data(customer_id))\n",
84
+ "print(\"Recommendations:\", get_recommendations(customer_id))\n",
85
+ "\n"
86
+ ]
87
+ },
88
+ {
89
+ "cell_type": "code",
90
+ "execution_count": 4,
91
+ "metadata": {},
92
+ "outputs": [],
93
+ "source": [
94
+ "a = get_recommendations([\"00000dbacae5abe5e23885899a1fa44253a17956c6d1c3d25f88aa139fdfc657\"])"
95
+ ]
96
+ },
97
+ {
98
+ "cell_type": "code",
99
+ "execution_count": 23,
100
+ "metadata": {},
101
+ "outputs": [],
102
+ "source": [
103
+ "a[0]['prediction'][0]['article']['article_id']\n",
104
+ "img = get_images_from_id([str(a[0]['prediction'][0]['article']['article_id'])])"
105
+ ]
106
+ },
107
+ {
108
+ "cell_type": "code",
109
+ "execution_count": null,
110
+ "metadata": {},
111
+ "outputs": [],
112
+ "source": [
113
+ "img = get_images_from_id([str(a[0]['prediction'][0]['article']['article_id'])])"
114
+ ]
115
+ },
116
+ {
117
+ "cell_type": "code",
118
+ "execution_count": 57,
119
+ "metadata": {},
120
+ "outputs": [],
121
+ "source": [
122
+ "product_ids = [\"0110065002\", \"0111609001\"]\n"
123
+ ]
124
+ },
125
+ {
126
+ "cell_type": "code",
127
+ "execution_count": 71,
128
+ "metadata": {},
129
+ "outputs": [
130
+ {
131
+ "name": "stdout",
132
+ "output_type": "stream",
133
+ "text": [
134
+ "None\n"
135
+ ]
136
+ }
137
+ ],
138
+ "source": [
139
+ "import os\n",
140
+ "l = os.environ.get(\"RECOMMENDER_KEY\")\n",
141
+ "print(l)"
142
+ ]
143
+ },
144
+ {
145
+ "cell_type": "code",
146
+ "execution_count": 61,
147
+ "metadata": {},
148
+ "outputs": [],
149
+ "source": [
150
+ "im = get_images_from_id([product_ids[0]])\n"
151
+ ]
152
+ },
153
+ {
154
+ "cell_type": "code",
155
+ "execution_count": 64,
156
+ "metadata": {},
157
+ "outputs": [
158
+ {
159
+ "data": {
160
+ "text/plain": [
161
+ "{'name': '0110065002.jpg',\n",
162
+ " 'url': 'https://urfjxlnyxlehjqlucvuu.supabase.co/storage/v1/object/public/images/011/0110065002.jpg'}"
163
+ ]
164
+ },
165
+ "execution_count": 64,
166
+ "metadata": {},
167
+ "output_type": "execute_result"
168
+ }
169
+ ],
170
+ "source": [
171
+ "im[0]['url']"
172
+ ]
173
+ },
174
+ {
175
+ "cell_type": "code",
176
+ "execution_count": 62,
177
+ "metadata": {},
178
+ "outputs": [
179
+ {
180
+ "data": {
181
+ "text/plain": [
182
+ "[{'name': '0110065002.jpg',\n",
183
+ " 'url': 'https://urfjxlnyxlehjqlucvuu.supabase.co/storage/v1/object/public/images/011/0110065002.jpg'}]"
184
+ ]
185
+ },
186
+ "execution_count": 62,
187
+ "metadata": {},
188
+ "output_type": "execute_result"
189
+ }
190
+ ],
191
+ "source": [
192
+ "im"
193
+ ]
194
+ },
195
+ {
196
+ "cell_type": "code",
197
+ "execution_count": 67,
198
+ "metadata": {},
199
+ "outputs": [],
200
+ "source": [
201
+ "def parse_article_data(article_data):\n",
202
+ " \"\"\"Parse the article data and return a dictionary\"\"\"\n",
203
+ " \n",
204
+ " # get the article information\n",
205
+ " article_info = article_data['article']\n",
206
+ " aricle_id = article_info.get('article_id', 'Unknown')\n",
207
+ " article_name = article_info.get('prod_name', 'Unknown')\n",
208
+ " article_type = article_info.get('department_name', 'Unknown')\n",
209
+ " article_color = article_info.get('perceived_colour_master_name', 'Unknown')\n",
210
+ " \n",
211
+ " # get the article images\n",
212
+ " article_images = get_images_from_id([aricle_id])\n",
213
+ " img_url = 'no image'\n",
214
+ " if len(article_images) > 0:\n",
215
+ " img_url = article_images[0].get('url', 'no image')\n",
216
+ " \n",
217
+ " return {\n",
218
+ " \"product_name\": article_name,\n",
219
+ " \"product_type\": article_type,\n",
220
+ " \"product_color\": article_color,\n",
221
+ " \"product_image_url\": img_url\n",
222
+ " }\n",
223
+ "\n",
224
+ "\n",
225
+ "\n",
226
+ "def parse_recommendations(recommendations):\n",
227
+ " \"\"\"Parse the recommendations and return a list of product IDs\"\"\"\n",
228
+ " \n",
229
+ " # get the client information\n",
230
+ " customer_info = recommendations[0]['customer'][0]\n",
231
+ " customer_name = customer_info.get('name', 'Unknown')\n",
232
+ " customer_age = customer_info.get('age', 'Unknown')\n",
233
+ "\n",
234
+ " print(customer_name)\n",
235
+ " print(customer_age)\n",
236
+ " \n",
237
+ "\n",
238
+ " # get the recommendations\n",
239
+ " formatted_recommendations = [parse_article_data(article) for article in recommendations[0]['prediction']]\n",
240
+ "\n",
241
+ " return {\n",
242
+ " \"customer_name\": customer_name,\n",
243
+ " \"customer_age\": customer_age,\n",
244
+ " \"recommendations\": formatted_recommendations\n",
245
+ " }\n",
246
+ " "
247
+ ]
248
+ },
249
+ {
250
+ "cell_type": "code",
251
+ "execution_count": 68,
252
+ "metadata": {},
253
+ "outputs": [
254
+ {
255
+ "name": "stdout",
256
+ "output_type": "stream",
257
+ "text": [
258
+ "Unknown\n",
259
+ "49\n"
260
+ ]
261
+ },
262
+ {
263
+ "data": {
264
+ "text/plain": [
265
+ "{'customer_name': 'Unknown',\n",
266
+ " 'customer_age': '49',\n",
267
+ " 'recommendations': [{'name': 'Jade HW Skinny Denim TRS',\n",
268
+ " 'type': 'Trousers',\n",
269
+ " 'color': 'Black',\n",
270
+ " 'image_url': 'no image'},\n",
271
+ " {'name': 'Jade HW Skinny Denim TRS',\n",
272
+ " 'type': 'Trousers',\n",
273
+ " 'color': 'Blue',\n",
274
+ " 'image_url': 'no image'},\n",
275
+ " {'name': '7p Basic Shaftless',\n",
276
+ " 'type': 'Shopbasket Socks',\n",
277
+ " 'color': 'Black',\n",
278
+ " 'image_url': 'https://urfjxlnyxlehjqlucvuu.supabase.co/storage/v1/object/public/images/037/0372860001.jpg'},\n",
279
+ " {'name': 'Tilly (1)',\n",
280
+ " 'type': 'Jersey Basic',\n",
281
+ " 'color': 'Black',\n",
282
+ " 'image_url': 'no image'},\n",
283
+ " {'name': 'Tilda tank',\n",
284
+ " 'type': 'EQ Divided Basics',\n",
285
+ " 'color': 'Black',\n",
286
+ " 'image_url': 'no image'},\n",
287
+ " {'name': 'Greta Thong Mynta Low 3p',\n",
288
+ " 'type': 'Casual Lingerie',\n",
289
+ " 'color': 'Black',\n",
290
+ " 'image_url': 'https://urfjxlnyxlehjqlucvuu.supabase.co/storage/v1/object/public/images/046/0464297007.jpg'},\n",
291
+ " {'name': '7p Basic Shaftless',\n",
292
+ " 'type': 'Shopbasket Socks',\n",
293
+ " 'color': 'White',\n",
294
+ " 'image_url': 'https://urfjxlnyxlehjqlucvuu.supabase.co/storage/v1/object/public/images/037/0372860002.jpg'},\n",
295
+ " {'name': 'Tilly (1)',\n",
296
+ " 'type': 'Jersey Basic',\n",
297
+ " 'color': 'White',\n",
298
+ " 'image_url': 'no image'},\n",
299
+ " {'name': 'Curvy Jeggings HW Ankle',\n",
300
+ " 'type': 'Denim Trousers',\n",
301
+ " 'color': 'Black',\n",
302
+ " 'image_url': 'no image'},\n",
303
+ " {'name': 'Jade HW Skinny Denim TRS',\n",
304
+ " 'type': 'Trousers',\n",
305
+ " 'color': 'Blue',\n",
306
+ " 'image_url': 'no image'},\n",
307
+ " {'name': 'SUPREME RW tights',\n",
308
+ " 'type': 'Ladies Sport Bottoms',\n",
309
+ " 'color': 'Black',\n",
310
+ " 'image_url': 'no image'},\n",
311
+ " {'name': 'Box 4p Tights',\n",
312
+ " 'type': 'Tights basic',\n",
313
+ " 'color': 'Black',\n",
314
+ " 'image_url': 'https://urfjxlnyxlehjqlucvuu.supabase.co/storage/v1/object/public/images/015/0156231001.jpg'}]}"
315
+ ]
316
+ },
317
+ "execution_count": 68,
318
+ "metadata": {},
319
+ "output_type": "execute_result"
320
+ }
321
+ ],
322
+ "source": [
323
+ "parse_recommendations(a)"
324
+ ]
325
+ },
326
+ {
327
+ "cell_type": "code",
328
+ "execution_count": 38,
329
+ "metadata": {},
330
+ "outputs": [
331
+ {
332
+ "data": {
333
+ "text/plain": [
334
+ "{'prod_name': 'Jade HW Skinny Denim TRS',\n",
335
+ " 'article_id': '0706016001',\n",
336
+ " 'index_code': 'D',\n",
337
+ " 'index_name': 'Divided',\n",
338
+ " 'section_no': '53',\n",
339
+ " 'detail_desc': 'High-waisted jeans in washed superstretch denim with a zip fly and button, fake front pockets, real back pockets and super-skinny legs.',\n",
340
+ " 'product_code': '0706016',\n",
341
+ " 'section_name': 'Divided Collection',\n",
342
+ " 'department_no': '1747',\n",
343
+ " 'index_group_no': '2',\n",
344
+ " 'department_name': 'Trousers',\n",
345
+ " 'product_type_no': '272',\n",
346
+ " 'garment_group_no': '1009',\n",
347
+ " 'index_group_name': 'Divided',\n",
348
+ " 'colour_group_code': '09',\n",
349
+ " 'colour_group_name': 'Black',\n",
350
+ " 'product_type_name': 'Trousers',\n",
351
+ " 'garment_group_name': 'Trousers',\n",
352
+ " 'product_group_name': 'Garment Lower body',\n",
353
+ " 'graphical_appearance_no': '1010016',\n",
354
+ " 'graphical_appearance_name': 'Solid',\n",
355
+ " 'perceived_colour_value_id': '4',\n",
356
+ " 'perceived_colour_master_id': '5',\n",
357
+ " 'perceived_colour_value_name': 'Dark',\n",
358
+ " 'perceived_colour_master_name': 'Black'}"
359
+ ]
360
+ },
361
+ "execution_count": 38,
362
+ "metadata": {},
363
+ "output_type": "execute_result"
364
+ }
365
+ ],
366
+ "source": [
367
+ "a[0]['prediction'][0]['article']\n"
368
+ ]
369
+ },
370
+ {
371
+ "cell_type": "code",
372
+ "execution_count": 72,
373
+ "metadata": {},
374
+ "outputs": [
375
+ {
376
+ "data": {
377
+ "text/plain": [
378
+ "[{'customer': [{'customer_id': '00000dbacae5abe5e23885899a1fa44253a17956c6d1c3d25f88aa139fdfc657',\n",
379
+ " 'FN': '',\n",
380
+ " 'Active': '',\n",
381
+ " 'club_member_status': 'ACTIVE',\n",
382
+ " 'fashion_news_frequency': 'NONE',\n",
383
+ " 'age': '49',\n",
384
+ " 'postal_code': '52043ee2162cf5aa7ee79974281641c6f11a68d276429a91f8ca0d4b6efa8100'}],\n",
385
+ " 'prediction': [{'article': {'prod_name': 'Jade HW Skinny Denim TRS',\n",
386
+ " 'article_id': '0706016001',\n",
387
+ " 'index_code': 'D',\n",
388
+ " 'index_name': 'Divided',\n",
389
+ " 'section_no': '53',\n",
390
+ " 'detail_desc': 'High-waisted jeans in washed superstretch denim with a zip fly and button, fake front pockets, real back pockets and super-skinny legs.',\n",
391
+ " 'product_code': '0706016',\n",
392
+ " 'section_name': 'Divided Collection',\n",
393
+ " 'department_no': '1747',\n",
394
+ " 'index_group_no': '2',\n",
395
+ " 'department_name': 'Trousers',\n",
396
+ " 'product_type_no': '272',\n",
397
+ " 'garment_group_no': '1009',\n",
398
+ " 'index_group_name': 'Divided',\n",
399
+ " 'colour_group_code': '09',\n",
400
+ " 'colour_group_name': 'Black',\n",
401
+ " 'product_type_name': 'Trousers',\n",
402
+ " 'garment_group_name': 'Trousers',\n",
403
+ " 'product_group_name': 'Garment Lower body',\n",
404
+ " 'graphical_appearance_no': '1010016',\n",
405
+ " 'graphical_appearance_name': 'Solid',\n",
406
+ " 'perceived_colour_value_id': '4',\n",
407
+ " 'perceived_colour_master_id': '5',\n",
408
+ " 'perceived_colour_value_name': 'Dark',\n",
409
+ " 'perceived_colour_master_name': 'Black'}},\n",
410
+ " {'article': {'prod_name': 'Jade HW Skinny Denim TRS',\n",
411
+ " 'article_id': '0706016002',\n",
412
+ " 'index_code': 'D',\n",
413
+ " 'index_name': 'Divided',\n",
414
+ " 'section_no': '53',\n",
415
+ " 'detail_desc': 'High-waisted jeans in washed superstretch denim with a zip fly and button, fake front pockets, real back pockets and super-skinny legs.',\n",
416
+ " 'product_code': '0706016',\n",
417
+ " 'section_name': 'Divided Collection',\n",
418
+ " 'department_no': '1747',\n",
419
+ " 'index_group_no': '2',\n",
420
+ " 'department_name': 'Trousers',\n",
421
+ " 'product_type_no': '272',\n",
422
+ " 'garment_group_no': '1009',\n",
423
+ " 'index_group_name': 'Divided',\n",
424
+ " 'colour_group_code': '71',\n",
425
+ " 'colour_group_name': 'Light Blue',\n",
426
+ " 'product_type_name': 'Trousers',\n",
427
+ " 'garment_group_name': 'Trousers',\n",
428
+ " 'product_group_name': 'Garment Lower body',\n",
429
+ " 'graphical_appearance_no': '1010016',\n",
430
+ " 'graphical_appearance_name': 'Solid',\n",
431
+ " 'perceived_colour_value_id': '3',\n",
432
+ " 'perceived_colour_master_id': '2',\n",
433
+ " 'perceived_colour_value_name': 'Light',\n",
434
+ " 'perceived_colour_master_name': 'Blue'}},\n",
435
+ " {'article': {'prod_name': '7p Basic Shaftless',\n",
436
+ " 'article_id': '0372860001',\n",
437
+ " 'index_code': 'B',\n",
438
+ " 'index_name': 'Lingeries/Tights',\n",
439
+ " 'section_no': '62',\n",
440
+ " 'detail_desc': 'Fine-knit trainer socks in a soft cotton blend.',\n",
441
+ " 'product_code': '0372860',\n",
442
+ " 'section_name': 'Womens Nightwear, Socks & Tigh',\n",
443
+ " 'department_no': '3611',\n",
444
+ " 'index_group_no': '1',\n",
445
+ " 'department_name': 'Shopbasket Socks',\n",
446
+ " 'product_type_no': '302',\n",
447
+ " 'garment_group_no': '1021',\n",
448
+ " 'index_group_name': 'Ladieswear',\n",
449
+ " 'colour_group_code': '09',\n",
450
+ " 'colour_group_name': 'Black',\n",
451
+ " 'product_type_name': 'Socks',\n",
452
+ " 'garment_group_name': 'Socks and Tights',\n",
453
+ " 'product_group_name': 'Socks & Tights',\n",
454
+ " 'graphical_appearance_no': '1010016',\n",
455
+ " 'graphical_appearance_name': 'Solid',\n",
456
+ " 'perceived_colour_value_id': '4',\n",
457
+ " 'perceived_colour_master_id': '5',\n",
458
+ " 'perceived_colour_value_name': 'Dark',\n",
459
+ " 'perceived_colour_master_name': 'Black'}},\n",
460
+ " {'article': {'prod_name': 'Tilly (1)',\n",
461
+ " 'article_id': '0610776002',\n",
462
+ " 'index_code': 'A',\n",
463
+ " 'index_name': 'Ladieswear',\n",
464
+ " 'section_no': '16',\n",
465
+ " 'detail_desc': 'T-shirt in lightweight jersey with a rounded hem. Slightly longer at the back.',\n",
466
+ " 'product_code': '0610776',\n",
467
+ " 'section_name': 'Womens Everyday Basics',\n",
468
+ " 'department_no': '1676',\n",
469
+ " 'index_group_no': '1',\n",
470
+ " 'department_name': 'Jersey Basic',\n",
471
+ " 'product_type_no': '255',\n",
472
+ " 'garment_group_no': '1002',\n",
473
+ " 'index_group_name': 'Ladieswear',\n",
474
+ " 'colour_group_code': '09',\n",
475
+ " 'colour_group_name': 'Black',\n",
476
+ " 'product_type_name': 'T-shirt',\n",
477
+ " 'garment_group_name': 'Jersey Basic',\n",
478
+ " 'product_group_name': 'Garment Upper body',\n",
479
+ " 'graphical_appearance_no': '1010016',\n",
480
+ " 'graphical_appearance_name': 'Solid',\n",
481
+ " 'perceived_colour_value_id': '4',\n",
482
+ " 'perceived_colour_master_id': '5',\n",
483
+ " 'perceived_colour_value_name': 'Dark',\n",
484
+ " 'perceived_colour_master_name': 'Black'}},\n",
485
+ " {'article': {'prod_name': 'Tilda tank',\n",
486
+ " 'article_id': '0759871002',\n",
487
+ " 'index_code': 'D',\n",
488
+ " 'index_name': 'Divided',\n",
489
+ " 'section_no': '80',\n",
490
+ " 'detail_desc': 'Cropped, fitted top in cotton jersey with narrow shoulder straps.',\n",
491
+ " 'product_code': '0759871',\n",
492
+ " 'section_name': 'Divided Complements Other',\n",
493
+ " 'department_no': '3936',\n",
494
+ " 'index_group_no': '2',\n",
495
+ " 'department_name': 'EQ Divided Basics',\n",
496
+ " 'product_type_no': '253',\n",
497
+ " 'garment_group_no': '1002',\n",
498
+ " 'index_group_name': 'Divided',\n",
499
+ " 'colour_group_code': '09',\n",
500
+ " 'colour_group_name': 'Black',\n",
501
+ " 'product_type_name': 'Vest top',\n",
502
+ " 'garment_group_name': 'Jersey Basic',\n",
503
+ " 'product_group_name': 'Garment Upper body',\n",
504
+ " 'graphical_appearance_no': '1010016',\n",
505
+ " 'graphical_appearance_name': 'Solid',\n",
506
+ " 'perceived_colour_value_id': '4',\n",
507
+ " 'perceived_colour_master_id': '5',\n",
508
+ " 'perceived_colour_value_name': 'Dark',\n",
509
+ " 'perceived_colour_master_name': 'Black'}},\n",
510
+ " {'article': {'prod_name': 'Greta Thong Mynta Low 3p',\n",
511
+ " 'article_id': '0464297007',\n",
512
+ " 'index_code': 'B',\n",
513
+ " 'index_name': 'Lingeries/Tights',\n",
514
+ " 'section_no': '61',\n",
515
+ " 'detail_desc': 'Thong briefs in cotton jersey with a wide lace trim at the top. Low waist, lined gusset, narrow sides and a string back.',\n",
516
+ " 'product_code': '0464297',\n",
517
+ " 'section_name': 'Womens Lingerie',\n",
518
+ " 'department_no': '1334',\n",
519
+ " 'index_group_no': '1',\n",
520
+ " 'department_name': 'Casual Lingerie',\n",
521
+ " 'product_type_no': '286',\n",
522
+ " 'garment_group_no': '1017',\n",
523
+ " 'index_group_name': 'Ladieswear',\n",
524
+ " 'colour_group_code': '09',\n",
525
+ " 'colour_group_name': 'Black',\n",
526
+ " 'product_type_name': 'Underwear bottom',\n",
527
+ " 'garment_group_name': 'Under-, Nightwear',\n",
528
+ " 'product_group_name': 'Underwear',\n",
529
+ " 'graphical_appearance_no': '1010014',\n",
530
+ " 'graphical_appearance_name': 'Placement print',\n",
531
+ " 'perceived_colour_value_id': '4',\n",
532
+ " 'perceived_colour_master_id': '5',\n",
533
+ " 'perceived_colour_value_name': 'Dark',\n",
534
+ " 'perceived_colour_master_name': 'Black'}},\n",
535
+ " {'article': {'prod_name': '7p Basic Shaftless',\n",
536
+ " 'article_id': '0372860002',\n",
537
+ " 'index_code': 'B',\n",
538
+ " 'index_name': 'Lingeries/Tights',\n",
539
+ " 'section_no': '62',\n",
540
+ " 'detail_desc': 'Fine-knit trainer socks in a soft cotton blend.',\n",
541
+ " 'product_code': '0372860',\n",
542
+ " 'section_name': 'Womens Nightwear, Socks & Tigh',\n",
543
+ " 'department_no': '3611',\n",
544
+ " 'index_group_no': '1',\n",
545
+ " 'department_name': 'Shopbasket Socks',\n",
546
+ " 'product_type_no': '302',\n",
547
+ " 'garment_group_no': '1021',\n",
548
+ " 'index_group_name': 'Ladieswear',\n",
549
+ " 'colour_group_code': '10',\n",
550
+ " 'colour_group_name': 'White',\n",
551
+ " 'product_type_name': 'Socks',\n",
552
+ " 'garment_group_name': 'Socks and Tights',\n",
553
+ " 'product_group_name': 'Socks & Tights',\n",
554
+ " 'graphical_appearance_no': '1010016',\n",
555
+ " 'graphical_appearance_name': 'Solid',\n",
556
+ " 'perceived_colour_value_id': '3',\n",
557
+ " 'perceived_colour_master_id': '9',\n",
558
+ " 'perceived_colour_value_name': 'Light',\n",
559
+ " 'perceived_colour_master_name': 'White'}},\n",
560
+ " {'article': {'prod_name': 'Tilly (1)',\n",
561
+ " 'article_id': '0610776001',\n",
562
+ " 'index_code': 'A',\n",
563
+ " 'index_name': 'Ladieswear',\n",
564
+ " 'section_no': '16',\n",
565
+ " 'detail_desc': 'T-shirt in lightweight jersey with a rounded hem. Slightly longer at the back.',\n",
566
+ " 'product_code': '0610776',\n",
567
+ " 'section_name': 'Womens Everyday Basics',\n",
568
+ " 'department_no': '1676',\n",
569
+ " 'index_group_no': '1',\n",
570
+ " 'department_name': 'Jersey Basic',\n",
571
+ " 'product_type_no': '255',\n",
572
+ " 'garment_group_no': '1002',\n",
573
+ " 'index_group_name': 'Ladieswear',\n",
574
+ " 'colour_group_code': '10',\n",
575
+ " 'colour_group_name': 'White',\n",
576
+ " 'product_type_name': 'T-shirt',\n",
577
+ " 'garment_group_name': 'Jersey Basic',\n",
578
+ " 'product_group_name': 'Garment Upper body',\n",
579
+ " 'graphical_appearance_no': '1010016',\n",
580
+ " 'graphical_appearance_name': 'Solid',\n",
581
+ " 'perceived_colour_value_id': '3',\n",
582
+ " 'perceived_colour_master_id': '9',\n",
583
+ " 'perceived_colour_value_name': 'Light',\n",
584
+ " 'perceived_colour_master_name': 'White'}},\n",
585
+ " {'article': {'prod_name': 'Curvy Jeggings HW Ankle',\n",
586
+ " 'article_id': '0399223001',\n",
587
+ " 'index_code': 'D',\n",
588
+ " 'index_name': 'Divided',\n",
589
+ " 'section_no': '57',\n",
590
+ " 'detail_desc': 'Jeggings in washed, superstretch denim with a high waist, fake front pockets, real back pockets and skinny legs.',\n",
591
+ " 'product_code': '0399223',\n",
592
+ " 'section_name': 'Ladies Denim',\n",
593
+ " 'department_no': '1772',\n",
594
+ " 'index_group_no': '2',\n",
595
+ " 'department_name': 'Denim Trousers',\n",
596
+ " 'product_type_no': '272',\n",
597
+ " 'garment_group_no': '1016',\n",
598
+ " 'index_group_name': 'Divided',\n",
599
+ " 'colour_group_code': '09',\n",
600
+ " 'colour_group_name': 'Black',\n",
601
+ " 'product_type_name': 'Trousers',\n",
602
+ " 'garment_group_name': 'Trousers Denim',\n",
603
+ " 'product_group_name': 'Garment Lower body',\n",
604
+ " 'graphical_appearance_no': '1010016',\n",
605
+ " 'graphical_appearance_name': 'Solid',\n",
606
+ " 'perceived_colour_value_id': '4',\n",
607
+ " 'perceived_colour_master_id': '5',\n",
608
+ " 'perceived_colour_value_name': 'Dark',\n",
609
+ " 'perceived_colour_master_name': 'Black'}},\n",
610
+ " {'article': {'prod_name': 'Jade HW Skinny Denim TRS',\n",
611
+ " 'article_id': '0706016003',\n",
612
+ " 'index_code': 'D',\n",
613
+ " 'index_name': 'Divided',\n",
614
+ " 'section_no': '53',\n",
615
+ " 'detail_desc': 'High-waisted jeans in washed superstretch denim with a zip fly and button, fake front pockets, real back pockets and super-skinny legs.',\n",
616
+ " 'product_code': '0706016',\n",
617
+ " 'section_name': 'Divided Collection',\n",
618
+ " 'department_no': '1747',\n",
619
+ " 'index_group_no': '2',\n",
620
+ " 'department_name': 'Trousers',\n",
621
+ " 'product_type_no': '272',\n",
622
+ " 'garment_group_no': '1009',\n",
623
+ " 'index_group_name': 'Divided',\n",
624
+ " 'colour_group_code': '73',\n",
625
+ " 'colour_group_name': 'Dark Blue',\n",
626
+ " 'product_type_name': 'Trousers',\n",
627
+ " 'garment_group_name': 'Trousers',\n",
628
+ " 'product_group_name': 'Garment Lower body',\n",
629
+ " 'graphical_appearance_no': '1010016',\n",
630
+ " 'graphical_appearance_name': 'Solid',\n",
631
+ " 'perceived_colour_value_id': '2',\n",
632
+ " 'perceived_colour_master_id': '2',\n",
633
+ " 'perceived_colour_value_name': 'Medium Dusty',\n",
634
+ " 'perceived_colour_master_name': 'Blue'}},\n",
635
+ " {'article': {'prod_name': 'SUPREME RW tights',\n",
636
+ " 'article_id': '0720125001',\n",
637
+ " 'index_code': 'S',\n",
638
+ " 'index_name': 'Sport',\n",
639
+ " 'section_no': '5',\n",
640
+ " 'detail_desc': 'Sports tights in fast-drying functional fabric with a wide waistband to hold in and shape the waist. Regular waist with a concealed key pocket in the waistband.',\n",
641
+ " 'product_code': '0720125',\n",
642
+ " 'section_name': 'Ladies H&M Sport',\n",
643
+ " 'department_no': '8310',\n",
644
+ " 'index_group_no': '26',\n",
645
+ " 'department_name': 'Ladies Sport Bottoms',\n",
646
+ " 'product_type_no': '273',\n",
647
+ " 'garment_group_no': '1005',\n",
648
+ " 'index_group_name': 'Sport',\n",
649
+ " 'colour_group_code': '09',\n",
650
+ " 'colour_group_name': 'Black',\n",
651
+ " 'product_type_name': 'Leggings/Tights',\n",
652
+ " 'garment_group_name': 'Jersey Fancy',\n",
653
+ " 'product_group_name': 'Garment Lower body',\n",
654
+ " 'graphical_appearance_no': '1010016',\n",
655
+ " 'graphical_appearance_name': 'Solid',\n",
656
+ " 'perceived_colour_value_id': '4',\n",
657
+ " 'perceived_colour_master_id': '5',\n",
658
+ " 'perceived_colour_value_name': 'Dark',\n",
659
+ " 'perceived_colour_master_name': 'Black'}},\n",
660
+ " {'article': {'prod_name': 'Box 4p Tights',\n",
661
+ " 'article_id': '0156231001',\n",
662
+ " 'index_code': 'B',\n",
663
+ " 'index_name': 'Lingeries/Tights',\n",
664
+ " 'section_no': '62',\n",
665
+ " 'detail_desc': 'Matt tights with an elasticated waist. 20 denier.',\n",
666
+ " 'product_code': '0156231',\n",
667
+ " 'section_name': 'Womens Nightwear, Socks & Tigh',\n",
668
+ " 'department_no': '3608',\n",
669
+ " 'index_group_no': '1',\n",
670
+ " 'department_name': 'Tights basic',\n",
671
+ " 'product_type_no': '304',\n",
672
+ " 'garment_group_no': '1021',\n",
673
+ " 'index_group_name': 'Ladieswear',\n",
674
+ " 'colour_group_code': '09',\n",
675
+ " 'colour_group_name': 'Black',\n",
676
+ " 'product_type_name': 'Underwear Tights',\n",
677
+ " 'garment_group_name': 'Socks and Tights',\n",
678
+ " 'product_group_name': 'Socks & Tights',\n",
679
+ " 'graphical_appearance_no': '1010016',\n",
680
+ " 'graphical_appearance_name': 'Solid',\n",
681
+ " 'perceived_colour_value_id': '4',\n",
682
+ " 'perceived_colour_master_id': '5',\n",
683
+ " 'perceived_colour_value_name': 'Dark',\n",
684
+ " 'perceived_colour_master_name': 'Black'}}]}]"
685
+ ]
686
+ },
687
+ "execution_count": 72,
688
+ "metadata": {},
689
+ "output_type": "execute_result"
690
+ }
691
+ ],
692
+ "source": [
693
+ "a"
694
+ ]
695
+ }
696
+ ],
697
+ "metadata": {
698
+ "kernelspec": {
699
+ "display_name": "apis",
700
+ "language": "python",
701
+ "name": "python3"
702
+ },
703
+ "language_info": {
704
+ "codemirror_mode": {
705
+ "name": "ipython",
706
+ "version": 3
707
+ },
708
+ "file_extension": ".py",
709
+ "mimetype": "text/x-python",
710
+ "name": "python",
711
+ "nbconvert_exporter": "python",
712
+ "pygments_lexer": "ipython3",
713
+ "version": "3.11.10"
714
+ }
715
+ },
716
+ "nbformat": 4,
717
+ "nbformat_minor": 2
718
+ }