<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Bardach Technologies]]></title><description><![CDATA[Bardach Technologies]]></description><link>https://bardachtech.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sun, 15 Nov 2026 18:45:07 GMT</lastBuildDate><atom:link href="https://bardachtech.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Effective hourly rate on a retainer, explained: the formula that shows when a client stops paying]]></title><description><![CDATA[A monthly retainer has a contracted rate (fee ÷ hours included) and an effective rate (fee ÷ hours actually used). The contract shows the first. Your…]]></description><link>https://bardachtech.hashnode.dev/effective-hourly-rate-on-a-retainer-explained-the-formula-that-shows-when-a-client-stops-paying</link><guid isPermaLink="true">https://bardachtech.hashnode.dev/effective-hourly-rate-on-a-retainer-explained-the-formula-that-shows-when-a-client-stops-paying</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[business]]></category><category><![CDATA[Math]]></category><category><![CDATA[Tutorial]]></category><dc:creator><![CDATA[Sebastian Bardach]]></dc:creator><pubDate>Sat, 14 Nov 2026 20:04:37 GMT</pubDate><content:encoded><![CDATA[<p>A monthly retainer has a contracted rate (fee ÷ hours included) and an effective rate (fee ÷ hours actually used). The contract shows the first. Your margin depends on the second.</p>
<h2>The formula</h2>
<pre><code class="language-plaintext">effective rate   = fee ÷ hours used
cost             = hours used × team cost per hour
margin           = fee − cost
utilization      = hours used ÷ hours included
overage value    = max(0, used − included) × (fee ÷ included)
break-even hours = fee ÷ team cost per hour
</code></pre>
<p>As a function:</p>
<pre><code class="language-js">function retainer({ fee, included, used, costPerHour }) {
  if (!(fee &gt;= 0 &amp;&amp; included &gt; 0 &amp;&amp; used &gt;= 0 &amp;&amp; costPerHour &gt;= 0)) return null;
  const cost = used * costPerHour;
  const over = Math.max(0, used - included);
  const contracted = fee / included;
  return {
    contracted,
    effective: used &gt; 0 ? fee / used : null,
    margin: fee - cost,
    marginPct: fee &gt; 0 ? (fee - cost) / fee * 100 : null,
    utilization: used / included * 100,
    overageValue: over * contracted,
    breakEvenHours: costPerHour &gt; 0 ? fee / costPerHour : null,
  };
}

