Skip to content

feat(web): 開放三種圖表分析公開計算,修復合圖資料完整性 - #42

Merged
Retsomm merged 3 commits into
mainfrom
dev
Jun 21, 2026
Merged

Retsomm merged 3 commits into
mainfrom
dev

Conversation

@Retsomm

@Retsomm Retsomm commented Jun 21, 2026 •

Copy link
Copy Markdown
Owner
  • 移除 /api/charts、/api/composite/create、/api/transit/create 強制登入限制 未登入可計算分析,結果不存 DB(chartId: null);登入後仍自動儲存
  • 新增 /api/composite/create、/api/transit/create 端點(含流日影響分析)
  • 修復合圖 meta 缺漏:personB 補存 birthTime 與 timezone
  • 抽取 computeImpact 至 lib/transitImpact.ts 消除重複
  • 修正 account/page.tsx 合圖偵測邏輯(chart.type → chart.chartKind) 並從 meta 讀取 personA/personB 出生資料供重新計算

Summary by CodeRabbit

  • New Features
    • Added composite chart analysis to compare birth charts between two people
    • Added transit chart functionality to display current planetary positions and their influence
    • Added transit impact analysis showing how current transits affect a personal chart
    • Enhanced chart management to support multiple chart types (personal, composite, transit)

- 移除 /api/charts、/api/composite/create、/api/transit/create 強制登入限制
  未登入可計算分析,結果不存 DB(chartId: null);登入後仍自動儲存
- 新增 /api/composite/create、/api/transit/create 端點(含流日影響分析)
- 修復合圖 meta 缺漏:personB 補存 birthTime 與 timezone
- 抽取 computeImpact 至 lib/transitImpact.ts 消除重複
- 修正 account/page.tsx 合圖偵測邏輯(chart.type → chart.chartKind)
  並從 meta 讀取 personA/personB 出生資料供重新計算

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 21, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
self-map Ready Ready Preview, Comment Jun 21, 2026 3:26am

@coderabbitai

coderabbitai Bot commented Jun 21, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@Retsomm, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 27 minutes and 10 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: cc3f0d12-2763-4006-8e99-1234a9c16bf6

📥 Commits

Reviewing files that changed from the base of the PR and between 2f6f49a and 85563c7.

📒 Files selected for processing (7)
  • app/account/page.tsx
  • app/api/charts/route.ts
  • app/api/composite/create/route.ts
  • app/api/composite/route.ts
  • app/api/transit/create/route.ts
  • app/api/transit/impact/route.ts
  • prisma/schema.prisma
📝 Walkthrough

Walkthrough

Adds transit chart routes (GET /api/transit, POST /api/transit/create, POST /api/transit/impact) and composite chart routes (POST /api/composite/create, POST /api/composite). Introduces a computeImpact utility in lib/transitImpact.ts. Updates the personal chart API to persist chartKind and serve unauthenticated callers, and refactors the account page to use structured chartKind/meta fields for composite charts.

Changes

Transit & Composite Chart Features

