Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 8 additions & 4 deletions khata/web/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,11 @@ def shift_day(d: date, delta: int) -> date:
return d + timedelta(days=delta)


# Indian weekly expiry days: NIFTY (NFO) weekly = Thursday (3 in Python weekday).
# BSE/Sensex weekly = Tuesday (1). We mark both.
def is_expiry_day(d: date) -> bool:
return d.weekday() in (1, 3)
# Retained for compatibility — the web UI now derives expiry days from the
# user's own trade history (see queries.expiry_days_in_range). This helper
# defaults to False so templates without an `expiry_days` set don't light up
# days that aren't actually expiries.
def is_expiry_day(d: date, expiry_days: frozenset[date] | None = None) -> bool:
if expiry_days is None:
return False
return d in expiry_days
9 changes: 7 additions & 2 deletions khata/web/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
TEMPLATES.env.globals.update(
fmt_rupees=fmt_rupees,
paise_to_rupees=paise_to_rupees,
is_expiry_day=H.is_expiry_day,
month_name=H.month_name,
fmt_time_ist=H.fmt_time_ist,
today_iso=lambda: H.today_ist().isoformat(),
Expand Down Expand Up @@ -70,6 +69,9 @@ def calendar_view(
grid = H.month_grid(year, month)
prev_y, prev_m = H.prev_month(year, month)
next_y, next_m = H.next_month(year, month)
expiry_days = Q.expiry_days_in_range(
conn, user_id, date(year, month, 1), date(next_y, next_m, 1)
)

# Month totals
total_net = sum((d.get("net_paise") or 0) for d in summary.values())
Expand All @@ -84,6 +86,7 @@ def calendar_view(
"month": month,
"grid": grid,
"summary": summary,
"expiry_days": expiry_days,
"prev_y": prev_y,
"prev_m": prev_m,
"next_y": next_y,
Expand All @@ -109,6 +112,8 @@ def day_view(
trades = Q.trades_on_day(conn, user_id, d)
totals = Q.day_totals(trades)
note = Q.get_daily_note(conn, user_id, d)
# `d` counts as an expiry day if any trade in the user's book expires on it.
expiry_days = Q.expiry_days_in_range(conn, user_id, d, H.shift_day(d, 1))
return TEMPLATES.TemplateResponse(
request,
"day.html",
Expand All @@ -120,7 +125,7 @@ def day_view(
"totals": totals,
"note": note,
"endpoint": f"/notes/day/{d.isoformat()}",
"is_expiry": H.is_expiry_day(d),
"is_expiry": d in expiry_days,
},
)

Expand Down
29 changes: 29 additions & 0 deletions khata/web/queries.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,35 @@


# ── calendar ───────────────────────────────────────────────────────────
def expiry_days_in_range(
conn: sqlite3.Connection, user_id: int, start: date, end_excl: date
) -> frozenset[date]:
"""Distinct `trades.expiry` values falling inside [start, end_excl).

Used to mark calendar days the user actually had an expiring contract on,
rather than hardcoding Tue/Thu. Empty set if the user has no derivative
trades or no data yet.
"""
rows = conn.execute(
"""
SELECT DISTINCT expiry
FROM trades
WHERE user_id = ?
AND expiry IS NOT NULL
AND expiry >= ?
AND expiry < ?
""",
(user_id, start.isoformat(), end_excl.isoformat()),
).fetchall()
out: set[date] = set()
for r in rows:
try:
out.add(date.fromisoformat(r["expiry"]))
except (TypeError, ValueError):
continue
return frozenset(out)


def month_summary_by_day(
conn: sqlite3.Connection, user_id: int, year: int, month: int
) -> dict[str, dict[str, Any]]:
Expand Down
6 changes: 3 additions & 3 deletions khata/web/templates/calendar.html
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ <h1>{{ month_name(month) }} {{ year }}</h1>
{% set iso = d.isoformat() %}
{% set s = summary.get(iso) %}
{% set net = s.net_paise if s else 0 %}
<a class="day {% if d == today %}day-today{% endif %} {% if s %}{% if net > 0 %}day-win{% elif net < 0 %}day-loss{% else %}day-neutral{% endif %}{% endif %} {% if is_expiry_day(d) %}day-expiry{% endif %}"
<a class="day {% if d == today %}day-today{% endif %} {% if s %}{% if net > 0 %}day-win{% elif net < 0 %}day-loss{% else %}day-neutral{% endif %}{% endif %} {% if d in expiry_days %}day-expiry{% endif %}"
href="/day/{{ iso }}">
<span class="day-num">{{ d.day }}</span>
{% if is_expiry_day(d) %}<span class="expiry-dot" title="weekly expiry">•</span>{% endif %}
{% if d in expiry_days %}<span class="expiry-dot" title="expiry day">•</span>{% endif %}
{% if s %}
<span class="day-pnl">{{ fmt_rupees(net) }}</span>
<span class="day-count">{{ s.n }} trade{{ '' if s.n == 1 else 's' }}</span>
Expand All @@ -52,7 +52,7 @@ <h1>{{ month_name(month) }} {{ year }}</h1>
<footer class="legend">
<span class="swatch swatch-win"></span> win day
<span class="swatch swatch-loss"></span> loss day
<span class="swatch swatch-expiry"></span> weekly expiry (Tue/Thu)
<span class="swatch swatch-expiry"></span> expiry of a contract you traded
</footer>
</section>
{% endblock %}