console.log(retainer({ fee: 4000, included: 40, used: 46, costPerHour: 60 }));
// contracted 100, effective 86.96, margin 1240, marginPct 31, utilization 115, overageValue 600, breakEvenHours 66.67
</code></pre>
<p>A 4,000 retainer for 40 hours is 100 an hour on paper. The client used 46: the effective rate is 86.96, the margin falls from 1,600 (40 %, if they had used exactly 40) to 1,240 (31 %), and six hours worth 600 at the contracted rate went unbilled.</p>
<h2>Three details in the code</h2>
<ul>
<li><code>effective</code> <strong>is</strong> <code>null</code> <strong>at zero hours, not infinity.</strong> A month with no work is not an infinitely profitable month; it is a month with no rate to report.</li>
<li><strong>Overage is valued at the contracted rate, not the cost rate.</strong> The 600 is what the client would have paid for those hours under the contract, which is the number to bring to a conversation about scope. At cost it would be 360.</li>
<li><strong>Break-even hours ignore the hours included.</strong> At 60 an hour of team cost, the retainer stops covering cost at 66.67 hours, whatever the contract says. That is the line to watch on a client who keeps asking for "one more thing".</li>
</ul>
<h2>Utilization over 100 % is not good news</h2>
<p>On a timesheet, a team at 115 % looks busy. On a retainer it means the client is getting 15 % more than they pay for. Track the effective rate per client per month, and the drift shows up before the quarterly review.</p>
<h2>Try it in the browser</h2>
<p>The same function runs in our free calculator, nothing stored: <a href="https://tools.bardach-technologies.com/tool/retainer-profitability?utm_source=syn-hashnode">retainer profitability calculator</a>.</p>
<p><em>The formulas and every number above were checked by running the code shown. We built the free calculator linked above.</em></p>
]]></content:encoded></item><item><title><![CDATA[The freelance hourly rate formula, explained: one division and the input that moves it most]]></title><description><![CDATA[A freelance hourly rate is one division: the revenue you need over a year, divided by the hours a client will actually pay for. Both halves have a trap…]]></description><link>https://bardachtech.hashnode.dev/the-freelance-hourly-rate-formula-explained-one-division-and-the-input-that-moves-it-most</link><guid isPermaLink="true">https://bardachtech.hashnode.dev/the-freelance-hourly-rate-formula-explained-one-division-and-the-input-that-moves-it-most</guid><category><![CDATA[JavaScript]]></category><category><![CDATA[Freelancing]]></category><category><![CDATA[Math]]></category><category><![CDATA[Career]]></category><dc:creator><![CDATA[Sebastian Bardach]]></dc:creator><pubDate>Sat, 14 Nov 2026 02:09:28 GMT</pubDate><content:encoded><![CDATA[<p>A freelance hourly rate is one division: the revenue you need over a year, divided by the hours a client will actually pay for. Both halves have a trap, and the second one is where most rates go wrong.</p>
<h2>The formula</h2>
<pre><code class="language-plaintext">revenue needed = income goal ÷ (1 − tax set-aside) + business costs
billable hours = (52 − weeks off) × hours per week × billable share
hourly rate    = revenue needed ÷ billable hours
</code></pre>
<p>As a function:</p>
<pre><code class="language-js">function hourlyRate({ income, tax, costs, weeksOff, hoursPerWeek, billable }) {
  if (!(tax &gt;= 0 &amp;&amp; tax &lt; 100 &amp;&amp; billable &gt; 0 &amp;&amp; billable &lt;= 100 &amp;&amp; hoursPerWeek &gt; 0
        &amp;&amp; weeksOff &gt;= 0 &amp;&amp; weeksOff &lt; 52 &amp;&amp; income &gt;= 0 &amp;&amp; costs &gt;= 0)) return null;
  const revenue = income / (1 - tax / 100) + costs;
  const hours = (52 - weeksOff) * hoursPerWeek * billable / 100;
  return { revenue, hours, hourly: revenue / hours, day: revenue / hours * 8 };
}