Layer / File(s) Summary
Transit impact utility
lib/transitImpact.ts
Adds CENTER_ZH label map, ImpactLayer interface, and computeImpact function comparing personal vs. transit gates/centers/channels to produce center-activated, new-channel, and completing-channel impact layers.
chartKind/meta schema on personal charts and account page
app/account/page.tsx, app/api/charts/route.ts
Adds PersonMeta/ChartMeta interfaces and chartKind/meta to SavedChart; reworks composite HD computation to read from chart.meta instead of pipe-split strings; updates POST /api/charts to parse chartKind, return computed results without DB write for unauthenticated callers, and persist chartKind on creation.
Transit snapshot GET route
app/api/transit/route.ts
Implements GET /api/transit: initializes Swiss Ephemeris, computes current planetary longitudes, maps bodies to HD gate/line results, derives unique gate set, calculates definedCenterIds/definedChannels, and returns a timestamped payload.
Transit chart creation POST route
app/api/transit/create/route.ts
Implements POST /api/transit/create: validates birth inputs, concurrently computes HD and Swiss Ephemeris data, derives transit gates/centers/channels, merges with personal gates, calls computeImpact, returns result with chartId: null for unauthenticated callers, or creates a Prisma chart record with chartKind: 'transit' and meta.transitSnapshot for authenticated users.
Transit impact POST for saved chart
app/api/transit/impact/route.ts
Implements POST /api/transit/impact: fetches saved chart by chartId+userId, computes current transit gates/centers/channels via Swiss Ephemeris, calls computeImpact against stored personal data, and returns layers + computedAt.
Composite chart creation POST route
app/api/composite/create/route.ts
Implements POST /api/composite/create: defines BirthPayload type and hdToCompositeInput helper, validates both persons' birth fields, concurrently computes HD results, runs analyzeComposite, returns composite analysis for unauthenticated callers, or creates a Prisma chart with chartKind: 'composite' and serialized meta for authenticated users.
Composite analysis POST from saved charts
app/api/composite/route.ts
Implements POST /api/composite: validates chartAId/chartBId, fetches both charts from Prisma, converts each via toHdResult helper, calls analyzeComposite, and returns structured composite analysis with chart metadata.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant TransitCreate as POST /api/transit/create
  participant computeHdResult
  participant SwissEphemeris
  participant computeImpact
  participant Prisma

  Client->>TransitCreate: birth fields
  par HD calculation
    TransitCreate->>computeHdResult: birth inputs
    computeHdResult-->>TransitCreate: hdResult
  and Ephemeris calculation
    TransitCreate->>SwissEphemeris: current JD + planet indices
    SwissEphemeris-->>TransitCreate: longitudes
  end
  TransitCreate->>TransitCreate: derive transit gates/centers/channels + combined
  TransitCreate->>computeImpact: personal sets + transit sets
  computeImpact-->>TransitCreate: ImpactLayer[]
  alt unauthenticated
    TransitCreate-->>Client: chartId null + result
  else authenticated
    TransitCreate->>Prisma: chart.create chartKind transit meta transitSnapshot
    Prisma-->>TransitCreate: chart.id
    TransitCreate-->>Client: chartId + result
  end
Loading
sequenceDiagram
  participant Client
  participant CompositeCreate as POST /api/composite/create
  participant computeHdResult
  participant analyzeComposite
  participant Prisma

  Client->>CompositeCreate: personA + personB birth fields
  par
    CompositeCreate->>computeHdResult: personA inputs
    computeHdResult-->>CompositeCreate: hdA
  and
    CompositeCreate->>computeHdResult: personB inputs
    computeHdResult-->>CompositeCreate: hdB
  end
  CompositeCreate->>analyzeComposite: hdToCompositeInput(hdA) + hdToCompositeInput(hdB)
  analyzeComposite-->>CompositeCreate: composite analysis
  alt unauthenticated
    CompositeCreate-->>Client: chartId null + analysis + metaA/metaB
  else authenticated
    CompositeCreate->>Prisma: chart.create chartKind composite meta personA/B
    Prisma-->>CompositeCreate: chart.id
    CompositeCreate-->>Client: chartId + analysis + metaA/metaB
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • Retsomm/SelfMap#25: Added the original composite chart support using pipe-split birthDate/birthTime/timezone strings in app/account/page.tsx — the same composite detection and parsing logic that this PR replaces with structured chart.meta.
  • Retsomm/SelfMap#40: Modified POST /api/charts to use computeHdResultServer and derive chart fields, which this PR extends further by adding chartKind persistence and an unauthenticated return branch.

Poem

🐇 Hopping through the stars tonight,
Transits mapped with gates alight,
Composite souls now stored with care,
chartKind tells who's really there.
No more pipes to split apart—
Structured meta warms each chart! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 describes the main changes: opening three chart analysis types for public calculation and fixing composite chart data integrity.
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
  • Commit unit tests in branch dev

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🧹 Nitpick comments (2)
lib/transitImpact.ts (1)

16-64: ⚡ Quick win

Document the calculation principles inside computeImpact.

This function encodes three domain rules, but lacks comments explaining why each rule classifies impacts the way it does, which makes future changes brittle.

