Skip to content

Feature: medication pricing#1

Open
KMS74 wants to merge 2 commits into
devfrom
feature/medication-pricing-fixes
Open

Feature: medication pricing#1
KMS74 wants to merge 2 commits into
devfrom
feature/medication-pricing-fixes

Conversation

@KMS74

@KMS74 KMS74 commented Apr 25, 2026

Copy link
Copy Markdown
Owner

Summary by CodeRabbit

New Features

  • Displays estimated medication prices in Egyptian Pounds (EGP) on medication cards and PDF reports
  • Shows local Egyptian generic alternatives with manufacturer details and pricing comparisons
  • Includes savings badges highlighting cost differences between main and alternative medications
  • Provides toggleable sections to view/hide pricing and alternatives information
  • Adds external links to search for local pharmacy pricing in Egypt
  • Integrates pricing disclaimers in reports and UI

Documentation

  • Added pricing-related text labels in English and Arabic translations

Karim Shabana added 2 commits April 25, 2026 15:35
- Update pricing instructions to request "approximate prices" instead of real-time market rates.
- Add mandatory user-facing disclaimers in the prompt stating that prices are estimates.
- Align prompt examples and descriptions for `estimatedPrice` with the actual JSON schema (object format).
- Refine uncertainty handling to allow omitting pricing data or setting it to null when confidence is low.
- Fix TypeScript syntax error in `lib/gemini.ts` by escaping nested backticks in the prompt template string.
- Consolidate medical and pricing disclaimers into the root `disclaimer` output field.
@KMS74 KMS74 self-assigned this Apr 25, 2026
@KMS74 KMS74 added the enhancement New feature or request label Apr 25, 2026
@vercel

vercel Bot commented Apr 25, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
roshetta-ai Ready Ready Preview, Comment Apr 25, 2026 1:02pm

@coderabbitai

coderabbitai Bot commented Apr 25, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR introduces a medication pricing and Egyptian local alternatives feature throughout the application. It extends data models with pricing types, updates the Gemini API prompt to request structured pricing and alternative medication data, and adds UI components to display pricing badges, collapsible cards, and local alternative suggestions with savings calculations.

Changes

Cohort / File(s) Summary
Type Definitions
lib/types/prescription.ts
Added PriceEstimate and EgyptianAlternative interfaces; extended Medication with optional estimatedPrice and egyptianAlternatives fields.
PDF Report Components
components/MedicalReport.tsx, hooks/useScanner.tsx
Added pricing section to PDF with conditional rendering, introduced pricing-related label props, and extended labels object with translated pricing strings.
Medication Display Components
components/results/MedicationCard.tsx, components/results/MedicationPricing.tsx
Added inline price badge to MedicationCard; introduced new MedicationPricing component with animated collapsible sections, pricing toggles, alternatives list, savings calculations, and external search links.
API Integration
lib/gemini.ts
Updated Gemini prompt to request Egyptian pricing (EGP) and up to three local generic alternatives per medication; expanded JSON schema to include structured estimatedPrice and egyptianAlternatives fields in responses.
Localization
messages/en.json, messages/ar.json
Added UI text for estimated price display, alternatives discovery, manufacturer/savings labels, pricing toggles, disclaimers, and local product search hints in both English and Arabic.
Configuration
next-env.d.ts
Updated Next.js generated types import path from /.next/dev/types/routes.d.ts to /.next/types/routes.d.ts.

Sequence Diagram

sequenceDiagram
    actor User
    participant MedicationCard as MedicationCard<br/>(Client)
    participant MedicationPricing as MedicationPricing<br/>(Display)
    participant Gemini as Gemini API<br/>(Data Source)
    
    User->>Gemini: Upload prescription image
    Gemini->>Gemini: Extract & structure pricing<br/>+ alternatives
    Gemini-->>MedicationCard: Return Medication[]<br/>with pricing data
    
    MedicationCard->>MedicationCard: Render price badge<br/>(if price > 0)
    MedicationCard->>MedicationPricing: Pass estimatedPrice<br/>+ egyptianAlternatives
    
    MedicationPricing->>MedicationPricing: Render price card<br/>with toggle
    MedicationPricing->>MedicationPricing: Calculate maxSavings<br/>from alternatives
    MedicationPricing->>MedicationPricing: Render collapsible<br/>alternatives list
    
    User->>MedicationPricing: Click toggles
    MedicationPricing->>MedicationPricing: Animate expand/collapse
    MedicationPricing-->>User: Display alternatives<br/>+ savings badges
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 A bundle of prices hops into view,
Local alternatives, both fresh and true!
Savings dance, toggles shine with delight,
Egyptian generics make wallets light!
From Gemini's wisdom to cards so neat,
A pricing adventure, complete and sweet! 💊✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 25.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 'Feature: medication pricing' directly corresponds to the main objective of the pull request, which adds comprehensive medication pricing functionality including price estimation, Egyptian alternatives display, and related UI components.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/medication-pricing-fixes

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: 6

🧹 Nitpick comments (4)
lib/gemini.ts (1)

89-118: Schema/type alignment for alternatives.

