Case 28: User profile dynamic segmentation
This case corresponds to scenario 28 “User profile dynamic segmentation” in the JQuick-Java repository USE-CASES, in the e-commerce user-operations domain. The system performs two parallel segmentations on a user’s last-90-day behavioral data: an activity segmentation (ACTIVE/WARM/COLD/DORMANT) based on browsing and most-recent purchase, and a value segmentation based on four value dimensions scored and summed into a 0-75 value score mapped to HIGH/MID/LOW/NO_VALUE, then combined with risk and identity tags to assemble differentiated marketing strategies.
Industry scenario
Section titled “Industry scenario”E-commerce operations teams need fine-grained user outreach every day: who should be maintained by a dedicated account manager one-on-one, who should receive high-value recall coupons to prevent churn, who is only suitable for low-price bestsellers to pull a first order, and who should enter the dormant pool and no longer be actively contacted. Manual segmentation cannot keep up with behavioral changes or guarantee unified calibers, so a dynamic mechanism that recalculates daily from the latest behavioral data is needed.
Activity segmentation is completed by a single rule method, combining “days with browsing in the last 30 days” and “days since the last purchase” with progressive coverage: 15+ browsing days and a purchase within 7 days = ACTIVE; 8+ browsing days or a purchase within 30 days = WARM; 3+ browsing days or a purchase within 90 days = COLD; long-term inactivity = DORMANT.
On the value side, four dimensions are scored independently: 90-day GMV (0-30), 90-day order count (0-20), membership rank (0-15), historical average order amount (0-10), totaling 0-75. The Service maps these to HIGH_VALUE, MID_VALUE, LOW_VALUE and NO_VALUE along the 55/35/15 lines. In addition, the Java side supplements dynamic tags: coupon sensitivity 60+ gets PRICE_SENSITIVE, return rate 30+ gets HIGH_RETURN_RISK, active plus high value gets VIP_CULTIVATION, and dormant high-rank members (rank ≥ 4) get WINBACK.
The decision output is UserSegmentDecision: activity segment, value segment, value score, tag collection and the final marketing-strategy text. Strategy assembly has a clear short-circuit priority: high return-risk users have the highest priority and receive “cautious marketing: content seeding only, tighten return-insurance and freight-insurance entitlements”; after that, in order: high-value active VIP one-on-one maintenance, high-value non-active high-value recall coupons, mid-value active/warm customers receive threshold-reduction add-to-cart coupons, mid-value brand content touch, low-value active/warm customers receive check-in promotions for first orders, and the fallback enters the dormant pool for low-frequency touch only.
Business pain points
Section titled “Business pain points”- Segmentation thresholds change frequently with business rhythms (the ACTIVE and HIGH_VALUE lines differ before and after major promotions). Hard-coding score lines in the profiling task means code changes and re-release for every adjustment.
- The four value-dimension weights need independent tuning and A/B experiments. Coupling GMV, order count, membership rank and average order amount in the same scoring logic makes it hard to gray-release a single dimension.
- Activity segmentation and value segmentation are two orthogonal calibers, and must also be overlaid with risk tags such as coupon sensitivity and high return rate. Pure hard-coded branch combinations would grow exponentially with the number of tags.
- Marketing strategies have strict business priorities (e.g. high return risk must override VIP treatment). Without a short-circuit mechanism, it is easy to send high-value coupons to habitual returners, causing financial loss.
- Segmentation results must be able to answer “why is this user high value” and “why was this user silenced”; a black-box profile cannot be audited back to operations and merchants.
- Legacy membership and marketing middle platforms mostly run on JDK 8 and domestic-innovation JDKs, making it hard to introduce heavy profiling-rule platforms that need independent compute clusters and consoles.
Solution architecture
Section titled “Solution architecture”This case uses the six-part structure “input model + decision model + rule interface + XML rule script + Service orchestration + JUnit tests”: activity segmentation and the four-dimension scoring are both declared as interface methods with thresholds externalized in XML, and the Service handles score aggregation, segment mapping, tag collection and strategy short-circuit assembly.
| Layer | Responsibility | Files |
|---|---|---|
| Domain model layer | Carries user 90-day behavioral elements; outputs dual segmentation, value score, tags and marketing strategy | UserBehavior.java, UserSegmentDecision.java |
| Rule interface layer | Declares activity segmentation and four scoring methods, bound by name via @Param |
UserSegmentRules.java |
| Rule script layer | Activity tiers and four-dimension score thresholds externalized; supports hot update | user-segment.xml |
| Orchestration & test layer | Score aggregation, 55/35/15 segmentation, tag collection, strategy priority assembly; regression over 6 user profiles | UserSegmentService.java, UserSegmentServiceTest.java |
Rule method breakdown
Section titled “Rule method breakdown”| Rule method | Caliber | Score/output |
|---|---|---|
| activitySegment | Browsing ≥15 days and purchase within 7 days = ACTIVE; browsing ≥8 days or purchase within 30 days = WARM; browsing ≥3 days or purchase within 90 days = COLD; otherwise DORMANT (progressive coverage) | ACTIVE/WARM/COLD/DORMANT |
| scoreGmv | 90-day GMV ≥5000 = 30, ≥2000 = 22, ≥800 = 14, ≥300 = 7, otherwise 0 | 0-30 |
| scoreOrders | 90-day orders ≥12 = 20, ≥6 = 15, ≥3 = 9, ≥1 = 4, otherwise 0 | 0-20 |
| scoreMember | Membership rank S5=15, S4=12, S3=8, S2=4, S1=0 | 0-15 |
| scoreAvgPrice | Average order amount ≥1000 = 10, ≥500 = 7, ≥200 = 4, otherwise 0 | 0-10 |
Code implementation (complete & runnable)
Section titled “Code implementation (complete & runnable)”Place the following files into the JQuick-Java test project under their original packages: the two models into src/test/java/com/github/paohaijiao/demo/model/, the rule interface and test class into src/test/java/com/github/paohaijiao/demo/, the Service into src/test/java/com/github/paohaijiao/demo/service/, and the XML script into src/test/resources/demo/. Run mvn test -Dtest=UserSegmentServiceTest at the project root to execute.
UserBehavior.java
Section titled “UserBehavior.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * 用户近 90 天行为与价值要素 */@Data@NoArgsConstructor@AllArgsConstructorpublic class UserBehavior {
/** * 用户号 */ private String userId;
/** * 最近一次成交距今天数 */ private int lastOrderDays;
/** * 90 天成交订单数 */ private int orderCount90d;
/** * 90 天成交金额(元) */ private double gmv90d;
/** * 历史平均客单价(元) */ private double avgOrderAmount;
/** * 近 30 天有浏览行为的天数 */ private int browseDays30d;
/** * 优惠券订单占比(整数百分比) */ private int couponSensitivity;
/** * 退货率(整数百分比) */ private int returnRate;
/** * 会员等级序位(S1=1 ... S5=5) */ private int memberRank;}UserSegmentDecision.java
Section titled “UserSegmentDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * 用户画像分层与营销策略决策 */@Datapublic class UserSegmentDecision {
/** * 用户号 */ private String userId;
/** * 活跃分层:ACTIVE 活跃 / WARM 温客 / COLD 冷客 / DORMANT 沉睡 */ private String activitySegment;
/** * 价值分层:HIGH_VALUE 高价值 / MID_VALUE 中价值 / LOW_VALUE 低价值 / NO_VALUE 无价值 */ private String valueSegment;
/** * 价值评分(0-75) */ private int valueScore;
/** * 动态营销标签 */ private List<String> tags = new ArrayList<String>();
/** * 营销策略 */ private String marketingStrategy;
public void addTag(String tag) { this.tags.add(tag); }}UserSegmentRules.java
Section titled “UserSegmentRules.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/** * 场景 28:用户活跃度分层与价值评分规则 */public interface UserSegmentRules {
/** * 活跃度分层:按浏览天数与最近购买日逐级覆盖 */ String activitySegment(@Param("browseDays") int browseDays, @Param("lastOrderDays") int lastOrderDays);
/** * 消费金额得分(0-30) */ int scoreGmv(@Param("gmv") double gmv);
/** * 订单数得分(0-20) */ int scoreOrders(@Param("orders") int orders);
/** * 会员等级得分(0-15) */ int scoreMember(@Param("rank") int rank);
/** * 客单价得分(0-10) */ int scoreAvgPrice(@Param("avgAmount") double avgAmount);}user-segment.xml
Section titled “user-segment.xml”<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE javas PUBLIC "-//PAOHAIJIAO//DTD API JAVA 1.0//EN" "classpath:paohaijiao/dtd/Jquick-java.dtd"><javas namespace="com.github.paohaijiao.demo.UserSegmentRules">
<java name="activitySegment" returnClass="java.lang.String"> <![CDATA[ str def activitySegment(int:browseDays, int:lastOrderDays) { if (browseDays >= 15) { if (lastOrderDays <= 7) { return "ACTIVE"; } else { if (browseDays >= 8 || lastOrderDays <= 30) { return "WARM"; } else { if (browseDays >= 3 || lastOrderDays <= 90) { return "COLD"; } else { return "DORMANT"; } } } } else if (browseDays >= 8 || lastOrderDays <= 30) { return "WARM"; } else if (browseDays >= 3 || lastOrderDays <= 90) { return "COLD"; } else { return "DORMANT"; } } ]]> </java>
<java name="scoreGmv" returnClass="int"> <![CDATA[ int def scoreGmv(double:gmv) { if (gmv >= 5000) { return 30; } else if (gmv >= 2000) { return 22; } else if (gmv >= 800) { return 14; } else if (gmv >= 300) { return 7; } else { return 0; } } ]]> </java>
<java name="scoreOrders" returnClass="int"> <![CDATA[ int def scoreOrders(int:orders) { if (orders >= 12) { return 20; } else if (orders >= 6) { return 15; } else if (orders >= 3) { return 9; } else if (orders >= 1) { return 4; } else { return 0; } } ]]> </java>
<java name="scoreMember" returnClass="int"> <![CDATA[ int def scoreMember(int:rank) { if (rank >= 5) { return 15; } else if (rank == 4) { return 12; } else if (rank == 3) { return 8; } else if (rank == 2) { return 4; } else { return 0; } } ]]> </java>
<java name="scoreAvgPrice" returnClass="int"> <![CDATA[ int def scoreAvgPrice(double:avgAmount) { if (avgAmount >= 1000) { return 10; } else if (avgAmount >= 500) { return 7; } else if (avgAmount >= 200) { return 4; } else { return 0; } } ]]> </java>
</javas>UserSegmentService.java
Section titled “UserSegmentService.java”package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.UserSegmentRules;import com.github.paohaijiao.demo.model.UserBehavior;import com.github.paohaijiao.demo.model.UserSegmentDecision;
/** * 场景 28:用户画像动态分层与营销策略装配 */public class UserSegmentService {
private final UserSegmentRules rules;
public UserSegmentService(UserSegmentRules rules) { this.rules = rules; }
public UserSegmentDecision segment(UserBehavior user) { UserSegmentDecision d = new UserSegmentDecision(); d.setUserId(user.getUserId());
String activity = rules.activitySegment(user.getBrowseDays30d(), user.getLastOrderDays()); d.setActivitySegment(activity);
int valueScore = rules.scoreGmv(user.getGmv90d()) + rules.scoreOrders(user.getOrderCount90d()) + rules.scoreMember(user.getMemberRank()) + rules.scoreAvgPrice(user.getAvgOrderAmount()); d.setValueScore(valueScore);
String value; if (valueScore >= 55) { value = "HIGH_VALUE"; } else if (valueScore >= 35) { value = "MID_VALUE"; } else if (valueScore >= 15) { value = "LOW_VALUE"; } else { value = "NO_VALUE"; } d.setValueSegment(value);
collectTags(d, user, activity, value); d.setMarketingStrategy(buildStrategy(activity, value, d.getTags())); return d; }
private void collectTags(UserSegmentDecision d, UserBehavior user, String activity, String value) { if (user.getCouponSensitivity() >= 60) { d.addTag("PRICE_SENSITIVE"); } if (user.getReturnRate() >= 30) { d.addTag("HIGH_RETURN_RISK"); } if ("ACTIVE".equals(activity) && "HIGH_VALUE".equals(value)) { d.addTag("VIP_CULTIVATION"); } if ("DORMANT".equals(activity) && user.getMemberRank() >= 4) { d.addTag("WINBACK"); } }
private String buildStrategy(String activity, String value, java.util.List<String> tags) { if (tags.contains("HIGH_RETURN_RISK")) { return "审慎营销:仅做内容种草,收紧退货险与运费险权益"; } if ("HIGH_VALUE".equals(value) && "ACTIVE".equals(activity)) { return "专属客户经理一对一维护,发放 VIP 专享权益"; } if ("HIGH_VALUE".equals(value)) { return "EDM/短信推送大额召回券,安排客服一对一唤醒"; } if ("MID_VALUE".equals(value) && ("ACTIVE".equals(activity) || "WARM".equals(activity))) { return "推送满减加购券与凑单活动,提升客单价"; } if ("MID_VALUE".equals(value)) { return "EDM 推送品牌内容与爆品清单,保持品牌触达"; } if ("LOW_VALUE".equals(value) && ("ACTIVE".equals(activity) || "WARM".equals(activity))) { return "签到领积分与低价爆品推送,促进首单与复购"; } return "进入静默池,仅大促节点低频触达,不做主动打扰"; }}UserSegmentServiceTest.java
Section titled “UserSegmentServiceTest.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.UserBehavior;import com.github.paohaijiao.demo.model.UserSegmentDecision;import com.github.paohaijiao.demo.service.UserSegmentService;import com.github.paohaijiao.xml.JQuickJava;import org.junit.Assert;import org.junit.Before;import org.junit.Test;
/** * 场景 28:用户画像标签动态分层 * <p> * 覆盖 6 组用户形态:高价值活跃 VIP、高价值温客召回、中价值价格敏感、 * 无价值沉睡静默、活跃高退货审慎营销、高价值冷客 EDM 唤醒(55 分边界)。 */public class UserSegmentServiceTest {
private UserSegmentService service;
@Before public void setUp() { UserSegmentRules rules = JQuickJava.create() .createApi(UserSegmentRules.class, "demo/user-segment.xml"); this.service = new UserSegmentService(rules); }
/** * 全维度头部用户 → ACTIVE / HIGH_VALUE(75) / VIP 培育 */ @Test public void shouldSegmentActiveHighValueVip() { UserBehavior u = base("U001"); u.setBrowseDays30d(22); u.setLastOrderDays(3); u.setOrderCount90d(15); u.setGmv90d(6000); u.setAvgOrderAmount(1200); u.setMemberRank(5); u.setCouponSensitivity(30); u.setReturnRate(5);
UserSegmentDecision d = service.segment(u);
Assert.assertEquals("ACTIVE", d.getActivitySegment()); Assert.assertEquals(75, d.getValueScore()); Assert.assertEquals("HIGH_VALUE", d.getValueSegment()); Assert.assertTrue(d.getTags().contains("VIP_CULTIVATION")); Assert.assertTrue(d.getMarketingStrategy().contains("专属客户经理")); }
/** * 高价值但浏览活跃转温 → 大额召回券防流失 */ @Test public void shouldRecallHighValueWarmUser() { UserBehavior u = base("U002"); u.setBrowseDays30d(10); u.setLastOrderDays(25); u.setOrderCount90d(8); u.setGmv90d(5200); u.setAvgOrderAmount(1100); u.setMemberRank(4);
UserSegmentDecision d = service.segment(u);
Assert.assertEquals("WARM", d.getActivitySegment()); Assert.assertEquals(67, d.getValueScore()); Assert.assertEquals("HIGH_VALUE", d.getValueSegment()); Assert.assertTrue(d.getMarketingStrategy().contains("大额召回券")); }
/** * 价值分恰好 35 的温客且券敏感 → 中价值,满减加购券 */ @Test public void shouldSegmentPriceSensitiveMidValue() { UserBehavior u = base("U003"); u.setBrowseDays30d(12); u.setLastOrderDays(5); u.setOrderCount90d(4); u.setGmv90d(900); u.setAvgOrderAmount(300); u.setMemberRank(3); u.setCouponSensitivity(70);
UserSegmentDecision d = service.segment(u);
Assert.assertEquals("WARM", d.getActivitySegment()); Assert.assertEquals(35, d.getValueScore()); Assert.assertEquals("MID_VALUE", d.getValueSegment()); Assert.assertTrue(d.getTags().contains("PRICE_SENSITIVE")); Assert.assertTrue(d.getMarketingStrategy().contains("满减加购券")); }
/** * 长期无行为低等级用户 → DORMANT / NO_VALUE,静默池 */ @Test public void shouldSilenceDormantNoValueUser() { UserBehavior u = base("U004"); u.setBrowseDays30d(1); u.setLastOrderDays(150); u.setOrderCount90d(0); u.setGmv90d(0); u.setAvgOrderAmount(0); u.setMemberRank(1);
UserSegmentDecision d = service.segment(u);
Assert.assertEquals("DORMANT", d.getActivitySegment()); Assert.assertEquals(0, d.getValueScore()); Assert.assertEquals("NO_VALUE", d.getValueSegment()); Assert.assertEquals(0, d.getTags().size()); Assert.assertTrue(d.getMarketingStrategy().contains("静默池")); }
/** * 活跃中价值但退货率 35% → 高退货风险,审慎营销优先于活跃策略 */ @Test public void shouldApplyCautiousStrategyForHighReturnRisk() { UserBehavior u = base("U005"); u.setBrowseDays30d(20); u.setLastOrderDays(2); u.setOrderCount90d(10); u.setGmv90d(2200); u.setAvgOrderAmount(400); u.setMemberRank(3); u.setReturnRate(35);
UserSegmentDecision d = service.segment(u);
Assert.assertEquals("ACTIVE", d.getActivitySegment()); Assert.assertEquals(49, d.getValueScore()); Assert.assertEquals("MID_VALUE", d.getValueSegment()); Assert.assertTrue(d.getTags().contains("HIGH_RETURN_RISK")); Assert.assertTrue(d.getMarketingStrategy().contains("审慎营销")); }
/** * 价值分恰好 55 的冷客(高等级)→ HIGH_VALUE + EDM 唤醒,不打 VIP 标签 */ @Test public void shouldWakeHighValueColdUserAtThreshold() { UserBehavior u = base("U006"); u.setBrowseDays30d(4); u.setLastOrderDays(60); u.setOrderCount90d(3); u.setGmv90d(5000); u.setAvgOrderAmount(200); u.setMemberRank(4);
UserSegmentDecision d = service.segment(u);
Assert.assertEquals("COLD", d.getActivitySegment()); Assert.assertEquals(55, d.getValueScore()); Assert.assertEquals("HIGH_VALUE", d.getValueSegment()); Assert.assertFalse(d.getTags().contains("VIP_CULTIVATION")); Assert.assertFalse(d.getTags().contains("WINBACK")); Assert.assertTrue(d.getMarketingStrategy().contains("大额召回券")); }
private UserBehavior base(String id) { UserBehavior u = new UserBehavior(); u.setUserId(id); u.setLastOrderDays(999); u.setOrderCount90d(0); u.setGmv90d(0); u.setAvgOrderAmount(0); u.setBrowseDays30d(0); u.setCouponSensitivity(0); u.setReturnRate(0); u.setMemberRank(1); return u; }}Run & verified results
Section titled “Run & verified results”Execute at the project root:
mvn test -Dtest=UserSegmentServiceTestTest coverage (6 @Test methods in total):
| Test method | Business profile | Key assertions / expected decision |
|---|---|---|
| shouldSegmentActiveHighValueVip | 22 browsing days, purchased 3 days ago, 15 orders, GMV 6000, order amount 1200, S5 | ACTIVE, valueScore=75, HIGH_VALUE; contains VIP_CULTIVATION tag; strategy contains “专属客户经理” |
| shouldRecallHighValueWarmUser | 10 browsing days, purchased 25 days ago, 8 orders, GMV 5200, S4 | WARM, valueScore=67, HIGH_VALUE; strategy contains “大额召回券” |
| shouldSegmentPriceSensitiveMidValue | 12 browsing days, 4 orders, GMV 900, order amount 300, S3, coupon sensitivity 70, exactly 35 points | WARM, valueScore=35, MID_VALUE; contains PRICE_SENSITIVE; strategy contains “满减加购券” |
| shouldSilenceDormantNoValueUser | 1 browsing day, 150 days since last purchase, 0 orders 0 yuan, S1 | DORMANT, valueScore=0, NO_VALUE, tags empty (size=0); strategy contains “静默池” |
| shouldApplyCautiousStrategyForHighReturnRisk | Active mid-value (10 orders, GMV 2200, S3) but return rate 35% | ACTIVE, valueScore=49, MID_VALUE; contains HIGH_RETURN_RISK; strategy contains “审慎营销”, verifying risk short-circuit priority |
| shouldWakeHighValueColdUserAtThreshold | 4 browsing days, 60 days since last purchase, GMV 5000, S4, exactly 55 points | COLD, valueScore=55, HIGH_VALUE; does not contain VIP_CULTIVATION or WINBACK; strategy contains “大额召回券” |
Technical highlights
Section titled “Technical highlights”- Externalized segmentation thresholds with hot update: activity tiers and four-dimension score values all live in user-segment.xml. Redrawing the score lines before or after promotions only requires editing the XML and rebuilding the proxy; the profiling batch task does not need code changes.
- One method per caliber, independently tunable: GMV, order count, membership rank and average order amount each occupy one method. Operations can adjust a single dimension’s tiers and audit its contribution; the four dimensions are summed and mapped to value tiers by the Service.
- Strategy short-circuit priority fixed on the Java side: buildStrategy first checks HIGH_RETURN_RISK for cautious marketing, then in order checks VIP, recall, add-to-cart, silence — risk floor precedes entitlement issuance, preventing high-value coupons from going to high-return-risk users.
- Dual-axis segmentation + dynamic-tag orthogonal combination: activity and value are two independent rule axes, and tags such as coupon sensitivity, high return risk, VIP_CULTIVATION and WINBACK are appended on the Java side by thresholds, so adding a new tag does not require changing the XML scoring logic.
- Boundary scores locked by tests: the 35-point mid-value boundary and the 55-point high-value cold user (no VIP, no WINBACK) critical profiles all have assertions; any threshold adjustment immediately exposes caliber changes through the tests.
@Paramname binding and proxy reuse: the interface is implemented via an ASM dynamic proxy, with no independent compute process or heavy dependencies, and can be embedded directly into JDK 8 legacy membership middle platforms and domestic-innovation environments for daily batch segmentation tasks.