console.log(hourlyRate({ income: 60000, tax: 25, costs: 6000, weeksOff: 6, hoursPerWeek: 40, billable: 60 }));
// revenue 86000, hours 1104, hourly 77.90, day 623.19
</code></pre>
<p>An income goal of 60,000 with 25 % set aside for tax needs 80,000 before tax, plus 6,000 of costs: 86,000. Forty-six working weeks of 40 hours at 60 % billable give 1,104 paid hours. 86,000 ÷ 1,104 = 77.90 an hour.</p>
<h2>Trap 1: dividing by (1 − t), not multiplying by (1 + t)</h2>
<p>To keep 60,000 after setting aside 25 %, you need 60,000 ÷ 0.75 = 80,000. Multiplying by 1.25 gives 75,000, and 25 % of 75,000 leaves you 56,250. The gross-up is a division.</p>
<h2>Trap 2: the billable share</h2>
<p>Change one input at a time from the example above:</p>
<table>
<thead>
<tr>
<th>change</th>
<th>billable hours</th>
<th>hourly rate</th>
</tr>
</thead>
<tbody><tr>
<td>example (60 % billable, 6 weeks off)</td>
<td>1,104</td>
<td>77.90</td>
</tr>
<tr>
<td>10 weeks off instead of 6</td>
<td>1,008</td>
<td>85.32</td>
</tr>
<tr>
<td>45 % billable instead of 60 %</td>
<td>828</td>
<td>103.86</td>
</tr>
</tbody></table>
<p>Four extra weeks off raise the rate by about 10 %. Dropping from 60 % to 45 % billable raises it by 33 %. Sales calls, proposals, invoicing and learning all sit in the unbilled share, and a first-year freelancer rarely bills more than half of their working hours. If you guess the billable share, guess low.</p>
<h2>The day rate is a multiplication, not a new formula</h2>
<p><code>day = hourly × hours per day</code>. It is a presentation choice: some clients compare day rates, and quoting one hides the billable-share maths from them, which is fine as long as it does not hide it from you.</p>
<h2>Try it in the browser</h2>
<p>The same function runs in our free calculator, nothing stored: <a href="https://tools.bardach-technologies.com/tool/freelance-hourly-rate-calculator?utm_source=syn-hashnode">freelance hourly rate calculator</a>.</p>
<p><em>The formulas and every number above were checked by running the code shown. We built the free calculator linked above.</em></p>
]]></content:encoded></item><item><title><![CDATA[2,512 product ideas, most of them killed: designing a kill-reason dataset (free 100-row sample)]]></title><description><![CDATA[For a month I screened ideas for small software and digital products: SaaS, web templates, books, downloads, content channels, e-commerce. 2,512 ideas went…]]></description><link>https://bardachtech.hashnode.dev/2-512-product-ideas-most-of-them-killed-designing-a-kill-reason-dataset-free-100-row-sample</link><guid isPermaLink="true">https://bardachtech.hashnode.dev/2-512-product-ideas-most-of-them-killed-designing-a-kill-reason-dataset-free-100-row-sample</guid><category><![CDATA[Python]]></category><category><![CDATA[Data Science]]></category><category><![CDATA[#OpenData]]></category><category><![CDATA[startup]]></category><dc:creator><![CDATA[Sebastian Bardach]]></dc:creator><pubDate>Fri, 13 Nov 2026 23:32:58 GMT</pubDate><content:encoded><![CDATA[<p>For a month I screened ideas for small software and digital products: SaaS, web templates, books, downloads, content channels, e-commerce. 2,512 ideas went in. Most of them failed, and that turned out to be the useful part, because every failure was stored with the rule that stopped it.</p>
<p>I published a free 100-row sample of that table under CC BY 4.0: <a href="https://huggingface.co/datasets/bardachtech/screened-product-ideas">screened-product-ideas on Hugging Face</a>. This post is about the schema, the three design choices that made it worth querying, and a few lines of Python to read it.</p>
<h2>The columns</h2>
<table>
<thead>
<tr>
<th>column</th>
<th>what it holds</th>
</tr>
</thead>
<tbody><tr>
<td><code>id</code>, <code>title</code>, <code>field</code>, <code>created</code></td>
<td>the idea and when it was generated</td>
</tr>
<tr>
<td><code>stage</code>, <code>status</code>, <code>verdict</code></td>
<td>how far it got (S1, S2, …) and how it ended: parked, killed, still open</td>
</tr>
<tr>
<td><code>screen_total</code>, <code>screen_line</code></td>
<td>the score at the field screen and the pass line it had to beat</td>
</tr>
<tr>
<td><code>desk_total</code>, <code>c1</code> … <code>c10</code></td>
<td>the deeper ten-criterion scores, only for ideas that got that far</td>
</tr>
<tr>
<td><code>weakest_criterion</code>, <code>fatal_codes</code></td>
<td>which criterion sank it</td>
</tr>
<tr>
<td><code>kill_codes</code></td>
<td>one or more coded reasons, separated by <code>;</code></td>
</tr>
<tr>
<td><code>kill_reason</code></td>
<td>the same reason as a sentence, for humans</td>
</tr>
</tbody></table>
<h2>Design choice 1: code the reason at the moment of the kill</h2>
<p>Free-text reasons are easy to write and painful to count. Every kill in this pipeline writes a short code from a fixed list (<code>below_line</code>, <code>sibling_family</code>, <code>crowded_no_wedge</code>, <code>not_sharp</code>, <code>market_too_small</code>, …) next to the sentence. The sentence is for reading one row; the code is for reading all of them.</p>
<p>An idea can fail for more than one reason, so <code>kill_codes</code> is multi-valued: <code>crowded_no_wedge;llm_substitute</code> means "existing products already do this, and a chatbot answer would replace it too". Counting has to split the field:</p>
<pre><code class="language-python">import csv, io, urllib.request
from collections import Counter

