<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Rajesh Lohith</title>
    <description>The latest articles on DEV Community by Rajesh Lohith (@rajesh_lohith_b3cc29daf3c).</description>
    <link>https://dev.to/rajesh_lohith_b3cc29daf3c</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4217591%2F1803ae54-6a23-4743-9580-115f0aca56df.jpg</url>
      <title>DEV Community: Rajesh Lohith</title>
      <link>https://dev.to/rajesh_lohith_b3cc29daf3c</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/rajesh_lohith_b3cc29daf3c"/>
    <language>en</language>
    <item>
      <title>My AI astrologer isn't allowed to do math</title>
      <dc:creator>Rajesh Lohith</dc:creator>
      <pubDate>Fri, 30 Oct 2026 15:38:48 +0000</pubDate>
      <link>https://dev.to/rajesh_lohith_b3cc29daf3c/my-ai-astrologer-isnt-allowed-to-do-math-535e</link>
      <guid>https://dev.to/rajesh_lohith_b3cc29daf3c/my-ai-astrologer-isnt-allowed-to-do-math-535e</guid>
      <description>&lt;p&gt;I run &lt;a href="https://horoscopes.today" rel="noopener noreferrer"&gt;horoscopes.today&lt;/a&gt;, an AI Vedic astrology site. It has one hard rule: &lt;strong&gt;the language model never calculates anything.&lt;/strong&gt; A deterministic engine computes the birth chart first, and the model only explains what was computed.&lt;/p&gt;

&lt;p&gt;This post covers why we split it that way, what the model actually receives, and how we exposed the engine over a REST API and an MCP server so an agent can ask for exactly the piece of the chart a question needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the model can't be trusted with the chart
&lt;/h2&gt;

&lt;p&gt;Vedic (sidereal) astrology is mostly arithmetic before it is interpretation. To answer "what is my Moon sign?" you need:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the planet's tropical longitude at the birth moment, from an ephemeris&lt;/li&gt;
&lt;li&gt;an &lt;strong&gt;ayanamsa&lt;/strong&gt; correction to move it into the sidereal zodiac (about 23.7° for a 1990 birth under Lahiri)&lt;/li&gt;
&lt;li&gt;the correct timezone and coordinates for the birth place&lt;/li&gt;
&lt;li&gt;then the sign, the &lt;strong&gt;nakshatra&lt;/strong&gt; (one of 27 lunar mansions), and the &lt;strong&gt;pada&lt;/strong&gt; (quarter) it falls in&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Questions about timing, such as "when does this period of my life change?", depend on &lt;strong&gt;dashas&lt;/strong&gt;: nested planetary periods computed from the Moon's exact position at birth. Being off by a fraction of a degree moves those dates.&lt;/p&gt;

&lt;p&gt;A language model asked to do this will give a confident, well-written and often wrong answer. It may also give a different answer the next time you ask the same question. For a product where someone types their birth details and asks about their marriage or career, that is unacceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  The split: engine calculates, model interprets
&lt;/h2&gt;

&lt;p&gt;The flow for every reading looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The user enters date, time and place of birth.&lt;/li&gt;
&lt;li&gt;The place is resolved to latitude, longitude and timezone. The engine has a location search over 162,000+ cities.&lt;/li&gt;
&lt;li&gt;The &lt;strong&gt;Jagannatha Hora engine&lt;/strong&gt; computes the chart: signs, ascendant, planetary positions, nakshatras, divisional charts, dashas and transits.&lt;/li&gt;
&lt;li&gt;The model (Claude) receives that calculated chart context.&lt;/li&gt;
&lt;li&gt;It answers in plain language, and follow-up questions stay in the same thread, about the same chart.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The model's job is explanation, context and follow-ups. The chart's facts are fixed before the question is even read. Ask the same thing twice and you are still asking about the same chart.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the engine returns
&lt;/h2&gt;

