102 lines
4.9 KiB
Go
102 lines
4.9 KiB
Go
// Package sybimport owns the SYB (顺云宝 ERP) shipment detail import: parsing
|
||
// productSpec into color/size, and folding each detail into the independent
|
||
// syb_product table and the shared shopee_product archive. It never creates
|
||
// purchase tasks or stores order/logistics fields (#41).
|
||
package sybimport
|
||
|
||
import (
|
||
"regexp"
|
||
"strings"
|
||
|
||
"go-admin/app/goauto/models"
|
||
)
|
||
|
||
var bracketPattern = regexp.MustCompile(`【[^】]*】`)
|
||
|
||
// explicitSizePattern recognizes only values whose spelling carries a strong
|
||
// size signal. SYB has now been observed returning both "color,size" and
|
||
// "size,color". A color dictionary would inevitably guess at product-specific
|
||
// labels, so role reversal is allowed only when exactly one side matches this
|
||
// deliberately narrow pattern.
|
||
var explicitSizePattern = regexp.MustCompile(`(?i)^(?:均(?:码|碼|号|號)|one\s*size|free\s*size|x{0,4}[sml]|[2-9]xl|(?:加大|大|中|小)(?:码|碼|号|號)|\d+(?:\.\d+)?(?:cm|mm|m|码|碼|号|號|公分)|\d+(?:\.\d+)?(?:[-~~至到]\d+(?:\.\d+)?)?(?:斤|公斤|千克|kg))$`)
|
||
|
||
// ambiguousPattern flags leftover separators or multi-token noise after
|
||
// bracket stripping — the signal that a "clean" split still isn't reliable.
|
||
// Observed in the real SYB sample: "黑色+白色【純棉兩件裝】 簡約親膚" strips
|
||
// its bracket to "黑色+白色 簡約親膚", which still carries a '+' and internal
|
||
// whitespace, so it must not be reported as a confident match.
|
||
var ambiguousPattern = regexp.MustCompile(`[++\s]`)
|
||
|
||
// ParseResult is the color/size candidate extracted from one productSpec
|
||
// string, plus how much the caller should trust it.
|
||
type ParseResult struct {
|
||
Color string
|
||
Size string
|
||
Status string
|
||
Note string
|
||
}
|
||
|
||
// Parse implements the #41/#216 rule: split on the last comma, strip 【...】
|
||
// annotations, and classify the result. It never guesses a
|
||
// missing value and never invents a color or size that is not literally
|
||
// present in the input.
|
||
//
|
||
// Rules, derived from real SYB samples (demo/shunyunbaoerp_stock_list.har)
|
||
// plus the boundary cases already confirmed in the #41 prototype:
|
||
// - empty/whitespace-only input -> failed, nothing to extract.
|
||
// - no comma present (e.g. "均碼") -> uncertain: the whole string, with
|
||
// brackets stripped, becomes the size candidate; color stays empty. A
|
||
// single token with no separator cannot be split into two dimensions
|
||
// without guessing which one it is.
|
||
// - comma present and exactly one side has an explicit size signal -> that
|
||
// side is size and the other side is color. This supports both observed
|
||
// SYB orders without allowing AI or a fuzzy color dictionary to swap roles.
|
||
// - comma present and neither side has an explicit size signal -> retain the
|
||
// established SYB color,size contract for backward compatibility.
|
||
// - comma present and both sides have explicit size signals -> uncertain;
|
||
// there is no safe color decision.
|
||
// - comma present but either side is empty after stripping, or the color
|
||
// candidate still carries a leftover '+' or internal whitespace -> the
|
||
// split happened but is not trustworthy -> uncertain.
|
||
func Parse(raw string) ParseResult {
|
||
trimmed := strings.TrimSpace(raw)
|
||
if trimmed == "" {
|
||
return ParseResult{Status: models.SYBParseStatusFailed, Note: "productSpec 为空,无法拆分颜色尺码"}
|
||
}
|
||
|
||
lastComma := strings.LastIndex(trimmed, ",")
|
||
// SYB samples use the ASCII comma; a full-width Chinese comma has not been
|
||
// observed, so it is deliberately not treated as a separator here rather
|
||
// than guessed at.
|
||
if lastComma < 0 {
|
||
size := stripBrackets(trimmed)
|
||
if size == "" {
|
||
return ParseResult{Status: models.SYBParseStatusFailed, Note: "productSpec 剥离备注后为空"}
|
||
}
|
||
return ParseResult{Size: size, Status: models.SYBParseStatusUncertain, Note: "无分隔符,仅识别出单一规格值"}
|
||
}
|
||
|
||
firstPart := stripBrackets(trimmed[:lastComma])
|
||
secondPart := stripBrackets(trimmed[lastComma+1:])
|
||
colorPart, sizePart := firstPart, secondPart
|
||
|
||
if colorPart == "" || sizePart == "" {
|
||
return ParseResult{Color: colorPart, Size: sizePart, Status: models.SYBParseStatusUncertain, Note: "按逗号拆分后有一侧为空"}
|
||
}
|
||
firstIsSize, secondIsSize := explicitSizePattern.MatchString(firstPart), explicitSizePattern.MatchString(secondPart)
|
||
if firstIsSize && secondIsSize {
|
||
return ParseResult{Color: firstPart, Size: secondPart, Status: models.SYBParseStatusUncertain, Note: "逗号两侧均具有尺码特征,无法安全识别颜色"}
|
||
}
|
||
if firstIsSize {
|
||
colorPart, sizePart = secondPart, firstPart
|
||
}
|
||
if ambiguousPattern.MatchString(colorPart) {
|
||
return ParseResult{Color: colorPart, Size: sizePart, Status: models.SYBParseStatusUncertain, Note: "颜色部分含备注文本或多个分隔符,拆分结果可能不准确"}
|
||
}
|
||
return ParseResult{Color: colorPart, Size: sizePart, Status: models.SYBParseStatusSuccess}
|
||
}
|
||
|
||
func stripBrackets(part string) string {
|
||
return strings.TrimSpace(bracketPattern.ReplaceAllString(part, ""))
|
||
}
|