URL = ("https://huggingface.co/datasets/bardachtech/screened-product-ideas"
       "/resolve/main/idea-database-sample-100.csv")

with urllib.request.urlopen(URL) as resp:
    rows = list(csv.DictReader(io.TextIOWrapper(resp, encoding="utf-8")))

codes = Counter(code
                for row in rows
                for code in row["kill_codes"].split(";") if code)
print(len(rows), "rows")
for code, n in codes.most_common(6):
    print(f"{code:&lt;20} {n}")
</code></pre>
<pre><code class="language-plaintext">100 rows
below_line           43
sibling_family       25
crowded_no_wedge     9
weak_evidence        4
not_a_unit           4
not_sharp            4
</code></pre>
<p>(On macOS with the <a href="http://python.org">python.org</a> build, run <code>Install Certificates.command</code> once if <code>urlopen</code> fails with a certificate error, or download the CSV and open it locally.)</p>
<h2>Design choice 2: empty means "not measured", not zero</h2>
<p>Only ideas that passed the first screens got the ten-criterion scores. In the sample, 2 of 100 rows have <code>c1</code> … <code>c10</code> filled. If those blanks were stored as 0, every average over the column would be wrong and every "lowest score" query would return ideas that were never scored. The CSV keeps them empty, and the reading code must keep them apart:</p>
<pre><code class="language-python">scored = [r for r in rows if r["c1"] != ""]
print("rows with C1-C10 scores:", len(scored))   # 2
</code></pre>
<h2>Design choice 3: store the line next to the score</h2>
<p>Different fields were screened with different scales. In the sample, one digital-download idea has <code>screen_total</code> 47 against a line of 55, a book idea has 3.12 against 3.5. Comparing raw totals across rows would rank a 47 above everything on the 1-to-5 scale. Because each row carries its own <code>screen_line</code>, the honest comparison is the gap to the line:</p>
<pre><code class="language-python">def margin(row):
    if not row["screen_total"]:
        return None
    return float(row["screen_total"]) / float(row["screen_line"]) - 1

