2 Commits

Author SHA1 Message Date
TeamCity ea8048e064 ci: bump version to v0.4.3 2026-06-21 09:06:47 +00:00
Janis Eccarius ce1fb0d60b fix(analysis): fix API field mismatch and enable full game analysis
Map raw backend response (evaluation/continuationMoves) to frontend
model (eval/winChance/continuations). Add getFenHistory() call after
loading a game or PGN so runAnalysis() gets per-ply FEN history and
triggers analyzeGame() instead of falling back to single-position
analysis. Remove !hasAnnotations guard so positionAnalysis card shows
even when a game is loaded.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-21 11:00:29 +02:00
6 changed files with 52 additions and 10 deletions
+5
View File
@@ -76,3 +76,8 @@
### Bug Fixes
* NCS-122 send WS token via first-message auth instead of query param ([#13](https://git.janis-eccarius.de/NowChess/NowChess-Frontend/issues/13)) ([1d2c217](https://git.janis-eccarius.de/NowChess/NowChess-Frontend/commit/1d2c217da8982d361e2eb7de26f6447171a1dd43))
## [0.0.0](https://git.janis-eccarius.de/NowChess/NowChess-Frontend/compare/0.4.2...0.0.0) (2026-06-21)
### Bug Fixes
* **analysis:** fix API field mismatch and enable full game analysis ([ce1fb0d](https://git.janis-eccarius.de/NowChess/NowChess-Frontend/commit/ce1fb0d60b695093495ee0ad824c511dd2db7fbb))
+10
View File
@@ -3,11 +3,21 @@ export interface AnalysisRequest {
depth: number;
}
export interface RawAnalysisResponse {
fen: string;
evaluation: number;
depth: number;
bestMove: string;
mate: number | null;
continuationMoves: string[];
}
export interface AnalysisResponse {
eval: number;
winChance: number;
depth: number;
bestMove: string;
mate: number | null;
continuations: string[];
}
@@ -246,7 +246,7 @@
<!-- Side column -->
<aside class="side">
<!-- Single position analysis result -->
@if (positionAnalysis && !hasAnnotations) {
@if (positionAnalysis) {
<details class="side-card" open>
<summary class="side-card-summary">
<span class="side-card-title">Position Analysis</span>
+12 -6
View File
@@ -2,7 +2,7 @@ import { Component, DestroyRef, inject, OnInit } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { ActivatedRoute, RouterLink } from '@angular/router';
import { FormsModule } from '@angular/forms';
import { switchMap, of } from 'rxjs';
import { switchMap, of, forkJoin } from 'rxjs';
import { ChessBoardComponent } from '../../components/chess-board/chess-board.component';
import { EvalTimelineComponent } from '../../components/eval-timeline/eval-timeline.component';
@@ -146,6 +146,7 @@ export class AnalysisComponent implements OnInit {
return;
}
this.analysing = true;
this.positionAnalysis = null;
this.errorMessage = '';
const sans =
this.annotatedMoves.length > 0
@@ -207,12 +208,7 @@ export class AnalysisComponent implements OnInit {
private applyGame(game: GameFull): void {
this.game = game;
this.currentFen = game.state.fen;
// Build a flat FEN history from scratch using moves array
// The server gives us the final FEN. We reconstruct history by
// storing the final FEN; full per-ply history requires per-move API calls
// which is out of scope here — we store what we have and allow analysis to proceed.
this.fenHistory = [game.state.fen];
// Seed annotated moves with san strings, no quality yet
this.annotatedMoves = game.state.moves.map((san) => ({
san,
fen: game.state.fen,
@@ -223,6 +219,16 @@ export class AnalysisComponent implements OnInit {
winChanceBefore: null,
winChanceAfter: null,
}));
this.gameApi
.getFenHistory(game.gameId)
.pipe(takeUntilDestroyed(this.destroyRef))
.subscribe({
next: (fens) => {
if (fens.length >= 2) {
this.fenHistory = fens;
}
},
});
}
private analyseSinglePosition(fen: string): void {
+23 -2
View File
@@ -1,6 +1,7 @@
import { Injectable, inject } from '@angular/core';
import { HttpClient, HttpParams } from '@angular/common/http';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { environment } from '../../environments/environment';
import {
GameFull,
@@ -9,7 +10,7 @@ import {
LegalMovesResponse,
PlayerInfo,
} from '../models/game.models';
import { AnalysisRequest, AnalysisResponse } from '../models/analysis.models';
import { AnalysisRequest, AnalysisResponse, RawAnalysisResponse } from '../models/analysis.models';
import { StreamHandlerService } from './stream-handler.service';
@Injectable({ providedIn: 'root' })
@@ -78,8 +79,28 @@ export class GameApiService {
return this.http.post<void>(`${this.apiBase}${this.apiPath}/${gameId}/draw/offer`, {});
}
getFenHistory(gameId: string): Observable<string[]> {
return this.http
.get<{ fens: string[] }>(`${this.apiBase}${this.apiPath}/${gameId}/fen-history`)
.pipe(map((r) => r.fens));
}
analyzePosition(request: AnalysisRequest): Observable<AnalysisResponse> {
return this.http.post<AnalysisResponse>(`${this.apiBase}/api/analysis/position`, request);
return this.http
.post<RawAnalysisResponse>(`${this.apiBase}/api/analysis/position`, request)
.pipe(map((raw) => this.mapAnalysisResponse(raw)));
}
private mapAnalysisResponse(raw: RawAnalysisResponse): AnalysisResponse {
const evalPawns = raw.evaluation / 100;
return {
eval: evalPawns,
winChance: 1 / (1 + Math.exp(-0.374 * evalPawns)),
depth: raw.depth,
bestMove: raw.bestMove,
mate: raw.mate,
continuations: raw.continuationMoves ?? [],
};
}
private resolveWsBase(): string {
+1 -1
View File
@@ -1,3 +1,3 @@
MAJOR=0
MINOR=4
PATCH=2
PATCH=3