<?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[Olzhas Azirali - Build Notes]]></title><description><![CDATA[Olzhas Azirali - Build Notes]]></description><link>https://olzhasazirali.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 09:58:30 GMT</lastBuildDate><atom:link href="https://olzhasazirali.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[HTTP 200 does not tell your frontend what it received]]></title><description><![CDATA[Separate HTTP status, media type, JSON parsing and a minimal id check, with synthetic Response cases and explicit limits.]]></description><link>https://olzhasazirali.hashnode.dev/http-200-does-not-tell-your-frontend-what-it-received</link><guid isPermaLink="true">https://olzhasazirali.hashnode.dev/http-200-does-not-tell-your-frontend-what-it-received</guid><category><![CDATA[http]]></category><category><![CDATA[JavaScript]]></category><dc:creator><![CDATA[Olzhas Azirali]]></dc:creator><pubDate>Sun, 01 Nov 2026 17:13:41 GMT</pubDate><content:encoded><![CDATA[<p>A successful HTTP status can accompany an HTML page where JSON was expected, malformed JSON, or valid JSON with the wrong shape. Checking only response.ok leaves all three possibilities unresolved. A useful client contract separates the questions instead of treating success as one Boolean.</p>
<p>This note was prepared with AI assistance. Its checks use synthetic Response objects in one Node process, not captured production traffic or requests to a live API.</p>
<h2>Four checks, four different jobs</h2>
<p>Response.ok is true for HTTP statuses from 200 to 299. It does not inspect whether the response contains the data your screen needs. Source: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Response/ok">https://developer.mozilla.org/en-US/docs/Web/API/Response/ok</a></p>
<p>Content-Type describes the declared media type. A client expecting application/json can reject text/html before attempting to parse a proxy or sign-in page as data. A JSON media type is still a declaration, not proof that its body is valid. Source: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Type">https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Headers/Content-Type</a></p>
<p>Response.json() reads and parses the body. A malformed body can reject with SyntaxError. Successful parsing also allows arrays, strings and numbers, so it cannot establish an application-specific object shape. Source: <a href="https://developer.mozilla.org/en-US/docs/Web/API/Response/json">https://developer.mozilla.org/en-US/docs/Web/API/Response/json</a></p>
<h2>A deliberately small contract</h2>
<p>This helper expects a successful response containing application/json and an object with a nonblank string id. It normalizes the media type before comparison and leaves parse failures visible. The rule belongs to this example endpoint; it is not a universal JSON client.</p>
<pre><code class="language-javascript">export async function readRecord(response) {
  if (!response.ok) throw new Error(`http_${response.status}`);
  if (response.status === 204) throw new Error('body_required');
  const mediaType = (response.headers.get('content-type') ?? '')
    .split(';', 1)[0].trim().toLowerCase();
  if (mediaType !== 'application/json') throw new Error('json_expected');
  const value = await response.json();
  if (!value || typeof value !== 'object' || Array.isArray(value) ||
      typeof value.id !== 'string' || value.id.trim() === '') {
    throw new Error('record_expected');
  }
  return value;
}
</code></pre>
<p>The executed examples produce distinct outcomes. A 200 HTML response fails with json_expected. A 200 malformed JSON body rejects with SyntaxError. A 200 object containing only message fails with record_expected. A 200 response containing {"id":"sample-1"} is accepted, including when the media type includes a charset parameter. Additional cases cover an array, a blank id, a missing media type, an unsupported JSON-suffixed media type and HTTP 500.</p>
<p>A synthetic 204 response has ok equal to true but fails this helper with body_required. That is intentional for an endpoint whose contract requires a record. Another endpoint can legitimately complete with no response body: 204 means No Content. Such a client should handle that contract explicitly instead of always calling json(). Source: <a href="https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/204">https://developer.mozilla.org/en-US/docs/Web/HTTP/Reference/Status/204</a></p>
<h2>Keep failures distinguishable</h2>
<p>A local API client inspected for this note already rejects successful non-JSON responses, but converts JSON parse failures to an empty object. That fallback illustrates a separate review question: can an empty object be mistaken for valid data? Silencing a parse failure does not establish the expected shape.</p>
<p>The teaching helper accepts only application/json. An API that documents another JSON media type needs a deliberate allowlist or another appropriate media-type rule. The id check is similarly narrow: it does not validate every field, prove that the identifier exists, or establish authorization.</p>
<p>The useful test report records which layer rejected each response. Network failures, aborts, body-size limits and server-side behavior need their own relevant checks. These local cases establish only how the example handles the supplied status, headers, body and minimal shape.</p>
<p>Olzhas Azirali is an app developer in Astana and founder of AZIRAL since 2019.</p>
]]></content:encoded></item><item><title><![CDATA[A support request should ask for the smallest useful report]]></title><description><![CDATA[A proposed diagnostic template that separates environment, steps and observations before asking for attachments.]]></description><link>https://olzhasazirali.hashnode.dev/a-support-request-should-ask-for-the-smallest-useful-report</link><guid isPermaLink="true">https://olzhasazirali.hashnode.dev/a-support-request-should-ask-for-the-smallest-useful-report</guid><category><![CDATA[software development]]></category><dc:creator><![CDATA[Olzhas Azirali]]></dc:creator><pubDate>Fri, 30 Oct 2026 16:07:23 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/6ae3cc9fbe4e8f99cb67db3a/a751a618-83d9-4dcc-aff2-00e937068d46.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>By Olzhas Azirali / Олжас Азирали, founder of AZIRAL since 2019.</p>
<p>Prepared with AI assistance from the linked public support pages. The portrait and cover also use AI assistance. The report format and examples below are design proposals, separate from the existing products' support interfaces.</p>
<p>A support request has one immediate job: help another person understand a problem well enough to choose the next useful step. A large attachment is not automatically a useful report. A short description can be more informative when it identifies the version, the action and the difference between expectation and observation.</p>
<p>This creates a design problem for developers. Ask too little and the first reply becomes a questionnaire. Ask for everything and people may send unrelated personal material. A better starting point is a small report whose fields each have a clear diagnostic purpose.</p>
<h2>Start with the public instructions</h2>
<p>The <a href="https://bunker-friends-support.olzhadam1.chatgpt.site/support/">Bunker support page</a> asks for the app version, phone model and actions before an error. <a href="https://finance.aziral.com/support">Finance Bro support</a> additionally names the operating system, expected result and observed result. It also asks people to leave out passwords, verification and recovery codes, and complete unredacted bank statements.</p>
<p>These instructions provide a concrete starting point. The form design proposed here develops that small set of questions into an understandable request, without treating extra data as a default requirement.</p>
<h2>Make each field answer a question</h2>
<p>The app version and build identify the software being discussed. Device and operating system help describe the environment. Steps identify the path through the product. Expected and observed results identify the disagreement.</p>
<p>Keeping those questions separate is useful. "It failed" does not say whether the app stopped, showed a message or produced an unexpected value. A prompt such as "What appeared after the last step?" invites an observation someone can describe directly.</p>
<p>The task belongs near the beginning too. A person trying to resume a session has a different goal from someone trying to change a preference. Naming that goal helps a maintainer understand why the reported result matters.</p>
<h2>Offer a compact report format</h2>
<p>A proposed plain-text format can be enough for an initial request:</p>
<pre><code class="language-text">Task:
App version and build:
Device model and operating system:
Steps:
Expected result:
Observed result:
Optional attachment: only the relevant part, with unrelated details removed
</code></pre>
<p>This is an editorial template, not a product API or an existing support form. It can be adapted to email, an issue tracker or a small in-app screen.</p>
<p>As a fictional example, imagine a person opening a saved item, changing a display preference and returning. They expected to see the saved item again, but the preferences screen remained visible. That description gives a maintainer a sequence to investigate. It does not claim that either Bunker or Finance Bro has this defect.</p>
<h2>Request an attachment for a reason</h2>
<p>An attachment should answer a question left open by the initial report. A cropped screenshot might show the relevant message. An input-dependent problem might need a small synthetic example that preserves the troublesome format.</p>
<p>The request should name what needs to remain visible and what can be removed. Asking for a complete document when only one field is relevant makes that decision harder for the person seeking help.</p>
<p>A proposed follow-up could be: "Please show the message and the control immediately above it. Remove names, account details and unrelated content." That is clearer than an unexplained request for more information. Redaction and synthetic examples still require care; their purpose here is to reduce unnecessary sharing, without making a security guarantee.</p>
<h2>Keep observation separate from explanation</h2>
<p>A support screen can encourage people to report what they saw before asking why they think it happened. "The previous screen remained visible" is an observation. "The database is broken" is an interpretation that may lead the conversation away from the available evidence.</p>
<p>Both can have a place in a discussion, but the initial report benefits from preserving that distinction. The same applies to certainty: a person may not remember one step, and the report should allow them to say so.</p>
<h2>Review the request as a user journey</h2>
<p>Before shipping a support form, try completing it for a fictional problem. Can someone identify the app version, describe a short sequence and explain the result? Can they understand which attachment is optional and which information should stay out?</p>
<p>The useful takeaway is specific: ask for the smallest report that distinguishes the environment, action and result, then request additional material for an explained reason. This makes the next step in support easier to understand while keeping the initial burden limited.</p>
<p>Product context: Finance Bro is developed on an open-source Flutter foundation, as disclosed in its <a href="https://aziral.com/en/build-notes/finance-bro">product note</a>. Public support materials were checked on 30 October 2026; their instructions can change with the product.</p>
<p>Originally published in <a href="https://github.com/azirali/azirali/blob/main/notes/minimal-support-report.en.md">AZIRAL’s GitHub product notes</a>.</p>
]]></content:encoded></item><item><title><![CDATA[An empty CSV cell is not zero: a small Python reporting lesson]]></title><description><![CDATA[A tested Python CSV example by Olzhas Azirali (AZIRAL): preserve missing values, guard rates and show the sample behind a median.]]></description><link>https://olzhasazirali.hashnode.dev/an-empty-csv-cell-is-not-zero-a-small-python-reporting-lesson</link><guid isPermaLink="true">https://olzhasazirali.hashnode.dev/an-empty-csv-cell-is-not-zero-a-small-python-reporting-lesson</guid><category><![CDATA[Python]]></category><dc:creator><![CDATA[Olzhas Azirali]]></dc:creator><pubDate>Thu, 29 Oct 2026 23:00:40 GMT</pubDate><content:encoded><![CDATA[<p>By Olzhas Azirali, founder of AZIRAL since 2019.</p>
<p>A report can acquire false precision before it draws its first chart. Replace every empty CSV cell with zero, calculate a rate, and an incomplete observation suddenly looks like a measured result.</p>
<p>A small Python tool makes the problem easy to inspect. The public <code>insights.py</code> helper in AZIRAL's Instagram Agent Skill reads a local CSV and prints a JSON report. It keeps a blank metric separate from an observed zero. The distinction affects both action rates and the sample used for comparisons.</p>
<p>The example below is synthetic. It is a reproducible data-handling exercise, not AZIRAL account performance or a client result.</p>
<h2>Start with a data contract</h2>
<p>The helper requires two columns: post_id and format. Metrics such as reach, views, saves and shares are optional. A missing metric column represents an unknown observation for every row. A blank cell represents an unknown observation for that row. An explicit 0 records an observed zero.</p>
<p>Those meanings belong in the input contract before any calculation. Missing data could reflect an unavailable export field, an incomplete collection process or another cause. The CSV alone does not establish which explanation applies. Preserving the gap avoids silently choosing one.</p>
<p>For this helper, counts must be finite, nonnegative integers no larger than 9007199254740991. It rejects duplicate post IDs, repeated column names, mismatched row widths and malformed count values. A validation error stops the CLI with a nonzero exit code instead of producing a partial JSON report.</p>
<h2>An eight-row example</h2>
<p>Save this as <code>synthetic.csv</code> in a checkout of the public repository:</p>
<pre><code class="language-csv">post_id,format,reach,views,saves,shares
r1,reel,1000,1000,0,5
r2,reel,2000,3000,10,
r3,reel,,500,4,0
r4,reel,0,0,2,1
r5,reel,500,,5,2
c1,carousel,100,200,1,0
c2,carousel,100,400,0,
c3,carousel,50,,,1
</code></pre>
<p>Run it with Python 3.10 or later:</p>
<pre><code class="language-sh">python3 skills/aziral-instagram-insights/scripts/insights.py synthetic.csv
</code></pre>
<p>The helper uses Python's standard library. Its CSV schema assumes one account and comparable observation windows. The rows above are invented for explanation; their combinations exercise validation and calculation rules rather than describe a real account.</p>
<p>Five rows are reels and three are carousels. Some observations are intentionally absent. Row r4 records zero reach and zero views while preserving its other supplied counts. Keeping those raw values does not make a reach-based rate calculable.</p>
<h2>Guard the numerator and the denominator</h2>
<p>The calculation in the public script has a small, explicit guard:</p>
<pre><code class="language-python">def ratio(numerator, denominator, scale=1):
    if numerator is None or denominator is None or denominator &lt;= 0:
        return None
    return round(numerator / denominator * scale, 6)
