Skip to content

Sprint 2 jack#55

Merged
lukepring merged 38 commits intomainfrom
Sprint-2-Jack
Mar 20, 2026
Merged

Sprint 2 jack#55
lukepring merged 38 commits intomainfrom
Sprint-2-Jack

Conversation

@jackoturner
Copy link
Copy Markdown
Collaborator

No description provided.

Implemented system interfaces and external data integration for share price retrieval using Clean Architecture principles. Added Alpha Vantage support with API key configuration via environment variables and introduced an output size setting to stay within free tier limits. Implemented basic rate limiting of one request per second to prevent throttling when comparing multiple symbols. Improved error handling to detect API specific responses such as Information, Note, and Error Message despite HTTP 200 status codes, with appropriate logging. Fixed parsing logic to reliably extract closing prices from the Time Series Daily data. Ensured data is converted into domain models and cached locally to support offline access and improve system reliability.
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR appears to introduce a new Alpha Vantage–backed market data path (with in-memory persistence) and updates the Thymeleaf UI flow to compare symbols via a GET request, alongside some project configuration/documentation tweaks.

Changes:

  • Add Alpha Vantage service + repository abstractions and wire them into a new Spring Boot entrypoint.
  • Update the web UI controller and template to fetch/plot comparison data via GET /compare.
  • Add runtime configuration/docs, but also (unintentionally) commit multiple .DS_Store files.

Reviewed changes

Copilot reviewed 11 out of 17 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
stockscope/src/main/resources/templates/index.html Switch compare form to GET and tweak chart/table markup + empty-state messaging
stockscope/src/main/resources/application.properties Add Alpha Vantage key/outputsize config and dev-oriented Spring settings
stockscope/src/main/java/com/sharecomparison/StockComparisonApplication.java Add new Spring Boot entrypoint and explicit beans for repository + market data service
stockscope/src/main/java/com/sharecomparison/presentation/WebPageController.java Replace prior compare flow with direct MarketDataService fetch + chart building in controller
stockscope/src/main/java/com/sharecomparison/infrastructure/InMemoryPriceRepository.java Add simple in-memory store for fetched prices
stockscope/src/main/java/com/sharecomparison/infrastructure/AlphaVantageService.java Implement Alpha Vantage HTTP client, parsing, and rate limiting
stockscope/src/main/java/com/sharecomparison/application/PriceRepository.java Introduce repository interface for storing/loading prices
stockscope/src/main/java/com/sharecomparison/application/MarketDataService.java Introduce service interface for fetching prices
stockscope/src/main/java/com/.DS_Store macOS metadata file added (should not be committed)
stockscope/src/main/java/.DS_Store macOS metadata file added (should not be committed)
stockscope/src/main/.DS_Store macOS metadata file added (should not be committed)
stockscope/src/.DS_Store macOS metadata file added (should not be committed)
stockscope/pom.xml Configure Spring Boot Maven plugin mainClass (and set explicit plugin version)
stockscope/.DS_Store macOS metadata file added (should not be committed)
README.md Update run instructions and add API key setup notes
Meeting-Documentation/Meeting-2.md Adjust task ownership line
.DS_Store macOS metadata file added (should not be committed)

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +45 to +46
LocalDate start = (startDate != null && !startDate.isBlank()) ? LocalDate.parse(startDate) : LocalDate.now().minusYears(1);
LocalDate end = (endDate != null && !endDate.isBlank()) ? LocalDate.parse(endDate) : LocalDate.now();
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

LocalDate.parse(startDate/endDate) will throw DateTimeParseException for malformed query params; because these are plain Strings, a crafted URL can crash the request with a 500. Consider using @RequestParam @DateTimeFormat LocalDate (as before) or catching parsing errors and returning a user-facing validation message.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +45 to +55
LocalDate start = (startDate != null && !startDate.isBlank()) ? LocalDate.parse(startDate) : LocalDate.now().minusYears(1);
LocalDate end = (endDate != null && !endDate.isBlank()) ? LocalDate.parse(endDate) : LocalDate.now();

LocalDate effectiveEnd = endDate == null ? LocalDate.now() : endDate;
LocalDate effectiveStart = startDate == null ? effectiveEnd.minusMonths(6) : startDate;
symbol1 = symbol1.trim().toUpperCase();
symbol2 = symbol2.trim().toUpperCase();

model.addAttribute("symbol1", s1);
model.addAttribute("symbol2", s2);
model.addAttribute("startDate", effectiveStart);
model.addAttribute("endDate", effectiveEnd);
List<PriceData> data1 = marketDataService.fetchSharePrices(symbol1, start, end);
List<PriceData> data2 = marketDataService.fetchSharePrices(symbol2, start, end);