&lt;p&gt;Here is the public REST call for a full chart, using the sample birth details from our API docs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-X&lt;/span&gt; POST &lt;span class="s2"&gt;"https://jagannatha-hora-359167915530.europe-west1.run.app/horoscope"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"content-type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "date": "1990-01-01",
    "time": "06:30:00",
    "place": "Bengaluru",
    "latitude": 12.9716,
    "longitude": 77.5946,
    "timezone": 5.5
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;An abridged excerpt of the response:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"birth_details"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezone_used"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;5.5&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"timezone_source"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"derived"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"request_settings"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"ayanamsa_mode"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"LAHIRI"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"calculation_type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"drik"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"horoscope"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"ayanamsa_value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;23.7174169601642&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"nakshatra_pada"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"Ascendant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"nakshatra"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Moola"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"pada"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"nakshatra_lord"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Kethu"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"divisional_charts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"D-1_rasi"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Ascendant"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sign"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Sagittarius"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;13.0166&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"Moon"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"sign"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Aquarius"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"longitude"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;3.3941&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"engine_revision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details turned out to matter more than I expected:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;The response echoes every setting it used.&lt;/strong&gt; That covers the ayanamsa (20 modes are supported, Lahiri is the default), the calculation type and where the timezone came from. When a user says "another site gave me a different nakshatra," you can see exactly why.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;engine_revision&lt;/code&gt; makes results cacheable.&lt;/strong&gt; The same inputs return the same chart until calculated values change, and then the revision goes up. You cache by input plus revision, and the model always reads the same facts.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why an MCP server, not just one big JSON blob
&lt;/h2&gt;

&lt;p&gt;A full chart is large: 23 divisional charts, dozens of dasha systems, house strengths, ashtakavarga, transits. Dumping all of it into the context for every question is wasteful, and it gives the model more to misread.&lt;/p&gt;

&lt;p&gt;So the same engine is also exposed as an &lt;strong&gt;MCP server with 33 individually callable tools&lt;/strong&gt;. A model answering a timing question calls the dasha tool. A model answering a compatibility question calls the marriage-match tool with two people. The transit and muhurta tools also take an optional &lt;em&gt;event location&lt;/em&gt;, for the common case of someone born in one city who is asking about something happening in another.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;generate_horoscope&lt;/code&gt; is the expensive call, so it runs once. Later calls for the same birth details reuse the computed chart and return quickly.&lt;/p&gt;

&lt;p&gt;Every tool result comes back twice: once as a JSON-encoded text part, and once as &lt;code&gt;structuredContent&lt;/code&gt; in a consistent &lt;code&gt;{"section": ..., "data": ...}&lt;/code&gt; envelope. Clients that only read text still work, and clients that read structured output don't have to parse strings.&lt;/p&gt;

&lt;h2&gt;
  
  
  Docs written for machines, too
&lt;/h2&gt;

&lt;p&gt;Because agents and LLM-based fetchers read docs now, every reference page has machine-readable twins:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://horoscopes.today/openapi.json" rel="noopener noreferrer"&gt;&lt;code&gt;/openapi.json&lt;/code&gt;&lt;/a&gt;: OpenAPI 3.1 for every endpoint&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://horoscopes.today/api.md" rel="noopener noreferrer"&gt;&lt;code&gt;/api.md&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://horoscopes.today/mcp.md" rel="noopener noreferrer"&gt;&lt;code&gt;/mcp.md&lt;/code&gt;&lt;/a&gt;: the full references as plain Markdown&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://horoscopes.today/mcp-tools.json" rel="noopener noreferrer"&gt;&lt;code&gt;/mcp-tools.json&lt;/code&gt;&lt;/a&gt;: the tool manifest, with every name, description and input schema&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you point an agent at the Markdown version, it gets the same facts as the HTML page without the navigation and styling.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd tell anyone building an "AI + domain math" product
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Draw a hard line between computing and explaining.&lt;/strong&gt; If a number can be calculated, don't let the model produce it.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Return structured data, and say which settings produced it.&lt;/strong&gt; Debugging trust issues becomes possible.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Make results deterministic and versioned.&lt;/strong&gt; Same input, same output, with a revision number for when that changes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Give the model small tools, not the whole database.&lt;/strong&gt; It asks better follow-up questions when it can fetch exactly what it needs.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://horoscopes.today/free-ai-astrology-chat" rel="noopener noreferrer"&gt;Free AI astrology chat&lt;/a&gt;: enter birth details and ask a question&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://horoscopes.today/calculators" rel="noopener noreferrer"&gt;Vedic astrology calculators&lt;/a&gt;: each with a working form and a worked example&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://horoscopes.today/api" rel="noopener noreferrer"&gt;REST API reference&lt;/a&gt; and &lt;a href="https://horoscopes.today/mcp" rel="noopener noreferrer"&gt;MCP server guide&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://horoscopes.today/powered-by-jagannatha-hora" rel="noopener noreferrer"&gt;How the calculation layer works&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Astrology is offered for guidance and entertainment. The engineering problem underneath it, which is keeping a language model honest about facts it didn't compute, applies to many other domains. I'd love feedback on the architecture, and to hear how others handle the same problem.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>mcp</category>
      <category>claude</category>
      <category>showdev</category>
    </item>
  </channel>
</rss>
