Claude Code Plugins

Community-maintained marketplace

Feedback

skin-matching

@raydocs/SkinLab
0
0

皮肤双胞胎匹配算法,找到相似皮肤用户并推荐其有效产品。实现社区匹配功能时使用此技能。

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name skin-matching
description 皮肤双胞胎匹配算法,找到相似皮肤用户并推荐其有效产品。实现社区匹配功能时使用此技能。

皮肤匹配技能

概述

通过皮肤指纹向量匹配相似用户,推荐他们验证有效的产品。

皮肤指纹设计

struct SkinFingerprint: Codable {
    // 基础信息(用于匹配)
    let skinType: SkinType              // 肤质
    let ageRange: AgeRange              // 年龄段
    let gender: Gender?                 // 性别(可选)
    let region: String?                 // 地区/气候(可选)
    
    // 问题向量(0-1归一化)
    let issueVector: [Double]           // [spots, acne, pores, wrinkles, redness, evenness, texture]
    
    // 计算后的向量表示
    var vector: [Double] {
        var v: [Double] = []
        
        // One-hot编码肤质
        v.append(contentsOf: skinType.oneHot)
        
        // 年龄归一化
        v.append(ageRange.normalized)
        
        // 问题向量
        v.append(contentsOf: issueVector)
        
        return v
    }
}

enum SkinType: String, Codable, CaseIterable {
    case dry, oily, combination, sensitive
    
    var oneHot: [Double] {
        Self.allCases.map { $0 == self ? 1.0 : 0.0 }
    }
}

enum AgeRange: String, Codable {
    case under20, age20to25, age25to30, age30to35, age35to40, over40
    
    var normalized: Double {
        switch self {
        case .under20: return 0.1
        case .age20to25: return 0.25
        case .age25to30: return 0.4
        case .age30to35: return 0.55
        case .age35to40: return 0.7
        case .over40: return 0.85
        }
    }
}

相似度计算

class SkinMatcher {
    /// 计算余弦相似度
    func cosineSimilarity(_ a: [Double], _ b: [Double]) -> Double {
        guard a.count == b.count, !a.isEmpty else { return 0 }
        
        let dotProduct = zip(a, b).map(*).reduce(0, +)
        let magnitudeA = sqrt(a.map { $0 * $0 }.reduce(0, +))
        let magnitudeB = sqrt(b.map { $0 * $0 }.reduce(0, +))
        
        guard magnitudeA > 0, magnitudeB > 0 else { return 0 }
        return dotProduct / (magnitudeA * magnitudeB)
    }
    
    /// 加权相似度(肤质权重更高)
    func weightedSimilarity(user: SkinFingerprint, other: SkinFingerprint) -> Double {
        let basicSimilarity = cosineSimilarity(user.vector, other.vector)
        
        // 肤质必须相同才算高度相似
        let skinTypeBonus = user.skinType == other.skinType ? 0.2 : -0.3
        
        // 年龄接近加分
        let ageDiff = abs(user.ageRange.normalized - other.ageRange.normalized)
        let ageBonus = ageDiff < 0.2 ? 0.1 : 0
        
        return min(1.0, max(0, basicSimilarity + skinTypeBonus + ageBonus))
    }
    
    /// 查找皮肤双胞胎
    func findSkinTwins(
        for user: SkinFingerprint,
        from pool: [UserProfile],
        limit: Int = 10
    ) -> [SkinTwin] {
        pool
            .map { profile -> SkinTwin in
                let similarity = weightedSimilarity(user: user, other: profile.fingerprint)
                return SkinTwin(
                    userId: profile.id,
                    similarity: similarity,
                    skinProfile: profile.anonymized,
                    effectiveProducts: profile.effectiveProducts
                )
            }
            .filter { $0.similarity >= 0.6 }  // 最低相似度阈值
            .sorted { $0.similarity > $1.similarity }
            .prefix(limit)
            .map { $0 }
    }
}

匹配结果模型