failed = [r for r in rows if margin(r) is not None and margin(r) &lt; 0]
near_misses = sorted(failed, key=margin, reverse=True)[:5]   # closest to the line first
</code></pre>
<p>A margin of −0.05 means "5 % short of the line", whatever the scale. The closest miss in the sample is a content idea at 3.18 against 3.2.</p>
<h2>What the codes say across all 2,512 ideas</h2>
<p>Counting codes over the whole table (any position in <code>kill_codes</code>; these counts come from the export report of all 2,512 rows, the sample alone gives the numbers above), the top four are:</p>
<table>
<thead>
<tr>
<th>code</th>
<th>ideas</th>
<th>meaning</th>
</tr>
</thead>
<tbody><tr>
<td><code>below_line</code></td>
<td>787</td>
<td>scored under the pass line of its stage</td>
</tr>
<tr>
<td><code>sibling_family</code></td>
<td>770</td>
<td>settled because a family of near-identical ideas had already failed</td>
</tr>
<tr>
<td><code>crowded_no_wedge</code></td>
<td>228</td>
<td>existing products already do the job, nothing structural to win with</td>
</tr>
<tr>
<td><code>not_sharp</code></td>
<td>207</td>
<td>the card was too vague: no clear trigger, buyer or wedge</td>
</tr>
</tbody></table>
<p>The second line is the lesson I did not expect. An idea generator produces many variants of one idea. Without a "sibling" rule, the screen would have spent its budget re-checking the same dead end 770 times. If you build any kind of generate-and-filter pipeline, group near-duplicates before you score them.</p>
<h2>Limits of the data</h2>
<ul>
<li>The scores are rubric scores from the screen run between 10 October and 10 November 2026, not sales results. A killed idea is not proven bad; it failed this screen.</li>
<li>Titles were scrubbed: rows with personal or third-party sources were dropped before export.</li>
<li>The sample is 100 rows stratified by field, so its code counts differ from the full table's.</li>
</ul>
<h2>Links</h2>
<p>The sample, its data card and the licence: <a href="https://huggingface.co/datasets/bardachtech/screened-product-ideas">huggingface.co/datasets/bardachtech/screened-product-ideas</a>. About the author: the same project runs a set of free browser calculators, and this screen is how it picks what to build next: <a href="https://tools.bardach-technologies.com/?utm_source=syn-hashnode">tools.bardach-technologies.com</a>.</p>
<p><em>The counts and code were checked by running them against the published CSV. I built the pipeline and published the free sample linked above.</em></p>
]]></content:encoded></item><item><title><![CDATA[Your day rate is a utilisation problem: a worked example]]></title><description><![CDATA[Take two freelancers who want the same 60,000 a year after tax. One bills 60 % of their working hours, the other 45 %. The first can charge 77.90 an hour…]]></description><link>https://bardachtech.hashnode.dev/your-day-rate-is-a-utilisation-problem-a-worked-example</link><guid isPermaLink="true">https://bardachtech.hashnode.dev/your-day-rate-is-a-utilisation-problem-a-worked-example</guid><category><![CDATA[Freelancing]]></category><category><![CDATA[business]]></category><category><![CDATA[Career]]></category><dc:creator><![CDATA[Sebastian Bardach]]></dc:creator><pubDate>Thu, 12 Nov 2026 11:57:42 GMT</pubDate><content:encoded><![CDATA[<p>Take two freelancers who want the same 60,000 a year after tax. One bills 60 % of their working hours, the other 45 %. The first can charge 77.90 an hour; the second needs 103.86 for the same life: a rate 33 % higher, with the same skills and the same goal.</p>
<h2>The formula in four lines</h2>
<ol>
<li>Revenue needed = income goal ÷ (1 − tax set-aside) + business costs → 60,000 ÷ 0.75 + 6,000 = 86,000.</li>
<li>Working weeks = 52 − weeks off → 46 (six weeks off assumed).</li>
<li>Billable hours = working weeks × hours per week × billable share → 46 × 40 × 0.60 = 1,104.</li>
<li>Hourly rate = revenue needed ÷ billable hours → 77.90. Day rate = revenue needed ÷ billable days (46 × 5 × 0.60 = 138) → 623.19.</li>
</ol>
<h2>What to do with it</h2>
<ul>
<li>Quote day rates for project work: clients compare days, not hours, and you stop selling admin time for free.</li>
<li>Track unbilled time for one month. If your billable share turns out under 50 %, fixing that beats any rate increase.</li>
<li>Re-run it when costs, taxes or holidays change, not only when you feel underpaid.</li>
</ul>
<p>Run your own numbers, free and without sign-up: <a href="https://clientsroster.bardach-technologies.com/tool/?utm_source=syn-hashnode">freelance hourly rate calculator</a>.</p>
<p><em>Disclosure: I built the calculator linked above and make Clientsroster, a Notion client hub for freelancers.</em></p>
]]></content:encoded></item><item><title><![CDATA[What does one plate cost? Building a menu costing sheet from scratch]]></title><description><![CDATA[Picture a bestseller that "makes good money". Costed properly it runs at 41 % food cost, because nobody re-costed it after the cream price went up. This…]]></description><link>https://bardachtech.hashnode.dev/what-does-one-plate-cost-building-a-menu-costing-sheet-from-scratch</link><guid isPermaLink="true">https://bardachtech.hashnode.dev/what-does-one-plate-cost-building-a-menu-costing-sheet-from-scratch</guid><category><![CDATA[excel]]></category><category><![CDATA[google sheets]]></category><category><![CDATA[Small business]]></category><dc:creator><![CDATA[Sebastian Bardach]]></dc:creator><pubDate>Thu, 12 Nov 2026 11:55:56 GMT</pubDate><content:encoded><![CDATA[<p>Picture a bestseller that "makes good money". Costed properly it runs at 41 % food cost, because nobody re-costed it after the cream price went up. This post builds the sheet that catches that, step by step.</p>
<h2>The layout</h2>
<table>
<thead>
<tr>
<th>Sheet</th>
<th>Rows</th>
<th>Columns</th>
</tr>
</thead>
<tbody><tr>
<td>Ingredients</td>
<td>one per purchased item</td>
<td>A name, B pack size, C pack unit, D pack price, E usable %, F <strong>cost per usable unit</strong></td>
</tr>
<tr>
<td>Recipes</td>
<td>one per ingredient per dish</td>
<td>A dish, B ingredient, C quantity, D unit, E <strong>unit cost</strong>, F <strong>line cost</strong></td>
</tr>
<tr>
<td>Dishes</td>
<td>one per menu item</td>
<td>A dish, B portions, C waste %, D extras, E net price, F target %</td>
</tr>
</tbody></table>
<h2>Step 1 — price every ingredient per usable unit</h2>
<p>Cost per usable unit = pack price ÷ (pack size × usable share), in Ingredients!F: <code>=D2/(B2*E2)</code>. Trim is real cost: a 12 kg case of chicken thighs at 54.00 that is 75 % usable after trimming costs 6.00 per usable kg, not 4.50. Keep recipe quantities in the same unit as the pack (kg with kg).</p>
<h2>Step 2 — recipes as rows, not as cells</h2>
<p>One row per ingredient per dish lets two short formulas cost everything:</p>
<pre><code class="language-plaintext">unit cost (Recipes!E2):  =XLOOKUP(B2, Ingredients!A:A, Ingredients!F:F)
line cost (Recipes!F2):  =C2*E2
</code></pre>
<h2>Step 3 — from batch to plate</h2>
<pre><code class="language-plaintext">plate cost = SUMIF(Recipes!A:A, dish, Recipes!F:F) × (1 + waste %) ÷ portions + extras
</code></pre>
<p>Example: a curry batch of 30.00, 4 % waste, 6 portions, 0.50 extras (rice garnish, bread) → 30 × 1.04 ÷ 6 + 0.50 = 5.70 a plate.</p>
<h2>Step 4 — the two ratios that matter</h2>
<ul>
<li>Food cost % = plate cost ÷ net menu price → 5.70 ÷ 16.00 = 35.6 %.</li>
<li>Price for a target = plate cost ÷ target % → 5.70 ÷ 0.30 = 19.00. At 16.00 this dish sits almost 6 points above a 30 % target: re-price it, re-portion it, or accept it as a traffic driver on purpose.</li>
</ul>
<h2>Step 5 — the monthly check</h2>
<p>Update pack prices from the month's invoices; the menu re-costs itself. Compare with actual food cost (opening stock + purchases − closing stock, ÷ food sales) to see whether the kitchen matches the recipes.</p>
<h2>Common mistakes</h2>
<p>Menu price including VAT/sales tax; costing the bought weight instead of the usable weight; grams against a per-kg price; forgetting extras; never re-costing after a supplier price rise.</p>
<p>Free calculator for one dish, no sign-up: <a href="https://recipescosting.bardach-technologies.com/tool/food-cost-menu-price-calculator?utm_source=syn-hashnode">food cost and menu price calculator</a>.</p>
<p><em>Disclosure: I built the calculator linked above and make Larderly, a ready-made menu costing sheet.</em></p>
]]></content:encoded></item></channel></rss>