RunPage tech overview: Danfo.js integration

In my previous post – RunPage tech overview: JS Sandboxing, I discussed how I handled client-side sandboxing of script-block codes. Here I will describe how Danfo.js was integrated into RunPage API.

Why integrate Danfo.js?

Relational DB is quite popular because it allows us to query for variety of information by use of SQL statements. It is very versatile and powerful. However, that requires we host a relational DB at the server-side which further means now all data now needs to be imported into the DB and fetched back to client-side for processing. So there goes the speed and security of data features of RunPage.

Our modern browsers now do support Web SQL which allows to store all data right in the browser which solves the above two fundamental issues. But, we now then need to clear and rebuild the DB on every run. Remember every run of RunPage starts with a clean slate. Also we will need to deal provide an ORM to ensure easier working with Web SQL as SQL is inherently quite verbose and the table creation and data insertion process is pretty cumbersome. What we really need is SQL’s query power without its other pains. The answer to that is virtual tables or DataFrames.

DataFrame concept is made popular by Python’s Pandas. Danfo.js is meant to provide the power of Pandas in Javascript.

Using Proxy

ES6 have a neat little api called Proxy. I have used that extensibly while integrating Danfo.js into RunPage. If you do not know what Proxy is then in short it allows you to wrap any JS object where invoking any method, property, etc. on the wrapped object can be intercepted by your code and you have the ability to change the complete outcome. The way it is different from creating a simple wrapper is that instanceof operator will still work with your wrapper object as it would with the wrapped object, and you need not reimplement all methods etc. of the wrapped object to intercept them. A single method in your wrapper can intercept all method and property calls.

The reason I had to wrap Danfo.js’ DataFrame and Series is because of its plot api – https://danfo.jsdata.org/api-reference/plotting/line-charts. If you notice the api there the plot is a method which takes input a DOM’s id where the graph is to be plotted. However, you do not have DOM access in script-block and I do not want you to have that access since RunPage needs to be able to decide where to render your graph. I use Proxy to to replace DataFrame and Seriesplot with my own version which eventually returns a JSON which the main thread code can interpret as instructions to render the plot.

Conceptually this was simple but quite challenging to actually implement because any kind of operation and series of method calls can eventually return a DataFrame or Series object. So RunPage wraps all objects returned by the wrapper object recursively including Arrays, Functions, etc.

How the main thread renders the graphs

The JSON object returned by the proxy plot is something like below.

{
   "$renderAs":"plot",
   "data":{
      "method":"pie",
      "args":[
            // Arguments passed to plot.pie()
      ],
      "dataframeOrSeriesJ": // DataFrame or Series data as JSON
   }
}

Using these data a Danfo DataFrame or Series is recreated and the actual plot method is invoked. In the above example the invocation code will be something like dfObject.plot('generatedDomId').pie(...args).

The main thread runs a series of output formatters each of which is meant for to render a particular type of JSON. The DOM returned by the output formatter is then used another sub-routine to finally add it into appropriate location on the page. So, the plot output formatter does not know when its given DOM will be add to the page. Only when the DOM is added then the above code needs to be run to actually render the graph. For this another trick is used.

const id = uuidv4();
const {method, args, dataframeOrSeriesJ} = json.data;

const div = document.createElement('div');
div.innerHTML = `<div class="outbox plot" id="${id}"></div>
<img data-id="plotLoader" src="${dummyImg}" style="height:1px;width:1px;" />`;
div.querySelector('[data-id="plotLoader"]').addEventListener('load', function plotter() {
    const dataframe = DataFrameOrSeriesJsonToDataFrameOrSeries(dataframeOrSeriesJ);
    const plotter = dataframe.plot(id);
    const plotM = plotter[method];
    plotM.apply(plotter, args);
});

return div;

Here we generate a div with a unique id which we later pass to the plot method. The neat trick to note here is the use of img tag. The src attribute contains path to an actual one pixel image transparent. When the image is loaded the browser invokes its load event handler which further invokes the actual plot function. Since the img tag is after the plot’s div hence we can be sure that by the time img‘s load is fired the div with the given id is already available.

Problems with Danfo.js

It claims to be Pandas’ equivalent in Javascript but in reality it provides fraction of the tools when compared with Pandas. It does not even provide a ‘not’ or ‘invert’ operator when querying data.

As of now I have filed three defects which are not even assigned to anyone or has any activity yet. The first of which was filed 20 days back. So it looks like after April 2022 the activity on this codebase has suddenly died out.

Looking at the kind of issues I have found the quality of this library is very poor. For example, it seems it is meant to process only numbers, strings and boolean data. If you store other JSON objects in DataFrame then it won’t complain but silently give you wrong and unexpected results. (ref) There lot more fundamental issues which makes it unreliable. In data processing the one thing which cannot be compromised on is reliability else what is the point of processing data if you cannot be sure if you can rely on its output or not! It even has a defect filed which claims that the current latest version 1.1.1’s package on NPM contains old code – https://github.com/javascriptdata/danfojs/issues/462; and this defect is more than month old and still zero activity on it.

