Add integration and development documentation

This commit is contained in:
2026-06-11 14:22:57 +00:00
parent 9fe480d3ab
commit 42321eb166
3 changed files with 472 additions and 0 deletions

View File

@@ -0,0 +1,138 @@
# Feedapi Runtime Contract
`weatherapi` uses feedapi as its generic HTTP runtime and configuration layer.
This document describes the feedapi behavior that `weatherapi` relies on.
## Version
`go.mod` depends on:
- `gitea.maximumdirect.net/ejr/feedapi v0.1.0`
Only feedapi behavior used by `weatherapi` is documented here.
## Packages Used
Runtime composition imports:
- `feedapi/app`
- `feedapi/config`
- `feedapi/db`
The HTTP adapter imports:
- `feedapi/bind`
- `feedapi/endpoint`
- `feedapi/errors`
- `feedapi/render`
- `feedapi/response`
Tests also use:
- `feedapi/templates`
- `feedapi/transport/httpx`
## Config Ownership
Feedapi owns loading and validating the YAML config used by `weatherapi`.
`weatherapi` adds one local runtime check: `databases` must contain at least
one entry.
The implemented config areas used by `weatherapi` are:
- `server`: HTTP listen/default format/timeouts;
- `databases`: named database handles opened into a registry;
- `templates`: base directory for text templates.
The canonical config reference is [`docs/config.md`](../config.md).
## Database Registry
`cmd/weatherapi` calls feedapi `db.OpenAll` with configured databases and passes
the resulting registry into `feedapi/app.New`. It also selects the first
configured database name from the registry as the primary weather store.
Feedapi owns opening and closing database handles. `weatherapi` owns choosing
which opened handle is used by the Postgres repository.
## Endpoint Registry
`weatherapi` builds endpoint definitions with `httpapi.Definitions` and passes
them to feedapi through `feedapi/app.WithEndpoints`.
Feedapi owns:
- route adaptation;
- HTTP method/path matching;
- invoking endpoint binders;
- invoking endpoint handlers;
- rendering handler results.
Endpoint definitions remain owned by `internal/adapters/inbound/httpapi`.
## Renderers and Templates
Each implemented endpoint declares JSON, XML, and text output through feedapi
render formats. Text endpoints also name a template file.
Feedapi owns:
- renderer registration;
- format negotiation;
- template loading from `templates.base_dir`;
- applying templates to response envelopes.
`weatherapi` owns the template files under `templates/` and presenter output
shapes consumed by those templates.
## Content Negotiation
`weatherapi` relies on feedapi's negotiation order:
1. `format` query parameter;
2. `Accept` header;
3. configured default format.
Unsupported formats are exposed as structured API errors. See
[`docs/api.md`](../api.md) for the public HTTP contract.
## Success and Error Envelopes
Endpoint handlers return `response.Envelope{Data: ...}` for successful
responses. Nil data is rendered as `data: null`.
Feedapi error handling exposes structured error envelopes with:
- `error.code`;
- `error.message`.
`weatherapi` relies on feedapi invalid-parameter and unsupported-format errors
for request validation and negotiation failures.
## Middleware and Shutdown
Feedapi owns generic HTTP middleware and server lifecycle. The architecture
policy records that recovery, request ID, and timing middleware are installed by
default.
`weatherapi` supplies a signal-cancelable context to feedapi startup. Feedapi
owns graceful HTTP shutdown after that context is canceled.
## Upgrade Checklist
Before upgrading feedapi:
- verify config field names and defaults still match [`docs/config.md`](../config.md);
- verify database registry behavior still supports first configured database
selection;
- verify endpoint definition APIs still support binders, handlers, formats,
and template names;
- verify negotiation order remains `format`, then `Accept`, then default;
- verify success and error envelopes still match [`docs/api.md`](../api.md);
- run `go test ./...` with private module access configured.
## Related Docs
- [`docs/internal/runtime.md`](../internal/runtime.md)
- [`docs/internal/http-adapter.md`](../internal/http-adapter.md)
- [`docs/api.md`](../api.md)

View File