As per coding guidelines, "Each functionality should be implemented as an independent, reusable function with complete comments including calculation principles."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@lib/transitImpact.ts` around lines 16 - 64, The computeImpact function
contains three distinct domain rules for classifying impacts (center-activated,
new-channel, and completing-channel) but lacks explanatory comments describing
the calculation principles behind each rule. Add comment blocks above each of
the three main loops (the transitCenterIds loop, the transitChannels loop, and
the CHANNEL_DEFS loop) that clearly explain what principle or condition triggers
each impact type and why that classification makes sense from a domain
perspective. This will make the logic transparent and help future maintainers
understand the business rules without reverse-engineering the code.

Source: Coding guidelines

app/api/transit/route.ts (1)

5-42: 🏗️ Heavy lift

Extract transit snapshot computation into one reusable helper with calculation comments.

Lines 5-42 duplicate the same body constants and longitude→gate/center/channel pipeline also implemented in app/api/transit/create/route.ts and app/api/transit/impact/route.ts. Centralizing this into a shared server utility will prevent drift and keep transit semantics consistent.

As per coding guidelines, "Each functionality should be implemented as an independent, reusable function with complete comments including calculation principles."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/transit/route.ts` around lines 5 - 42, Extract the transit snapshot
computation pipeline (Swiss Ephemeris body constants SUN through PLUTO, the
dateToJulianDay/calculatePosition/calculatePlanetGates chain, and the
calculateCentersAndChannels call) from lines 5-42 into a dedicated reusable
server utility function. Create a new function that accepts a Date parameter and
returns an object containing the computed planets array, allGates set, and
center/channel calculations. Move all body constants into this utility module,
add detailed comments explaining the Swiss Ephemeris calculation principles and
gate/center/channel logic, then replace the duplicated implementation in
route.ts, create/route.ts, and impact/route.ts with calls to this shared helper
function.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/account/page.tsx`:
- Around line 227-231: The composite chart detection is inconsistent between two
locations in the file. Line 227 checks chart.chartKind === 'composite' to
compute HD results for both participants, but the UI gating at line 580 still
checks activeChart.type === 'composite' instead. Since new composite charts
created by app/api/composite/create/route.ts have chartKind set to 'composite'
but type set to an HD type, the UI gating at line 580 will fail to recognize
them. Update the condition at line 580 to check activeChart.chartKind ===
'composite' to match the detection logic at line 227, ensuring both the
computation and UI rendering use the same property for identifying composite
charts.

In `@app/api/charts/route.ts`:
- Around line 43-54: The error handling in the `computeHdResultServer` function
call (around line 106) needs to distinguish between input/domain validation
errors and actual server errors. Instead of returning a generic "伺服器錯誤" message
for all errors, wrap the `computeHdResultServer` call in a try-catch block that
catches and categorizes different error types. For validation or domain errors
(such as invalid birth data or chart computation issues), return a user-friendly
error message that explains what went wrong. Only return the generic server
error message for unexpected system-level errors. This allows unauthenticated
users to understand and correct their input rather than seeing an opaque error
message.
- Around line 11-12: Add validation for the `chartKind` variable immediately
after it is destructured from the request body in the line where you extract
`birthDate, birthTime, birthCity, timezone, name, chartKind`. The validation
should ensure that `chartKind` is not an empty string and contains only valid
chart type values that align with the contract expected by
account/composite/transit flows. Return an appropriate error response if the
validation fails, before any persistence operations occur.

In `@app/api/composite/create/route.ts`:
- Around line 31-37: The route handler uses an unsafe type assertion on the JSON
body without runtime validation of the actual structure and types. Before
accessing personA, personB, and name fields, add comprehensive validation to
ensure the body matches the expected shape and that all required fields are the
correct types (strings for birthDate, birthTime, birthCity, timezone, etc.).
Implement checks that verify not just the existence of these fields but also
their types and formats, returning a descriptive 400 error with field-level
guidance if any validation fails, rather than allowing the code to proceed with
potentially malformed data that could throw unhandled errors later in the
computeHdResultServer call.

In `@app/api/composite/route.ts`:
- Around line 24-27: The validation check for chartAId and chartBId in the
composite route handler only verifies their presence but does not validate that
they are strings, allowing invalid types to pass through and cause 500 errors in
Prisma operations. Enhance the validation condition to additionally check that
both chartAId and chartBId are of type string using typeof operator, ensuring
non-string values are rejected with a 400 status code and appropriate error
message before any database access occurs.
- Around line 9-17: In the toHdResult function, replace the unsafe type casts
with proper validation logic. For the chart.gates, chart.centers, and
chart.channels fields (all typed as unknown), add guard checks to verify they
are not null and are actually arrays before converting them to Sets. For the
profile property assignment on the return statement, remove the as any cast by
properly typing the profile object structure instead of relying on the unsafe
cast. Ensure each field is validated to prevent runtime crashes from malformed
or null Prisma Json data.

In `@app/api/transit/create/route.ts`:
- Around line 26-29: The Promise.all block calling
computeHdResultServer(birthDate, birthTime, timezone) can throw domain or input
validation errors (like unsupported birth years), but the error handling at
lines 148-150 converts all errors to HTTP 500 status codes. To fix this, wrap
the Promise.all block in a try-catch, and in the catch block distinguish between
validation/domain errors (which should return a 4xx status with a clear
user-friendly error message) and unexpected server errors (which should return
500). Check for specific error types or error properties to classify whether the
failure is due to invalid user input or an actual server issue, then respond
accordingly with the appropriate HTTP status code and error message.

In `@app/api/transit/impact/route.ts`:
- Around line 43-46: The code at lines 43-46 constructs Sets from untyped DB
JSON fields (chart.gates, chart.centers, chart.channels) using unsafe type casts
without validation. Add validation logic before constructing each Set to verify
that chart.gates is an array of numbers, chart.centers is an array of strings
corresponding to valid CenterName values, and chart.channels is an array of
strings. If any field fails validation, either throw an error with a descriptive
message or return early with an appropriate error response. This ensures that
malformed or legacy data from the database is caught before being used to
compute impact calculations.

---

Nitpick comments:
In `@app/api/transit/route.ts`:
- Around line 5-42: Extract the transit snapshot computation pipeline (Swiss
Ephemeris body constants SUN through PLUTO, the
dateToJulianDay/calculatePosition/calculatePlanetGates chain, and the
calculateCentersAndChannels call) from lines 5-42 into a dedicated reusable
server utility function. Create a new function that accepts a Date parameter and
returns an object containing the computed planets array, allGates set, and
center/channel calculations. Move all body constants into this utility module,
add detailed comments explaining the Swiss Ephemeris calculation principles and
gate/center/channel logic, then replace the duplicated implementation in
route.ts, create/route.ts, and impact/route.ts with calls to this shared helper
function.

In `@lib/transitImpact.ts`:
- Around line 16-64: The computeImpact function contains three distinct domain
rules for classifying impacts (center-activated, new-channel, and
completing-channel) but lacks explanatory comments describing the calculation
principles behind each rule. Add comment blocks above each of the three main
loops (the transitCenterIds loop, the transitChannels loop, and the CHANNEL_DEFS
loop) that clearly explain what principle or condition triggers each impact type
and why that classification makes sense from a domain perspective. This will
make the logic transparent and help future maintainers understand the business
rules without reverse-engineering the code.
🪄 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 Plus

Run ID: 938e240f-a8ec-43b0-9f18-639e5dd7958d

📥 Commits

Reviewing files that changed from the base of the PR and between ac545b8 and 2f6f49a.

📒 Files selected for processing (8)
  • app/account/page.tsx
  • app/api/charts/route.ts
  • app/api/composite/create/route.ts
  • app/api/composite/route.ts
  • app/api/transit/create/route.ts
  • app/api/transit/impact/route.ts
  • app/api/transit/route.ts
  • lib/transitImpact.ts

Comment thread app/account/page.tsx
Comment thread app/api/charts/route.ts
Comment on lines +11 to 12
const { birthDate, birthTime, birthCity, timezone, name, chartKind } = body
let { type, authority, profile, definition, centers, channels, gates } = body

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate chartKind before persisting.

chartKind is accepted as arbitrary input and can be stored as any string (including empty string), which weakens the cross-file contract used by account/composite/transit flows.

Suggested fix
-    const { birthDate, birthTime, birthCity, timezone, name, chartKind } = body
+    const { birthDate, birthTime, birthCity, timezone, name, chartKind } = body
+    const allowedChartKinds = new Set(['personal', 'composite', 'transit'] as const)
+    const normalizedChartKind =
+      typeof chartKind === 'string' && allowedChartKinds.has(chartKind as 'personal' | 'composite' | 'transit')
+        ? chartKind
+        : 'personal'
@@
-        chartKind: chartKind ?? 'personal',
+        chartKind: normalizedChartKind,

Also applies to: 98-98

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/charts/route.ts` around lines 11 - 12, Add validation for the
`chartKind` variable immediately after it is destructured from the request body
in the line where you extract `birthDate, birthTime, birthCity, timezone, name,
chartKind`. The validation should ensure that `chartKind` is not an empty string
and contains only valid chart type values that align with the contract expected
by account/composite/transit flows. Return an appropriate error response if the
validation fails, before any persistence operations occur.