So many issues and on top of that it has dependency on @tensorflow/tfjs which I do not need at all.

Given all these factors I am considering ripping out Danfo.js out of RunPage and replacing it with Data-Forge. However, I will first evaluate that extensibly so as not to commit the same mistake I did by integrating with Danfo.js.

RunPage tech overview: JS Sandboxing

In this post I will explain how RunPage runs the sandboxed Javascript code in your browser.

How the sandboxing works

It achieves sandboxing by running the provided code inside a dedicated Web Worker. The worker first instantiates a constructor of Async function using the following code.

const AsyncFunction = Object.getPrototypeOf(async function(){}).constructor;

This constructor is used to a create an async function with the script-block code as the function body, and executed as below.

try {
    const f = new AsyncFunction("globalThis", "api", "\"use strict\";\n" + scriptBlockCode);
    result = f(SharedGlobal, Api);
} catch (e) {
    // Report script error
}

SharedGlobal is the globalThis object using which script-blocks on a page can share objects among themselves. Api provides access to all the apis provided by RunPage.

The worker is instantiated when the page is executed. The same worker instance is used for all script blocks on the page and is disposed when the execution is complete. So for every run a new worker instance is created and disposed-off immediately. This ensures so memory leak persists from one run to another and the states are properly reset on every run.

The use of worker also ensures that there is no DOM access, however other browser apis like fetch etc. are available.

The main thread which initiates the worker, works by passing code of each script-block to the worker one-by-one. When the code of first script-block is executed and the main thread gets the output then only it sends the code of next script-block for execution. This means on error the main thread can terminate the process then and there and skip the rest of the script-blocks. Also this allows the main thread to set a time limit for each script-block execution. If it does not hear from the worker within the set time it can destroy the worker, effectively killing that run.

Finally the use of worker ensures that the UI is not frozen while the script-block codes are running.

Challenges with the implementation

The biggest challenge is passing data between the main thread and worker. The browser auto serializes objects when passing between these two domains. However, few objects cannot be serialized like functions which have captured a scope, etc. So many complex objects are converted into JSON before sending across the domains.

Some apis provided by RunPage allow access to other blocks on the page, like file selector, input and table blocks. These actually require access to those blocks’ DOMs. The api on the worker side does this by passing instruction messages to corresponding “server” code living on the main thread. The code on the main thread access the DOM and gets appropriate data from them and passes them back to the worker.

There is one more challenge which I have not been able to solve yet. It is reporting clear precise error. Right now the stack trace is captured and presented as output to the page user but the stack trace includes code lines from the worker and hence could be confusing to end-user. Also it does not report clearly which exact line and column in the code in the script-block ran into error. Fortunately the code can still be debugged by putting a debugger statement in the script-block code and opening the browser console. The browser will correctly pause at that point and full browser debugging facility can be used.

Introducing RunPage

RunPage is a Jupyter-like portal. If are not familiar with Jupyter, then that is a server which allows you to create documents with embedded Python codes. The documents can contain normal document stuff intermixed with Python codes. The Python codes have the ability to render directly onto the document. RunPage these documents are referred to as pages.

Difference with Jupyter

The first biggest difference is that RunPage allows you to embed Javascript codes instead of Python. Although technically Jupyter can be used to run any kind of language codes, depending on the “kernel”, but all those codes are run on server-side. In case of RunPage they all run in a “sandboxed” environment inside your own browser. So no roundtrip to server is involved which makes it faster and secure. RunPage also allows you to embed file selector in your page, which you can reference in your JS code. These selected files (excel, text, etc. files) can then be processed using APIs provided by RunPage right in your browser without uploading them to server for processing.

RunPage also provides a proper block editor, so that the article writing process is as frictionless as possible.

RunPage is a fully hosted solution with concepts of teams and sharing between teams and to public. The provided APIs are designed to be simple and intuitive. For example, unlike Jupyter the last statement in the code-block are not automatically rendered as output on the document. In RunPage each code-block (referred to as script-block in RunPage) behaves like an Async function body. So only items which are returned from that function get rendered on the page. In fact it renders an array as series of multiple outputs.

Head over to run.applegrew.com for a free account.

Downloading Certified Copy of your sale deed in Telangana

If you received a SMS from Telangana registration department, similar to the one below :-

Please collect your Document No:____, year:____ Dated _______ From SRO xxxxxx,Telangana between 10:30AM and 05:00PM on any working day. Visit website registration.telangana.gov.in to download Certified Copy of document using your registered Mobile No. and Security Code: xxxxxx. In case of any issues please call IGRS Helpdesk no. 18005994788

Then the first thing you would want to do is download the certified copy. As per instructions here you need to goto registration.telangana.gov.in then click on Certified Copy button there.

Get certified copy of your registered document like Sale Deed.

After this you will be required to create an account and login there. Which is big pain in the a*s. Here I will give you a way to bypass all that.

Below is a simple form, just click that and it will take you to the Telangana government’s portal where you need to provide your mobile number (where you received the above SMS) and the Security Code sent to you in that SMS. Clicking the below button will launch the government’s portal in a new tab.