</code></pre>
<p>This excerpt omits type annotations for readability; its calculation matches the source. For an action rate, the script passes 1000 as the scale and uses that post's reach as the denominator. These are action counts per 1000 reached accounts, not probabilities of unique-person conversion.</p>
<p>The results show why a single fallback value would lose information:</p>
<pre><code class="language-text">post   saves per 1000 reach   shares per 1000 reach
r1     0                     5
r2     5                     null
r3     null                  null
r4     null                  null
r5     10                    4
</code></pre>
<p>Row r1 has an observed saves count of zero and positive reach, so its saves rate is zero. Row r2 has a valid saves rate but no shares observation. Row r3 has no reach observation. Row r4 has an observed denominator of zero. Both r3 and r4 therefore have unavailable action rates, while their raw metrics retain different states.</p>
<p>In Python, the unavailable result is None. JSON serialization represents it as null. A consumer can display an unavailable marker and retain the reason in the underlying metrics instead of presenting an invented numeric result.</p>
<h2>Show how much data supports the summary</h2>
<p>The report includes observed_posts counts for each metric. In the reel group, reach is observed for four of five rows. The observed zero in r4 contributes to that coverage count. Views are also observed for four rows; the blank views cell in r5 does not contribute.</p>
<p>Coverage is useful beside a summary because different metrics can have different denominators of available observations. A group containing five posts does not imply five observations for every field. This example also has no comments column, so the observed comments count is zero. That describes data availability, not a measured total of zero comments.</p>
<p>The report retains raw metrics, derived rates, group coverage and warnings together. A reader can trace an unavailable result without guessing what a blank meant earlier in the pipeline.</p>
<h2>Keep format comparisons descriptive</h2>
<p>The reel views sample is 0, 500, 1000 and 3000. Its median is 750. The missing observation is excluded; the observed zero is included. Row r2 therefore has a views_vs_format_median value of 4.</p>
<p>That number compares the row with the supplied same-format sample. The focal post is included in the median. It is not a comparison with an independently measured account baseline, and it does not explain why a post received its views.</p>
<p>The carousel sample has two observed views values, 200 and 400, producing a median of 300. Its group contains only three posts, so the helper adds a small-sample warning. The five-post threshold is a reporting caution chosen by the tool, not a statistical validation rule. Missing observations also generate warnings.</p>
<p>Publication age, paid distribution and selective exports can still affect comparability. None of those conditions can be reconstructed from these columns. A useful next experiment would define a consistent observation window and change one content variable, while recording the relevant metrics explicitly.</p>
<h2>Make number formatting explicit</h2>
<p>The parser uses Decimal on stripped input; it does not detect the export's numeric locale. In a direct parser check, the string 1.000 becomes the integer 1. The string 1,000 is rejected. A quoted CSV field can preserve a comma inside the cell, but that does not turn it into an accepted numeric grouping convention.</p>
<p>The sample deliberately uses unformatted integers. If a source uses decimal commas or grouping separators, establish that source's convention before normalizing it. Silent punctuation removal could change the value. Validation and a documented export format work together here.</p>
<h2>A small rule with a visible consequence</h2>
<p>Keeping unknown values distinct from zero makes the report easier to audit. The output can state which values were observed, which calculations were possible and what sample supported a comparison.</p>
<p>The source and usage instructions are public:
<a href="https://github.com/azirali/instagram-agent-skill/tree/main/skills/aziral-instagram-insights">https://github.com/azirali/instagram-agent-skill/tree/main/skills/aziral-instagram-insights</a></p>
<p>About the author and other AZIRAL products:
<a href="https://aziral.com/en/olzhas-azirali">https://aziral.com/en/olzhas-azirali</a></p>
<p>Prepared with AI assistance from the public source code. The eight-row example and the parser cases described here were reproduced locally with Python 3.14.7. They demonstrate implementation behavior, not audience growth or a forecast.</p>
]]></content:encoded></item></channel></rss>