The first rule was successful!

Hello everyone, I’m sharing a first success with DEVONthink that I’m quite happy with.

Thanks to ChatGPT and Claude, I managed to create a rule so that my company invoices, once placed in the Finder inbox, are automatically filed into the correct directory of the right database. It’s a very significant and appreciable time saver.

I can obviously still improve this, as for the moment, I will have to change the year of the directory every year. It’s a good start for me :slight_smile:

1 Like

Congratulations. Perhaps posting what you have might also be helpful to others – more so than that you have something working for you.

3 Likes

That’s a good idea; it is better if I explain the whole process or share the code (which, despite everything, is specific to my organization).

For me, code is always more interesting. In the description of a process, details can get lost because the person describing it is so involved in it.

Okay, here are some explanations. My goal is to be able to save my invoices in the Finder Inbox and have a rule in DEVONthink move them to the chosen folder structure based on the file title.

I should specify that my folder structure has a 2026 folder, followed by subfolders for each month to file my invoices. I have requested that the rule trigger upon import so I don’t have to think about it.

Here is the code :slight_smile:

on performSmartRule(theRecords)
	tell application id "DNtp"
		set baseGroup to get record with uuid "YOUR UUID FOLDER"
		repeat with theRecord in theRecords
			set n to name of theRecord as string
			set theMonth to my extractMonthFR(n)
			if theMonth is missing value then
				my addTag(theRecord, "auto:ERR-mois-non-detecte")
			else
				set folderName to "NAME FOLDER " & theMonth & " 2026"
				set monthGroup to my findChildByName(baseGroup, folderName)
				if monthGroup is missing value then
					my addTag(theRecord, "auto:ERR-dossier-manquant")
				else
					move record theRecord to monthGroup
					my addTag(theRecord, "auto:OK-" & theMonth)
				end if
			end if
		end repeat
	end tell
end performSmartRule

on findChildByName(parentGroup, childName)
	tell application id "DNtp"
		set kids to children of parentGroup
		repeat with k in kids
			if (name of k as string) is childName then return k
		end repeat
	end tell
	return missing value
end findChildByName

on extractMonthFR(n)
	set n to my toLower(n)
	if n contains "janvier" then return "Janvier"
	if n contains "février" or n contains "fevrier" then return "Février"
	if n contains "mars" then return "Mars"
	if n contains "avril" then return "Avril"
	if n contains "mai" then return "Mai"
	if n contains "juin" then return "Juin"
	if n contains "juillet" then return "Juillet"
	if n contains "août" or n contains "aout" then return "Août"
	if n contains "septembre" then return "Septembre"
	if n contains "octobre" then return "Octobre"
	if n contains "novembre" then return "Novembre"
	if n contains "décembre" or n contains "decembre" then return "Décembre"
	return missing value
end extractMonthFR

on toLower(s)
	return do shell script "echo " & quoted form of s & " | tr '[:upper:]' '[:lower:]'"
end toLower

on addTag(theRecord, newTag)
	tell application id "DNtp"
		set existingTags to tags of theRecord
		if existingTags does not contain newTag then
			set tags of theRecord to existingTags & {newTag}
		end if
	end tell
end addTag

Make sure to replace the UUID with your target directory’s ID.

“NAME FOLDER” is the name of your directory at the root level.

Did you use an AI to create that script?
Regardless:

  • why have set n to name of theRecord as string if name already is a string?
  • looping over the list of children in findChildByName is a bit awkward. You could simply do set foundChild to parentGroup's child childName. Wrap that in a try to catch a missing group.
  • myAddTag is slightly over-engineered. There’s no need to check if a tag exists already, you can’t have duplicate tags in DT anyway.
  • I’d rewrite extractMonthFR as a simple lookup, but I don’t know if that’s possible in AppleScript

Which brings me to … I’d write it in JavaScript (unsurprisingly):

