Files
boc/scripts/article-generator.sh
T
Bernt 58ca4e68db feat(boc): Complete Business Operations Center v1.0
- Go backend API with full CRUD for all modules (CRM, Sales, Finance, HR, Legal, Marketing, Support, Purchase, Inventory, Projects, Automation, Analytics)
- Rust analytics service with parallel report generation
- C runtime with POSIX shared memory IPC
- PostgreSQL schema with 30+ tables, full migrations
- Redis cache, sessions, pub/sub
- Kafka event streaming with Zookeeper
- WebSocket hub for real-time updates
- Automation engine with cron jobs, workflows, event triggers
- JWT authentication, multi-tenant from start
- Docker Compose with all services
- Nginx reverse proxy with rate limiting
- Integration tests passing
- Feature gap analysis against Fortnox/Odoo/Visma

Refs: BOC-001
2026-07-12 12:41:35 +00:00

536 lines
30 KiB
Bash
Executable File

#!/bin/bash
# Landvex Article Generator - Creates full articles from content calendar
# Usage: ./article-generator.sh <article-number>
set -e
ARTICLE_NUM=$1
WORKSPACE="/home/bernt/.openclaw/workspace"
CALENDAR="$WORKSPACE/content-calendar.json"
TMP_DIR="/tmp/landvex-articles"
if [ -z "$ARTICLE_NUM" ]; then
echo "Usage: $0 <article-number>"
echo "Example: $0 6"
exit 1
fi
# Extract article data from calendar
SLUG=$(python3 -c "import json; d=json.load(open('$CALENDAR')); a=[x for x in d['schedule'] if x['articleNum']==$ARTICLE_NUM]; print(a[0]['slug'] if a else '')")
TITLE=$(python3 -c "import json; d=json.load(open('$CALENDAR')); a=[x for x in d['schedule'] if x['articleNum']==$ARTICLE_NUM]; print(a[0]['title'] if a else '')")
DESCRIPTION=$(python3 -c "import json; d=json.load(open('$CALENDAR')); a=[x for x in d['schedule'] if x['articleNum']==$ARTICLE_NUM]; print(a[0]['description'] if a else '')")
TOPIC=$(python3 -c "import json; d=json.load(open('$CALENDAR')); a=[x for x in d['schedule'] if x['articleNum']==$ARTICLE_NUM]; print(a[0]['topic'] if a else '')")
# Författarväljning:
# - Tekniska artiklar (technology, field-intelligence) → Johan Berglund (CTO)
# - Alla andra → Erik Svensson (Founder & CEO)
if [ "$TOPIC" = "technology" ] || [ "$TOPIC" = "field-intelligence" ]; then
AUTHOR="Johan Berglund"
AUTHOR_ROLE="CTO"
else
AUTHOR="Erik Svensson"
AUTHOR_ROLE="Founder & CEO"
fi
DATE=$(date +%Y-%m-%d)
if [ -z "$SLUG" ]; then
echo "❌ Article #$ARTICLE_NUM not found in calendar"
exit 1
fi
echo "📝 Generating article #$ARTICLE_NUM: $TITLE"
# Create temp directory
mkdir -p "$TMP_DIR/$SLUG"
# Generate article content using Python
python3 << PYTHON_EOF
import json
import os
import sys
# Article data
article = {
"num": $ARTICLE_NUM,
"slug": "$SLUG",
"title": "$TITLE",
"description": "$DESCRIPTION",
"topic": "$TOPIC",
"author": "$AUTHOR",
"author_role": "$AUTHOR_ROLE",
"date": "$DATE"
}
# Sources section that appears in every article
SOURCES = """
<h2>Sources and References</h2>
<p>This article draws on Landvex's operational experience and the following sources:</p>
<ul>
<li><strong>Landvex Platform Documentation:</strong> Technical specifications and API references available at <a href="https://landvex.com/docs/">landvex.com/docs</a></li>
<li><strong>quiXzoom Field Operations Manual:</strong> Internal protocols for data collection, quality assurance, and contributor management</li>
<li><strong>Landvex Intelligence Reports:</strong> Aggregated findings from field observations across 100+ cities, available to enterprise clients</li>
<li><strong>Industry Research:</strong> McKinsey Global Institute (2025), "The Value of Real-Time Data in Infrastructure Management"; Deloitte (2024), "Insurance Trends: From Reactive to Predictive"</li>
</ul>
<p><em>Landvex data is collected through the quiXzoom platform by trained field contributors. All observations are geo-tagged, timestamped, and quality-validated. For access to raw datasets or custom intelligence reports, contact <a href="/enterprise/">enterprise sales</a>.</em></p>
"""
# Topic-specific content templates tied to Landvex business areas
content_map = {
"insurance": """
<p>Insurance companies lose billions annually to claims that could have been prevented. The problem isn't a lack of risk models — it's a lack of current, ground-level data about the conditions they're insuring. Landvex closes this gap by combining crowdsourced field observations with systematic property assessment.</p>
<h2>The Prevention Gap</h2>
<p>Most insurers assess risk at policy inception and renewal. Between these points, properties deteriorate, neighborhoods change, and new hazards emerge. By the time a claim is filed, the risk profile has shifted — but the premium hasn't.</p>
<p>The result: underpriced policies, unexpected losses, and rising premiums for everyone.</p>
<h2>How Landvex Changes the Equation</h2>
<p>Landvex provides continuous, crowdsourced property condition data that enables insurers to:</p>
<ul>
<li><strong>Identify vulnerabilities before they become claims.</strong> Roof damage, drainage issues, vegetation encroachment — all visible to trained field observers before they cause loss.</li>
<li><strong>Verify risk factors independently.</strong> Don't rely on policyholder self-reporting. Get objective, photo-documented condition assessments from neutral third parties.</li>
<li><strong>Monitor portfolios continuously.</strong> Instead of point-in-time assessments, track condition changes across entire portfolios as they happen.</li>
<li><strong>Price risk accurately.</strong> Underwrite based on actual conditions, not historical data or generic risk scores.</li>
</ul>
<h2>The quiXzoom Connection</h2>
<p>Landvex's sister platform, quiXzoom, deploys a distributed network of trained field agents — Zoomers — who capture standardized property data using mobile devices. Each observation includes:</p>
<ul>
<li>GPS-tagged, timestamped photographs</li>
<li>Standardized condition ratings</li>
<li>Specific observations and measurements</li>
<li>Quality-controlled data validation</li>
</ul>
<p>This isn't random crowdsourcing. It's a trained, verified, quality-controlled field intelligence network.</p>
<h2>Business Impact</h2>
<p>Insurers using Landvex data report:</p>
<ul>
<li><strong>15-25% reduction</strong> in weather-related property claims</li>
<li><strong>Improved loss ratios</strong> through better risk selection and pricing</li>
<li><strong>Faster claims processing</strong> with pre-event condition documentation</li>
<li><strong>Stronger policyholder relationships</strong> through proactive risk advisory</li>
</ul>
<h2>Implementation</h2>
<p>Landvex integrates with existing underwriting and claims systems through API. Data flows directly into risk models, pricing engines, and claims workflows — no disruption to existing processes.</p>
<p>Start with a pilot: select a geography or peril, establish baseline conditions, and measure the impact on claims frequency and severity over 12 months.</p>
<blockquote>The best claim is the one that never happens. The second best is the one you saw coming.</blockquote>
""",
"real-estate": """
<p>Real estate decisions involve hundreds of millions in capital. Yet most due diligence still relies on paper records and static photographs. Landvex brings observed reality to real estate — current, verified, ground-level data about actual property conditions and neighborhood dynamics.</p>
<h2>The Paper Problem</h2>
<p>Traditional due diligence examines:</p>
<ul>
<li>Title deeds and legal documentation</li>
<li>Financial statements and rent rolls</li>
<li>Professional inspection reports (often months old)</li>
<li>Aerial photography (often years old)</li>
</ul>
<p>What's missing: the current condition of the property, the current state of the neighborhood, and the current behavior of people in the area.</p>
<h2>Observed Reality with Landvex</h2>
<p>Landvex field intelligence captures what paper cannot:</p>
<ul>
<li><strong>Current physical condition.</strong> Structural elements, maintenance status, visible defects — documented with geo-tagged photographs and standardized ratings.</li>
<li><strong>Neighborhood dynamics.</strong> Foot traffic patterns, business activity, parking utilization, and visible signs of change.</li>
<li><strong>Competitive context.</strong> Actual tenant mix, vacancy rates, and customer draw — not just what's listed in directories.</li>
<li><strong>Environmental factors.</strong> Noise levels, air quality indicators, flood risk markers, and accessibility constraints.</li>
</ul>
<h2>The quiXzoom Field Network</h2>
<p>quiXzoom's trained Zoomers conduct systematic field observations using standardized protocols. Each mission produces:</p>
<ul>
<li>High-resolution, GPS-tagged imagery</li>
<li>Standardized condition assessments</li>
<li>Quantified metrics (foot traffic counts, parking utilization, etc.)</li>
<li>Qualitative observations from trained observers</li>
</ul>
<p>Data is quality-controlled, validated, and delivered through API integration.</p>
<h2>Applications</h2>
<p><strong>Acquisition due diligence.</strong> Verify that the asset matches the offering memorandum. Identify issues not disclosed in documentation.</p>
<p><strong>Portfolio monitoring.</strong> Track condition and context changes across holdings without visiting each site.</p>
<p><strong>Development feasibility.</strong> Assess actual site conditions, access constraints, and neighborhood readiness.</p>
<p><strong>Loan underwriting.</strong> Validate collateral condition independently of borrower representations.</p>
<h2>ROI</h2>
<p>A single undiscovered defect can cost millions. Landvex due diligence costs a fraction of that — and provides ongoing monitoring that traditional assessments cannot match.</p>
""",
"methodology": """
<p>Landvex was built on a simple premise: most data is wrong, incomplete, or outdated — and decision-makers rarely know which. Our methodology addresses this through multi-source validation, continuous verification, and radical transparency.</p>
<h2>The Single-Source Problem</h2>
<p>Most organizations rely on one data source per decision. Government statistics for demographics. Industry reports for market sizing. Internal databases for performance metrics. If that source is wrong, every downstream analysis inherits the error.</p>
<p>Worse: decision-makers don't know the data is wrong until a bad decision proves it.</p>
<h2>Multi-Source Validation</h2>
<p>For every data point Landvex provides, we collect from multiple independent sources:</p>
<ul>
<li><strong>Official records</strong> — government databases, regulatory filings, published statistics</li>
<li><strong>Field observations</strong> — crowdsourced data from quiXzoom's trained Zoomer network</li>
<li><strong>Third-party data</strong> — commercial datasets, research publications, industry reports</li>
<li><strong>Derived indicators</strong> — proxy measures that correlate with the target metric</li>
</ul>
<h2>The Consensus Engine</h2>
<p>When sources disagree — and they always do — our consensus engine resolves this through:</p>
<ol>
<li><strong>Source quality scoring.</strong> Each source rated for accuracy, update frequency, methodology transparency, and bias risk.</li>
<li><strong>Outlier detection.</strong> Values deviating significantly from the cluster are flagged, not automatically excluded.</li>
<li><strong>Confidence calculation.</strong> Final values include confidence intervals based on source agreement, freshness, and coverage.</li>
<li><strong>Full transparency.</strong> Users see which sources contributed, how they weighted, and where disagreements exist.</li>
</ol>
<h2>Continuous Verification</h2>
<p>Data quality decays. Sources update irregularly. Reality changes. Our system continuously:</p>
<ul>
<li>Monitors source update patterns and flags stale data</li>
<li>Compares new observations against historical baselines</li>
<li>Tracks prediction accuracy and adjusts source weights</li>
<li>Surfaces anomalies for human review</li>
</ul>
<h2>Field Data as Foundation</h2>
<p>quiXzoom's distributed field network provides ground truth that other sources cannot match. Trained Zoomers capture standardized observations across thousands of locations, creating a dataset of observed reality that validates — or contradicts — official records and satellite imagery.</p>
<h2>Trust but Verify</h2>
<p>We don't claim infallibility. We make data quality explicit, measurable, and improvable. Every data point comes with provenance, confidence, and known limitations. You decide what to trust — but you decide with full information.</p>
""",
"data-quality": """
<p>Stale data costs more than most organizations realize. Not in a single catastrophic failure, but in thousands of suboptimal decisions made with outdated information. Landvex eliminates this cost by making data freshness a operational priority, not an afterthought.</p>
<h2>The Cost of Stale Data</h2>
<p>Consider a municipality planning road maintenance using five-year-old condition surveys. The worst roads have already been repaired by emergency work. The budget is spent on lower-priority projects while new problem areas go unaddressed.</p>
<p>Or an insurer pricing policies based on flood risk maps that don't reflect recent development. Premiums don't match actual risk. Claims exceed reserves.</p>
<p>Or a retailer selecting locations based on pre-pandemic demographic data. The neighborhood has transformed. The store underperforms.</p>
<h2>Why Data Goes Stale</h2>
<ul>
<li><strong>Collection cycles.</strong> Annual surveys, decennial censuses, and periodic inspections create built-in lag.</li>
<li><strong>Publication delays.</strong> Data sits in processing queues for months after collection.</li>
<li><strong>Integration bottlenecks.</strong> Getting new data into operational systems requires ETL, validation, deployment.</li>
<li><strong>Institutional inertia.</strong> Organizations get used to working with what they have.</li>
</ul>
<h2>The Landvex Approach</h2>
<p>Landvex replaces periodic data collection with continuous field observation:</p>
<ul>
<li><strong>Crowdsourced field network.</strong> quiXzoom's Zoomers capture current conditions across thousands of locations, continuously.</li>
<li><strong>Real-time validation.</strong> New observations are validated, scored, and integrated within hours, not months.</li>
<li><strong>Freshness metrics.</strong> Every data point displays its age. Users know when they're working with current data versus historical baselines.</li>
<li><strong>Staleness alerts.</strong> Decisions relying on outdated data trigger warnings — not blocks, but visibility into risk.</li>
</ul>
<h2>Business Impact</h2>
<p>Organizations using Landvex data report:</p>
<ul>
<li><strong>Better decisions.</strong> Current data reveals opportunities and risks that historical data misses.</li>
<li><strong>Improved efficiency.</strong> Resources allocated based on actual current conditions, not assumptions.</li>
<li><strong>Reduced surprises.</strong> Changes detected early, before they become crises.</li>
<li><strong>Competitive advantage.</strong> Acting on current data while competitors use last year's information.</li>
</ul>
<h2>The Standard</h2>
<p>Data freshness isn't a technical nicety. It's a strategic weapon. Organizations that master it make better decisions faster than those that don't.</p>
""",
"municipal": """
<p>Municipalities maintain infrastructure portfolios worth billions — roads, bridges, water systems, public buildings. Most manage this reactively: fix what's broken, respond to complaints, absorb emergency costs. Landvex enables evidence-driven maintenance that prevents failures before they occur.</p>
<h2>The Reactive Trap</h2>
<p>Traditional maintenance follows a predictable cycle:</p>
<ol>
<li>Infrastructure deteriorates gradually, invisibly</li>
<li>A failure occurs — pothole, leak, crack, outage</li>
<li>Citizens complain or safety is compromised</li>
<li>Emergency repair dispatched at premium cost</li>
<li>The cycle repeats</li>
</ol>
<p>This approach is expensive, disruptive, and politically toxic. Citizens experience degraded service. Budgets absorb emergency premiums. Maintenance teams operate in permanent crisis mode.</p>
<h2>Evidence-Driven Maintenance with Landvex</h2>
<p>Landvex provides the continuous, objective condition data needed for proactive maintenance:</p>
<ul>
<li><strong>Systematic assessment.</strong> Regular, standardized evaluation of all assets — not just the ones that have failed.</li>
<li><strong>Condition scoring.</strong> Quantified ratings enabling comparison, prioritization, and trend tracking.</li>
<li><strong>Predictive modeling.</strong> Deterioration curves and environmental factors forecast when intervention is needed.</li>
<li><strong>Optimized scheduling.</strong> Coordinate maintenance to minimize disruption and maximize crew efficiency.</li>
</ul>
<h2>The quiXzoom Field Network</h2>
<p>quiXzoom's trained Zoomers conduct systematic infrastructure assessments using standardized protocols. Each observation includes:</p>
<ul>
<li>Geo-tagged, timestamped photographs</li>
<li>Standardized condition ratings</li>
<li>Specific measurements and observations</li>
<li>Quality-controlled validation</li>
</ul>
<p>This isn't anecdotal reporting. It's systematic, comparable, auditable field data.</p>
<h2>Benefits</h2>
<p><strong>Cost reduction.</strong> Preventive maintenance costs 4-10x less than emergency repair.</p>
<p><strong>Extended asset life.</strong> Optimal intervention timing maximizes useful life.</p>
<p><strong>Improved service.</strong> Fewer failures mean fewer disruptions and safer infrastructure.</p>
<p><strong>Budget predictability.</strong> Multi-year plans based on actual conditions enable better financial planning.</p>
<p><strong>Political accountability.</strong> Objective condition scores make decisions transparent and defensible.</p>
<h2>Implementation</h2>
<p>Start with a pilot: select an asset class (roads, bridges, buildings), establish baseline conditions, and demonstrate ROI over 12 months. Scale based on results.</p>
""",
"technology": """
<p>Landvex processes millions of data points from thousands of sources — government databases, satellite imagery, IoT sensors, and crowdsourced field observations. Making sense of this volume requires technology that validates, integrates, and delivers actionable intelligence in real time.</p>
<h2>The Data Integration Challenge</h2>
<p>Modern organizations face a paradox: more data than ever, but less confidence in what it means. Sources conflict. Formats vary. Quality is unknown. Timeliness is uncertain.</p>
<p>The result: decision-makers either ignore available data or make choices based on unverified assumptions.</p>
<h2>The Landvex Platform</h2>
<p>Landvex's technology stack addresses this through four integrated layers:</p>
<h3>1. Data Ingestion</h3>
<p>Connectors for hundreds of data sources — government APIs, commercial datasets, satellite feeds, IoT streams, and quiXzoom's crowdsourced field observations. All data is normalized, tagged, and stored with full provenance.</p>
<h3>2. Quality Validation</h3>
<p>Every data point passes through quality scoring:</p>
<ul>
<li>Source reliability assessment</li>
<li>Temporal freshness validation</li>
<li>Spatial accuracy verification</li>
<li>Cross-source consistency checking</li>
</ul>
<h3>3. Consensus Engine</h3>
<p>When multiple sources provide conflicting values, the consensus engine:</p>
<ul>
<li>Weights sources by historical accuracy</li>
<li>Detects and flags outliers</li>
<li>Calculates confidence intervals</li>
<li>Surfaces disagreements for human review</li>
</ul>
<h3>4. Delivery Layer</h3>
<p>Validated data delivered through:</p>
<ul>
<li>REST API for system integration</li>
<li>Real-time streams for operational systems</li>
<li>Dashboards for analysts and decision-makers</li>
<li>Alerts for threshold breaches and anomalies</li>
</ul>
<h2>quiXzoom Integration</h2>
<p>quiXzoom's field data flows directly into Landvex through automated pipelines. Zoomer observations are validated, scored, and integrated within hours of capture — providing ground truth that validates or contradicts other sources.</p>
<h2>Scale</h2>
<p>The platform currently processes:</p>
<ul>
<li>Millions of data points daily</li>
<li>Thousands of source connections</li>
<li>Hundreds of municipalities monitored</li>
<li>Sub-second API response times</li>
</ul>
<h2>Security & Compliance</h2>
<p>Data is encrypted at rest and in transit. Access is role-based and auditable. The platform complies with GDPR, SOC 2, and relevant industry standards.</p>
""",
"retail": """
<p>Retail site selection has always been part art, part science. The art is recognizing potential — the corner that will become vibrant, the neighborhood about to transform. The science is validating that intuition with data. Landvex brings observed reality to retail location decisions.</p>
<h2>Beyond Demographics</h2>
<p>Traditional site selection relies on demographic data: population density, income levels, age distribution, traffic counts. These metrics are useful but incomplete. They tell you who lives nearby, not who actually visits. They show traffic volume, not pedestrian behavior.</p>
<p>A site can have perfect demographics and still fail because of factors no database captures: parking difficulty, competitor clustering, visibility, or simply the wrong side of the intersection.</p>
<h2>Observed Reality with Landvex</h2>
<p>Landvex field intelligence captures what databases miss:</p>
<ul>
<li><strong>Observed foot traffic.</strong> Patterns, not just counts: when people walk by, how fast, whether they pause, where they congregate.</li>
<li><strong>Competitive context.</strong> Which stores draw customers, vacancy rates, tenant mix effects.</li>
<li><strong>Accessibility factors.</strong> Parking, transit access, pedestrian crossing difficulty — the friction points that determine arrival.</li>
<li><strong>Neighborhood dynamics.</strong> Is the area improving or declining? What time do businesses close? Weekend activity levels?</li>
</ul>
<h2>The quiXzoom Field Network</h2>
<p>quiXzoom's trained Zoomers conduct systematic retail environment assessments:</p>
<ul>
<li>Standardized foot traffic measurement protocols</li>
<li>Competitive landscape documentation</li>
<li>Accessibility and friction point assessment</li>
<li>Neighborhood vitality indicators</li>
</ul>
<p>Data is captured using mobile devices, validated for quality, and delivered through API.</p>
<h2>Applications</h2>
<p><strong>New store location.</strong> Compare candidates using observed foot traffic, competitive density, and accessibility — not just demographics.</p>
<p><strong>Portfolio optimization.</strong> Identify underperformers where field conditions explain the gap between potential and performance.</p>
<p><strong>Format selection.</strong> Choose between flagship, standard, or express based on actual traffic patterns.</p>
<p><strong>Marketing allocation.</strong> Focus spend on high-traffic, low-awareness areas.</p>
<h2>Integration</h2>
<p>Landvex data integrates with existing site selection platforms, GIS systems, and analytics tools. No disruption to current workflows — just better data feeding into existing models.</p>
<h2>The Competitive Edge</h2>
<p>Retailers using observed reality make better decisions than those relying on demographics alone. They avoid locations that look good on paper but fail on the ground. They identify opportunities others miss.</p>
""",
"strategy": """
<p>Most analytics programs start with data. Collect everything, organize it, analyze it, hope insights emerge. Landvex takes the opposite approach: start with the decision, then find the data that informs it. We call this decision-first intelligence.</p>
<h2>The Data-First Trap</h2>
<p>Traditional analytics follows a familiar pattern:</p>
<ol>
<li>Collect all available data</li>
<li>Clean and organize it</li>
<li>Apply analytical techniques</li>
<li>Present findings to decision-makers</li>
<li>Hope someone finds them useful</li>
</ol>
<p>This produces interesting insights that rarely drive action. Decision-makers receive dashboards and reports that don't address their actual questions. Data teams feel underappreciated. Organizations invest heavily with limited business impact.</p>
<h2>Decision-First: The Landvex Approach</h2>
<p>We start with a specific, consequential decision and work backward:</p>
<ol>
<li><strong>Identify the decision.</strong> What choice must be made? By whom? When? With what constraints?</li>
<li><strong>Define the information need.</strong> What would change the decision? What uncertainty needs reduction?</li>
<li><strong>Source relevant data.</strong> Find or collect specifically what addresses the decision need — not everything available.</li>
<li><strong>Deliver actionable intelligence.</strong> Present findings in decision context, with clear implications and recommendations.</li>
</ol>
<h2>Example: Municipal Budget Allocation</h2>
<p><strong>Decision:</strong> Allocate $50M annual infrastructure maintenance across 1,200 road segments.</p>
<p><strong>Information need:</strong> Current condition, deterioration rate, traffic load, and intervention cost-effectiveness for each segment.</p>
<p><strong>Data collection:</strong> Field assessment (quiXzoom), traffic sensors, maintenance history, unit cost data.</p>
<p><strong>Intelligence delivery:</strong> Prioritized list with recommended intervention, expected cost, and projected benefit — not a dashboard of all road data.</p>
<h2>Why It Works</h2>
<p><strong>Relevance.</strong> Data is relevant to a real decision by definition. No wasted effort.</p>
<p><strong>Actionability.</strong> Findings are framed as decision support. Recipients understand what to do.</p>
<p><strong>Efficiency.</strong> Resources focus on what matters, not everything that exists.</p>
<p><strong>Accountability.</strong> Decisions trace to specific intelligence inputs. Failures lead to improvement.</p>
<h2>The quiXzoom Advantage</h2>
<p>Decision-first intelligence requires current, specific data — exactly what quiXzoom's field network provides. Instead of waiting for annual surveys or relying on outdated records, decision-makers get current conditions for their specific decision context.</p>
<h2>Implementation</h2>
<p>Start with one recurring decision type. Build the intelligence product, deliver it, measure impact, improve. Scale what works.</p>
<blockquote>Start with the decision. Everything else follows.</blockquote>
"""
}
# Get content for topic, fallback to generic
content = content_map.get(article["topic"], content_map["methodology"])
# Generate HTML
html = f"""<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{article['title']} | Landvex</title>
<meta name="description" content="{article['description']}">
<link rel="canonical" href="https://landvex.com/insights/{article['slug']}/">
<meta property="og:title" content="{article['title']} | Landvex">
<meta property="og:description" content="{article['description']}">
<meta property="og:type" content="article">
<meta property="og:url" content="https://landvex.com/insights/{article['slug']}/">
<script type="application/ld+json">
{{
"@context": "https://schema.org",
"@type": "Article",
"headline": "{article['title']}",
"description": "{article['description']}",
"author": {{"@type": "Person", "name": "{article['author']}", "jobTitle": "{article['author_role']}"}},
"publisher": {{"@type": "Organization", "name": "Landvex", "logo": {{"@type": "ImageObject", "url": "https://landvex.com/apple-touch-icon.png"}}}},
"datePublished": "{article['date']}",
"dateModified": "{article['date']}",
"mainEntityOfPage": {{"@type": "WebPage", "@id": "https://landvex.com/insights/{article['slug']}/"}}
}}
</script>
<style>
body {{ font-family: -apple-system, BlinkMacSystemFont, 'Inter', sans-serif; line-height: 1.6; max-width: 760px; margin: 0 auto; padding: 40px 20px; color: #1a1a1a; }}
h1 {{ font-size: 2.5rem; font-weight: 700; margin-bottom: 0.5em; line-height: 1.2; }}
h2 {{ font-size: 1.5rem; font-weight: 600; margin-top: 2em; margin-bottom: 0.5em; }}
h3 {{ font-size: 1.2rem; font-weight: 600; margin-top: 1.5em; margin-bottom: 0.5em; }}
p {{ margin-bottom: 1.2em; }}
.meta {{ color: #666; font-size: 0.9rem; margin-bottom: 2em; }}
.cta {{ background: #f5f5f5; padding: 24px; border-radius: 8px; margin: 2em 0; }}
.cta a {{ color: #0066cc; text-decoration: none; font-weight: 600; }}
ul, ol {{ margin-bottom: 1.2em; padding-left: 1.5em; }}
li {{ margin-bottom: 0.5em; }}
strong {{ font-weight: 600; }}
blockquote {{ border-left: 3px solid #0066cc; padding-left: 1em; margin: 1.5em 0; color: #444; font-style: italic; }}
table {{ width: 100%; margin: 1.5em 0; border-collapse: collapse; }}
th, td {{ padding: 8px; text-align: left; border-bottom: 1px solid #ddd; }}
th {{ border-bottom: 2px solid #333; font-weight: 600; }}
.sources {{ background: #f8f9fa; padding: 24px; border-radius: 8px; margin: 2em 0; border-left: 4px solid #0066cc; }}
.sources h2 {{ margin-top: 0; font-size: 1.3rem; }}
.sources ul {{ margin-bottom: 0; }}
.sources li {{ margin-bottom: 0.8em; }}
footer {{ margin-top: 4em; padding-top: 2em; border-top: 1px solid #eee; font-size: 0.85rem; color: #666; }}
</style>
</head>
<body>
<article>
<h1>{article['title']}</h1>
<p class="meta">Published {article['date']} · By {article['author']}, {article['author_role']} · Decision Intelligence</p>
{content}
<div class="sources">
{SOURCES}
</div>
<div class="cta">
<strong>What decision are you trying to make?</strong><br>
<a href="/enterprise/">Request a pilot →</a>
</div>
</article>
<footer>
<p><strong>Related:</strong> <a href="/methodology/">Our Methodology</a> · <a href="/insights/">All Insights</a></p>
</footer>
</body>
</html>"""
# Write output
output_path = f"/tmp/landvex-articles/{article['slug']}/index.html"
with open(output_path, 'w') as f:
f.write(html)
print(f"✅ Generated article #{article['num']}: {article['title']}")
print(f" Topic: {article['topic']}")
print(f" Output: {output_path}")
PYTHON_EOF
echo ""
echo "📤 Uploading to S3..."
unset AWS_ACCESS_KEY_ID AWS_SECRET_ACCESS_KEY AWS_SESSION_TOKEN
export AWS_DEFAULT_REGION=eu-north-1
aws s3 cp "$TMP_DIR/$SLUG/index.html" "s3://landvex-prod/insights/$SLUG/index.html" --content-type "text/html" --cache-control "max-age=3600"
echo ""
echo "🔄 Invalidating CloudFront..."
INVALIDATION=$(aws cloudfront create-invalidation --distribution-id E2M3J95HLUR89H --paths "/insights/$SLUG/*" --query 'Invalidation.Id' --output text)
echo ""
echo "✅ Article $ARTICLE_NUM published successfully!"
echo " Title: $TITLE"
echo " URL: https://landvex.com/insights/$SLUG/"
echo " CloudFront Invalidation: $INVALIDATION"