Meters: record events from the UI, and book consumption in the months it accrued in
Two threads that ended up in the same files. One is navigation: a meter
swap that happened today had no click path at all, and most per-meter
tasks were reachable only by knowing which admin page owned them. The
other is attribution: readings on 1 August and 16 September showed six
weeks of water under September and nothing under August.
Meter events from the UI
Swap, counter reset, tank level, delivery and note are recorded through
MeterEventService rather than ad-hoc inserts, so the dialog's verdict and
the saved result come from the same Validate call, and every record or
delete recomputes the meter inside one transaction. MeterEventRules
decides which events a mode offers -- a tank has no register to swap, and
Correction is offered nowhere because nothing reads it.
A swap is stored as the event at T plus a manual reading of the new
register's start value at exactly T. That pairing is the whole trick: the
boundary window is (previousReading, reading], so the old register's tail
books at T and every later reading counts from the new start. Writing the
old final value as the reading at T instead -- the obvious thing -- double
counts the tail and then rejects every reading the new register produces.
Deleting a swap removes that start reading only while it is still the
untouched start value, and only Manual readings can be deleted at all.
Navigation
The meter page is now the hub: primary entry by mode, a "Record event"
menu, and Edit through a shared MeterEditor that also owns tank setup.
Other pages link into it with MeterLinks (/meters/{id}?tab=...&action=...),
whose action is consumed once after the interactive render and dropped
from the address -- the reverse order flashes the dialog and closes it,
because a circuit's first location change dismisses every open dialog.
The app bar gains a "Find a meter" dialog with the same quick entry.
A source that has no usable connector now links to creating (or enabling)
one and comes back to the same source dialog with the connector picked
and everything typed still there; the draft survives in a circuit-scoped
DraftStore, and the way back is a meter id rather than a URL, so the page
cannot be made to redirect anywhere else. The connector list shows which
meters use each connector, import batches list the meters and categories
they wrote to, the meter editor owns the meter's own cost categories, and
the dashboard's empty cost panel names the first missing step instead of
listing every admin page.
Months
A reading is an instant, and what it measures accrued over the time since
the previous one. Booking the whole delta at the closing reading misfiles
it whenever the interval crosses a month boundary, so a plain increase is
now divided at local month boundaries in proportion to elapsed time, each
share stamped inside its month and marked estimated: the meter recorded a
total, not a shape. The parts always sum to the original.
Imported monthly tables are the exception that keeps the golden fixtures
reconciling. "Mai 2026" carries the register at the end of May but is
stamped on the 1st, so the importer -- the only place that still knows
whether the date cell named a month or a day -- flags it MonthLabel, and
the engine reads it as the end of its month. Inferring that from the
stamp instead would catch day-dated rows: a sheet with "01.08.2026" in it
is not a monthly table, and reading it as one moves two thirds of July
into August.
ReadingTimeline is the single ordering built on that: effective time,
then stamp. The register normalizers walk it, and so do the decrease
guard and the event dialog, which is what stops them disagreeing about
which reading is "previous" -- a sheet imported after live readings of the
same month used to count that month twice, and a mid-month reading below
the month's end value was rejected as a drop. A swap detected in a
monthly table applies from the start of that local month, i.e. to the
first reading in it, and a recorded start value never counts above the
reading it lands on.
Every reader buckets in the configured timezone rather than a hardcoded
one, and turns a requested date into that zone's local midnight, so the
divided shares are read back under the months they were stamped in. The
zone id is normalised to its IANA form, because .NET accepts a Windows id
that PostgreSQL will not bucket by, and both are checked at startup.
Stored consumption is derived, so a rule change reaches a meter only at
its next reading -- weeks, for a meter read monthly. NormalizationUpgrade
records the revision and zone the stored series was built with and
rebuilds everything once at startup when either differs, each meter in
its own transaction. A meter that fails is logged, kept in
normalization_pending and retried at the next start: one bad series must
never keep the application down.
What an operator sees once
Existing charts change on the first start after the update: months that
carried a neighbour's use give it back. Rows of earlier imports from
monthly tables are marked as such before anything is recomputed, and if
that marking fails nothing is rebuilt or recorded, so the upgrade simply
runs again next time rather than shifting every imported month by one. A
wizard import whose date format was left on auto-detect is treated as a
monthly table when all of its rows sit on the 1st across at least two
months -- exactly how those rows were attributed before -- and each such
batch is named in the log, because a day-dated sheet always read on the
1st looks identical; revert and re-import it with the day format if that
is what it was.
Tests: 120 unit and 230 integration, including the reference fixtures,
which still reconcile month for month.
This commit is contained in:
@@ -240,6 +240,9 @@
|
||||
<data name="Connectors_AddConnector" xml:space="preserve">
|
||||
<value>Add connector</value>
|
||||
</data>
|
||||
<data name="Connectors_BackToMeter" xml:space="preserve">
|
||||
<value>Back to '{0}'</value>
|
||||
</data>
|
||||
<data name="Connectors_BaseUrl" xml:space="preserve">
|
||||
<value>Base URL (e.g. http://homeassistant.local:8123)</value>
|
||||
</data>
|
||||
@@ -252,6 +255,9 @@
|
||||
<data name="Connectors_DeleteTitle" xml:space="preserve">
|
||||
<value>Delete connector</value>
|
||||
</data>
|
||||
<data name="Connectors_DisabledInUse" xml:space="preserve">
|
||||
<value>Disabled — these sources are not ingesting</value>
|
||||
</data>
|
||||
<data name="Connectors_EditTitle" xml:space="preserve">
|
||||
<value>Edit {0}</value>
|
||||
</data>
|
||||
@@ -270,6 +276,9 @@
|
||||
<data name="Connectors_ExtraTopics" xml:space="preserve">
|
||||
<value>Extra topics (comma-separated, optional)</value>
|
||||
</data>
|
||||
<data name="Connectors_ForMeter" xml:space="preserve">
|
||||
<value>Setting up a connector for a source of '{0}'. Saving it takes you back.</value>
|
||||
</data>
|
||||
<data name="Connectors_Host" xml:space="preserve">
|
||||
<value>Host</value>
|
||||
</data>
|
||||
@@ -366,6 +375,15 @@
|
||||
<data name="Connectors_TokenStored" xml:space="preserve">
|
||||
<value>Long-lived access token (stored — type to replace)</value>
|
||||
</data>
|
||||
<data name="Connectors_Unused" xml:space="preserve">
|
||||
<value>Not used</value>
|
||||
</data>
|
||||
<data name="Connectors_UsedBy" xml:space="preserve">
|
||||
<value>Used by</value>
|
||||
</data>
|
||||
<data name="Connectors_UsedByMore" xml:space="preserve">
|
||||
<value>+{0} more</value>
|
||||
</data>
|
||||
<data name="Connectors_UsernameEnv" xml:space="preserve">
|
||||
<value>Username env-var name (optional)</value>
|
||||
</data>
|
||||
@@ -408,8 +426,11 @@
|
||||
<data name="Consumables_NoMetersLead" xml:space="preserve">
|
||||
<value>No consumable meters found. Add a meter with mode</value>
|
||||
</data>
|
||||
<data name="Consumables_NoMetersOrImport" xml:space="preserve">
|
||||
<value>, or load the reference data from</value>
|
||||
</data>
|
||||
<data name="Consumables_NoMetersTail" xml:space="preserve">
|
||||
<value>and a tank, or load the reference data from</value>
|
||||
<value>and set up its tank in</value>
|
||||
</data>
|
||||
<data name="Consumables_PageTitle" xml:space="preserve">
|
||||
<value>Consumables</value>
|
||||
@@ -417,9 +438,15 @@
|
||||
<data name="Consumables_PerDay" xml:space="preserve">
|
||||
<value>({0} {1}/day)</value>
|
||||
</data>
|
||||
<data name="Consumables_RecordDelivery" xml:space="preserve">
|
||||
<value>Record delivery</value>
|
||||
</data>
|
||||
<data name="Consumables_TankLevel" xml:space="preserve">
|
||||
<value>Tank level</value>
|
||||
</data>
|
||||
<data name="Consumables_TankNotConfigured" xml:space="preserve">
|
||||
<value>{0} has no tank set up yet — its level, fill and forecast need the tank's capacity and dipstick calibration.</value>
|
||||
</data>
|
||||
<data name="Consumables_UnitUsed" xml:space="preserve">
|
||||
<value>{0} used</value>
|
||||
</data>
|
||||
@@ -438,8 +465,20 @@
|
||||
<data name="Dashboard_LatestMonthWithData" xml:space="preserve">
|
||||
<value>Latest month with data</value>
|
||||
</data>
|
||||
<data name="Dashboard_NoCostData" xml:space="preserve">
|
||||
<value>No cost data yet — import a sheet or add tariffs.</value>
|
||||
<data name="Dashboard_SetupNoCategories" xml:space="preserve">
|
||||
<value>Costs are reported per cost category, and there is none yet:</value>
|
||||
</data>
|
||||
<data name="Dashboard_SetupNoCostsThisYear" xml:space="preserve">
|
||||
<value>No costs this year yet. They appear once a meter in a cost category records consumption with a tariff in effect.</value>
|
||||
</data>
|
||||
<data name="Dashboard_SetupNoMembers" xml:space="preserve">
|
||||
<value>No meter counts toward a cost category yet. Pick categories when editing a meter, or assign them per category:</value>
|
||||
</data>
|
||||
<data name="Dashboard_SetupNoMeters" xml:space="preserve">
|
||||
<value>No meters yet. Costs start with a meter — add one, or import existing data:</value>
|
||||
</data>
|
||||
<data name="Dashboard_SetupNoTariffs" xml:space="preserve">
|
||||
<value>No tariffs yet — consumption needs a price before it has a cost:</value>
|
||||
</data>
|
||||
<data name="Dashboard_ThisMonth" xml:space="preserve">
|
||||
<value>This month</value>
|
||||
@@ -528,6 +567,9 @@
|
||||
<data name="EnergyView_NoChainUpstream" xml:space="preserve">
|
||||
<value>upstream meter(s)</value>
|
||||
</data>
|
||||
<data name="EnergyView_NoDataInRange" xml:space="preserve">
|
||||
<value>No consumption recorded for these meters in the selected range yet.</value>
|
||||
</data>
|
||||
<data name="EnergyView_NoMetersIntro" xml:space="preserve">
|
||||
<value>No meters for this energy type yet. Add meters in</value>
|
||||
</data>
|
||||
@@ -612,6 +654,9 @@
|
||||
<data name="Enum_ReadingFlags_MeterSwap" xml:space="preserve">
|
||||
<value>Meter swap</value>
|
||||
</data>
|
||||
<data name="Enum_ReadingFlags_MonthLabel" xml:space="preserve">
|
||||
<value>Month-end value</value>
|
||||
</data>
|
||||
<data name="Enum_ReadingQuality_Estimated" xml:space="preserve">
|
||||
<value>Estimated</value>
|
||||
</data>
|
||||
@@ -879,6 +924,9 @@
|
||||
<data name="Import_ColumnSource" xml:space="preserve">
|
||||
<value>Source</value>
|
||||
</data>
|
||||
<data name="Import_ColumnWritesTo" xml:space="preserve">
|
||||
<value>Wrote to</value>
|
||||
</data>
|
||||
<data name="Import_DryRunReferenceSheet" xml:space="preserve">
|
||||
<value>Dry-run a reference sheet</value>
|
||||
</data>
|
||||
@@ -957,6 +1005,9 @@
|
||||
<data name="Layout_DarkMode" xml:space="preserve">
|
||||
<value>Dark mode</value>
|
||||
</data>
|
||||
<data name="Layout_FindMeter" xml:space="preserve">
|
||||
<value>Find a meter</value>
|
||||
</data>
|
||||
<data name="Layout_Language" xml:space="preserve">
|
||||
<value>Language</value>
|
||||
</data>
|
||||
@@ -975,6 +1026,9 @@
|
||||
<data name="MeterDetail_AboutTheSame" xml:space="preserve">
|
||||
<value>about the same</value>
|
||||
</data>
|
||||
<data name="MeterDetail_AddFirstReading" xml:space="preserve">
|
||||
<value>Add first reading</value>
|
||||
</data>
|
||||
<data name="MeterDetail_AddReading" xml:space="preserve">
|
||||
<value>Add reading</value>
|
||||
</data>
|
||||
@@ -984,6 +1038,9 @@
|
||||
<data name="MeterDetail_AddSource" xml:space="preserve">
|
||||
<value>Add source</value>
|
||||
</data>
|
||||
<data name="MeterDetail_AnotherConnector" xml:space="preserve">
|
||||
<value>Set up another connector</value>
|
||||
</data>
|
||||
<data name="MeterDetail_AttributeLabel" xml:space="preserve">
|
||||
<value>Attribute (optional; blank = state)</value>
|
||||
</data>
|
||||
@@ -1002,12 +1059,18 @@
|
||||
<data name="MeterDetail_Component" xml:space="preserve">
|
||||
<value>Component</value>
|
||||
</data>
|
||||
<data name="MeterDetail_ConnectSource" xml:space="preserve">
|
||||
<value>Connect a live source</value>
|
||||
</data>
|
||||
<data name="MeterDetail_Connector" xml:space="preserve">
|
||||
<value>Connector</value>
|
||||
</data>
|
||||
<data name="MeterDetail_ConnectorDisabled" xml:space="preserve">
|
||||
<value>'{0}' is disabled, so this source would never ingest. Enable it first.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_ConnectorOnlyDisabled" xml:space="preserve">
|
||||
<value>'{0}' is disabled, so it cannot serve this source —</value>
|
||||
</data>
|
||||
<data name="MeterDetail_ConnectorTypeMismatch" xml:space="preserve">
|
||||
<value>'{0}' is a {1} connector; a {2} source needs {3}.</value>
|
||||
</data>
|
||||
@@ -1021,7 +1084,25 @@
|
||||
<value>create one</value>
|
||||
</data>
|
||||
<data name="MeterDetail_DecreaseWarning" xml:space="preserve">
|
||||
<value>Below the last reading ({0} {1}) on a register that only counts up, so it will be rejected. If the meter was swapped or reset, record that on the Events tab first.</value>
|
||||
<value>Below the last reading ({0} {1}) on a register that only counts up, so it would be rejected. If the meter was swapped or its counter reset, record that first — the value you typed is kept.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_DeleteBoundaryConfirm" xml:space="preserve">
|
||||
<value>Delete the {0} of {1}? The new register's start reading recorded with it is removed too, and consumption is recomputed.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_DeleteEvent" xml:space="preserve">
|
||||
<value>Delete event</value>
|
||||
</data>
|
||||
<data name="MeterDetail_DeleteEventConfirm" xml:space="preserve">
|
||||
<value>Delete the {0} of {1}? Consumption is recomputed.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_DeleteReading" xml:space="preserve">
|
||||
<value>Delete this hand-entered reading</value>
|
||||
</data>
|
||||
<data name="MeterDetail_DeleteReadingConfirm" xml:space="preserve">
|
||||
<value>Delete the reading {0} {1} of {2}? Consumption is recomputed without it.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_DeleteReadingTitle" xml:space="preserve">
|
||||
<value>Delete reading</value>
|
||||
</data>
|
||||
<data name="MeterDetail_DeleteSourceConfirm" xml:space="preserve">
|
||||
<value>Delete this {0} source?</value>
|
||||
@@ -1029,15 +1110,36 @@
|
||||
<data name="MeterDetail_DeleteSourceTitle" xml:space="preserve">
|
||||
<value>Delete source</value>
|
||||
</data>
|
||||
<data name="MeterDetail_EditMeter" xml:space="preserve">
|
||||
<value>Edit meter</value>
|
||||
</data>
|
||||
<data name="MeterDetail_EditSource" xml:space="preserve">
|
||||
<value>Edit source</value>
|
||||
</data>
|
||||
<data name="MeterDetail_EnableConnectorLink" xml:space="preserve">
|
||||
<value>enable it</value>
|
||||
</data>
|
||||
<data name="MeterDetail_EnergyTypeFlow" xml:space="preserve">
|
||||
<value>Open the {0} flow</value>
|
||||
</data>
|
||||
<data name="MeterDetail_EnterValue" xml:space="preserve">
|
||||
<value>Enter a value</value>
|
||||
</data>
|
||||
<data name="MeterDetail_EntityIdLabel" xml:space="preserve">
|
||||
<value>Entity id (e.g. sensor.house_power)</value>
|
||||
</data>
|
||||
<data name="MeterDetail_EventDeleted" xml:space="preserve">
|
||||
<value>Event deleted — consumption recomputed.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_EventsHintNote" xml:space="preserve">
|
||||
<value>Notes annotate this meter's history; they change no figures.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_EventsHintRegister" xml:space="preserve">
|
||||
<value>Record a meter swap or a counter reset here — the history stays continuous on this meter.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_EventsHintTank" xml:space="preserve">
|
||||
<value>Tank levels and deliveries drive this tank's consumption.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_Flags" xml:space="preserve">
|
||||
<value>Flags</value>
|
||||
</data>
|
||||
@@ -1047,6 +1149,18 @@
|
||||
<data name="MeterDetail_FutureTime" xml:space="preserve">
|
||||
<value>That time is in the future.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_GetStarted" xml:space="preserve">
|
||||
<value>Nothing recorded for this meter yet — start with a first value.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_GoToEvents" xml:space="preserve">
|
||||
<value>Go to events</value>
|
||||
</data>
|
||||
<data name="MeterDetail_Imported" xml:space="preserve">
|
||||
<value>Imported</value>
|
||||
</data>
|
||||
<data name="MeterDetail_ImportedEventHint" xml:space="preserve">
|
||||
<value>Came from an import — revert that import to remove it.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_Kind" xml:space="preserve">
|
||||
<value>Kind</value>
|
||||
</data>
|
||||
@@ -1071,6 +1185,9 @@
|
||||
<data name="MeterDetail_LocalTimeIn" xml:space="preserve">
|
||||
<value>Local time in {0}.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_ManageTariffs" xml:space="preserve">
|
||||
<value>Manage tariffs</value>
|
||||
</data>
|
||||
<data name="MeterDetail_MeterGone" xml:space="preserve">
|
||||
<value>This meter no longer exists.</value>
|
||||
</data>
|
||||
@@ -1093,7 +1210,7 @@
|
||||
<value>No normalized consumption yet.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_NoEvents" xml:space="preserve">
|
||||
<value>No events (swaps, deliveries, corrections).</value>
|
||||
<value>No events recorded yet.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_NoRawReadings" xml:space="preserve">
|
||||
<value>No raw readings.</value>
|
||||
@@ -1104,6 +1221,9 @@
|
||||
<data name="MeterDetail_NoSources" xml:space="preserve">
|
||||
<value>No ingest sources bound to this meter. Add one to pull from MQTT/Tasmota or Home Assistant.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_NoTankConfigured" xml:space="preserve">
|
||||
<value>No tank set up for this meter yet — its level, fill and forecast need the tank's capacity and dipstick calibration.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_NoTariffs" xml:space="preserve">
|
||||
<value>No applicable tariffs.</value>
|
||||
</data>
|
||||
@@ -1149,11 +1269,14 @@
|
||||
<data name="MeterDetail_Quality" xml:space="preserve">
|
||||
<value>Quality</value>
|
||||
</data>
|
||||
<data name="MeterDetail_ReadingDeleted" xml:space="preserve">
|
||||
<value>Reading deleted — consumption recomputed.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_ReadingLabel" xml:space="preserve">
|
||||
<value>Reading ({0})</value>
|
||||
</data>
|
||||
<data name="MeterDetail_ReadingRejected" xml:space="preserve">
|
||||
<value>Rejected — below the previous reading on a register that only counts up. Record a counter reset or meter swap first.</value>
|
||||
<value>Rejected — below the previous reading on a register that only counts up. If the meter was swapped or reset, record that first.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_ReadingReplaced" xml:space="preserve">
|
||||
<value>Replaced the reading at that time with {0} {1}.</value>
|
||||
@@ -1170,6 +1293,18 @@
|
||||
<data name="MeterDetail_RecentReadingsCaption" xml:space="preserve">
|
||||
<value>Most recent {0} (raw, immutable audit truth). Times in {1}.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_RecordEvent" xml:space="preserve">
|
||||
<value>Record event</value>
|
||||
</data>
|
||||
<data name="MeterDetail_RecordReset" xml:space="preserve">
|
||||
<value>Record counter reset</value>
|
||||
</data>
|
||||
<data name="MeterDetail_RecordSwap" xml:space="preserve">
|
||||
<value>Record meter swap</value>
|
||||
</data>
|
||||
<data name="MeterDetail_RecordTankLevel" xml:space="preserve">
|
||||
<value>Record tank level</value>
|
||||
</data>
|
||||
<data name="MeterDetail_RegisterDetails" xml:space="preserve">
|
||||
<value>Meter register details</value>
|
||||
</data>
|
||||
@@ -1179,6 +1314,9 @@
|
||||
<data name="MeterDetail_ReplaceNotice" xml:space="preserve">
|
||||
<value>This meter already has a reading at that time — saving replaces its value.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_ReplaceSwapStartNotice" xml:space="preserve">
|
||||
<value>Replaces the new meter's start value recorded with the swap — consumption across the swap stays correct.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_Retired" xml:space="preserve">
|
||||
<value>retired</value>
|
||||
</data>
|
||||
@@ -1191,6 +1329,15 @@
|
||||
<data name="MeterDetail_Scale" xml:space="preserve">
|
||||
<value>Scale</value>
|
||||
</data>
|
||||
<data name="MeterDetail_ScopeThisMeter" xml:space="preserve">
|
||||
<value>This meter</value>
|
||||
</data>
|
||||
<data name="MeterDetail_SerialValue" xml:space="preserve">
|
||||
<value>S/N {0}</value>
|
||||
</data>
|
||||
<data name="MeterDetail_SetUpTank" xml:space="preserve">
|
||||
<value>Set up tank</value>
|
||||
</data>
|
||||
<data name="MeterDetail_SkippedTime" xml:space="preserve">
|
||||
<value>That clock time never happened in {0} — the clocks moved forward. Pick another time.</value>
|
||||
</data>
|
||||
@@ -1203,6 +1350,9 @@
|
||||
<data name="MeterDetail_SourceType" xml:space="preserve">
|
||||
<value>Source type</value>
|
||||
</data>
|
||||
<data name="MeterDetail_SwappedOrResetHint" xml:space="preserve">
|
||||
<value>Below last reading — swapped or reset?</value>
|
||||
</data>
|
||||
<data name="MeterDetail_TabConsumption" xml:space="preserve">
|
||||
<value>Consumption ({0})</value>
|
||||
</data>
|
||||
@@ -1218,6 +1368,9 @@
|
||||
<data name="MeterDetail_TabTariffs" xml:space="preserve">
|
||||
<value>Tariffs ({0})</value>
|
||||
</data>
|
||||
<data name="MeterDetail_TankUsesEvents" xml:space="preserve">
|
||||
<value>A tank's consumption comes from tank levels and deliveries, which are recorded as events — a reading entered here would change nothing.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_TariffOpenEnd" xml:space="preserve">
|
||||
<value>open</value>
|
||||
</data>
|
||||
@@ -1243,13 +1396,13 @@
|
||||
<value>Value path (e.g. ENERGY.Total; blank = bare scalar)</value>
|
||||
</data>
|
||||
<data name="MeterDetail_VirtualNoReadings" xml:space="preserve">
|
||||
<value>A virtual meter is an expression over other meters, so it stores no readings of its own — enter the reading on the meter the expression refers to.</value>
|
||||
<value>A virtual meter has no readings of its own — enter readings on the meters it adds up.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_VirtualNotice" xml:space="preserve">
|
||||
<value>Virtual meter — its value is an expression over other meters, evaluated when read, so it has no stored series of its own.</value>
|
||||
<value>Virtual meter — it has no readings of its own; its value is the sum of the upstream meters linked to it.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_VirtualNoticeTrends" xml:space="preserve">
|
||||
<value>See Trends for its figures.</value>
|
||||
<data name="MeterDetail_VirtualNoticeFlow" xml:space="preserve">
|
||||
<value>See it in the flow view.</value>
|
||||
</data>
|
||||
<data name="MeterDetail_VsLastMonth" xml:space="preserve">
|
||||
<value>vs last month</value>
|
||||
@@ -1257,18 +1410,189 @@
|
||||
<data name="MeterDetail_VsLastYear" xml:space="preserve">
|
||||
<value>{0} vs {1} last year</value>
|
||||
</data>
|
||||
<data name="MeterDetail_WillBeRejectedSuffix" xml:space="preserve">
|
||||
<value> — will be rejected</value>
|
||||
</data>
|
||||
<data name="MeterDetail_Yes" xml:space="preserve">
|
||||
<value>yes</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ActionFailed" xml:space="preserve">
|
||||
<value>That did not go through, and nothing was changed. Try again; the details are in the log.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_AfterResetLabel" xml:space="preserve">
|
||||
<value>Register after the reset ({0})</value>
|
||||
</data>
|
||||
<data name="MeterEvent_BeforeResetHelp" xml:space="preserve">
|
||||
<value>Its last value before it started over. Leave empty if unknown — the stretch since the last reading is then not counted.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_BeforeResetLabel" xml:space="preserve">
|
||||
<value>Register before the reset ({0})</value>
|
||||
</data>
|
||||
<data name="MeterEvent_CountsFrom" xml:space="preserve">
|
||||
<value>Later readings count on from {0} {1}.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_DeliveredLabel" xml:space="preserve">
|
||||
<value>Delivered ({0})</value>
|
||||
</data>
|
||||
<data name="MeterEvent_DeliveredSince" xml:space="preserve">
|
||||
<value>Delivered since then: {0} {1}</value>
|
||||
</data>
|
||||
<data name="MeterEvent_FirstLevel" xml:space="preserve">
|
||||
<value>First level for this tank — consumption is counted from the next one.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_IntroDelivery" xml:space="preserve">
|
||||
<value>A refill of the tank. It raises the level; the next tank level turns the difference into consumption.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_IntroNote" xml:space="preserve">
|
||||
<value>A remark on this meter's history, e.g. a repair or a moved sensor. It changes no figures.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_IntroReset" xml:space="preserve">
|
||||
<value>The register started over — a reset or a rollover — but it is still the same meter. Later readings count on from the value after the reset.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_IntroSwap" xml:space="preserve">
|
||||
<value>The physical meter was replaced. Enter the old meter's final reading and the new meter's start reading — the history continues on this meter without a gap or a spike.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_IntroTankLevel" xml:space="preserve">
|
||||
<value>A dipstick or gauge reading of what is in the tank. Consumption is the difference to the previous level, plus the deliveries in between.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_LastLevel" xml:space="preserve">
|
||||
<value>Last level: {0} ({1} {2}) on {3}</value>
|
||||
</data>
|
||||
<data name="MeterEvent_LastReadingBefore" xml:space="preserve">
|
||||
<value>Last reading before: {0} {1} on {2}</value>
|
||||
</data>
|
||||
<data name="MeterEvent_LevelLabel" xml:space="preserve">
|
||||
<value>Tank level</value>
|
||||
</data>
|
||||
<data name="MeterEvent_LevelRose" xml:space="preserve">
|
||||
<value>The level rose {0} {1} more than the recorded deliveries — is a delivery missing? It is booked as no consumption.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_LevelVolume" xml:space="preserve">
|
||||
<value>= {0} {1} in the tank</value>
|
||||
</data>
|
||||
<data name="MeterEvent_LiveSourcesWarning" xml:space="preserve">
|
||||
<value>{0} live source(s) feed this meter. If they still read the old register, update or disable them on the Sources tab — otherwise their next value counts against the new one.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_NewStartHelp" xml:space="preserve">
|
||||
<value>Usually 0, or the value on the installation record.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_NewStartLabel" xml:space="preserve">
|
||||
<value>New meter — start reading ({0})</value>
|
||||
</data>
|
||||
<data name="MeterEvent_NoCalibrationHint" xml:space="preserve">
|
||||
<value>Centimetres need the tank's dipstick calibration — set it up under Edit meter.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_NoReadingBefore" xml:space="preserve">
|
||||
<value>No earlier reading — the old register counts from the meter's initial baseline, {0} {1}.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_NotANumber" xml:space="preserve">
|
||||
<value>Not a number</value>
|
||||
</data>
|
||||
<data name="MeterEvent_NoteLabel" xml:space="preserve">
|
||||
<value>Note</value>
|
||||
</data>
|
||||
<data name="MeterEvent_NotesOptional" xml:space="preserve">
|
||||
<value>Note (optional)</value>
|
||||
</data>
|
||||
<data name="MeterEvent_OldFinalHelp" xml:space="preserve">
|
||||
<value>From the swap record or a photo of the old meter. Prefilled with the last reading — the difference is booked as consumption at the swap.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_OldFinalLabel" xml:space="preserve">
|
||||
<value>Old meter — final reading ({0})</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemAlreadyRecorded" xml:space="preserve">
|
||||
<value>A swap or reset is already recorded between the same two readings — delete it first, or pick a time after the next reading.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemAmountOutOfRange" xml:space="preserve">
|
||||
<value>That value is not possible here — a delivery must be above zero, and a level cannot be negative.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemAmountRequired" xml:space="preserve">
|
||||
<value>Enter an amount.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemBelowPrevious" xml:space="preserve">
|
||||
<value>The old register's final reading is below where it last stood (its last reading, or the meter's initial baseline) — check both numbers.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemImported" xml:space="preserve">
|
||||
<value>This came from an import — revert that import to remove it.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemLaterBoundary" xml:space="preserve">
|
||||
<value>A later meter swap or counter reset was recorded against this — delete that one first.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemLevelAtSameTime" xml:space="preserve">
|
||||
<value>A tank level is already recorded at exactly this time — delete that one first, or pick another minute.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemNeedsCalibration" xml:space="preserve">
|
||||
<value>A level in centimetres needs the tank's calibration — set it up under Edit meter, or enter the volume.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemNotForMode" xml:space="preserve">
|
||||
<value>This event does not apply to a meter in this measurement mode.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemNotFound" xml:space="preserve">
|
||||
<value>It no longer exists — the page was out of date.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemNotManual" xml:space="preserve">
|
||||
<value>Only hand-entered readings can be deleted here — measured readings are the audit record, and imported ones go with their import.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemNoteRequired" xml:space="preserve">
|
||||
<value>Enter the note.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemReadingAtSameTime" xml:space="preserve">
|
||||
<value>There is already a reading at exactly this time — pick a minute before or after it, depending on which meter it belongs to.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ProblemStartRejected" xml:space="preserve">
|
||||
<value>The new register's start reading could not be stored because a reading arrived meanwhile. Nothing was saved — try again.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_ReadingsAfterWarning" xml:space="preserve">
|
||||
<value>{0} reading(s) are already recorded after this time — the next is {1} {2} on {3}. They will count as the new register; if they still belong to the old meter, pick a later time.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_SavedDelivery" xml:space="preserve">
|
||||
<value>Delivery recorded — consumption recomputed.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_SavedNote" xml:space="preserve">
|
||||
<value>Note saved.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_SavedReset" xml:space="preserve">
|
||||
<value>Counter reset recorded — consumption recomputed.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_SavedSwap" xml:space="preserve">
|
||||
<value>Meter swap recorded — consumption recomputed.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_SavedTankLevel" xml:space="preserve">
|
||||
<value>Tank level recorded — consumption recomputed.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_TailBooked" xml:space="preserve">
|
||||
<value>{0} {1} since that reading, booked at this time.</value>
|
||||
</data>
|
||||
<data name="MeterEvent_Title" xml:space="preserve">
|
||||
<value>{0} — {1}</value>
|
||||
</data>
|
||||
<data name="MeterEvent_UsedSince" xml:space="preserve">
|
||||
<value>Used since the last level: {0} {1}</value>
|
||||
</data>
|
||||
<data name="MeterSearch_More" xml:space="preserve">
|
||||
<value>Showing {0} of {1} — keep typing to narrow down.</value>
|
||||
</data>
|
||||
<data name="MeterSearch_NoMeters" xml:space="preserve">
|
||||
<value>No meters yet.</value>
|
||||
</data>
|
||||
<data name="MeterSearch_Placeholder" xml:space="preserve">
|
||||
<value>Name, serial number or location</value>
|
||||
</data>
|
||||
<data name="Meters_Active" xml:space="preserve">
|
||||
<value>Active</value>
|
||||
</data>
|
||||
<data name="Meters_AddMeter" xml:space="preserve">
|
||||
<value>Add meter</value>
|
||||
</data>
|
||||
<data name="Meters_ChangedMeanwhile" xml:space="preserve">
|
||||
<value>Something changed while this dialog was open (a meter or category was deleted). Nothing was saved — check and save again.</value>
|
||||
</data>
|
||||
<data name="Meters_CostCategories" xml:space="preserve">
|
||||
<value>Cost categories</value>
|
||||
</data>
|
||||
<data name="Meters_CostCategoriesHelp" xml:space="preserve">
|
||||
<value>The dashboard reports cost per category; a meter in none has no cost there.</value>
|
||||
</data>
|
||||
<data name="Meters_CostCategoriesInherited" xml:space="preserve">
|
||||
<value>Already counted through its energy type in: {0}</value>
|
||||
</data>
|
||||
<data name="Meters_DeleteConfirm" xml:space="preserve">
|
||||
<value>Delete '{0}'? This cannot be undone.</value>
|
||||
</data>
|
||||
@@ -1317,24 +1641,69 @@
|
||||
<data name="Meters_No" xml:space="preserve">
|
||||
<value>no</value>
|
||||
</data>
|
||||
<data name="Meters_NoCostCategories" xml:space="preserve">
|
||||
<value>No cost categories yet, so this meter's cost will not show on the dashboard. Create one in</value>
|
||||
</data>
|
||||
<data name="Meters_NoSearchMatch" xml:space="preserve">
|
||||
<value>No meter matches “{0}”.</value>
|
||||
</data>
|
||||
<data name="Meters_PvRole" xml:space="preserve">
|
||||
<value>PV role (optional)</value>
|
||||
</data>
|
||||
<data name="Meters_PvRoleHelp" xml:space="preserve">
|
||||
<value>Tag the house meter total_load and the grid meter grid_import to unlock self-consumption, autarky and savings on the Solar page.</value>
|
||||
</data>
|
||||
<data name="Meters_RecomputeNotice" xml:space="preserve">
|
||||
<value>Mode/baseline changed — consumption will be recomputed on save.</value>
|
||||
<value>Settings that affect consumption changed — it will be recomputed on save.</value>
|
||||
</data>
|
||||
<data name="Meters_RecordSwapInstead" xml:space="preserve">
|
||||
<value>Record meter swap instead</value>
|
||||
</data>
|
||||
<data name="Meters_RequiredFields" xml:space="preserve">
|
||||
<value>Name, energy type and unit are required.</value>
|
||||
</data>
|
||||
<data name="Meters_RetireSwapHint" xml:space="preserve">
|
||||
<value>Was the physical meter replaced? Record a meter swap instead: history, sources, flow links and cost categories stay on this meter, without a gap. Retire a meter only when it is gone for good.</value>
|
||||
</data>
|
||||
<data name="Meters_RoleNone" xml:space="preserve">
|
||||
<value>— none —</value>
|
||||
</data>
|
||||
<data name="Meters_SearchPlaceholder" xml:space="preserve">
|
||||
<value>Search by name, serial number, location or type</value>
|
||||
</data>
|
||||
<data name="Meters_SerialNumber" xml:space="preserve">
|
||||
<value>Serial number (optional)</value>
|
||||
</data>
|
||||
<data name="Meters_Sources" xml:space="preserve">
|
||||
<value>Sources</value>
|
||||
</data>
|
||||
<data name="Meters_TankCapacity" xml:space="preserve">
|
||||
<value>Capacity</value>
|
||||
</data>
|
||||
<data name="Meters_TankCapacityRequired" xml:space="preserve">
|
||||
<value>Enter the tank's capacity to set up its tank.</value>
|
||||
</data>
|
||||
<data name="Meters_TankFixedRate" xml:space="preserve">
|
||||
<value>Fixed rate ({0}/h)</value>
|
||||
</data>
|
||||
<data name="Meters_TankHelp" xml:space="preserve">
|
||||
<value>Capacity sets the fill level; the calibration turns dipstick centimetres into volume. Levels and deliveries are then recorded on the meter's Events tab.</value>
|
||||
</data>
|
||||
<data name="Meters_TankOffset" xml:space="preserve">
|
||||
<value>Offset ({0})</value>
|
||||
</data>
|
||||
<data name="Meters_TankRateMode" xml:space="preserve">
|
||||
<value>Burner rate</value>
|
||||
</data>
|
||||
<data name="Meters_TankSection" xml:space="preserve">
|
||||
<value>Tank</value>
|
||||
</data>
|
||||
<data name="Meters_TankVolumePerCm" xml:space="preserve">
|
||||
<value>{0} per cm</value>
|
||||
</data>
|
||||
<data name="Meters_TankVolumePerCmHelp" xml:space="preserve">
|
||||
<value>e.g. 7000 L ÷ 150 cm = 46.67. Leave empty if levels are entered as volume.</value>
|
||||
</data>
|
||||
<data name="Meters_UpstreamHelp" xml:space="preserve">
|
||||
<value>This meter measures a subsection of the selected meter(s)' flow.</value>
|
||||
</data>
|
||||
@@ -1371,6 +1740,12 @@
|
||||
<data name="Nav_Overview" xml:space="preserve">
|
||||
<value>Overview</value>
|
||||
</data>
|
||||
<data name="Nav_SectionData" xml:space="preserve">
|
||||
<value>Meters & data</value>
|
||||
</data>
|
||||
<data name="Nav_SectionEnergy" xml:space="preserve">
|
||||
<value>Energy</value>
|
||||
</data>
|
||||
<data name="Nav_Settings" xml:space="preserve">
|
||||
<value>Settings</value>
|
||||
</data>
|
||||
@@ -1506,8 +1881,11 @@
|
||||
<data name="Solar_NoGenerationLead" xml:space="preserve">
|
||||
<value>No generation meters found. Add a meter with mode</value>
|
||||
</data>
|
||||
<data name="Solar_NoGenerationOrImport" xml:space="preserve">
|
||||
<value>, or load the reference data from</value>
|
||||
</data>
|
||||
<data name="Solar_NoGenerationTail" xml:space="preserve">
|
||||
<value>or load the reference data from</value>
|
||||
<value>in</value>
|
||||
</data>
|
||||
<data name="Solar_Savings" xml:space="preserve">
|
||||
<value>Savings (Ersparnis)</value>
|
||||
@@ -1519,13 +1897,13 @@
|
||||
<value>{0}% of generation</value>
|
||||
</data>
|
||||
<data name="Solar_TagMetersLead" xml:space="preserve">
|
||||
<value>Tag a meter</value>
|
||||
<value>Set the PV role of your house meter to</value>
|
||||
</data>
|
||||
<data name="Solar_TagMetersMid" xml:space="preserve">
|
||||
<value>and one</value>
|
||||
<value>and of your grid meter to</value>
|
||||
</data>
|
||||
<data name="Solar_TagMetersTail" xml:space="preserve">
|
||||
<value>(in meter metadata) to unlock self-consumption, autarky and savings.</value>
|
||||
<value> to unlock self-consumption, autarky and savings — in the meter editor under</value>
|
||||
</data>
|
||||
<data name="Tariffs_AddTariff" xml:space="preserve">
|
||||
<value>Add tariff</value>
|
||||
|
||||
Reference in New Issue
Block a user