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

**URL:** <https://discourse.nodered.org/t/daily-deterministic-card-selector-using-node-red-csv-and-flow-context/101669>\
**Category:** Share Your Projects\
**Created:** [22 August 2026 06:10 UTC](https://discourse.nodered.org/t/daily-deterministic-card-selector-using-node-red-csv-and-flow-context/101669 "2026-08-22T06:10:23Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![gokimedia](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/gokimedia/32/108235_2.png) [@gokimedia](https://discourse.nodered.org/u/gokimedia)\
**Post date:** [22 August 2026 06:10 UTC](https://discourse.nodered.org/t/daily-deterministic-card-selector-using-node-red-csv-and-flow-context/101669/1 "2026-08-22T06:10:23Z")

</div>

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

```plaintext
[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](https://deckaura.com/pages/ai-data-sources) 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:

```plaintext
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

```javascript
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

```javascript
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.