@@ -0,0 +1,175 @@
# Weatherfeeder Postgres Contract
`weatherapi` reads weather data from Postgres tables owned and populated by
`weatherfeeder`. This document describes only the storage contract consumed by
`weatherapi`; it is not a full weatherfeeder schema reference.
## Version
`go.mod` depends on:
- `gitea.maximumdirect.net/ejr/weatherfeeder v0.10.0`
The repository code also depends on weatherfeeder canonical model types. Table
compatibility must match the SQL in `internal/adapters/outbound/postgres`.
## Boundary
`weatherapi` is read-only:
- it does not create tables;
- it does not migrate tables;
- it does not ingest provider data;
- it does not write weatherfeeder events.
Weatherfeeder owns provider polling, normalization, table shape, and writes.
Postgres owns persistence, backup, restore, and availability.
## Table Families Read
| Resource | Tables read |
| --- | --- |
| Latest observation | `observations`, `observation_present_weather` |
| Current conditions | `observations` |
| Active alerts | `alert_runs`, `alerts`, `alert_references` |
| Hourly forecast | `forecasts`, `forecast_periods` |
| Narrative forecast | `forecasts`, `forecast_periods` |
| Forecast discussion | `forecast_discussions`, `forecast_discussion_key_messages` |
| Weather story run | `weather_story_runs`, `weather_stories` |
| Latest weather story | `weather_stories` |
## Latest Row Selection
Latest parent resources use these ordering rules:
- observations: `observed_at DESC, event_emitted_at DESC`;
- alert runs: `as_of DESC, event_emitted_at DESC`;
- hourly forecasts: `product = 'hourly'`, then `issued_at DESC,
event_emitted_at DESC`;
- narrative forecasts: `product = 'narrative'`, then `issued_at DESC,
event_emitted_at DESC`;
- forecast discussions: `issued_at DESC, event_emitted_at DESC`;
- weather story runs: `as_of DESC, event_emitted_at DESC`;
- latest individual weather story: `updated_at DESC, as_of DESC,
story_order ASC, story_index ASC`.
Current conditions aggregate `observations` rows where `observed_at` is inside
the application-provided observation window.
## Child Ordering
Child rows are loaded separately and attached in stored order:
- observation present weather: `weather_index ASC`;
- alerts: `alert_index ASC`;
- alert references: `alert_index ASC, reference_index ASC`;
- forecast periods: `period_index ASC`;
- forecast discussion key messages: `message_index ASC`;
- weather stories for a run: `story_index ASC`.
## Columns Read
The repository reads only these columns.
### `observations`
`event_id`, `station_id`, `station_name`, `observed_at`, `condition_code`,
`is_day`, `text_description`, `temperature_c`, `dewpoint_c`,
`wind_direction_degrees`, `wind_speed_kmh`, `wind_gust_kmh`,
`barometric_pressure_pa`, `visibility_meters`, `relative_humidity_percent`,
`apparent_temperature_c`, and `event_emitted_at`.
Current conditions additionally aggregate recent `observations` values for
temperature, apparent temperature, dewpoint, humidity, wind speed, wind
direction, condition code, and latest `is_day`.
### `observation_present_weather`
`weather_index`, `raw_text`, and `event_id`.
`raw_text` is decoded as JSON when present. Empty or null raw text maps to an
empty present-weather value; invalid JSON is returned as a repository error.
### `alert_runs`
`event_id`, `location_id`, `location_name`, `as_of`, `latitude`, `longitude`,
and `event_emitted_at`.
### `alerts`
`alert_index`, `alert_id`, `event`, `headline`, `severity`, `urgency`,
`certainty`, `status`, `message_type`, `category`, `response`, `description`,
`instruction`, `sent`, `effective`, `onset`, `expires`, `area_description`,
`sender_name`, and `run_event_id`.
### `alert_references`
`alert_index`, `reference_index`, `id`, `identifier`, `sender`, `sent`, and
`run_event_id`.
### `forecasts`
`event_id`, `location_id`, `location_name`, `issued_at`, `updated_at`,
`product`, `latitude`, `longitude`, `elevation_meters`, and
`event_emitted_at`.
Only `product = 'hourly'` and `product = 'narrative'` are read by implemented
routes.
### `forecast_periods`
`period_index`, `start_time`, `end_time`, `name`, `is_day`,
`condition_code`, `text_description`, `temperature_c`, `temperature_c_min`,
`temperature_c_max`, `dewpoint_c`, `relative_humidity_percent`,
`wind_direction_degrees`, `wind_speed_kmh`, `wind_gust_kmh`,
`barometric_pressure_pa`, `visibility_meters`, `apparent_temperature_c`,
`cloud_cover_percent`, `probability_of_precipitation_percent`,
`precipitation_amount_mm`, `snowfall_depth_mm`, `uv_index`, and
`run_event_id`.
### `forecast_discussions`
`event_id`, `office_id`, `office_name`, `issued_at`, `updated_at`, `product`,
`short_term_qualifier`, `short_term_issued_at`, `short_term_text`,
`long_term_qualifier`, `long_term_issued_at`, `long_term_text`, and
`event_emitted_at`.
### `forecast_discussion_key_messages`
`message_index`, `message_text`, and `run_event_id`.
### `weather_story_runs`
`event_id`, `office_id`, `as_of`, and `event_emitted_at`.
### `weather_stories`
`story_index`, `office_id`, `start_time`, `end_time`, `updated_at`, `title`,
`description`, `alt_text`, `priority`, `story_order`, `download_url`,
`run_event_id`, and `as_of`.
## Nullability and Time Assumptions
The repository scans nullable columns with `sql.Null*` types and maps them to
nil pointers or omitted zero values depending on the canonical model field.
Timestamps returned by the repository are normalized to UTC. Presentation
timezone conversion happens later in HTTP presenters.
Missing latest parent rows return `nil, nil` from repository methods. HTTP
rendering exposes this as a successful response with `data: null`.
## Compatibility Checklist
Before changing weatherfeeder storage or upgrading the weatherfeeder module:
- compare table names and columns with `internal/adapters/outbound/postgres`;
- preserve latest-row ordering columns used by `weatherapi`;
- preserve child index columns used for ordering;
- preserve nullable behavior expected by row mappers;
- run `go test ./internal/adapters/outbound/postgres` and affected HTTP tests.
## Related Docs
- [`docs/internal/postgres-repository.md`](../internal/postgres-repository.md)
- [`docs/operations.md`](../operations.md)

