Always-on Amazon.in price tracker with statistical buy-zone alerts
Translation pending
CommercePythonGitHub @bhanu-thakurOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗Always-on multi-retailer price tracker. GitHub Actions is configured to run every 30 minutes on a best-effort schedule — free, whether or not your laptop is on. GitHub can delay or drop scheduled runs, so this is monitoring rather than a strict 30-minute service. Alert delivery remains postponed until provider and dashboard work stabilizes.
The point isn't "tell me the price." It's judging whether a price is actually good, by scoring it against that product's own history rather than a threshold you guessed.
Live dashboard: https://bhanu-thakur.github.io/price-sentinel/
The tracker never requests retailer pages during scheduled runs. It uses verified provider product pages in this order:
The pricehistoryapp.com adapter is not enabled until its public URL resolver can be verified without using its signed client API. Fallbacks are failure-driven, sequential, and circuit-broken; providers are not queried for comparison every cycle.
Each product stores stable provider URLs resolved once during intake. At runtime there is no retailer search step.
Accordingly, a recent successful check means the provider page was checked. It does not prove that Amazon or Flipkart was fetched at that moment. Provider-reported price, stock, seller, coupons and delivery can differ by account or location; confirm all of them on the retailer checkout page before buying.
The shipped catalog is researched for the all-new 2026 Kia Seltos HTK(O) diesel
automatic. Community anecdotes and marketplace ratings are stored separately in
structured research metadata and shown as such on the dashboard. A marketplace
rating shown by PriceHistory is not described as a Team-BHP or Reddit rating.
Fit-sensitive accessories are added only when the exact listing names the 2026 Seltos. Generic “Seltos” and “2023-present” mats were excluded because the all-new 2026 model changed shape. The currently available 12.3-inch “all trims” screen protector was also excluded: Kia specifies a 10.25-inch touchscreen for HTK(O). Boot mats and mud flaps remain untracked until an exact listing has consistent model metadata. Shampoo and microfiber cloth remain excluded because they are already owned.
Public repo, so Actions minutes are unlimited and free. A private repo burns your 2,000 free minutes/month and a 30-minute cadence would exhaust them. Nothing sensitive lives in the repo — only product links and price history. Credentials go in encrypted Secrets, which stay private either way.
Settings → Actions → General → Workflow permissions → Read and write.
Required. GitHub defaults new repos to read-only, and the workflow commits price history back to the repo. Without this every run appears to succeed while silently saving nothing.
bhanu-deals-7fk29xNeeds an App Password, not your login password: Google Account → Security → 2-Step Verification (must be on) → App passwords.
Settings → Secrets and variables → Actions:
| Secret | Value |
|---|---|
NTFY_TOPIC | your ntfy topic |
SMTP_HOST | smtp.gmail.com |
SMTP_PORT | 587 |
SMTP_USER | your Gmail address |
SMTP_PASS | 16-character app password |
MAIL_TO | where alerts go |
Either channel works alone. Leave secrets unset to disable that channel — the tracker still runs, stores history and updates the dashboard, it just doesn't notify. Alert cooldowns begin only after at least one configured channel confirms delivery.
Settings → Pages → Source: Deploy from branch → main / /docs.
Run the interactive intake with one real product URL:
python add_product.py "https://www.amazon.in/dp/B0GMR9PN1W" --target 3200 --tier cold
The intake resolves the URL through the verified public providers, shows the discovered listings and lifetime statistics, and asks for confirmation before writing watchlist.json. The resulting schema-v2 entry has a stable product ID, listing IDs, provider source URLs, and optional target/tier values. The current working adapters are pricehistory.app and buyhatke.com; pricehistoryapp.com remains disabled until its public resolver can be verified without a signed client API.
For non-interactive setup, write the same schema-v2 shape directly:
{
"schema_version": 2,
"products": [
{
"id": "vahan-expo-7d-floor-mats-seltos-2026",
"name": "Vahan Expo 7D floor mats for Seltos 2026",
"target": 3200,
"tier": "cold",
"notes": "",
"rejected_candidate_urls": [],
"listings": [
{
"id": "amazon-in-b0gmr9pn1w",
"retailer": "amazon.in",
"url": "https://www.amazon.in/dp/B0GMR9PN1W",
"confirmed_by": "seed",
"attributes": {
"brand": "Vahan Expo",
"model": "Kia Seltos 2026"
},
"source_urls": {
"pricehistory.app": "https://pricehistory.app/p/vahan-expo-premium-7d-car-mat-full-Yr0l7zce"
}
}
]
}
]
}
target is optional — a hard floor that always alerts. The statistical engine works
without it. tier (hot/warm/cold) is only a starting hint; anything scoring 60+
is auto-promoted to hot.
Every check appends to data/<listing-id>.csv, committed to the repo — your price history
is yours permanently, not locked inside someone's app.
It judges every price against two windows at once, and alerts if either fires.
Recent (180 days) — "is this cheap versus how it has been trading lately?" Adapts to price drift, so a product that launched at ₹4,500 and now sits at ₹3,300 doesn't look like a permanent bargain.
targetLifetime (max available) — "is this a festival-grade low?" Big Billion Days and the Great Indian Festival run once a year, so any window shorter than a year is structurally blind to the biggest drop of the year. These figures come from the source site, so they work from the very first run.
Both are needed. Lifetime-only would hide ordinary good deals behind an unbeatable annual floor; recent-only would miss the annual sale entirely.
Then these suppress the alert:
And these are surfaced inside the alert rather than hidden:
score (0–100) is measured against the lifetime range, not the recent one. The
180-day span is narrow enough that any new low saturates it, which would rate a modest
dip identically to a festival-grade one. On the Gillette trimmer (lifetime ₹2,799–₹3,999)
that gives ₹3,359 → 53, ₹3,250 → 62, ₹2,899 → 92, ₹2,799 → 100.
| Tier | Frequency |
|---|---|
| hot | ~30 min |
| warm | 3 hours |
| cold | 12 hours |
For 50 products that's roughly 350 primary requests/day, with randomised 4–11s gaps. Fallbacks add requests only when an earlier provider fails. Running cost: ₹0.
GitHub is not the limit. On a public repo Actions minutes are unlimited, so the binding constraint is how long one cycle takes when every product happens to be due at once (~9s each: fetch plus a randomised 4–11s gap).
| Products | Worst-case cycle | Verdict |
|---|---|---|
| 25 | ~4 min | comfortable |
| 50 | ~8 min | comfortable |
| 75 | ~11 min | fine |
| 100 | ~15 min | near the 20-min job timeout |
| 130+ | >20 min | would be cut off |
Practical ceiling: ~75 products. To go higher, either drop the inter-product sleep
in main.py, raise timeout-minutes in the workflow, or split the watchlist across
parallel matrix jobs.
Two caveats that matter more than the raw number:
Repo growth is a non-issue: price history appends ~60 bytes per product per check, so 50 products is roughly 50 MB/year before git compression, against GitHub's ~1 GB guidance.
Two bugs during build, both of which failed silently — worth knowing the shape of them:
os.environ.get("X", default)
returns "", not the default. Use os.environ.get("X") or default.daily_low). Charting every
30-minute sample meant ~156 KB of committed HTML per product, rewritten 48×/day.
Daily lows are what matter for a price tracker and are 41× smaller.Accept-Encoding: br unless brotli is installed. requests
cannot decode Brotli on its own and hands back binary garbage with HTTP 200, which
looks exactly like an IP block. This cost a long detour — the fetcher now requests
only gzip, deflate, and brotli is pinned in requirements as a safety net.The fetcher records provider, failure kind, HTTP status when available, breaker state,
and the latest attempts in data/state.json for diagnosis from Actions logs and state.
watchlist.json what to track
catalog.py URL normalization, identity, schema, and migration helpers
add_product.py confirmed one-link intake through verified providers
providers/ provider adapters and the Observation contract
fetch.py sequential provider fetch orchestration and breakers
analyze.py history storage + dual-window buy-zone engine
notify.py ntfy push + SMTP email
dashboard.py static dashboard generator
main.py orchestrator, adaptive scheduling
.github/workflows/track.yml best-effort 30-minute schedule
data/ listing price history (auto-committed)
data/state.json schema-v2 provider, product, and listing state
docs/index.html dashboard (auto-generated)
docs/chart-data/ lazy chart JSON (auto-generated)
Personal-use tool: low request volume, no account access, no resale of data.