Comment thread app/api/charts/route.ts
Comment thread app/api/composite/create/route.ts Outdated
Comment thread app/api/composite/route.ts
Comment thread app/api/composite/route.ts
Comment thread app/api/transit/create/route.ts Outdated
Comment on lines +43 to +46
const personalGates = new Set<number>(chart.gates as number[])
const personalCenterIds = new Set<CenterName>((chart.centers as string[]) as CenterName[])
const channelIdSet = new Set<string>(chart.channels as string[])
const personalChannels = CHANNEL_DEFS.filter(ch => channelIdSet.has(ch.id))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Validate persisted JSON shapes before constructing Sets.

Lines 43-46 trust untyped DB JSON via casts. If any row contains malformed/legacy gates/centers/channels, this can throw at runtime or produce invalid impact output.

Proposed fix
-    const personalGates     = new Set<number>(chart.gates as number[])
-    const personalCenterIds = new Set<CenterName>((chart.centers as string[]) as CenterName[])
-    const channelIdSet      = new Set<string>(chart.channels as string[])
+    const gates = Array.isArray(chart.gates) && chart.gates.every(g => typeof g === 'number')
+      ? chart.gates
+      : null
+    const centers = Array.isArray(chart.centers) && chart.centers.every(c => typeof c === 'string')
+      ? chart.centers
+      : null
+    const channels = Array.isArray(chart.channels) && chart.channels.every(c => typeof c === 'string')
+      ? chart.channels
+      : null
+
+    if (!gates || !centers || !channels) {
+      return NextResponse.json({ error: '圖表資料格式錯誤,請重新計算後再試' }, { status: 422 })
+    }
+
+    const personalGates     = new Set<number>(gates)
+    const personalCenterIds = new Set<CenterName>(centers as CenterName[])
+    const channelIdSet      = new Set<string>(channels)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@app/api/transit/impact/route.ts` around lines 43 - 46, The code at lines
43-46 constructs Sets from untyped DB JSON fields (chart.gates, chart.centers,
chart.channels) using unsafe type casts without validation. Add validation logic
before constructing each Set to verify that chart.gates is an array of numbers,
chart.centers is an array of strings corresponding to valid CenterName values,
and chart.channels is an array of strings. If any field fails validation, either
throw an error with a descriptive message or return early with an appropriate
error response. This ensures that malformed or legacy data from the database is
caught before being used to compute impact calculations.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- account/page.tsx: 合圖 UI 判斷改用 chartKind 而非 type
- api/charts: 新增 chartKind 白名單驗證;計算域錯誤回傳 400
- api/composite/create: BirthPayload 欄位型別驗證;計算錯誤回傳 400
- api/composite: chartAId/chartBId 補 typeof 檢查;toHdResult 加 Array.isArray 防護並移除 as any
- api/transit/create: 計算域錯誤回傳 400 而非 500
- api/transit/impact: DB JSON 欄位加 Array.isArray 前置驗證

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview — 85563c78 Deployed Jun 21, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant