Skip to content

feat(investments): add India investment subtypes and exchange support#1659

Merged
jjmata merged 7 commits into
we-promise:mainfrom
ahnv:indian-investments
May 4, 2026
Merged

feat(investments): add India investment subtypes and exchange support#1659
jjmata merged 7 commits into
we-promise:mainfrom
ahnv:indian-investments

Conversation

@ahnv
Copy link
Copy Markdown
Contributor

@ahnv ahnv commented May 2, 2026

Adds first-class support for Indian investors by extending the existing Investment and Property accountable types with India-region subtypes, and by teaching Provider::YahooFinance to handle NSE/BSE listings. Indian mutual fund NAVs are already covered by the existing [Provider::Mfapi(https://github.com/we-promise/sure/blob/694e879f440633f47e5c5177b9e3f6f215008414/app/models/provider/mfapi.rb); this PR builds on top of that.

Related: #1413 (closed) previously attempted Indian investment support via country-specific accountable models. This PR takes a different approach — per @jjmata's feedback on that thread — using subtypes on existing models instead of new tables.

Addressing the prior review on #1413

Maintainer feedback on that PR from @jjmata:

"adding indian_* tables/data model proliferation sounds like the wrong approach. Can't have that for each country, you know? Why is that necessary?"

This PR is designed to resolve exactly that. No country-specific tables, no new accountable types — Indian investments live on the existing Investment model via subtypes (same pattern already used for 401k, isa, rrsp, super, pea, etc.), and Indian property types live on Property. Zero new migrations, zero schema changes.

Changes

Investment subtypes (SUBTYPES hash, India section)

Region in:

  • Pensions & insurance: nps, apy, life_insurance
  • Equity / market-linked: demat, indian_equity, indian_etf
  • Fixed-income / small-savings: ppf, ssy, nsc, scss, fd, rd, pomis, kvp
  • Bonds: g_sec, sdl, corporate_bond, infrastructure_bond, tax_free_bond
  • India-specific gold instruments: gold_etf, gold_mf, sgb

Generic:

  • gold (covers physical + digital gold, region-agnostic)

Each subtype carries a proper tax_treatment (:tax_exempt for PPF/SSY/tax-free bonds, :tax_advantaged for NPS/SGB/infra bonds/life insurance, :taxable otherwise).

Property subtypes (SUBTYPES hash)

  • Added: apartment, plot, commercial, rented, agri_land

Subtype ordering

  • Dropdown groups: Generic → US → UK → CA → AU → EU → India.

Provider::YahooFinance — NSE/BSE support

Tests

All existing tests continue to pass.

Screenshots

image image

Checklist

  • Minitest coverage added for new behavior
  • bin/rubocop clean
  • Biome (npm run lint) clean
  • No new migrations
  • No secrets committed
  • Branch rebased on main
  • Allow edits from maintainers (please enable when opening against upstream)

Summary by CodeRabbit

  • New Features
    • Expanded investment options with India-specific products and added a generic "Gold" subtype; region ordering updated for grouped selection lists.
    • Added property categories: apartment, plot, commercial, rented, agricultural land, and updated second-home entry.
    • Improved market-data handling: normalized Indian equity symbols, deduplicated dual-listed Indian listings, and improved currency resolution for historical prices.
  • Documentation
    • Added English locale entries for India-related subtype labels.
  • Tests
    • Added tests for India subtypes and Indian exchange/symbol handling.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds India-specific investment subtypes and property subtype entries and translations; updates Investment region ordering; and enhances Provider::YahooFinance with symbol normalization, INR/default-currency resolution, and dual-listing deduplication. Tests and locale entries updated accordingly.

Changes

India Investment & Property Support

Layer / File(s) Summary
Data Shape
app/models/investment.rb, app/models/property.rb
Investment::SUBTYPES gains many region: "in" subtype entries (nps, apy, life_insurance, demat, ppf, nsc, fd, g_sec, sgb, etc.) and adds a generic gold subtype. Property::SUBTYPES adds second_home, apartment, plot, commercial, rented, and agri_land.
Region Ordering / Grouping
app/models/investment.rb
subtypes_grouped_for_select updated to include "in" in other_regions (%w[us uk ca au eu in]), affecting grouped-select ordering and making India appear among other regions.
Locales
config/locales/views/accounts/en.yml, config/locales/views/investments/en.yml, config/locales/views/properties/en.yml
Adds accounts.subtype_regions.in: "India", investment translations for India subtypes and generic gold, and property subtype translations for apartment, plot, commercial, rented, agri_land.
Tests
test/models/investment_test.rb
Adds "in" to valid regions test and new tests asserting India subtypes' tax_treatment, region mapping, and that India group appears last for currency: "INR" in subtypes_grouped_for_select.

Yahoo Finance Indian Exchange Support

Layer / File(s) Summary
Constants & Mappings
app/models/provider/yahoo_finance.rb
Adds EXCHANGE_CONFIG and YAHOO_EXCHANGE_CURRENCY (Yahoo suffixes/default currencies and preference ranks) and extends map_exchange_mic to map NSE/NSIXNSE and BSE/BOMXBOM.
Core Helpers
app/models/provider/yahoo_finance.rb
Adds normalize_symbol(symbol, exchange_operating_mic) to apply Yahoo suffixes, default_currency_for_exchange(yahoo_exchange_name) to infer currency (INR for Indian exchanges), and deduplicate_dual_listings(securities) to prefer configured listings by rank when dual-listed.
Integration / Wiring
app/models/provider/yahoo_finance.rb
search_securities applies deduplicate_dual_listings; fetch_security_info and fetch_security_prices normalize symbols before Yahoo requests; historical price parsing derives currency from chart.meta.currency then default_currency_for_exchange then "USD".
Tests
test/models/provider/yahoo_finance_test.rb
Adds tests for map_exchange_mic/country mapping, normalize_symbol suffixing and idempotence, default_currency_for_exchange currency resolution, and deduplicate_dual_listings preference/deduplication behavior.
sequenceDiagram
  participant Client as rgba(63,81,181,0.5) Client
  participant Provider as rgba(0,150,136,0.5) Provider::YahooFinance
  participant Yahoo as rgba(255,152,0,0.5) Yahoo API
  participant Cache as rgba(233,30,99,0.5) Cache/DB

  Client->>Provider: search_securities(query, exchange_mic)
  Provider->>Provider: map_exchange_mic(exchange_mic)
  Provider->>Provider: normalize_symbol(symbol, mapped_mic)
  Provider->>Yahoo: fetch /chart or /quoteSummary with normalized symbol
  Yahoo-->>Provider: chart/quote response (includes meta.exchange, meta.currency)
  Provider->>Provider: default_currency_for_exchange(chart_meta) / set currency
  Provider->>Provider: deduplicate_dual_listings(securities)
  Provider->>Cache: cache results
  Provider-->>Client: return normalized, deduplicated securities (with currency)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • jjmata
  • sokie

"With twitchy whiskers I paste and hop,
I add India’s types, and never stop.
Symbols get suffixes, currencies mend,
Dual-listings shrink till only one friend.
🐇✨ Ledger and ticker sing — hip-hop!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main feature being added: India investment subtypes and exchange support for the Yahoo Finance provider.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: Turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get your free trial and get 200 agent minutes per Slack user (a $50 value).


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@superagent-security superagent-security Bot added the contributor:flagged Contributor flagged for review by trust analysis. label May 2, 2026
@superagent-security
Copy link
Copy Markdown

superagent-security Bot commented May 2, 2026

⚠️ Contributor Trust Check — Review Recommended

This contributor's profile shows patterns that may warrant additional review. This is based on their GitHub activity, not the contents of this PR.

ahnv · Score: 68/100

Dimension breakdown
Dimension Score What it measures
Identity 30 Account age, contribution history, GPG keys, org memberships
Behavior 80 PR patterns, unsolicited contribution ratio, activity cadence
Content 100 PR body substance, issue linkage, contribution quality
Graph 30 Cross-repo trust, co-contributor relationships

Analyzed by Brin · Full profile

@superagent-security superagent-security Bot added the pr:verified PR passed security analysis. label May 2, 2026
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 694e879f44

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/models/provider/yahoo_finance.rb Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (2)
test/models/provider/yahoo_finance_test.rb (1)

412-432: ⚡ Quick win

Add a regression test for preserving distinct Indian listings.

Current coverage verifies NSE is preferred over BSE for one pair, but it doesn’t protect against collapsing all Indian securities into one result.

🧪 Suggested test addition
+  test "prefer_indian_exchange preserves distinct Indian securities while preferring NSE over BSE duplicates" do
+    rel_nse = Provider::SecurityConcept::Security.new(symbol: "RELIANCE.NS", name: "Reliance", exchange_operating_mic: "XNSE")
+    rel_bse = Provider::SecurityConcept::Security.new(symbol: "500325.BO",   name: "Reliance", exchange_operating_mic: "XBOM")
+    infy_nse = Provider::SecurityConcept::Security.new(symbol: "INFY.NS",     name: "Infosys",  exchange_operating_mic: "XNSE")
+
+    result = `@provider.send`(:prefer_indian_exchange, [ rel_nse, rel_bse, infy_nse ])
+
+    assert result.map(&:symbol).include?("RELIANCE.NS")
+    assert_not result.map(&:symbol).include?("500325.BO")
+    assert result.map(&:symbol).include?("INFY.NS")
+  end
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/models/provider/yahoo_finance_test.rb` around lines 412 - 432, Add a
regression test in test/models/provider/yahoo_finance_test.rb that verifies
prefer_indian_exchange does not collapse distinct Indian listings: create two
different Provider::SecurityConcept::Security instances (different symbol/name)
where one is on XNSE and the other on XBOM, call
`@provider.send`(:prefer_indian_exchange, ...) and assert both Indian listings
remain in the result (i.e., both XNSE and XBOM are present) while still
preferring NSE only when duplicates for the same security exist; reference the
prefer_indian_exchange method and Provider::SecurityConcept::Security to locate
where to add the test.
app/models/investment.rb (1)

57-84: 🏗️ Heavy lift

Subtype display labels are hard-coded in the model, which can drift from locale translations.

These new entries embed user-facing short/long strings directly in Investment::SUBTYPES. Since translation keys were also added, this creates duplicate sources of truth and weakens localization consistency.

Consider storing only non-display metadata here (e.g., region, tax_treatment) and resolving labels via I18n at render/select-build time.

As per coding guidelines **/*.{erb,rb}: “Always use t() helper for user-facing strings… Update config/locales/en.yml for new strings.”

Also applies to: 90-90

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/models/investment.rb` around lines 57 - 84, Investment::SUBTYPES
currently embeds user-facing "short"/"long" strings (e.g., entries like "nps" =>
{ short: "NPS", long: "National Pension System", ... }) which duplicates locale
keys; remove the hard-coded display labels from Investment::SUBTYPES so it only
holds stable metadata (region, tax_treatment) and leave keys like "nps", "apy",
"demat" etc. as identifiers; update codepaths that render or build select
options (where SUBTYPES is read) to call I18n.t with the corresponding
translation keys (use the existing locale keys in config/locales/*.yml) to
obtain short/long labels at render time; ensure tests and any usages of methods
that reference SUBTYPES (e.g., any code referencing Investment::SUBTYPES[...]
.fetch(:short) or .fetch(:long)) are updated to fetch labels via
I18n.t("investments.subtypes.<key>.short") / .long instead.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/models/provider/yahoo_finance.rb`:
- Around line 371-377: The current prefer_indian_exchange method removes all but
one Indian listing by selecting a single preferred Indian security; instead,
change it to perform deduplication only within likely-duplicate groups: in
prefer_indian_exchange, group the securities by a stable identifier for
duplicates (e.g. s.ticker or [s.ticker, s.name] using group_by), and for each
group pick the preferred entry by ranking exchange_operating_mic with
INDIAN_EXCHANGE_PREFERENCE (using min_by) while preserving non-duplicate groups
intact; return the flattened array of selected entries in original group order
so unrelated Indian listings are not dropped (update variables
indian/preferred/non_indian logic to operate per-group rather than globally).

In `@test/models/investment_test.rb`:
- Around line 161-166: The test "India subtypes all belong to the 'in' region"
currently checks a hardcoded subset; change it to derive the keys from
Investment::SUBTYPES itself so all current and future India subtypes are
covered: compute india_keys = Investment::SUBTYPES.keys.select { |k|
Investment::SUBTYPES.dig(k, :region) == 'in' } (referenced symbol:
Investment::SUBTYPES) and then iterate that list asserting each entry's region
equals "in" in the test method "India subtypes all belong to the 'in' region".

---

Nitpick comments:
In `@app/models/investment.rb`:
- Around line 57-84: Investment::SUBTYPES currently embeds user-facing
"short"/"long" strings (e.g., entries like "nps" => { short: "NPS", long:
"National Pension System", ... }) which duplicates locale keys; remove the
hard-coded display labels from Investment::SUBTYPES so it only holds stable
metadata (region, tax_treatment) and leave keys like "nps", "apy", "demat" etc.
as identifiers; update codepaths that render or build select options (where
SUBTYPES is read) to call I18n.t with the corresponding translation keys (use
the existing locale keys in config/locales/*.yml) to obtain short/long labels at
render time; ensure tests and any usages of methods that reference SUBTYPES
(e.g., any code referencing Investment::SUBTYPES[...] .fetch(:short) or
.fetch(:long)) are updated to fetch labels via
I18n.t("investments.subtypes.<key>.short") / .long instead.

In `@test/models/provider/yahoo_finance_test.rb`:
- Around line 412-432: Add a regression test in
test/models/provider/yahoo_finance_test.rb that verifies prefer_indian_exchange
does not collapse distinct Indian listings: create two different
Provider::SecurityConcept::Security instances (different symbol/name) where one
is on XNSE and the other on XBOM, call `@provider.send`(:prefer_indian_exchange,
...) and assert both Indian listings remain in the result (i.e., both XNSE and
XBOM are present) while still preferring NSE only when duplicates for the same
security exist; reference the prefer_indian_exchange method and
Provider::SecurityConcept::Security to locate where to add the test.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 87e86b79-a103-4fab-a052-07ffa25f5332

📥 Commits

Reviewing files that changed from the base of the PR and between a8425a2 and 694e879f440633f47e5c5177b9e3f6f215008414.

📒 Files selected for processing (8)
  • app/models/investment.rb
  • app/models/property.rb
  • app/models/provider/yahoo_finance.rb
  • config/locales/views/accounts/en.yml
  • config/locales/views/investments/en.yml
  • config/locales/views/properties/en.yml
  • test/models/investment_test.rb
  • test/models/provider/yahoo_finance_test.rb

Comment thread app/models/provider/yahoo_finance.rb Outdated
Comment thread test/models/investment_test.rb
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d598507301

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread app/models/provider/yahoo_finance.rb Outdated
@jjmata jjmata self-requested a review May 2, 2026 22:15
@jjmata jjmata removed the contributor:flagged Contributor flagged for review by trust analysis. label May 2, 2026
Copy link
Copy Markdown
Collaborator

@jjmata jjmata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking another crack at this, @ahnv!

I do see great progress, but still functions like prefer_indian_exchange and normalize_indian_symbol show that this is too specific.

What would we do, have a function like that for each country? Those code paths (and others, if they exist!) need to be generalized as well. 🙏

@jjmata jjmata added this to the future milestone May 2, 2026
@ahnv
Copy link
Copy Markdown
Contributor Author

ahnv commented May 2, 2026

Thanks for taking another crack at this, @ahnv!

I do see great progress, but still functions like prefer_indian_exchange and normalize_indian_symbol show that this is too specific.

What would we do, have a function like that for each country? Those code paths (and others, if they exist!) need to be generalized as well. 🙏

Thanks for the feedback @jjmata — totally agree these shouldn't be India-specific.

My plan is to replace all the India-specific constants and methods with a single data-driven EXCHANGE_CONFIG mapping keyed by MIC (Market Identifier Code):

EXCHANGE_CONFIG = {
  "XNSE" => { yahoo_suffix: ".NS", default_currency: "INR", dual_list_group: :india, preference_rank: 0 },
  "XBOM" => { yahoo_suffix: ".BO", default_currency: "INR", dual_list_group: :india, preference_rank: 1 },
  # Easy to extend for other markets:
  # "XLON" => { yahoo_suffix: ".L", default_currency: "GBP" },
  # "XJPX" => { yahoo_suffix: ".T", default_currency: "JPY" },
}.freeze

This lets me collapse the India-specific code paths into three generic methods:

Current Generalized
normalize_indian_symbol normalize_symbol — appends EXCHANGE_CONFIG[mic][:yahoo_suffix] if present
prefer_indian_exchange deduplicate_dual_listings — groups by dual_list_group + name, picks lowest preference_rank
Hardcoded INR fallback default_currency_for_exchange — looks up EXCHANGE_CONFIG[mic][:default_currency]

Adding a new market (e.g. LSE/XLON) becomes a one-line hash entry instead of a new method. Does this direction work for you, or would you prefer a different structure?

@superagent-security superagent-security Bot added the contributor:flagged Contributor flagged for review by trust analysis. label May 3, 2026
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/models/provider/yahoo_finance.rb (1)

340-345: Remove dead code: YAHOO_EXCHANGE_CURRENCY constant is never referenced.

The constant at lines 343–345 is defined but never used. The default_currency_for_exchange method (line 379) retrieves currency directly via EXCHANGE_CONFIG.dig(mic, :default_currency), not from this constant. Additionally, the comment is inaccurate: the iteration is over EXCHANGE_CONFIG keys (MIC codes like "XNSE"), not raw Yahoo codes, and map_exchange_mic is never invoked during construction.

Proposed fix — remove the unused constant and misleading comment
-    # Reverse lookup: Yahoo exchange name (from chart metadata) → default currency.
-    # Built from EXCHANGE_CONFIG via map_exchange_mic so the raw Yahoo codes
-    # ("NSE", "BSE", etc.) resolve to the same config.
-    YAHOO_EXCHANGE_CURRENCY = EXCHANGE_CONFIG.each_with_object({}) do |(mic, cfg), h|
-      h[mic] = cfg[:default_currency]
-    end.freeze
-
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/models/provider/yahoo_finance.rb` around lines 340 - 345, Remove the dead
constant YAHOO_EXCHANGE_CURRENCY and its accompanying misleading comment: delete
the YAHOO_EXCHANGE_CURRENCY constant definition and the comment above it, since
default_currency_for_exchange already reads directly from EXCHANGE_CONFIG (via
EXCHANGE_CONFIG.dig(mic, :default_currency)) and map_exchange_mic is not used
here; leave EXCHANGE_CONFIG and the method default_currency_for_exchange as-is.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/models/provider/yahoo_finance.rb`:
- Around line 390-394: The grouping key in deduplicate_dual_listings uses s.name
verbatim so listings with different casing/whitespace (e.g., "Reliance
Industries Ltd" vs "RELIANCE INDUSTRIES LTD") are treated as distinct; update
the group_by block key to normalize the name (call to_s.strip.downcase on
s.name) so the grouping uses
[EXCHANGE_CONFIG[s.exchange_operating_mic][:dual_list_group],
s.name.to_s.strip.downcase], ensuring case- and whitespace-insensitive duplicate
detection while leaving EXCHANGE_CONFIG and preference_rank logic unchanged.

---

Nitpick comments:
In `@app/models/provider/yahoo_finance.rb`:
- Around line 340-345: Remove the dead constant YAHOO_EXCHANGE_CURRENCY and its
accompanying misleading comment: delete the YAHOO_EXCHANGE_CURRENCY constant
definition and the comment above it, since default_currency_for_exchange already
reads directly from EXCHANGE_CONFIG (via EXCHANGE_CONFIG.dig(mic,
:default_currency)) and map_exchange_mic is not used here; leave EXCHANGE_CONFIG
and the method default_currency_for_exchange as-is.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ceb69f6b-8f7b-4201-a72a-a954e64786df

📥 Commits

Reviewing files that changed from the base of the PR and between d598507301c719ff3e187d8151d2f037c218ce8e and 20727be9d6cc940bb386bd179266337c43643c2d.

📒 Files selected for processing (2)
  • app/models/provider/yahoo_finance.rb
  • test/models/provider/yahoo_finance_test.rb
✅ Files skipped from review due to trivial changes (1)
  • test/models/provider/yahoo_finance_test.rb

Comment thread app/models/provider/yahoo_finance.rb Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/models/provider/yahoo_finance.rb`:
- Line 168: The deduplication step currently calls
deduplicate_dual_listings(securities) unconditionally which both loses Yahoo’s
original relevance ordering and can cause exchange_scoped searches (where params
include exchange_operating_mic) to return the other market’s listing; change the
logic so that you skip calling deduplicate_dual_listings when an
exchange_operating_mic filter is present, and when you must deduplicate preserve
stability by maintaining Yahoo's original securities order rather than
rebuilding as preferred + others (avoid the preferred + others reorder in the
block currently around lines 386-400); locate the calls to
deduplicate_dual_listings and the code that constructs preferred + others and
modify them to either no-op for exchange-scoped lookups or to perform a stable
in-place dedupe that keeps the original array order and only removes duplicates
while marking a preferred item.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 76d5b337-aafc-4524-b434-cc5ae107b4c3

📥 Commits

Reviewing files that changed from the base of the PR and between 20727be9d6cc940bb386bd179266337c43643c2d and f27c31e643d6027ece0aebd4a7f8a54ee8729a49.

📒 Files selected for processing (1)
  • app/models/provider/yahoo_finance.rb

Comment thread app/models/provider/yahoo_finance.rb Outdated
@ahnv
Copy link
Copy Markdown
Contributor Author

ahnv commented May 3, 2026

Updated per feedback — the India-specific methods (normalize_indian_symbol, prefer_indian_exchange, INDIAN_EXCHANGE_CODES, INDIAN_EXCHANGE_PREFERENCE) have been generalized into a data-driven EXCHANGE_CONFIG mapping with three generic methods:

No more country-specific functions. Adding a new market (e.g. LSE, Tokyo) is a one-line hash entry in EXCHANGE_CONFIG. All tests updated and passing.

@ahnv ahnv requested a review from jjmata May 3, 2026 07:54
@jjmata jjmata removed contributor:flagged Contributor flagged for review by trust analysis. labels May 3, 2026
Copy link
Copy Markdown
Collaborator

@jjmata jjmata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

This is a well-structured PR that correctly follows the project's subtype pattern (no new tables, no new accountable models) and addresses the prior feedback from #1413. The EXCHANGE_CONFIG-driven approach in Provider::YahooFinance is genuinely extensible. A few issues worth addressing before merging:


Bug: cache key mismatch in fetch_security_price

fetch_security_price builds its cache key from the pre-normalization symbol, but stores a Price object whose .symbol field is the post-normalization value (e.g. "RELIANCE""RELIANCE.NS"). On a cache hit the caller gets back a Price with symbol: "RELIANCE.NS" even though they passed "RELIANCE". Downstream code that relies on the returned symbol matching the input will silently see a mismatch.

# fetch_security_price (before normalization happens)
cache_key = "security_price_#{symbol}_#{exchange_operating_mic}_#{date}"
# ...
prices_response = fetch_security_prices(symbol: symbol, ...)  # normalizes inside
# target_price.symbol == "RELIANCE.NS", cached under key "RELIANCE"

The simplest fix is to normalize the symbol at the top of fetch_security_price as well, so the cache key and the stored object agree.


Dead code: YAHOO_EXCHANGE_CURRENCY

The constant is built but never referenced anywhere:

YAHOO_EXCHANGE_CURRENCY = EXCHANGE_CONFIG.each_with_object({}) do |(mic, cfg), h|
  h[mic] = cfg[:default_currency]
end.freeze

default_currency_for_exchange goes straight through map_exchange_micEXCHANGE_CONFIG.dig(mic, :default_currency) and doesn't use this reverse-lookup table. Either wire it in or remove it.


normalize_symbol guard is too broad for dotted symbols on Indian exchanges

def normalize_symbol(symbol, exchange_operating_mic)
  return symbol if symbol.include?(".")  # ← skips suffix if symbol already has any dot
  ...
end

A symbol like BRK.A listed on XNSE (contrived, but the logic is general) would silently skip the .NS suffix. A tighter guard — e.g. checking for the specific configured suffix — is safer:

suffix = EXCHANGE_CONFIG.dig(exchange_operating_mic, :yahoo_suffix)
return symbol if suffix.nil? || symbol.end_with?(suffix)
"#{symbol}#{suffix}"

Property subtypes are region-agnostic; the # India comment is misleading

Property::SUBTYPES has no region: key, so every subtype is shown to all users globally. apartment and commercial are legitimate property types everywhere (US, UK, EU…), not India-specific. The # India comment in property.rb incorrectly implies these are scoped to Indian users. Consider either removing the comment or — if you want these to be India-only — opening a follow-up to add region support to Property::SUBTYPES the same way Investment::SUBTYPES has it.


rented is a use-case descriptor, not a property type

"rented" describes what the owner does with a property, not what kind of property it is. The existing investment_property covers the rental-income use case. Consider dropping rented in favour of strengthening investment_property, or renaming it to something like rental_property that aligns with the naming convention of the other entries.


demat as an investment subtype is conceptually imprecise

A Demat account is a container that holds securities (stocks, ETFs, bonds), not an investment product in itself. A user who opens one won't know what balance to enter. Indian users tracking NSE/BSE equities would typically use indian_equity for individual stock positions; the demat entry adds ambiguity. It could be removed or renamed (e.g. indian_stocks) to reduce confusion.


Test asserts India comes last for INR users — is that intentional UX?

test "subtypes_grouped_for_select places India region last" do
  grouped = Investment.subtypes_grouped_for_select(currency: "INR")
  last_group_label = grouped.last[0]
  assert_equal I18n.t("accounts.subtype_regions.in"), last_group_label
end

Because CURRENCY_REGION_MAP deliberately omits "INR", user_region resolves to nil for INR users and India appears after Generic in the dropdown. The test is passing the correct, intentional behavior — but it means Indian users scroll past all other regions to find their instruments. The PR description explains the tradeoff, but it's worth a brief comment in the code (or a TODO) to make this discoverable for the next contributor who wonders why INR is absent from CURRENCY_REGION_MAP.


Minor: Set may need an explicit require

deduplicate_dual_listings calls Set.new. In Ruby 3.2+ Set is no longer auto-loaded, and Rails 7 no longer auto-requires it via Active Support. The rest of the codebase may already require 'set' somewhere in the load path, but it's worth verifying a require 'set' (or require "set") is present — either at the top of yahoo_finance.rb or in an initializer — to avoid a NameError in production environments where the constant hasn't been loaded yet.


Generated by Claude Code

@superagent-security superagent-security Bot added the contributor:flagged Contributor flagged for review by trust analysis. label May 3, 2026
ahnv added 6 commits May 3, 2026 20:44
…instead of globally

Resolves feedback from Codex and CodeRabbit on #1413.

prefer_indian_exchange previously collapsed all Indian securities into a
single entry, silently dropping unrelated tickers. Now groups Indian
listings by name and only de-duplicates within each group, so distinct
companies (e.g. Reliance and Infosys) are preserved while NSE/BSE
dual-listings still prefer NSE.

- Derive India subtype keys dynamically from Investment::SUBTYPES in tests
- Fix missing keyword arguments in Security.new test calls
…de-duplication

Replaces hardcoded Indian exchange logic with a declarative EXCHANGE_CONFIG
hash that maps ISO MIC codes to Yahoo-specific settings (symbol suffix,
default currency, dual-listing group, and preference rank). This makes
adding new markets a one-line hash entry instead of scattered conditionals.
- fix cache key mismatch in fetch_security_price by normalizing symbol before building cache key
- remove dead YAHOO_EXCHANGE_CURRENCY constant
- tighten normalize_symbol guard to use end_with?(suffix) instead of include?('.')
- remove misleading '# India' comment from Property::SUBTYPES
- remove 'rented' property subtype in favor of 'investment_property'
- rename 'demat' to 'indian_stocks' for clarity
- add INR to CURRENCY_REGION_MAP so India appears first for INR users
- add dotted-symbol regression test for normalize_symbol
@ahnv ahnv force-pushed the indian-investments branch from 3e119fa to 08bc46d Compare May 3, 2026 15:14
Copy link
Copy Markdown
Collaborator

@jjmata jjmata left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review

Good progress — almost everything from my first pass has been addressed cleanly. One real bug remains.


Still open: locale key mismatch for indian_stocks

The latest commit renamed the model key from "demat" to "indian_stocks", but the locale file was not updated to match. The result:

  • config/locales/views/investments/en.yml adds a demat: key that no longer corresponds to any subtype (orphaned).
  • indian_stocks has no locale entry at all — it will fall back to the raw key or raise a missing-translation error in development.

The locale block should read:

indian_stocks:
  short: Indian Stocks
  long: Indian Stocks (Demat)

instead of the current demat: entry.


Everything else resolved

For completeness, confirming each earlier point is now closed:

Issue Status
Cache key mismatch in fetch_security_price ✅ Fixed — normalize_symbol now runs before the cache key is built
Dead YAHOO_EXCHANGE_CURRENCY constant ✅ Removed
normalize_symbol guard too broad (include?(".")) ✅ Fixed — now uses end_with?(suffix), test added for BRK.A case
# India comment misleading in property.rb ✅ Removed
rented as use-case descriptor ✅ Removed
demat as container-account subtype ✅ Renamed to indian_stocks in the model
INR users see India last in dropdown ✅ Fixed — "INR" => "in" added to CURRENCY_REGION_MAP, test updated
Set missing explicit require ✅ Fixed — require "set" added at top of file

Just fix the locale key and this is clean.


Generated by Claude Code

@ahnv ahnv requested a review from jjmata May 3, 2026 20:15
@jjmata jjmata removed the contributor:flagged Contributor flagged for review by trust analysis. label May 4, 2026
@jjmata jjmata modified the milestones: future, v0.7.1 May 4, 2026
@jjmata jjmata merged commit 139c89d into we-promise:main May 4, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr:verified PR passed security analysis.

Development

Successfully merging this pull request may close these issues.

2 participants