159
docs/policy/development.md Normal file
View File

@@ -0,0 +1,159 @@
# Development Policy
This document gives developers and LLM coding agents the concrete workflow for
changing `weatherapi` safely. Architecture invariants are defined in
[`docs/policy/architecture.md`](architecture.md).
## Repository Layout
| Path | Purpose |
| --- | --- |
| `cmd/weatherapi` | executable composition root |
| `internal/app` | read use cases, repository port, current-conditions model |
| `internal/adapters/inbound/httpapi` | route definitions, query binding, handlers |
| `internal/adapters/inbound/httpapi/presenter` | response payload shaping |
| `internal/adapters/outbound/postgres` | SQL reads and row mapping |
| `templates` | text response templates |
| `docs` | current documentation, policy, internals, integrations, roadmap |
| `examples` | copyable config and request examples |
## Build and Test Commands
Build:
```sh
go build ./cmd/weatherapi
```
Run all tests:
```sh
go test ./...
```
Focused tests:
```sh
go test ./internal/app
go test ./internal/adapters/inbound/httpapi
go test ./internal/adapters/inbound/httpapi/presenter
go test ./internal/adapters/outbound/postgres
```
Private module access is required for `feedapi` and `weatherfeeder` downloads.
If tests fail because Go cannot fetch a private module, fix module access before
treating package tests as behavior failures.
## Coding Conventions
- Keep `cmd/weatherapi` thin.
- Keep application use cases independent of HTTP, SQL, renderers, and templates.
- Keep query validation in the HTTP adapter.
- Keep unit conversion, rounding, timezone presentation, and payload copy
behavior in presenters.
- Keep SQL text, row structs, null handling, and UTC normalization in the
Postgres adapter.
- Prefer small focused changes over broad refactors.
- Preserve contextual error wrapping in repository and runtime code.
## Dependency Policy
Current direct dependencies are:
- `feedapi` for config, DB registry, endpoint definitions, renderers,
middleware, templates, and HTTP runtime;
- `weatherfeeder` for canonical model and standards types;
- `github.com/lib/pq` for the Postgres driver.
Do not add dependencies for small conveniences. New dependencies need a clear
adapter or domain purpose and should not leak through application boundaries
unless they are the explicit boundary contract.
## Adding or Changing Endpoints
1. Add or update the `Service` method in `internal/adapters/inbound/httpapi` if
the handler needs a new application read.
2. Add or update the application repository port in `internal/app`.
3. Implement the read in the Postgres adapter if needed.
4. Add the endpoint definition and binder in `internal/adapters/inbound/httpapi`.
5. Add presenter behavior in `presenter` instead of shaping payloads in handlers.
6. Add or update text templates when `format=text` should be supported.
7. Add endpoint tests for registration, query validation, formats, errors,
`data: null`, and representative payload behavior.
8. Update [`docs/api.md`](../api.md) and examples when the public HTTP contract changes.
## Changing Query Parameters
- Update binder functions and endpoint tests together.
- Preserve strict unknown-parameter rejection unless the route explicitly allows
a new parameter.
- Keep timezone parsing limited to route families that support it.
- Keep precision range validation aligned with presenter rounding support.
- Update [`docs/api.md`](../api.md) for public query behavior changes.
## Adding Config Fields
Config shape is loaded by feedapi. When `weatherapi` starts using a new config
field:
1. update runtime composition or the relevant adapter;
2. update [`docs/config.md`](../config.md);
3. update examples under `examples/` if operators need to set it;
4. add config/runtime tests where practical;
5. avoid committing real credentials or private infrastructure details.
## Adding CLI Flags
The executable currently supports only `-config`. If adding a flag:
1. keep parsing in `cmd/weatherapi`;
2. avoid putting business logic in `cmd`;
3. document precedence with environment variables if applicable;
4. update [`docs/cli.md`](../cli.md);
5. add tests when flag behavior is not trivial.
## Changing Repository Reads
- Keep SQL in `*_queries.go`.
- Keep row DTOs in `*_rows.go`.
- Keep mapping/null handling in `*_mapper.go`.
- Return `nil, nil` for missing latest parent rows.
- Normalize timestamps to UTC in mappers.
- Preserve child ordering from stored index columns.
- Update [`docs/internal/postgres-repository.md`](../internal/postgres-repository.md)
and [`docs/integrations/weatherfeeder-postgres.md`](../integrations/weatherfeeder-postgres.md)
when table or column assumptions change.
## Changing Presenters or Templates
- Copy input values before conversion, rounding, or timezone changes.
- Preserve nil pointer and `omitempty` behavior.
- Keep text-template helper fields out of JSON/XML when they are not public API
fields.
- Check `templates/*.txt.tmpl` for field references.
- Update presenter tests and endpoint text tests.
- Update [`docs/api.md`](../api.md) when response shape changes.
## Updating Examples
Examples must be copyable, valid, and free of secrets.
- Config examples belong under `examples/config.*.yml`.
- HTTP examples belong in `examples/requests.http`.
- Do not include unimplemented routes.
- Re-run YAML syntax checks or config loading tests when config examples change.
## Documentation Checklist
When behavior changes, update the canonical doc in the same change:
- README for project orientation and shortest useful command;
- `docs/api.md` for public HTTP behavior;
- `docs/config.md` for YAML config;
- `docs/cli.md` for executable flags and environment variables;
- `docs/operations.md` and `docs/troubleshooting.md` for operator behavior;
- `docs/internal/` for implementation boundaries;
- `docs/integrations/` for external storage/runtime contracts;
- `docs/roadmap/` only for unimplemented work.
Do not describe unimplemented behavior outside `docs/roadmap/`.