Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
A
ai company
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Vũ Hoàng Anh
ai company
Commits
ad443b44
Commit
ad443b44
authored
Apr 23, 2026
by
Vũ Hoàng Anh
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: refactor AI Tagging to output semantic natural language tags and fix unicode logging
parent
f49217d8
Changes
5
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
5 changed files
with
936 additions
and
400 deletions
+936
-400
bulk_ops_route.py
backend/api/product_desc/bulk_ops_route.py
+14
-13
tags_direct_route.py
backend/api/product_desc/tags_direct_route.py
+369
-0
ultra_desc_db.py
backend/common/ultra_desc_db.py
+62
-53
extract_seasons.py
backend/database/starrocks/extract_seasons.py
+36
-0
product-desc.html
backend/static/product-desc/product-desc.html
+455
-334
No files found.
backend/api/product_desc/bulk_ops_route.py
View file @
ad443b44
...
@@ -285,18 +285,19 @@ CANIFA_LINES = [
...
@@ -285,18 +285,19 @@ CANIFA_LINES = [
]
]
# Attempt to dynamically fetch product lines from DB to override the static list
# Attempt to dynamically fetch product lines from DB to override the static list
try
:
# Removed dynamic loading on import to prevent blocking thread when DB is slow
_conn
=
get_pooled_connection_compat
()
# try:
_cur
=
_conn
.
cursor
()
# _conn = get_pooled_connection_compat()
_cur
.
execute
(
f
"SELECT DISTINCT product_line FROM {PG_TABLE} WHERE product_line IS NOT NULL AND product_line != ''"
)
# _cur = _conn.cursor()
_db_lines
=
[
r
[
0
]
for
r
in
_cur
.
fetchall
()]
# _cur.execute(f"SELECT DISTINCT product_line FROM {PG_TABLE} WHERE product_line IS NOT NULL AND product_line != ''")
_cur
.
close
()
# _db_lines = [r[0] for r in _cur.fetchall()]
_conn
.
close
()
# _cur.close()
if
_db_lines
:
# _conn.close()
CANIFA_LINES
=
_db_lines
# if _db_lines:
logger
.
info
(
f
"✅ Loaded {len(CANIFA_LINES)} product lines dynamically from DB"
)
# CANIFA_LINES = _db_lines
except
Exception
as
e
:
# logger.info(f"✅ Loaded {len(CANIFA_LINES)} product lines dynamically from DB")
logger
.
warning
(
f
"⚠️ Could not load product lines from DB, using fallback. Error: {e}"
)
# except Exception as e:
# logger.warning(f"⚠️ Could not load product lines from DB, using fallback. Error: {e}")
AI_SEARCH_SYSTEM
=
"""Bạn là AI phân tích ý định tìm kiếm sản phẩm thời trang Canifa.
AI_SEARCH_SYSTEM
=
"""Bạn là AI phân tích ý định tìm kiếm sản phẩm thời trang Canifa.
...
@@ -945,7 +946,7 @@ async def batch_generate_tags():
...
@@ -945,7 +946,7 @@ async def batch_generate_tags():
conn
=
get_pooled_connection_compat
()
conn
=
get_pooled_connection_compat
()
cur
=
conn
.
cursor
()
cur
=
conn
.
cursor
()
# Find up to 500 products that either don't have the tags column populated or it's empty
# Find up to 500 products that either don't have the tags column populated or it's empty
cur
.
execute
(
f
"SELECT internal_ref_code FROM {PG_TABLE} WHERE tags IS NULL
OR tags = '[]'::jsonb
LIMIT 500"
)
cur
.
execute
(
f
"SELECT internal_ref_code FROM {PG_TABLE} WHERE tags IS NULL LIMIT 500"
)
rows
=
cur
.
fetchall
()
rows
=
cur
.
fetchall
()
if
not
rows
:
if
not
rows
:
...
...
backend/api/product_desc/tags_direct_route.py
0 → 100644
View file @
ad443b44
This diff is collapsed.
Click to expand it.
backend/common/ultra_desc_db.py
View file @
ad443b44
...
@@ -28,52 +28,61 @@ class UltraDescriptionDB:
...
@@ -28,52 +28,61 @@ class UltraDescriptionDB:
"""Create ultra_descriptions table if it doesn't exist."""
"""Create ultra_descriptions table if it doesn't exist."""
if
cls
.
_initialized
:
if
cls
.
_initialized
:
return
return
from
config
import
USE_LOCAL_SQLITE
conn
=
None
conn
=
None
try
:
try
:
conn
=
get_pooled_connection_compat
()
conn
=
get_pooled_connection_compat
()
cur
=
conn
.
cursor
()
cur
=
conn
.
cursor
()
cur
.
execute
(
f
"""
CREATE TABLE IF NOT EXISTS {TABLE} (
if
USE_LOCAL_SQLITE
:
id SERIAL PRIMARY KEY,
# SQLite: Create full table at once with all columns
internal_ref_code VARCHAR(50) NOT NULL,
cur
.
execute
(
f
"""
product_name VARCHAR(500),
CREATE TABLE IF NOT EXISTS {TABLE} (
product_image_url TEXT,
id INTEGER PRIMARY KEY AUTOINCREMENT,
product_line VARCHAR(200),
internal_ref_code TEXT NOT NULL,
description_data JSONB NOT NULL,
magento_ref_code TEXT,
phase VARCHAR(20) DEFAULT 'enriched',
base_ref_code TEXT,
status SMALLINT DEFAULT 0,
product_name TEXT,
created_at TIMESTAMPTZ DEFAULT NOW(),
product_image_url TEXT,
updated_at TIMESTAMPTZ DEFAULT NOW()
product_line TEXT,
);
description_data TEXT NOT NULL,
CREATE INDEX IF NOT EXISTS idx_ultra_desc_ref_code
phase TEXT DEFAULT 'enriched',
ON {TABLE}(internal_ref_code);
status INTEGER DEFAULT 0,
-- Migration: add columns if table already existed
clean_description TEXT DEFAULT '',
ALTER TABLE {TABLE} ADD COLUMN IF NOT EXISTS status SMALLINT DEFAULT 0;
tags TEXT DEFAULT '[]',
ALTER TABLE {TABLE} ADD COLUMN IF NOT EXISTS clean_description TEXT DEFAULT '';
ai_matches TEXT,
ALTER TABLE {TABLE} ADD COLUMN IF NOT EXISTS tags JSONB DEFAULT '[]'::jsonb;
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-- Migration v2: magento_ref_code support
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
ALTER TABLE {TABLE} ADD COLUMN IF NOT EXISTS magento_ref_code VARCHAR(100);
);
ALTER TABLE {TABLE} ADD COLUMN IF NOT EXISTS base_ref_code VARCHAR(50);
"""
)
"""
)
else
:
# Drop old unique constraint on internal_ref_code (v2 migration: allow multiple colors per style)
# PostgreSQL: Original logic
try
:
cur
.
execute
(
f
"""
cur
.
execute
(
f
"ALTER TABLE {TABLE} DROP CONSTRAINT IF EXISTS ultra_descriptions_internal_ref_code_key"
)
CREATE TABLE IF NOT EXISTS {TABLE} (
except
Exception
:
id SERIAL PRIMARY KEY,
pass
internal_ref_code VARCHAR(50) NOT NULL,
# Non-unique index on internal_ref_code (for grouping)
product_name VARCHAR(500),
try
:
product_image_url TEXT,
cur
.
execute
(
f
"CREATE INDEX IF NOT EXISTS idx_ultra_desc_internal_code ON {TABLE}(internal_ref_code)"
)
product_line VARCHAR(200),
except
Exception
:
description_data JSONB NOT NULL,
pass
phase VARCHAR(20) DEFAULT 'enriched',
# Unique index on magento_ref_code (partial, ignores NULLs)
status SMALLINT DEFAULT 0,
try
:
created_at TIMESTAMPTZ DEFAULT NOW(),
cur
.
execute
(
f
"""CREATE UNIQUE INDEX IF NOT EXISTS idx_ultra_desc_magento_code
updated_at TIMESTAMPTZ DEFAULT NOW()
ON {TABLE}(magento_ref_code) WHERE magento_ref_code IS NOT NULL;"""
)
);
except
Exception
:
ALTER TABLE {TABLE} ADD COLUMN IF NOT EXISTS status SMALLINT DEFAULT 0;
pass
# already exists
ALTER TABLE {TABLE} ADD COLUMN IF NOT EXISTS clean_description TEXT DEFAULT '';
ALTER TABLE {TABLE} ADD COLUMN IF NOT EXISTS tags JSONB DEFAULT '[]'::jsonb;
ALTER TABLE {TABLE} ADD COLUMN IF NOT EXISTS magento_ref_code VARCHAR(100);
ALTER TABLE {TABLE} ADD COLUMN IF NOT EXISTS base_ref_code VARCHAR(50);
ALTER TABLE {TABLE} ADD COLUMN IF NOT EXISTS ai_matches JSONB;
"""
)
cur
.
close
()
cur
.
close
()
conn
.
commit
()
cls
.
_initialized
=
True
cls
.
_initialized
=
True
logger
.
info
(
"✅ Table
%
s ready
"
,
TABL
E
)
logger
.
info
(
"✅ Table
%
s ready
(Mock:
%
s)"
,
TABLE
,
USE_LOCAL_SQLIT
E
)
except
Exception
as
e
:
except
Exception
as
e
:
logger
.
error
(
"Error creating ultra_descriptions table:
%
s"
,
e
)
logger
.
error
(
"Error creating ultra_descriptions table:
%
s"
,
e
)
finally
:
finally
:
...
@@ -134,9 +143,9 @@ class UltraDescriptionDB:
...
@@ -134,9 +143,9 @@ class UltraDescriptionDB:
json
.
dumps
(
description_data
,
ensure_ascii
=
False
),
phase
,
clean_description
),
json
.
dumps
(
description_data
,
ensure_ascii
=
False
),
phase
,
clean_description
),
)
)
row
=
cur
.
fetchone
()
row
=
cur
.
fetchone
()
cur
.
close
()
row_id
=
row
[
0
]
if
row
else
None
row_id
=
row
[
0
]
if
row
else
None
conn
.
commit
()
# critical: without this INSERT/UPDATE rolls back on conn.close()
cur
.
close
()
# Close cursor FIRST
conn
.
commit
()
logger
.
info
(
"💾 Saved ultra desc:
%
s / magento=
%
s (id=
%
s)"
,
internal_ref_code
,
magento_ref_code
,
row_id
)
logger
.
info
(
"💾 Saved ultra desc:
%
s / magento=
%
s (id=
%
s)"
,
internal_ref_code
,
magento_ref_code
,
row_id
)
return
row_id
return
row_id
except
Exception
as
e
:
except
Exception
as
e
:
...
@@ -565,7 +574,7 @@ class UltraDescriptionDB:
...
@@ -565,7 +574,7 @@ class UltraDescriptionDB:
try
:
try
:
conn
=
get_pooled_connection_compat
()
conn
=
get_pooled_connection_compat
()
cur
=
conn
.
cursor
()
cur
=
conn
.
cursor
()
cur
.
execute
(
f
"SELECT COUNT(*) FROM {TABLE} WHERE tags IS NULL
OR tags = '[]'::jsonb
"
)
cur
.
execute
(
f
"SELECT COUNT(*) FROM {TABLE} WHERE tags IS NULL"
)
count
=
cur
.
fetchone
()[
0
]
count
=
cur
.
fetchone
()[
0
]
cur
.
close
()
cur
.
close
()
return
count
return
count
...
@@ -618,11 +627,11 @@ class UltraDescriptionDB:
...
@@ -618,11 +627,11 @@ class UltraDescriptionDB:
conn
.
close
()
conn
.
close
()
#
Auto-init table on import
#
Removed auto-init on import to prevent blocking thread when DB is slow
try
:
#
try:
UltraDescriptionDB
.
ensure_table
()
#
UltraDescriptionDB.ensure_table()
except
Exception
:
#
except Exception:
pass
#
pass
# ═══════════════════════════════════════════════════════════════
# ═══════════════════════════════════════════════════════════════
...
@@ -848,8 +857,8 @@ class DescFieldConfig:
...
@@ -848,8 +857,8 @@ class DescFieldConfig:
conn
.
close
()
conn
.
close
()
#
Auto-init field config table
#
Removed auto-init on import to prevent blocking thread when DB is slow
try
:
#
try:
DescFieldConfig
.
ensure_table
()
#
DescFieldConfig.ensure_table()
except
Exception
:
#
except Exception:
pass
#
pass
backend/database/starrocks/extract_seasons.py
0 → 100644
View file @
ad443b44
import
re
from
collections
import
Counter
with
open
(
'test_db.magento_product_dimension_with_text_embedding.sql'
,
encoding
=
'utf-8'
,
errors
=
'ignore'
)
as
f
:
data
=
f
.
read
()
# Find the column order from INSERT statement
col_match
=
re
.
search
(
r'INSERT INTO[^(]+\(([^)]+)\)'
,
data
,
re
.
DOTALL
)
if
col_match
:
cols
=
[
c
.
strip
()
.
strip
(
'`'
)
for
c
in
col_match
.
group
(
1
)
.
split
(
','
)]
season_idx
=
cols
.
index
(
'season'
)
if
'season'
in
cols
else
-
1
print
(
f
"Column order found. 'season' is at index: {season_idx}"
)
print
(
f
"Columns around season: {cols[max(0,season_idx-2):season_idx+3]}"
)
else
:
print
(
"Could not find INSERT column list"
)
season_idx
=
-
1
# Extract VALUES rows - find all value tuples
# Look for patterns like ('...', '...', NULL, ...)
rows
=
re
.
findall
(
r'\(([^;]+?)\)(?:,|\s*;)'
,
data
,
re
.
DOTALL
)
print
(
f
"
\n
Total value rows found: {len(rows)}"
)
if
season_idx
>=
0
and
rows
:
seasons
=
[]
for
row
in
rows
[:
50
]:
# sample first 50
# Split by comma but respect quoted strings
parts
=
re
.
split
(
r",(?=(?:[^']*'[^']*')*[^']*$)"
,
row
.
strip
())
if
len
(
parts
)
>
season_idx
:
val
=
parts
[
season_idx
]
.
strip
()
.
strip
(
"'"
)
seasons
.
append
(
val
)
counts
=
Counter
(
seasons
)
print
(
f
"
\n
Season values (sample from first 50 rows):"
)
print
(
"-"
*
40
)
for
s
,
c
in
sorted
(
counts
.
items
(),
key
=
lambda
x
:
-
x
[
1
]):
print
(
f
"{c:5d} | '{s}'"
)
backend/static/product-desc/product-desc.html
View file @
ad443b44
This diff is collapsed.
Click to expand it.
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment