Founder of Shanraq.org. I distinguish between economy and greed—Aristotle called the latter "chrematistics." From this distinction, in my opinion, grows almost everything else: both the structure of the world and our view of it.
Shanraq’s open course teaches Python through one continuous project. Learners collect data from official sources, validate and analyse it, then turn their work into a reproducible report. Here is who the 57 lessons are for, how the learning path works, and why money is a subject to investigate rather than a conclusion supplied in advance.
Lesson fifty-three of the Python course. Describe data shapes in signatures, narrow `None` with a check, and run `mypy` so incompatible values are found before the program executes.
Lesson fifty-five and the end of the introductory Python course. Combine checks into one gate, build an immutable report directory, publish only after success, and expose verifiable source and build information to the reader.
Lesson fifty-four of the Python course. Separate direct dependencies from a complete lock file, pin the Python environment, and rebuild it cleanly so the project can still run a year from now.
Lesson fifty-two of the Python course. Turn project rules into automated tests: check the normal case, boundaries, and expected failures, then run the whole suite with one command.
Lesson fifty-one of the Python course. Separate an observation, a computed result, and a causal explanation. Code can verify a value and reproduce a calculation, but plausible causal prose remains a hypothesis until it has independent evidence and human review.
Lesson fifty of the Python course. Give the model a small table with stable source identifiers, accept structured claims, and compare every number with its source row. The model proposes wording; our code inserts the verified value, unit, and year into the final text.
Lesson forty-nine of the Python course. Ask the model for JSON governed by a schema instead of free-form prose, parse it with the standard `json` module, and validate it with `jsonschema`. Three separate gates—syntax, structure, and facts—keep a plausible-looking error out of the report.
Lesson forty-eight of the Python course. Stop composing a model request as one disposable line: separate role, task, data, and constraints; pin temperature, seed, and an output limit; then store the whole request beside its result. An identical request becomes a repeatable experiment, not a promise of byte-for-byte identical prose.
Lesson forty-seven of the Python course. Install Ollama, get a first answer without an API key, and follow the whole request: the program sends JSON to localhost, checks the HTTP response, and only then takes the model's text. The data stays on your computer unless you expose the local port yourself.
The forty-sixth lesson of the Python course and the end of the module on models. The same line promises an index of 146,206 for the year 2100 and minus a thousand for 1800 — and marks neither answer as doubtful. The border has to be written by hand: what it learned on, where it was checked, past which year it no longer holds.
The forty-fifth lesson of the Python course. The model answers yes or no: will next year's inflation be above ten per cent. Accuracy of 0.75 against 0.675 for a model that always says no — and the confusion matrix shows that twenty of the twenty-six high years were missed along the way.
The forty-fourth lesson of the Python course. The same model on the same data: an error of 2.48 points on the years it was taught, and 35.99 on the years it never saw. A random cut instead of a cut by time shows 5.37 and lies. A fifth-degree polynomial learns almost perfectly and is three times worse than a line.
The forty-third lesson of the Python course. A line drawn through sixteen years of prices gives an R² of 0.927 and loses to the rule "same as last year" — by a hundredth of a point. Its error is not random but has a shape: plus, minus, plus. A line in logarithms is wrong by half as much, because prices multiply.
The forty-second lesson of the Python course. In 2022 world food rose by 14.9 % — the same for everybody. Inflation among ten neighbours came out between 8.6 and 72.3 %, a difference of eight times. The two usual culprits, the rate and money, are tested with a link — and one country moves the answer, while for money it flips the sign.
The forty-first lesson of the Python course. One row of an official table carries five different numbers about the same prices, and all five are right. Which question each of them answers, how to recover the weights of the official basket that the release does not print, and why the World Bank ends up with a different figure than the news.
The fortieth lesson of the Python course. Of all the money in the country the National Bank printed nine per cent; the other ninety-one are records in bank accounts that appeared at the moment a bank made a loan. The aggregates as a matryoshka, money per tenge of GDP, the money multiplier — and what the law says about all of it.
The thirty-ninth lesson of the Python course. "Inflation is 8 %, but everything doubled for me" — both sentences can be true at once, and what tells them apart is weights. The basket, each item's share of the spending, its contribution to the growth — and why a simple mean over items promises 84.6 % where the basket grew by 69.8 %.
Lesson thirty-eight of the Python course. Add up ten yearly rates and you get 92.98 % — and that is the wrong answer: prices grew by 141.98 %, because percentages multiply. The index through `cumprod`, the multiplier, the average yearly rate, and a thousand tenge of 2014 with 413 of them left by 2024.
Lesson thirty-seven of the Python course, and the start of the module about money. Ten countries, one year: the mean is 20.48 %, the median 13.88 % — and the mean is higher than what eight of the ten actually had. `mean`, `median`, `std`, the quarters, and the rule that picks a measure before anyone has seen the result.
The thirty-sixth lesson of the Python course and the end of the module on reports. Four steps in one `main()`, a write through a temporary file, a repeat with no consequences and an exit code; the exercise adds `--dry-run`. And the rule it was all for: a source that fell over leaves yesterday's report rather than erasing it.
The thirty-fifth lesson of the Python course. `1234567.891` and `1 234 567,89` are one number and two different reports. Format strings, the local separators, the `round` that goes to the even one, `Decimal` for money, and the sum of rounded rows that does not equal the rounded sum.
The thirty-fourth lesson of the Python course. HTML built out of data: a template instead of glued strings, `html.escape` for everything that came from the table, the picture inside the file — and a page that checks itself. One file that opens in any browser, prints to PDF and travels as a single attachment.
The thirty-third lesson of the Python course. The same two numbers: from zero the bar is 1.31 times taller, with the axis starting at eight it is 4.86. The same segment of a line: 14 degrees on a wide sheet and 73 on a narrow one. Per cent against percentage points, the choice of window — and a chart that checks itself.
The thirty-second lesson of the Python course. We draw the series from the last lesson: a sheet and axes, a line, labels, a grid, a legend — and save it to a file without a single window opening. Plus the three things that break a chart for everybody: `Agg`, the order of drawing before saving, and a figure nobody closed.
The thirty-first lesson of the Python course. An index of dates can do what a list cannot: `asfreq` shows the days that are not there, `resample` folds days into weeks, `rolling` smooths. And the measured price of smoothing: the range halves, and the day of the real peak disappears from the series.
Not a lesson but a reference page, standing in front of the heavy part of the pandas module. Eight words — a table, a column, a label and a position, a mask, a missing value, a key, a type and a shape — on one table of three rows. Read it once now and come back when a word turns up in a lesson.
The thirtieth lesson of the Python course. Data arrives broken: numbers as text, a city spelled three ways, a duplicate row and an empty cell. `isna`, `to_numeric` with `errors="coerce"`, `astype`, `drop_duplicates` — and the question the lesson turns on: drop a gap or fill it, and what filling it costs.
The twenty-ninth lesson of the Python course. `merge` joins two tables on a key — and by default throws away, in silence, every row that found no match. `inner` and `left`, `indicator`, keys with different names, `join` by label, and the duplicated key that multiplies rows and the sums along with them.
The twenty-eighth lesson of the Python course. `groupby` is a loop written as a question: cut the table by a key, count each piece, put the pieces back. `agg` with column names of your own, two keys, a key the table does not hold, `transform` for a share, the difference between `count` and `size`, and the groups that go missing without a word.
The twenty-seventh lesson of the Python course. A mask is a column of yes and no, and a whole table is filtered with one. `df[mask]`, `.loc[mask, columns]`, `&`, `|`, `~` and the brackets that are not decoration, `isin`, `between`, `str` — and the labels a filter leaves exactly as they were, which is where `loc` and `iloc` finally part company.
The twenty-sixth lesson of the Python course. Somebody else's file means a separator, a comma inside the decimals, a BOM and its own mark for a missing value; in a loop you have to remember every one of them, and in `read_csv` they are arguments. `read_csv`, `to_csv`, `read_sql` and `to_sql` — and what a file does not remember about a table.
The twenty-fifth lesson of the Python course. A table whose rows carry labels does not answer the way a list does: `loc` takes by label, `iloc` by number, and two series are added by year rather than by position. `DataFrame` and `Series`, the index, slices whose ends differ, and the `NaN` that appears on its own.
The twenty-fourth lesson of the Python course. The pandas module opens with the question it ought to open with: is the library worth it. A measurement answers, not belief. Where pandas is forty times faster, where it is merely twice, where it loses to an ordinary loop, what the memory costs — and the rule for when a table is needed.
The twenty-third lesson of the Python course. A program that works through the night without you: arguments instead of editing the source, a journal with levels instead of `print`, a lock against two runs at once, and the exit code — the one thing the scheduler actually reads. Plus cron, launchd and systemd.
The twenty-second lesson of the Python course. The database does the counting: `GROUP BY` gives one row per currency, `JOIN` brings the names from a second table, and a month is a group made of the first seven characters of the date. Plus the trap: over an empty answer `avg` returns `None` rather than zero.
The twenty-first lesson of the Python course. A database instead of a file: a row is added, yesterday's stay, and running the same day again makes no duplicates. Plus two measured traps: a value glued into a query returned the whole table instead of one currency, and closing without a `commit` lost the row.
The twentieth lesson of the Python course. Politeness to somebody else's server is three concrete things: a timeout, so you do not wait for ever; a retry with the pause doubling, so you do not hit what is already down; and a cache, so you do not ask twice. Measured on a server of our own: four readings, one request.
The nineteenth lesson of the Python course. The same answer from the National Bank, read by a parser this time: 39 currencies, the rate taken by the name of a tag rather than by cutting a string. Plus two traps: `find` returns `None` for a tag that is not there, and a tag in a namespace is not found by its short name at all.
The eighteenth lesson of the Python course. A request to the National Bank: 200, `text/xml`, 11,407 bytes — and those are `bytes`, not a string. Plus the difference that breaks error handling: `HTTPError` means the server said no, `URLError` means there was no answer at all.
The seventeenth lesson of the Python course. The digest has grown into one long file, and it is time to lay it out under names. `import` runs somebody else's file whole, `__name__` tells a module from the file that was started, and your own `csv.py` beside the program quietly replaces the standard one.
The sixteenth lesson of the Python course. A hundred thousand squares take almost four megabytes as a list and 376 bytes as a generator — measured, with every number counted in. The difference is not in how you count them but in when the values appear. Plus the mistake that matters: a generator is good for one pass, and the second one quietly gives you nothing.
The fifteenth lesson of the Python course. Of the five names in the list only four are different: a set swallows the repeat without a word. In exchange, three questions about two exports take three signs: `&` for what is in both, `-` for what is gone, `|` for all of it. Plus `{}` is an empty dictionary, not an empty set.
The fourteenth lesson of the Python course. There are 45 days between the 15th of January and the 1st of March, and `timedelta` counts them, not you. Plus two traps: `01.02.2026` parses under two formats without an error — as January and as February — and a naive time cannot be taken from an aware one, which Python says outright.
The thirteenth lesson of the Python course. The World Bank's answer is a list of two elements: a service part and the records. We take it apart by hand and find what spoils a calculation silently: `null` becomes `None`, and numeric keys come back from a round trip through JSON as strings — `2025` goes out, `"2025"` comes in.
The twelfth lesson of the Python course. On the row `2022,15.0,"shock, war, logistics"`, `split(",")` returns five pieces instead of three — measured. The csv module returns three. Plus `DictReader`, the required `newline=""`, and Excel's mark, which makes `row["year"]` answer `KeyError` while the column is right there.
The eleventh lesson of the Python course. The program writes its data, reads it line by line and puts the report beside it. The path is built by `Path` rather than by gluing strings: on Windows the same path is written with a backslash. And the encoding is always named — leave it out and the system chooses it for you.
The tenth lesson of the Python course. Three of five rows from somebody else's export made it through: a comma is a full stop, while "no data" and an empty string are not numbers. `try/except/else/finally`, an error name of your own in one line, and the skill that matters — reading a traceback from its last line upwards.
The ninth lesson of the Python course. The walk over a series gets a name and works on any series: Kazakhstan at 11.52%, the world at 4.68%. Plus an `assert` that stops the calculation on an empty series, the `lambda` in `sorted(key=)`, and a measured trap: `into=[]` in a header keeps its values between calls.
The eighth lesson of the Python course. One comparison instead of thirty: a loop walks the series itself, `continue` steps over the year with no data, an accumulator gives the average — 11.52% across five years. Plus two traps: `zip` silently cuts to the shorter series, and a `while` without a way out walks off the edge.
The seventh lesson of the Python course. A number on its own says nothing: is 11.4% a lot or a little? A condition compares, and the comparison picks the road. Measured on inflation: Kazakhstan at 11.4% in 2025, the world at 3.0%. Plus the first rule of working with data: a gap in a series is not a zero, and `is None` is how you ask.
The forty-sixth lesson of the Go course. A host decides three things for you: the port arrives in PORT, the service has to listen on every address rather than on itself, and the disk of a free plan is wiped on every deploy — measured: the same password gives 303 before and 401 after. Plus a build into one static file.
The sixth lesson of the Python course. Prices by name: `get` instead of an error, `in` instead of a search, `items()` as a ready table. Measured: reaching for a missing key is a `KeyError` rather than emptiness; Counter tallies a basket in one line, and defaultdict supplies the zero where an ordinary dictionary fails.
The fifth lesson of the Python course. Prices in a list: indexes, slices, changes, the sum and the maximum — without a single loop. The difference everybody trips over is measured: sorted() returns a new list, .sort() changes the original and returns None. And a tuple that refuses to change: TypeError.
The fourth lesson of the Python course. Data from somebody else's file arrives as text, and until the text becomes a number, adding it makes no sense: "260" + "260" gives 260260. The money rule is measured too: 0.1 + 0.2 is not 0.3, while Decimal is. Plus None, the truth of "0", and the difference between == and is.
The site's second free course — 55 lessons, from data to a digest that updates itself. Why Python, why data and AI rather than the web, and what the reader will actually count: prices up 3.48 times, and what cost a thousand tenge now costs 3481. No account, no payment, in three languages.
The third lesson of the Python course. We count on a shop receipt: two divisions instead of one, the remainder, a power, and then strings — trim, replace, cut, glue. And f-strings, which put a number inside text: 52,751,740,084,727 tenge with separators and 11.4% to one decimal place.
The second lesson of the Python course. We install the language, create the project's environment and move yesterday's program into it. Measured: a package installed into the environment is invisible outside it — `ModuleNotFoundError` — which is exactly what an environment is for. The `.venv` folder weighs 17 MB and never goes into the repository.
The first lesson of the Python course. No libraries have to be installed: a ready program fetches the official numbers itself. Prices in Kazakhstan are 3.48 times higher than in 2010: what cost a thousand tenge now costs 3481. And after the same shock inflation in other countries of the region is three times lower, which is the first reason to count for yourself.
The fiftieth and last lesson of the Go course. Generics are one function instead of three, and the condition on the type is checked by the compiler: measured, a `string` does not get through before the program runs. Measured too is what they gain over `any`: 534 nanoseconds against 1801. Plus where to go from here.