fix
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
||||
"strings"
|
||||
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/billing"
|
||||
"github.com/descrybe/descrybe-v2/apps/api/internal/processing"
|
||||
"github.com/google/uuid"
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
@@ -34,30 +35,9 @@ type categoryBackfillResult struct {
|
||||
}
|
||||
|
||||
// backfillMappedCategoriesFromProcessed copies processed_products.category into
|
||||
// raw_products.mapped_data.category for A1 only. Legacy dumps store category on
|
||||
// processed rows (unique_id codes); feed mappings never mapped a category field,
|
||||
// so re-processing without this backfill yields Uncategorized / grey C coverage.
|
||||
// raw_products.mapped_data.category for A1 only. Delegates to processing.
|
||||
func backfillMappedCategoriesFromProcessed(ctx context.Context, pg *pgxpool.Pool, companyID uuid.UUID) (int64, error) {
|
||||
ct, err := pg.Exec(ctx, `
|
||||
UPDATE raw_products r
|
||||
SET mapped_data = jsonb_set(
|
||||
COALESCE(r.mapped_data, '{}'::jsonb),
|
||||
'{category}',
|
||||
to_jsonb(p.category),
|
||||
true
|
||||
),
|
||||
updated_at = now()
|
||||
FROM processed_products p
|
||||
WHERE p.raw_product_id = r.id
|
||||
AND p.company_id = $1
|
||||
AND r.company_id = $1
|
||||
AND COALESCE(NULLIF(trim(p.category), ''), '') <> ''
|
||||
AND lower(trim(p.category)) <> 'none'
|
||||
AND COALESCE(NULLIF(trim(r.mapped_data->>'category'), ''), '') = ''`, companyID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("backfill mapped category: %w", err)
|
||||
}
|
||||
return ct.RowsAffected(), nil
|
||||
return processing.BackfillMappedCategoriesFromProcessed(ctx, pg, companyID)
|
||||
}
|
||||
|
||||
// backfillCategoriesFromMySQLDump streams dump processed_products for the A1
|
||||
|
||||
@@ -358,6 +358,15 @@ func cloneCompanyCatalog(ctx context.Context, pool *pgxpool.Pool, src, dest uuid
|
||||
}
|
||||
add("raw_products", ct.RowsAffected())
|
||||
|
||||
var mappedWithCat int64
|
||||
if err := tx.QueryRow(ctx, `
|
||||
SELECT COUNT(*) FROM raw_products
|
||||
WHERE company_id = $1
|
||||
AND COALESCE(NULLIF(trim(mapped_data->>'category'), ''), '') <> ''`, dest).Scan(&mappedWithCat); err != nil {
|
||||
return nil, fmt.Errorf("count mapped categories: %w", err)
|
||||
}
|
||||
add("mapped_with_category", mappedWithCat)
|
||||
|
||||
ct, err = tx.Exec(ctx, `
|
||||
INSERT INTO processed_products (
|
||||
id, company_id, user_id, product_id, name, category, description, processed_description,
|
||||
|
||||
@@ -92,6 +92,67 @@ func BackfillProcessedCategoriesFromMapped(ctx context.Context, pool *pgxpool.Po
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// BackfillMappedCategoriesFromProcessed copies processed_products.category into
|
||||
// raw_products.mapped_data.category when mapped category is empty. Legacy A1 dumps
|
||||
// often store category only on processed rows; feed mappings do not map category,
|
||||
// so Fix Catalog / re-seed must push processed → mapped for Products UI coverage.
|
||||
// Does not invent categories.
|
||||
func BackfillMappedCategoriesFromProcessed(ctx context.Context, pool *pgxpool.Pool, companyID uuid.UUID) (int64, error) {
|
||||
if pool == nil {
|
||||
return 0, fmt.Errorf("mapped category backfill: nil pool")
|
||||
}
|
||||
if companyID == uuid.Nil {
|
||||
return 0, fmt.Errorf("mapped category backfill: empty company id")
|
||||
}
|
||||
ct, err := pool.Exec(ctx, `
|
||||
UPDATE raw_products r
|
||||
SET mapped_data = jsonb_set(
|
||||
COALESCE(r.mapped_data, '{}'::jsonb),
|
||||
'{category}',
|
||||
to_jsonb(p.category),
|
||||
true
|
||||
),
|
||||
updated_at = now()
|
||||
FROM processed_products p
|
||||
WHERE p.raw_product_id = r.id
|
||||
AND p.company_id = $1
|
||||
AND r.company_id = $1
|
||||
AND COALESCE(NULLIF(trim(p.category), ''), '') <> ''
|
||||
AND lower(trim(p.category)) <> 'none'
|
||||
AND COALESCE(NULLIF(trim(r.mapped_data->>'category'), ''), '') = ''`, companyID)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("mapped category backfill: %w", err)
|
||||
}
|
||||
return ct.RowsAffected(), nil
|
||||
}
|
||||
|
||||
// CountMappedCategoryCoverage returns how many raw_products rows have a non-empty
|
||||
// mapped_data.category vs empty, plus taxonomy row count for the company.
|
||||
func CountMappedCategoryCoverage(ctx context.Context, pool *pgxpool.Pool, companyID uuid.UUID) (withCat, withoutCat, taxonomy int64, err error) {
|
||||
if pool == nil {
|
||||
return 0, 0, 0, fmt.Errorf("mapped category coverage: nil pool")
|
||||
}
|
||||
err = pool.QueryRow(ctx, `
|
||||
SELECT
|
||||
COUNT(*) FILTER (
|
||||
WHERE COALESCE(NULLIF(trim(mapped_data->>'category'), ''), '') <> ''
|
||||
),
|
||||
COUNT(*) FILTER (
|
||||
WHERE COALESCE(NULLIF(trim(mapped_data->>'category'), ''), '') = ''
|
||||
)
|
||||
FROM raw_products
|
||||
WHERE company_id = $1`, companyID).Scan(&withCat, &withoutCat)
|
||||
if err != nil {
|
||||
return 0, 0, 0, fmt.Errorf("mapped category coverage: %w", err)
|
||||
}
|
||||
err = pool.QueryRow(ctx, `
|
||||
SELECT COUNT(*) FROM categories WHERE company_id = $1`, companyID).Scan(&taxonomy)
|
||||
if err != nil {
|
||||
return withCat, withoutCat, 0, fmt.Errorf("taxonomy count: %w", err)
|
||||
}
|
||||
return withCat, withoutCat, taxonomy, nil
|
||||
}
|
||||
|
||||
// CountProcessedCategoryCoverage returns how many processed rows for companyID have
|
||||
// a non-empty category vs empty/"none".
|
||||
func CountProcessedCategoryCoverage(ctx context.Context, pool *pgxpool.Pool, companyID uuid.UUID) (withCat, withoutCat int64, err error) {
|
||||
|
||||
@@ -470,3 +470,82 @@ func TestBackfillProcessedCategoriesFromMapped(t *testing.T) {
|
||||
t.Fatalf("coverage with=%d without=%d", withCat, withoutCat)
|
||||
}
|
||||
}
|
||||
|
||||
func TestBackfillMappedCategoriesFromProcessed(t *testing.T) {
|
||||
dsn := os.Getenv("DATABASE_URL")
|
||||
if dsn == "" {
|
||||
t.Skip("DATABASE_URL not set")
|
||||
}
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
pg, err := pgxpool.New(ctx, dsn)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer pg.Close()
|
||||
|
||||
companyID := uuid.New()
|
||||
rawID := uuid.New()
|
||||
ppID := uuid.New()
|
||||
gtin := "map-bf-" + companyID.String()[:8]
|
||||
|
||||
if _, err := pg.Exec(ctx, `INSERT INTO companies (id, name) VALUES ($1, $2)`, companyID, "mapped-backfill-test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
_, _ = pg.Exec(context.Background(), `DELETE FROM processed_products WHERE company_id = $1`, companyID)
|
||||
_, _ = pg.Exec(context.Background(), `DELETE FROM raw_products WHERE company_id = $1`, companyID)
|
||||
_, _ = pg.Exec(context.Background(), `DELETE FROM companies WHERE id = $1`, companyID)
|
||||
}()
|
||||
|
||||
if _, err := pg.Exec(ctx, `
|
||||
INSERT INTO raw_products (id, company_id, gtin, raw_data, mapped_data, is_processed, processing_status)
|
||||
VALUES ($1, $2, $3, '{}'::jsonb, '{"name":"Widget"}'::jsonb, true, 'processed')`,
|
||||
rawID, companyID, gtin); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := pg.Exec(ctx, `
|
||||
INSERT INTO processed_products (
|
||||
id, company_id, product_id, name, category, description, processed_name, processed_description,
|
||||
raw_product_id, status, attributes, processed_attributes, field_sources
|
||||
) VALUES (
|
||||
$1, $2, $3, 'Widget', '42', 'x', 'Widget', 'x',
|
||||
$4, 'needs_review', '{}'::jsonb, '{}'::jsonb, '{}'::jsonb
|
||||
)`, ppID, companyID, gtin, rawID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
n, err := BackfillMappedCategoriesFromProcessed(ctx, pg, companyID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n != 1 {
|
||||
t.Fatalf("updated=%d want 1", n)
|
||||
}
|
||||
var mappedCat string
|
||||
if err := pg.QueryRow(ctx, `
|
||||
SELECT COALESCE(mapped_data->>'category', '') FROM raw_products WHERE id = $1`, rawID).Scan(&mappedCat); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if mappedCat != "42" {
|
||||
t.Fatalf("mapped category=%q want 42", mappedCat)
|
||||
}
|
||||
|
||||
n2, err := BackfillMappedCategoriesFromProcessed(ctx, pg, companyID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n2 != 0 {
|
||||
t.Fatalf("second backfill updated=%d want 0", n2)
|
||||
}
|
||||
|
||||
withCat, withoutCat, taxonomy, err := CountMappedCategoryCoverage(ctx, pg, companyID)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if withCat != 1 || withoutCat != 0 || taxonomy != 0 {
|
||||
t.Fatalf("mapped coverage with=%d without=%d taxonomy=%d", withCat, withoutCat, taxonomy)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,9 +41,19 @@ type FixCompanyCatalogResult struct {
|
||||
|
||||
WeakHashesCleared int `json:"weak_hashes_cleared"`
|
||||
CategoriesBackfilled int `json:"categories_backfilled"`
|
||||
// MappedCategoriesBackfilled is processed → mapped_data.category copies.
|
||||
MappedCategoriesBackfilled int `json:"mapped_categories_backfilled"`
|
||||
// Coverage after Fix (Products UI reads mapped_data.category for raw rows).
|
||||
TaxonomyCategories int64 `json:"taxonomy_categories"`
|
||||
MappedWithCategory int64 `json:"mapped_with_category"`
|
||||
MappedWithoutCategory int64 `json:"mapped_without_category"`
|
||||
ProcessedWithCategory int64 `json:"processed_with_category"`
|
||||
ProcessedWithoutCategory int64 `json:"processed_without_category"`
|
||||
// Hashes / Categories alias the long names for flash.admin.fixA1Success.
|
||||
Hashes int `json:"hashes"`
|
||||
Categories int `json:"categories"`
|
||||
// MappedBackfilled aliases mapped_categories_backfilled for flash {mapped_backfilled}.
|
||||
MappedBackfilled int `json:"mapped_backfilled"`
|
||||
|
||||
DescriptionsNormalized int `json:"descriptions_normalized"`
|
||||
DescriptionsBackfilled int `json:"descriptions_backfilled"`
|
||||
@@ -106,6 +116,14 @@ func FixCompanyCatalog(ctx context.Context, pool *pgxpool.Pool, companyID uuid.U
|
||||
out.MetaBackfilled = hygiene.MetaBackfilled
|
||||
out.ProductsScanned = hygiene.ProductsScanned
|
||||
|
||||
if opts.BackfillCategories {
|
||||
mappedN, err := BackfillMappedCategoriesFromProcessed(ctx, pool, companyID)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("mapped category backfill: %w", err)
|
||||
}
|
||||
out.MappedCategoriesBackfilled = int(mappedN)
|
||||
}
|
||||
|
||||
sanitized, err := BackfillCompanyProcessedAttributes(ctx, pool, companyID)
|
||||
if err != nil {
|
||||
return out, fmt.Errorf("sanitize attributes: %w", err)
|
||||
@@ -119,9 +137,24 @@ func FixCompanyCatalog(ctx context.Context, pool *pgxpool.Pool, companyID uuid.U
|
||||
out.ReprocessNeededCount = needed
|
||||
out.ReprocessSampleRawProductIDs = sample
|
||||
|
||||
// Short keys for flash.admin.fixA1Success {prompts,hashes,categories}.
|
||||
mWith, mWithout, taxonomy, err := CountMappedCategoryCoverage(ctx, pool, companyID)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.MappedWithCategory = mWith
|
||||
out.MappedWithoutCategory = mWithout
|
||||
out.TaxonomyCategories = taxonomy
|
||||
pWith, pWithout, err := CountProcessedCategoryCoverage(ctx, pool, companyID)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
out.ProcessedWithCategory = pWith
|
||||
out.ProcessedWithoutCategory = pWithout
|
||||
|
||||
// Short keys for flash.admin.fixA1Success {prompts,hashes,categories,mapped_backfilled}.
|
||||
out.Prompts = out.CategoryPromptsUpdated
|
||||
out.Hashes = out.WeakHashesCleared
|
||||
out.Categories = out.CategoriesBackfilled
|
||||
out.MappedBackfilled = out.MappedCategoriesBackfilled
|
||||
return out, nil
|
||||
}
|
||||
|
||||
@@ -598,6 +598,7 @@ func (p *Pipeline) ProcessJob(ctx context.Context, jobID uuid.UUID) error {
|
||||
return err
|
||||
}
|
||||
if !IsProcessableJobStatus(status) {
|
||||
log.Printf("processing: skip job=%s status=%s reason=not_processable", jobID, status)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -625,6 +626,9 @@ func (p *Pipeline) ProcessJob(ctx context.Context, jobID uuid.UUID) error {
|
||||
modeLabel = label
|
||||
}
|
||||
|
||||
log.Printf("processing: start job=%s company=%s type=%s mode=%s prior_processed=%d",
|
||||
jobID, companyID, processingType, modeLabel, alreadyProcessed)
|
||||
|
||||
progress := InitialStepProgress(processingType)
|
||||
if len(progress) > 0 {
|
||||
progress[0].Status = "running"
|
||||
@@ -648,6 +652,11 @@ func (p *Pipeline) ProcessJob(ctx context.Context, jobID uuid.UUID) error {
|
||||
batch := resolveBatchSize(p.BatchSize)
|
||||
progressEvery := resolveProgressEvery(p.ProgressEvery)
|
||||
jobCache := p.loadJobScopedCache(ctx, companyID, jobID)
|
||||
if !jobCache.stepPolicy().AllowAI {
|
||||
log.Printf("processing: ai_skip job=%s reason=entitlement_can_use_ai", jobID)
|
||||
} else if !jobEngine.CompleterEnabled() {
|
||||
log.Printf("processing: ai_skip job=%s reason=openai_not_configured", jobID)
|
||||
}
|
||||
processed := alreadyProcessed
|
||||
failed := 0
|
||||
tokenTotal := 0
|
||||
@@ -672,6 +681,7 @@ func (p *Pipeline) ProcessJob(ctx context.Context, jobID uuid.UUID) error {
|
||||
flushProgress(true)
|
||||
if errors.Is(err, errJobCancelled) {
|
||||
_ = p.reclaimOrphanedProcessingItems(ctx, jobID)
|
||||
log.Printf("processing: cancelled job=%s", jobID)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("processing: check cancel job=%s: %w", jobID, err)
|
||||
@@ -728,6 +738,7 @@ func (p *Pipeline) ProcessJob(ctx context.Context, jobID uuid.UUID) error {
|
||||
flushProgress(true)
|
||||
if errors.Is(err, errJobCancelled) {
|
||||
_ = p.reclaimOrphanedProcessingItems(ctx, jobID)
|
||||
log.Printf("processing: cancelled job=%s", jobID)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("processing: check cancel job=%s: %w", jobID, err)
|
||||
@@ -807,7 +818,8 @@ func (p *Pipeline) ProcessJob(ctx context.Context, jobID uuid.UUID) error {
|
||||
current_step = $6, step_progress = $7::jsonb,
|
||||
ai_provider_mode = $8
|
||||
WHERE id = $1 AND status = 'running'`, jobID, finalStatus, processed, errMsg, tokenTotal, finalStep, finalJSON, finalMode)
|
||||
log.Printf("processing: finished job=%s status=%s processed=%d failed=%d mode=%s", jobID, finalStatus, processed, failed, finalMode)
|
||||
log.Printf("processing: finished job=%s status=%s processed=%d failed=%d mode=%s event=%s",
|
||||
jobID, finalStatus, processed, failed, finalMode, finalStatus)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1556,7 +1568,12 @@ func (p *Pipeline) processOne(ctx context.Context, companyID, jobID uuid.UUID, i
|
||||
// name tokens are not wiped. Empty taxonomy set skips filtering (unit tests).
|
||||
if cache != nil {
|
||||
coerceCategoryToCompanyUniqueID(&result, cache.categoryNamesByUID, cache.categoryUniqueIDs)
|
||||
catPreFilter := strings.TrimSpace(result.Category)
|
||||
filterCategoryIfInvalid(&result, cache.categoryUniqueIDs)
|
||||
if catPreFilter != "" && strings.TrimSpace(result.Category) == "" {
|
||||
log.Printf("processing: category_empty job=%s raw=%s rejected=%s reason=unknown_unique_id",
|
||||
jobID, it.RawID, catPreFilter)
|
||||
}
|
||||
syncCategoryName(&result, cache.categoryNamesByUID)
|
||||
}
|
||||
// Re-apply after category validation so allowlist matches the persisted category.
|
||||
|
||||
@@ -246,6 +246,14 @@ export type FixCatalogResult = {
|
||||
/** Alias of categories_backfilled for flash {categories}. */
|
||||
categories?: number;
|
||||
categories_backfilled?: number;
|
||||
/** Alias of mapped_categories_backfilled for flash {mapped_backfilled}. */
|
||||
mapped_backfilled?: number;
|
||||
mapped_categories_backfilled?: number;
|
||||
taxonomy_categories?: number;
|
||||
mapped_with_category?: number;
|
||||
mapped_without_category?: number;
|
||||
processed_with_category?: number;
|
||||
processed_without_category?: number;
|
||||
attributes_sanitized?: number;
|
||||
products_scanned?: number;
|
||||
reprocess_needed_count?: number;
|
||||
|
||||
+1912
-1911
File diff suppressed because it is too large
Load Diff
@@ -842,9 +842,9 @@ export const en: MessageDict = {
|
||||
"admin.users.fixA1": "Fix A1 catalog",
|
||||
"admin.users.fixA1Aria": "Repair AI prompts, categories, and enhance hashes for {name}",
|
||||
"admin.users.fixA1Title": "Fix company catalog",
|
||||
"admin.users.fixA1Desc": "In-place repair: category attribute links, category enhance prompts (with attrs), weak enhance hashes, optional category backfill, attribute sanitize. No mass reprocess.",
|
||||
"admin.users.fixA1Desc": "In-place repair: category attribute links, category enhance prompts, weak enhance hashes, bidirectional category backfill (mapped↔processed when data exists), attribute sanitize. No mass reprocess. Does not invent categories for unmapped SKUs.",
|
||||
"admin.users.fixA1Company": "Company: {name}",
|
||||
"admin.users.fixA1Warning": "Prefer Platform Demo; A1 may be targeted in place with confirm. Never clears the catalog or uses A1 as a clone destination.",
|
||||
"admin.users.fixA1Warning": "Repairs existing category data only. Products UI uses mapped_data.category — Fix cannot invent missing feed categories. For full A1 coverage run seed-a1 -mode backfill-categories with the MySQL dump, then clone A1 → sandbox.",
|
||||
"admin.users.fixA1Cancel": "Cancel",
|
||||
"admin.users.fixA1Confirm": "Fix catalog",
|
||||
"admin.users.noUsers": "No users match this filter.",
|
||||
@@ -2333,9 +2333,10 @@ export const en: MessageDict = {
|
||||
"flash.admin.staffRoleUpdated": "Staff role updated for {email}.",
|
||||
"flash.admin.staffRoleUnavailable": "Staff role updates are not available on this server yet.",
|
||||
"flash.admin.planAssigned": "Plan assigned to {name}.",
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} categories. Open Products and process catalog GTINs (EAN-only API calls need a matching feed product).",
|
||||
"flash.admin.fixA1Success": "Catalog repair finished for {name}: {prompts} category prompts updated, {hashes} hashes cleared, {categories} categories backfilled.",
|
||||
"flash.admin.catalogCloned": "Copied {source} into {dest}: {products} products, {categories} taxonomy categories, {mapped_with} products with mapped category. Open Products — uncategorized SKUs need dump backfill or categorize, not another Fix.",
|
||||
"flash.admin.fixA1Success": "Catalog repair finished for {name}: {prompts} prompts, {hashes} hashes, {categories} processed←mapped, {mapped_backfilled} mapped←processed. Coverage: {mapped_with}/{mapped_total} raw products have mapped categories ({taxonomy} taxonomy rows).",
|
||||
"flash.admin.fixA1Error": "Catalog repair failed for {name}.",
|
||||
"flash.admin.cloneDestMissing": "No sandbox destination. Switch active company to Platform Demo (or your staff home), then clone again. Clone refuses to overwrite A1.",
|
||||
"flash.admin.planAssignedShort": "Plan assigned.",
|
||||
"flash.admin.creditsUpdated": "Credits updated.",
|
||||
"flash.admin.cyclesProcessed": "Billing cycles processed: {count}",
|
||||
|
||||
+2470
-2469
File diff suppressed because it is too large
Load Diff
+3228
-3227
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+5179
-5178
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
+3267
-3266
File diff suppressed because it is too large
Load Diff
+2676
-2675
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,16 @@
|
||||
import type { MessageDict } from "./types.ts";
|
||||
import type { MessageDict } from "./types.ts";
|
||||
|
||||
/** Slovenian (sl) UI strings for admin Fix A1 — ready pack. Not registered in UI_LOCALES yet (avoid inventing locale switcher). */
|
||||
/** Slovenian (sl) UI strings for admin Fix A1 — ready pack. Not registered in UI_LOCALES yet (avoid inventing locale switcher). */
|
||||
export const sl: MessageDict = {
|
||||
"admin.users.fixA1": "Popravi katalog A1",
|
||||
"admin.users.fixA1Aria": "Popravi AI pozive, kategorije in zgoščevalne vrednosti za {name}",
|
||||
"admin.users.fixA1Aria": "Popravi AI pozive, kategorije in zgoÅ¡Äevalne vrednosti za {name}",
|
||||
"admin.users.fixA1Title": "Popravi katalog podjetja",
|
||||
"admin.users.fixA1Desc": "Ponovno uporabi popravljene AI pozive, počisti šibke enhance zgoščevalne vrednosti in dopolni kategorije iz mapped podatkov.",
|
||||
"admin.users.fixA1Desc": "Ponovno uporabi popravljene AI pozive, poÄisti Å¡ibke enhance zgoÅ¡Äevalne vrednosti in dopolni kategorije iz mapped podatkov.",
|
||||
"admin.users.fixA1Company": "Podjetje: {name}",
|
||||
"admin.users.fixA1Warning": "Raje Platform Demo ali izrecno podjetje. Ne briše feedov, mapiranj ali surovih izdelkov. Ščiti pravila prepisovanja A1 kohorte.",
|
||||
"admin.users.fixA1Cancel": "Prekliči",
|
||||
"admin.users.fixA1Warning": "Raje Platform Demo ali izrecno podjetje. Ne briÅ¡e feedov, mapiranj ali surovih izdelkov. Å Äiti pravila prepisovanja A1 kohorte.",
|
||||
"admin.users.fixA1Cancel": "PrekliÄi",
|
||||
"admin.users.fixA1Confirm": "Popravi katalog",
|
||||
"flash.admin.fixA1Success": "Popravilo kataloga za {name} končano: posodobljenih pozivov {prompts}, počiščenih zgoščevalnih vrednosti {hashes}, dopolnjenih kategorij {categories}.",
|
||||
"flash.admin.fixA1Success": "Popravilo kataloga za {name}: pozivi {prompts}, zgoščene {hashes}, processed←mapped {categories}, mapped←processed {mapped_backfilled}. Pokritost: {mapped_with}/{mapped_total} ({taxonomy} taksonomija).",
|
||||
"flash.admin.fixA1Error": "Popravilo kataloga za {name} ni uspelo.",
|
||||
"flash.admin.cloneDestMissing": "Ni ciljnega sandbox podjetja. Preklopite na Platform Demo, nato klonirajte.",
|
||||
};
|
||||
|
||||
@@ -384,11 +384,18 @@
|
||||
reprocessSampleLimit: 25
|
||||
});
|
||||
const r = res.result;
|
||||
const mappedWith = Number(r.mapped_with_category ?? 0);
|
||||
const mappedWithout = Number(r.mapped_without_category ?? 0);
|
||||
const mappedTotal = mappedWith + mappedWithout;
|
||||
success = i18n.t("flash.admin.fixA1Success", {
|
||||
name: targetName,
|
||||
prompts: String(r.prompts ?? r.category_prompts_updated ?? 0),
|
||||
hashes: String(r.hashes ?? r.weak_hashes_cleared ?? 0),
|
||||
categories: String(r.categories ?? r.categories_backfilled ?? 0)
|
||||
categories: String(r.categories ?? r.categories_backfilled ?? 0),
|
||||
mapped_backfilled: String(r.mapped_backfilled ?? r.mapped_categories_backfilled ?? 0),
|
||||
mapped_with: String(mappedWith),
|
||||
mapped_total: String(mappedTotal),
|
||||
taxonomy: String(r.taxonomy_categories ?? 0)
|
||||
});
|
||||
fixOpen = false;
|
||||
fixCompany = null;
|
||||
@@ -410,17 +417,23 @@
|
||||
me?.staff_home_company_id?.trim() ||
|
||||
me?.staff_home_company?.id?.trim() ||
|
||||
undefined;
|
||||
if (!destId && me?.company?.id && me.company.id === cloneCompany.id) {
|
||||
error = i18n.t("flash.admin.cloneDestMissing");
|
||||
return;
|
||||
}
|
||||
const res = await cloneAdminCompanyCatalog(cloneCompany.id, {
|
||||
destCompanyId: destId
|
||||
});
|
||||
const products = Number(res.counts?.raw_products ?? 0);
|
||||
const categories = Number(res.counts?.categories ?? 0);
|
||||
const mappedWith = Number(res.counts?.mapped_with_category ?? 0);
|
||||
const activeDest = (res.active_company_id || res.dest_company_id || "").trim();
|
||||
success = i18n.t("flash.admin.catalogCloned", {
|
||||
source: cloneCompany.name,
|
||||
dest: cloneDestLabel,
|
||||
products: String(products),
|
||||
categories: String(categories)
|
||||
categories: String(categories),
|
||||
mapped_with: String(mappedWith)
|
||||
});
|
||||
cloneOpen = false;
|
||||
cloneCompany = null;
|
||||
|
||||
Reference in New Issue
Block a user