A few schema details to confirm against the intended product behavior (root-cause already captured against lib/types/prescription.ts):

  • The alternative's estimatedPrice is nullable: true and not in required, but the prompt at line 44 instructs "Omit if uncertain". Combined with the current TS type marking it required, downstream renderers will crash on omitted prices. Either tighten the schema to require it or relax the type.
  • required: ["name", "manufacturer", "note"] makes note mandatory, yet MedicationPricing.tsx and MedicalReport.tsx both render it via if (alt.note)/ternary, suggesting the UI expects it to be optional. Decide which side is canonical.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@lib/gemini.ts` around lines 89 - 118, The schema for egyptianAlternatives is
inconsistent with the prompt and UI: either make alternative.estimatedPrice
required in the JSON schema (remove nullable and add "estimatedPrice" to
required) to match a required TS type, or (preferred) relax types by keeping
estimatedPrice nullable/omitted in the schema and update the TS type in
lib/types/prescription.ts to make estimatedPrice optional; likewise remove
"note" from egyptianAlternatives.required so note is optional (or alternatively
update MedicationPricing.tsx and MedicalReport.tsx to treat note as required);
pick the side of least UI disruption—make estimatedPrice and note optional in
the TS type and schema to match the "Omit if uncertain" prompt and the
components that check alt.note.
components/results/MedicationPricing.tsx (2)

12-18: medicationName prop is unused.

It's declared in MedicationPricingProps and destructured but never referenced in the body. Either drop it or wire it up to something useful — e.g., as an aria-label for the section, or to enrich the pharmacy search URL on line 187 (currently it searches only the alternative's name; including the original drug context could help users compare against the prescribed brand):

Suggested usage example
-                          <a
-                            href={`https://www.google.com/search?q=${encodeURIComponent(alt.name + ' pharmacy Egypt price')}`}
+                          <a
+                            href={`https://www.google.com/search?q=${encodeURIComponent(`${alt.name} (${medicationName} alternative) pharmacy Egypt price`)}`}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/results/MedicationPricing.tsx` around lines 12 - 18, The
medicationName prop is declared and destructured in the MedicationPricing
component but never used; either remove it from
MedicationPricingProps/destructuring or wire it into the UI: add medicationName
as an aria-label on the component's root container (e.g., "<section
aria-label={medicationName}>") and/or include it when building the pharmacy
search URL used in the pharmacy link logic (concatenate or include as a query
param alongside the alternative's name so the search query contains both the
original prescribed drug and the alternative). Update references in
MedicationPricing (props and JSX) accordingly and remove the prop from the type
if you choose to drop it.

67-79: Two unrelated buttons drive the same isExpanded state — confusing UX.

The savings-chip button on the price card (line 69) and the alternatives section header (line 93) both call setIsExpanded(!isExpanded). Side effects:

  • Clicking the savings chip on the price card silently toggles the alternatives panel below, even though the chip's text ("Save up to EGP …") doesn't visually communicate "open list".
  • Conversely, the chevron on the savings chip (line 76) rotates whenever the alternatives panel is opened from the header — but the chip is in a different visual region, so the rotation looks like a stray animation.
  • If a future change shows the price card without alternatives (or vice versa), one of the buttons becomes a dead control still mutating shared state.

Consider either consolidating to a single primary toggle (the section header), or giving the savings chip a clearer call-to-action label (e.g., "View alternatives") so the relationship is explicit. At minimum, give the toggle button an aria-expanded and aria-controls linking it to the collapsible region for screen-reader users.

Also applies to: 93-116

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

In `@components/results/MedicationPricing.tsx` around lines 67 - 79, Two unrelated
controls are toggling the same state (isExpanded via setIsExpanded) causing
confusing UX and stray animations; update the component so only one primary
toggle controls the alternatives panel (preferably the alternatives section
header's toggle) and make the savings chip a non-toggle affordance or an
explicit "View alternatives" action that activates the header toggle;
specifically, remove or change the onClick on the savings-chip button (the
element containing TrendingDown, t('savingsLabel', ...), and ChevronDown) so it
no longer directly calls setIsExpanded(!isExpanded), or wire it to call a single
handler (e.g., toggleAlternatives) that delegates to the header's toggle logic;
additionally add aria-expanded and aria-controls attributes to the real
collapsible trigger and ensure the chip's button (if still clickable) also uses
aria-expanded and aria-controls pointing to the collapsible region so screen
readers understand the relationship.
components/results/MedicationCard.tsx (1)

44-50: Price is rendered twice per medication card.

When medication.estimatedPrice.price > 0, this inline badge displays the price (e.g. "💊 EGP 97") and MedicationPricing (line 117) renders a large prominent price panel showing the same value. That's redundant on a single card and adds visual noise for what is effectively one data point.

Consider one of:

  • Keep only the large panel inside MedicationPricing (drop the inline badge), or
  • Keep the inline badge as the primary price surface and remove the price block from MedicationPricing (let it focus on alternatives only).

Also, the 💊 glyph here is a decorative inline emoji with no accessible name. The equivalent emoji inside MedicationPricing is wrapped with role="img" + aria-label. Either add aria-hidden="true" here or wrap consistently:

Proposed a11y fix
-                  <span className="inline-flex items-center gap-1 rounded-lg bg-emerald-50 dark:bg-emerald-950/30 border border-emerald-200/60 dark:border-emerald-700/30 px-2 py-0.5 text-[11px] font-black tabular-nums text-emerald-700 dark:text-emerald-300">
-                    💊 {medication.estimatedPrice.currency} {medication.estimatedPrice.price}
-                  </span>
+                  <span className="inline-flex items-center gap-1 rounded-lg bg-emerald-50 dark:bg-emerald-950/30 border border-emerald-200/60 dark:border-emerald-700/30 px-2 py-0.5 text-[11px] font-black tabular-nums text-emerald-700 dark:text-emerald-300">
+                    <span aria-hidden="true">💊</span> {medication.estimatedPrice.currency} {medication.estimatedPrice.price}
+                  </span>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/results/MedicationCard.tsx` around lines 44 - 50, The inline price
badge in MedicationCard (the span rendering medication.estimatedPrice)
duplicates the price shown by MedicationPricing; remove the inline badge so the
large price panel from MedicationPricing is the single price surface, and keep
MedicationPricing responsible for displaying medication.estimatedPrice.price and
currency. If you prefer the badge as primary instead, instead remove the price
display logic inside MedicationPricing and leave it to show alternatives only.
Also fix the emoji accessibility: if you keep any inline decorative emoji (💊)
in MedicationCard, add aria-hidden="true" to that emoji or otherwise wrap it
with role="img" and an aria-label to match MedicationPricing’s approach.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@components/MedicalReport.tsx`:
- Around line 689-725: The pricing section currently checks (med.estimatedPrice
|| (med.egyptianAlternatives && med.egyptianAlternatives.length > 0)) which
still renders when estimatedPrice.price === 0 and alternatives are empty; change
the outer guard to require the same detailed conditions used inside: only render
if (med.estimatedPrice && med.estimatedPrice.price > 0) ||
(med.egyptianAlternatives && med.egyptianAlternatives.length > 0), so the inner
blocks (the estimated price block that checks med.estimatedPrice.price > 0 and
the egyptianAlternatives.map block) are guaranteed to produce content before
showing the pricingSection, keeping labels.pricingSection and pricingDisclaimer
from rendering alone.
- Around line 518-538: Replace the locally redeclared types PriceEstimatePDF,
EgyptianAlternativePDF and Medication with the canonical exports from
lib/types/prescription.ts (import PriceEstimate, EgyptianAlternative and
Medication and use them instead of the local declarations) to avoid type drift;
update the interface references in this file to use those imported symbols,
remove the duplicate declarations, and add a null-safety guard around accesses
like alt.estimatedPrice.currency / alt.estimatedPrice.price (or narrow via
optional chaining) since estimatedPrice may be optional in the shared type.

In `@lib/gemini.ts`:
- Around line 31-37: The pricing-estimate sentence is duplicated because it's
present in the root "disclaimer" and also rendered per-medication via
labels.pricingDisclaimer and t('priceDisclaimer') in MedicalReport.tsx and
results/MedicationPricing.tsx; remove the sentence "Prices are estimates and may
not reflect current market rates" from the root `disclaimer` value (and
corresponding root-language strings) so the per-section
`labels.pricingDisclaimer` / t('priceDisclaimer') remain the single source of
truth, and update any translation entries for the root disclaimer to omit that
sentence.

In `@lib/types/prescription.ts`:
- Around line 11-16: The TypeScript interface EgyptianAlternative is too strict:
change estimatedPrice: PriceEstimate to estimatedPrice?: PriceEstimate and note:
string to note?: string in lib/types/prescription.ts to match the Gemini schema
which allows nullable/absent prices; then update callers (e.g.,
components/results/MedicationPricing.tsx and components/MedicalReport.tsx) to
either filter egyptianAlternatives for alt.estimatedPrice before computing
maxSavings/savings or add guards where used (e.g., check alt.estimatedPrice &&
alt.estimatedPrice.price and only render price/currency when present) so no code
assumes estimatedPrice is always defined.

In `@messages/en.json`:
- Around line 67-79: The mentioned translation keys (priceRange, noAlternatives,
showPricing, hidePricing, localProduct) are defined but unused; either wire them
into the UI or remove them. Fix by: (1) replace the inline price rendering with
the i18n template key priceRange (pass a single formatted price string or
numeric price as the {price} param where the current estimatedPrice rendering
occurs), (2) add an empty-state render that uses noAlternatives when the
alternatives list is empty inside the alternatives component, (3) make the
pricing toggle button text come from showPricing and hidePricing based on the
toggle state instead of hardcoded text, and (4) use localProduct as the
label/aria-label when rendering each locally manufactured alternative in the
alternatives list; if you prefer not to surface these strings, remove them from
messages/en.json and messages/ar.json to avoid translation drift.

In `@next-env.d.ts`:
- Line 3: The committed auto-generated Next.js type file next-env.d.ts
(containing the line `import "./.next/types/routes.d.ts";`) should be removed
from version control and ignored: add next-env.d.ts to .gitignore and run git rm
--cached next-env.d.ts to stop tracking it, then amend the commit so this change
is not part of the medication-pricing feature PR; ensure no code edits are made
to next-env.d.ts itself since it is auto-generated.

---

Nitpick comments:
In `@components/results/MedicationCard.tsx`:
- Around line 44-50: The inline price badge in MedicationCard (the span
rendering medication.estimatedPrice) duplicates the price shown by
MedicationPricing; remove the inline badge so the large price panel from
MedicationPricing is the single price surface, and keep MedicationPricing
responsible for displaying medication.estimatedPrice.price and currency. If you
prefer the badge as primary instead, instead remove the price display logic
inside MedicationPricing and leave it to show alternatives only. Also fix the
emoji accessibility: if you keep any inline decorative emoji (💊) in
MedicationCard, add aria-hidden="true" to that emoji or otherwise wrap it with
role="img" and an aria-label to match MedicationPricing’s approach.

In `@components/results/MedicationPricing.tsx`:
- Around line 12-18: The medicationName prop is declared and destructured in the
MedicationPricing component but never used; either remove it from
MedicationPricingProps/destructuring or wire it into the UI: add medicationName
as an aria-label on the component's root container (e.g., "<section
aria-label={medicationName}>") and/or include it when building the pharmacy
search URL used in the pharmacy link logic (concatenate or include as a query
param alongside the alternative's name so the search query contains both the
original prescribed drug and the alternative). Update references in
MedicationPricing (props and JSX) accordingly and remove the prop from the type
if you choose to drop it.
- Around line 67-79: Two unrelated controls are toggling the same state
(isExpanded via setIsExpanded) causing confusing UX and stray animations; update
the component so only one primary toggle controls the alternatives panel
(preferably the alternatives section header's toggle) and make the savings chip
a non-toggle affordance or an explicit "View alternatives" action that activates
the header toggle; specifically, remove or change the onClick on the
savings-chip button (the element containing TrendingDown, t('savingsLabel',
...), and ChevronDown) so it no longer directly calls
setIsExpanded(!isExpanded), or wire it to call a single handler (e.g.,
toggleAlternatives) that delegates to the header's toggle logic; additionally
add aria-expanded and aria-controls attributes to the real collapsible trigger
and ensure the chip's button (if still clickable) also uses aria-expanded and
aria-controls pointing to the collapsible region so screen readers understand
the relationship.

In `@lib/gemini.ts`:
- Around line 89-118: The schema for egyptianAlternatives is inconsistent with
the prompt and UI: either make alternative.estimatedPrice required in the JSON
schema (remove nullable and add "estimatedPrice" to required) to match a
required TS type, or (preferred) relax types by keeping estimatedPrice
nullable/omitted in the schema and update the TS type in
lib/types/prescription.ts to make estimatedPrice optional; likewise remove
"note" from egyptianAlternatives.required so note is optional (or alternatively
update MedicationPricing.tsx and MedicalReport.tsx to treat note as required);
pick the side of least UI disruption—make estimatedPrice and note optional in
the TS type and schema to match the "Omit if uncertain" prompt and the
components that check alt.note.
🪄 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: cf1b6ae6-e647-4445-8cf7-96711f30cd14

📥 Commits

Reviewing files that changed from the base of the PR and between fe83754 and 8569471.

📒 Files selected for processing (9)
  • components/MedicalReport.tsx
  • components/results/MedicationCard.tsx
  • components/results/MedicationPricing.tsx
  • hooks/useScanner.tsx
  • lib/gemini.ts
  • lib/types/prescription.ts
  • messages/ar.json
  • messages/en.json
  • next-env.d.ts

Comment on lines +518 to 538
interface PriceEstimatePDF {
price: number;
currency: string;
}

interface EgyptianAlternativePDF {
name: string;
manufacturer: string;
estimatedPrice: PriceEstimatePDF;
note: string;
}

interface Medication {
name: string;
dosage: string;
usage: string;
tip: string;
reminders: { time: string; label: string }[];
estimatedPrice?: PriceEstimatePDF;
egyptianAlternatives?: EgyptianAlternativePDF[];
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Avoid redeclaring pricing types — import the canonical ones.

PriceEstimatePDF and EgyptianAlternativePDF are structurally identical to PriceEstimate and EgyptianAlternative in lib/types/prescription.ts, and the local Medication interface here is a structural subset of the exported Medication. Redeclaring them invites drift (e.g., if note becomes optional in the canonical type, the PDF copy will still mark it required). Reuse the shared types so changes there propagate automatically.

Proposed fix
-import { normalizeInteractionSeverity, type InteractionSeverity } from '@/lib/types/prescription';
+import {
+  normalizeInteractionSeverity,
+  type InteractionSeverity,
+  type Medication,
+  type Interaction,
+  type PriceEstimate,
+  type EgyptianAlternative,
+} from '@/lib/types/prescription';
@@
-interface PriceEstimatePDF {
-  price: number;
-  currency: string;
-}
-
-interface EgyptianAlternativePDF {
-  name: string;
-  manufacturer: string;
-  estimatedPrice: PriceEstimatePDF;
-  note: string;
-}
-
-interface Medication {
-  name: string;
-  dosage: string;
-  usage: string;
-  tip: string;
-  reminders: { time: string; label: string }[];
-  estimatedPrice?: PriceEstimatePDF;
-  egyptianAlternatives?: EgyptianAlternativePDF[];
-}
-
-interface Interaction {
-  severity: string;
-  description: string;
-}

Note: the unsafe access on line 715 (alt.estimatedPrice.currency} {alt.estimatedPrice.price) is part of the broader null-safety issue raised on lib/types/prescription.ts and will need a guard once the canonical type marks estimatedPrice optional.

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

In `@components/MedicalReport.tsx` around lines 518 - 538, Replace the locally
redeclared types PriceEstimatePDF, EgyptianAlternativePDF and Medication with
the canonical exports from lib/types/prescription.ts (import PriceEstimate,
EgyptianAlternative and Medication and use them instead of the local
declarations) to avoid type drift; update the interface references in this file
to use those imported symbols, remove the duplicate declarations, and add a
null-safety guard around accesses like alt.estimatedPrice.currency /
alt.estimatedPrice.price (or narrow via optional chaining) since estimatedPrice
may be optional in the shared type.

Comment on lines +689 to +725

{/* Pricing & Alternatives Section */}
{(med.estimatedPrice || (med.egyptianAlternatives && med.egyptianAlternatives.length > 0)) ? (
<View style={styles.pricingSection}>
<Text style={styles.pricingLabel}>{labels.pricingSection}</Text>

{med.estimatedPrice && med.estimatedPrice.price > 0 ? (
<View style={styles.priceBox}>
<Text style={styles.priceText}>
{labels.estimatedPriceLabel}: {med.estimatedPrice.currency} {med.estimatedPrice.price}
</Text>
</View>
) : null}

{med.egyptianAlternatives && med.egyptianAlternatives.length > 0 ? (
<View style={{ marginTop: 4 }}>
<Text style={[styles.pricingLabel, { marginTop: 4, marginBottom: 8 }]}>{labels.alternativesLabel}</Text>
{med.egyptianAlternatives.map((alt, ai) => (
<View key={ai} style={styles.altRow}>
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: 0, minWidth: 0 }}>
<Text style={styles.altName}>{alt.name}</Text>
<Text style={styles.altDetail}>{alt.manufacturer}</Text>
{alt.note ? <Text style={styles.altDetail}>{alt.note}</Text> : null}
</View>
<View style={{ flexShrink: 0, alignItems: isRtl ? 'flex-start' : 'flex-end' }}>
<Text style={styles.altPrice}>
{alt.estimatedPrice.currency} {alt.estimatedPrice.price}
</Text>
</View>
</View>
))}
</View>
) : null}

<Text style={styles.pricingDisclaimer}>{labels.pricingDisclaimer}</Text>
</View>
) : null}

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 | 🟡 Minor

Pricing section can render with no content.

The outer guard med.estimatedPrice || (med.egyptianAlternatives?.length > 0) admits the case where med.estimatedPrice is present with price === 0 and egyptianAlternatives is empty. In that scenario both inner blocks short-circuit (line 695 requires price > 0; line 703 requires non-empty alternatives) and the section ends up rendering only the section title and disclaimer. Tighten the outer guard to match:

Proposed fix
-              {(med.estimatedPrice || (med.egyptianAlternatives && med.egyptianAlternatives.length > 0)) ? (
+              {((med.estimatedPrice && med.estimatedPrice.price > 0) || (med.egyptianAlternatives && med.egyptianAlternatives.length > 0)) ? (
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
{/* Pricing & Alternatives Section */}
{(med.estimatedPrice || (med.egyptianAlternatives && med.egyptianAlternatives.length > 0)) ? (
<View style={styles.pricingSection}>
<Text style={styles.pricingLabel}>{labels.pricingSection}</Text>
{med.estimatedPrice && med.estimatedPrice.price > 0 ? (
<View style={styles.priceBox}>
<Text style={styles.priceText}>
{labels.estimatedPriceLabel}: {med.estimatedPrice.currency} {med.estimatedPrice.price}
</Text>
</View>
) : null}
{med.egyptianAlternatives && med.egyptianAlternatives.length > 0 ? (
<View style={{ marginTop: 4 }}>
<Text style={[styles.pricingLabel, { marginTop: 4, marginBottom: 8 }]}>{labels.alternativesLabel}</Text>
{med.egyptianAlternatives.map((alt, ai) => (
<View key={ai} style={styles.altRow}>
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: 0, minWidth: 0 }}>
<Text style={styles.altName}>{alt.name}</Text>
<Text style={styles.altDetail}>{alt.manufacturer}</Text>
{alt.note ? <Text style={styles.altDetail}>{alt.note}</Text> : null}
</View>
<View style={{ flexShrink: 0, alignItems: isRtl ? 'flex-start' : 'flex-end' }}>
<Text style={styles.altPrice}>
{alt.estimatedPrice.currency} {alt.estimatedPrice.price}
</Text>
</View>
</View>
))}
</View>
) : null}
<Text style={styles.pricingDisclaimer}>{labels.pricingDisclaimer}</Text>
</View>
) : null}
{/* Pricing & Alternatives Section */}
{((med.estimatedPrice && med.estimatedPrice.price > 0) || (med.egyptianAlternatives && med.egyptianAlternatives.length > 0)) ? (
<View style={styles.pricingSection}>
<Text style={styles.pricingLabel}>{labels.pricingSection}</Text>
{med.estimatedPrice && med.estimatedPrice.price > 0 ? (
<View style={styles.priceBox}>
<Text style={styles.priceText}>
{labels.estimatedPriceLabel}: {med.estimatedPrice.currency} {med.estimatedPrice.price}
</Text>
</View>
) : null}
{med.egyptianAlternatives && med.egyptianAlternatives.length > 0 ? (
<View style={{ marginTop: 4 }}>
<Text style={[styles.pricingLabel, { marginTop: 4, marginBottom: 8 }]}>{labels.alternativesLabel}</Text>
{med.egyptianAlternatives.map((alt, ai) => (
<View key={ai} style={styles.altRow}>
<View style={{ flexGrow: 1, flexShrink: 1, flexBasis: 0, minWidth: 0 }}>
<Text style={styles.altName}>{alt.name}</Text>
<Text style={styles.altDetail}>{alt.manufacturer}</Text>
{alt.note ? <Text style={styles.altDetail}>{alt.note}</Text> : null}
</View>
<View style={{ flexShrink: 0, alignItems: isRtl ? 'flex-start' : 'flex-end' }}>
<Text style={styles.altPrice}>
{alt.estimatedPrice.currency} {alt.estimatedPrice.price}
</Text>
</View>
</View>
))}
</View>
) : null}
<Text style={styles.pricingDisclaimer}>{labels.pricingDisclaimer}</Text>
</View>
) : null}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@components/MedicalReport.tsx` around lines 689 - 725, The pricing section
currently checks (med.estimatedPrice || (med.egyptianAlternatives &&
med.egyptianAlternatives.length > 0)) which still renders when
estimatedPrice.price === 0 and alternatives are empty; change the outer guard to
require the same detailed conditions used inside: only render if
(med.estimatedPrice && med.estimatedPrice.price > 0) ||
(med.egyptianAlternatives && med.egyptianAlternatives.length > 0), so the inner
blocks (the estimated price block that checks med.estimatedPrice.price > 0 and
the egyptianAlternatives.map block) are guaranteed to produce content before
showing the pricingSection, keeping labels.pricingSection and pricingDisclaimer
from rendering alone.