struct SkinTwin: Identifiable {
    let id = UUID()
    let userId: String
    let similarity: Double              // 0-1
    let skinProfile: AnonymousProfile
    let effectiveProducts: [EffectiveProduct]
    
    var matchLevel: MatchLevel {
        switch similarity {
        case 0.9...: return .twin        // 皮肤双胞胎
        case 0.8..<0.9: return .verySimilar
        case 0.7..<0.8: return .similar
        default: return .somewhatSimilar
        }
    }
}

enum MatchLevel: String {
    case twin = "皮肤双胞胎 👯"
    case verySimilar = "非常相似"
    case similar = "相似"
    case somewhatSimilar = "有点相似"
}

struct AnonymousProfile: Codable {
    let skinType: SkinType
    let ageRange: String                // "25-30岁"
    let mainConcerns: [SkinConcern]
    let region: String?
    // 不包含任何可识别个人信息
}

struct EffectiveProduct: Identifiable {
    let id: String
    let product: Product
    let usageDuration: Int              // 使用天数
    let improvementPercent: Double      // 改善百分比
    let verifiedAt: Date
}

推荐分数计算

struct ProductRecommendationScore {
    let product: Product
    let score: Double
    let reasons: [String]
    
    static func calculate(
        product: Product,
        userFingerprint: SkinFingerprint,
        skinTwins: [SkinTwin]
    ) -> ProductRecommendationScore {
        var score: Double = 0
        var reasons: [String] = []
        
        // 1. 相似用户有效率 (权重40%)
        let relevantTwins = skinTwins.filter { twin in
            twin.effectiveProducts.contains { $0.product.id == product.id }
        }
        if !relevantTwins.isEmpty {
            let weightedEffectiveness = relevantTwins.reduce(0.0) { sum, twin in
                let effectiveness = twin.effectiveProducts
                    .first { $0.product.id == product.id }?
                    .improvementPercent ?? 0
                return sum + twin.similarity * effectiveness
            } / relevantTwins.reduce(0.0) { $0 + $1.similarity }
            
            score += weightedEffectiveness * 0.4
            reasons.append("\(relevantTwins.count)位相似用户验证有效")
        }
        
        // 2. 成分适配度 (权重30%)
        let ingredientMatch = calculateIngredientMatch(product, userFingerprint)
        score += ingredientMatch * 0.3
        if ingredientMatch > 0.7 {
            reasons.append("成分适合你的肤质")
        }
        
        // 3. 问题匹配度 (权重20%)
        let concernMatch = calculateConcernMatch(product, userFingerprint)
        score += concernMatch * 0.2
        if concernMatch > 0.7 {
            reasons.append("针对你的皮肤问题")
        }
        
        // 4. 刺激风险扣分 (权重10%)
        let riskPenalty = calculateRiskPenalty(product, userFingerprint)
        score -= riskPenalty * 0.1
        if riskPenalty > 0.3 {
            reasons.append("⚠️ 部分成分可能刺激")
        }
        
        return ProductRecommendationScore(
            product: product,
            score: min(1.0, max(0, score)),
            reasons: reasons
        )
    }
}

数据贡献机制

struct DataContribution {
    /// 用户贡献追踪数据时调用
    func contribute(
        session: TrackingSession,
        report: TrackingReport,
        consentLevel: ConsentLevel
    ) {
        switch consentLevel {
        case .anonymous:
            // 只贡献统计数据,不关联用户
            uploadAnonymousStats(report)
        case .pseudonymous:
            // 贡献数据并参与匹配,但不显示可识别信息
            uploadForMatching(session, report)
        case .public:
            // 公开分享,可被其他用户看到
            uploadPublic(session, report)
        case .none:
            // 不贡献数据
            break
        }
    }
    
    enum ConsentLevel {
        case anonymous      // 匿名统计
        case pseudonymous   // 参与匹配但不公开
        case `public`       // 公开分享
        case none           // 完全私密
    }
}

验证

  • 相似度计算准确
  • 匹配结果排序正确
  • 推荐分数合理
  • 隐私保护到位
  • 冷启动时有合理降级