Skip to content

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.

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.

  • 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.

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 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

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.

package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 用户近 90 天行为与价值要素
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public 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;
}
package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 用户画像分层与营销策略决策
*/
@Data
public 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);
}
}
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);
}
<?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>
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 "进入静默池,仅大促节点低频触达,不做主动打扰";
}
}
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;
}
}

Execute at the project root:

Terminal window
mvn test -Dtest=UserSegmentServiceTest

Test 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 “大额召回券”
  • 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.
  • @Param name 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.