Comment thread lib/gemini.ts
Comment on lines +31 to +37
5. **Medication Pricing (Egyptian Market)**:
- Provide an **approximate price** (as of your knowledge cutoff or available data) for medications in Egypt.
- **Disclaimer**: In the root 'disclaimer' field, you MUST include a note that "Prices are estimates and may not reflect current market rates" in the appropriate language, along with the mandatory medical disclaimer.
- **Accuracy**: If real-time accuracy is required, the system should ideally call an external pricing API. If you cannot determine a price with reasonable confidence, you MUST omit the \`estimatedPrice\` field or set it to null.
- **Format**: \`estimatedPrice\` must be an object: \`{ "price": number, "currency": "EGP" }\`.
- **Example**: \`estimatedPrice: { "price": 97, "currency": "EGP" }\`. Be precise with concentrations and pack sizes.
- Always set currency to "EGP".

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 | 🟡 Minor

Pricing disclaimer is doubled in the rendered output.

The prompt mandates that the model append "Prices are estimates and may not reflect current market rates" (line 33) into the root disclaimer (and the mandatory English/Arabic disclaimer texts on lines 58–59 already contain this sentence). At the same time:

  • components/MedicalReport.tsx line 723 renders the standalone labels.pricingDisclaimer.
  • components/results/MedicationPricing.tsx line 210 renders t('priceDisclaimer').

Net effect: in the PDF the user sees the pricing-estimate caveat both inside the medical disclaimer block at the bottom and inside every medication's pricing section; in the app UI it appears in every medication card too. Pick one source of truth — either drop the pricing-estimate sentence from the root disclaimer text and keep the per-section pricingDisclaimer, or remove the per-section disclaimer and rely on the root one.

Also applies to: 56-59

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

In `@lib/gemini.ts` around lines 31 - 37, The pricing-estimate sentence is
duplicated because it's present in the root "disclaimer" and also rendered
per-medication via labels.pricingDisclaimer and t('priceDisclaimer') in
MedicalReport.tsx and results/MedicationPricing.tsx; remove the sentence "Prices
are estimates and may not reflect current market rates" from the root
`disclaimer` value (and corresponding root-language strings) so the per-section
`labels.pricingDisclaimer` / t('priceDisclaimer') remain the single source of
truth, and update any translation entries for the root disclaimer to omit that
sentence.

Comment thread lib/types/prescription.ts
Comment on lines +11 to +16
export interface EgyptianAlternative {
name: string;
manufacturer: string;
estimatedPrice: PriceEstimate;
note: string; // e.g., "Same active ingredient (Amoxicillin)"
}

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 | 🔴 Critical

Type contract is stricter than the runtime data — make estimatedPrice (and note) optional.

The Gemini schema in lib/gemini.ts declares the alternative's estimatedPrice with nullable: true and lists only ["name", "manufacturer", "note"] in required. So at runtime an EgyptianAlternative can legitimately arrive without a price, but TypeScript currently believes estimatedPrice: PriceEstimate is always present.

This silently masks unsafe accesses downstream, all of which will throw a TypeError: Cannot read properties of undefined (reading 'price' / 'currency') if Gemini omits the field:

  • components/results/MedicationPricing.tsx line 34 (alt.estimatedPrice.price inside maxSavings reducer), line 132 (Math.round(estimatedPrice.price - alt.estimatedPrice.price)), and line 173 (alt.estimatedPrice.currency } { alt.estimatedPrice.price).
  • components/MedicalReport.tsx line 715 (alt.estimatedPrice.currency } { alt.estimatedPrice.price) — also rendered into the PDF.

Similarly, MedicationPricing.tsx line 178 already does if (alt.note), suggesting note is treated as optional in practice; aligning the type avoids the false sense of safety.

Proposed type fix (then add the missing guards at the call sites)
 export interface EgyptianAlternative {
   name: string;
   manufacturer: string;
-  estimatedPrice: PriceEstimate;
-  note: string; // e.g., "Same active ingredient (Amoxicillin)"
+  estimatedPrice?: PriceEstimate;
+  note?: string; // e.g., "Same active ingredient (Amoxicillin)"
 }

You'll also want to either:

  • Filter egyptianAlternatives for alt.estimatedPrice before computing maxSavings/savings, or
  • Guard each render site (e.g., only show the price badge in MedicationPricing.tsx and MedicalReport.tsx when alt.estimatedPrice && alt.estimatedPrice.price > 0).

Please confirm whether the Gemini schema's nullable: true for the alternative's estimatedPrice is intentional — if alternatives are always expected to carry a price, the schema (not the type) should be tightened by adding estimatedPrice to required and removing nullable: true.

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

In `@lib/types/prescription.ts` around lines 11 - 16, The TypeScript interface
EgyptianAlternative is too strict: change estimatedPrice: PriceEstimate to
estimatedPrice?: PriceEstimate and note: string to note?: string in
lib/types/prescription.ts to match the Gemini schema which allows
nullable/absent prices; then update callers (e.g.,
components/results/MedicationPricing.tsx and components/MedicalReport.tsx) to
either filter egyptianAlternatives for alt.estimatedPrice before computing
maxSavings/savings or add guards where used (e.g., check alt.estimatedPrice &&
alt.estimatedPrice.price and only render price/currency when present) so no code
assumes estimatedPrice is always defined.

Comment thread messages/en.json
Comment on lines +67 to +79
"sharingText": "Check out my decoded prescription from Roshetta-AI:\n\n",
"estimatedPrice": "Estimated Price",
"priceRange": "EGP {price}",
"egyptianAlternatives": "Egyptian Alternatives",
"alternativesHint": "Locally manufactured options with the same active ingredient",
"manufacturerLabel": "by {manufacturer}",
"savingsLabel": "Save up to EGP {amount}",
"noAlternatives": "No local alternatives identified",
"priceDisclaimer": "Prices are AI-estimated and may vary between pharmacies",
"showPricing": "Pricing & Alternatives",
"hidePricing": "Hide Pricing",
"searchAlternative": "Search",
"localProduct": "Local Product"

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 | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find any references to the suspected-unused keys across the codebase.
for key in priceRange noAlternatives showPricing hidePricing localProduct; do
  echo "=== $key ==="
  rg -nP --type=ts --type=tsx --type=js --type=jsx "['\"]${key}['\"]|\.${key}\b|t\(\s*['\"]\w+\.${key}['\"]\s*\)|t\(\s*['\"]${key}['\"]\s*\)" || true
done

Repository: KMS74/roshetta-ai

Length of output: 322


🏁 Script executed:

#!/bin/bash
# Search all files for references to the suspected-unused keys (no file type filter)
for key in priceRange noAlternatives showPricing hidePricing localProduct; do
  echo "=== Searching for: $key ==="
  rg -n "\"${key}\"|'${key}'" --max-count=5 || echo "No matches found"
done

Repository: KMS74/roshetta-ai

Length of output: 836


🏁 Script executed:

# Also find and inspect the MedicationPricing.tsx file
fd -name "MedicationPricing.tsx" -x cat -n {}

Repository: KMS74/roshetta-ai

Length of output: 292


🏁 Script executed:

# Check for any translation/i18n usage patterns with these keys
rg -n "showPricing|hidePricing|noAlternatives|priceRange|localProduct" --context 3

Repository: KMS74/roshetta-ai

Length of output: 2209


🏁 Script executed:

# Search for these keys being used in component files (tsx/ts)
rg -n "priceRange|noAlternatives|showPricing|hidePricing|localProduct" --type-list | head -20

Repository: KMS74/roshetta-ai

Length of output: 451


🏁 Script executed:

# List all tsx/ts files to understand file types
fd -e tsx -e ts --max-depth 5 | head -20

Repository: KMS74/roshetta-ai

Length of output: 597


🏁 Script executed:

# Search in all files for these keys in component/import context
rg "showPricing|hidePricing|priceRange|noAlternatives|localProduct" -t ts -t js 2>/dev/null || rg "showPricing|hidePricing|priceRange|noAlternatives|localProduct"

Repository: KMS74/roshetta-ai

Length of output: 628


🏁 Script executed:

# Find and read MedicationPricing component
fd "MedicationPricing" -x head -100 {}

Repository: KMS74/roshetta-ai

Length of output: 4815


🏁 Script executed:

fd "MedicationPricing" -x wc -l {}

Repository: KMS74/roshetta-ai

Length of output: 108


🏁 Script executed:

fd "MedicationPricing" -x cat {}

Repository: KMS74/roshetta-ai

Length of output: 11876


🏁 Script executed:

# Search for these keys being referenced in component code (not just defined in json)
rg "t\(['\"]" components/ app/ --type ts --type js 2>/dev/null | grep -E "(priceRange|noAlternatives|showPricing|hidePricing|localProduct)" || echo "No usage found in component code"

Repository: KMS74/roshetta-ai

Length of output: 94


🏁 Script executed:

# Also search for direct key references (object notation style)
rg "Insights\.(priceRange|noAlternatives|showPricing|hidePricing|localProduct)" --max-count=10

Repository: KMS74/roshetta-ai

Length of output: 43


Five Insights keys are unused and should be wired up or removed.

Verification confirms these translation keys are defined in both messages/en.json and messages/ar.json but have no usage in the codebase:

  • priceRange — the component renders price inline via {estimatedPrice.currency} {estimatedPrice.price} instead of using this template
  • noAlternatives — no empty state UI renders when alternatives are unavailable
  • showPricing, hidePricing — the toggle button exists but lacks text labels from these keys
  • localProduct — not referenced in the alternatives rendering

Either wire them up (e.g., use priceRange for consistent formatting, add noAlternatives as an empty state, drive toggle labels from showPricing/hidePricing) or remove them to prevent translation drift between locales.

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

In `@messages/en.json` around lines 67 - 79, The mentioned translation keys
(priceRange, noAlternatives, showPricing, hidePricing, localProduct) are defined
but unused; either wire them into the UI or remove them. Fix by: (1) replace the
inline price rendering with the i18n template key priceRange (pass a single
formatted price string or numeric price as the {price} param where the current
estimatedPrice rendering occurs), (2) add an empty-state render that uses
noAlternatives when the alternatives list is empty inside the alternatives
component, (3) make the pricing toggle button text come from showPricing and
hidePricing based on the toggle state instead of hardcoded text, and (4) use
localProduct as the label/aria-label when rendering each locally manufactured
alternative in the alternatives list; if you prefer not to surface these
strings, remove them from messages/en.json and messages/ar.json to avoid
translation drift.

Comment thread next-env.d.ts
/// <reference types="next" />
/// <reference types="next/image-types/global" />
import "./.next/dev/types/routes.d.ts";
import "./.next/types/routes.d.ts";

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 | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Check Next.js version and recent changes to package files

echo "=== Current Next.js version ==="
cat package.json | jq '.dependencies.next // .devDependencies.next'

echo -e "\n=== Recent changes to package.json ==="
git log --oneline --follow -10 package.json

echo -e "\n=== Check if next-env.d.ts is in .gitignore ==="
if [ -f .gitignore ]; then
  rg -n "next-env" .gitignore || echo "next-env.d.ts is NOT ignored"
fi

Repository: KMS74/roshetta-ai

Length of output: 296


🏁 Script executed:

#!/bin/bash
# Check git history of next-env.d.ts
echo "=== Recent git log for next-env.d.ts ==="
git log --oneline -15 next-env.d.ts

echo -e "\n=== Latest commit details for next-env.d.ts ==="
git log -1 --format="%H %s" next-env.d.ts
LATEST_COMMIT=$(git log -1 --format="%H" next-env.d.ts)

echo -e "\n=== Check if next-env.d.ts change is in current PR (compare HEAD with main/master) ==="
git diff HEAD next-env.d.ts || echo "No diff found"

echo -e "\n=== Show the actual change in the latest commit ==="
git show $LATEST_COMMIT:next-env.d.ts | head -10

echo -e "\n=== Check .gitignore history ==="
git log --oneline -5 .gitignore

Repository: KMS74/roshetta-ai

Length of output: 834


🏁 Script executed:

#!/bin/bash
# Get detailed view of the change to next-env.d.ts in commit 8569471
echo "=== Full diff of next-env.d.ts in commit 8569471 ==="
git show 8569471 -- next-env.d.ts

echo -e "\n=== Check parent commit state ==="
PARENT_COMMIT=$(git rev-parse 8569471^)
echo "Parent commit: $PARENT_COMMIT"
git show $PARENT_COMMIT:next-env.d.ts 2>/dev/null || echo "File did not exist in parent commit"

echo -e "\n=== Check .gitignore changes in this commit ==="
git show 8569471 -- .gitignore | head -30

Repository: KMS74/roshetta-ai

Length of output: 2764


Remove this auto-generated file from version control and add to .gitignore.

This is a Next.js auto-generated file that should never be committed to the repository. The file itself explicitly states "This file should not be edited" and is generated automatically by Next.js during builds. Committing it to version control will cause conflicts during Next.js version upgrades and violates Next.js best practices.

Required changes:

  1. Add next-env.d.ts to .gitignore
  2. Remove the file from git history: git rm --cached next-env.d.ts

Additionally, this change is unrelated to the medication pricing feature described in the PR and should not be included in this commit.

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

In `@next-env.d.ts` at line 3, The committed auto-generated Next.js type file
next-env.d.ts (containing the line `import "./.next/types/routes.d.ts";`) should
be removed from version control and ignored: add next-env.d.ts to .gitignore and
run git rm --cached next-env.d.ts to stop tracking it, then amend the commit so
this change is not part of the medication-pricing feature PR; ensure no code
edits are made to next-env.d.ts itself since it is auto-generated.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant