Timeseries Processing V2
This directory contains the new implementation of timeseries CSV processing with dynamic frequency detection and LPS tag support.
Overview
The v2 implementation provides:
- Dynamic frequency detection: Determines reading frequency from actual data patterns
- Mixed-frequency support: Handles devices that change frequency over time
- Dual tag creation: Creates both consumption (cubic feet) and demand (liters/second) tags
- Pandas-based processing: Efficient data analysis and manipulation
- Comprehensive reporting: Detailed analysis and summary reports
Scripts
1. analyze_frequency_patterns.py
Purpose: Read-only analysis script to profile timeseries data patterns.
Usage:
python v2/analyze_frequency_patterns.py <csv_file> [--output <report_file>]
Examples:
# Print report to console
python v2/analyze_frequency_patterns.py data/timeseries.csv
# Save report to file
python v2/analyze_frequency_patterns.py data/timeseries.csv --output analysis_report.txt
Output:
- Frequency distribution statistics
- Devices with mixed frequencies
- Anomalies (unexpected period counts)
- Recommended tag counts
- Data quality insights
2. parse_timeseries_csv_v2.py
Purpose: Main processing script that creates DeviceTags and DeviceTagDatum records.
Usage:
python v2/parse_timeseries_csv_v2.py --csv <csv_file> [options]
Options:
--csv: Path to timeseries CSV file (required)--dry-run: Run analysis without database writes--no-lps: Skip LPS tag creation (consumption tags only)
Examples:
# Dry run (no database writes)
python v2/parse_timeseries_csv_v2.py --csv data/timeseries.csv --dry-run
# Production run (creates all tags and data)
python v2/parse_timeseries_csv_v2.py --csv data/timeseries.csv
# Create only consumption tags (skip LPS)
python v2/parse_timeseries_csv_v2.py --csv data/timeseries.csv --no-lps
Workflow
Step 1: Analysis (Recommended)
Before processing, run the analysis script to understand your data:
python v2/analyze_frequency_patterns.py data/timeseries.csv --output analysis_report.txt
Review the report to:
- Verify frequency distribution matches expectations
- Identify devices with mixed frequencies
- Check for data quality issues
- Estimate number of tags that will be created
Step 2: Dry Run
Test the processing logic without database writes:
python v2/parse_timeseries_csv_v2.py --csv data/timeseries.csv --dry-run
Review the output to:
- Confirm tag names are correct
- Verify record counts
- Check for any errors or warnings
Step 3: Production Run
Once satisfied with analysis and dry run:
python v2/parse_timeseries_csv_v2.py --csv data/timeseries.csv
Monitor the logs for:
- Progress updates
- Any warnings or errors
- Final summary report
Configuration
Database connection settings are in parse_timeseries_csv_v2.py:
DB_HOST = "localhost"
DB_PORT = 5432
DB_NAME = "dnv_10_7_2025"
DB_USER = "postgres"
DB_PASSWORD = "gq010102"
Update these values for your environment before running.
Frequency Classification
The script automatically classifies frequencies based on periods per year:
| Periods/Year | Frequency Type | Tag Suffix |
|---|---|---|
| 1 | annual | _annual_consumption / _annual_demand_lps |
| 2 | semiannual | _semiannual_consumption / _semiannual_demand_lps |
| 4 | quarterly | _quarterly_consumption / _quarterly_demand_lps |
| 12 | monthly | _monthly_consumption / _monthly_demand_lps |
Tag Naming Convention
Tags follow this pattern:
Consumption tags:
{DeviceName}_{frequency}_consumption- Example:
WTRMN00345_quarterly_consumption - Unit: cubic feet
- Example:
LPS tags:
{DeviceName}_{frequency}_demand_lps- Example:
WTRMN00345_quarterly_demand_lps - Unit: liters/second
- Example:
LPS Conversion
Cubic feet consumption is converted to average liters per second (LPS) based on the time period:
Formula:
LPS = (consumption_cuft × 28.3168) / period_seconds
Period Seconds:
- Monthly: 2,629,440 seconds (30.44 days average)
- Quarterly: 7,888,320 seconds (3 months)
- Semi-annual: 15,776,640 seconds (6 months)
- Annual: 31,557,600 seconds (365.25 days)
Example:
Quarterly consumption: 1000 cubic feet
Liters: 1000 × 28.3168 = 28,316.8 L
LPS: 28,316.8 / 7,888,320 = 0.003589 L/s
CSV Requirements
The input CSV must contain these columns:
REG_SERIAL: Device serial number (joins to Devices table)READING_YEAR: Year of readingREADING_PERIOD: Period number (1-4 for quarterly, 1-12 for monthly, etc.)CONSUMPTION: Consumption value in cubic feet
Optional columns (for reference/debugging):
ROUTE_CATEGORYBILLING_CYCLEACCOUNT
Database Schema
DeviceTags Table
CREATE TABLE "DeviceTags" (
"Id" SERIAL PRIMARY KEY,
"Name" VARCHAR UNIQUE,
"TagType" VARCHAR,
"Unit" VARCHAR,
"DeviceId" INTEGER REFERENCES "Devices"("Id")
);
DeviceTagData Table
CREATE TABLE "DeviceTagData" (
"DeviceTagId" INTEGER REFERENCES "DeviceTags"("Id"),
"UnixTime" BIGINT,
"Value" DOUBLE PRECISION,
PRIMARY KEY ("DeviceTagId", "UnixTime")
);
Logs
Both scripts create log files in the v2 directory:
analyze_frequency_patterns.logparse_timeseries_csv_v2.log
Logs include:
- Timestamps for all operations
- Progress updates
- Warnings and errors
- Summary statistics
Troubleshooting
"Missing required columns" error
Cause: CSV doesn't have required columns Solution: Verify CSV has REG_SERIAL, READING_YEAR, READING_PERIOD, CONSUMPTION
"Devices not found in database" warning
Cause: Some REG_SERIAL values don't exist in Devices table Solution: Review missing serials in log, ensure devices are loaded first
"Unexpected period count" warning
Cause: Device has unusual number of readings per year (e.g., 3, 5-11, >12) Solution: Review anomalies in analysis report, may indicate data quality issues
Performance issues with large datasets
Cause: Processing millions of records Solution:
- Increase BATCH_SIZE in script (default: 10,000)
- Run during off-peak hours
- Consider processing in chunks by year
Validation Queries
After processing, validate results with these SQL queries:
Check tag counts
-- Should be 1:1 ratio between consumption and LPS tags
SELECT
COUNT(CASE WHEN "TagType" LIKE '%_consumption' THEN 1 END) as consumption_tags,
COUNT(CASE WHEN "TagType" LIKE '%_demand_lps' THEN 1 END) as lps_tags
FROM "DeviceTags";
Check datum counts per tag type
SELECT
dt."TagType",
COUNT(*) as datum_count
FROM "DeviceTagData" dtd
JOIN "DeviceTags" dt ON dtd."DeviceTagId" = dt."Id"
GROUP BY dt."TagType"
ORDER BY dt."TagType";
Verify consumption/LPS pairs have same timestamps
SELECT
d."Name",
dt_cons."Name" as consumption_tag,
dt_lps."Name" as lps_tag,
dtd_cons."UnixTime",
dtd_cons."Value" as cuft,
dtd_lps."Value" as lps
FROM "Devices" d
JOIN "DeviceTags" dt_cons ON d."Id" = dt_cons."DeviceId"
AND dt_cons."TagType" LIKE '%_consumption'
JOIN "DeviceTags" dt_lps ON d."Id" = dt_lps."DeviceId"
AND dt_lps."TagType" LIKE '%_demand_lps'
AND REPLACE(dt_cons."TagType", '_consumption', '') = REPLACE(dt_lps."TagType", '_demand_lps', '')
JOIN "DeviceTagData" dtd_cons ON dt_cons."Id" = dtd_cons."DeviceTagId"
JOIN "DeviceTagData" dtd_lps ON dt_lps."Id" = dtd_lps."DeviceTagId"
AND dtd_cons."UnixTime" = dtd_lps."UnixTime"
WHERE d."Name" = 'WTRMN00345'
ORDER BY dtd_cons."UnixTime";
Comparison with V1
| Feature | V1 | V2 |
|---|---|---|
| Frequency detection | Metadata (ROUTE_CATEGORY) | Actual data patterns |
| Mixed frequencies | Not supported | Fully supported |
| Tags per device | 1 | 1-4 (as needed) |
| LPS tags | No | Yes |
| Data processing | Row-by-row | Pandas bulk operations |
| Analysis tools | Limited | Comprehensive |
Dependencies
Required Python packages:
pandas>=2.0.0
psycopg2-binary>=2.9.0
Install with:
pip install pandas psycopg2-binary
Support
For issues or questions:
- Review logs for detailed error messages
- Check validation queries to verify data integrity
- Compare with v1 implementation in
../v1/directory - Review documentation in parent directory
References
- Design documentation: Timeseries v2 Design Specification
- Implementation plan: Implementation Plan
- Legacy scripts:
../v1/directory