The "Global Pricing Reference" is no longer a single file at
storage/reference/global_pricing.json. Pricing references are now
first-class DB rows (PricingReference model), uploadable as a library
in the Campaigns tab and selectable per-run alongside the campaign
presentation dropdown on the HM QC and Video QC configure pages.
New:
- core/models/pricing_reference.py — PricingReference model: id, name,
pdf_filename, pdf_path, parsed_content, parsed_data_json, status,
created_at/by. get_lookup() deserializes parsed_data_json; to_dict()
powers the dropdown API.
- /campaigns/pricing/upload — creates a PricingReference row, saves PDF
under storage/pricing_references/<id>/, kicks off background parse.
- /campaigns/pricing/<id> DELETE, /campaigns/api/pricing/list,
/campaigns/api/pricing/status/<id>.
- Campaigns index: "Pricing References" table card (mirrors the
presentations card) + upload form with optional name field.
Changed:
- pricing_parser: parse_pricing_pdf_to_dict returns (dict, raw_text);
new parse_pricing_reference(id) runs the parse against a DB row and
sets status to ready/error. Legacy file-based path removed.
- QCExecutor and VideoQCExecutor accept pricing_reference_id; load the
row into context['pricing_reference']={id, name, lookup}.
- BatchQCExecutor and BatchVideoQCExecutor thread pricing_reference_id
through to per-file executors.
- price_currency_check._validate_currency reads context instead of the
disk file; returns 'skipped_no_reference' if no ref attached.
- HM QC + Video QC /execute and /execute/batch routes pass
pricing_reference_id from the JSON payload.
- Configure templates for HM QC and Video QC add a second dropdown
"Pricing Reference (Optional)" loaded from /campaigns/api/pricing/list.
Backwards compatibility:
- app.py: on startup, if storage/reference/global_pricing.json exists
and the pricing_references table is empty, import it as a
"Default (legacy global)" PricingReference row so existing installs
keep a valid reference attached (user can pick it at configure time).
- config.py: retains GLOBAL_PRICING_{PDF,JSON}_PATH for the legacy
importer; adds PRICING_REF_STORAGE_PATH for the new per-row storage.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
74 lines
2.7 KiB
Python
74 lines
2.7 KiB
Python
"""Application configuration."""
|
|
import os
|
|
from dotenv import load_dotenv
|
|
|
|
# Load environment variables
|
|
load_dotenv()
|
|
|
|
class Config:
|
|
"""Base configuration."""
|
|
|
|
# Flask
|
|
SECRET_KEY = os.environ.get('SECRET_KEY', 'dev-secret-key-change-in-production')
|
|
|
|
# Authentication (simple username:password_hash pairs, comma-separated)
|
|
AUTH_USERS = os.environ.get('AUTH_USERS', '')
|
|
|
|
# Box
|
|
BOX_CONFIG_PATH = os.environ.get('BOX_CONFIG_PATH', 'config/box_config.json')
|
|
BOX_REPORT_FOLDER_ID = os.environ.get('BOX_REPORT_FOLDER_ID', '133295752718')
|
|
BOX_CAMPAIGNS_FOLDER_ID = os.environ.get('BOX_CAMPAIGNS_FOLDER_ID', '156182880490')
|
|
|
|
# Server
|
|
HOST = os.environ.get('HOST', '0.0.0.0')
|
|
PORT = int(os.environ.get('PORT', 5000))
|
|
|
|
# Database
|
|
# Use absolute path for SQLite database
|
|
BASE_DIR = os.path.abspath(os.path.dirname(__file__))
|
|
SQLALCHEMY_DATABASE_URI = os.environ.get(
|
|
'DATABASE_URI',
|
|
f'sqlite:///{os.path.join(BASE_DIR, "database", "qc_platform.db")}'
|
|
)
|
|
SQLALCHEMY_TRACK_MODIFICATIONS = False
|
|
SQLALCHEMY_ECHO = os.environ.get('FLASK_ENV') == 'development'
|
|
|
|
# Session Configuration
|
|
SESSION_COOKIE_SECURE = os.environ.get('FLASK_ENV') == 'production'
|
|
SESSION_COOKIE_HTTPONLY = True
|
|
SESSION_COOKIE_SAMESITE = 'Lax'
|
|
SESSION_COOKIE_PATH = os.environ.get('SESSION_COOKIE_PATH', '/')
|
|
|
|
# File Upload Configuration
|
|
MAX_CONTENT_LENGTH = 500 * 1024 * 1024 # 500 MB max file size
|
|
UPLOAD_FOLDER = 'uploads'
|
|
STORAGE_FOLDER = 'storage'
|
|
|
|
# Module-specific paths
|
|
HM_QC_UPLOAD_PATH = 'uploads/hm_qc'
|
|
VIDEO_QC_UPLOAD_PATH = 'uploads/video_qc'
|
|
VIDEO_MASTER_UPLOAD_PATH = 'uploads/video_master'
|
|
|
|
HM_QC_STORAGE_PATH = 'storage/reports/hm_qc'
|
|
CONSOLIDATED_STORAGE_PATH = 'storage/reports/consolidated'
|
|
|
|
# Campaign presentation storage
|
|
CAMPAIGN_STORAGE_PATH = 'storage/campaigns'
|
|
|
|
# Pricing reference storage (per-row, selectable at configure time)
|
|
PRICING_REF_STORAGE_PATH = 'storage/pricing_references'
|
|
|
|
# Legacy global pricing (auto-imported into PricingReference on first run)
|
|
GLOBAL_PRICING_PDF_PATH = 'storage/reference/global_pricing.pdf'
|
|
GLOBAL_PRICING_JSON_PATH = 'storage/reference/global_pricing.json'
|
|
|
|
# LLM Configuration
|
|
OPENAI_API_KEY = os.environ.get('OPENAI_API_KEY')
|
|
ANTHROPIC_API_KEY = os.environ.get('ANTHROPIC_API_KEY')
|
|
AZURE_OPENAI_API_KEY = os.environ.get('AZURE_OPENAI_API_KEY')
|
|
AZURE_OPENAI_ENDPOINT = os.environ.get('AZURE_OPENAI_ENDPOINT')
|
|
GOOGLE_API_KEY = os.environ.get('GOOGLE_API_KEY')
|
|
|
|
# Application
|
|
MAX_REPORTS_PER_JOB = 100 # Limit reports fetched per job number
|
|
CACHE_TIMEOUT = 300 # 5 minutes cache for Box searches
|