const frenchMonths = {
'janvier': 'Janvier',
'fevrier': 'Fevrier',
'mars': 'Mars',
...
'aout': 'Août',
…
}
const monthsAlternation = RegExp(`(${Object.keys(frenchMonths).join('|'))}`;
function performsmartrule(records) {
  const app = Application("DEVONthink");
  const baseGroup = app.getRecordWithUuid('…');
  records.forEach( r => {
    const name = r.name();
    const cleanedName = name.toLowerCase().replaceAll('é','e').replaceAll('û','u');
    const matchMonth = cleanedName.match(monthsAlternation);
    if (!match) {
      r.tags = r.tags.concat('auto:ERR-mois-non-detecte');
      return; // continue with next record
    } 
    const folderName = `NAME FOLDER ${frenchMonths[matchMonth]} 2026`;
    let monthGroup;
    try {
     monthGorup = baseGroup.children[folderName];
     } catch {
      r.tags = r.tags.concat('auto:ERR-dossier-manquant');
      return;
    }
    app.move({record: r, to: monthGroup});
  })
}

The code is not tested at all. It’s just meant as a short example how one could achieve the same goal in a more versatile language, eg avoiding a shell call only to lower case a string and using an object instead of a subroutine to normalize the month names.

An ignoring block in AppleScript would be sufficient too:

property pMonthList : {"Janvier", "Février", "Mars", "Avril", "Mai", "Juin", "Juillet", "Août", "Septembre", "Octobre", "Novembre", "Décembre"}

on extractMonthFR(recordName)
	ignoring case and diacriticals
		repeat with theMonth in pMonthList
			if recordName contains theMonth then return theMonth
		end repeat
	end ignoring
	return missing value
end extractMonthFR
2 Likes

Interesting. And got me thinking. Apparently (Removing diacritics in JavaScript — an universal solution for international applications) one can do something similar in JS:

const cleanedName = name.normalize('NFD').replaceAll(/[\u0300-\u036f]/g, '')

The normalize call converts a diacritizied (diacriticalized?) character to a character followed by the diacritical (for any kind of diacritical, it appears), and the replace removes all the diacriticals.

Thank you for your feedback, but you’ve lost me. Unfortunately, I don’t know how to code, though I would like to learn one day.

I worked with ChatGPT and Claude to achieve this result, so I have no sense of what is considered good or bad practice. I confess that what interests me most is the final result.

To help me understand, what is the advantage of changing the code—for example, by using JavaScript?

First, making the code shorter makes it easier to understand, maintain and less susceptible to contain errors. For example, name as string when name already is a string is confusing and useless. The same goes for the “algorithm” to find the correct child group. It’s as ridiculous as one would expect from an AI, and as convoluted, too.

Short and concise should be a goal for code in any language. Even for AppleScript, which is chatty by design.

Take your extractMonthFR subroutine, for example, and compare it with what @cgrunenberg suggested and what I showed in the JavaScript code. Both implementations exploit the best features of the language. What your AI wrote, is a tedious to read and brute force “solution” to a fairly simple problem. I’m, btw, not even sure that it is needed – one ensure that the file names are correct in the first place.

Now, for the question which language to use… That depends on many things – taste, education, experience, to name but a few. I abhor AppleScript, which in my opinion is not even a programming language in the strict sense of the word, since it lacks a formal definition. I also find it difficult to write, because it is so verbose. Why would anyone want to write set a to b instead of a = b or a := b?

So, my arguments for JavaScript instead of AppleScript should be seen in the light of a personal preference.

  • It is a formally-defined language
  • It’s evolving, new features are occasionally added to it (about every two years, I’d say)
  • It’s used and useful nearly everywhere – in browsers, in servers, with macOS, Linux, and Windows
  • JavaScript has better object orientation and a rich type system.
  • It offers regular expressions and a huge set of string and array methods
  • Also, you can “introspect” objects, ie find out which attributes etc they contain. Try doing that with an AppleScript record.
2 Likes

All done with no scripting.

2 Likes

Okay, I understand and I’m taking note of those.

To be honest, I don’t know how to use either of them anyway. At this point, I can only get things done by having the AI help me.

I also thought it was possible, but I didn’t know how to explain the second part to him, which involves finding the month to put my file in.

What are some real-world example filenames for these documents?

The files always have the words “invoice, EURL, month, and year” in their names.
At the destination, we find almost the same thing, with the month and year always included.

Can you paste in an example?

I think AI coding is an amazing recent tool - particularly for small businesses looking to create internal tools.

Might I suggest that not only can AI write the script for you, but also it can teach you how it works if you ask?

As great as AI conding is, there are hidden gotchas that can lurk in code. Even if you do not have the time to write code, it’s a really good idea for you to learn how to read it. That’s the only way to know it’s not going to surprise you in a bad way when you least expect it.

3 Likes

What explanation should I give you? I’m not sure I understand.

I think @BLUEFROG asked for an example of a file name. Verbatim, not a description.

2 Likes

I was trying to do exactly what you fellows are doing but I lost all programming skills… I used to program a bit with Prolog years back. I love being able to do this without scripting and have used Perplexity and one of its models to externally achieve the results you have. But it is not to my liking and would like to do without scripting the classification and subsequently the extracting of data. I have been trying other stand alone programs, but none yet do the job and i know it can be done within Devonthink. So.. Bluefrog , can you explain what you have done with no scripting..I see informacycle as under a smart rule called File invoices. Did you set up rules to achieve the results ?

Can you explain the rules ?

I know its never too late to learn, but at 83 not sure how much time i have left to learn AppleScript