if (s1.isEmpty() || s2.isEmpty()) {
model.addAttribute("error", "Both symbols are required.");
if (data1.isEmpty() || data2.isEmpty()) {
model.addAttribute("error", "No data available for one or both symbols in this range.");
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

The previous startDate <= endDate validation was removed. When start is after end, the API call/filtering will likely return empty data and show the misleading "No data available" error instead of prompting the user to fix the date range.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +84 to +104
int n = Math.min(data1.size(), data2.size());
double xStep = n > 1 ? chartWidth / (n - 1) : 0;

StringBuilder path1 = new StringBuilder();
StringBuilder path2 = new StringBuilder();
List<Map<String, Object>> points1 = new ArrayList<>();
List<Map<String, Object>> points2 = new ArrayList<>();

for (int i = 0; i < n; i++) {
double x = left + i * xStep;

PriceData p1 = data1.get(i);
double y1 = bottom - ((p1.getClosingPrice() - minPrice) / priceRange) * chartHeight;
path1.append(i == 0 ? "M" : "L").append(x).append(",").append(y1).append(" ");
points1.add(Map.of("x", x, "y", y1, "value", p1.getClosingPrice()));

PriceData p2 = data2.get(i);
double y2 = bottom - ((p2.getClosingPrice() - minPrice) / priceRange) * chartHeight;
path2.append(i == 0 ? "M" : "L").append(x).append(",").append(y2).append(" ");
points2.add(Map.of("x", x, "y", y2, "value", p2.getClosingPrice()));
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

Chart building uses n = min(data1.size(), data2.size()) and then pairs points by index, which can compare different dates if one symbol has missing trading days/data points. Align series by date (intersection/union) before plotting so each x-coordinate represents the same date for both symbols.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment thread stockscope/src/main/java/com/sharecomparison/presentation/WebPageController.java Outdated
Comment on lines +66 to +124
// Chart coords calculation
double chartWidth = 800, chartHeight = 300;
double left = 80, right = left + chartWidth;
double bottom = 320, top = bottom - chartHeight;
double midY = top + chartHeight / 2;

double minPrice = Double.MAX_VALUE, maxPrice = Double.MIN_VALUE;
for (PriceData p : data1) {
minPrice = Math.min(minPrice, p.getClosingPrice());
maxPrice = Math.max(maxPrice, p.getClosingPrice());
}
for (PriceData p : data2) {
minPrice = Math.min(minPrice, p.getClosingPrice());
maxPrice = Math.max(maxPrice, p.getClosingPrice());
}
if (maxPrice == minPrice) maxPrice += 1.0;
double priceRange = maxPrice - minPrice;

int n = Math.min(data1.size(), data2.size());
double xStep = n > 1 ? chartWidth / (n - 1) : 0;

StringBuilder path1 = new StringBuilder();
StringBuilder path2 = new StringBuilder();
List<Map<String, Object>> points1 = new ArrayList<>();
List<Map<String, Object>> points2 = new ArrayList<>();

for (int i = 0; i < n; i++) {
double x = left + i * xStep;

PriceData p1 = data1.get(i);
double y1 = bottom - ((p1.getClosingPrice() - minPrice) / priceRange) * chartHeight;
path1.append(i == 0 ? "M" : "L").append(x).append(",").append(y1).append(" ");
points1.add(Map.of("x", x, "y", y1, "value", p1.getClosingPrice()));

PriceData p2 = data2.get(i);
double y2 = bottom - ((p2.getClosingPrice() - minPrice) / priceRange) * chartHeight;
path2.append(i == 0 ? "M" : "L").append(x).append(",").append(y2).append(" ");
points2.add(Map.of("x", x, "y", y2, "value", p2.getClosingPrice()));
}

Map<String, Object> chartData = new HashMap<>();
chartData.put("left", left);
chartData.put("right", right);
chartData.put("top", top);
chartData.put("bottom", bottom);
chartData.put("midY", midY);
chartData.put("symbol1Path", path1.toString());
chartData.put("symbol2Path", path2.toString());
chartData.put("symbol1Points", points1);
chartData.put("symbol2Points", points2);

Map<String, Object> result = new HashMap<>();
result.put("symbol1", symbol1);
result.put("symbol2", symbol2);
result.put("startDate", start);
result.put("endDate", end);
result.put("chartData", chartData);
result.put("symbol1Data", data1);
result.put("symbol2Data", data2);
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

This controller reimplements chart/coordinate logic and returns a loosely-typed Map for "result", even though the codebase already has ComparisonResult/ChartData and a ChartBuilder service. Reusing those existing domain types/services would reduce duplication and keep web + API behavior consistent.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +12 to +34
private List<PriceData> storedPrices = new ArrayList<>();

@Override
public void savePrices(List<PriceData> prices) {
storedPrices.addAll(prices);
}

@Override
public List<PriceData> loadPrices(
String symbol,
LocalDate startDate,
LocalDate endDate) {

List<PriceData> results = new ArrayList<>();

for (PriceData p : storedPrices) {

if (p.getSymbol().equals(symbol)
&& !p.getDate().isBefore(startDate)
&& !p.getDate().isAfter(endDate)) {

results.add(p);
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

storedPrices is a mutable ArrayList accessed without synchronization; in a web app this repository can be hit concurrently, causing data races / ConcurrentModificationException during iteration. Consider using a thread-safe structure (or synchronizing access) and marking the field final.

Copilot uses AI. Check for mistakes.
Comment on lines +51 to +61
@Override
public List<PriceData> fetchSharePrices(String symbol, LocalDate startDate, LocalDate endDate) {
try {
if (symbol == null || symbol.isBlank()) {
return repository.loadPrices(symbol, startDate, endDate);
}

String json = fetchTimeSeriesDailyJson(symbol, outputSize);
if (json == null) {
return repository.loadPrices(symbol, startDate, endDate);
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

fetchSharePrices() always calls Alpha Vantage first and only uses the repository as a fallback on errors/throttling. This means cached data is never used to avoid external calls, increasing latency and the chance of hitting rate limits. Consider checking the repository for an existing result (or partially cached range) before making the HTTP request, and only call the API for missing data.

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment on lines +13 to +31
@SpringBootApplication
public class StockComparisonApplication {

public static void main(String[] args) {
SpringApplication.run(StockComparisonApplication.class, args);
}

@Bean
public PriceRepository priceRepository() {
return new InMemoryPriceRepository();
}

@Bean
public MarketDataService marketDataService(
PriceRepository repository,
@Value("${alphavantage.key:demo}") String apiKey,
@Value("${alphavantage.outputsize:compact}") String outputSize) {
return new AlphaVantageService(repository, apiKey, outputSize);
}
Copy link

Copilot AI Mar 20, 2026

Choose a reason for hiding this comment

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

The project already has an @SpringBootApplication entrypoint (com.sharecomparison.presentation.WebUI). Adding a second one can confuse IDE run configurations and make it unclear which application class is authoritative. Consider consolidating to a single entrypoint (either remove WebUI or move these @Bean definitions into the existing entrypoint/config).

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

@copilot open a new pull request to apply changes based on this feedback

Comment thread stockscope/src/main/resources/application.properties
- Review Sprint 1 for code review + fix any issues (**Luke + Sameer**)
- Support team with repository processes (**Jack + Azlan**)
- Final check of architecture diagram to ensure it matches code structure (**Azlan**)
- Update Kanban with Sprint 2 tasks from feedback (**Luke + Jack**)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

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

Not cool man

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 20, 2026

@lukepring I've opened a new pull request, #56, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 20, 2026

@lukepring I've opened a new pull request, #57, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 20, 2026

@lukepring I've opened a new pull request, #58, to work on those changes. Once the pull request is ready, I'll request review from you.

lukepring and others added 2 commits March 20, 2026 15:45
…ageController.java

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 20, 2026

@lukepring I've opened a new pull request, #59, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 4 commits March 20, 2026 15:48
Co-authored-by: lukepring <68820847+lukepring@users.noreply.github.com>
…MemoryPriceRepository.java

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: lukepring <68820847+lukepring@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 20, 2026

@lukepring I've opened a new pull request, #60, to work on those changes. Once the pull request is ready, I'll request review from you.

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 20, 2026

@lukepring I've opened a new pull request, #61, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 16 commits March 20, 2026 15:50
Co-authored-by: lukepring <68820847+lukepring@users.noreply.github.com>
Co-authored-by: lukepring <68820847+lukepring@users.noreply.github.com>
Co-authored-by: lukepring <68820847+lukepring@users.noreply.github.com>
Co-authored-by: lukepring <68820847+lukepring@users.noreply.github.com>
Fix chart date alignment: use date intersection instead of index-based pairing
Cache-first strategy in AlphaVantageService to avoid redundant API calls
Co-authored-by: lukepring <68820847+lukepring@users.noreply.github.com>
Refactor WebPageController to use IPriceController and ComparisonResult
Co-authored-by: lukepring <68820847+lukepring@users.noreply.github.com>
Restore compare flow and add startDate ≤ endDate validation
Fix unhandled DateTimeParseException on malformed date query params
@lukepring
Copy link
Copy Markdown
Owner

@copilot resolve merge conflicts pls

Copy link
Copy Markdown
Contributor

Copilot AI commented Mar 20, 2026

@lukepring I've opened a new pull request, #62, to work on those changes. Once the pull request is ready, I'll request review from you.

Copilot AI and others added 3 commits March 20, 2026 16:32
Co-authored-by: lukepring <68820847+lukepring@users.noreply.github.com>
[WIP] Update market data path and Thymeleaf UI flow
@lukepring lukepring merged commit c31bc8f into main Mar 20, 2026
@lukepring lukepring deleted the Sprint-2-Jack branch March 20, 2026 16:41
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.

4 participants