@@ -9,6 +9,43 @@ export const DEFAULT_FOLDERS_BY_CATEGORY = {
99 video : [ "Videos" ]
1010} ;
1111
12+ const MONTHS = [ "Jan" , "Feb" , "Mar" , "Apr" , "May" , "Jun" , "Jul" , "Aug" , "Sep" , "Oct" , "Nov" , "Dec" ] ;
13+
14+ function extractDate ( text , baseName ) {
15+ // Try to find YYYY-MM or MM-YYYY or DD-MM-YYYY or Month YYYY
16+ const combined = ( text + " " + baseName ) . replace ( / \n / g, ' ' ) ;
17+
18+ // Look for "Month YYYY" e.g., "January 2024" or "Jan 2024"
19+ const monthNames = "(january|jan|february|feb|march|mar|april|apr|may|june|jun|july|jul|august|aug|september|sep|october|oct|november|nov|december|dec)" ;
20+ const monthYearRegex = new RegExp ( monthNames + "\\s*,?\\s*-?\\s*(\\d{4})" , "i" ) ;
21+ let match = combined . match ( monthYearRegex ) ;
22+ if ( match ) {
23+ const mStr = match [ 1 ] . toLowerCase ( ) . substring ( 0 , 3 ) ;
24+ const mIndex = [ "jan" , "feb" , "mar" , "apr" , "may" , "jun" , "jul" , "aug" , "sep" , "oct" , "nov" , "dec" ] . indexOf ( mStr ) ;
25+ return { year : match [ 2 ] , month : String ( mIndex + 1 ) . padStart ( 2 , '0' ) + "_" + MONTHS [ mIndex ] } ;
26+ }
27+
28+ // Look for YYYY-MM or YYYY_MM
29+ match = combined . match ( / 2 0 [ 0 - 2 ] \d [ - _ ] ( 0 [ 1 - 9 ] | 1 [ 0 - 2 ] ) / ) ;
30+ if ( match ) {
31+ const mIndex = parseInt ( match [ 1 ] , 10 ) - 1 ;
32+ return { year : match [ 0 ] . substring ( 0 , 4 ) , month : match [ 1 ] + "_" + MONTHS [ mIndex ] } ;
33+ }
34+
35+ // Look for DD/MM/YYYY or DD-MM-YYYY
36+ match = combined . match ( / (?: ^ | \D ) ( [ 0 - 3 ] \d ) [ / . - ] ( 0 [ 1 - 9 ] | 1 [ 0 - 2 ] ) [ / . - ] ( 2 0 [ 0 - 2 ] \d ) (?: \D | $ ) / ) ;
37+ if ( match ) {
38+ const mIndex = parseInt ( match [ 2 ] , 10 ) - 1 ;
39+ return { year : match [ 3 ] , month : match [ 2 ] + "_" + MONTHS [ mIndex ] } ;
40+ }
41+
42+ // Look for standalone year 2010-2029
43+ match = combined . match ( / (?: ^ | \D ) ( 2 0 [ 1 - 2 ] \d ) (?: \D | $ ) / ) ;
44+ if ( match ) return { year : match [ 1 ] , month : null } ;
45+
46+ return { year : null , month : null } ;
47+ }
48+
1249export const PURPOSE_RULES = [
1350 {
1451 purpose : "resume" ,
@@ -22,12 +59,6 @@ export const PURPOSE_RULES = [
2259 renameLabel : "Travel_Ticket" ,
2360 pattern : / ( ^ | [ \s . _ - ] ) ( t i c k e t | f l i g h t | t r a i n | i r c t c | b o a r d i n g | i t i n e r a r y | b o o k i n g | c l e a r t r i p | m a k e m y t r i p ) ( [ \s . _ - ] | $ ) / i
2461 } ,
25- {
26- purpose : "finance" ,
27- expectedFolders : [ "Finance" ] ,
28- renameLabel : "Finance_Record" ,
29- pattern : / ( ^ | [ \s . _ - ] ) ( i n v o i c e | r e c e i p t | t a x - ? f o r m | p a y [ \s . _ - ] ? s l i p | s a l a r y [ \s . _ - ] ? s l i p | e p f o - ? s t a t e m e n t | b a n k [ \s . _ - ] ? s t a t e m e n t | a c c o u n t [ \s . _ - ] ? s t a t e m e n t | t r a n s a c t i o n s ? | n o m i n a t i o n - ? f o r m | c t c - ? l e t t e r | i n c r e m e n t - ? l e t t e r ) ( [ \s . _ - ] | $ ) / i
30- } ,
3162 {
3263 purpose : "insurance" ,
3364 expectedFolders : [ "Finance/Insurance" ] ,
@@ -72,136 +103,126 @@ export const PURPOSE_RULES = [
72103 }
73104] ;
74105
75- // Supports: _v1, -v1, .v1, (1), - Copy, - Copy (1), 2024.4, 1.2.3
76- export const VERSION_PATTERN = / ( [ . _ - ] v ? ( \d + ( [ . _ ] \d + ) * ) ) | ( \( ( \d + ) \) ) | ( [ - ] C o p y ( \( ( \d + ) \) ) ? ) $ / i;
106+ export const VERSION_PATTERN = / ( [ . _ - ] v ? ( \d + ( [ . _ ] \d + ) * ) ) | ( \\ ( ( \d + ) \\ ) ) | ( [ - ] C o p y ( \\ ( ( \d + ) \\ ) ) ? ) $ / i;
77107
78108export const CODE_EXTENSIONS = new Set ( [
79109 ".js" , ".ts" , ".jsx" , ".tsx" , ".py" , ".java" , ".c" , ".cpp" , ".h" , ".hpp" , ".cs" , ".go" , ".rs" , ".rb" , ".php" , ".html" , ".css" , ".sql" , ".sh" , ".bat" , ".ps1" , ".yml" , ".yaml" , ".json" , ".xml" , ".md" , ".sol"
80110] ) ;
81111
82112export function inferPurposeDetails ( { absolutePath = "" , baseName = "" , extension = "" , category = "other" , extractedText = "" , isEntity = false , entityType = "" } ) {
83- // If it's a cohesive entity, categorize it immediately to prevent fragmentation
84113 if ( isEntity ) {
85- if ( entityType === "software_project" ) {
86- return {
87- purpose : "code" ,
88- expectedFolders : [ "Projects" ] ,
89- matchedByRule : true ,
90- renameLabel : "Project"
91- } ;
92- }
93- if ( entityType === "application" ) {
94- return {
95- purpose : "installer" ,
96- expectedFolders : [ "Applications" ] ,
97- matchedByRule : true ,
98- renameLabel : "App"
99- } ;
100- }
114+ if ( entityType === "software_project" ) return { purpose : "code" , expectedFolders : [ "Projects" ] , matchedByRule : true , renameLabel : "Project" } ;
115+ if ( entityType === "application" ) return { purpose : "installer" , expectedFolders : [ "Applications" ] , matchedByRule : true , renameLabel : "App" } ;
101116 }
102117
103118 const normalizedBaseName = String ( baseName || path . basename ( absolutePath ) ) . toLowerCase ( ) ;
104119 const normalizedExtension = String ( extension || path . extname ( absolutePath ) ) . toLowerCase ( ) ;
105120
106- // Specific Deep Content Rule: Form 16 Segregation
107- if ( normalizedBaseName . includes ( "f16" ) || normalizedBaseName . includes ( "form 16" ) || / f o r m \s * n o \. ? \s * 1 6 / i. test ( extractedText ) ) {
108- // Try to extract Assessment Year from text (e.g. "Assessment Year: 2024-25" or "Assessment Year 2024-25")
109- let yearMatch = extractedText . match ( / A s s e s s m e n t Y e a r [: \s] * ( \d { 4 } - \d { 2 } ) / i) || extractedText . match ( / ( \d { 4 } - \d { 2 } ) / ) ;
110- // Fallback to year in filename
111- if ( ! yearMatch ) {
112- yearMatch = normalizedBaseName . match ( / ( \d { 4 } - \d { 2 } ) / ) ;
121+ const dateInfo = extractDate ( extractedText , normalizedBaseName ) ;
122+
123+ // Specific Deep Content Rule: Pay Slips
124+ if ( / p a y [ \s . _ - ] ? s l i p / i. test ( normalizedBaseName ) || / s a l a r y [ \s . _ - ] ? s l i p / i. test ( normalizedBaseName ) || / p a y \s * s l i p / i. test ( extractedText ) || / s a l a r y \s * s l i p / i. test ( extractedText ) ) {
125+ let folder = "Finance/Pay_Slips" ;
126+ let label = "Pay_Slip" ;
127+ if ( dateInfo . year ) {
128+ folder += `/${ dateInfo . year } ` ;
129+ label += `_${ dateInfo . year } ` ;
130+ if ( dateInfo . month ) {
131+ folder += `/${ dateInfo . month } ` ;
132+ label += `_${ dateInfo . month . split ( '_' ) [ 1 ] } ` ;
133+ }
113134 }
114-
115- let folder = "Finance/Form_16" ;
135+ return { purpose : "finance" , expectedFolders : [ folder ] , matchedByRule : true , renameLabel : label } ;
136+ }
137+
138+ // Specific Deep Content Rule: Form 16 / IT Returns
139+ if ( normalizedBaseName . includes ( "f16" ) || normalizedBaseName . includes ( "form 16" ) || / f o r m \s * n o \\ .? \s * 1 6 / i. test ( extractedText ) || / i n c o m e \s * t a x \s * r e t u r n / i. test ( extractedText ) || / i t \s * c o m p u t a t i o n / i. test ( extractedText ) ) {
140+ let yearMatch = extractedText . match ( / A s s e s s m e n t Y e a r [: \s] * ( \d { 4 } - \d { 2 } ) / i) || extractedText . match ( / ( \d { 4 } - \d { 2 } ) / ) ;
141+ if ( ! yearMatch ) yearMatch = normalizedBaseName . match ( / ( \d { 4 } - \d { 2 } ) / ) ;
142+ let folder = "Finance/IT_Returns" ;
143+ let label = normalizedBaseName . includes ( "form" ) || / f o r m / i. test ( extractedText ) ? "Form_16" : "IT_Computation" ;
116144 if ( yearMatch ) {
117- // Convert '2024-25' to '2024' or keep '2024-25'
118- const yearStr = yearMatch [ 1 ] ;
119- const startYear = yearStr . split ( '-' ) [ 0 ] ;
120- folder = `Finance/Form_16/${ startYear } ` ;
145+ const startYear = yearMatch [ 1 ] . split ( '-' ) [ 0 ] ;
146+ folder += `/${ startYear } ` ;
147+ label += `_${ startYear } ` ;
148+ } else if ( dateInfo . year ) {
149+ folder += `/${ dateInfo . year } ` ;
150+ label += `_${ dateInfo . year } ` ;
121151 }
152+ return { purpose : "finance" , expectedFolders : [ folder ] , matchedByRule : true , renameLabel : label } ;
153+ }
122154
123- return {
124- purpose : "finance" ,
125- expectedFolders : [ folder ] ,
126- matchedByRule : true ,
127- renameLabel : "Form_16"
128- } ;
155+ // Specific Deep Content Rule: EPF Statement
156+ if ( / e p f / i. test ( normalizedBaseName ) || / e p f o / i. test ( normalizedBaseName ) || / p r o v i d e n t \s * f u n d / i. test ( extractedText ) || / e p f o \s * s t a t e m e n t / i. test ( extractedText ) ) {
157+ let folder = "Finance/EPF" ;
158+ let label = "EPF_Statement" ;
159+ if ( dateInfo . year ) {
160+ folder += `/${ dateInfo . year } ` ;
161+ label += `_${ dateInfo . year } ` ;
162+ }
163+ return { purpose : "finance" , expectedFolders : [ folder ] , matchedByRule : true , renameLabel : label } ;
129164 }
130165
131166 // Specific Deep Content Rule: Bank Statement
132167 if ( normalizedBaseName . includes ( "statement" ) || / a c c o u n t \s * s t a t e m e n t / i. test ( extractedText ) || / b a n k \s * s t a t e m e n t / i. test ( extractedText ) || ( / a c c o u n t \s * s u m m a r y / i. test ( extractedText ) && / b a l a n c e / i. test ( extractedText ) ) ) {
133- return {
134- purpose : "finance" ,
135- expectedFolders : [ "Finance/Bank_Statements" ] ,
136- matchedByRule : true ,
137- renameLabel : "Bank_Statement"
138- } ;
168+ let folder = "Finance/Bank_Statements" ;
169+ let label = "Bank_Statement" ;
170+
171+ // Try to extract bank name
172+ let bankName = "" ;
173+ if ( / h d f c / i. test ( extractedText ) || / h d f c / i. test ( normalizedBaseName ) ) bankName = "HDFC" ;
174+ else if ( / s b i / i. test ( extractedText ) || / s t a t e b a n k / i. test ( extractedText ) || / s b i / i. test ( normalizedBaseName ) ) bankName = "SBI" ;
175+ else if ( / i c i c i / i. test ( extractedText ) || / i c i c i / i. test ( normalizedBaseName ) ) bankName = "ICICI" ;
176+ else if ( / a x i s / i. test ( extractedText ) || / a x i s / i. test ( normalizedBaseName ) ) bankName = "Axis" ;
177+
178+ if ( bankName ) label = `${ bankName } _Statement` ;
179+
180+ if ( dateInfo . year ) {
181+ folder += `/${ dateInfo . year } ` ;
182+ label += `_${ dateInfo . year } ` ;
183+ if ( dateInfo . month ) {
184+ folder += `/${ dateInfo . month } ` ;
185+ label += `_${ dateInfo . month . split ( '_' ) [ 1 ] } ` ;
186+ }
187+ }
188+ return { purpose : "finance" , expectedFolders : [ folder ] , matchedByRule : true , renameLabel : label } ;
139189 }
140190
141191 // Specific Deep Content Rule: Aadhaar Card
142192 if ( normalizedBaseName . includes ( "aadhaar" ) || / u n i q u e i d e n t i f i c a t i o n a u t h o r i t y o f i n d i a / i. test ( extractedText ) || ( / g o v e r n m e n t o f i n d i a / i. test ( extractedText ) && / a a d h a a r / i. test ( extractedText ) ) ) {
143- return {
144- purpose : "identity" ,
145- expectedFolders : [ "Identity" ] ,
146- matchedByRule : true ,
147- renameLabel : "Aadhaar_Card"
148- } ;
193+ return { purpose : "identity" , expectedFolders : [ "Identity/Aadhaar" ] , matchedByRule : true , renameLabel : "Aadhaar_Card" } ;
149194 }
150195
151196 // Specific Deep Content Rule: PAN Card
152197 if ( ( normalizedBaseName . includes ( "pan" ) && ! normalizedBaseName . includes ( "company" ) ) || ( / i n c o m e t a x d e p a r t m e n t / i. test ( extractedText ) && / p e r m a n e n t a c c o u n t n u m b e r / i. test ( extractedText ) ) ) {
153- return {
154- purpose : "identity" ,
155- expectedFolders : [ "Identity" ] ,
156- matchedByRule : true ,
157- renameLabel : "PAN_Card"
158- } ;
198+ return { purpose : "identity" , expectedFolders : [ "Identity/PAN" ] , matchedByRule : true , renameLabel : "PAN_Card" } ;
159199 }
160200
161201 // Specific Deep Content Rule: Passport
162202 if ( normalizedBaseName . includes ( "passport" ) || ( / r e p u b l i c o f i n d i a / i. test ( extractedText ) && / p a s s p o r t / i. test ( extractedText ) ) ) {
163- return {
164- purpose : "identity" ,
165- expectedFolders : [ "Identity" ] ,
166- matchedByRule : true ,
167- renameLabel : "Passport"
168- } ;
203+ return { purpose : "identity" , expectedFolders : [ "Identity/Passport" ] , matchedByRule : true , renameLabel : "Passport" } ;
204+ }
205+
206+ // Specific Deep Content Rule: Offer Letter
207+ if ( / o f f e r [ \s . _ - ] ? l e t t e r / i. test ( normalizedBaseName ) || / a p p o i n t m e n t [ \s . _ - ] ? l e t t e r / i. test ( normalizedBaseName ) || ( / o f f e r o f e m p l o y m e n t / i. test ( extractedText ) ) ) {
208+ let label = "Offer_Letter" ;
209+ if ( dateInfo . year ) label += `_${ dateInfo . year } ` ;
210+ return { purpose : "legal" , expectedFolders : [ "Legal/Offer_Letters" ] , matchedByRule : true , renameLabel : label } ;
169211 }
170212
171- // If it's a known code extension, prefer the code category/purpose to avoid keyword misclassification
172213 if ( CODE_EXTENSIONS . has ( normalizedExtension ) ) {
173- return {
174- purpose : "code" ,
175- expectedFolders : DEFAULT_FOLDERS_BY_CATEGORY . code ,
176- matchedByRule : false ,
177- renameLabel : null
178- } ;
214+ return { purpose : "code" , expectedFolders : DEFAULT_FOLDERS_BY_CATEGORY . code , matchedByRule : false , renameLabel : null } ;
179215 }
180216
181217 for ( const rule of PURPOSE_RULES ) {
182218 if ( rule . pattern . test ( normalizedBaseName ) ) {
183- return {
184- purpose : rule . purpose ,
185- expectedFolders : rule . expectedFolders ,
186- matchedByRule : true ,
187- renameLabel : rule . renameLabel
188- } ;
219+ return { purpose : rule . purpose , expectedFolders : rule . expectedFolders , matchedByRule : true , renameLabel : rule . renameLabel } ;
189220 }
190221 }
191222
192223 if ( [ ".exe" , ".msi" , ".dmg" , ".pkg" , ".appx" ] . includes ( normalizedExtension ) ) {
193- return {
194- purpose : "installer" ,
195- expectedFolders : [ "Installers" ] ,
196- matchedByRule : true ,
197- renameLabel : "Installer"
198- } ;
199- }
200-
201- return {
202- purpose : category ,
203- expectedFolders : DEFAULT_FOLDERS_BY_CATEGORY [ category ] ?? DEFAULT_FOLDERS_BY_CATEGORY . other ,
204- matchedByRule : false ,
205- renameLabel : null
206- } ;
224+ return { purpose : "installer" , expectedFolders : [ "Installers" ] , matchedByRule : true , renameLabel : "Installer" } ;
225+ }
226+
227+ return { purpose : category , expectedFolders : DEFAULT_FOLDERS_BY_CATEGORY [ category ] ?? DEFAULT_FOLDERS_BY_CATEGORY . other , matchedByRule : false , renameLabel : null } ;
207228}
0 commit comments