Daily deterministic card selector using Node-RED, CSV and flow context

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.

1 Like