Skip to content

Commit b19dfcc

Browse files
committed
Add exact ratio distribution for deterministic coupon and refund generation
- Pre-generate exact counts of coupons/refunds based on ratios - Ensures 100 orders at 0.5 ratio = exactly 50 coupons (not 45-55) - Refunds split: 50% full, 25% partial, 25% multi-partial - Maintains backwards compatibility with single order generation - Only affects batch mode for predictable test data
1 parent 5734f42 commit b19dfcc

File tree

1 file changed

+137
-32
lines changed

1 file changed

+137
-32
lines changed

includes/Generator/Order.php

Lines changed: 137 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,22 @@ class Order extends Generator {
3333
*/
3434
const SECOND_REFUND_MAX_DAYS = 30;
3535

36+
/**
37+
* Pre-generated coupon flags for exact ratio distribution in batch mode.
38+
* Each element is a boolean: true = apply coupon, false = skip.
39+
*
40+
* @var array|null
41+
*/
42+
protected static $batch_coupon_flags = null;
43+
44+
/**
45+
* Pre-generated refund flags for exact ratio distribution in batch mode.
46+
* Each element is a string: 'none', 'full', 'partial', or 'multi'.
47+
*
48+
* @var array|null
49+
*/
50+
protected static $batch_refund_flags = null;
51+
3652
/**
3753
* Return a new order.
3854
*
@@ -115,20 +131,26 @@ public static function generate( $save = true, $assoc_args = array() ) {
115131

116132
// Handle --coupon-ratio parameter
117133
if ( isset( $assoc_args['coupon-ratio'] ) ) {
118-
$coupon_ratio = floatval( $assoc_args['coupon-ratio'] );
134+
// Use exact ratio flag if in batch mode
135+
if ( null !== self::$batch_coupon_flags && ! empty( self::$batch_coupon_flags ) ) {
136+
$include_coupon = array_shift( self::$batch_coupon_flags );
137+
} else {
138+
// Fall back to probabilistic approach for single order generation
139+
$coupon_ratio = floatval( $assoc_args['coupon-ratio'] );
119140

120-
// Validate ratio is between 0.0 and 1.0
121-
if ( $coupon_ratio < 0.0 || $coupon_ratio > 1.0 ) {
122-
$coupon_ratio = max( 0.0, min( 1.0, $coupon_ratio ) );
123-
}
141+
// Validate ratio is between 0.0 and 1.0
142+
if ( $coupon_ratio < 0.0 || $coupon_ratio > 1.0 ) {
143+
$coupon_ratio = max( 0.0, min( 1.0, $coupon_ratio ) );
144+
}
124145

125-
// Apply coupon based on ratio
126-
if ( $coupon_ratio >= 1.0 ) {
127-
$include_coupon = true;
128-
} elseif ( $coupon_ratio > 0 && wp_rand( 1, 100 ) <= ( $coupon_ratio * 100 ) ) {
129-
$include_coupon = true;
130-
} else {
131-
$include_coupon = false;
146+
// Apply coupon based on ratio
147+
if ( $coupon_ratio >= 1.0 ) {
148+
$include_coupon = true;
149+
} elseif ( $coupon_ratio > 0 && wp_rand( 1, 100 ) <= ( $coupon_ratio * 100 ) ) {
150+
$include_coupon = true;
151+
} else {
152+
$include_coupon = false;
153+
}
132154
}
133155
}
134156

@@ -163,29 +185,43 @@ public static function generate( $save = true, $assoc_args = array() ) {
163185

164186
// Handle --refund-ratio parameter for completed orders
165187
if ( isset( $assoc_args['refund-ratio'] ) && 'completed' === $status ) {
166-
$refund_ratio = floatval( $assoc_args['refund-ratio'] );
167-
168-
// Validate ratio is between 0.0 and 1.0
169-
if ( $refund_ratio < 0.0 || $refund_ratio > 1.0 ) {
170-
$refund_ratio = max( 0.0, min( 1.0, $refund_ratio ) );
171-
}
172-
173-
$should_refund = false;
188+
$refund_type = 'none';
189+
190+
// Use exact ratio flag if in batch mode
191+
if ( null !== self::$batch_refund_flags && ! empty( self::$batch_refund_flags ) ) {
192+
$refund_type = array_shift( self::$batch_refund_flags );
193+
} else {
194+
// Fall back to probabilistic approach for single order generation
195+
$refund_ratio = floatval( $assoc_args['refund-ratio'] );
196+
197+
// Validate ratio is between 0.0 and 1.0
198+
if ( $refund_ratio < 0.0 || $refund_ratio > 1.0 ) {
199+
$refund_ratio = max( 0.0, min( 1.0, $refund_ratio ) );
200+
}
174201

175-
if ( $refund_ratio >= 1.0 ) {
176-
// Always refund if ratio is 1.0 or higher
177-
$should_refund = true;
178-
} elseif ( $refund_ratio > 0 && wp_rand( 1, 100 ) <= ( $refund_ratio * 100 ) ) {
179-
// Use random chance for ratios between 0 and 1
180-
$should_refund = true;
202+
if ( $refund_ratio >= 1.0 ) {
203+
// Always refund if ratio is 1.0 or higher
204+
$refund_type = 'full';
205+
} elseif ( $refund_ratio > 0 && wp_rand( 1, 100 ) <= ( $refund_ratio * 100 ) ) {
206+
// Use random chance for ratios between 0 and 1
207+
// Split evenly between full and partial
208+
$refund_type = (bool) wp_rand( 0, 1 ) ? 'full' : 'partial';
209+
210+
// 25% chance for multi-partial
211+
if ( 'partial' === $refund_type && wp_rand( 1, 100 ) <= self::SECOND_REFUND_PROBABILITY ) {
212+
$refund_type = 'multi';
213+
}
214+
}
181215
}
182216

183-
if ( $should_refund ) {
184-
// Create first refund with date within 2 months of completion
185-
$first_refund = self::create_refund( $order );
186-
187-
// Some partial refunds get a second refund (always partial)
188-
if ( $first_refund && wp_rand( 1, 100 ) <= self::SECOND_REFUND_PROBABILITY ) {
217+
// Process refund based on type
218+
if ( 'full' === $refund_type ) {
219+
self::create_refund( $order );
220+
} elseif ( 'partial' === $refund_type ) {
221+
self::create_refund( $order, true );
222+
} elseif ( 'multi' === $refund_type ) {
223+
$first_refund = self::create_refund( $order, true );
224+
if ( $first_refund ) {
189225
self::create_refund( $order, true, $first_refund );
190226
}
191227
}
@@ -218,13 +254,19 @@ public static function batch( $amount, array $args = array() ) {
218254
return $amount;
219255
}
220256

257+
// Initialize exact ratio flags for deterministic distribution
258+
self::init_ratio_flags( $amount, $args );
259+
221260
$order_ids = array();
222261

223262
for ( $i = 1; $i <= $amount; $i ++ ) {
224263
$order = self::generate( true, $args );
225264
$order_ids[] = $order->get_id();
226265
}
227266

267+
// Clear ratio flags after batch generation
268+
self::clear_ratio_flags();
269+
228270
return $order_ids;
229271
}
230272

@@ -737,4 +779,67 @@ protected static function calculate_refund_date( $order, $previous_refund = null
737779
$random_days = wp_rand( 0, $max_days );
738780
return date( 'Y-m-d H:i:s', strtotime( $base_date->date( 'Y-m-d H:i:s' ) ) + ( $random_days * DAY_IN_SECONDS ) );
739781
}
782+
783+
/**
784+
* Initialize exact ratio flags for batch generation.
785+
* Creates pre-generated arrays for exact distribution of coupons and refunds.
786+
*
787+
* @param int $count Number of orders to generate.
788+
* @param array $args Arguments containing ratio parameters.
789+
* @return void
790+
*/
791+
protected static function init_ratio_flags( $count, $args ) {
792+
// Initialize coupon flags if coupon-ratio is set
793+
if ( isset( $args['coupon-ratio'] ) ) {
794+
$coupon_ratio = floatval( $args['coupon-ratio'] );
795+
$coupon_ratio = max( 0.0, min( 1.0, $coupon_ratio ) );
796+
797+
$num_with_coupons = (int) round( $count * $coupon_ratio );
798+
$num_without = $count - $num_with_coupons;
799+
800+
// Create array with exact counts
801+
self::$batch_coupon_flags = array_merge(
802+
array_fill( 0, $num_with_coupons, true ),
803+
array_fill( 0, $num_without, false )
804+
);
805+
806+
// Shuffle for randomness
807+
shuffle( self::$batch_coupon_flags );
808+
}
809+
810+
// Initialize refund flags if refund-ratio is set
811+
if ( isset( $args['refund-ratio'] ) && isset( $args['status'] ) && 'completed' === $args['status'] ) {
812+
$refund_ratio = floatval( $args['refund-ratio'] );
813+
$refund_ratio = max( 0.0, min( 1.0, $refund_ratio ) );
814+
815+
$total_refunds = (int) round( $count * $refund_ratio );
816+
817+
// Split refunds: 50% full, 25% single partial, 25% multi-partial
818+
$num_full = (int) round( $total_refunds * 0.5 );
819+
$num_partial = (int) round( $total_refunds * 0.25 );
820+
$num_multi = $total_refunds - $num_full - $num_partial; // Remainder goes to multi
821+
$num_none = $count - $total_refunds;
822+
823+
// Create array with exact counts
824+
self::$batch_refund_flags = array_merge(
825+
array_fill( 0, $num_full, 'full' ),
826+
array_fill( 0, $num_partial, 'partial' ),
827+
array_fill( 0, $num_multi, 'multi' ),
828+
array_fill( 0, $num_none, 'none' )
829+
);
830+
831+
// Shuffle for randomness
832+
shuffle( self::$batch_refund_flags );
833+
}
834+
}
835+
836+
/**
837+
* Clear ratio flags after batch generation is complete.
838+
*
839+
* @return void
840+
*/
841+
protected static function clear_ratio_flags() {
842+
self::$batch_coupon_flags = null;
843+
self::$batch_refund_flags = null;
844+
}
740845
}

0 commit comments

Comments
 (0)