Harmonia is an instructor-facing web application that analyzes student team collaboration in programming courses. It clones Git repositories, processes commit history, and computes a Collaboration Quality Index (CQI) score (0–100) for each team. AI-powered commit analysis (effort rating, classification, anomaly detection) provides deeper insights.
- Integration: Artemis LMS (fetches teams, exercises, participation data)
- Target users: Instructors, teaching assistants
- Analysis time: ~15 minutes for ~200 teams
| Layer | Technologies |
|---|---|
| Server | Java 25, Spring Boot 4.0.1, Spring Data JPA, Spring Security, Spring Batch, Spring AI 2.0, JGit 7.5, PostgreSQL 18, Liquibase, Lombok |
| Client | React 19, TypeScript 5.9, Vite 7, Tailwind CSS 4, Radix UI / Shadcn, TanStack Query 5, React Router DOM 7, Axios |
| Build | Gradle 9.3.1, Node 22, OpenAPI Generator (typescript-axios) |
| CI/CD | GitHub Actions |
Monolithic Spring Boot application with modular packages under de.tum.cit.aet:
src/main/java/de/tum/cit/aet/
├── core/ # Config, security, exceptions, shared DTOs
├── usermanagement/ # User entity, auth, controllers
├── repositoryProcessing/ # Artemis integration, Git clone/fetch, team data
├── analysis/ # CQI calculation, analysis orchestration
│ └── service/cqi/ # CQI calculator, commit pre-filter, config
├── ai/ # Spring AI services (effort rating, classification, anomaly detection)
├── dataProcessing/ # Batch processing, request orchestration
└── util/ # Shared utilities
Layered architecture:
Web (*Resource controllers) → Service → Repository → Domain (JPA entities)
src/main/webapp/src/
├── app/generated/ # OpenAPI-generated API client (DO NOT EDIT)
│ ├── apis/ # API service classes
│ └── models/ # TypeScript interfaces
├── components/ # React components
│ └── ui/ # Shadcn/UI primitives (button, card, badge, etc.)
├── pages/ # Page-level components
├── hooks/ # Custom React hooks
├── data/ # Data loaders, config
├── lib/ # Utilities (cn(), devMode, etc.)
└── types/ # TypeScript type definitions
Data flow:
Pages → Components → Custom Hooks → Generated API Client → Axios → Server
Vite dev server proxies /api to the server on port 8080.
CQI = BASE_SCORE × PENALTY_MULTIPLIER
BASE_SCORE = 0.55 × EffortBalance + 0.25 × LoCBalance + 0.15 × OwnershipSpread + 0.05 × TemporalSpread
| Component | Weight | Measures |
|---|---|---|
| Effort Balance | 55% | LLM-weighted effort distribution (Gini coefficient) |
| LoC Balance | 25% | Lines of code distribution |
| Ownership Spread | 15% | Multiple authors per file |
| Temporal Spread | 5% | Work spread over project duration |
Weights are configurable in application.yml under harmonia.cqi.weights.
Raw Commits → [Pre-Filter] → Productive Commits → [LLM Analysis] → [CQI Calculator] → Score
Filters out: empty, merge, revert, rename-only, format-only, mass-reformat, generated files, trivial message patterns.
Flags for instructor review (does NOT affect CQI): LATE_DUMP, SOLO_DEVELOPMENT, INACTIVE_PERIOD, UNEVEN_DISTRIBUTION.
See docs/CQI_Score_Formula_Design.md for full details.
TeamParticipation— team + exercise + CQI score + analysis statusTeamRepository— cloned Git repoStudent/Tutor— team membersAnalysisStatus— progress tracking (IDLE → RUNNING → COMPLETED/FAILED/CANCELLED)AnalyzedChunk— AI-analyzed commit chunk with effort rating and classificationVCSLog— individual commit entry
Formatting: Spotless (auto-fix: ./gradlew spotlessApply, check: ./gradlew spotlessCheck)
Linting: Checkstyle
- JavaDoc required on all public methods ≥4 lines (with
@return,@paramtags) - Exceptions:
@Override,@Test,@BeforeEach,@AfterEach - Braces required for all
if/else/for/while/doblocks - Modifier order enforced
Conventions:
- Lombok:
@Getter,@Setter,@RequiredArgsConstructor,@Slf4j - Constructor injection only (never
@Autowiredon fields) recordtypes for immutable DTOs- UUIDs as primary keys (
@GeneratedValue(strategy = GenerationType.UUID)) - Controllers named
*Resource(e.g.,AnalysisResource) - Services named
*Service, Repositories named*Repository
Formatting: Prettier
- Print width: 140, single quotes, 2-space indent, avoid arrow parens, end-of-line auto
Linting: ESLint
- TypeScript ESLint recommended rules
- React hooks rules
- Unused vars = error (except
_-prefixed) - Generated code (
**/generated/**) is excluded
Conventions:
- Arrow functions for components:
const MyComponent = () => { ... } - Props via interface:
interface MyComponentProps { ... } - Path alias:
@/maps tosrc/(e.g.,import { Button } from '@/components/ui/button') - Class composition:
cn()utility (clsx + tailwind-merge) - Variants:
class-variance-authority(CVA) - Server state: TanStack Query (React Query)
- Icons: Lucide React
- Toasts: Sonner
When server endpoints or DTOs change:
# 1. Generate OpenAPI spec from Spring controllers
./gradlew generateApiDocs -x webapp
# 2. Generate TypeScript client from spec
./gradlew openApiGenerate- Generated files:
src/main/webapp/src/app/generated/ - CRITICAL: Never manually edit generated files — they get overwritten
- Generated files are committed to version control
- OpenAPI spec:
openapi/openapi.yaml
import { AnalysisResourceApi } from '@/app/generated';
import type { AnalysisStatusDTO } from '@/app/generated';./gradlew bootRun # Run Spring Boot server (port 8080)
./gradlew test # Run unit tests (excludes integration/e2e)
./gradlew test --tests ClassName # Run specific test class
./gradlew integrationTest # Run integration tests (requires external services)
./gradlew spotlessApply # Auto-format Java code
./gradlew spotlessCheck # Check Java formatting
./gradlew checkstyleMain # Check JavaDoc and code style
./gradlew checkstyleTest # Check test code stylecd src/main/webapp
npm run dev # Start Vite dev server (port 5173)
npm run build # Production build
npm run lint # Run ESLint
npm run prettier:check # Check Prettier formatting
npm run prettier:format # Auto-format TypeScript
npm run compile:ts # TypeScript type-check (npx tsc --noEmit)./gradlew generateApiDocs -x webapp # Generate OpenAPI spec
./gradlew openApiGenerate # Generate TypeScript client`Category`: Description starting with capital letter
Valid categories: Usability, Performance, Development, General
Examples:
`Development`: Add commit pre-filter pipeline`General`: Fix analysis status race condition`Performance`: Optimize database queries for large teams`Usability`: Add loading spinner for better user feedback
Common mistakes:
docs: update readme— wrong format, missing backticks and capital letterDevelopment: fix bug— missing backticks around category, description must start with capital letter
| Job | What it checks | Must pass |
|---|---|---|
validate-pr-title |
PR title format (see above) | Yes |
server-style |
spotlessCheck + checkstyleMain |
Yes |
client-style |
prettier:check + lint |
Yes |
client-compilation |
tsc --noEmit |
Yes |
tests |
./gradlew test |
Yes |
All checks must pass before merging.
# Server changes
./gradlew spotlessApply
./gradlew checkstyleMain checkstyleTest
# Client changes
cd src/main/webapp
npm run prettier:format
npm run lint
npm run compile:ts- Create DTO(s) in the module's
dto/package - Add business logic in
*Service - Add REST endpoint in
*Resourcecontroller - Add JavaDoc to all public methods
- Run
./gradlew spotlessApply && ./gradlew checkstyleMain - Regenerate OpenAPI:
./gradlew generateApiDocs -x webapp && ./gradlew openApiGenerate - Commit generated files alongside your changes
- Create
MyComponent.tsxinsrc/main/webapp/src/components/ - Use Shadcn/UI primitives from
components/ui/ - Import with
@/alias:import { Card } from '@/components/ui/card' - Use
cn()for conditional Tailwind classes - For API data: use TanStack Query hooks
- Run
npm run lint && npm run compile:ts
- Edit
src/main/resources/config/application.ymlunderharmonia.cqi.weights - Weights must sum to 1.0
- Run tests:
./gradlew test
- Create Liquibase changelog in
src/main/resources/db/changelog/ - Reference it in
db.changelog-master.xml - Use
validateDDL strategy — schema changes must go through Liquibase
- Missing or incomplete JavaDoc comments (missing
@return,@paramtags) - Single-line
if/else/for/whilewithout braces - Wrong modifier order
- Unused imports or variables
- Inconsistent formatting (run
npm run prettier:formatto auto-fix) - Type errors from stale generated code (regenerate with
./gradlew openApiGenerate)
- Use constructor injection with
@RequiredArgsConstructor - Use Lombok annotations (
@Getter,@Setter,@Slf4j) - Use
recordtypes for immutable DTOs - Name controllers
*Resource, services*Service, repos*Repository - Add JavaDoc for public methods ≥4 lines
- Use UUIDs for primary keys
- Use
cn()for conditional Tailwind classes - Use TanStack Query for API calls in React
- Use
@/path aliases for imports - Run style checks before committing
- Commit generated OpenAPI files
- Use braces for all control flow blocks
- Never manually edit files in
src/main/webapp/src/app/generated/ - Never use field injection (
@Autowiredon fields) - Never skip JavaDoc on public methods ≥4 lines
- Never commit without running style checks
- Never hardcode config values — use
application.yml - Never use single-line
if/elsewithout braces - Never modify generated TypeScript client manually
- Never blindly fix style violations — understand why the rule exists before fixing