If you are still reading, means that you have not clicked above and are worried about possible hacks stealing your passwords. These worries are very real and hence I will explain here what is going on in the button above. You can also inspect the button’s code in your browser as well.

<html>
<head>
   <title>Signed Copy Download Launcher</title>
</head>
<body>
   <form action="https://registration.telangana.gov.in/TGCertCopiesClient/LoginServlet" method="POST" target="_blank">
      <input type="hidden" name="ccType" value="CITIZEN" />
      <input type="submit" value="Go" />
   </form>
</body>
</html>

The code is simple. It just makes a POST call wit ccType=CITIZEN as form data. You can also save the above code as html page and click the button there.

Unable to login to registration.telangana.gov.in because of not receiving OTP SMS

registration.telangana.gov.in is a one stop shop to search for all registered documents. Anyone who has bought a property or wants to buy one, this is an excellent portal provided by the State Government to check for encumbrance and other details about the property.

The problem

To access these functions you need to register here, which you did; and after that when you try to login you are presented with a form where you need to enter OTP which you will get from SMS sent to your phone. However, the SMS never arrives. You click on – Resent OTP, and still nothing, no SMS. You call up the toll free helpline which is there on the portal’s header, and they ask you to clear browser history and retry. You do that diligently, inside your mind you know that it will do nothing to help you. Anyway, you follow the instructions and retry and still the same issue. – If this is what happened to you as well, read on….

After some clicking around I figured that if I tried using the “Forgot password” functionality then that too sends OTP via SMS and there the SMS is actually delivered!

The solution

When you login and you are confronted with that OTP form then open another browser tab and open https://registration.telangana.gov.in/citizen_forgot_pwd.htm. Enter your email or mobile number. This will send a OTP SMS now. Instead of putting it there use that OTP on your post login form, and it will now successfully validate! Yeah!

Update from 2025

On seeing comments on this post about the above steps no longer working, I took another view. It seems OTP is no longer needed while logging-in, and now support to use email as login id has been added.

After signing up using your email id, if you get “Invalid username/password” even for providing correct captcha then try resetting your password. Goto https://registration.telangana.gov.in/citizen_forgot_pwd.htm as before. Provide your email id and you will receive the OTP over email. Use that and it should now reset successfully. You should be able to login now.

Please note: Use only email-ids and not your phone number for login and signup.

What is your effective income tax rate?

In India the Income tax calculation is quite complicated. There are different slabs with varying percentages, then surcharges, education and health cess etc. Do you know what is the cumulative effect of all these various percentages on your net income?

Effective income tax rate is the percentage value of your final tax amount by your net taxable income after all deductions. This gives a clear picture of what percentage of your income is going to the government.

Effective Tax Rate = (Total Tax / Net Taxable Income) x 100

This analysis is not only a fun way to feel proud or… maybe sad. This also useful to make decisions like should I take home loan or pay it out of pocket? Yup sometimes taking loan is actually more economical!

First let us look at the tax amounts for taxable income ranging from zero to 5Cr, in FY 2020-2021.

See the Pen Effective Income Tax rate by Nirupam (@applegrew) on CodePen.

Next let’s look at the effective tax rate for zero to 6Cr net taxable income range. (In both the graphs the blue line is when the “new-regime” tax slab is used, and the orange line for “old regime”). The comparison for both the regimes are here – Deep dive into new tax regime of Budget 2020.

See the Pen Effective Income Tax rate by Nirupam (@applegrew) on CodePen.

In the above graph the first cliff comes at 50L mark, then at 1Cr (which is mentioned as 10M on the graph), lastly at 2Cr mark. At 2Cr the effective tax rate is whooping 43%!

Scenario: Home loan vs paying out of pocket

Now let us say you are earning 1.5Cr a year. You want to buy a 2Cr worth house (inclusive of all fees). You are able to save 1Cr a year. So that means you can pretty much pay for the house out of pocket in two years. Also since house is under construction which will need next 3years to actually complete. In this case we will talk in context of two different situations.

You take a home loan 1.6Cr for at 7.2% for 20years. Let’s use our calculator from Calculating Amortisation Schedule of your loans. You will need to pay back total 3,02,34,176 over the course of 20years. (Plus some processing fees, which would be small in comparison here). So your total expenditure would be 3.02Cr + 40L (out of pocket) = 3.42Cr.

Instead let’s say you paid for the full amount out of pocket then the cost of house would had be exact 2Cr.

However, in the case where you took loan, if you had invested the 1.6Cr amount in Mutual Funds, which typically returns about 12%; you would have got 12.3Cr at the end of 18years! (Assuming it took two year to accumulate 1.6Cr after all expenses.) Even if you invest in the safest MFs like Liquid funds which gives a typical return of 5% you would have got 3.85Cr at the end of 18yrs. 3.85-3.42 = 43L. Means you got the house practically for free with 43L to spare. OR more than 8Cr to spare in former case.

Not only that you would save on taxes due to deductions due to home loan under 80C and section 24.