Hi everyone,
I wanted a small daily automation where restarting or manually triggering the flow would not change the result for that day. Using Math.random() made testing inconsistent, so I replaced it with a deterministic date hash.
The example selects one tarot card per day, but the same pattern could be used for daily quotes, exercises, educational prompts or rotating maintenance tasks.
Flow structure
[Inject: once after startup]
-> [HTTP Request: fetch CSV]
-> [CSV: parse rows into an array]
-> [Function: store records in flow context]
[Inject: every day at 07:00]
-> [Function: select record from local date]
-> [Debug / MQTT / notification]
I used the DeckAura open tarot card meanings dataset because it provides a structured 78-card CSV with upright, reversed, love and career meanings.
The raw CSV URL used by the HTTP Request node is:
https://huggingface.co/datasets/Blacik/deckaura-tarot-card-meanings/resolve/main/tarot_card_meanings.csv
Configure the CSV node to use the first row as column names and return a single array.
Cache the parsed dataset
const cards = msg.payload;
if (!Array.isArray(cards) || cards.length !== 78) {
const received = Array.isArray(cards) ? cards.length : "non-array";
node.error(`Expected 78 records, received ${received}`, msg);
return null;
}
flow.set("tarotCards", cards);
node.status({
fill: "green",
shape: "dot",
text: `${cards.length} cards cached`
});
msg.payload = {
loaded: cards.length
};
return msg;
Select the daily record
const cards = flow.get("tarotCards");
if (!Array.isArray(cards) || cards.length === 0) {
node.error("Dataset is not loaded. Trigger the loader first.", msg);
return null;
}
const now = new Date();
const dateKey = [
now.getFullYear(),
String(now.getMonth() + 1).padStart(2, "0"),
String(now.getDate()).padStart(2, "0")
].join("-");
let hash = 2166136261;
for (let i = 0; i < dateKey.length; i++) {
hash ^= dateKey.charCodeAt(i);
hash = Math.imul(hash, 16777619);
}
const index = (hash >>> 0) % cards.length;
const card = cards[index];
msg.topic = `daily-card/${dateKey}`;
msg.payload = {
date: dateKey,
card_number: Number(card.card_number),
card_name: card.card_name,
arcana: card.arcana,
suit: card.suit || null,
element: card.element || null,
upright_meaning: card.upright_meaning,
reversed_meaning: card.reversed_meaning,
guide_url: card.guide_url
};
return msg;
The date is based on the timezone of the Node-RED host. Triggering the selector several times on the same day returns the same record. The dataset is fetched again after a restart, so the cached copy can remain disposable.
For a production flow I would also add a Switch node that checks for HTTP status 200 and a Catch node for download or parsing failures.
Would you keep a dataset this small in memory and reload it at startup, or use file-backed context? I am leaning toward startup reload because it keeps the cache simple and allows source updates to be picked up automatically.