<?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[FranchiseNegosyo Engineering Notes]]></title><description><![CDATA[FranchiseNegosyo Engineering Notes]]></description><link>https://franchisenegosyo.hashnode.dev</link><image><url>https://cdn.hashnode.com/uploads/logos/6ac4bf1c85db1d14579512a1/b3856d5b-71c4-4b7a-a977-914709445402.png</url><title>FranchiseNegosyo Engineering Notes</title><link>https://franchisenegosyo.hashnode.dev</link></image><generator>RSS for Node</generator><lastBuildDate>Sat, 17 Oct 2026 09:12:13 GMT</lastBuildDate><atom:link href="https://franchisenegosyo.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[How a Franchise Comparison Directory Keeps Cost Data in One Shape]]></title><description><![CDATA[Anyone who has tried to compare two franchise offers knows the real problem is rarely missing data, because the numbers usually exist somewhere, scattered across brochures, chat replies and slide deck]]></description><link>https://franchisenegosyo.hashnode.dev/how-a-franchise-comparison-directory-keeps-cost-data-in-one-shape</link><guid isPermaLink="true">https://franchisenegosyo.hashnode.dev/how-a-franchise-comparison-directory-keeps-cost-data-in-one-shape</guid><category><![CDATA[PostgreSQL]]></category><category><![CDATA[Next.js]]></category><category><![CDATA[data-modeling]]></category><dc:creator><![CDATA[FranchiseNegosyo]]></dc:creator><pubDate>Tue, 06 Oct 2026 09:32:12 GMT</pubDate><content:encoded><![CDATA[<p>Anyone who has tried to compare two franchise offers knows the real problem is rarely missing data, because the numbers usually exist somewhere, scattered across brochures, chat replies and slide decks that each put the figures in a different order and use different words for the same thing. A comparison directory solves this less with clever search and more with a disciplined data model, and the approach behind <a href="https://franchisenegosyo.com/">FranchiseNegosyo, a franchise comparison directory for the Philippines</a> is a useful case study in how a small, strict schema can drive thousands of consistent pages.</p>
<h2>One shape for every brand</h2>
<p>The core decision is that every brand record carries the same four cost fields, and they always render in the same sequence: the franchise fee, the total investment range, the royalty, and the payback period. That order is deliberate, since it follows the way a buyer actually reasons, starting with the entry ticket, widening to the full capital outlay, then moving to the ongoing cost of running the business and finally asking how long it takes to earn the money back.</p>
<p>Storing these values well matters more than displaying them. Money is kept as whole pesos in integer columns rather than floating point numbers, which avoids rounding drift when ranges are sorted or bucketed later. The total investment is stored as a minimum and a maximum instead of a single free text string, so a brand quoting a band of figures can still be placed correctly on a budget filter. Royalty is modelled as a typed value that can be a percentage of sales, a fixed monthly amount, or explicitly none, because flattening those three cases into one number quietly misleads readers. Payback is stored as a month range with a nullable flag for brands that do not publish an estimate, and the page says so plainly instead of guessing.</p>
<p>The rendering layer then becomes almost boring, which is exactly the goal. A single cost component in the Next.js front end takes the typed record and prints the four rows in fixed order with consistent labels, so a reader who has learned to scan one page can scan every other one without relearning the layout. Anyone browsing the <a href="https://franchisenegosyo.com/franchises">full list of Philippine franchise brands</a> sees the same rhythm repeated on each card, and that sameness is what makes side by side comparison possible at all.</p>
<h2>Hubs as queries, not hand-written pages</h2>
<p>The second idea is that collection pages are views over one dataset rather than separately maintained content. A PostgreSQL database holds the brand records together with a small set of lookup tables for industries, formats, budget bands and locations, and each hub page is essentially a saved query with a template around it.</p>
<p>Budget hubs illustrate this well. Instead of letting every page invent its own thresholds, the system defines a fixed ladder of budget bands, and a brand belongs to a band when the lower end of its investment range fits within it. The <a href="https://franchisenegosyo.com/budget">franchise budget hubs</a> are generated directly from that ladder, so adding a brand or correcting its investment figures moves it to the right band everywhere at once without anyone editing a page.</p>
<p>Industry and format hubs follow the same pattern through a taxonomy table that supports parent and child categories, which lets a broad food category contain narrower trades such as coffee or snacks while still rolling up cleanly. The <a href="https://franchisenegosyo.com/industries">franchise industries index</a> is simply the top of that tree rendered as navigation, and each child page reuses the same listing query filtered by a different taxonomy key.</p>
<h2>Crossing dimensions without exploding the site</h2>
<p>The interesting engineering problem appears when dimensions are combined, because budget times city times industry produces far more possible pages than there is meaningful content. The rule used here is that a combined page is only generated when the underlying query returns enough real brands to be useful, and the threshold is checked at build and revalidation time rather than hard coded per page. A page such as <a href="https://franchisenegosyo.com/budget/100k/negros-oriental">franchises under 100,000 pesos in Negros Oriental</a> exists because the intersection of that budget band and that province actually holds listings, while empty intersections are never emitted and never enter the sitemap.</p>
<p>Sorting is the last quiet trap. When many listings share the same quality score or the same investment floor, a paginated query without a unique final sort key can return the same brand on two pages and skip another entirely, so every ordered listing query ends with the primary key as a tiebreaker to keep pagination stable.</p>
<h2>Why consistency is the feature</h2>
<p>All of this structure exists to serve a reader who wants to put two offers next to each other and see where they genuinely differ. The practical side of that comparison, including which questions to ask once the numbers line up, is covered in a guide on <a href="https://franchisenegosyo.com/guides/how-to-compare-two-philippine-franchise-offers">how to compare two Philippine franchise offers</a>, and it works precisely because the data underneath was forced into one shape from the start. For developers building any kind of comparison product, the lesson travels well: decide the canonical order of the facts that matter, type them strictly, and let every collection page be a query rather than a document someone has to remember to update.</p>
]]></content:encoded></item></channel></rss>