@@ -20,10 +20,10 @@ const REPEAT_REASONS = [
2020 'technical fault'
2121]
2222
23- // Probability settings for repeats
24- const REPEAT_PROBABILITIES = {
25- needsRepeat : 0.35 , // 15 % chance of needing any repeat
26- multipleRepeats : 0.2 // 20 % chance of needing more than one repeat if already repeating
23+ // Default probability settings
24+ const DEFAULT_PROBABILITIES = {
25+ viewMissing : 0.05 , // 5 % chance of a view being missing
26+ needsRepeat : 0.15 // 15 % chance of one view needing a repeat
2727}
2828
2929const generateViewKey = ( side , view ) => {
@@ -46,14 +46,14 @@ const generateImageUrl = (side, view, accessionNumber) => {
4646 * @param {string } params.accessionBase - Base accession number
4747 * @param {number } params.startIndex - Starting index for image numbering
4848 * @param {string } params.startTime - Start timestamp
49- * @param {boolean } params.isSeedData - Whether generating seed data or live data
49+ * @param {boolean } params.isSeedData - Whether generating seed data
50+ * @param {boolean } [params.needsRepeat] - Force this view to be repeated
5051 * @returns {Object } View data with images
5152 */
52- const generateViewImages = ( { side, view, accessionBase, startIndex, startTime, isSeedData } ) => {
53+ const generateViewImages = ( { side, view, accessionBase, startIndex, startTime, isSeedData, needsRepeat = false } ) => {
5354 let currentIndex = startIndex
5455 let currentTime = dayjs ( startTime )
5556 const images = [ ]
56- const needsRepeat = Math . random ( ) < REPEAT_PROBABILITIES . needsRepeat
5757
5858 // Generate initial image
5959 images . push ( {
@@ -62,20 +62,16 @@ const generateViewImages = ({ side, view, accessionBase, startIndex, startTime,
6262 url : generateImageUrl ( side , view , `${ accessionBase } /${ currentIndex } ` )
6363 } )
6464
65- // Generate repeats if needed
65+ // Generate repeat if needed
6666 if ( needsRepeat ) {
67- const repeatCount = Math . random ( ) < REPEAT_PROBABILITIES . multipleRepeats ? 2 : 1
67+ currentIndex ++
68+ currentTime = currentTime . add ( faker . number . int ( { min : 25 , max : 50 } ) , 'seconds' )
6869
69- for ( let i = 0 ; i < repeatCount ; i ++ ) {
70- currentIndex ++
71- currentTime = currentTime . add ( faker . number . int ( { min : 25 , max : 50 } ) , 'seconds' )
72-
73- images . push ( {
74- timestamp : currentTime . toISOString ( ) ,
75- accessionNumber : `${ accessionBase } /${ currentIndex } ` ,
76- url : generateImageUrl ( side , view , `${ accessionBase } /${ currentIndex } ` )
77- } )
78- }
70+ images . push ( {
71+ timestamp : currentTime . toISOString ( ) ,
72+ accessionNumber : `${ accessionBase } /${ currentIndex } ` ,
73+ url : generateImageUrl ( side , view , `${ accessionBase } /${ currentIndex } ` )
74+ } )
7975 }
8076
8177 return {
@@ -94,24 +90,50 @@ const generateViewImages = ({ side, view, accessionBase, startIndex, startTime,
9490 * @param {Object } options - Generation options
9591 * @param {Date|string } [options.startTime] - Starting timestamp (defaults to now)
9692 * @param {boolean } [options.isSeedData=false] - Whether generating seed data
93+ * @param {Object } [options.config] - Optional configuration for specific scenarios
94+ * @param {string } [options.config.repeatView] - Force a specific view to be repeated (e.g. 'RMLO')
95+ * @param {string[] } [options.config.missingViews] - Array of views to omit (e.g. ['RMLO'])
96+ * @param {Object } [options.probabilities] - Override default probabilities
9797 * @returns {Object } Complete mammogram data
9898 */
99- const generateMammogramImages = ( { startTime = new Date ( ) , isSeedData = false } = { } ) => {
99+ const generateMammogramImages = ( {
100+ startTime = new Date ( ) ,
101+ isSeedData = false ,
102+ config = { } ,
103+ probabilities = DEFAULT_PROBABILITIES
104+ } = { } ) => {
100105 const accessionBase = faker . number . int ( { min : 100000000 , max : 999999999 } ) . toString ( )
101106 let currentIndex = 1
102107 let currentTime = dayjs ( startTime )
103108 const views = { }
104109
110+ // Determine which view gets repeated (if any)
111+ let viewToRepeat = null
112+ if ( config . repeatView ) {
113+ viewToRepeat = config . repeatView
114+ } else if ( Math . random ( ) < probabilities . needsRepeat ) {
115+ viewToRepeat = faker . helpers . arrayElement ( [ 'RMLO' , 'RCC' , 'LCC' , 'LMLO' ] )
116+ }
117+
105118 // Generate each standard view
106119 STANDARD_VIEWS . forEach ( ( { side, view } ) => {
107120 const viewKey = generateViewKey ( side , view )
121+ const viewShortWithSide = `${ side === 'right' ? 'R' : 'L' } ${ view === 'mediolateral oblique' ? 'MLO' : 'CC' } `
122+
123+ // Skip if this view is in missingViews config
124+ if ( config . missingViews ?. includes ( viewShortWithSide ) ||
125+ ( ! config . missingViews && Math . random ( ) < probabilities . viewMissing ) ) {
126+ return
127+ }
128+
108129 const viewData = generateViewImages ( {
109130 side,
110131 view,
111132 accessionBase,
112133 startIndex : currentIndex ,
113134 startTime : currentTime . toISOString ( ) ,
114- isSeedData
135+ isSeedData,
136+ needsRepeat : viewToRepeat === viewShortWithSide
115137 } )
116138
117139 views [ viewKey ] = viewData
@@ -132,7 +154,7 @@ const generateMammogramImages = ({ startTime = new Date(), isSeedData = false }
132154 views,
133155 metadata : {
134156 totalImages,
135- standardViewsCompleted : true ,
157+ standardViewsCompleted : Object . keys ( views ) . length === 4 ,
136158 startTime : allTimestamps [ 0 ] ,
137159 endTime : allTimestamps [ allTimestamps . length - 1 ]
138160 }
0 commit comments