This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
TransitClock (formerly TheTransitClock, formerly Transitime) is a Java real-time transit prediction and monitoring system. It ingests AVL (Automatic Vehicle Location) data, matches vehicles to GTFS schedules/routes, and generates arrival/departure predictions. Java 21, Maven multi-module, Hibernate 5.5 over PostgreSQL (MySQL and HSQLDB also wired up).
Built from the repository root as a Maven multi-module project.
- Build everything without tests:
mvn install -DskipTests - Run all unit tests across the reactor:
mvn verify(notmvn test— see below) - Run a single module's tests:
mvn -pl transitclock test - Run a single test class:
mvn -pl transitclock test -Dtest=TestAPIKeyManager - Integration tests live in the
transitclockIntegrationmodule and are excluded by default via theskip-integration-testsprofile. Enable with:mvn install -P include-integration-tests - Pipeline tests live in the
transitclockPipelineTestsmodule and are excluded by default — opt-in via theinclude-pipeline-testsprofile. They boot a real Core against an in-memory HSQL database populated with a small WMATA GTFS fixture and exercise matcher/generator behavior end-to-end (lighter than the full AVL-trace runs intransitclockIntegration). Enable with:mvn -pl transitclockPipelineTests -am -P include-pipeline-tests test. CI runs this as a separate step aftermvn verify. - Run everything (unit + pipeline + integration) in one invocation:
mvn install -P run-all-tests. Therun-all-testsprofile activates both extra modules so the reactor picks them up alongside the default build. - Shaded executable JARs are emitted into
transitclock/target/(e.g.Core.jar,GtfsFileProcessor.jar,SchemaGenerator.jar,CreateWebAgency.jar,CreateAPIKey.jar,RmiQuery.jar,UpdateTravelTimes.jar,ScheduleGenerator.jar) — each is a maven-shade execution intransitclock/pom.xml.
There is no lint step configured in the build.
The webapp module has a frontend toolchain (Vite + Vitest) wired into Maven via frontend-maven-plugin. mvn package on transitclockWebapp automatically downloads a pinned Node, runs npm ci, and runs vite build — its output (target/frontend-dist/) is folded into the WAR at /dist. mvn verify also runs Vitest. -DskipTests skips Vitest (the Vite build still runs because the WAR depends on its output).
JaCoCo 0.8.12 is wired at the root pom.xml. It inherits into any module that declares <parent>, which today means transitclock, transitclockBarefootClient, and transitclockTraccarClient. transitclockApi and transitclockWebapp don't declare <parent>, so they currently produce no coverage data.
- Per-module HTML reports land at
<module>/target/site/jacoco/index.htmlaftermvn verify. - Aggregate report (merges Core + both thin clients) lands at
coverage-report/target/site/jacoco-aggregate/index.html. Thecoverage-reportmodule's only job is to runjacoco:report-aggregate; it has no sources of its own. - Quickest way to regenerate just the aggregate:
mvn verify -pl coverage-report -am. - No coverage threshold / build-break rule is configured.
jacoco:checkwith a minimum goal would be the place to add one.
Eight Maven modules under the root aggregator pom.xml:
- transitclock — core engine. Artifact id
transitclockCore. Contains domain model, AVL ingestion, matching, prediction generation, Hibernate entities, config, modules, IPC servers, and all executablemainclasses underorg.transitclock.applications. - transitclockApi — JAX-RS REST API WAR. Calls into a running Core process via RMI (see
org.transitclock.ipc); does not talk to the DB directly for live vehicle/prediction data. - transitclockWebapp — user-facing web UI WAR. Consumes the REST API; deployed to the same Tomcat instance as
transitclockApi. Connects to the DB viahibernate.cfg.xmlinsrc/main/resources. - transitclockTraccarClient, transitclockBarefootClient — thin clients for Traccar GPS devices and the Barefoot map-matching server. Depended on by
transitclock. - transitclockIntegration — end-to-end / prediction-accuracy tests; only built under the
include-integration-testsprofile. Fixture-refresh workflow (capture, subset, promote, regenerate baselines) is documented indocs/integration-tests.md; supporting tooling undertools/wmata_capture/. - transitclockPipelineTests — real-Core behavior tests for the prediction pipeline (
AvlProcessor, and eventually the other matcher/generator classes). Boots a real Core against in-memory HSQL with a small WMATA GTFS fixture viaCoreHarness(a JUnit@ClassRule). Only built under theinclude-pipeline-testsprofile. - coverage-report —
packaging=pomaggregator whose sole purpose is producing a JaCoCo aggregate report. No sources; inherits the root jacoco plugin and runsreport-aggregateatverify.
The system is a pipeline of long-running processes, not a single server. Understanding this pipeline is the fastest way to orient in the codebase.
-
Core (
org.transitclock.applications.Core) is the workhorse long-running JVM per agency. It:- Loads GTFS config from the DB into
org.transitclock.gtfs.DbConfig(an in-memory snapshot). - Starts
Modules (subclasses oforg.transitclock.modules.Module) declared in config — each is a thread. AVL feed modules underorg.transitclock.avl/(GTFS-RT, NextBus, JMS, CSV playback, Traccar, Barefoot, etc.) pull or receive vehicle locations and push them intoAvlExecutor→AvlProcessor. AvlProcessorcallsSpatialMatcher+TemporalMatcherto snap a vehicle to aSpatialMatchon a route's stopPath, thenMatchProcessorderivesArrivalDepartures and triggersPredictionGenerator(seecore/predictiongenerator/) to produce predictions.- Exposes data over RMI via servers in
org.transitclock.ipc.servers(PredictionsServer,VehiclesServer,ConfigServer,CommandsServer,CacheQueryServer, etc.). RMI interfaces are underipc/interfaces/, client stubs underipc/clients/. - Writes predictions, arrivals/departures, and vehicle state back to the DB through
db/hibernate/DataDbLogger(an async batched writer — do not open short-lived Hibernate sessions for high-volume writes; go through the logger).
- Loads GTFS config from the DB into
-
transitclockApi (REST) runs in Tomcat. Resources under
org.transitclock.api.rootResourcesconvert REST requests into RMI calls against a Core JVM. No live Core → no live predictions. -
transitclockWebapp (UI) runs in Tomcat alongside the API and proxies through it.
These are all main classes in org.transitclock.applications and are wired as shaded JARs:
SchemaGenerator— emits DDL from Hibernate annotated classes. Targets bothorg.transitclock.db.structs(core tables) andorg.transitclock.db.webstructs(webapp tables — web agency registry, API keys). There is a known classloader issue running the shaded JAR; usemvn exec:java -Dexec.mainClass=...instead (seetransitclock/README.md).GtfsFileProcessor— imports a GTFS feed into the DB. Use-storeNewRevsto activate the imported revision.DbTest— connectivity smoke test.CreateWebAgency,CreateAPIKey— seed webapp-side records.RmiQuery— CLI to hit a running Core's RMI servers.UpdateTravelTimes,ScheduleGenerator— offline batch jobs over historical data.
- Agency/runtime config comes from an XML config file (passed with
-cand via the JVM property-Dtransitclock.configFiles=...) plus system properties. The config framework isorg.transitclock.config.*(typedConfigValuesubclasses registered statically on class load). - Agency identity is a JVM-wide setting:
-Dtransitclock.core.agencyId=<id>is required on mostmainclasses. - DB connection info lives in
hibernate.cfg.xml(on classpath) and can be overridden by config. The webapp has its ownhibernate.cfg.xmlintransitclockWebapp/src/main/resources.
GTFS data in the DB is versioned by configRev and travel-time data by travelTimesRev. ActiveRevisions points at the currently-live pair. When changing ingestion logic or entity shapes, consider that multiple revs coexist in the same tables.
docker/Dockerfile bakes web.war into the Tomcat image at build time, so naively iterating on JSP/CSS/JS means a multi-minute image rebuild per edit. docker-compose.dev.yml is a checked-in compose override that swaps the WAR for a live bind mount of transitclockWebapp/src/main/webapp/. With it active, edits to JSP/CSS/JS/images appear in the running container on the next browser refresh — Tomcat recompiles JSPs on demand and serves the rest as static files.
- One-time build to populate
transitclockWebapp/target/web/WEB-INF/{lib,classes}(the override mounts these on top of the source tree, since the source has no built classpath):mvn -pl transitclockWebapp -am package -DskipTests. - Bring tomcat up with the override:
docker compose -f docker-compose.yml -f docker-compose.dev.yml up -d tomcat. The override deletesweb.warfrom the image at startup so it doesn't fight the bind mount. - The override only affects
tomcat;core,db, andtoolskeep their normal behavior. - Java class changes (anything under
transitclockWebapp/src/main/java,transitclockApi, or core) still requiremvn -pl transitclockWebapp -am package -DskipTestsfollowed bydocker compose -f docker-compose.yml -f docker-compose.dev.yml restart tomcat. Only the JSP/CSS/JS/HTML/image loop is fast. - For Tailwind / Vite-bundled assets, run
cd transitclockWebapp && npm run devin a second terminal — Vite watchesfrontend/and writes totarget/frontend-dist/, which the dev override bind-mounts into Tomcat at/dist. Browser refresh shows the change. - To return to the production-style baked-WAR flow, drop the
-f docker-compose.dev.ymlflag.
- Package name is
org.transitclock(notorg.transitime); the project was renamed but README files still reference the old name in places. - Hibernate entities in
db/structs/are the source of truth for the schema —SchemaGeneratorderives DDL from them. Add@Entityclasses there, not hand-written SQL. - New data sources belong in
org.transitclock.avl/as aModulesubclass. - New prediction algorithms plug in via
PredictionGeneratorFactory(configured by class name in the config file) — don't editPredictionGeneratorDefaultImplin place for an experiment. - Lombok is on the classpath (
@Slf4j,@Data, etc.); your IDE needs the annotation processor enabled. PlaybackModule.runTraceis non-deterministic across JVMs — replaying the same AVL trace produces prediction counts that vary by >20% run-to-run (and AD counts by >2×). Any integration test that depends on exact prediction counts or compares against a frozen CSV baseline will be flaky.PredictionAccuracyIntegrationTestis@Ignored for this reason; seedocs/integration-tests.mdfor context and suggested re-enablement paths.